/****************************************************************************/
/* Copyright 2003 - 2016 MBARI												*/
/****************************************************************************/
/* Summary	: Data Logging Routines for OASIS5 Mooring Controller			*/
/* Filename : log.c															*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS Mooring Replacement (OASIS3)							*/
/* $Revision: 1.14 $														*/
/* Created	: 05/24/2016													*/
/*																			*/
/* 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:													*/
/* 30jan2003 rah - created from OASIS log.c									*/
/* 24may2016 rah - Oasis5 version with LogProtocol3 created from Oasis3 version*/
/* $Log: log.c,v $
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <custom.h>						/* DISK definition					*/
#include <log.h>						/* Log record definitions			*/
#include <drvr.h>						/* OASIS driver functions			*/
#include <fatFs/ff.h>					/* FatFs file system defns			*/
#include <file.h>						/* OASIS File I/O Routines			*/
#include <otask.h>						/* OASIS task dispatcher			*/
#include <utils.h>						/* OASIS utility functions			*/
#include <usrcmds.h>					/* User Command function definitions*/
#include <malloc.h>						/* OASIS malloc routines			*/
#include <timer.h>
#include <syslog.h>						/* System logger					*/
#include <debug.h>

#include <FreeRTOS.h>					/* FreeRTOS definitions				*/
#include <istdio.h>						/* Standard I/O library				*/
#include <string.h>						/* String library functions			*/

//#define CHUNK_TEST

#define MAX_UNFLUSHED_RECS		10


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

const char *logParmDesc[] = 
	{"Recs/iteration", "Delay before exit", NULL, NULL, NULL, NULL, NULL, NULL};

const char *logbinParmDesc[] = 
	{"Recs/iteration", "Delay before exit", "Record size", NULL, NULL, NULL, NULL, NULL};

const DrvDesc logDrvDesc =
	{ "Logger", log_drv, nullWakeFunc, LS_NULL, logParmDesc,
	  0, NO_SERIAL, 0, 0, 0, LOG_ASCII, 0, 1, 100, 0, 0};
  
const DrvDesc logbinDrvDesc =
	{ "BinLogger", logbin_drv, nullWakeFunc, LS_NULL, logbinParmDesc,
	  0, NO_SERIAL, 0, 0, 0, LOG_BIN, 0, 1, 100, 0, 256};
  
const DrvDesc fileLogDrvDesc =
	{ "FileLogger", file_drv, nullWakeFunc, LS_NULL, logParmDesc,
	  0, NO_SERIAL, 0, 0, 0, LOG_ASCII, 0, 1, 100, 0, 0};
  

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

MLocal LogRecNum		nextFreeLog;	/* Where next log record will go*/
MLocal LogRecNum		oldestLog;		/* Oldest log record found		*/
MLocal Nat32			freeKB;			/* Free space left on disk in KB*/
MLocal Int32			freeRemainder;	/* Remainder (0-1KB)			*/

MLocal char				*filepath = FILEPATH;

/* File Descriptors for writing to, reading from log files		*/
MLocal LogFileDesc		writeLogDesc = {0, 0, FALSE};
MLocal LogFileDesc		readLogDesc = {0, 0, FALSE};

#ifdef CHUNK_TEST
MLocal char *tstString =
"This is a very long test string.\n"
"It is used for testing data logging of records longer than LogRec allows.\n"
"To use it, change #define LOG_CHUNK in log.h\n"
"to something smaller than than the size of this string.\n"
"I use #define LOG_CHUNK 32 for this testing.\n";
#endif


/************************************************************************/
/* Function	   : log_init												*/
/* Purpose	   : Initialize logging routines							*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno log_init(void)
{
	file_findLogFiles(&nextFreeLog, &oldestLog);
										/* Continue existing logs		*/
	initUsrLog(nextFreeLog);			/* Init the user download ptr	*/
	return(OK);

} /* log_init() */


/************************************************************************/
/* Function	   : updateFreeSpace										*/
/* Purpose	   : Get Free Space from FatFs, save in freeKB & freeRemainder*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/* Comments	   : Updates freeKB and freeRemainder						*/
/************************************************************************/
MLocal void updateFreeSpace(void)
{
	Nat32	freeClust;
	FATFS	*fs;

	if (f_getfree("", &freeClust, &fs) == FR_OK)
	{
		freeKB = freeClust * fs->csize / 2;
		freeRemainder = 0;
	}

} /* updateFreeSpace() */


/************************************************************************/
/* Function	   : decFreeSpace											*/
/* Purpose	   : Decrement amount of free space remaining				*/
/* Inputs	   : Number of bytes to decrement							*/
/* Outputs	   : None													*/
/* Comments	   : Updates freeKB and freeRemainder						*/
/************************************************************************/
MLocal void decFreeSpace(Int32 size)
{
	freeKB -= size/1024;
	freeRemainder -= size%1024;

	while (freeRemainder < 0)
	{
		freeRemainder += 1024;
		freeKB--;
	}

} /* decFreeSpace() */


/************************************************************************/
/* Function	   : closeFileDesc											*/
/* Purpose	   : Close files in a LogFileDesc							*/
/* Inputs	   : Ptr to LogFileDesc										*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void closeFileDesc(LogFileDesc *lfd)
{
	FSIZE_t	idxSize;

	if (lfd->isOpen)
	{
		f_close(&lfd->datFP);

		idxSize = f_tell(&lfd->idxFP);
		f_close(&lfd->idxFP);
		lfd->firstUnflushedRec = (LogRecNum)(idxSize/sizeof(Nat32));
	}

	lfd->isOpen = FALSE;

} /* closeFileDesc() */


/************************************************************************/
/* Function	   : deleteOldestBlock										*/
/* Purpose	   : Delete the files associated with oldest Log block		*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/* Comment	   : Assumes caller owns fileSem							*/
/************************************************************************/
MLocal Errno deleteOldestBlock(void)
{								/* Not enough room.	 Delete oldest file*/
	Nat32	oldFreeKB;
	char	filename[FILENAME_SIZE];

	if (BlkNum(oldestLog) >= writeLogDesc.blk)	/* If oldest file is same*/
		return(ERROR);							/* as we're writing, error*/

	isnprintf(filename, sizeof(filename), DATA_FILEFMT, BlkNum(oldestLog));
	oldFreeKB = freeKB;
	f_unlink(filename);

	isnprintf(filename, sizeof(filename), INDEX_FILEFMT, BlkNum(oldestLog));
	f_unlink(filename);

	oldestLog = (oldestLog + RECS_PER_BLK) & LOG_BLK_MASK;

	updateFreeSpace();

	sysLogPrintf("Deleting %s, Free space was %u KB now %u KB",
				 filename, oldFreeKB, freeKB);
	return(OK);

} /* deleteOldestBlock() */


/************************************************************************/
/* Function	   : retryOpenError											*/
/* Purpose	   : Retry a failed open, print error message				*/
/* Inputs	   : File name with error, open mode						*/
/* Outputs	   : FILE ptr to newly opened file, or NULL					*/
/************************************************************************/
MLocal FRESULT retryOpenError(FIL *fp, char *filename, FRESULT err, BYTE mode)
{
	int tmpErrno = errno;

	xprintf("Error opening file %s for %s:  ", 
			filename, (mode == FA_READ) ? "reading" : "writing");

	fperr(err, NULL);
	newline();

	if (err == FR_DENIED)				/* I think this is what's returned*/
	{									/* if disk full.  Verify?		  */
		deleteOldestBlock();
		return(f_open(fp, filename, mode));
	}

	return(err);

} /* retryOpenError() */


/************************************************************************/
/* Function	   : openFileDesc											*/
/* Purpose	   : Open files in a LogFileDesc							*/
/* Inputs	   : Ptr to LogFileDesc, LogPtr for blk/rec to open,		*/
/*				 Boolean = TRUE to write								*/
/* Outputs	   : OK or ERROR											*/
/* Comment	   : Fills in LogFileDesc if successful						*/
/************************************************************************/
MLocal Errno openFileDesc(LogFileDesc *lfd, LogPtr lp, MBool toWrite)
{
	Nat32	idxSize;
	FRESULT ferr;
	BYTE	mode;
	char	fileName[FILENAME_SIZE];
	LogBlk blk = BlkNum(lp);

	if ((blk == lfd->blk) && lfd->isOpen)
		return(OK);						/* Files already open to right blk*/

#ifdef DEBUG_LOG
	dprintf("openFileDesc: opening blk %u for record %u, lfd->blk = %u\r\n",
			blk, lp, lfd->blk);
#endif
	closeFileDesc(lfd);

	if (toWrite)
	{
		if (RecOffset(lp))
			mode = (BYTE)(FA_WRITE | FA_OPEN_APPEND);
		else
			mode = (BYTE)(FA_WRITE | FA_CREATE_ALWAYS);
		
		updateFreeSpace();
	}
	else
		mode = (BYTE)(FA_READ | FA_OPEN_EXISTING);
	
	isprintf(fileName, DATA_FILEFMT, blk);
	if (f_open(&lfd->datFP, fileName, mode) != FR_OK)
		if ((ferr = retryOpenError(&lfd->datFP, fileName, ferr, mode)) != FR_OK)
		{
			fperr(ferr, "Failed to open log data file");
			return(ERROR);
		}

	isprintf(fileName, INDEX_FILEFMT, blk);

	if (f_open(&lfd->idxFP, fileName, mode) != FR_OK)
		if ((ferr = retryOpenError(&lfd->idxFP, fileName, ferr, mode)) != FR_OK)
		{
			f_close(&lfd->datFP);
			lfd->isOpen = FALSE;
			fperr(ferr, "Failed to open log index file");
			return(ERROR);
		}

	lfd->isOpen = TRUE;
	lfd->blk = blk;

	idxSize = f_tell(&lfd->idxFP);
	lfd->firstUnflushedRec = (LogRecNum)(idxSize/sizeof(Nat32));
	return(OK);

} /* openFileDesc() */


/************************************************************************/
/* Function	   : logDoFlush												*/
/* Purpose	   : Local version of logFlush, doesn't take fileSem		*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void logDoFlush(void)
{
#ifdef DEBUG_LOG
	dprintf("logFlush()\r\n");
#endif
	closeFileDesc(&writeLogDesc);
	closeFileDesc(&readLogDesc);

} /* logDoFlush() */


/************************************************************************/
/* Function	   : logFlush												*/
/* Purpose	   : Flush Log Files to disk								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void logFlush(void)
{
	takeFileSem();
	logDoFlush();
	giveFileSem();

} /* logFlush() */


/************************************************************************/
/* Function	   : logClear												*/
/* Purpose	   : Erase all log files									*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void logClear(void)
{
	logFlush();									/* Close the log files	*/
	dirScan("OAS*.DAT", dirFileDelete, NULL);	/* Erase log files		*/
	dirScan("OAS*.IDX", dirFileDelete, NULL);
	nextFreeLog = 0;							/* Clear nextFreeLog	*/
	oldestLog = 0;								/* Clear oldestLog		*/		
	initUsrLog(0);								/* Start downloads at 0 */

} /* logClear() */


/************************************************************************/
/* Function	   : isLogged												*/
/* Purpose	   : Determine whether a given record number is in log memory*/
/* Inputs	   : Log Pointer											*/
/* Outputs	   : LogRtn code											*/
/************************************************************************/
LogRtn isLogged(LogPtr lp)
{
	if (lp >= nextFreeLog)				/* If record nmbr too high,		*/
		return(LOG_TOO_BIG);			/*	not logged yet				*/

	if (lp < oldestLog)					/* If record nmbr too low,		*/
		return(LOG_TOO_SMALL);			/*	file was already erased		*/

	return(LOG_OK);						/* Record is there (somewhere)	*/

} /* isLogged() */


/************************************************************************/
/* Function	   : logRead												*/
/* Purpose	   : Helper routine, does the actual work for logGetRec		*/
/* Inputs	   : LogPtr get record from, ptr to put rcd,				*/
/*				 offset into log data									*/
/* Outputs	   : Number of bytes found, ERROR, or NO_DATA				*/
/************************************************************************/
MLocal Int32 logRead(LogPtr lp, LogRec *logp, Nat16 offset)
{
	Nat32		fpos;
	UINT		br;
	Nat16		len;
	int			rtn;

	if (openFileDesc(&readLogDesc, lp, FALSE) != OK)
		return(ERROR);					/* If can't open log file, abort*/

	/* go to the index record */
	if (f_lseek(&readLogDesc.idxFP, RecOffset(lp) * sizeof(Nat32)) != FR_OK)
		return(NO_DATA);

	/* read the index (offset) from the index file*/
	if ((f_read(&readLogDesc.idxFP, &fpos, sizeof(Nat32), &br) != FR_OK) ||
		(br != sizeof(Nat32)))
		return(NO_DATA);
	
	/* locate the record in the data file */
	if (f_lseek(&readLogDesc.datFP, fpos) != FR_OK)
		return(ERROR);

	/* read the log header */
	if ((f_read(&readLogDesc.datFP, logp, LOGHDR_SIZE, &br) != FR_OK) ||
		(br != LOGHDR_SIZE))
		return(ERROR);

	/* check for sync character and record number */
	if ((logp->log_hdr.log_syncc != LOG_SYNC) || 
		(getSwapLong(&logp->log_hdr.log_rcd) != lp))
		return(ERROR);

	/* if offset not 0, go to offset; fail if unsuccessful */
	if (offset)
		if ((rtn = f_lseek(&readLogDesc.datFP, fpos + LOGHDR_SIZE + (Nat32)offset)) != FR_OK)
			return(ERROR);

	/* read the total record len; error out if it's less than the specified offset */
	if ((len = getSwapWord(&logp->log_hdr.log_len)) <= offset)
		return(ERROR);

	/* calculate bytes remaining in this record */
	len -= offset;
  
	/* if more than LOG_CHUNK bytes, just read LOG_CHUNK bytes */
	if (len > LOG_CHUNK)
		len = LOG_CHUNK;

	if (f_read(&readLogDesc.datFP, logp->log_data, (UINT)len, &br) != FR_OK)
		return(ERROR);
	
	return(br);

} /* logRead() */


/************************************************************************/
/* Function	   : logGetRec												*/
/* Purpose	   : Get a (possibly partial) Record from Logging Memory	*/
/* Inputs	   : LogPtr get record from, ptr to put rcd, offset into log data*/
/* Outputs	   : Number of bytes found, ERROR, or NO_DATA				*/
/* Comments	   : Gets record pointed to by lp							*/
/*				 If invalid, searches for record number lp->lp_rcd.		*/
/*				 Updates lp to point to next record.					*/
/*				 Writes to *lp and *logp, even if returns FALSE.		*/
/************************************************************************/
Int32 logGetRec(LogPtr lp, LogRec *logp, Nat16 offset)
{
	Int32			rtn;

#ifdef DEBUG_LOG
	dprintf("logGetRec: rec=%u offset=%u\r\n", lp, offset);
#endif
	if ( isLogged(lp) != LOG_OK )
		return(NO_DATA);

	takeFileSem();

	if ((BlkNum(lp) == writeLogDesc.blk) &&
		(RecOffset(lp) >= writeLogDesc.firstUnflushedRec))
		logDoFlush();		/* If reading an unflushed record, flush the files*/

	rtn = logRead(lp, logp, offset);
	
	giveFileSem();

#ifdef DEBUG_LOG
	dprintf("logGetRec: rtn=%d\r\n",rtn);
#endif
	return(rtn);

} /* logGetRec() */


/************************************************************************/
/* Function	   : logWrite												*/
/* Purpose	   : Write data to Log Data and Index files					*/
/* Inputs	   : Log Record Header, Data ptrs							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno logWrite(LogRecHdr *hdrp, Byte *datp)
{
	Nat32			fpos, i;
	UINT			bw;
	FRESULT			fres;
	LogRecHdr		logHdr;				/* Copy of the header			*/

	if (openFileDesc(&writeLogDesc, nextFreeLog, TRUE) != OK)
		return(ERROR);					/* If can't open log file, abort*/

	if (freeKB < MIN_FREE_KB)			/* Not enough room.	 Delete oldest file*/
		if (deleteOldestBlock() == ERROR)
			return(ERROR);

	logHdr.log_syncc = LOG_SYNC;				/* Insert sync chr		*/
	logHdr.log_version = LOG_PROTOCOL3;			/* Insert version 3 id	*/

	if (strlen(hdrp->log_instrumentName) == 0)
		strcpy(logHdr.log_instrumentName, "Unknown");
	else
		strncpy(logHdr.log_instrumentName,
				hdrp->log_instrumentName, LOG_NAMESIZE);
	for (i = strlen(logHdr.log_instrumentName)+1; i < LOG_NAMESIZE; i++)
		logHdr.log_instrumentName[i] = ' ';

	logHdr.log_type = swapWord(hdrp->log_type);	/* Copy the type		*/
	logHdr.log_rcd = swapLong(nextFreeLog);		/* Insert record number */
	logHdr.log_time = swapLong(clkTime());		/* Insert time			*/
	logHdr.log_len = swapWord(hdrp->log_len + 1); /* Insert length		*/

	fpos = f_tell(&writeLogDesc.datFP);
	decFreeSpace(hdrp->log_len + sizeof(LogRecHdr) + 1 + sizeof(Nat32));

	if (((fres = f_write(&writeLogDesc.datFP, &logHdr, LOGHDR_SIZE, &bw)) != FR_OK)
		|| (bw != LOGHDR_SIZE))
		return(fres);
	
	if (((fres = f_write(&writeLogDesc.datFP, datp, hdrp->log_len, &bw)) != FR_OK)
		|| (bw != hdrp->log_len))
		return(fres);

	if (f_putc(LOG_TRAILSYNC, &writeLogDesc.datFP) == EOF)
		return(EOF);
	
	if (((fres = f_write(&writeLogDesc.idxFP, &fpos, sizeof(Nat32), &bw)) != FR_OK)
		|| (bw != sizeof(Nat32)))
		return(fres);
	
	return(OK);
  
} /* logWrite() */


/************************************************************************/
/* Function	   : logPutRec												*/
/* Purpose	   : Put a Record into Logging Memory						*/
/* Inputs	   : Ptr to Log Hdr, Ptr to data							*/
/* Outputs	   : None													*/
/************************************************************************/
void logPutRec(LogRecHdr *hdrp, Byte *datp)
{
	FRESULT		fres;
	Driver		*dp;

#ifdef DEBUG_LOG
	dprintf("logPutRec: rec=%u\r\n", nextFreeLog);
#endif
	takeFileSem();

	if ((fres = logWrite(hdrp, datp)) == OK)	/* Write dat & idx files*/
	{											/* If OK,				*/
		dp = (Driver *)pvTaskGetThreadLocalStoragePointer(NULL, DrvrP);
		if (dp != NULL)
			dp->drv_lastlog = nextFreeLog;		/* Save last logged rcd	*/

		nextFreeLog++;							/* Increment rcd number */

		if (RecOffset(nextFreeLog) == 0)		/* If log file full,	*/
		{							
			closeFileDesc(&writeLogDesc);		/*	close the output file*/
			if (readLogDesc.blk == writeLogDesc.blk) /* If read file same,*/
				closeFileDesc(&readLogDesc);	/*	close it to flush	*/
		}
		else if ((Int32)(RecOffset(nextFreeLog) - writeLogDesc.firstUnflushedRec)
				 > MAX_UNFLUSHED_RECS)
			logDoFlush();
	}
	else
	{											/* If error on file write*/
		sysLogPrintf("Error in logPutRec, errno = %d\n", fres);
		closeFileDesc(&writeLogDesc);			/*	close files and return*/
	}

	giveFileSem();

} /* logPutRec() */


/************************************************************************/
/* Function	   : logGetCurrentLogPtrs									*/
/* Purpose	   : Get pointer to next record to log, oldest record		*/
/* Inputs	   : Ptrs to next rcd ptr, oldest rcd ptr					*/
/* Outputs	   : Pointer to next record to log							*/
/************************************************************************/
LogRecNum logGetCurrentLogPtrs(LogRecNum *newest, LogRecNum *oldest)
{
	if (newest != NULL)
		*newest = nextFreeLog;
	if (oldest != NULL)
		*oldest = oldestLog;
	return(nextFreeLog);

} /* logGetCurrentLogPtrs() */


/************************************************************************/
/* Function	   : logError												*/
/* Purpose	   : Log an error											*/
/* Inputs	   : Error vector											*/
/* Outputs	   : None													*/
/************************************************************************/
void logError(Word vect)
{
	LogRecHdr		rechdr;			/* Logging record header			*/
	Word			swappedVect;

	strcpy(rechdr.log_instrumentName, "ERROR");
	rechdr.log_type = (Nat16)LOG_BIN;
	rechdr.log_len = sizeof(Word);
	swappedVect = swapWord(vect);
	logPutRec(&rechdr, (Byte *)(&swappedVect));

} /* logError() */


/************************************************************************/
/* Function	   : logOasisStatus											*/
/* Purpose	   : Log OASIS on/off status								*/
/* Inputs	   : Status: 1 for on, 0 for off							*/
/* Outputs	   : None													*/
/************************************************************************/
void logOasisStatus(Word onoff)
{
	LogRecHdr		rechdr;			/* Logging record header			*/
	Word			swappedWord;

	strcpy(rechdr.log_instrumentName, "OASIS_STATUS");
	rechdr.log_type = (Nat16)LOG_BIN;
	rechdr.log_len = sizeof(Word);
	swappedWord = swapWord(onoff);
	logPutRec(&rechdr, (Byte *)(&swappedWord));

} /* logOasisStatus() */


/************************************************************************/
/* Function	   : log_drv												*/
/* Purpose	   : Test Driver that simply logs comments					*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
void log_drv(Driver *dp)
{
	LogRecHdr		hdr;
	Nat32			cntr;
#ifndef CHUNK_TEST
	char			buff[48];
#endif

	for ( cntr = 0; cntr < dp->drv_parms[PARM0]; cntr++ )
	{
		strncpy(hdr.log_instrumentName, dp->drv_name, sizeof(hdr.log_instrumentName));
		hdr.log_type = dp->drv_parms[SAMPLE_TYPE];
#ifdef CHUNK_TEST
		hdr.log_len = strlen(tstString);
		logPutRec(&hdr, tstString);
#else
		hdr.log_len = isnprintf(buff, sizeof(buff), "Test Logger iteration %lu cnt %lu\n",
								dp->drv_cnt, cntr);
		logPutRec(&hdr, (Byte *)buff);
#endif
		dispatch();
	}

	if (dp->drv_parms[PARM1] > 0)
		task_delay(dp->drv_parms[PARM1]);
	dp->drv_cnt++;

} /* log_drv() */


/************************************************************************/
/* Function	   : logbin_drv												*/
/* Purpose	   : Test Driver that logs dummy binary data				*/
/* Inputs	   : Driver Pointer											*/
/* Outputs	   : None													*/
/************************************************************************/
void logbin_drv(Driver *dp)
{
	LogRecHdr		hdr;
	Nat32			cntr, recLen, i;
	Byte			*buff;

	recLen = dp->drv_parms[PARM2];
	if (recLen < 32)
		recLen = 32;
	if (recLen > MAXLOGSIZE)
		recLen = MAXLOGSIZE;
	buff = malloc(recLen);

	for ( cntr = 0; cntr < dp->drv_parms[PARM0]; cntr++ )
	{
		strncpy(hdr.log_instrumentName, dp->drv_name, sizeof(hdr.log_instrumentName));
		hdr.log_type = dp->drv_parms[SAMPLE_TYPE];
		for (i = 0; i < recLen; i++)
			buff[i] = (Byte)(i & 0xff);

		hdr.log_len = recLen;
		logPutRec(&hdr, buff);
		dispatch();
	}

	free(buff);
	if (dp->drv_parms[PARM2] > 0)
		task_delay(dp->drv_parms[PARM1]);
	dp->drv_cnt++;

} /* logbin_drv() */


/************************************************************************/
/* Function    : file_drv												*/
/* Purpose     : Test Driver that simply writes to an external file		*/
/* Inputs      : Driver Pointer											*/
/* Outputs     : None													*/
/************************************************************************/
void file_drv(Driver *dp)
{
    Nat32	cntr;
    FIL		fp;

    for ( cntr = 0; cntr < dp->drv_parms[PARM0]; cntr++ )
    {
		takeFileSem();
		if (f_open(&fp, "TEST.LOG", (BYTE)(FA_WRITE | FA_READ | FA_OPEN_APPEND)) == FR_OK)
		{
			f_printf(&fp, "File Logger iteration %u cnt %u\r\n", dp->drv_cnt, cntr);
			f_close(&fp);
		}
		giveFileSem();
		dispatch();
    }

    if (dp->drv_parms[PARM1] > 0)
		task_delay(dp->drv_parms[PARM1]);
    dp->drv_cnt++;

} /* file_drv() */
