
/* Copyright 2013-2014 MBARI						*/
/************************************************************************/
/* Summary  : Menu-driven program to test mvLib functions under Linux or Cygwin*/
/* Filename : mvTest.c							*/
/* Author   : Robert Herlien (rah)					*/
/* Project  : xFOCE							*/
/* Revision: 1.0							*/
/* Created  : 11/12/2014						*/
/*									    */
/* 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:						*/
/* 12nov2014 rah - created						*/
/************************************************************************/

#include <modbus.h>		/* libmodbus include			*/
#include <modbus-rtu.h>		/* libmodbus include			*/
#include <mvConv.h>		/* Include for MV Converter board	*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <termios.h>


typedef struct			/****************************************/
{				/* Struct to hold commands		*/
    char  *name;		/* Command name				*/
    int   (*func)();		/* Function to execute			*/
    int   nparms;		/* Number of parms (0 or 1 only for now)*/
    uint  parmMax;		/* Max value of parm (0 to parmMax)	*/
} CommandStruct;		/****************************************/

typedef struct			/****************************************/
{				/* Analog channel descriptors		*/
    char  *name;		/* Channel name				*/
    char  *units;		/* Name of engineering units		*/
    double mult;		/* Multiplier				*/
    double offset;		/* Offset				*/
} AnalogDesc;			/****************************************/


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

/* Return codes from function execution		*/
#define CONTINUE 0
#define EXIT	(-2)


/********************************/
/*	Forward Declarations	*/
/********************************/

void	usage(char **argv);
void	rawMode(void);
int	menu(void);
int	printModbusError(char *msg);
CommandStruct *getCommand(uint *parmp);
int	vicorOn(MVConvHandle mvh, uint port);
int	vicorOff(MVConvHandle mvh, uint port);
int	pwrRelayOn(MVConvHandle mvh, uint port);
int	pwrRelayOff(MVConvHandle mvh, uint port);
int	fanOn(MVConvHandle mvh, uint port);
int	fanOff(MVConvHandle mvh, uint port);
void	printAnalogChan(uint chan, uint16_t cnt);
int	analogRead(MVConvHandle mvh, uint port);
int	analogAllRead(MVConvHandle mvh);
int	analogParms(MVConvHandle mvh);
int	analogNumAvg(MVConvHandle mvh, uint numAvg);
int	analogAvgMs(MVConvHandle mvh, uint avgMs);
int	blinkyCmd(MVConvHandle, uint parm);
int	serNum(MVConvHandle mvh);
int	softwareRev(MVConvHandle mvh);
int	statusVector(MVConvHandle mvh);
int	configMode(MVConvHandle mvh);
int	exitProg(void);
int	testCmd(MVConvHandle, uint parm);

int	setFan1(MVConvHandle mvh, uint parm);
int	setFan2(MVConvHandle mvh, uint parm);
int	getFans(MVConvHandle mvh);
int	fanSetpt1(MVConvHandle mvh, uint deg);
int	fanSetpt2(MVConvHandle mvh, uint deg);

int	groundFault(MVConvHandle mvh);
int	groundFaultParms(MVConvHandle mvh);
int	groundFaultCtrl(MVConvHandle mvh, uint parm);
int	groundFaultNumSamps(MVConvHandle mvh, uint num);
int	groundFaultSampPeriod(MVConvHandle mvh, uint per);
int	groundFaultIgnoreSamps(MVConvHandle mvh, uint ign);


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

static MBool	mvDebug = FALSE;
static int	slaveID = 1, baud = 115200, modDebug = FALSE;
static char	*modPort = "/dev/ttyS7";
static char	modPortBuf[64];
static char	inputBuf[256];

static CommandStruct cmdTbl[] = {
    {"von", vicorOn, 1, 1},
    {"voff", vicorOff, 1, 1},
    {"on", pwrRelayOn, 1, 1},
    {"off", pwrRelayOff, 1, 1},
    {"fon", fanOn, 1, 1},
    {"foff", fanOff, 1, 1},
    {"aa", analogAllRead, 0, 0},
    {"ar", analogRead, 1, MV_AD_CHANS-1},
    {"an", analogNumAvg, 1, 99},
    {"am", analogAvgMs, 1, 99},
    {"ap", analogParms, 0, 0},
    {"b", blinkyCmd, 1, 2},
    {"sn", serNum, 0, 0},
    {"sr", softwareRev, 0, 0}, 
    {"sv", statusVector, 0, 0}, 
    {"cm", configMode, 0, 0},
    {"gfr", groundFault, 0, 0}, 
    {"gfp", groundFaultParms, 0, 0},
    {"gfc", groundFaultCtrl, 1, 0x3f},
    {"gfn", groundFaultNumSamps, 1, 65535},
    {"gfs", groundFaultSampPeriod, 1, 65535},
    {"gfi", groundFaultIgnoreSamps, 1, 65535},
    {"h", menu, 0, 0},
    {"x", exitProg, 0, 0},
    {"q", exitProg, 0, 0},
    {"test", testCmd, 1, 250}
};


const char *volt = "volts";
const char *amp  = "amps";
const char *deg  = "degrees C";

static AnalogDesc analogDesc[] = {
    {"VICOR1_TEMP", "deg C",  33.33, 0.0},
    {"VICOR2_TEMP", "deg C",  33.33, 0.0},
    {"VICOR3_TEMP", "deg C",  33.33, 0.0},
    {"HEATSINK",   "deg C",   33.33, 0.0},
    {"48V_1_CURR", "amps",     4.17, 0.0},
    {"48V_1_VOLT", "volts",   20.00, 0.0},
    {"48V_2_CURR", "amps",     4.17, 0.0},
    {"48V_2_VOLT", "volts",    20.00, 0.0},
    {"24V_CURR",   "amps",     1.11, 0.0},
    {"24V_VOLT",   "volts",   10.00, 0.0},
    {"3.3V_CURR",  "amps",     1.11, 0.0},
    {"3.3V_VOLT",  "volts",    1.20, 0.0},
    {"AMB_HUM",    "%RH",     49.87, -37.7},
    {"AMB_TEMP",   "deg C",   37.50, -37.7}
};

static char *gfChans[] = {
    "48V_01", "48V_01_RET", "48V_02", "48V_02_RET", "GF_TEST", "GF_TEST_RET"
};

/************************************************************************/
/* Function    : main							*/
/* Purpose     : Main entry point					*/
/* Inputs      : argc, argv						*/
/* Outputs     : Application return code				*/
/************************************************************************/
int	main(int argc, char **argv)
{
    int			opt, parm;
    uint		cmdParm;
    MVConvHandle	mvh;
    CommandStruct	*cmdp;

    while ((opt = getopt(argc, argv, "a:b:d:hp:")) != -1)
	switch(opt)
	{
	  case 'a':
	      slaveID = atoi(optarg);
	      if ((slaveID < 1) || (slaveID > 247))
	      {
		  printf("Invalid slave ID: %d.  Setting to 1\n", slaveID);
		  slaveID = 1;
	      }
	      break;

	  case 'b':
	      baud = atoi(optarg);
	      break;

	  case 'd':
	      parm = atoi(optarg);
	      if (parm & 1)
		  mvDebug = TRUE;
	      if (parm & 2)
		  modDebug = TRUE;
	      break;

	  case 'p':
	      modPort = optarg;

	      /* Cygwin no longer likes COMn.  Translate to /dev/ttyS{n-1}	*/
	      if (strncasecmp(modPort, "COM", 3) == 0)
		  if (sscanf(modPort+3, "%d", &opt) > 0)
		  {
		      sprintf(modPortBuf, "/dev/ttyS%u", opt-1);
		      modPort = modPortBuf;
		  }
	      break;

	  case 'h':
	      usage(argv);
	      return(0);
	}

    printf("Testing Modbus port %s  baud %u  slave %u\n", modPort, baud, slaveID);

    initMVConvLib();
    mvSetDebug(mvDebug);

    if ((mvh = openMVConv(modPort, baud, slaveID, 'N', 8, 1)) == NULL)
    {
	printModbusError("Failure in openMVConv");
	exit(1);
    }

    rawMode();
    menu();

    while (TRUE)
    {
	cmdParm = 0;
	if ((cmdp = getCommand(&cmdParm)) != NULL)
	{
	    printf("\n");
	    if ((*cmdp->func)(mvh, cmdParm) == EXIT)
		return(0);
	}
    }

    return(0);

} /* main() */


/************************************************************************/
/* Function    : printModbusError					*/
/* Purpose     : Print an error from libmodbus				*/
/* Inputs      : Preamble message					*/
/* Outputs     : ERROR							*/
/************************************************************************/
int printModbusError(char *msg)
{
    printf("%s: errno %d %s\n", msg, errno, modbus_strerror(errno));
    return(ERROR);
}


/************************************************************************/
/* Function    : rawMode						*/
/* Purpose     : Put input into raw mode				*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/************************************************************************/
void rawMode(void)
{
    struct termios	tio;

    tcgetattr(STDIN_FILENO, &tio);

    tio.c_lflag &= ~ICANON;
    tio.c_cc[VMIN] = 0;
    tio.c_cc[VTIME] = 0;

    tcsetattr(STDIN_FILENO, TCSANOW, &tio);

} /* rawMode() */


/************************************************************************/
/* Function    : usage							*/
/* Purpose     : Print usage message					*/
/* Inputs      : argv							*/
/* Outputs     : None							*/
/************************************************************************/
void usage(char **argv)
{
    printf("%s [-a <slaveAddr>] [-b <baud>] [-d] [-p <port>] [-h]\n\n",
	   argv[0]);
    printf("\t[-p <port>] Use serial port <port> to communicate with Modbus device\n");
    printf("\t\tDefaults to /dev/ttyS7 (equiv to COM8 in Cygwin)\n");
    printf("\t[-a <slaveAddr>] Communicate with Modbus slave <slaveAddr>, 1-247\n");
    printf("\t\tDefaults to 1\n");
    printf("\t[-b <baud>] Set Modbus baud rate.  Defaults to 115200\n");
    printf("\t[-d] Print debug messages\n");
    printf("\t[-h] Print this message\n");

} /* usage() */


/************************************************************************/
/* Function    : menu							*/
/* Purpose     : Print the menu						*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/************************************************************************/
int menu(void)
{
    printf("\nType a command:\n\n");
    printf("h for this help message\n");
    printf("x to exit this program\n\n");

    printf("von[0-1] to turn on a vicor\n");
    printf("voff[0-1] to turn off a vicor\n");
    printf("on[0-1] to turn on a FOCE power relay\n");
    printf("off[0-1] to turn off a FOCE power relay\n\n");
    printf("fon[0-1] to turn on a fan\n");
    printf("foff[0-1] to turn off a fan\n");

    printf("ar[0-15] to read an analog port\n");
    printf("aa to read all analog ports\n");
    printf("ap to read analog parameters\n");
    printf("an[0-99] to set analog numAvg (number samples to average)\n");
    printf("am[0-99] to set analog avgMs (ms between samples)\n\n");

    printf("sn to get board serial number\n");
    printf("sr to get board software revision number\n");
    printf("sv to get system status register\n");
    printf("cm to reset board to configuration mode\n");

    printf("gfr to get ground fault conversion results\n");
    printf("gfp to read ground fault parameters\n");
    printf("gfc to set ground fault control reg\n");
    printf("gfn to set ground fault number of samples\n");
    printf("gfs to set ground fault sample period\n");
    printf("gfi to set ground fault ignore sample reg\n");

    printf("b[0-2] to set blinky LED (0=off, 1=on, 2=debug)\n\n");
    return(OK);

} /* menu() */


/********************************************************/
/*  Support routines for getCommand()			*/
/********************************************************/

static CommandStruct  *findCommand(uint *parmp)
{
    int		i, nameSize;
    uint	parm;
    CommandStruct *csp;

    for (i = 0, csp = cmdTbl; i < NumberOf(cmdTbl); i++, csp++)
    {
	nameSize = strlen(csp->name);

	if (strncasecmp(inputBuf, csp->name, nameSize) == 0)
	{
	    if (csp->nparms == 0)
		return(csp);

	    if ((sscanf(&inputBuf[nameSize], "%u", &parm) >= 1) && (parmp != NULL))
		*parmp = parm;

	    return(csp);
	}
    }
    return(NULL);
}

static CommandStruct  *checkParm(CommandStruct *csp, uint *parmp)
{
    if (csp->nparms == 0)
	return(csp);
    if (parmp == NULL)
	return(NULL);
    if (*parmp <= csp->parmMax)
	return(csp);

    printf("\nBad parameter: %u\n", *parmp);
    return(NULL);
}

static uint decDigits(uint n)
{
    uint digits=1;

    while (n >= 10)
    {
	digits++;
	n /= 10;
    }

    return(digits);
}


/************************************************************************/
/* Function    : getCommand						*/
/* Purpose     : Get a command						*/
/* Inputs      : Ptr to place to put parm				*/
/* Outputs     : Ptr to command to execute				*/
/************************************************************************/
CommandStruct  *getCommand(uint *parmp)
{
    int		nr, nchars, nameSize, digits;
    char	*p;
    CommandStruct *csp;

    printf("Enter a command:  ");
    fflush(stdout);
    memset(inputBuf, 0, sizeof(inputBuf));
    nchars = 0;
    p = inputBuf;

    while( nchars < sizeof(inputBuf) - 1 )
	if ((nr = read(STDIN_FILENO, p, 1)) > 0)
	{
	    if ((*p == '\n') || (*p == '\r') || (*p == 4))
	    {
		if ((csp = findCommand(parmp)) == NULL)
		    return(NULL);
		return(checkParm(csp, parmp));
	    }

	    nchars += nr;
	    p += nr;
	    *p = '\0';

	    if ((csp = findCommand(parmp)) != NULL)
	    {
		if (csp->nparms == 0)
		    return(csp);

		nameSize = strlen(csp->name);
		digits = decDigits(csp->parmMax);

		if (nchars >= nameSize + digits)
		    return(checkParm(csp, parmp));
		
		if ((csp->parmMax < 20) && (nchars >= nameSize+1) && (*parmp >= 2))
		    return(checkParm(csp, parmp));
	    }
	}
	else
	    usleep(10000);

    return(NULL);
}


/************************************************************************/
/* Function    : exitProg						*/
/* Purpose     : Command function to exit the program			*/
/* Inputs      : None used						*/
/* Outputs     : EXIT return code					*/
/************************************************************************/
int	exitProg(void)
{
    return(EXIT);
}


/************************************************************************/
/* Function    : VicorOn						*/
/* Purpose     : Command function to turn on a Vicor converter		*/
/* Inputs      : MVConvHandle, Vicor Number				*/
/* Outputs     : Return code						*/
/************************************************************************/
int     vicorOn(MVConvHandle mvh, uint port)
{
    printf("Turning on Vicor %u\n", port);
    mvPower(mvh, VICOR_01 + port, TRUE);
    return(OK);
}


/************************************************************************/
/* Function    : VicorOff						*/
/* Purpose     : Command function to turn off a Vicor converter		*/
/* Inputs      : MVConvHandle, Vicor Number				*/
/* Outputs     : Return code						*/
/************************************************************************/
int	vicorOff(MVConvHandle mvh, uint port)
{
    printf("Turning off Vicor %u\n", port);
    mvPower(mvh, VICOR_01 + port, FALSE);
    return(OK);
}


/************************************************************************/
/* Function    : pwrRelayOn						*/
/* Purpose     : Command function to turn on a FOCE power relay		*/
/* Inputs      : MVConvHandle, Relay Number				*/
/* Outputs     : Return code						*/
/************************************************************************/
int	pwrRelayOn(MVConvHandle mvh, uint port)
{
    printf("Turning on FOCE relay %u\n", port);
    mvPower(mvh, FOCE_01 + port, TRUE);
    return(OK);
}


/************************************************************************/
/* Function    : pwrRelayOff						*/
/* Purpose     : Command function to turn off a FOCE power relay	*/
/* Inputs      : MVConvHandle, Port Number				*/
/* Outputs     : Return code						*/
/************************************************************************/
int	pwrRelayOff(MVConvHandle mvh, uint port)
{
    printf("Turning off FOCE relay %u\n", port);
    mvPower(mvh, FOCE_01 + port, FALSE);
    return(OK);
}


/************************************************************************/
/* Function    : fanOn							*/
/* Purpose     : Command function to turn on a Fan			*/
/* Inputs      : MVConvHandle, Fan Number				*/
/* Outputs     : Return code						*/
/************************************************************************/
int     fanOn(MVConvHandle mvh, uint port)
{
    printf("Turning on Fan %u\n", port);
    mvPower(mvh, FAN_01 + port, TRUE);
    return(OK);
}


/************************************************************************/
/* Function    : fanOff							*/
/* Purpose     : Command function to turn off a Fan			*/
/* Inputs      : MVConvHandle, Fan Number				*/
/* Outputs     : Return code						*/
/************************************************************************/
int	fanOff(MVConvHandle mvh, uint port)
{
    printf("Turning off fan %u\n", port);
    mvPower(mvh, FAN_01 + port, FALSE);
    return(OK);
}


/************************************************************************/
/* Function    : printAnalogChan					*/
/* Purpose     : Print one analog channel				*/
/* Inputs      : analog channel number					*/
/* Outputs     : None							*/
/************************************************************************/
void	printAnalogChan(uint chan, uint16_t cnt)
{
    AnalogDesc	*adp = &analogDesc[chan];
    double	volt = ((double)cnt * 3.0 / 65536.0);

    if (chan == AD_AMB_TEMP)
    {
	volt *= 1.665;
	printf("Ch %2d  %-12s %5hu cnts %6.3f ADC volts %6.3f %s\n",
	       chan, adp->name, cnt, volt,
	       -2.43*volt*volt*volt + 20.0*volt*volt - 74.19*volt + 123.32,
	       adp->units);
    }
    else
	printf("Ch %2d  %-12s %5hu cnts %6.3f ADC volts %6.3f %s\n",
	       chan, adp->name, cnt, volt,
	       volt * adp->mult + adp->offset, adp->units);
}

/************************************************************************/
/* Function    : analogRead						*/
/* Purpose     : Command function to read analog channel		*/
/* Inputs      : MVConvHandle, analog channel number			*/
/* Outputs     : Return code						*/
/************************************************************************/
int	analogRead(MVConvHandle mvh, uint chan)
{
    uint16_t	cnt;

    if (chan >= MV_AD_CHANS)
	printf("Bad channel number\n");

    else if (mvAnalogChan(mvh, chan, &cnt) < 0)
	return(printModbusError("Error reading analog channel"));

    printAnalogChan(chan, cnt);

    return(OK);
}

/************************************************************************/
/* Function    : analogAllRead						*/
/* Purpose     : Command function to read all 8 analog channels		*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : Return code						*/
/************************************************************************/
int	analogAllRead(MVConvHandle mvh)
{
    uint	i;
    uint16_t	cnt[MV_AD_CHANS];

    if (mvAnalogChans(mvh, 0, 16, cnt) < 0)
	return(printModbusError("Error reading analog channels"));

    for (i = 0; i < MV_AD_CHANS; i++)
	printAnalogChan(i, cnt[i]);

    return(OK);
}


/************************************************************************/
/* Function    : analogParms						*/
/* Purpose     : Command function to print analog parms			*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : Return code						*/
/************************************************************************/
int	analogParms(MVConvHandle mvh)
{
    uint16_t	numAvg, avgMs;

    if (mvGetAnalogParms(mvh, &numAvg, &avgMs) < 0)
	return(printModbusError("Error reading analog parms"));

    printf("numAvg = %hu, avgMs = %hu\n", numAvg, avgMs);

    return(OK);
}


/************************************************************************/
/* Function    : analogNumAvg						*/
/* Purpose     : Command function to set numAvg				*/
/* Inputs      : MVConvHandle, parm					*/
/* Outputs     : OK							*/
/************************************************************************/
int	analogNumAvg(MVConvHandle mvh, uint numAvg)
{
    uint16_t	numAvg16, avgMs16;

    if (mvGetAnalogParms(mvh, &numAvg16, &avgMs16) < 0)
	return(printModbusError("Error reading analog parms"));

    numAvg16 = numAvg;

    if (mvSetAnalogParms(mvh, numAvg16, avgMs16) < 0)
	return(printModbusError("Error writing analog parms"));

    return(OK);
}


/************************************************************************/
/* Function    : analogAvgMs						*/
/* Purpose     : Command function to set avgMs				*/
/* Inputs      : MVConvHandle, parm					*/
/* Outputs     : OK							*/
/************************************************************************/
int	analogAvgMs(MVConvHandle mvh, uint avgMs)
{
    uint16_t	numAvg16, avgMs16;

    if (mvGetAnalogParms(mvh, &numAvg16, &avgMs16) < 0)
	return(printModbusError("Error reading analog parms"));

    avgMs16 = avgMs;

    if (mvSetAnalogParms(mvh, numAvg16, avgMs16) < 0)
	return(printModbusError("Error writing analog parms"));

    return(OK);
}


/************************************************************************/
/* Function    : serNum							*/
/* Purpose     : Command function to get board serial number		*/
/* Inputs      : MVConvHandle					*/
/* Outputs     : OK							*/
/************************************************************************/
int	serNum(MVConvHandle mvh)
{
    uint16_t	serNum;

    if (mvGetSerialNum(mvh, &serNum) < 0)
	return(printModbusError("Error reading serial number"));

    printf("Serial Number = %u\n", (uint)serNum);
    return(OK);
}


/************************************************************************/
/* Function    : softwareRev						*/
/* Purpose     : Command function to get board firmware revision	*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	softwareRev(MVConvHandle mvh)
{
    uint16_t	rev;

    if (mvGetSoftwareRev(mvh, &rev) < 0)
	return(printModbusError("Error reading software rev"));
    
    printf("Software Rev = 0x%x\n", (uint)rev);
    return(OK);
}

/************************************************************************/
/* Function    : statusVector						*/
/* Purpose     : Command function to get system status vector		*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	statusVector(MVConvHandle mvh)
{
    uint16_t	stat;

    if (mvGetStatusVector(mvh, &stat) < 0)
	return(printModbusError("Error reading status vector"));
    
    printf("Status Vector = 0x%x\n", (uint)stat);
    return(OK);
}


/************************************************************************/
/* Function    : waterAlarms						*/
/* Purpose     : Command function to get water alarms			*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	waterAlarms(MVConvHandle mvh)
{
    uint16_t	alarm;

    if (mvGetWaterAlarms(mvh, &alarm) < 0)
	return(printModbusError("Error reading water alarms\n"));

    printf("Water Alarm = 0x%x\n", (uint)alarm);
    return(OK);
}


/************************************************************************/
/* Function    : blinkyCmd						*/
/* Purpose     : Command function to set blinky LED			*/
/* Inputs      : MVConvHandle, parm					*/
/* Outputs     : OK							*/
/************************************************************************/
int	blinkyCmd(MVConvHandle mvh, uint parm)
{
    mvBlinky(mvh, parm);
    return(OK);
}

/************************************************************************/
/* Function    : configMode						*/
/* Purpose     : Command function to set board to configuration mode	*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	configMode(MVConvHandle mvh)
{
    mvResetConfigMode(mvh);
    printf("Config mode now active\n");
    return(OK);
}

/********************************/
/*  Ground Fault Testing	*/
/********************************/

/************************************************************************/
/* Function    : groundFault						*/
/* Purpose     : Command function to get ground fault results		*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	groundFault(MVConvHandle mvh)
{
    int		i;
    uint16_t	seq, chan, gfBits;
    uint16_t	result[NUM_GF_CHANS];

    if (mvGetGFCtrl(mvh, &gfBits) < 0)
	printModbusError("Error getting GF Ctrl\r\n");
    else
	printf("GF_CTRL     :  0x%02x\r\n", gfBits);
    
    if (mvGetGFSeq(mvh, &seq) < 0)
	perror("Error in mvGetGFSeq()");
    else
	printf("GF_SEQ      :  %hu\r\n", seq);

    if (mvGetGFChan(mvh, &chan) < 0)
	perror("Error in mvGetGFChan()");
    else
	printf("GF_CHAN     :  %hu\r\n", chan);

    if (mvGetGFResult(mvh, 0, NUM_GF_CHANS, result) < 0)
	perror("Error in mvGetGFResult()");
    
    else
	for (i = 0; i < NUM_GF_CHANS; i++)
	    printf("%-12s:  %6u %9.2f uamps\r\n", gfChans[i], (uint)result[i],
		   0.03052 * ((double)result[i]-32768.0));
    
    return(OK);
}


/************************************************************************/
/* Function    : groundFaultParms					*/
/* Purpose     : Command function to get ground fault parameters	*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	groundFaultParms(MVConvHandle mvh)
{
    uint16_t	numSamps, sampPeriod, ignoreSamps, gfBits;

    if (mvGetGFCtrl(mvh, &gfBits))
	return(printModbusError("Error getting GF Ctrl\r\n"));

    if (mvGetGFParms(mvh, &numSamps, &sampPeriod, &ignoreSamps))
	return(printModbusError("Error getting GF Parms\r\n"));

    printf("GF_CTRL        :  0x%02x\r\n", gfBits);
    printf("GF_NUM_SAMPS   :  %hu\r\n", numSamps);
    printf("GF_SAMP_PERIOD :  %hu ms\r\n", sampPeriod);
    printf("GF_IGNORE_SAMPS:  %hu\r\n", ignoreSamps);

    return(OK);
}

/************************************************************************/
/* Function    : groundFaultCtrl					*/
/* Purpose     : Command function to set ground fault control word	*/
/* Inputs      : MVConvHandle, gfCtrl					*/
/* Outputs     : OK							*/
/************************************************************************/
int	groundFaultCtrl(MVConvHandle mvh, uint parm)
{
    if (mvSetGFCtrl(mvh, (parm & GFC_ALL)))
	return(printModbusError("Error setting GF Ctrl\r\n"));
    
    return(OK);
}

/************************************************************************/
/* Function    : groundFaultNumSamps					*/
/* Purpose     : Command function to set ground fault number of samples */
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	groundFaultNumSamps(MVConvHandle mvh, uint num)
{
    uint16_t	numSamps, sampPeriod, ignoreSamps;

    if (mvGetGFParms(mvh, &numSamps, &sampPeriod, &ignoreSamps))
	return(printModbusError("Error getting GF Parms\r\n"));

    if (mvSetGFParms(mvh, num, sampPeriod, ignoreSamps))
	return(printModbusError("Error setting GF Parms\r\n"));

    return(OK);
}

/************************************************************************/
/* Function    : groundFaultSampPeriod					*/
/* Purpose     : Command function to set ground fault sample period	*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	groundFaultSampPeriod(MVConvHandle mvh, uint per)
{
    uint16_t	numSamps, sampPeriod, ignoreSamps;

    if (mvGetGFParms(mvh, &numSamps, &sampPeriod, &ignoreSamps))
	return(printModbusError("Error getting GF Parms\r\n"));

    if (mvSetGFParms(mvh, numSamps, per, ignoreSamps))
	return(printModbusError("Error setting GF Parms\r\n"));

    return(OK);
}

/************************************************************************/
/* Function    : groundFaultIgnoreSamps					*/
/* Purpose     : Command function to set ground fault ignore sample count*/
/* Inputs      : MVConvHandle						*/
/* Outputs     : OK							*/
/************************************************************************/
int	groundFaultIgnoreSamps(MVConvHandle mvh, uint ign)
{
    uint16_t	numSamps, sampPeriod, ignoreSamps;

    if (mvGetGFParms(mvh, &numSamps, &sampPeriod, &ignoreSamps))
	return(printModbusError("Error getting GF Parms\r\n"));

    if (mvSetGFParms(mvh, numSamps, sampPeriod, ign))
	return(printModbusError("Error setting GF Parms\r\n"));

    return(OK);
}


/************************************************************************/
/* Function    : testCmd						*/
/* Purpose     : Command function to test parser			*/
/* Inputs      : MVConvHandle, parm					*/
/* Outputs     : OK							*/
/************************************************************************/
int	testCmd(MVConvHandle mvh, uint parm)
{
    printf("Test Command, parm = %u\n", parm);
    return(OK);
}

