/****************************************************************************
 * apps/examples/modbusmaster/mbmaster_main.c
 *
 * Copyright (c) 2016 Vytautas Lukenskas <lukevyta@gmail.com>
 * Copyright (c) 2019 Alan Carvalho de Assis <acassis@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>

#include "modbus/mb.h"
#include "modbus/mb_m.h"
#include "modbus/mbport.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* modbus master port */

#ifndef CONFIG_SERIAL_TERMIOS
#  error "CONFIG_SERIAL_TERMIOS is needed by modbus example"
#endif

#ifdef CONFIG_EXAMPLES_MODBUSMASTER_PORT
#  define MBMASTER_PORT CONFIG_EXAMPLES_MODBUSMASTER_PORT
#else
#  define MBMASTER_PORT 1             /* port, 1 -- /dev/ttyS1 */
#endif

#ifdef CONFIG_EXAMPLES_MODBUSMASTER_BAUDRATE
#  define MBMASTER_BAUD CONFIG_EXAMPLES_MODBUSMASTER_BAUDRATE
#else
#  define MBMASTER_BAUD B115200       /* baudrate, 115200kbps */
#endif

#ifdef CONFIG_EXAMPLES_MODBUSMASTER_PARITY
#  define MBMASTER_PARITY CONFIG_EXAMPLES_MODBUSMASTER_PARITY
#else
#  define MBMASTER_PARITY MB_PAR_EVEN /* parity, even */
#endif

#ifdef CONFIG_EXAMPLES_MODBUSMASTER_SLAVEADDR
#  define SLAVE_ID CONFIG_EXAMPLES_MODBUSMASTER_SLAVEADDR
#else
#  define SLAVE_ID 1                  /* slave address */
#endif

#ifdef CONFIG_EXAMPLES_MODBUSMASTER_SLAVEREG
#  define SLAVE_STATUS_REG CONFIG_EXAMPLES_MODBUSMASTER_SLAVEREG
#else
#  define SLAVE_STATUS_REG 1          /* slave status register part1 */
#endif

#ifdef CONFIG_EXAMPLES_MODBUSMASTER_TESTCOUT
#  define MBMASTER_REQUESTS_COUNT CONFIG_EXAMPLES_MODBUSMASTER_TESTCOUT
#else
#  define MBMASTER_REQUESTS_COUNT 100 /* number of requests to send  */
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

enum mbmaster_threadcmd_e
{
  STOPPED = 0,
  RUN,
  SHUTDOWN
};

struct mbmaster_statistics_s
{
  int reqcount;
  int rspcount;
  int errcount;
};

struct mbmaster_run_s
{
  pthread_t pollthreadid;
  enum mbmaster_threadcmd_e threadcmd;
  struct mbmaster_statistics_s statistics;
};

/****************************************************************************
 * Private Global Variables
 ****************************************************************************/

static struct mbmaster_run_s g_mbmaster;

/****************************************************************************
 * Private functions
 ****************************************************************************/

/****************************************************************************
 * Name: mbmaster_initialize
 *
 * Description:
 *   Should initialize modbus stack.
 *
 ****************************************************************************/

static inline int mbmaster_initialize(void)
{
  eMBErrorCode mberr;

  /* Initialize the FreeModBus library (master) */

  mberr = eMBMasterInit(MB_RTU, MBMASTER_PORT,
                        MBMASTER_BAUD, MBMASTER_PARITY);
  if (mberr != MB_ENOERR)
    {
      fprintf(stderr, "mbmaster_main: "
              "ERROR: eMBMasterInit failed: %d\n", mberr);
      return ENODEV;
    }

  /* Enable FreeModBus Master */

  mberr = eMBMasterEnable();
  if (mberr != MB_ENOERR)
    {
      fprintf(stderr, "mbmaster_main: "
              "ERROR: eMBMasterEnable failed: %d\n", mberr);
      return ENODEV;
    }

  return OK;
}

/****************************************************************************
 * Name: mbmaster_deinitialize
 *
 * Description:
 *   Should release resources taken by modbus stack.
 *
 ****************************************************************************/

static inline void mbmaster_deinitialize(void)
{
  /* Disable modbus stack */

  eMBMasterDisable();

  /* Release hardware resources */

  eMBMasterClose();
}

/****************************************************************************
 * Name: mbmaster_pollthread
 *
 * Description:
 *   This is the ModBus Master polling thread.
 *
 ****************************************************************************/

static FAR void *mbmaster_pollthread(FAR void *pvarg)
{
  eMBErrorCode mberr;

  do
    {
      /* Poll */

      mberr = eMBMasterPoll();
      if (mberr != MB_ENOERR)
        {
          break;
        }
    }
  while (g_mbmaster.threadcmd != SHUTDOWN);

  /* Free/uninitialize data structures */

  g_mbmaster.threadcmd = STOPPED;
  printf("mbmaster_main: "
         "Exiting poll thread.\n");
  return NULL;
}

/****************************************************************************
 * Name: mbmaster_showstatistics
 *
 * Description:
 *   Show final connection statistics
 *
 ****************************************************************************/

static void mbmaster_showstatistics(void)
{
  printf("Modbus master statistics:\n");
  printf("Requests count:  %d\n", g_mbmaster.statistics.reqcount);
  printf("Responses count: %d\n", g_mbmaster.statistics.rspcount);
  printf("Errors count:    %d\n", g_mbmaster.statistics.errcount);
}

/****************************************************************************
 * Name: eMBMasterRegHoldingCB
 *
 * Description:
 *   Required FreeModBus callback function
 *
 ****************************************************************************/

eMBErrorCode eMBMasterRegHoldingCB(FAR uint8_t *buffer,
                                   uint16_t address, uint16_t nregs,
                                   eMBRegisterMode mode)
{
  eMBErrorCode mberr = MB_ENOERR;

  switch (mode)
    {
    case MB_REG_READ:
      g_mbmaster.statistics.rspcount++;
      break;

    case MB_REG_WRITE:
      printf("mbmaster_main: writing holding register, value: %d\n",
             *(buffer));
      break;
    }

  return mberr;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: main/mbmaster_main
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  eMBMasterReqErrCode mberr;
  int reqcounter = 0;
  int ret;

  g_mbmaster.statistics.reqcount = 0;
  g_mbmaster.statistics.rspcount = 0;
  g_mbmaster.statistics.errcount = 0;
  g_mbmaster.threadcmd = STOPPED;
  reqcounter = 0;

  /* Initialize modbus stack, etc */

  printf("Initializing modbus master...\n");
  ret = mbmaster_initialize();

  if (ret != OK)
    {
      fprintf(stderr, "mbmaster_main: ",
              "ERROR: mbmaster_initialize failed: %d\n", ret);
      goto errout;
    }

  printf("Creating poll thread.\n");
  g_mbmaster.threadcmd = RUN;
  ret = pthread_create(&g_mbmaster.pollthreadid, NULL,
                       mbmaster_pollthread, NULL);

  if (ret != OK)
    {
      fprintf(stderr, "mbmaster_main: ",
              "ERROR: mbmaster_pollthread create failed: %d\n", ret);
      goto errout_with_initialize;
    }

  printf("Sending %d requests to slave %d\n",
         MBMASTER_REQUESTS_COUNT, SLAVE_ID);

  /* modbus is initialized and polling thread is running */

  while (reqcounter < MBMASTER_REQUESTS_COUNT)
    {
      g_mbmaster.statistics.reqcount++;
      mberr = eMBMasterReqReadHoldingRegister(SLAVE_ID,
                                              SLAVE_STATUS_REG, 1, -1);
      if (mberr != MB_MRE_NO_ERR)
        {
          fprintf(stderr, "mbmaster_main: "
                  "ERROR: holding reg %d read failed: %d\n",
                  SLAVE_STATUS_REG, mberr);

          g_mbmaster.statistics.errcount++;
        }

      reqcounter++;
    }

  /* Stop polling thread */

  g_mbmaster.threadcmd = SHUTDOWN;
  pthread_join(g_mbmaster.pollthreadid, NULL);
  g_mbmaster.threadcmd = STOPPED;

  /* Display connection statistics */

  mbmaster_showstatistics();

  printf("Deinitializing modbus master...\n");
  mbmaster_deinitialize();

  return OK;

errout_with_initialize:

  /* Release hardware resources */

  mbmaster_deinitialize();

errout:
  exit(EXIT_FAILURE);
}
