/************************************************************************/
/* 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 <stdint.h>
#include <mvLib.h>			/* defs for this module		*/
#include <mvConv.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 MVConvStruct	mvConv;

static MBool		mvDebug = FALSE;

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

//#define MUTEX

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


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

    INIT_MUTEX();

} /* initMVConvLib() */


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


/************************************************************************/
/* Function    : openMVConv						*/
/* Purpose     : Establish ModBus connection to MVConv Board		*/
/* Inputs      : Device name, baud rate, slave ID, parity, data bits, stop bits*/
/* Outputs     : MVConvHandle, or NULL if failed			*/
/************************************************************************/
MVConvHandle	openMVConv(char *dev, int baud, int slaveID,
			   char parity, int data_bit, int stop_bit)
{
    struct timeval	tmout;

    if (mvDebug)
	printf("openMVConv(%s, %d, %d, %c, %d, %d)\n",
	       dev, baud, slaveID, parity, data_bit, stop_bit);

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


    LOCK();

    if ((mvConv.useCount > 0) && (mvConv.slaveID == slaveID) &&
	strncmp(dev, mvConv.devName, DEVNAME_LEN))
    {
	mvConv.useCount++;
	UNLOCK();
	return(&mvConv);
    }

    if (mvConv.useCount == 0)
    {
	if ((mvConv.ctx = modbus_new_rtu(dev, baud, parity, data_bit, stop_bit)) == NULL)
	{
	    if (mvDebug)
		fprintf(stderr, "Failed modbus_new_rtu(): %s\n",
			modbus_strerror(errno));
	    UNLOCK();
	    return(NULL);
	}

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

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

	mvConv.useCount = 1;
	strncpy(mvConv.devName, dev, DEVNAME_LEN);
	modbus_rtu_set_serial_mode(mvConv.ctx, MODBUS_RTU_RS232);
	tmout.tv_sec = 0;
	tmout.tv_usec = RESPONSE_TIMEOUT;
	modbus_set_response_timeout(mvConv.ctx, &tmout);
	UNLOCK();
	return(&mvConv);
    }

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


/************************************************************************/
/* Function    : closeMVConv						*/
/* Purpose     : Close ModBus connection to MVConv			*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
Errno closeMVConv(MVConvHandle mvh)
{
    if (mvh == NULL)
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (mvDebug)
	printf("closeMVConv(%s[%d])\n", mvh->devName, mvh->slaveID);

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

    UNLOCK();
    return(OK);

} /* closeMVConv() */


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

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

void mvModbusGetResponseTimeout(MVConvHandle mvh, struct timeval *timeout)
{
    modbus_get_response_timeout(mvh->ctx, timeout);
}

void mvModbusSetResponseTimeout(MVConvHandle mvh, struct timeval *timeout)
{
    modbus_set_response_timeout(mvh->ctx, timeout);
}

void mvModbusGetByteTimeout(MVConvHandle mvh,  struct timeval *timeout)
{
    modbus_get_byte_timeout(mvh->ctx, timeout);
}

void mvModbusSetByteTimeout(MVConvHandle mvh,  struct timeval *timeout)
{
    modbus_set_byte_timeout(mvh->ctx, timeout);
}



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

    if ((mvh == NULL) || (port >= PWR_CHANS))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (mvDebug)
	printf("mvPower(%s[%d], %d, %hd)\n", mvh->devName,
	       mvh->slaveID, port, onoff);

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

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

} /* mvPower() */


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

    if ((mvh == NULL) || (port >= PWR_CHANS))
    {
	errno = EINVAL;
	return(ERROR);
    }

    if (mvDebug)
	printf("mvPowerIsOn(%s[%d], %d)\n", mvh->devName, mvh->slaveID, port);

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

    if (rtn < 1)
	return(ERROR);

    return(val ? 1 : 0);

} /* mvPowerIsOn() */


/***********************************/
/*   Remote A/D Functions	   */
/***********************************/
/************************************************************************/
/* Function    : mvAnalogChan						*/
/* Purpose     : Read one analog channel from MVConv Board		*/
/* Inputs      : MVConvHandle, Analog channel, ptr for return value	*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	mvAnalogChan(MVConvHandle mvh, uint16_t chan, uint16_t *val)
{
    int		rtn;

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

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

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

} /* mvAnalogChan() */


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

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

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

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

} /* mvAnalogChans() */


/************************************************************************/
/* Function    : mvSetAnalogParms					*/
/* Purpose     : Set analog averaging parameters			*/
/* Inputs      : MVConvHandle, num ADC samples to avg, avereraging time ms*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	mvSetAnalogParms(MVConvHandle mvh, uint16_t numAvg, uint16_t avgMs)
{
    int		rtn;
    uint16_t	parms[2];

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

    parms[0] = numAvg;
    parms[1] = avgMs;

    LOCK();
    rtn = modbus_write_registers(mvh->ctx, AdcNumAvgReg, 2, parms);
    UNLOCK();

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

} /* mvSetAnalogParms() */


/************************************************************************/
/* Function    : mvGetAnalogParms					*/
/* Purpose     : Get analog averaging parameters			*/
/* Inputs      : MVConvHandle, Analog channel, num chans, 		*/
/*		 ptr to array of 2*nchans ushorts			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvGetAnalogParms(MVConvHandle mvh, uint16_t *numAvg, uint16_t *avgMs)
{
    Errno	rtn;
    uint16_t	parms[2];

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

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, AdcNumAvgReg, 2, parms);
    UNLOCK();

    if (rtn < 0)
	return(ERROR);

    if (numAvg != NULL)
	*numAvg = parms[0];

    if (avgMs != NULL)
	*avgMs = parms[1];

    return(OK);

} /* mvGetAnalogParms() */


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

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

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

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

} /* mvGetSerialNum() */


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

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

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

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

} /* mvGetSoftwareRev() */


/************************************************************************/
/* Function    : mvGetStatusVector					*/
/* Purpose     : Get Board Status Bits					*/
/* Inputs      : MVConvHandle, ptr to return vector			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	mvGetStatusVector(MVConvHandle mvh, uint16_t *vect)
{
    int		rtn;

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

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, SysStatusReg, 1, vect);
    UNLOCK();

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

}

/********************************/
/*   Ground Fault Functions	*/	
/********************************/

/************************************************************************/
/* Function    : mvSetGFCtrl						*/
/* Purpose     : Set Ground Fault control (which chans to sample)	*/
/* Inputs      : MVConvHandle, GFCtrl parm				*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvSetGFCtrl(MVConvHandle mvh, uint16_t gfBits)
{
    int		rtn;

    if ((mvh == NULL) || (gfBits & ~GFC_ALL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_write_register(mvh->ctx, GfCtrlReg, gfBits);
    UNLOCK();

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

} /* mvSetGFCtrl() */


/************************************************************************/
/* Function    : mvGetGFCtrl						*/
/* Purpose     : Get Ground Fault control (which chans to sample)	*/
/* Inputs      : MVConvHandle, ptr to return value			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvGetGFCtrl(MVConvHandle mvh, uint16_t *gfBits)
{
    int		rtn;

    if ((mvh == NULL) || (gfBits  == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, GfCtrlReg, 1, gfBits)
    UNLOCK();

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

} /* mvGetGFCtrl() */


/************************************************************************/
/* Function    : mvGetGFResult						*/
/* Purpose     : Get Ground Fault Results				*/
/* Inputs      : MVConvHandle, GF chan number and num chans, result ptr */
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvGetGFResult(MVConvHandle mvh, uint16_t chan, uint16_t nchans, uint16_t *result)
{
    int		rtn;
    uint16_t	tempRes[NUM_GF_CHANS];

    if ((mvh == NULL) || (chan < GF_48V_01) || (chan+nchans > GF_TEST_RET+1) ||
	(nchans > NUM_GF_CHANS))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, GfResultReg(chan), nchans, tempRes);
    UNLOCK();

    if (rtn < 0)
	return(ERROR);

    if (result != NULL)
	memcpy(result, tempRes, NUM_GF_CHANS * sizeof(uint16_t));
    
    return(OK);

} /* mvGetGFResult() */


/************************************************************************/
/* Function    : mvSetGFParms						*/
/* Purpose     : Set Parameters for Ground Fault conversions		*/
/* Inputs      : MVConvHandle, GF sample period (ms), num samples to ignore at start*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	mvSetGFParms(MVConvHandle mvh, uint16_t numSamps, uint16_t sampPeriodMs, uint16_t ignoreSamps)
{
    int		rtn;
    uint16_t	parms[3];

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

    parms[0] = numSamps;
    parms[1] = sampPeriodMs;
    parms[2] = ignoreSamps;

    LOCK();
    rtn = modbus_write_registers(mvh->ctx, GfNumSampsReg, 3, parms);
    UNLOCK();

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

} /* mvSetGFParms() */


/************************************************************************/
/* Function    : mvGetGFParms						*/
/* Purpose     : Get parameters for Ground Fault conversions		*/
/* Inputs      : MVConvHandle, Analog channel, num chans, 		*/
/* Inputs      : MVConvHandle, Ptrs to samp period and ignore samps	*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvGetGFParms(MVConvHandle mvh, uint16_t *numSamps, uint16_t *sampPeriodMs, uint16_t *ignoreSamps)
{
    Errno	rtn;
    uint16_t	parms[3];

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

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, GfNumSampsReg, 3, parms);
    UNLOCK();

    if (rtn < 0)
	return(ERROR);

    if (numSamps != NULL)
	*numSamps = parms[0];

    if (sampPeriodMs != NULL)
	*sampPeriodMs = parms[1];

    if (ignoreSamps != NULL)
    *ignoreSamps = parms[2];

    return(OK);

} /* mvGetGFParms() */


/************************************************************************/
/* Function    : mvGetGFSeq						*/
/* Purpose     : Get GF Sequence number					*/
/* Inputs      : MVConvHandle, place to put result			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvGetGFSeq(MVConvHandle mvh, uint16_t *seq)
{
    int		rtn;

    if ((mvh == NULL) || (seq == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, GfSeqReg, 1, seq);
    UNLOCK();

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

} /* mvGetGFSeq() */


/************************************************************************/
/* Function    : mvGetGFChan						*/
/* Purpose     : Get GF Channel currently being sampled			*/
/* Inputs      : MVConvHandle, place to put result			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvGetGFChan(MVConvHandle mvh, uint16_t *chan)
{
    int		rtn;

    if ((mvh == NULL) || (chan == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, GfChanReg, 1, chan);
    UNLOCK();

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

} /* mvGetGFChan() */


/************************************************************************/
/* Function    : mvGetWaterAlarms					*/
/* Purpose     : Get Water Alarm Bits					*/
/* Inputs      : MVConvHandle, ptr to return vector			*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno mvGetWaterAlarms(MVConvHandle mvh, uint16_t *alarms)
{
    int		rtn;

    if ((mvh == NULL) || (alarms == NULL))
    {
	errno = EINVAL;
	return(ERROR);
    }

    LOCK();
    rtn = modbus_read_registers(mvh->ctx, H2OSenseReg, 1, alarms);
    UNLOCK();

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

} /* mvGetWaterAlarms() */


/************************************************************************/
/* Function    : mvResetConfigMode					*/
/* Purpose     : Put MVConv board back into configuration mode		*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : Errno							*/
/************************************************************************/
Errno	mvResetConfigMode(MVConvHandle mvh)
{
    int		rtn;

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

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

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

} /* mvResetConfigMode() */


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

    if (mvh == 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(mvh->ctx, BlinkyDebugCoil, 2, coils);
    UNLOCK();

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

} /* mvBlinky() */
