/************************************************************************/
/* Copyright 2009 MBARI													*/
/************************************************************************/
/* Summary	: Remote serial and power ports for Respiromters			*/
/* Filename : remote.c													*/
/* Author	: Robert Herlien (rah)										*/
/* Project	: Benthic/Midwater Respirometers (BRS/MRS)					*/
/* Revision: 1.0														*/
/* Created	: 04/07/2009												*/
/*																			*/
/* 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:												*/
/* 07apr2009 rah - created												*/
/************************************************************************/

#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 <io.h>							/* OASIS I/O definitions		*/
#include <list.h>						/* OASIS Linked List library	*/
#include <sem.h>						/* OASIS Semaphore library		*/
#include <task.h>						/* OASIS task dispatcher		*/
#include <serial.h>						/* OASIS Serial I/O				*/		
#include <utils.h>						/* OASIS Utility routines		*/
#include <modbus.h>						/* Modbus protocol library		*/
#include <remote.h>						/* Remote I/O library			*/
#include <parse.h>						/* Generic parser declarations	*/
#include <variable.h>					/* Variable definitions			*/
#include <debug.h>

#include <cfxpico.h>					/* Persistor PicoDOS definitions*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


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

Extern Errno	modErrno;
Extern MBool	blinky;


/********************************************************/
/*				Global Data								*/
/* (Global only so we can share with usrRemoteCmds.c)	*/
/********************************************************/

Global RemSerPort		serPorts[VIRT_SER_PORTS];
Global RemIOPort		pwrPorts[VIRT_PWR_PORTS];
Global RemIOPort		outPorts[VIRT_OUT_PORTS];
Global RemID			remIDs[REMOTE_IDS];


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

MLocal Semaphore remSem;


/************************************************************************/
/* Function	   : initRemoteLib											*/
/* Purpose	   : Initialize this module									*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
Void initRemoteLib(Void)
{
	int			i;

	memset(serPorts, 0, sizeof(serPorts));
	memset(pwrPorts, 0, sizeof(pwrPorts));
	memset(outPorts, 0, sizeof(outPorts));

	for (i = 0; i < NumberOf(pwrPorts); i++) {
		pwrPorts[i].multiplier = DFLT_MULT;
		pwrPorts[i].surgeDelayMs = DFLT_SURGE;
	}

	for (i = 0; i < NumberOf(outPorts); i++) {
		outPorts[i].multiplier = DFLT_MULT;
		outPorts[i].surgeDelayMs = DFLT_SURGE;
	}

	for (i = 0; i < NumberOf(remIDs); i++) {
		remIDs[i].assigned = FALSE;
	}

	sem_init(&remSem, MUTEX, 1);
	sem_name(&remSem, "remoteSem");

} /* initRemoteLib() */


/************************************************************************/
/* Function	   : takeRemoteSem											*/
/* Purpose	   : Take the remSem semaphore								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
	Void
takeRemoteSem(Void)
{
	sem_take(&remSem);

} /* takeRemoteSem() */


/************************************************************************/
/* Function	   : giveRemoteSem											*/
/* Purpose	   : Give back the remSem semaphore							*/
/* Inputs	   : Delay ms before giving sem								*/
/* Outputs	   : None													*/
/************************************************************************/
Void giveRemoteSem(Nat32 delay_ms)
{
	if (delay_ms > 5)
		task_delay_ms(delay_ms);
	sem_give(&remSem);

} /* giveRemoteSem() */


/************************************************************************/
/* Function	   : findRemPwrPort											*/
/* Purpose	   : Find remote power port number from string input		*/
/* Inputs	   : String to look up										*/
/* Outputs	   : Port number or ERROR									*/
/************************************************************************/
Int16 findRemPwrPort(char *s)
{
	Int32		longNum;
	VarStruct	*vp;
	char		*name;
	Int16		i;

	if (getLongVal(s, &longNum))
	{
		if ((longNum < 0) || (longNum >= PHYS_PWR_PORTS+VIRT_PWR_PORTS))
			return(ERROR);
		return((Int16)(longNum & 0xffff));
	}

	vp = findVariable(s);
	name = (vp == NULL) ? s : vp->str;

	for (i = 0; i < VIRT_PWR_PORTS; i++)
		if (strcasecmp(name, pwrPorts[i].name) == 0)
			return(i+PHYS_PWR_PORTS);

	return(ERROR);

} /* findRemPwrPort() */


/************************************************************************/
/* Function	   : findRemOutputPort										*/
/* Purpose	   : Find remote Output port number from string input		*/
/* Inputs	   : String to look up										*/
/* Outputs	   : Port number or ERROR									*/
/************************************************************************/
Int16 findRemOutputPort(char *s)
{
	Int32		longNum;
	VarStruct	*vp;
	char		*name;
	Int16		i;

	if (getLongVal(s, &longNum))
	{
		if ((longNum < 0) || (longNum >= VIRT_OUT_PORTS))
			return(ERROR);
		return((Int16)(longNum & 0xffff));
	}

	vp = findVariable(s);
	name = (vp == NULL) ? s : vp->str;

	for (i = 0; i < VIRT_OUT_PORTS; i++)
		if (strcasecmp(name, outPorts[i].name) == 0)
			return(i);

	return(ERROR);

} /* findRemOutputPort() */


/************************************************************************/
/* Function	   : getVirtSerPort											*/
/* Purpose	   : Get ptr to RemSerPort, or NULL if port invalid			*/
/* Inputs	   : Virtual serial port number								*/
/* Outputs	   : RemSerHandle or NULL									*/
/************************************************************************/
RemSerHandle getVirtSerPort(Nat32 virtPort)
{
	if ((virtPort < PHYS_SER_PORTS) || (virtPort >= (PHYS_SER_PORTS+VIRT_SER_PORTS)))
		return(NULL);

	return(&serPorts[virtPort-PHYS_SER_PORTS]);

} /* getVirtSerPort() */


/************************************************************************/
/* Function	   : assignRemoteID											*/
/* Purpose	   : Allocate a remID for a chamber controller				*/
/* Inputs	   : Modbus bus, slave ID									*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal Void assignRemoteID(Nat16 bus, Nat16 slave)
{
	Nat16 i;

	for (i = 0; i < NumberOf(remIDs); i++)
		if (remIDs[i].assigned &&
		   (remIDs[i].bus == bus) && (remIDs[i].slave == slave))
			return;						/* Already know about this controller*/

	for (i = 0; i < NumberOf(remIDs); i++)
		if (!remIDs[i].assigned)		/* New controller.	Find a blank remID*/
		{
			remIDs[i].assigned = TRUE;
			remIDs[i].bus = bus;
			remIDs[i].slave = slave;
			return;
		}

} /* assignRemoteID() */


/************************************************************************/
/* Function	   : assignVirtSerial										*/
/* Purpose	   : Assign a virtual serial port							*/
/* Inputs	   : Virtual port num, identifiers for remote port			*/
/* Outputs	   : OK or PORT_CONFLICT									*/
/************************************************************************/
Errno assignVirtSerial(char *name, Nat32 virtPort, Nat16 bus, Nat16 slave, Nat16 port)
{
	RemSerPort	*rp;
	Nat16		i;

	if (((rp = getVirtSerPort(virtPort)) == NULL) || (bus >= PHYS_SER_PORTS) ||
		(slave <= 0) || (slave > 255) || (port >= CHAMBER_SER_PORTS))
		return(PORT_CONFLICT);

	if (rp->assigned) 
		return(PORT_CONFLICT);

	for (i = 0; i < VIRT_SER_PORTS; i++)
		if (serPorts[i].assigned && 
			(serPorts[i].bus == bus) &&
			(serPorts[i].slave == slave) &&
			(serPorts[i].port == port))
			xprintf("WARNING! Virtual serial ports %u and %u assigned to same remote port\n",
					i, (Nat16)virtPort);
	
	rp->assigned = PortAssigned;
	rp->bus = bus;
	rp->slave = slave;
	rp->port = port;
	if (name)
		strncpy(rp->name, name, RNAME_SIZE);

	sem_init(&rp->sem, MUTEX, 1);
	sprintf(rp->buf, "remSerSem%02ld", virtPort);
	sem_name(&rp->sem, rp->buf);
	assignRemoteID(bus, slave);

	return(OK);
}

/************************************************************************/
/* Function	   : assignVirtPwr											*/
/* Purpose	   : Assign a virtual power port							*/
/* Inputs	   : Virtual port num, identifiers for remote port			*/
/* Outputs	   : OK or PORT_CONFLICT									*/
/************************************************************************/
Errno assignVirtPwr(char *name, Nat32 virtPort, Nat16 bus, Nat16 slave,
					Nat16 coil, Flt32 mult, Nat32 surgeDelay)
{
	RemIOPort	*rp;
	Nat16		i;

	if ((virtPort < PHYS_PWR_PORTS) || (virtPort >= (PHYS_PWR_PORTS+VIRT_PWR_PORTS))
		|| (slave <= 0) || (slave > 255) || (coil >= CHAMBER_PWR_PORTS))
		return(PORT_CONFLICT);

	rp = &pwrPorts[virtPort-PHYS_PWR_PORTS];

	if (rp->assigned)
		return(PORT_CONFLICT);

	for (i = 0; i < VIRT_PWR_PORTS; i++)
		if (pwrPorts[i].assigned && 
			(pwrPorts[i].bus == bus) &&
			(pwrPorts[i].slave == slave) &&
			(pwrPorts[i].coil == coil))
			xprintf("WARNING! Virtual power ports %u and %u assigned to same remote port\n",
					i, (Nat16)virtPort);
	
	rp->assigned = TRUE;
	rp->bus = bus;
	rp->slave = slave;
	rp->coil = coil;
	rp->coilState = FALSE;
	if (name)
		strncpy(rp->name, name, RNAME_SIZE);
	rp->multiplier = mult;
	rp->surgeDelayMs = surgeDelay;
	assignRemoteID(bus, slave);
	return(OK);
}

/************************************************************************/
/* Function	   : assignVirtOutput										*/
/* Purpose	   : Assign a virtual output port							*/
/* Inputs	   : Virtual port num, identifiers for remote port			*/
/* Outputs	   : OK or PORT_CONFLICT									*/
/************************************************************************/
Errno assignVirtOutput(char *name, Nat32 virtPort, Nat16 bus, Nat16 slave,
					   Nat16 coil, Flt32 mult, Nat32 surgeDelay)
{
	RemIOPort	*rp;
	
	if ( (virtPort >= VIRT_OUT_PORTS) || (slave <= 0) || (slave > 255) || 
		 (coil >= CHAMBER_OUT_PORTS) )
		return(PORT_CONFLICT);

	rp = &outPorts[virtPort];

	if (rp->assigned)
		return(PORT_CONFLICT);

	rp->assigned = TRUE;
	rp->bus = bus;
	rp->slave = slave;
	rp->coil = coil;
	rp->coilState = FALSE;
	if (name)
		strncpy(rp->name, name, RNAME_SIZE);
	rp->multiplier = mult;
	rp->surgeDelayMs = surgeDelay;
	assignRemoteID(bus, slave);
	return(OK);
}

/************************************************************************/
/* Function	   : openRemSerHandle										*/
/* Purpose	   : Local func to manipulate RemSerPort for an open		*/
/* Inputs	   : RemSerPort, boolean to open async						*/
/* Outputs	   : OK or ERROR											*/
/* Comment	   : If async==TRUE and semaphore not avail, return PORT_IN_USE*/
/************************************************************************/
Errno openRemSerHandle(RemSerHandle rsh, MBool async)
{
	if ((rsh == NULL) || ((rsh->handle = modBusOpen(rsh->bus)) == NULL))
		return(ERROR);

	if (async && (rsh->sem.sem_cnt < 1))
		return(modErrno = PORT_IN_USE);

	sem_take(&rsh->sem);
	rsh->bufPtr = rsh->buf;
	rsh->bufChars = 0;
	if ((modErrno = writeOneCoil(rsh->handle, rsh->slave, TxEnable(rsh->port), TRUE)) != OK)
		remSerClose(rsh);

	return(modErrno);

} /* openRemSerHandle() */


/************************************************************************/
/* Function	   : findRemSerHandle										*/
/* Purpose	   : Find a remote serial port handle						*/
/* Inputs	   : bus, slave, port										*/
/* Outputs	   : RemSerHandle, or NULL if error							*/
/************************************************************************/
RemSerHandle findRemSerHandle(Nat16 bus, Nat16 slave, Nat16 port)
{
	RemSerPort	*rp = serPorts, *rpOK = NULL;
	Nat16		i;

	if ((slave <= 0) || (slave > 255) || (port >= CHAMBER_SER_PORTS) ||
		(bus >= PHYS_SER_PORTS))
	{
		modErrno = PORT_CONFLICT;
		return(NULL);
	}

	for (i = 0; i < VIRT_SER_PORTS; i++, rp++)
	{
		if ((rp->assigned != PortUnused) && (rp->bus == bus) && 
			(rp->slave == slave) && (rp->port == port))
			return(rp);
		else if (rp->assigned == PortUnused)
			rpOK = rp;
	}

	if (rpOK != NULL)
	{
		rpOK->assigned = PortInUse;
		rpOK->bus = bus;
		rpOK->slave = slave;
		rpOK->port = port;
		sem_init(&rpOK->sem, MUTEX, 1);
		return(rpOK);
	}

	modErrno = PORT_ERROR;
	return(NULL);

} /* findRemSerHandle() */


/************************************************************************/
/* Function	   : remSerOpen												*/
/* Purpose	   : Open a remote serial port								*/
/* Inputs	   : bus, slave, port, boolean TRUE to open async			*/
/* Outputs	   : RemSerHandle, or NULL if error							*/
/* Comment	   : If async==TRUE and semaphore not avail, return NULL	*/
/************************************************************************/
RemSerHandle remSerOpen(Nat16 bus, Nat16 slave, Nat16 port, MBool async)
{
	RemSerHandle rsh;

	if ((rsh = findRemSerHandle(bus, slave, port)) == NULL)
		return(NULL);

	return((openRemSerHandle(rsh, async) == OK) ? rsh : NULL);

} /* remSerOpen() */


/************************************************************************/
/* Function	   : remOpenVirtSer											*/
/* Purpose	   : Open a virtual serial port								*/
/* Inputs	   : Virtual serial port number, boolean to open async		*/
/* Outputs	   : Errno													*/
/* Comment	   : If async==TRUE and semaphore not avail, return PORT_IN_USE*/
/************************************************************************/
Errno remOpenVirtSer(Nat32 virtPort, MBool async)
{
	RemSerPort	*rp;

	if ((rp = getVirtSerPort(virtPort)) == NULL)
		return(PORT_CONFLICT);

	return(openRemSerHandle(rp, async));

} /* remOpenVirtSer() */


/************************************************************************/
/* Function	   : remSerClose											*/
/* Purpose	   : Close a remote serial port								*/
/* Inputs	   : RemSerHandle											*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remSerClose(RemSerHandle rsh)
{
	Errno		rtn;

	if ((rsh == NULL) || (rsh->handle == NULL) || (rsh->sem.sem_owner != task_get()))
		return(ERROR);

	writeOneCoil(rsh->handle, rsh->slave, TxEnable(rsh->port), FALSE);
	rtn = modBusClose(rsh->handle);
	rsh->handle = NULL;
	if (rsh->assigned == PortInUse)
		rsh->assigned = PortUnused;
	sem_give(&rsh->sem);
	return(rtn);

} /* remSerClose() */


/************************************************************************/
/* Function	   : remCloseVirtSer										*/
/* Purpose	   : Close a virtual serial port							*/
/* Inputs	   : RemSerHandle											*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remCloseVirtSer(Nat32 virtPort)
{
	RemSerPort	*rp;

	if ((rp = getVirtSerPort(virtPort)) == NULL)
		return(PORT_CONFLICT);

	return(remSerClose(rp));

} /* remCloseVirtSer() */


/************************************************************************/
/* Function	   : remSerInit												*/
/* Purpose	   : Set baud rate and mode for a remote serial port		*/
/* Inputs	   : RemSerHandle, baud rate, mode							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remSerInit(RemSerHandle rsh, Nat32 baud, Nat32 mode)
{
	Nat16		regs[2];

	if ((rsh == NULL) || (rsh->handle == NULL))
		return(ERROR);

	regs[0] = baud >> 2;

	switch(mode & PTY_MASK)
	{
	  case ODD_PTY:
		  regs[1] = REM_ODD_PTY;
		  break;

	  case EVEN_PTY:
		  regs[1] = REM_EVEN_PTY;
		  break;
		
	  case NO_PTY:
		  regs[1] = REM_NO_PTY;
		  break;
	}

	if (mode & STOP2)
		regs[1] |= REM_STOP2;

	modErrno = writeRegs(rsh->handle, rsh->slave, BaudReg(rsh->port),
						 2, regs);

	if (mode & BIT7)
		return(BAD_PARM);

	return(modErrno);

} /* remSerInit() */


/************************************************************************/
/* Function	   : remVirtSerInit											*/
/* Purpose	   : Set baud rate for a virtual serial port				*/
/* Inputs	   : Virtual ser port number, baud rate, mode				*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remVirtSerInit(Nat32 virtPort, Nat32 baud, Nat32 mode)
{
	RemSerPort	*rp;

	if ((rp = getVirtSerPort(virtPort)) == NULL)
		return(PORT_CONFLICT);

	return(remSerInit(rp, baud, mode));

} /* remVirtSerInit() */


/************************************************************************/
/* Function	   : remPower												*/
/* Purpose	   : Turn on/off a remote power bit							*/
/* Inputs	   : Remote Power number, boolean							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remPower(Nat32 pwrnum, MBool onoff)
{
	RemIOPort			*rp;
	ModBusHandle		mh;
	Errno				rtn;

	if ((pwrnum < PHYS_PWR_PORTS) || (pwrnum >= (PHYS_PWR_PORTS+VIRT_PWR_PORTS)))
		return(ERROR);

	rp = &pwrPorts[pwrnum - PHYS_PWR_PORTS];

	if (!(rp->assigned))
		return(ERROR);

	if ((mh = modBusOpen(rp->bus)) == NULL)
		return(ERROR);

	if (onoff)
		takeRemoteSem();

	rtn = writeOneCoil(mh, rp->slave, PowerBit(rp->coil), onoff);

	rp->coilState = onoff;

	if (onoff)
		giveRemoteSem(rp->surgeDelayMs);

	modBusClose(mh);
	return(rtn);

} /* remPower() */


/************************************************************************/
/* Function	   : remOutput												*/
/* Purpose	   : Turn on/off a remote output bit						*/
/* Inputs	   : Remote Output number, boolean							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remOutput(Nat32 outnum, MBool onoff)
{
	RemIOPort			*rp;
	ModBusHandle		mh;
	Errno				rtn;

	if ( outnum >= VIRT_OUT_PORTS )
		return(ERROR);

	rp = &outPorts[outnum];

	if (!(rp->assigned))
		return(ERROR);

	if ((mh = modBusOpen(rp->bus)) == NULL)
		return(ERROR);

	if (onoff)
		takeRemoteSem();

	rtn = writeOneCoil(mh, rp->slave, OutputBit(rp->coil), onoff);

	rp->coilState = onoff;

	if (onoff)
		giveRemoteSem(rp->surgeDelayMs);

	modBusClose(mh);
	return(rtn);

} /* remOutput() */


/************************************************************************/
/* Function	   : remIsPowerOn											*/
/* Purpose	   : See if any power bits set on a given ModBus			*/
/* Inputs	   : ModBus number											*/
/* Outputs	   : TRUE if any power bits set, else FALSE					*/
/************************************************************************/
Errno remIsPowerOn(Nat16 bus)
{
	RemIOPort			*rp;
	Nat16				i;

	for (i = 0, rp = pwrPorts; i < NumberOf(pwrPorts); i++, rp++)
		if ((rp->bus == bus) && rp->coilState)
			return(TRUE);

	return(FALSE);

} /* remIsPowerOn() */


/************************************************************************/
/* Function	   : remIsOutputOn											*/
/* Purpose	   : See if any output bits set on a given ModBus			*/
/* Inputs	   : ModBus number											*/
/* Outputs	   : TRUE if any output bits set, else FALSE				*/
/************************************************************************/
Errno remIsOutputOn(Nat16 bus)
{
	RemIOPort			*rp;
	Nat16				i;

	for (i = 0, rp = outPorts; i < NumberOf(outPorts); i++, rp++)
		if ((rp->bus == bus) && rp->coilState)
			return(TRUE);

	return(FALSE);

} /* remIsOutputOn() */


/************************************************************************/
/* Function	   : remOff													*/
/* Purpose	   : Turn off ALL remote outputs and power bits				*/
/* Inputs	   : None													*/
/* Outputs	   : OK														*/
/************************************************************************/
Errno remOff(Void)
{
	RemIOPort			*rp;
	ModBusHandle		mh;
	Nat16				i;

	for (i = 0, rp = pwrPorts; i < NumberOf(pwrPorts); i++, rp++)
		if ((mh = modBusOpen(rp->bus)) != NULL)
		{
			writeOneCoil(mh, rp->slave, PowerBit(rp->coil), FALSE);
			rp->coilState = FALSE;
			modBusClose(mh);
		}

	for (i = 0, rp = outPorts; i < NumberOf(outPorts); i++, rp++)
		if ((mh = modBusOpen(rp->bus)) != NULL)
		{
			writeOneCoil(mh, rp->slave, OutputBit(rp->coil), FALSE);
			rp->coilState = FALSE;
			modBusClose(mh);
		}

	return(OK);

} /* remOff() */


/************************************************************************/
/* Function	   : remOutPulseOn											*/
/* Purpose	   : Pulse on remote output bit								*/
/* Inputs	   : Remote Output number, Number of ms to pulse on			*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remOutPulseOn(Nat32 bitNum, Flt32 sec)
{
	RemIOPort			*rp;
	ModBusHandle		mh;
	Errno				rtn;
	Nat32				msOn, surgeMs;

	if ( bitNum >= VIRT_OUT_PORTS )
		return(ERROR);

	rp = &outPorts[bitNum];

	if (!(rp->assigned))
		return(ERROR);

	if ((msOn = (Nat32)(sec * 1000.)) < 5)
		return(OK);

	if ((mh = modBusOpen(rp->bus)) == NULL)
		return(ERROR);

	takeRemoteSem();
	surgeMs = rp->surgeDelayMs;
	rtn = writeOneCoil(mh, rp->slave, OutputBit(rp->coil), TRUE);

	if (surgeMs <= msOn)
	{
		giveRemoteSem(surgeMs);
		task_delay_ms(msOn - surgeMs);
		rtn |= writeOneCoil(mh, rp->slave, OutputBit(rp->coil), FALSE);
	}
	else
	{
		task_delay_ms(msOn);
		rtn |= writeOneCoil(mh, rp->slave, OutputBit(rp->coil), FALSE);
		giveRemoteSem(surgeMs - msOn);
	}

	rp->coilState = FALSE;
	modBusClose(mh);
	return(rtn);

} /* remOutPulseOn() */


/************************************************************************/
/* Function	   : remOutPulseOnByVolume									*/
/* Purpose	   : Pulse on remote output bit								*/
/* Inputs	   : Remote Output number, volume to pulse on				*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remOutPulseOnByVolume(Nat32 bitNum, Flt32 vol)
{
	RemIOPort			*rp;

	if ( bitNum >= VIRT_OUT_PORTS )
		return(ERROR);

	rp = &outPorts[bitNum];

	if (!(rp->assigned))
		return(ERROR);

	return(remOutPulseOn(bitNum, vol * rp->multiplier));

} /* remOutPulseOnByVolume() */


/************************************************************************/
/* Function	   : remOutPulseOff											*/
/* Purpose	   : Pulse off remote output bit							*/
/* Inputs	   : Remote Output number, Number of ms to pulse off		*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno remOutPulseOff(Nat32 bitNum, Flt32 sec)
{
	RemIOPort			*rp;
	ModBusHandle		mh;
	Errno				rtn;
	Nat32				msOn;

	if ( bitNum >= VIRT_OUT_PORTS )
		return(ERROR);

	rp = &outPorts[bitNum];

	if (!(rp->assigned))
		return(ERROR);

	if ((msOn = (Nat32)(sec * rp->multiplier * 1000.)) < 5)
		return(OK);

	if ((mh = modBusOpen(rp->bus)) == NULL)
		return(ERROR);

	takeRemoteSem();
	rtn = writeOneCoil(mh, rp->slave, OutputBit(rp->coil), FALSE);
	task_delay_ms(msOn);

	rtn |= writeOneCoil(mh, rp->slave, OutputBit(rp->coil), TRUE);
	giveRemoteSem(rp->surgeDelayMs);

	rp->coilState = TRUE;
	modBusClose(mh);
	return(rtn);

} /* remOutPulseOff() */


/************************************************************************/
/* Function	   : remSerRead												*/
/* Purpose	   : Read a remote serial port								*/
/* Inputs	   : Remote serial handle, buffer ptr, length				*/
/* Outputs	   : Number of chars read									*/
/************************************************************************/
Nat32 remSerRead(RemSerHandle rsh, char *buffer, Nat32 len)
{
	Nat32		bytesRead;
	char		*p;
	Nat16		readLen;

	if ((rsh == NULL) || (rsh->handle == NULL))
		return(0);

	for (p = buffer, bytesRead = 0; bytesRead < len; )
	{
		if (rsh->bufChars > 0)
		{
			*p++ = *(rsh->bufPtr)++;
			rsh->bufChars--;
			bytesRead++;
		}
		else
		{
			readLen = (len+1)/2;
			if (readLen > SERREG_SIZE)
				readLen = SERREG_SIZE;
			if (readLen < MIN_READ)
				readLen = MIN_READ;

			modErrno = readHoldingRegs(rsh->handle, rsh->slave, RxQReg(rsh->port),
									   readLen+1, &rsh->rxQueued);
			if ((modErrno != OK) || (rsh->rxQueued == 0))
				return(bytesRead);
			rsh->bufPtr = rsh->buf;
			rsh->bufChars = (rsh->rxQueued >= 2*readLen) ? 
								2*readLen : rsh->rxQueued;
		}
	}

	return(bytesRead);

} /* remSerRead() */


/************************************************************************/
/* Function	   : remSerWrite											*/
/* Purpose	   : Write a remote serial port								*/
/* Inputs	   : Remote serial handle, buffer ptr, length				*/
/* Outputs	   : Number of chars written								*/
/************************************************************************/
Nat32 remSerWrite(RemSerHandle rsh, const char *buffer, Nat32 len)
{
	Nat32		bytesWritten;
	const char	*p;
	Nat16		port, writeLen, writeQueuedReg, writeQueued, zeroCount;

	if ((rsh == NULL) || (rsh->handle == NULL))
		return(0);

	port = rsh->port;
	bytesWritten = 0;
	zeroCount = 0;
	for (p = buffer; bytesWritten < len; )
	{
		writeLen = len - bytesWritten;
		if (writeLen > SERBUF_SIZE)
			writeLen = SERBUF_SIZE;

		modErrno = writeReadSpecial(rsh->handle, rsh->slave, TxQueuedReg(port),
									1, TxReqReg(port), 1, (Byte *)&writeLen, 
									(writeLen+1)/2, p, &writeQueuedReg);
		if (modErrno != OK)
			return(bytesWritten);

		writeQueued = writeQueuedReg & 0xff;
		bytesWritten += writeQueued;
		p += writeQueued;

		if (writeQueued < writeLen)
			task_delay(TICKS_PER_SECOND/20);

		if (writeQueued == 0)
		{
			if (++zeroCount > 10)
				return(bytesWritten);
		}
		else
			zeroCount = 0;
	}

	return(bytesWritten);

} /* remSerWrite() */


/************************************************************************/
/* Function	   : remSerDrain											*/
/* Purpose	   : Wait for serial output stream to finish sending		*/
/* Inputs	   : RemSerHandle, ticks to wait							*/
/* Outputs	   : Errno													*/
/************************************************************************/
Errno remSerDrain(RemSerHandle rsh, Nat32 tmout)
{
	Nat32		tmRemain;
	Nat16		status;
	Errno		rtn;

	if ((rsh == NULL) || (rsh->handle == NULL))
		return(ERROR);

	for (tmRemain = tmout; tmRemain > 0; tmRemain -= 10)
	{
		if ((rtn = readHoldingRegs(rsh->handle, rsh->slave, SerStatusReg,
								   1, &status)) != OK)
			return(rtn);

		if (TxBusy(status, rsh->port) == 0)
			return(OK);

		task_delay( TICKS_PER_SECOND/4 );
	}

	return(OK);

} /* remSerDrain() */


/************************************************************************/
/* Function	   : remSerIFlush											*/
/* Purpose	   : Flush remote serial input stream						*/
/* Inputs	   : RemSerHandle											*/
/* Outputs	   : Errno													*/
/************************************************************************/
Errno remSerIFlush(RemSerHandle rsh)
{
	if ((rsh == NULL) || (rsh->handle == NULL))
		return(ERROR);

	rsh->bufChars = 0;
	return(writeOneReg(rsh->handle, rsh->slave, RxCtlReg(rsh->port),
					   REM_RX_FLUSH));

} /* remSerIFlush() */


/************************************************************************/
/* Function	   : remSerOFlush											*/
/* Purpose	   : Flush remote serial output stream						*/
/* Inputs	   : RemSerHandle											*/
/* Outputs	   : Errno													*/
/************************************************************************/
Errno remSerOFlush(RemSerHandle rsh)
{
	if ((rsh == NULL) || (rsh->handle == NULL))
		return(ERROR);

	return(writeOneReg(rsh->handle, rsh->slave, TxCtlReg(rsh->port),
					   REM_TX_FLUSH));

} /* remSerOFlush() */


/************************************************************************/
/* Function	   : remSerRxCount											*/
/* Purpose	   : Get number of queued Rx characters						*/
/* Inputs	   : RemSerHandle											*/
/* Output	   : Number of queued Rx characters							*/
/************************************************************************/
Int16 remSerRxCount(RemSerHandle rsh)
{
	return((rsh == NULL) ? 0 : rsh->rxQueued);

} /* remSerRxCount() */


/************************************************************************/
/* Function	   : remSerBreak											*/
/* Purpose	   : Send a break character to remote output stream			*/
/* Inputs	   : RemSerHandle, break time in ms							*/
/* Outputs	   : Errno													*/
/************************************************************************/
Errno remSerBreak(RemSerHandle rsh, Nat16 breakTime)
{
	if ( (rsh == NULL) || (rsh->handle == NULL) )
		return(ERROR);
	
	/* set break bit */
	writeOneReg(rsh->handle, rsh->slave, TxBreakReg(rsh->port), REM_BREAK);

	/* wait for break delay */
	task_delay_ms(breakTime);

	/* clear break bit */
	return (writeOneReg(rsh->handle, rsh->slave, TxBreakReg(rsh->port), 0));

} /* remSerBreak() */


/************************************************************************/
/* Function	   : remTaskExit											*/
/* Purpose	   : Close remote ports owned by exiting task				*/
/* Inputs	   : Task Descriptor ptr of exiting task					*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
remTaskExit(TaskDesc *td)
{
	RemSerPort	*rp;
	Nat16		i;

	for (i = 0, rp = serPorts; i < VIRT_SER_PORTS; i++, rp++)
		if (rp->sem.sem_owner == td)
		{
			if (rp->handle != NULL)
				modBusClose(rp->handle);
			rp->handle = NULL;
			if (rp->assigned == PortInUse)
				rp->assigned = PortUnused;
			sem_give(&rp->sem);
		}

} /* remTaskExit() */


/************************************************************************/
/* Function	   : setAllSlaves											*/
/* Purpose	   : Set coil in all chamber controllers					*/
/* Inputs	   : Coil number, coil state								*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal Void setAllSlaves(Nat16 coil, MBool state)
{
	Nat16				i;
	ModBusHandle		mh;

	for (i = 0; i < NumberOf(remIDs); i++)
		if (remIDs[i].assigned &&
			((mh = modBusOpen(remIDs[i].bus)) != NULL))
		{
			writeOneCoil(mh, remIDs[i].slave, coil, state);
			modBusClose(mh);
		}

} /* setAllSlaves() */


/************************************************************************/
/* Function	   : remBlinkyThread										*/
/* Purpose	   : Thread that blinks remote LEDs to indicate OK			*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal Void remBlinkyThread()
{
	while(TRUE)
	{
		setAllSlaves(BlinkyDebugCoil, !blinky);

		if (blinky)
		{
			setAllSlaves(BlinkyStateCoil, TRUE);
			task_delay(TICKS_PER_SECOND);		
			setAllSlaves(BlinkyStateCoil, FALSE);
		}
		task_delay(TICKS_PER_SECOND);	
	}

} /* remBlinkyThread() */


/************************************************************************/
/* Function	   : remErrorThread											*/
/* Purpose	   : Thread that blinks remote LEDs to indicate error		*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal Void remErrorThread()
{
	while(TRUE)
	{
		setAllSlaves(BlinkyDebugCoil, FALSE);
		setAllSlaves(BlinkyStateCoil, TRUE);
		task_delay(TICKS_PER_SECOND/10);		
		setAllSlaves(BlinkyStateCoil, FALSE);
		task_delay(TICKS_PER_SECOND/10);		

		setAllSlaves(BlinkyStateCoil, TRUE);
		task_delay(TICKS_PER_SECOND/10);		
		setAllSlaves(BlinkyStateCoil, FALSE);
		task_delay(TICKS_PER_SECOND/10);		

		setAllSlaves(BlinkyStateCoil, TRUE);
		task_delay(TICKS_PER_SECOND/10);		
		setAllSlaves(BlinkyStateCoil, FALSE);
		task_delay(TICKS_PER_SECOND/10);		

		delay_secs(2);
	}

} /* remErrorThread() */


/************************************************************************/
/* Function	   : remCreateErrorThread									*/
/* Purpose	   : Create thread that blinks remote LEDs to indicate error*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
Void remCreateErrorThread()
{
	task_create(remErrorThread, NULL, "ErrorBlinky");

} /* remCreateErrorThread() */
