
/* Copyright 2013-2014 MBARI						*/
/************************************************************************/
/* Summary  : Quick and Dirty software to log analog channels from MVConv*/
/* Filename : mvLog.c							*/
/* Author   : Robert Herlien (rah)					*/
/* Project  : xFOCE							*/
/* Revision: 1.0							*/
/* Created  : 4/27/2015							*/
/*									    */
/* 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 <time.h>
#include <getopt.h>
#include <termios.h>


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);
int	printModbusError(char *msg);
void	printAnalogChan(uint chan, uint16_t cnt);
int	analogRead(MVConvHandle mvh, uint port);
int	analogAllRead(MVConvHandle mvh);

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

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

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;
    MVConvHandle	mvh;
    time_t		now;

    while ((opt = getopt(argc, argv, "a:b:d:hp:r:")) != -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 'r':
	      rate = atoi(optarg);
	      break;

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

    printf("MVConv logger, port %s  baud %u  slave %u  rate %u seconds\n", 
	   modPort, baud, slaveID, rate);

    initMVConvLib();
    mvSetDebug(mvDebug);

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

    mvPower(mvh, VICOR_01, TRUE);
    mvPower(mvh, FOCE_01, TRUE);
    sleep(5);

    while (TRUE)
    {
	time(&now);
	printf(asctime(localtime(&now)));
	analogAllRead(mvh);
	sleep(rate);
    }

    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    : 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[-r <rate>] Log all channels once per <rate> seconds, defaults to 60\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    : 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);
}


/********************************/
/*  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);
}

