/****************************************************************************/
/* Copyright 2003 MBARI														*/
/****************************************************************************/
/* Summary	: Driver Management Routines for OASIS3 Mooring Controller		*/
/* Filename : drvr.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS Mooring Replacement (OASIS3)							*/
/* $Revision : 4.4 $														*/
/* Created	: 01/07/2003													*/
/*																			*/
/* 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:													*/
/* 07jan2003 rah - created from OASIS drvr.c								*/
/* $Log: drvr.c,v $
/* Revision 1.1	 2011/10/25 22:39:22  bobh
/* Initial oasis4 revision
/*
/* Revision 1.8	 2011/02/24 00:00:11  bobh
/* Changes to: support calling drivers from scripts, better diagnostics for scripts, driver mods
/*
/* Revision 1.7	 2011/02/18 18:13:44  bobh
/* Added serialAtoD driver and misc changes for OA Moorings
/*
/* Revision 1.6	 2010/10/25 16:42:50  bobh
/* Changes to support scripting
/*
/* Revision 1.5	 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.4	 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.3	 2009/04/27 22:49:43  bobh
/* First working version of remote serial ports
/*
/* 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.5	 2006/05/09 17:58:25  mrisi
/* fixed TOD mask bug
/*
/* Revision 1.4	 2003/09/04 22:26:26  bobh
/* no message
/*
/* 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>						/* Custom def's for deployment		*/
#include <debug.h>
#include <drvr.h>						/* OASIS driver functions			*/
#include <io.h>							/* OASIS I/O definitions			*/
#include <serial.h>						/* OASIS Serial I/O definitions		*/
#include <malloc.h>						/* OASIS malloc routines			*/
#include <log.h>						/* Log record definitions			*/
#include <task.h>						/* OASIS Multitasking definitions	*/
#include <utils.h>						/* OASIS utility functions			*/
#include <config.h>						/* OASIS Config file parser			*/
#include <clock.h>						/* OASIS Clock module				*/
#include <modbus.h>						/* Modbus protocol library			*/
#include <remote.h>						/* Remote I/O library				*/

#include <string.h>						/* String library					*/


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

Extern Nat16			serPort;		/* Default serial port				*/
Extern Word				serMode;		/* Default serial setup				*/
Extern Nat32			pwr_perm;		/* Devices to leave powered on		*/
Extern TimeOfDay		radio_time;		/* Last time user connected on radio*/
										/*	(set in userif.c, used here)	*/
Extern MBool			wakeOnConsoleChar;
Extern Nat16			sync_tmout;		/* Timeout for drvSync				*/
Extern Nat32			drvPwrBits;		/* Power bits turned on by drivers	*/
Extern MBool			syncAtMidnight;
Extern MBool			deploymentMode;
Extern MBool			allowDrvrConflict;


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

Global LstHead			drv_list;				/* Driver list header		*/


/************************************************************************/
/* Function	   : null_func												*/
/* Purpose	   : Null function											*/
/* Inputs	   : None													*/
/* Outputs	   : TRUE													*/
/************************************************************************/
	MBool
null_func( Void )
{
  return( TRUE );

} /* null_func() */


/************************************************************************/
/* Function	   : pwrBitOn												*/
/* Purpose	   : Turn on a (physical or virtual) power bit				*/
/* Inputs	   : Power bit number										*/
/* Outputs	   : OK or ERROR number										*/
/************************************************************************/
Errno pwrBitOn(Nat32 pwrnum)
{
	if (isPhysPwr(pwrnum))
	{ 
		drvPwrBits |= (1L << pwrnum);
		io_pwron(pwrnum);
		return(OK);
	}
	else		/* Virtual power port */
		return(remPower(pwrnum, TRUE));

} /* pwrBitOn() */


/************************************************************************/
/* Function	   : pwrBitOff												*/
/* Purpose	   : Turn off a (physical or virtual) power bit				*/
/* Inputs	   : Power bit number										*/
/* Outputs	   : OK or ERROR number										*/
/************************************************************************/
Errno pwrBitOff(Nat32 pwrnum)
{
	Nat32		pwrbit = (1L << pwrnum);

	if (pwrbit & pwr_perm)				/* If perm pwred, don't turn off*/
		return(OK);

	if (isPhysPwr(pwrnum))
	{
		drvPwrBits &= ~pwrbit;
		io_pwroff(pwrnum);
		return(OK);
	}
	else		/* Virtual power port */
		return(remPower(pwrnum, FALSE));

} /* pwrBitOff() */


/************************************************************************/
/* Function	   : drv_pwron												*/
/* Purpose	   : Turn on power for given driver							*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
	Void
drv_pwron( Driver *dp )
{
	pwrBitOn(dp->drv_parms[PWR_CTRL]);

} /* drv_pwron() */


/************************************************************************/
/* Function	   : drv_pwroff												*/
/* Purpose	   : Turn off power for given driver						*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drv_pwroff( Driver *dp )
{
	pwrBitOff(dp->drv_parms[PWR_CTRL]);

} /* drv_pwroff() */


/************************************************************************/
/* Function	   : drv_ser_port											*/
/* Purpose	   : Get access to serial port, init port					*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drv_ser_port( Driver *dp )
{
	Reg Nat16	virtPort;

	if ((virtPort = dp->drv_parms[SER_PORT]) == NO_SERIAL)
		return;

	serPort = virtPort;					/* Save our physical ser port	*/
	serMode = dp->drv_parms[SER_SETUP]; /* Save serial mode				*/

	if (isPhysSer(virtPort))
	{
	  getSerialPort(serPort);			/* Get serial semaphore			*/
	  io_serRelay(serPort, TRUE);		/* Select the serial port		*/
	  io_serEnable(serPort, RS232);		/* Enable the EIA driver		*/
	  serPortInit(serPort, dp->drv_parms[SER_BAUD], dp->drv_parms[SER_SETUP]);
	}
	else
	{
		if (remOpenVirtSer(virtPort, FALSE) != OK)
			return;

		remVirtSerInit(virtPort, dp->drv_parms[SER_BAUD],
					   dp->drv_parms[SER_SETUP]);
	}
	
	drv_pwron(dp);						/* Turn on device				*/

} /* drv_ser_port() */


/************************************************************************/
/* Function	   : drv_ser_release										*/
/* Purpose	   : Release access to serial port							*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drv_ser_release( Driver *dp )
{
	Reg Nat16	virtPort;

	if ((virtPort = dp->drv_parms[SER_PORT]) == NO_SERIAL)
		return;
	
	if (isPhysSer(virtPort))					/* If physical ser port	  */
	{
		xdrain_ser(400);						/* Wait for output to drain*/
		io_serRelay(virtPort, FALSE);			/* Release the serial port */

		if ((virtPort != CPU_SER) || (!wakeOnConsoleChar))
			io_serEnable(virtPort, COMMS_OFF);	/* Disable the EIA driver  */

		releaseSerialPort(virtPort);
	}
	else
		remCloseVirtSer(virtPort);

} /* drv_ser_release() */
						

/************************************************************************/
/* Function	   : drvr_find												*/
/* Purpose	   : Find a driver by name									*/
/* Inputs	   : Pointer to name string									*/
/* Outputs	   : Corresponding driver, or DRV_NULL if none				*/
/************************************************************************/
		Driver *
drvr_find( char *name )
{
  Reg Driver	*dp;

  for ( dp = (Driver *)(drv_list.lst_head); dp != DRV_NULL; dp = dp->drv_next )
	if ( cmp_ulc(name, dp->drv_name) != NULL )
	  return( dp );

  return( DRV_NULL );

} /* drvr_find() */


/************************************************************************/
/* Function	   : drvrChkName											*/
/* Purpose	   : Check validity of a driver name, fix if illegal chars	*/
/* Inputs	   : Name pointer											*/
/* Outputs	   : OK or ERROR											*/
/* Comments	   : Driver name becomes Instrument name in instr log		*/
/************************************************************************/
MLocal Errno drvrChkName(char *name)
{
	char		*p;
	Nat16		i;
	Errno		rtn = OK;

	for (i = 0, p = name; i < strlen(name); i++, p++)
		if (!(isalnum(*p) || (*p == '-') || (*p == '_') || (*p == '.')))
		{
			*p = '_';
			rtn = ERROR;
		}

	return(rtn);
}


/************************************************************************/
/* Function	   : drvr_create											*/
/* Purpose	   : Create a driver header									*/
/* Inputs	   : Ptr to driver descriptor								*/
/* Outputs	   : Driver pointer											*/
/* Comments	   : Adds it to the system driver list						*/
/************************************************************************/
		Driver *
drvr_create( const DrvDesc *ddp )
{
  Reg Driver	*dp;
  Reg Nat16		namelen;

  namelen = strlen( ddp->dd_name ) + 1;

  if ( (dp = (Driver *)malloc(sizeof(Driver) + namelen)) != DRV_NULL )
  {
	memset(dp, 0, sizeof(Driver));
	memcpy(&(dp->drv_name), ddp, sizeof(DrvDesc));
	dp->drv_name = (char *)(dp + 1);
	memcpy(dp->drv_name, ddp->dd_name, namelen);
	drvrChkName(dp->drv_name);
	dp->drv_wakeup = clk_read();		/* Set to sample now			*/
	dp->drv_flags = DO_INIT;
	dp->drv_td = NULLTID;				/* Init no driver tasks			*/
	list_add(&drv_list, (Node *)dp);
  }
  return( dp );

} /* drvr_create() */


/************************************************************************/
/* Function	   : drvr_pwrup												*/
/* Purpose	   : Re-Initialize driver module after powerup				*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
	Void
drvr_pwrup( Void )
{
  Reg Driver	*dp;

  for (dp = (Driver *)drv_list.lst_head; dp != DRV_NULL; dp = dp->drv_next)
	dp->drv_td = NULLTID;				/* Init no driver tasks			*/

} /* drvr_pwrup() */


/************************************************************************/
/* Function	   : checkDrvrConflicts										*/
/* Purpose	   : Look for conflicting declarations of serial, power ports*/
/*				 Warn user if any found									*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
	Void
checkDrvrConflicts(Void)
{
  Reg Driver	*dp, *dp2;
  Nat32			serport, pwrnum;

  for (dp = (Driver *)drv_list.lst_head; dp != DRV_NULL; dp = dp->drv_next)
  {
	serport = dp->drv_parms[SER_PORT];
	if (!allowDrvrConflict)
	{
		for (dp2 = dp->drv_next; dp2 != DRV_NULL; dp2 = dp2->drv_next)
		{
			if ((serport != NO_SERIAL) && (serport == dp2->drv_parms[SER_PORT]))
				xprintf("WARNING!  Drivers %s and %s have conflict on serial port %lu\n",
						dp->drv_name, dp2->drv_name, serport);
		
			if ((pwrnum = dp->drv_parms[PWR_CTRL]) != 0)
				if (pwrnum == dp2->drv_parms[PWR_CTRL])
					xprintf("WARNING!  Drivers %s and %s have conflict on power port %#lx\n",
							dp->drv_name, dp2->drv_name, pwrnum);
		}
	}
	if (modBusConfigured((Nat16)serport))
		xprintf("WARNING!  Driver %s has conflict with modbus port %lu\n",
				dp->drv_name, serport);
  }

  newline();

} /* checkDrvrConflicts() */


/************************************************************************/
/* Function	   : drvr_init												*/
/* Purpose	   : Initialize driver module								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
	Void
drvr_init( Void )
{
  list_init(&drv_list);					/* Init driver list				*/

  if ((parseConfigFile(CFG_FILE) != OK) || (list_count(&drv_list) == 0))
  {										/* Parse cfg file.	If bad,		*/
	list_init(&drv_list);				/*	add default drivers			*/
	parseAddDefaultDrivers();
  }

  drvr_pwrup();							/* Init semaphores				*/
  checkDrvrConflicts();					/* Look for driver conflicts	*/

} /* drvr_init() */


/************************************************************************/
/* Function	   : drvWakeupTime											*/
/* Purpose	   : Determine time until driver needs to wake up			*/
/* Inputs	   : Driver pointer											*/
/* Outputs	   : Seconds until driver wakeup, or 0 if wakeup now		*/
/************************************************************************/
		MLocal Nat32
drvWakeupTime( Reg Driver *dp )
{
  Reg Nat16		nexthr;
  TimeOfDay		tod = clk_read();

  if ( tod < dp->drv_wakeup )					/* If not time to run,	*/
	return( dp->drv_wakeup - tod );				/* return time to wakeup*/

										/* Time to wakeup now. Check flags*/
  if ( dp->drv_flags & DO_SYNC )				/* If SYNC flag on, run */
	return( 0 );								/*	 it now				*/

  if ( dp->drv_parms[INTERVAL] == 0 )			/* If driver turned off */
	return( INT32_MAX );						/*	 return Max Int32	*/

  if ( !deploymentMode &&						/* Check TOD mask		*/
	   (dp->drv_parms[TOD_MASK] & ((unsigned long)1 << clkLocalHour())) )
  {								/* If TOD mask on for this 1 hr period, */
								/*	   go to next hour					*/
	nexthr = 3600 - (tod % 3600);
	dp->drv_wakeup = tod + nexthr;
	return( nexthr );
  }

  return( 0 );									/* If none above, run now*/

} /* drvWakeupTime() */


/************************************************************************/
/* Function	   : drvr_exit												*/
/* Purpose	   : Driver exit routine passed to task_create				*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvr_exit( Driver *dp )
{
	if (dp != NULL)
	{
		dp->drv_td = NULLTID;
		if ( (dp->drv_flags & FLAG_PERM) == 0)
			dp->drv_flags = 0;
	}

} /* drvr_exit() */


/************************************************************************/
/* Function	   : drvr_sample											*/
/* Purpose	   : (Re)start any driver not already running that should be*/
/* Inputs	   : None													*/
/* Outputs	   : Seconds until next driver wakeup						*/
/* Comments	   : Will restart any driver not already running that has	*/
/*				 exipired its wakeup time.								*/
/*				 Returns 0 if any tasks still running, to keep main alive*/
/************************************************************************/
		Nat32
drvr_sample( Void )
{
  Reg Driver	*dp;					/* Cur drvr ptr					  */
  Reg Nat32		wtime, t;				/* Earliest wake time			  */
  TaskDesc		*td;
  Nat16			serPortSave;
  Word			serModeSave;
  TimeOfDay		tod = clk_read();

  if (syncAtMidnight)
	wtime = SECS_PER_DAY - (tod % SECS_PER_DAY);
  else
	wtime = INT32_MAX;

  for ( dp = (Driver *)drv_list.lst_head; dp != DRV_NULL; dp = dp->drv_next )
  {
	if ( dp->drv_td != NULLTID )				/* If anybody running,	   */
	  wtime = 0;								/*	 next wake is now	   */

	else
	{
	  if ( (t = drvWakeupTime(dp)) == 0 )		/* If time to wake up	   */
	  {											/*	start the driver	   */
		serPortSave = serPort;
		serPort = (Nat16)(dp->drv_parms[SER_PORT]);
		serModeSave = serMode;
		serMode = (Nat16)(dp->drv_parms[SER_SETUP]);

		td = (Tid)task_create(dp->drv_task, dp, dp->drv_name);
		dp->drv_td = td;
		td->td_drvr = dp;

		serPort = serPortSave;
		serMode = serModeSave;

		if (dp->drv_td != NULLTASK)
		{				/* Normal case is to sample at constant rate	   */
		  dp->drv_wakeup += dp->drv_parms[INTERVAL];
						/* If we've been off more than one interval,	   */
						/*	 restart timing from now					   */
		  if (dp->drv_wakeup <= tod)
			dp->drv_wakeup = tod + dp->drv_parms[INTERVAL];
		}
		else					/* If task_create fails, try again in 2 sec*/
		  dp->drv_wakeup = tod + 2;

#ifdef DEBUG_DRVR_TIMES
/* Be VERY careful with DEBUG_DRVR_TIMES.  It causes a thread dispatch(), which */
/* can cause re-entrancy problems.	Use only if you don't have a script which	*/
/* uses 'run_driver'.  To fully support this, it should have a semaphore.		*/
		xprintf("Sampling %s  ", dp->drv_name);
		clk_printtime(tod);
		clk_printtime(dp->drv_wakeup);
		xprintf("  tick = %lu\n", ioGetTick());
#endif
	  }

	  if ( t < wtime )							/* Save min wake time	   */
		wtime = t;
	}
  }

  return( wtime );

} /* drvr_sample() */


/************************************************************************/
/* Function	   : drvLogError											*/
/* Purpose	   : Log an error for a driver								*/
/* Inputs	   : Driver ptr												*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvLogError( Driver *dp, Errno err )
{
  LogRecHdr		rechdr;			/* Logging record header				*/
  Nat16			recbuff[2];

  strcpy(rechdr.log_instrumentName, "ERROR");
  rechdr.log_type = (Byte)LOG_ERROR;
  rechdr.log_len = sizeof(recbuff);
  recbuff[0] = swapWord((Nat16)dp->drv_parms[SAMPLE_TYPE]);
  recbuff[1] = swapWord(err);
  logPutRec( &rechdr, (Byte *)recbuff );

} /* drvLogError() */


/************************************************************************/
/* Function	   : drvLog													*/
/* Purpose	   : Default sample logging routine							*/
/* Inputs	   : Driver ptr, sample ptr, number bytes					*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvLog( Driver *dp, Byte *samplep, Int16 len )
{
  LogRecHdr		rh;

  strncpy(rh.log_instrumentName, dp->drv_name, sizeof(rh.log_instrumentName));
  rh.log_type = (Nat16)dp->drv_parms[SAMPLE_TYPE];

  dprintf("drvLog, name = %s\n", rh.log_instrumentName);

  if ( (rh.log_len = len) > 0 )
	logPutRec( &rh, (Byte *)samplep );
  else
	drvLogError( dp, NO_DATA );

} /* drvLog() */


/************************************************************************/
/* Function	   : drvLogWords											*/
/* Purpose	   : Default sample logging routine							*/
/* Inputs	   : Driver ptr, sample ptr, number words					*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvLogWords( Driver *dp, Nat16 *samplep, Int16 wrds )
{
  Reg Int16		i;

  for (i = 0; i < wrds; i++)
	samplep[i] = swapWord(samplep[i]);

  drvLog( dp, (Byte *)samplep, wrds * sizeof(Nat16) );

} /* drvLogWords() */


/************************************************************************/
/* Function	   : drvSerPortAndMalloc									*/
/* Purpose	   : Get serial port and malloc buffer						*/
/* Inputs	   : Driver Pointer, buffer size							*/
/* Outputs	   : Buffer ptr, or NULL if malloc fails					*/
/************************************************************************/
		char *
drvSerPortAndMalloc( Driver *dp, Nat16 size )
{
  char	*mallocPtr;

  drv_ser_port( dp );

  if ( (mallocPtr = malloc(size)) == NULL )
  {
	drv_ser_release( dp );
	drvLogError( dp, MALLOC_ERR );
  }

  return( mallocPtr );

} /* drvSerPortAndMalloc() */


/************************************************************************/
/* Function	   : drvSerReleaseAndFree									*/
/* Purpose	   : Release serial port and malloc buffer					*/
/* Inputs	   : Driver Pointer, buffer ptr								*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvSerReleaseAndFree( Driver *dp, char *buffer )
{
  drv_ser_release( dp );
  free( buffer );
	
} /* drvSerReleaseAndFree() */


/************************************************************************/
/* Function	   : drvSync												*/
/* Purpose	   : Sync to an External driver								*/
/* Inputs	   : Driver name											*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvSync( char *name )
{
  Driver		*dp;
  Nat16			cnt;

  if ( (name == NULL) || ((dp = drvr_find(name)) == DRV_NULL) )
		return;

  dp->drv_wakeup = clk_read();
  dp->drv_flags |= DO_SYNC;
	
  for ( cnt = sync_tmout;
		(cnt > 0) && (dp->drv_flags & DO_SYNC); cnt-- )
	task_delay( TICKS_PER_SECOND );

} /* drvSync() */


/************************************************************************/
/* Function	   : drvr_timechange										*/
/* Purpose	   : Notify driver module that new time has been set		*/
/* Inputs	   : Amount that time changed								*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvr_timechange( Int32 diff )
{
  Reg Driver	*dp;					/* Driver pointer				*/

  for (dp = (Driver *)drv_list.lst_head; dp != DRV_NULL; dp = dp->drv_next)
	dp->drv_wakeup += diff;

  radio_time += diff;

} /* drvr_timechange() */


/************************************************************************/
/* Function	   : drvMidnightSync										*/
/* Purpose	   : Do midnight synchronization of drivers					*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
drvMidnightSync( Void )
{
  Reg Driver	*dp;					/* Driver ptr					   */
  static Byte	didSync = TRUE;			/* Did midnight sync				*/

  if ( clkLocalHour() != 0 )			/* See if we just passed midnight  */
	didSync = FALSE;					/* If no, reset didSync var		   */
  else if ( !didSync )					/* If yes, did we already sync?	   */
  {
	didSync = TRUE;						/* Do just once per day			   */
	deploymentMode = FALSE;				/* Turn off deployment mode		   */

	for (dp = (Driver *)drv_list.lst_head; dp != DRV_NULL;
		 dp = dp->drv_next)
	  if ( dp->drv_parms[INTERVAL] )
	  {
		dp->drv_wakeup = clk_read();
		dp->drv_flags |= (DO_SYNC | DO_ARGOS);
	  }
  }
} /* midnightSync() */
