/****************************************************************************
 * apps/modbus/rtu/mbrtu.c
 *
 * FreeModbus Library: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (c) 2006 Christian Walter <wolti@sil.at>
 * 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 <string.h>
#include <assert.h>

#include "port.h"

#include <apps/modbus/mb.h>
#include <apps/modbus/mbframe.h>
#include <apps/modbus/mbport.h>

#include "mbrtu.h"
#include "mbcrc.h"

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

#define MB_SER_PDU_SIZE_MIN     4    /* Minimum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_MAX     256  /* Maximum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_CRC     2    /* Size of CRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF     0    /* Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF      1    /* Offset of Modbus-PDU in Ser-PDU. */

/****************************************************************************
 * Private Type Definitions
 ****************************************************************************/

typedef enum
{
  STATE_RX_INIT,                /* Receiver is in initial state. */
  STATE_RX_IDLE,                /* Receiver is in idle state. */
  STATE_RX_RCV,                 /* Frame is being received. */
  STATE_RX_ERROR                /* If the frame is invalid. */
} eMBRcvState;

typedef enum
{
  STATE_TX_IDLE,                /* Transmitter is in idle state. */
  STATE_TX_XMIT                 /* Transmitter is in transfer state. */
} eMBSndState;

/****************************************************************************
 * Private Data
 ****************************************************************************/

static volatile eMBSndState eSndState;
static volatile eMBRcvState eRcvState;

volatile uint8_t  ucRTUBuf[MB_SER_PDU_SIZE_MAX];

static volatile uint8_t *pucSndBufferCur;
static volatile uint16_t usSndBufferCount;

static volatile uint16_t usRcvBufferPos;

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

eMBErrorCode eMBRTUInit(uint8_t ucSlaveAddress, uint8_t ucPort,
                        speed_t ulBaudRate, eMBParity eParity)
{
  eMBErrorCode eStatus = MB_ENOERR;
  uint32_t usTimerT35_50us;

  (void)ucSlaveAddress;
  ENTER_CRITICAL_SECTION();

  /* Modbus RTU uses 8 Databits. */

  if (xMBPortSerialInit(ucPort, ulBaudRate, 8, eParity) != true)
    {
      eStatus = MB_EPORTERR;
    }
  else
    {
      /* If baudrate > 19200 then we should use the fixed timer values
       * t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
       */

      if (ulBaudRate > 19200)
        {
          usTimerT35_50us = 35;       /* 1800us. */
        }
      else
        {
          /* The timer reload value for a character is given by:
           *
           * ChTimeValue = Ticks_per_1s / (Baudrate / 11)
           *             = 11 * Ticks_per_1s / Baudrate
           *             = 220000 / Baudrate
           * The reload for t3.5 is 1.5 times this value and similary
           * for t3.5.
           */

          usTimerT35_50us = (7UL * 220000UL) / (2UL * ulBaudRate);
        }

      if (xMBPortTimersInit((uint16_t) usTimerT35_50us) != true)
        {
          eStatus = MB_EPORTERR;
        }
    }

  EXIT_CRITICAL_SECTION();
  return eStatus;
}

void eMBRTUStart(void)
{
  ENTER_CRITICAL_SECTION();

  /* Initially the receiver is in the state STATE_RX_INIT. we start
   * the timer and if no character is received within t3.5 we change
   * to STATE_RX_IDLE. This makes sure that we delay startup of the
   * modbus protocol stack until the bus is free.
   */

  eRcvState = STATE_RX_INIT;
  vMBPortSerialEnable(true, false);
  vMBPortTimersEnable();

  EXIT_CRITICAL_SECTION();
}

void eMBRTUStop(void)
{
  ENTER_CRITICAL_SECTION();
  vMBPortSerialEnable(false, false);
  vMBPortTimersDisable();
  EXIT_CRITICAL_SECTION();
}

eMBErrorCode eMBRTUReceive(uint8_t *pucRcvAddress, uint8_t **pucFrame,
                           uint16_t *pusLength)
{
  eMBErrorCode eStatus = MB_ENOERR;

  ENTER_CRITICAL_SECTION();
  ASSERT(usRcvBufferPos < MB_SER_PDU_SIZE_MAX);

  /* Length and CRC check */

  if ((usRcvBufferPos >= MB_SER_PDU_SIZE_MIN) &&
      (usMBCRC16((uint8_t *) ucRTUBuf, usRcvBufferPos) == 0))
    {
      /* Save the address field. All frames are passed to the upper layed
       * and the decision if a frame is used is done there.
       */

      *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];

      /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
       * size of address field and CRC checksum.
       */

      *pusLength = (uint16_t)(usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);

      /* Return the start of the Modbus PDU to the caller. */

      *pucFrame = (uint8_t *) & ucRTUBuf[MB_SER_PDU_PDU_OFF];
    }
  else
    {
      eStatus = MB_EIO;
    }

  EXIT_CRITICAL_SECTION();
  return eStatus;
}

eMBErrorCode eMBRTUSend(uint8_t ucSlaveAddress, const uint8_t *pucFrame, uint16_t usLength)
{
  eMBErrorCode eStatus = MB_ENOERR;
  uint16_t usCRC16;

  ENTER_CRITICAL_SECTION();

  /* Check if the receiver is still in idle state. If not we where to
   * slow with processing the received frame and the master sent another
   * frame on the network. We have to abort sending the frame.
   */

  if (eRcvState == STATE_RX_IDLE)
    {
      /* First byte before the Modbus-PDU is the slave address. */

      pucSndBufferCur = (uint8_t *) pucFrame - 1;
      usSndBufferCount = 1;

      /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */

      pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
      usSndBufferCount += usLength;

      /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */

      usCRC16 = usMBCRC16((uint8_t *) pucSndBufferCur, usSndBufferCount);
      ucRTUBuf[usSndBufferCount++] = (uint8_t)(usCRC16 & 0xFF);
      ucRTUBuf[usSndBufferCount++] = (uint8_t)(usCRC16 >> 8);

      /* Activate the transmitter. */

      eSndState = STATE_TX_XMIT;
      vMBPortSerialEnable(false, true);
    }
  else
    {
      eStatus = MB_EIO;
    }

  EXIT_CRITICAL_SECTION();
  return eStatus;
}

bool xMBRTUReceiveFSM(void)
{
  bool xTaskNeedSwitch = false;
  uint8_t ucByte;

  ASSERT(eSndState == STATE_TX_IDLE);

  /* Always read the character. */

  (void)xMBPortSerialGetByte((int8_t *) & ucByte);

  switch (eRcvState)
    {
      /* If we have received a character in the init state we have to
       * wait until the frame is finished.
       */

      case STATE_RX_INIT:
        vMBPortTimersEnable();
        break;

      /* In the error state we wait until all characters in the
       * damaged frame are transmitted.
       */

      case STATE_RX_ERROR:
        vMBPortTimersEnable();
        break;

      /* In the idle state we wait for a new character. If a character
       * is received the t1.5 and t3.5 timers are started and the
       * receiver is in the state STATE_RX_RECEIVCE.
       */

      case STATE_RX_IDLE:
        usRcvBufferPos = 0;
        ucRTUBuf[usRcvBufferPos++] = ucByte;
        eRcvState = STATE_RX_RCV;

        /* Enable t3.5 timers. */

        vMBPortTimersEnable();
        break;

      /* We are currently receiving a frame. Reset the timer after
       * every character received. If more than the maximum possible
       * number of bytes in a modbus frame is received the frame is
       * ignored.
       */

      case STATE_RX_RCV:
        if (usRcvBufferPos < MB_SER_PDU_SIZE_MAX)
          {
            ucRTUBuf[usRcvBufferPos++] = ucByte;
          }
        else
          {
            eRcvState = STATE_RX_ERROR;
          }

        vMBPortTimersEnable();
        break;
    }

  return xTaskNeedSwitch;
}

bool xMBRTUTransmitFSM(void)
{
  bool xNeedPoll = false;

  ASSERT(eRcvState == STATE_RX_IDLE);

  switch (eSndState)
    {
      /* We should not get a transmitter event if the transmitter is in
       * idle state.
       */

    case STATE_TX_IDLE:
      /* enable receiver/disable transmitter. */

      vMBPortSerialEnable(true, false);
      break;

    case STATE_TX_XMIT:
      /* check if we are finished. */

      if (usSndBufferCount != 0)
        {
          xMBPortSerialPutByte((int8_t)*pucSndBufferCur);
          pucSndBufferCur++;  /* next byte in sendbuffer. */
          usSndBufferCount--;
        }
      else
        {
          xNeedPoll = xMBPortEventPost(EV_FRAME_SENT);

          /* Disable transmitter. This prevents another transmit buffer
           * empty interrupt.
           */

          vMBPortSerialEnable(true, false);
          eSndState = STATE_TX_IDLE;
        }
      break;
    }

  return xNeedPoll;
}

bool xMBRTUTimerT35Expired(void)
{
  bool xNeedPoll = false;

  switch (eRcvState)
    {
      /* Timer t35 expired. Start-up phase is finished. */

      case STATE_RX_INIT:
        xNeedPoll = xMBPortEventPost(EV_READY);
        break;

      /* A frame was received and t35 expired. Notify the listener that
       * a new frame was received.
       */

      case STATE_RX_RCV:
        xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED);
        break;

      /* An error occurred while receiving the frame. */

      case STATE_RX_ERROR:
        break;

      /* Function called in an illegal state. */

      default:
        ASSERT((eRcvState == STATE_RX_INIT) ||
               (eRcvState == STATE_RX_RCV) ||
               (eRcvState == STATE_RX_ERROR));
    }

  vMBPortTimersDisable();
  eRcvState = STATE_RX_IDLE;

  return xNeedPoll;
}