/************************************************************************/
/* Copyright 2009-2013 MBARI						*/
/************************************************************************/
/* Summary  : Remote serial/power/analog I/O using SensorNode		*/
/* Filename : snLib.c							*/
/* Author   : Robert Herlien (rah)					*/
/* Project  : xFOCE							*/
/* Revision: 1.0							*/
/* Created  : 09/19/2012 from Oasis4 remote.c				*/
/*									    */
/* 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:						*/
/* 19sep2012 rah - created from remote.c in Oasis4			*/
/************************************************************************/

#include <sys/types.h>
#include <snLib.h>			/* defs for this module		*/
#include <sensorNode.h>			/* Library func defns		*/
#include <modbus.h>			/* libmodbus include		*/
#include <modbus-rtu.h>			/* libmodbus include		*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>

#define NumberOf(arr)	(sizeof(arr) / sizeof(arr[0]))

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

static SensorNodeStruct	sNode[SENSOR_NODES];

static MBool		snDebug = FALSE;

/****************************************/
/* MUTEX (Thread Safety) definitions	*/
/****************************************/

#define MUTEX

#ifdef MUTEX
static pthread_mutex_t	snMutex;
#define INIT_MUTEX()	pthread_mutex_init(&snMutex, NULL)
#define LOCK()		pthread_mutex_lock(&snMutex)
#define UNLOCK()	pthread_mutex_unlock(&snMutex)
#else
#define INIT_MUTEX()
#define LOCK()
#define UNLOCK()
#endif




/************************************************************************/
/* Function    : initSensorNodeLib					*/
/* Purpose     : Initialize this module					*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/************************************************************************/
void initSensorNodeLib(void)
{
    memset(sNode, 0, sizeof(sNode));

    INIT_MUTEX();

} /* initSensorNodeLib() */


/************************************************************************/
/* Function    : snSetDebug						*/
/* Purpose     : Turn debug messages on/off				*/
/* Inputs      : Boolean TRUE to turn on, FALSE for off			*/
/* Outputs     : None							*/
/************************************************************************/
void	snSetDebug(MBool onoff)
{
    snDebug = onoff;
}


/************************************************************************/
/* Function    : openSensorNode						*/
/* Purpose     : Establish ModBus connection to SensorNode		*/
/* Inputs      : Device name, baud rate, slave ID			*/
/* Outputs     : SensorNodeHandle, or NULL if failed			*/
/************************************************************************/
SensorNodeHandle openSensorNode(char *dev, int baud, int slaveID)
{
    SensorNodeStruct	*snp;
    int			i;
    if (snDebug)
	printf("openSensorNode(%s, %d, %d)\n", dev, baud, slaveID);

    if ((slaveID <= 0) || (slaveID > 247))
    {
	errno = EINVAL;
	return(NULL);
    }

    LOCK();

    for (i = 0; i < NumberOf(sNode); i++)
	if ((sNode[i].useCount > 0) && (sNode[i].slaveID == slaveID) &&
	    strncmp(dev, sNode[i].devName, DEVNAME_LEN))
	{
	    sNode[i].useCount++;
	    UNLOCK();
	    return(&sNode[i]);
	}

    for (i = 0; i < NumberOf(sNode); i++)
	if (sNode[i].useCount == 0)
	{
	    snp = &sNode[i];
	    if ((snp->ctx = modbus_new_rtu(dev, baud, 'N', 8, 1)) == NULL)
	    {
		if (snDebug)
		    fprintf(stderr, "Failed modbus_new_rtu(): %s\n",
			    modbus_strerror(errno));
		UNLOCK();
		return(NULL);
	    }

	    if (modbus_set_slave(snp->ctx, slaveID) < 0)
	    {
		if (snDebug)
		    fprintf(stderr, "Failed modbus_set_slave(): %s\n",
			    modbus_strerror(errno));
		modbus_close(snp->ctx);
		modbus_free(snp->ctx);
		snp->ctx = NULL;
		UNLOCK();
		return(NULL);
	    }

	    if (modbus_connect(snp->ctx) < 0)
	    {
		if (snDebug)
		    fprintf(stderr, "Failed modbus_connect(): %s\n",
			    modbus_strerror(errno));
		modbus_close(snp->ctx);
		modbus_free(snp->ctx);
		snp->ctx = NULL;
		UNLOCK();
		return(NULL);
	    }

	    snp->useCount = 1;
	    strncpy(snp->devName, dev, DEVNAME_LEN);
	    modbus_rtu_set_serial_mode(snp->ctx, MODBUS_RTU_RS232);
	    modbus_set_response_timeout(snp->ctx, 0, RESPONSE_TIMEOUT);
	    modbus_set_byte_timeout(snp->ctx, 0, BYTE_TIMEOUT);
//	    setSensorNodeModbusTimeout(snp, 200);
	    UNLOCK();
	    return(snp);
	}

    errno = ENOMEM;
    UNLOCK();
    return(NULL);
	
} /* openSensorNode() */


/************************************************************************/
/* Function    : closeSensorNode					*/
/* Purpose     : Close ModBus connection to SensorNode			*/
/* Inputs      : SensorNodeHandle					*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
Errno	closeSensorNode(SensorNodeHandle snh)
{
    if (snh == NULL)
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("closeSensorNode(%s[%d])\n", snh->devName, snh->slaveID);

    LOCK();
    if (--snh->useCount == 0)
    {
	modbus_close(snh->ctx);
	modbus_free(snh->ctx);
	snh->ctx = NULL;
    }

    UNLOCK();
    return(OK);

} /* closeSensorConnection() */


/************************************************/
/* Functions to manipulate libmodbus stack	*/
/************************************************/

/************************************************************************/
/* Function    : modbusSetDebug						*/
/* Purpose     : Turn modbus stack debug messages on/off		*/
/* Inputs      : SensorNodeHandle, boolean TRUE to turn on, FALSE for off*/
/* Outputs     : None							*/
/************************************************************************/
void	snModbusSetDebug(SensorNodeHandle snh, MBool onoff)
{
    modbus_set_debug(snh->ctx, (int)onoff);
}

void snModbusGetResponseTimeout(SensorNodeHandle snh, struct timeval *timeout)
{
    uint32_t tmSec, tmUsec;

    modbus_get_response_timeout(snh->ctx, &tmSec, &tmUsec);
    if (timeout != NULL)
    {
      timeout->tv_sec = (time_t)tmSec;
      timeout->tv_usec = (long)tmUsec;
    }
}

void snModbusSetResponseTimeout(SensorNodeHandle snh, struct timeval *timeout)
{
    modbus_set_response_timeout(snh->ctx, timeout->tv_sec, timeout->tv_usec);
}

void snModbusGetByteTimeout(SensorNodeHandle snh,  struct timeval *timeout)
{
    uint32_t tmSec, tmUsec;

    modbus_get_byte_timeout(snh->ctx, &tmSec, &tmUsec);
    if (timeout != NULL)
    {
      timeout->tv_sec = (time_t)tmSec;
      timeout->tv_usec = (long)tmUsec;
    }
}

void snModbusSetByteTimeout(SensorNodeHandle snh,  struct timeval *timeout)
{
    modbus_set_byte_timeout(snh->ctx, timeout->tv_sec, timeout->tv_usec);
}

Errno	setSensorNodeModbusTimeout(SensorNodeHandle snh, uint16_t tmout50us)
{
    int	rtn;

    if ((snh == NULL) || (tmout50us < 20))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_write_register(snh->ctx,  MbTimeoutReg, tmout50us);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);
}

Errno	getSensorNodeModbusTimeout(SensorNodeHandle snh, uint16_t *tmout50us)
{
    int		rtn;

    if ((snh == NULL) || (tmout50us == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, MbTimeoutReg, 1, tmout50us);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

}


/************************/
/* Serial I/O		*/
/************************/

/************************************************************************/
/* Function    : snOpenSerial						*/
/* Purpose     : Open a SensorNode serial port				*/
/* Inputs      : SensorNodeHandle, serial port number			*/
/* Outputs     : RemSerHandle for opened port, or NULL if failed	*/
/************************************************************************/
RemSerHandle	snOpenSerial(SensorNodeHandle snh, uint16_t port)
{
    RemSerPort	*rsp;
    int		rtn;

    if ((snh == NULL) || (port >= SN_SER_PORTS))
    {
	errno = EINVAL;
	return(NULL);
    }

    if (snDebug)
	printf("snOpenSerial(%s[%d], %u)\n", snh->devName, snh->slaveID, port);

    LOCK();
    rsp = &snh->serPorts[port];
    rsp->snp = snh;
    rsp->ctx = snh->ctx;
    rsp->port = port;
    
    if (rsp->useCount++ == 0)
    {
	rsp->bufIndex = 2;
	rsp->bufChars = 0;
    }

    rtn = modbus_write_bit(snh->ctx, TxEnable(port), TRUE);
    UNLOCK();

    if (rtn < 0)
    {
	if (snDebug)
	    fprintf(stderr, "Failed TxEnable on %s port %d:  %s\n",
		    snh->devName, port, modbus_strerror(errno));
	
	snCloseSerial(rsp);
	return(NULL);
    }

    return(rsp);

} /* snOpenSerial() */


/************************************************************************/
/* Function    : snCloseSerial						*/
/* Purpose     : Close remote serial port				*/
/* Inputs      : RemSerHandle						*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
Errno	snCloseSerial(RemSerHandle rsh)
{
    if ((rsh == NULL) || (rsh->ctx == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snCloseSerial(%s[%d])\n", rsh->snp->devName, rsh->snp->slaveID);

    LOCK();

    if (--rsh->useCount == 0)
    {
	modbus_write_bit(rsh->ctx, TxEnable(rsh->port), FALSE);
	rsh->snp = NULL;		/*Just in case someone reuses rsh*/
	rsh->ctx = NULL;
    }

    UNLOCK();
    return(OK);

} /* snCloseSerial() */


/************************************************************************/
/* Function    : snSerInit						*/
/* Purpose     : Set baud rate and mode for a remote serial port	*/
/* Inputs      : RemSerHandle, baud rate, parity ('N','E', or 'O'),	*/
/*		 stop bits (1 or 2)					*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
Errno snSerInit(RemSerHandle rsh, int baud, char parity, int stop)
{
    uint16_t	regs[2];
    Errno	rtn;

    if ((rsh == NULL) || (rsh->ctx == NULL))	
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snSerInit(%s[%d,%d], %d, %c, %d)\n", 
	       rsh->snp->devName, rsh->snp->slaveID, rsh->port,
	       baud, parity, stop);

    regs[0] = baud >> 2;

    switch(parity)
    {
      case 'O':
      case 'o':
	  regs[1] = REM_ODD_PTY | REM_RCV_ENBL;
	  break;

      case 'E':
      case 'e':
	  regs[1] = REM_EVEN_PTY | REM_RCV_ENBL;
	  break;
	
      default:
	  regs[1] = REM_NO_PTY | REM_RCV_ENBL;
	  break;
    }

    if (stop == 2)
	regs[1] |= REM_STOP2;

    LOCK();
    rtn = modbus_write_registers(rsh->ctx, BaudReg(rsh->port), 2, regs);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snSerInit() */


/************************************************************************/
/* Function    : snSerRead						*/
/* Purpose     : Read a remote serial port				*/
/* Inputs      : Remote serial handle, buffer ptr, length		*/
/* Outputs     : Number of chars read					*/
/************************************************************************/
uint32_t snSerRead(RemSerHandle rsh, char *buffer, uint32_t len)
{
    uint32_t	bytesRead;
    char	*p;
    uint16_t	readLen, w, rxQueued;
    int		rtn;

    if ((rsh == NULL) || (rsh->snp == NULL) || (buffer == NULL))
    {
	errno = EINVAL;
	return(0);
    }

    if (snDebug)
	printf("snSerRead(%s[%d,%d], p, %lu)\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port, (unsigned long)len);

    LOCK();
    for (p = buffer, bytesRead = 0; bytesRead < len; )
    {
	if (rsh->bufChars > 0)
	{
	    w = rsh->rbuf[rsh->bufIndex/2];
	    *p++ = (rsh->bufIndex & 1) ? (w & 0xff) : (w >> 8);
	    rsh->bufChars--;
	    rsh->bufIndex++;
	    bytesRead++;
	}
	else
	{
	    readLen = (len+1)/2;
	    if (readLen > SERREG_SIZE)
		readLen = SERREG_SIZE;
	    if (readLen < MIN_READ)
		readLen = MIN_READ;

	    rtn = modbus_read_registers(rsh->ctx, RxQReg(rsh->port),
					readLen+1, rsh->rbuf);

	    rxQueued = rsh->rbuf[0];

	    if (snDebug && (rxQueued > 0))
		printf("snSerRead got %d chars\n", rxQueued);

	    if ((rtn == ERROR) || (rxQueued == 0))
	    {
		UNLOCK();
		return(bytesRead);
	    }

	    rsh->bufIndex = 2;		//Point past rxQueued
	    rsh->bufChars = (rxQueued >= 2*readLen) ? 2*readLen : rxQueued;
	}
    }

    UNLOCK();

    return(bytesRead);

} /* snSerRead() */


/************************************************************************/
/* Function    : snSerWrite						*/
/* Purpose     : Write a remote serial port				*/
/* Inputs      : Remote serial handle, buffer ptr, length		*/
/* Outputs     : Number of chars written				*/
/************************************************************************/
uint32_t snSerWrite(RemSerHandle rsh, const char *buffer, uint32_t len)
{
    uint32_t	bytesWritten;
    const char	*p, *q;
    uint16_t	port, writeLen, writeQueuedReg, writeQueued, zeroCount, i;
    uint16_t	*wbufp;
    Errno	rtn;

    if ((rsh == NULL) || (rsh->ctx == NULL) || (buffer == NULL))
    {
	errno = EINVAL;
	return(0);
    }

    if (snDebug)
	printf("snSerWrite(%s[%d,%d], p, %lu)\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port, (unsigned long)len);

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

	LOCK();
	wbufp = rsh->wbuf;
	*wbufp++ = writeLen;
	
	if (snDebug)
	    printf("Sending %s\n", p);

	for (i = 0, q = p; i < writeLen; i += 2)
	{
	    *wbufp = (*q++ << 8);
	    *wbufp++ |= (*q++ & 0xff);
	}

	rtn = modbus_write_and_read_registers(rsh->ctx, TxReqReg(port), 
					      (writeLen+3)/2, rsh->wbuf,
					      TxQueuedReg(port), 1, &writeQueuedReg);
	UNLOCK();

	if (rtn < 0)
	    return(bytesWritten);
    
	writeQueued = writeQueuedReg & 0xff;
	bytesWritten += writeQueued;
	p += writeQueued;

	if (writeQueued < writeLen)
	    usleep(50000L);

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

    return(bytesWritten);

} /* snSerWrite() */


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

    if ((rsh == NULL) || (rsh->ctx == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snSerDrain(%s[%d,%d], %lu)\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port, (unsigned long)tmout);

    for (tmRemain = tmout; tmRemain > 0; tmRemain -= 10)
    {
	LOCK();
	rtn = modbus_read_registers(rsh->ctx, SerStatusReg, 1, &status);
	UNLOCK();

	if (rtn < 1)
	    return(ERROR);

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

	usleep(100000L);
    }

    return(OK);

} /* snSerDrain() */


/************************************************************************/
/* Function    : remSerIFlush						*/
/* Purpose     : Flush remote serial input stream			*/
/* Inputs      : RemSerHandle		 				*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno snSerIFlush(RemSerHandle rsh)
{
    int	rtn;

    if ((rsh == NULL) || (rsh->ctx == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snSerIFlush(%s[%d,%d])\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port);

    LOCK();
    rsh->bufChars = 0;
    rtn = modbus_write_register(rsh->ctx,  RxCtlReg(rsh->port), REM_RX_FLUSH);
    UNLOCK();
    
    return((rtn > 0) ? OK : ERROR);

} /* snSerIFlush() */


/************************************************************************/
/* Function    : remSerOFlush						*/
/* Purpose     : Flush remote serial output stream			*/
/* Inputs      : RemSerHandle		 				*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno snSerOFlush(RemSerHandle rsh)
{
    int	rtn;

    if ((rsh == NULL) || (rsh->ctx == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snSerOFlush(%s[%d,%d])\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port);

    LOCK();
    rtn = modbus_write_register(rsh->ctx,  TxCtlReg(rsh->port), REM_TX_FLUSH);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snSerOFlush() */


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

} /* snSerRxCount() */


/************************************************************************/
/* Function    : snSerBreak						*/
/* Purpose     : Send a break character to remote output stream		*/
/* Inputs      : RemSerHandle, break time in ms				*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno snSerBreak(RemSerHandle rsh, uint16_t breakTime)
{
    int	rtn;

    if ( (rsh == NULL) || (rsh->ctx == NULL) )
    {
	errno = EINVAL;
	return(ERROR);
    }
    
    if (snDebug)
	printf("snSerBreak(%s[%d,%d], %hu)\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port, breakTime);

    /* set break bit */
    LOCK();
    modbus_write_register(rsh->ctx, TxBreakReg(rsh->port), REM_BREAK);
    UNLOCK();

    /* wait for break delay */
    usleep((useconds_t)breakTime * 1000L);

    /* clear break bit */
    LOCK();
    rtn = modbus_write_register(rsh->ctx, TxBreakReg(rsh->port), 0);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snSerBreak() */


/************************************************************************/
/* Function    : snSerSetTxEnbl						*/
/* Purpose     : Set/Clear Tx Enable for SensorNode serial port		*/
/* Inputs      : RemSerHandle, Boolean TRUE to set TxEn, FALSE to clear	*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snSerSetTxEnbl(RemSerHandle rsh, MBool enbl)
{
    int rtn;

    if ( (rsh == NULL) || (rsh->ctx == NULL) )
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_write_bit(rsh->ctx, TxEnable(rsh->port), (int)enbl);
    UNLOCK();

    if (snDebug)
	printf("snSerSetTxEnbl(%s[%d,%d], %hd)\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port, enbl);

    return((rtn > 0) ? OK : ERROR);

} /* snSerSetTxEnbl() */


/************************************************************************/
/* Function    : snSerGetErrFlags					*/
/* Purpose     : Get error flags for serial port			*/
/* Inputs      : RemSerHandle, ptr to where to return flags		*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno snSerGetErrFlags(RemSerHandle rsh, uint16_t *flags)
{
    int rtn;

    if ( (rsh == NULL) || (rsh->ctx == NULL) )
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(rsh->ctx, RxErrReg(rsh->port), 1, flags);
    UNLOCK();

    if (snDebug)
	printf("snSerGetErrFlags(%s[%d,%d] = 0x%hx)\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port, *flags);

    return((rtn > 0) ? OK : ERROR);

}


/************************************************************************/
/* Function    : snSerClearErrFlags					*/
/* Purpose     : Clear error flags for serial port			*/
/* Inputs      : RemSerHandle, flags byte (1 to clear corresponding flag*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno snSerClearErrFlags(RemSerHandle rsh, uint16_t flags)
{
    int rtn;

    if ( (rsh == NULL) || (rsh->ctx == NULL) )
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snSerClearErrFlags(%s[%d,%d], %hd)\n", rsh->snp->devName,
	       rsh->snp->slaveID, rsh->port, flags);

    LOCK();
    rtn = modbus_write_register(rsh->ctx, RxCtlReg(rsh->port), 
				(int)(flags & (PERR_BIT | FERR_BIT | OERR_BIT | XERR_BIT | YERR_BIT)));
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

}


/***********************************/
/*   Remote Power Functions	   */
/***********************************/
/************************************************************************/
/* Function    : snPower						*/
/* Purpose     : Turn remote power bit on/off on SensorNode		*/
/* Inputs      : SensorNodeHandle, Pwr bit num, Boolean TRUE/FALSE for on/off*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snPower(SensorNodeHandle snh, uint16_t port, MBool onoff)
{
    int rtn;

    if ((snh == NULL) || (port >= SN_PWR_PORTS))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snPower(%s[%d], %d, %hd)\n", snh->devName,
	       snh->slaveID, port, onoff);

    LOCK();
    rtn = modbus_write_bit(snh->ctx, PowerBit(port), (int)onoff);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snPower() */


/************************************************************************/
/* Function    : snPowerIsOn						*/
/* Purpose     : Return status of power bit on/off on SensorNode	*/
/* Inputs      : SensorNodeHandle, Pwr bit num				*/
/* Outputs     : 1 if on, 0 if off, ERROR (-1) if I/O error		*/
/************************************************************************/
int16_t	snPowerIsOn(SensorNodeHandle snh, uint16_t port)
{
    int		rtn;
    uint8_t	val;

    if ((snh == NULL) || (port >= SN_PWR_PORTS))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snPowerIsOn(%s[%d], %d)\n", snh->devName, snh->slaveID, port);

    LOCK();
    rtn = modbus_read_bits(snh->ctx, PowerBit(port), 1, &val);
    UNLOCK();

    if (rtn < 1)
	return(ERROR);

    return(val ? 1 : 0);

} /* snPowerIsOn() */


/************************************************************************/
/* Function    : snGetPowerVector					*/
/* Purpose     : Return vector of power bits on/off on SensorNode	*/
/* Inputs      : SensorNodeHandle, Ptr to put vector			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snGetPowerVector(SensorNodeHandle snh, uint16_t *vect)
{
    int		rtn;

    if ((snh == NULL) || (vect == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (snDebug)
	printf("snGetPowerVector(%s[%d])\n", snh->devName, snh->slaveID);

    LOCK();
    rtn = modbus_read_registers(snh->ctx, PwrStatusReg, 1, vect);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snGetPowerVector() */


/***********************************/
/*   Remote A/D Functions	   */
/***********************************/
/************************************************************************/
/* Function    : snAnalogChan						*/
/* Purpose     : Read one analog channel from SensorNode		*/
/* Inputs      : SensorNodeHandle, Analog channel, ptr for return value	*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snAnalogChan(SensorNodeHandle snh, uint16_t chan, uint16_t *val)
{
    int		rtn;

    if ((snh == NULL) || (chan >= SN_AD_CHANS) || (val == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, AnalogReg(chan), 1, val);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snAnalog() */


/************************************************************************/
/* Function    : snAnalogChans						*/
/* Purpose     : Read multiple analog channels from SensorNode		*/
/* Inputs      : SensorNodeHandle, Analog channel, number of chans to read,*/
/*		 ptr to array of uint16_ts for return values		*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snAnalogChans(SensorNodeHandle snh, uint16_t chan, uint16_t nchans, uint16_t *val)
{
    int		rtn;

    if ((snh == NULL) || ((chan+nchans) > SN_AD_CHANS) || (val == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, AnalogReg(chan), nchans, val);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snAnalog() */


/************************************************************************/
/* Function    : snSetAnalogParms					*/
/* Purpose     : Set analog averaging parameters			*/
/* Inputs      : SensorNodeHandle, Analog channel, num chans, 		*/
/*		 ptr to array of 2*nchans ushorts			*/
/* Outputs     : Errno							*/
/* Comments    : parms are numAvg, avgMs for 1st channel, then same for each chan*/
/************************************************************************/
Errno	snSetAnalogParms(SensorNodeHandle snh, uint16_t chan, uint16_t nchans, uint16_t *parms)
{
    int		rtn;
 
    if ((snh == NULL) || (chan + nchans > SN_AD_CHANS) || (parms == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_write_registers(snh->ctx, AnalogAvgReg(chan), 2*nchans, parms);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snSetAnalogParms() */


/************************************************************************/
/* Function    : snGetAnalogParms					*/
/* Purpose     : Get analog averaging parameters			*/
/* Inputs      : SensorNodeHandle, Analog channel, num chans, 		*/
/*		 ptr to array of 2*nchans ushorts			*/
/* Outputs     : Errno							*/
/* Comments    : parms are numAvg, avgMs for 1st channel, then same for each chan*/
/************************************************************************/
Errno	snGetAnalogParms(SensorNodeHandle snh, uint16_t chan, uint16_t nchans, uint16_t *parms)
{
    Errno	rtn;

    if ((snh == NULL) || (chan + nchans > SN_AD_CHANS) || (parms == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, AnalogAvgReg(chan), 2*nchans, parms);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snGetAnalogParms() */


/********************************/
/*   Misc Functions		*/	
/********************************/

/************************************************************************/
/* Function    : snGetSerialNum						*/
/* Purpose     : Get Board Serial Number				*/
/* Inputs      : SensorNodeHandle, ptr to return serial num		*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snGetSerialNum(SensorNodeHandle snh, uint16_t *num)
{
    int		rtn;

    if ((snh == NULL) || (num == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, SerialNum, 1, num);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

}


/************************************************************************/
/* Function    : snGetSoftwareRev					*/
/* Purpose     : Get Board Firmware Revision number			*/
/* Inputs      : SensorNodeHandle, ptr to return software rev		*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snGetSoftwareRev(SensorNodeHandle snh, uint16_t *num)
{
    int		rtn;

    if ((snh == NULL) || (num == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, SoftwareRev, 1, num);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

}


/************************************************************************/
/* Function    : snGetBoardConfig					*/
/* Purpose     : Get Board Configuration Register			*/
/* Inputs      : SensorNodeHandle, ptr to return config			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snGetBoardConfig(SensorNodeHandle snh, uint32_t *cfg)
{
    int		rtn;
    uint16_t	cfgVal[2];

    if ((snh == NULL) || (cfg == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, Config1Reg, 2, cfgVal);
    *cfg = (cfgVal[0] << 16) | cfgVal[1];
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

}

/************************************************************************/
/* Function    : snGetBoardConfig					*/
/* Purpose     : Get Hardware Config Register (DIP switches on board)	*/
/* Inputs      : SensorNodeHandle, ptr to return config			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snGetHwConfig(SensorNodeHandle snh, uint16_t *cfg)
{
    int		rtn;

    if ((snh == NULL) || (cfg == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(snh->ctx, HwConfigReg, 1, cfg);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

}

/************************************************************************/
/* Function    : snSetDCAlwaysOn					*/
/* Purpose     : Set SensorNode board to always enable the DC-DC converter*/
/* Inputs      : SensorNodeHandle, Boolean TRUE to perm enable converter*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snSetDCAlwaysOn(SensorNodeHandle snh, MBool on)
{
    int		rtn;

    if (snh == NULL)
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_write_bit(snh->ctx, DCOnCoil, (int)on);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snDCAlwaysOn() */


/************************************************************************/
/* Function    : snResetConfigMode					*/
/* Purpose     : Put SensorNode board back into config mode		*/
/* Inputs      : SensorNodeHandle					*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snResetConfigMode(SensorNodeHandle snh)
{
    int		rtn;

    if (snh == NULL)
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_write_bit(snh->ctx, ConfigModeCoil, 1);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);

} /* snResetConfigMode() */


/************************************************************************/
/* Function    : snBlinky						*/
/* Purpose     : Turn Blinky LED on/off/debug				*/
/* Inputs      : SensorNodeHandle, BlinkyState				*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	snBlinky(SensorNodeHandle snh, BlinkyState state)
{
    int		rtn;
    uint8_t	coils[2];

    if (snh == NULL)
    {
	errno = EINVAL;
	return(ERROR);
    }

    switch(state)
    {
      case BlinkyOff:
	  coils[0] = 0;
	  coils[1] = 0;
	  break;

      case BlinkyOn:
	  coils[0] = 0;
	  coils[1] = 1;
	  break;

      default:
	  coils[0] = 1;
	  coils[1] = 0;
	  break;
    }

    LOCK();
    rtn = modbus_write_bits(snh->ctx, BlinkyDebugCoil, 2, coils);
    UNLOCK();

    return((rtn > 0) ? OK : ERROR);
}
