/************************************************************************/
/* Copyright 2013-2015 MBARI                                            */
/************************************************************************/
/* Summary  : Serial loopback test program for O5 Expander              */
/* Filename : oeSerTest.c                                               */
/* Author   : Robert Herlien (rah)                                      */
/* Project  : OASIS5                                                    */
/* Revision: 1.0                                                        */
/* Created  : 11/9/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:                                                */
/* 9nov2015 rah - created                                               */
/************************************************************************/

#include <modbus.h>                     /* libmodbus include            */
#include <modbus-rtu.h>                 /* libmodbus include            */
#include <o5Expander.h>                 /* defs for O5 Expander library */

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


typedef struct                  /****************************************/
{                               /* Struct to keep RemSerHandle          */
    RemSerHandle rsh;           /* The serial handle (NULL if unopened) */
    int   baud;                 /* Baud rate it's opened at             */
    int   stop;                 /* Stop bits                            */
    char  parity;               /* 'N', 'E', or 'O'                     */
} RemSerPort;                   /****************************************/


#define READSIZE        20

#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);
RemSerHandle    getSerHandle(O5ExpHandle o5eh, int port, int baud);


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

static MBool    oeDebug = FALSE;
static int      slaveID = 1, modBaud = 115200, targetPort = 0,
                targetBaud = 9600, modDebug = FALSE;
static char     *modPort = "/dev/ttyS0";
static char     modPortBuf[16];
static char     inputBuf[256];
static struct termios   tioSave;


/************************************************************************/
/* Function    : main                                                   */
/* Purpose     : Main entry point                                       */
/* Inputs      : argc, argv                                             */
/* Outputs     : Application return code                                */
/************************************************************************/
int     main(int argc, char **argv)
{
    int                 opt, parm, nchars;
    O5ExpHandle    o5eh;
    RemSerHandle        rsh;
    fd_set              fds;
    struct timeval      selTmout;

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

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

          case 'm':
              modBaud = atoi(optarg);
              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 't':
              parm = atoi(optarg);
              if ((parm < 0) || (parm > 3))
              {
                  printf("Invalid target serial port: %d\n", parm);
                  usage(argv);
                  return(0);
              }
              targetPort = parm;
              break;

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

    printf("Terminal for Modbus port %s  baud %u  slave %u target serial port %d\n",
           modPort, modBaud, slaveID, targetPort);

    initO5ExpanderLib();
    o5eSetDebug(oeDebug);

    if ((o5eh = openO5Expander(modPort, modBaud, slaveID)) == NULL)
    {
        printf("Failure in openO5Expander: errno %d %s\n", errno,
               modbus_strerror(errno));
        exit(1);
    }

    o5eModbusSetDebug(o5eh, modDebug);
    rawMode();

    if ((rsh = getSerHandle(o5eh, targetPort, targetBaud)) == NULL)
        exit(1);

    FD_ZERO(&fds);
    selTmout.tv_sec = 0;
    selTmout.tv_usec = 20000;

    while(TRUE)
    {
        FD_SET(STDIN_FILENO, &fds);

        if (select(1, &fds, NULL, NULL, &selTmout) > 0)
            if ((nchars = read(STDIN_FILENO, inputBuf, sizeof(inputBuf))) > 0)
            {
                if (inputBuf[0] == 4)
                {
                    printf("Normal exit\n");
                    tcsetattr(STDIN_FILENO, TCSANOW, &tioSave);
                    exit(0);
                }

                o5eSerWrite(rsh, inputBuf, nchars);
            }

        if ((nchars = o5eSerRead(rsh, inputBuf, READSIZE)) > 0)
            write(STDOUT_FILENO, inputBuf, nchars);
    }

    return(0);

} /* main() */


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

    tcgetattr(STDIN_FILENO, &tioSave);
    memcpy(&tio, &tioSave, sizeof(tio));

    tio.c_lflag &= (~ICANON & ~ECHO);
    tio.c_iflag &= (~ICRNL & ~INLCR);
    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 <modbus port>] [-t <target port] [-h]\n\n",
            argv[0]);
    printf("\t[-p <port>] Use serial port <port> to communicate with Modbus device\n");
    printf("\t\tDefaults to COM1\n");
    printf("\t[-a <slaveAddr>] Communicate with Modbus slave <slaveAddr>, 1-247\n");
    printf("\t\tDefaults to 1\n");
    printf("\t[-m <modbus baud>] Set Modbus baud rate.  Defaults to 115200\n");
    printf("\t[-t <target port>] Send/get serial comms to target port <port> (0-3)\n");
    printf("\t\tDefaults to port 0\n");
    printf("\t[-b <target baud>] Baud rate for target (virtual) serial port\n");
    printf("\t\tDefaults to 9600\n");
    printf("\t[-d <0-3>] Print debug messages\n");
    printf("\t[-h] Print this message\n");

} /* usage() */


/************************************************************************/
/* Function    : getSerHandle                                           */
/* Purpose     : Get handle for remote serial port                      */
/* Inputs      : O5ExpHandle, Port Number, baud rate                    */
/* Outputs     : RemSerHandle, or NULL if error                         */
/************************************************************************/
RemSerHandle getSerHandle(O5ExpHandle o5eh, int port, int baud)
{
    RemSerHandle rsh;

    if ((rsh = o5eOpenSerial(o5eh, port)) == NULL)
    {
        printf("Failed o5eOpenSerial(), errno %d, %s\n",
               errno, modbus_strerror(errno));
        return(NULL);
    }

    if (o5eSerInit(rsh, baud, 'N', 1) == ERROR)
    {
        printf("Failed o5eSerInit(), errno %d, %s\n",
               errno, modbus_strerror(errno));
        return(NULL);
    }

    printf("Using serial port %d at %d,N81\n", port, baud);

    return(rsh);
}
