/************************************************************************/
/* Copyright 2010-2016 MBARI											*/
/************************************************************************/
/* Summary	: Script Interpreter for Oasis5								*/
/* Filename : script.c													*/
/* Author	: Robert Herlien (rah)										*/
/* Project	: Oasis5 Mooring Controller									*/
/* Revision : 1.0														*/
/* Created	: 09/08/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:												*/
/* 05oct2010 rah - created Oasis4 version								*/
/* 8sep2016 rah - created Oasis5 version from Oasis4 version			*/
/************************************************************************/

#include <mbariTypes.h>			/* MBARI type definitions				*/
#include <mbariConst.h>			/* MBARI constants						*/
#include <oasis.h>				/* OASIS controller definitions			*/
#include <olist.h>				/* Linked list library					*/
#include <file.h>				/* OASIS File I/O routines				*/
#include <script.h>				/* Scripting Language definitions		*/
#include <parse.h>				/* Generic parser declarations			*/
#include <variable.h>			/* Variable definitions					*/
#include <syslog.h>				/* Respirometer system logger			*/
#include <debug.h>
#include <task.h>
#include <drvr.h>				/* OASIS driver functions				*/
#include <remote.h>				/* Remote I/O library					*/
#include <utils.h>
#include <timer.h>				/* OASIS Clock module					*/

#include <string.h>				/* Standard string library				*/
#include <ctype.h>				/* Standard character library			*/
#include <stdarg.h>
#include <time.h>

#ifdef SIMULATE
#include <stdio.h>				/* Standard I/O library					*/
#else
#include <istdio.h>				/* Our integer standard I/O library		*/
#include <fatFs/ff.h>			/* FatFs file system defns				*/
#endif

#ifdef SIMULATE
#define sprintf			isprintf
#define FIL				FILE
#define f_close(fp)		fclose(fp)
#define f_gets(p,len,fp) fgets(p,len,fp)
#define f_lseek(fp,ofs)	 fseek(fp,offs,SEEK_SET)
#endif


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

Extern Nat32			scriptDebug;
Extern RemIOPort		pwrPorts[];
Extern RemIOPort		outPorts[];


/****************************************/
/*		Script Function Declarations	*/
/****************************************/

MLocal Errno scrSleep(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno scrEcho(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd);
MLocal Errno scrSysLog(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno scrRepeat(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd);
MLocal Errno scrRepeatInt(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd);
MLocal Errno scrForever(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd);
MLocal Errno scrNext(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd);
MLocal Errno pulseOn(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno pulseOff(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno outputOn(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno outputOff(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno powerOn(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno powerOff(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno flushCycle(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno flush2Cycle(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno flushPurge2Cycle(Int32 argc, char **argv, MBool syntaxOnly);
Extern Errno logModBus(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno waitIntervalRemainder(Int32 argc, char **argv,
								   MBool syntaxOnly, ScriptDesc *sd);
MLocal Errno runDriver(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno waitDriver(Int32 argc, char **argv, MBool syntaxOnly);
MLocal Errno waitVariable(Int32 argc, char **argv, MBool syntaxOnly);


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

MLocal LstHead	scrList;

MLocal ScriptFunc sFuncTbl[] =
{
	{"sleep", scrSleep}, {"delay", scrSleep}, {"wait", scrSleep},
	{"echo", scrEcho}, {"syslog", scrSysLog},
	{"repeat", scrRepeat}, {"repeat_interval", scrRepeatInt},
	{"forever", scrForever}, {"next", scrNext},
	{"pulse_on", pulseOn}, {"pulse_off", pulseOff},
	{"output_on", outputOn}, {"output_off", outputOff},
	{"power_on", powerOn}, {"power_off", powerOff},
	{"flush_cycle", flushCycle}, {"log_modbus", logModBus},
	{"flush2_cycle", flush2Cycle}, {"flush_purge2_cycle", flushPurge2Cycle},
	{"wait_interval_remainder", waitIntervalRemainder},
	{"wait_variable", waitVariable},
	{"run_driver", runDriver}, {"wait_driver", waitDriver}
};


/************************************************************************/
/* Function	   : initScript												*/
/* Purpose	   : Initialize this module									*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
Void initScript()
{
	list_init(&scrList);

} /* initScript() */


/************************************************************************/
/* Function	   : scriptError											*/
/* Purpose	   : Print error to system log and return ERROR				*/
/* Inputs	   : Fmt, arguments for system log							*/
/* Outputs	   : ERROR													*/
/************************************************************************/
MLocal Errno scriptError(char *fmt, ...)
{
	va_list		ap;
 
	va_start(ap, fmt);
	vSysLogPrintf(fmt, ap);
	return(ERROR);

} /* scriptError() */


/************************************************************************/
/* Function	   : readScriptLine											*/
/* Purpose	   : Read the next line in script							*/
/* Inputs	   : Ptr to ScriptDesc										*/
/* Outputs	   : OK, ERROR, or SCRIPT_DONE								*/
/************************************************************************/
Errno readScriptLine(ScriptDesc *sd)
#ifndef SIMULATE
{
	FIL		fil;
	Int32	linesRead;

	if (f_open(&fil, sd->fname, (FA_READ | FA_OPEN_EXISTING)) != FR_OK)
		return(ERROR);

	if ((f_lseek(&fil, sd->fpos) < 0) ||
		((linesRead = getOneConfigLine(&fil, sd->linebuf, LINELEN)) <= 0))
	{
		f_close(&fil);
		return(SCRIPT_DONE);
	}

	sd->lineno += linesRead;
	sd->fpos = f_tell(&fil);
	f_close(&fil);

	return(OK);

} /* readScriptLine() */
#else	
{
	FILE	*fp;
	Int32	linesRead;

	if ((fp = fopen(sd->fname, "r")) == NULL)
		return(ERROR);

	if ((fseek(fp, sd->fpos, SEEK_SET) < 0) ||
		((linesRead = getOneConfigLine(fp, sd->linebuf, LINELEN)) <= 0))
	{
		fclose(fp);
		return(SCRIPT_DONE);
	}

	sd->lineno += linesRead;
	sd->fpos = ftell(fp);
	fclose(fp);

	return(OK);
	
} /* readScriptLine() */
#endif


/************************************************************************/
/* Function	   : runScriptLine											*/
/* Purpose	   : Run the given script line								*/
/* Inputs	   : Ptr to ScriptDesc, boolean TRUE to check syntax only	*/
/* Outputs	   : OK to continue, ERROR to stop script					*/
/************************************************************************/
Errno runScriptLine(ScriptDesc *sd, MBool syntaxOnly)
{
	Int32		argc, i;
	Tokenizer	tok;
	Nat16		nowTicks;

	if (scriptDebug)
		sysLogPrintf("Running line %ld: %s", sd->lineno, sd->linebuf);

	initTokenizer(&tok, sd->linebuf);

	for (argc = 0; argc < MAX_ARGS; argc++)
	{
		sd->argv[argc] = getNextToken(&tok);
		if (!isaTok(sd->argv[argc]))
			break;
	}

	if ((sd->argv[0][0] == '$') && (sd->argv[1][0] == '=') && (argc >= 3))
		return(parseVariable(sd->argv[0], sd->argv[2]));

	for (i = 0; i < NumberOf(sFuncTbl); i++)
		if (strcasecmp(sd->argv[0], sFuncTbl[i].funcName) == 0)
		{
			RTCGetTime(&sd->lineTime, &nowTicks);
			return((*sFuncTbl[i].func)(argc, sd->argv, syntaxOnly, sd));
		}

	return(ERROR);
	
} /* runScriptLine() */


/************************************************************************/
/* Function	   : runScript												*/
/* Purpose	   : Run the given script file								*/
/* Inputs	   : Script Descriptor ptr									*/
/* Outputs	   : None - runs to completion								*/
/************************************************************************/
Void runScript(ScriptDesc *sd)
{
	Errno		rtn;

	sysLogPrintf("started");

	do
	{
		takeFileSem();
		rtn = readScriptLine(sd);
		giveFileSem();
		if (rtn == OK)
			runScriptLine(sd, FALSE);
	} while (rtn == OK);

	giveFileSem();
	
	if (rtn == SCRIPT_DONE)
		sysLogPrintf("%s finished successfully", sd->fname);
	else
		sysLogPrintf("%s errored out at line %ld",		
					 sd->fname, sd->lineno);

	list_get(&scrList, (Node *)sd);
	free(sd);

} /* runScript() */


/************************************************************************/
/* Function	   : syntaxCheckScript										*/
/* Purpose	   : Check syntax on the given script file					*/
/* Inputs	   : Script Descriptor ptr									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno syntaxCheckScript(ScriptDesc *sd)
{
	Errno		rtn;
	FILE		*fp;
	Int32		linesRead;

	takeFileSem();

	if ((fp = fopen(sd->fname, "r")) == NULL)
		rtn = ERROR;
	else
		while ((linesRead = getOneConfigLine(fp, sd->linebuf, LINELEN)) > 0)
		{
			sd->lineno += linesRead;
			if ((rtn = runScriptLine(sd, TRUE)) != OK)
				break;
		}

	fclose(fp);
	giveFileSem();
	return(rtn);

} /* syntaxCheckScript() */


/************************************************************************/
/* Function	   : getUniqueScriptName									*/
/* Purpose	   : Create a unique task name for script					*/
/* Inputs	   : Name to try											*/
/* Outputs	   : Unique name											*/
/************************************************************************/
MLocal char *getUniqueScriptName(char *s, char *buf)
{
	Nat16		i;

	strncpy(buf, s, NAMELEN);
	if (task_find(buf) == NULL)
		return(buf);

	for (i = 1; i < 1000; i++)
	{
		sprintf(buf, "%.27s_%03hd", s, i);
		if (task_find(buf) == NULL)
			return(buf);
	}

	strncpy(buf, s, NAMELEN);
	return(buf);

} /* getUniqueScriptName() */


/************************************************************************/
/* Function	   : parseScript											*/
/* Purpose	   : Parse "script = filename"								*/
/* Inputs	   : Ptr to remainder of parse line							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno parseScript(char *cmdtail)
{
	FILE		*fp;
	Tokenizer	tokenizer;
	char		*fname, *tname;
	ScriptDesc	*sd;

	initTokenizer(&tokenizer, cmdtail);
	fname = getNextToken(&tokenizer);
	if (!isaTok(fname))
		return(ERROR);

	fname = getStringVal(fname);
	takeFileSem();
	if ((fp = fopen(fname, "r")) == NULL)
	{
		giveFileSem();
		return(scriptError("Could not open script file %s", fname));
	}

	fclose(fp);
	giveFileSem();

	if ((sd = malloc(sizeof(ScriptDesc))) == NULL)
		return(scriptError("Could not malloc space for script %s", fname));

	memset(sd, 0, sizeof(ScriptDesc));
	strncpy(sd->fname, fname, NAMELEN);

	// Check script file for syntax errors
	if (syntaxCheckScript(sd) != OK)
	{
		sysLogPrintf("Error in script %s at line %ld", fname, sd->lineno);
		free(sd);
		return(ERROR);
	}

	sd->fpos = sd->lineno = sd->nestLevel = 0;
	tname = getUniqueScriptName(fname, sd->linebuf);

	if ((sd->td = task_create(runScript, sd, tname)) == NULL)
	{
		sysLogPrintf("Could not create task for %s", fname);
		free(sd);
		return(ERROR);
	}

	list_add(&scrList, (Node *)sd);
	return(OK);

} /* parseScript() */


/************************************************/
/*		User Functions to control Scripts		*/
/************************************************/

/************************************************************************/
/* Function	   : showScript												*/
/* Purpose	   : Show status of scripts running in system				*/
/* Inputs	   : None													*/
/* Outputs	   : OK														*/
/************************************************************************/
Int16 showScript(Void)
{
#ifndef SIMULATE
	Node		*np;
	ScriptDesc	*sd;
	FILE		*fp;
	Int32		lineno, i;
	char		buf[LINELEN];

	for (np = list_first(&scrList); np != NULLNODE; np = list_next(np))
	{
		sd = (ScriptDesc *)np;
		xprintf("%s at line number %ld since ", sd->fname, sd->lineno);
		clk_printtime(sd->lineTime);
		xputs("\n");

		takeFileSem();
		if ((fp = fopen(sd->fname, "r")) != NULL)
		{
			for (lineno = 0; lineno < sd->lineno; lineno++)
				if (fgets(buf, sizeof(buf), fp) == NULL)
					break;

			fclose(fp);
		}
		giveFileSem();

		i = strcspn(buf, "\r\n");
		buf[i] = '\0';

		if (lineno == sd->lineno)
			xprintf("\tline %ld:  %s\n", lineno, buf);
	}
#endif

	return(OK);

} /* showScript() */


/************************************************************************/
/* Function	   : startScript											*/
/* Purpose	   : User func to start a script							*/
/* Inputs	   : Parm mask, script name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 startScript(Nat16 pmask, char *fname)
{
	FILE		*fp;
	char		*tname;
	ScriptDesc	*sd;

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

	takeFileSem();
	if ((fp = fopen(fname, "r")) == NULL)
	{
		giveFileSem();
		xprintf("Could not open script file %s\n", fname);
		return(ERROR);
	}

	fclose(fp);
	giveFileSem();

	if ((sd = malloc(sizeof(ScriptDesc))) == NULL)
	{
		xprintf("Could not malloc space for script %s\n", fname);
		return(ERROR);
	}

	memset(sd, 0, sizeof(ScriptDesc));
	strncpy(sd->fname, fname, NAMELEN);
	
	// Check script file for syntax errors
	if (syntaxCheckScript(sd) != OK)
	{
		xprintf("Error in script %s at line %ld\n", fname, sd->lineno);
		free(sd);
		return(ERROR);
	}

	sd->fpos = sd->lineno = sd->nestLevel = 0;
	tname = getUniqueScriptName(fname, sd->linebuf);

	if ((sd->td = task_create(runScript, sd, tname)) == NULL)
	{
		xprintf("Could not create task for %s\n", fname);
		free(sd);
		return(ERROR);
	}

	list_add(&scrList, (Node *)sd);
	return(OK);

} /* startScript() */


/************************************************************************/
/* Function	   : findScript												*/
/* Purpose	   : Look for a script by file name							*/
/* Inputs	   : Script name											*/
/* Outputs	   : ScriptDesc ptr or NULL									*/
/************************************************************************/
ScriptDesc *findScript(char *fname)
{
	Node		*np;

	for (np = list_first(&scrList); np != NULLNODE; np = list_next(np))
		if (strcasecmp(fname, ((ScriptDesc *)np)->fname) == 0)
			return((ScriptDesc *)np);

	return(NULL);

} /* findScript() */


/************************************************************************/
/* Function	   : stopScriptByDesc										*/
/* Purpose	   : Stop a script											*/
/* Inputs	   : Script Descriptor										*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Int16 stopScriptByDesc(ScriptDesc *sd)
{
	if (sd == NULL)
		return(ERROR);

	task_kill(sd->td);
	sysLogPrintf("%s stopped by user", ((TaskDesc *)(sd->td))->td_name);
	free(sd);
	return(OK);

} /* stopScriptByDesc() */


/************************************************************************/
/* Function	   : stopScript												*/
/* Purpose	   : Stop a script											*/
/* Inputs	   : Parm mask, script name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 stopScript(Nat16 pmask, char *fname)
{
	ScriptDesc	*sd;
	Node		*np, *nnp;

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

	if (strcasecmp(fname, "all") == 0)
	{
		lockList(&scrList);
		for (np = list_first(&scrList); np != NULLNODE; np = nnp)
		{
			nnp = list_next(np);
			stopScriptByDesc((ScriptDesc *)np);
		}

		list_init(&scrList);			/* Creates new Mutex, so no need to release*/
		return(OK);
	}

	if ((sd = findScript(fname)) != NULL)
	{
		list_get(&scrList, (Node *)sd);
		stopScriptByDesc(sd);
		return(OK);
	}

	xprintf("Can't find %s\n", fname);
	return(ERROR);

} /* stopScript() */


/************************************************************************/
/* Function	   : restartScript											*/
/* Purpose	   : User func to stop and restart a script					*/
/* Inputs	   : Parm mask, script name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 restartScript(Nat16 pmask, char *fname)
{
	if (stopScript(pmask, fname) != OK)
		return(ERROR);

	return(startScript(pmask, fname));

} /* restartScript() */


/****************************************/
/*		Script Functions				*/
/****************************************/

/************************************************************************/
/* Function	   : scrSleep												*/
/* Purpose	   : Script function to sleep								*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
MLocal Errno scrSleep(Int32 argc, char **argv, MBool syntaxOnly)
{
	Flt32		delayTime;
	Nat32		delaySecs;
	Nat16		delayTicks;

	if ((argc < 2) || !getFltVal(argv[1], &delayTime))
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	delaySecs = (Nat32)delayTime;
	delayTicks = (Nat16)((Flt32)RTC_TICKS_PER_SECOND*(delayTime-(Flt32)delaySecs));

	task_delay_rtcTicks(delaySecs, delayTicks);
	return(OK);

} /* scrSleep() */


/************************************************************************/
/* Function	   : scrRepeat												*/
/* Purpose	   : Script function to implement loop						*/
/* Inputs	   : argc, argv, ScriptDesc									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno scrRepeat(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd)
{
#pragma unused(syntaxOnly)
	ScriptLoop	*slp = &sd->loop[sd->nestLevel];

	if ((argc < 2) || (sd->nestLevel >= NEST_DEPTH))
		return(ERROR);

	sd->nestLevel++;
	slp->flags = 0;
	slp->loopCnt = 0;

	if (strcasecmp(argv[1], "forever") == 0)
		slp->flags = FLG_FOREVER;
	else if (!getLongVal(argv[1], &slp->loopEnd))
		return(ERROR);

	slp->returnPos = sd->fpos;
	slp->returnLine = sd->lineno;
	return(OK);

} /* scrRepeat() */


/************************************************************************/
/* Function	   : scrRepeatInt											*/
/* Purpose	   : Script function to implement loop						*/
/* Inputs	   : argc, argv, ScriptDesc									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno scrRepeatInt(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd)
{
#pragma unused(syntaxOnly)
	ScriptLoop	*slp = &sd->loop[sd->nestLevel];
	Nat16		nowTicks;

	if ((argc < 3) || (sd->nestLevel >= NEST_DEPTH) ||
		!getLongVal(argv[2], (Int32 *)(&slp->period)))
		return(ERROR);

	sd->nestLevel++;
	slp->flags = FLG_INTERVAL;
	slp->loopCnt = 0;

	if (strcasecmp(argv[1], "forever") == 0)
		slp->flags |= FLG_FOREVER;
	else if (!getLongVal(argv[1], &slp->loopEnd))
		return(ERROR);

	slp->returnPos = sd->fpos;
	slp->returnLine = sd->lineno;
	RTCGetTime(&slp->startTime, &nowTicks);
	if (nowTicks > RTC_TICKS_PER_SECOND/2)
		slp->startTime++;
	return(OK);

} /* scrRepeatInt() */


/************************************************************************/
/* Function	   : scrForever												*/
/* Purpose	   : Script function to implement forever loop				*/
/* Inputs	   : argc, argv, ScriptDesc									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno scrForever(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd)
{
#pragma unused(argc, argv, syntaxOnly)
	ScriptLoop	*slp = &sd->loop[sd->nestLevel];

	if (sd->nestLevel >= NEST_DEPTH)
		return(ERROR);

	sd->nestLevel++;
	slp->flags = FLG_FOREVER;
	slp->loopCnt = 0;
	slp->returnPos = sd->fpos;
	slp->returnLine = sd->lineno;
	return(OK);

} /* scrForever() */


/************************************************************************/
/* Function	   : scrNext												*/
/* Purpose	   : Script function to 'next' for looping					*/
/* Inputs	   : argc, argv, ScriptDesc									*/
/* Outputs	   : OK														*/
/************************************************************************/
MLocal Errno scrNext(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd)
{
#pragma unused(argc, argv)
	ScriptLoop	*slp = &sd->loop[sd->nestLevel - 1];
	Nat32		nowSecs;
	Nat16		nowTicks;
	Nat32		endTime;

	slp->loopCnt++;

	/* Check if loop done  */
	if ((((slp->flags & FLG_FOREVER) == 0) && (slp->loopCnt >= slp->loopEnd))
		|| syntaxOnly)
	{
		sd->nestLevel--;
		return(OK);
	}

	/* Not done with loop.	Need to wait? */
	if (slp->flags & FLG_INTERVAL)
	{
		RTCGetTime(&nowSecs, &nowTicks);
		endTime = slp->startTime + slp->period;
		if (nowSecs < endTime)
			task_delay_rtcTicks(endTime-nowSecs-1, RTC_TICKS_PER_SECOND-nowTicks);
		else
			sysLogPrintf("Repeat_interval at line %ld took %ld secs, interval was %ld secs",
						 sd->lineno, nowSecs - slp->startTime, slp->period);
						 
		RTCtime(&slp->startTime);
	}

	/* Now return to top of loop */
	sd->fpos = slp->returnPos;
	sd->lineno = slp->returnLine;
	return(OK);

} /* scrNext() */


/************************************************************************/
/* Function	   : waitIntervalRemainder									*/
/* Purpose	   : Script function to delay the remainder of interval parm*/
/*				 from the last "repeat_interval" loop					*/
/* Inputs	   : argc, argv, ScriptDesc									*/
/* Outputs	   : OK or ERROR											*/
/* Comment	   : Must occur immediately after the "next" completing the loop*/
/************************************************************************/
MLocal Errno waitIntervalRemainder(Int32 argc, char **argv,
								   MBool syntaxOnly, ScriptDesc *sd)
{
#pragma unused(argc, argv)
	ScriptLoop	*slp = &sd->loop[sd->nestLevel];
	Nat32		nowSecs;
	Nat16		nowTicks;
	Nat32		endTime;

	if ((slp->flags & FLG_INTERVAL) == 0)
		return(ERROR);

	if (!syntaxOnly)
	{
		RTCGetTime(&nowSecs, &nowTicks);
		endTime = slp->startTime + slp->period;
		if (nowSecs < endTime)
			task_delay_rtcTicks(endTime-nowSecs-1, RTC_TICKS_PER_SECOND-nowTicks);
	}

	return(OK);

} /* waitIntervalRemainder() */


/************************************************************************/
/* Function	   : pulseOn												*/
/* Purpose	   : Script function to pulse output port					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno pulseOn(Int32 argc, char **argv, MBool syntaxOnly)
{
	Flt32		period;
	Int32		bitNum;

	if ((argc < 3) || !getFltVal(argv[2], &period) ||
		((bitNum = findRemOutputPort(argv[1])) == ERROR))
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	return(remOutPulseOnByVolume(bitNum, period));

} /* pulseOn() */


/************************************************************************/
/* Function	   : pulseOff												*/
/* Purpose	   : Script function to pulse output port					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno pulseOff(Int32 argc, char **argv, MBool syntaxOnly)
{
	Flt32		period;
	Int32		bitNum;

	if ((argc < 3) || !getFltVal(argv[2], &period) ||
		((bitNum = findRemOutputPort(argv[1])) == ERROR))
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	if (period <= 0.01)
		return(OK);

	return(remOutPulseOff(bitNum, period));

} /* pulseOff() */


/************************************************************************/
/* Function	   : outputOn												*/
/* Purpose	   : Script function to turn on output port					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno outputOn(Int32 argc, char **argv, MBool syntaxOnly)
{
	Int32		bitNum;

	if (argc < 2)
		return(ERROR);

	if ((bitNum = findRemOutputPort(argv[1])) == ERROR)
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	return(remOutput(bitNum, TRUE));

} /* outputOn() */


/************************************************************************/
/* Function	   : outputOff												*/
/* Purpose	   : Script function to turn off output port				*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno outputOff(Int32 argc, char **argv, MBool syntaxOnly)
{
	Int32		bitNum;

	if (argc < 2)
		return(ERROR);

	if ((bitNum = findRemOutputPort(argv[1])) == ERROR)
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	return(remOutput(bitNum, FALSE));

} /* outputOff() */


/************************************************************************/
/* Function	   : powerOn												*/
/* Purpose	   : Script function to turn on power port					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno powerOn(Int32 argc, char **argv, MBool syntaxOnly)
{
	Int32		bitNum;

	if (argc < 2)
		return(ERROR);

	if ((bitNum = findRemPwrPort(argv[1])) == ERROR)
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	return(remPower(bitNum, TRUE));

} /* powerOn() */


/************************************************************************/
/* Function	   : powerOff												*/
/* Purpose	   : Script function to turn off power port					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno powerOff(Int32 argc, char **argv, MBool syntaxOnly)
{
	Int32		bitNum;

	if (argc < 2)
		return(ERROR);

	if ((bitNum = findRemPwrPort(argv[1])) == ERROR)
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	return(remPower(bitNum, FALSE));

} /* powerOff() */


/************************************************************************/
/* Function	   : flushCycle												*/
/* Purpose	   : Script function to run one flush cycle					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno flushCycle(Int32 argc, char **argv, MBool syntaxOnly)
{
	Flt32 injectVol, extractVol, flushTime, mixingDelay;
	Int32 flushSettlingTime;
	Int16 injectPort, flushPort, extractPort;

	if ((argc < 8) || 
		((flushPort = findRemOutputPort(argv[1])) < 0) ||
		((injectPort = findRemOutputPort(argv[2])) < 0) ||
		((extractPort = findRemOutputPort(argv[3])) < 0) ||
		!getFltVal(argv[4], &flushTime) ||
		!getFltVal(argv[5], &mixingDelay) ||
		!getFltVal(argv[6], &injectVol) ||
		!getFltVal(argv[7], &extractVol))
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	/* turn on flush pump */
	sysLogPrintf("Flushing %s for %.1f secs", outPorts[flushPort].name, flushTime);
	remOutPulseOnByVolume(flushPort, flushTime);

	/* make sure flushing has settled before injection*/
	if (getLongVal("$flushSettlingTime", &flushSettlingTime))
		delay_secs(flushSettlingTime);
	else
		delay_secs(5);


	/* inject reagent, note volume is determined by the */
	/* scale factor when the output is configured		*/
	if (injectVol > 0.01)
	{
		sysLogPrintf("Chamber inject %s %.2f ml", outPorts[injectPort].name, injectVol);
		remOutPulseOnByVolume(injectPort, injectVol);
	}

	task_delay_ms((Int32)(1000. * mixingDelay));

	/* Extract sample if non-zero						*/
	if (extractVol > 0.01)
	{
		sysLogPrintf("Chamber extract %s %.2f ml", outPorts[extractPort].name, extractVol);
		remOutPulseOnByVolume(extractPort, extractVol);
	}

	sysLogPrintf("Starting incubation for chamber associated with %s",
		outPorts[flushPort].name);
	return(OK);

} /* flushCycle() */


/************************************************************************/
/* Function	   : flushPurge2Cycle										*/
/* Purpose	   : Script function to run one flush cycle					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/* Comments	   : Runs one flush cycle and injects from two inject pumps */
/*				 Purges both inject pumps during the flush				*/
/************************************************************************/
MLocal Errno flushPurge2Cycle(Int32 argc, char **argv, MBool syntaxOnly)
{
	Flt32 inject1Vol, inject2Vol, flushTime, mixingDelay;
	Int32 flushSettlingTime;
	Int16 inject1Port, flushPort, inject2Port;
	MBool doInject1, doInject2;
	Flt32 purgeTime = 10.0;

	if ((argc < 8) || 
		((flushPort = findRemOutputPort(argv[1])) < 0) ||
		((inject1Port = findRemOutputPort(argv[2])) < 0) ||
		((inject2Port = findRemOutputPort(argv[3])) < 0) ||
		!getFltVal(argv[4], &flushTime) ||
		!getFltVal(argv[5], &mixingDelay) ||
		!getFltVal(argv[6], &inject1Vol) ||
		!getFltVal(argv[7], &inject2Vol))
		return(ERROR);

	if (!getFltVal("$purgeTime", &purgeTime))
		purgeTime = 10.0;

	if (argc > 8)
		if (!getFltVal(argv[8], &purgeTime))
			purgeTime = 10.0;

	doInject1 = (inject1Vol > 0.01);
	doInject2 = (inject2Vol > 0.01);

	if (syntaxOnly)
		return(OK);
			
	if (flushTime < purgeTime)
		sysLogPrintf("Skipping flush on %s, flush time less than purge time",
					 outPorts[flushPort].name);
	else
	{	 /* turn on flush pump */
		sysLogPrintf("Flushing %s for %.1f secs", outPorts[flushPort].name, flushTime);
		remOutput(flushPort, ON);
	}

	if (doInject1)
	{
		sysLogPrintf("Purging %s for %.1f secs", outPorts[inject1Port].name, purgeTime);
		remOutput(inject1Port, ON);
	}

	if (doInject2)
	{
		sysLogPrintf("Purging %s for %.1f secs", outPorts[inject2Port].name, purgeTime);
		remOutput(inject2Port, ON);
	}

	if (doInject1 || doInject2)
		task_delay_ms((Int32)(1000.*purgeTime));
	else
		purgeTime = 0.0;

	if (doInject1)
		remOutput(inject1Port, OFF);
		
	if (doInject2)
		remOutput(inject2Port, OFF);
		
	/* Now continue running the flush port for the remainder of the flushTime */
	if (flushTime >= purgeTime)
		remOutPulseOnByVolume(flushPort, flushTime - purgeTime);

	/* make sure flushing has settled before injection*/
	if (getLongVal("$flushSettlingTime", &flushSettlingTime))
		delay_secs(flushSettlingTime);
	else
		delay_secs(5);


	/* inject reagent, note volume is determined by the */
	/* scale factor when the output is configured		*/
	if (doInject1)
	{
		sysLogPrintf("Chamber inject %s %.2f ml", outPorts[inject1Port].name, inject1Vol);
		remOutPulseOnByVolume(inject1Port, inject1Vol);
	}

	task_delay_ms((Int32)(1000. * mixingDelay));

	/* inject 2nd reagent, note volume is determined by */
	/* the scale factor when the output is configured	*/
	if (doInject2)
	{
		sysLogPrintf("Chamber inject %s %.2f ml", outPorts[inject2Port].name, inject2Vol);
		remOutPulseOnByVolume(inject2Port, inject2Vol);
	}

	sysLogPrintf("Starting incubation for chamber associated with %s",
		outPorts[flushPort].name);
	return(OK);

} /* flushPurge2Cycle() */


/************************************************************************/
/* Function	   : flush2Cycle											*/
/* Purpose	   : Script function to run flush cycle on pair of chambers */
/*				 where flush pump for 2 chambers are plumbed together	*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno flush2Cycle(Int32 argc, char **argv, MBool syntaxOnly)
{
	Flt32 inject1Vol, extract1Vol, inject2Vol, extract2Vol, flushTime, mixingDelay;
	Int32 flushSettlingTime;
	Int16 inject1Port, flushPort, extract1Port, inject2Port, extract2Port;

	if ((argc < 12) || 
		((flushPort = findRemOutputPort(argv[1])) < 0) ||
		((inject1Port = findRemOutputPort(argv[2])) < 0) ||
		((extract1Port = findRemOutputPort(argv[3])) < 0) ||
		((inject2Port = findRemOutputPort(argv[4])) < 0) ||
		((extract2Port = findRemOutputPort(argv[5])) < 0) ||
		!getFltVal(argv[6], &flushTime) ||
		!getFltVal(argv[7], &mixingDelay) ||
		!getFltVal(argv[8], &inject1Vol) ||
		!getFltVal(argv[9], &extract1Vol) ||
		!getFltVal(argv[10], &inject2Vol) ||
		!getFltVal(argv[11], &extract2Vol))
		return(ERROR);

	if (syntaxOnly)
		return(OK);

	/* turn on flush pump */
	sysLogPrintf("Flushing %s for %.1f secs", outPorts[flushPort].name, flushTime);
	remOutPulseOnByVolume(flushPort, flushTime);

	/* make sure flushing has settled before injection*/
	if (getLongVal("$flushSettlingTime", &flushSettlingTime))
		delay_secs(flushSettlingTime);
	else
		delay_secs(5);


	/* inject reagent, note volume is determined by the */
	/* scale factor when the output is configured		*/
	if (inject1Vol > 0.01)
	{
		sysLogPrintf("Chamber inject %s %.2f ml", outPorts[inject1Port].name, inject1Vol);
		remOutPulseOnByVolume(inject1Port, inject1Vol);
	}

	/* inject reagent, note volume is determined by the */
	/* scale factor when the output is configured		*/
	if (inject2Vol > 0.01)
	{
		sysLogPrintf("Chamber inject %s %.2f ml", outPorts[inject2Port].name, inject2Vol);
		remOutPulseOnByVolume(inject2Port, inject2Vol);
	}

	task_delay_ms((Int32)(1000. * mixingDelay));

	/* Extract sample if non-zero						*/
	if (extract1Vol > 0.01)
	{
		sysLogPrintf("Chamber extract %s %.2f ml", outPorts[extract1Port].name, extract1Vol);
		remOutPulseOnByVolume(extract1Port, extract1Vol);
	}

	/* Extract sample if non-zero						*/
	if (extract2Vol > 0.01)
	{
		sysLogPrintf("Chamber extract %s %.2f ml", outPorts[extract2Port].name, extract2Vol);
		remOutPulseOnByVolume(extract2Port, extract2Vol);
	}

	sysLogPrintf("Starting incubation for chamber associated with %s",
		outPorts[flushPort].name);
	return(OK);

} /* flush2Cycle() */


/************************************************************************/
/* Function	   : waitVariable											*/
/* Purpose	   : Script function to wait for non-zero value of a variable*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
MLocal Errno waitVariable(Int32 argc, char **argv, MBool syntaxOnly)
{
	VarStruct	*vp;
	Int32		waitTime, endTime = INT32_MAX;

	if ((argc < 2) || (*argv[1] != '$'))
		return(ERROR);

	if (getLongVal(argv[2], &waitTime))
		endTime = tod + waitTime;
	else
		endTime = INT32_MAX;
	
	if (syntaxOnly)
		return(OK);

#ifndef SIMULATE
	while(tod < endTime)
	{
		if ((vp = findVariable(argv[1])) != NULL)
			if (((vp->flags & VF_ISVAL) == 0) ||
				(vp->lval != 0))
				return(OK);
		task_delay(TICKS_PER_SECOND/2);
	}
#else
	printf("******** System would wait here for variable %s ********\n", argv[1]);
#endif

	return(OK);

} /* waitVariable() */


/************************************************************************/
/* Function	   : runDriver												*/
/* Purpose	   : Script function to run an Oasis driver					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
MLocal Errno runDriver(Int32 argc, char **argv, MBool syntaxOnly)
{
	Driver		*dp;
	Int32		usrparm = 0L;

	if (argc < 2)
		return(ERROR);

	getLongVal(argv[2], &usrparm);

	if ( (dp = drvr_find(argv[1])) == DRV_NULL )
		return( ERROR );
	
	if (syntaxOnly)
		return(OK);

	dp->drv_usrparm = usrparm;
	dp->drv_wakeup = RTCGetTime(NULL, NULL);
	dp->drv_runflags |= DO_SYNC;

#ifndef SIMULATE
	drvr_sample();						/* Force driver to start running */
#else
	printf("******** System would start driver %s and then continue ********\n",
		   argv[1]);
#endif

	return(OK);

} /* runDriver() */


/************************************************************************/
/* Function	   : waitDriver												*/
/* Purpose	   : Script function to wait for Oasis driver completion	*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
MLocal Errno waitDriver(Int32 argc, char **argv, MBool syntaxOnly)
{
	Driver		*dp;
	Int32		waitTime = 10L;

	if ((argc < 2) || ((dp = drvr_find(argv[1])) == DRV_NULL))
		return(ERROR);

	if (!getLongVal(argv[2], &waitTime))
		waitTime = 10L;							/* Dflt wait time is 10 secs */
	
	if (syntaxOnly)
		return(OK);

#ifndef SIMULATE
	/* wait for the sample to complete */
	while ((dp->drv_td != NULLTID) && (waitTime-- > 0))
		task_delay(TICKS_PER_SECOND);
#else
	printf("******** System would wait here for driver %s to finish ********\n",
		   argv[1]);
#endif

	return(OK);

} /* waitDriver() */


/************************************************************************/
/* Function	   : scrEcho												*/
/* Purpose	   : Script function to echo								*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
MLocal Errno scrEcho(Int32 argc, char **argv, MBool syntaxOnly, ScriptDesc *sd)
{
	Int32		i;

	if (syntaxOnly)
		return(OK);

	xprintf("%s: %ld args\n", sd->fname, argc);
	for (i = 0; i < argc; i++)
		xprintf("%s\n", argv[i]);
	return(OK);

} /* scrEcho() */


/************************************************************************/
/* Function	   : scrSysLog												*/
/* Purpose	   : Script function to log to syslog						*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
MLocal Errno scrSysLog(Int32 argc, char **argv, MBool syntaxOnly)
{
#pragma unused(argc)
	Nat16		i;
	char		*p;
	Int32		arg[10];

	if (syntaxOnly)
		return(OK);

	memset(arg, 0, sizeof(arg));

	for (p = argv[1], i=2; *p; )
	{
		if (*p++ == '%')
		{
			while (isdigit(*p) || (*p == '-') || (*p == '+') || (*p == 'l'))
				p++;

			switch (*p)
			{
			  case 'd':
			  case 'u':
				  getLongVal(argv[i], &arg[i]);
				  i++;
				  break;

			  case 'f':
			  case 'g':
				  getLongVal(argv[i], &arg[i]);
				  i++;
				  break;

			  case 's':
				  arg[i] = (Int32)getStringVal(argv[i]);
				  i++;
				  break;
			}
		}
	}

	sysLogPrintf(argv[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7], arg[8], arg[9]);

	return(OK);

} /* scrSysLog() */
