/** 
*******************************************************************************
* @file modbus_slave.c
* @brief Модуль для реализации слейв MODBUS.
******************************************************************************* 
* @details 
Файл содержит реализацию функций для работы Modbus в режиме слейва.

@section slave Функции и макросы

- MB_Slave_Response()         — Ответ на запрос
- MB_Slave_Collect_Message()  — Сбор сообщения в режиме слейва.
- MB_Slave_Parse_Message()    — Парс сообщения в режиме слейва.
******************************************************************************/
#include "modbus.h"

#ifdef MODBUS_ENABLE_SLAVE
/** 
  * @brief    Ответ на сообщение в режиме слейва.
  * @param    hmodbus     Указатель на хендлер RS.
  * @param    modbus_msg  Указатель на структуру сообщения.
  * @return   RS_RES  Статус о результате ответа на комманду.
  */
RS_StatusTypeDef MB_Slave_Response(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg)
{
  RS_StatusTypeDef MB_RES = 0;
  hmodbus->f.MessageHandled = 0;
  hmodbus->f.EchoResponse = 0;
  RS_Reset_TX_Flags(hmodbus);     // reset flag for correct transmit
  
  MB_Diagnostics_BusMessageCnt();
  if(hmodbus->ID == 0 || modbus_msg->DeviceAddr == 0)
  {
    MB_Diagnostics_SlaveNoResponseCnt();  // <-- Устройство не отвечает на широковещательные сообщения
    hmodbus->RS_STATUS = RS_SKIP;
    return RS_Handle_Receive_Start(hmodbus, modbus_msg);
  }
  MB_Diagnostics_SlaveMessageCnt();
    
  if(modbus_msg->FuncCode < FC_ERR_VALUES_START)// if no errors after parsing
  {
    switch (modbus_msg->FuncCode)
    {
      // Read Coils
      case FC_R_COILS: 
        hmodbus->f.MessageHandled =   MB_Process_Read_Coils(hmodbus->pMessagePtr);
        break;
      
      // Read Hodling Registers
      case FC_R_HOLD_REGS:
        hmodbus->f.MessageHandled =   MB_Process_Read_Hold_Regs(hmodbus->pMessagePtr);
#ifdef MODBUS_ENABLE_OSCIL
        int is_oscil_data_register = ((hmodbus->pMessagePtr->Addr >= R_HOLDING_OSCIL_ADDR+4) && (hmodbus->pMessagePtr->Addr + hmodbus->pMessagePtr->Qnt <= (R_HOLDING_OSCIL_ADDR+5+(sizeof(MB_OscilTypeDef)/2))));
        if(is_oscil_data_register)
          MB_Oscil_UpdateTail(&MB_INTERNAL.oscil);
#endif
        break;
      case FC_R_IN_REGS: 
        hmodbus->f.MessageHandled =   MB_Process_Read_Input_Regs(hmodbus->pMessagePtr);
        break;
      
      
      // Write Single Coils
      case FC_W_COIL: 
        hmodbus->f.MessageHandled = MB_Process_Write_Single_Coil(hmodbus->pMessagePtr); 
        if(hmodbus->f.MessageHandled) 
        {
          hmodbus->f.DataUpdated = 1;
          hmodbus->f.EchoResponse = 1; 
          hmodbus->RS_Message_Size -= 2; // echo response if write ok (minus 2 cause of two CRC bytes)
        }
        break;
      
      case FC_W_HOLD_REG:   
        hmodbus->f.MessageHandled = MB_Process_Write_Single_Reg(hmodbus->pMessagePtr);  
        if(hmodbus->f.MessageHandled) 
        {
          hmodbus->f.DataUpdated = 1;
          hmodbus->f.EchoResponse = 1;
          hmodbus->RS_Message_Size -= 2; // echo response if write ok (minus 2 cause of two CRC bytes)
        }
        break;
      
      // Write Multiple Coils
      case FC_W_COILS: 
        hmodbus->f.MessageHandled =   MB_Process_Write_Miltuple_Coils(hmodbus->pMessagePtr);
        if(hmodbus->f.MessageHandled) 
        {
          hmodbus->f.DataUpdated = 1;
          hmodbus->f.EchoResponse = 1; 
          hmodbus->RS_Message_Size = 6; // echo response if write ok (withous data bytes)
        }
        break;
      
      // Write Multiple Registers
      case FC_W_HOLD_REGS:
        hmodbus->f.MessageHandled =   MB_Process_Write_Miltuple_Regs(hmodbus->pMessagePtr);
        if(hmodbus->f.MessageHandled) 
        {
          hmodbus->f.DataUpdated = 1;
          hmodbus->f.EchoResponse = 1; 
          hmodbus->RS_Message_Size = 6; // echo response if write ok (withous data bytes)
        }
        break;
      
      case FC_R_DEVICE_ID:
        hmodbus->f.MessageHandled =   MB_Process_Read_Device_Identifications(hmodbus->pMessagePtr);
        break;
        
      // Добавить в switch-case после других case:
      case FC_R_DIAGNOSTICS:
          hmodbus->f.MessageHandled = MB_Process_Diagnostics(hmodbus->pMessagePtr);
          break;

      /* unknown func code */
      default: 
        modbus_msg->Except_Code = 0x01; /* set exception code: illegal function */
    }   
    
    
    // Проверяем режим устройства - если Listen Only, не обрабатываем команды
    if (MB_GetDeviceMode() == MODBUS_LISTEN_ONLY_MODE)
    {
      MB_Diagnostics_SlaveNoResponseCnt();
      hmodbus->RS_STATUS = RS_SKIP;
      return RS_Handle_Receive_Start(hmodbus, modbus_msg);;
    }
  
    // Проверяем статус обработки запроса
    if(hmodbus->f.MessageHandled == 0)
    {
      MB_Diagnostics_ExceptionErrorCnt();
      TrackerCnt_Warn(hmodbus->rs_err);
      modbus_msg->FuncCode |= FC_ERR_VALUES_START;
    }
    else
    {
      TrackerCnt_Ok(hmodbus->rs_err);
    }
    
      
  }
  
  // if we need response - check that transmit isnt busy
  if( RS_Is_TX_Busy(hmodbus) ) 
    RS_Abort(hmodbus, ABORT_TX);    // if tx busy - set it free
  
  // Transmit right there, or sets (fDeferredResponse) to transmit response in main code  
  if(hmodbus->f.DeferredResponse == 0)
  {
    MB_RES = RS_Handle_Transmit_Start(hmodbus, modbus_msg);
  }
  else
  {
    RS_Handle_Receive_Start(hmodbus, modbus_msg);
    hmodbus->f.DeferredResponse = 0;
  }
  
  hmodbus->RS_STATUS = MB_RES;
  return MB_RES;
}



/** 
  * @brief    Сбор сообщения в буфер UART в режиме слейв (фрейм слейва из msg -> uart).
  * @param    hmodbus           Указатель на хендлер RS.
  * @param    modbus_msg        Указатель на структуру сообщения.
  * @param    modbus_uart_buff  Указатель на буффер UART.
  * @return   RS_RES        Статус о результате заполнения буфера.
  */
RS_StatusTypeDef MB_Slave_Collect_Message(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg, uint8_t *modbus_uart_buff)
{
  int ind = 0; // ind for modbus-uart buffer
  
  if(hmodbus->f.EchoResponse && hmodbus->f.MessageHandled)  // if echo response need
    ind = hmodbus->RS_Message_Size;
  else
  {
    //------INFO ABOUT DATA/MESSAGE------
#ifdef MODBUS_PROTOCOL_TCP
    modbus_uart_buff[ind++] = modbus_msg->TransactionID >> 8;
    modbus_uart_buff[ind++] = modbus_msg->TransactionID& 0xFF;
    
    modbus_uart_buff[ind++] = modbus_msg->ProtocolID >> 8;
    modbus_uart_buff[ind++] = modbus_msg->ProtocolID& 0xFF;
  
    modbus_uart_buff[ind++] = modbus_msg->PDULength >> 8;
    modbus_uart_buff[ind++] = modbus_msg->PDULength& 0xFF;
#endif 
    //-----------[first bytes]-----------
    // set ID of message/user
    modbus_uart_buff[ind++] = modbus_msg->DeviceAddr; 

    // set dat or err response
    modbus_uart_buff[ind++] = modbus_msg->FuncCode;  

    if (modbus_msg->FuncCode < FC_ERR_VALUES_START) // if no error occur
    {
      // fill modbus header
      if(0) {}
#ifdef MODBUS_ENABLE_DEVICE_IDENTIFICATIONS
      else if(modbus_msg->FuncCode == FC_R_DEVICE_ID) // devide identifications header
      {
        modbus_uart_buff[ind++] = modbus_msg->DevId.MEI_Type;
        modbus_uart_buff[ind++] = modbus_msg->DevId.ReadDevId;
        modbus_uart_buff[ind++] = modbus_msg->DevId.Conformity;
        modbus_uart_buff[ind++] = modbus_msg->DevId.MoreFollows;
        modbus_uart_buff[ind++] = modbus_msg->DevId.NextObjId;
        modbus_uart_buff[ind++] = modbus_msg->DevId.NumbOfObj;
        
        if (modbus_msg->ByteCnt > MbData_size*2) // if ByteCnt less than MbData_size
        {
          TrackerCnt_Err(hmodbus->rs_err);
          return RS_COLLECT_MSG_ERR;
        }
        
        
        //---------------DATA----------------
        //-----------[data bytes]------------
        uint8_t *tmp_data_addr = (uint8_t *)modbus_msg->MbData;
        for(int i = 0; i < modbus_msg->ByteCnt; i++) // filling buffer with data
        { // set data
          modbus_uart_buff[ind++] = *tmp_data_addr;
          tmp_data_addr++;
        }
        
      }
#endif //MODBUS_ENABLE_DEVICE_IDENTIFICATIONS
#ifdef MODBUS_ENABLE_DIAGNOSTICS
      else if(modbus_msg->FuncCode == FC_R_DIAGNOSTICS)
      {
        // Diagnostics special format: [SubFunc_HI][SubFunc_LO][Data_HI][Data_LO]
        modbus_uart_buff[ind++] = modbus_msg->MbData[0] >> 8;   // Sub-function HI
        modbus_uart_buff[ind++] = modbus_msg->MbData[0] & 0xFF; // Sub-function LO
        modbus_uart_buff[ind++] = modbus_msg->MbData[1] >> 8;   // Data HI
        modbus_uart_buff[ind++] = modbus_msg->MbData[1] & 0xFF; // Data LO
      }
#endif //MODBUS_ENABLE_DIAGNOSTICS
      else // modbus data header
      {
        // set size of received data
        if (modbus_msg->ByteCnt <= MbData_size*2) // if ByteCnt less than MbData_size
          modbus_uart_buff[ind++] = modbus_msg->ByteCnt;
        else                                        // otherwise return data_size err
        {
          TrackerCnt_Err(hmodbus->rs_err);
          return RS_COLLECT_MSG_ERR;
        }        
        
        //---------------DATA----------------
        //-----------[data bytes]------------
        uint16_t *tmp_data_addr = (uint16_t *)modbus_msg->MbData;
        for(int i = 0; i < modbus_msg->ByteCnt; i++) // filling buffer with data
        { // set data
          if (i%2 == 0)   // HI byte
            modbus_uart_buff[ind++] = (*tmp_data_addr)>>8;
          else            // LO byte
          {
            modbus_uart_buff[ind++] = *tmp_data_addr;
            tmp_data_addr++;
          }
        }

      }
      
    }
    else  // if some error occur
    {     // send expection code
      modbus_uart_buff[ind++] = modbus_msg->Except_Code;
    }
  }
  if(ind < 0)
    return RS_COLLECT_MSG_ERR;
  
  //---------------CRC----------------
  //---------[last 16 bytes]----------
#ifndef MODBUS_PROTOCOL_TCP
  // calc crc of received data
  uint16_t CRC_VALUE = crc16(modbus_uart_buff, ind);
  // write crc to message structure and modbus-uart buffer
  modbus_msg->MbCRC = CRC_VALUE;
  modbus_uart_buff[ind++] = CRC_VALUE;
  modbus_uart_buff[ind++] = CRC_VALUE >> 8;
#endif
  
  hmodbus->RS_Message_Size = ind;
  
  return RS_OK; // returns ok
}

/** 
  * @brief    Определить размер модбас запроса (СЛЕЙВ версия).
  * @param    hRS           Указатель на хендлер RS.
  * @param    rx_data_size  Указатель на переменную для записи кол-ва байт для принятия.
  * @return   RS_RES        Статус о корректности рассчета кол-ва байт для принятия.
  * @details  Определение сколько байтов надо принять по протоколу.
  */
static int MB_Define_Size_of_Function(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg)
{   
  RS_StatusTypeDef MB_RES = 0;
  int mb_func_size = 0;
  
  if (modbus_msg->FuncCode == FC_R_DIAGNOSTICS)
  {
    mb_func_size = 1;
  }  
  else if(modbus_msg->FuncCode == FC_R_DEVICE_ID)
  {
    mb_func_size = 0;
  }
  else if ((modbus_msg->FuncCode & ~FC_ERR_VALUES_START) < 0x0F)
  {
    mb_func_size = 1;
  }
  else
  {
    mb_func_size = modbus_msg->ByteCnt + 2;
  }
  
  
  mb_func_size = RS_RX_FIRST_PART_SIZE + mb_func_size; // size of whole message
  return mb_func_size;
}


/** 
  * @brief    Парс сообщения в режиме слейв (фрейм мастера из uart -> msg).
  * @param    hmodbus           Указатель на хендлер RS.
  * @param    modbus_msg        Указатель на структуру сообщения.
  * @param    modbus_uart_buff  Указатель на буффер UART.
  * @return   RS_RES        Статус о результате заполнения структуры.
  * @details    Заполнение структуры сообщения из буффера UART.
  */
RS_StatusTypeDef MB_Slave_Parse_Message(RS_HandleTypeDef *hmodbus, RS_MsgTypeDef *modbus_msg, uint8_t *modbus_uart_buff)
{
  uint32_t check_empty_buff;
  int ind = 0; // ind for modbus-uart buffer
  hmodbus->f.RX_Continue = 0;
  int expected_size = 0;
  //-----INFO ABOUT DATA/MESSAGE-------
#ifdef MODBUS_PROTOCOL_TCP
  modbus_msg->TransactionID  =modbus_uart_buff[ind++]<<8;
  modbus_msg->TransactionID  |=modbus_uart_buff[ind++];
  
  modbus_msg->ProtocolID =modbus_uart_buff[ind++]<<8;
  modbus_msg->ProtocolID |=modbus_uart_buff[ind++];
  
  modbus_msg->PDULength  =modbus_uart_buff[ind++]<<8;
  modbus_msg->PDULength  |=modbus_uart_buff[ind++];
#endif 
  //-----------[first bits]------------
  // get ID of message/user
  if(modbus_uart_buff[ind] != hmodbus->ID)
  {
    modbus_msg->DeviceAddr = 0;
    ind++;
  }
  else
  {
    modbus_msg->DeviceAddr = modbus_uart_buff[ind++];
  }
  
  // get func code
  modbus_msg->FuncCode = modbus_uart_buff[ind++];
  if(modbus_msg->FuncCode & FC_ERR_VALUES_START) // явная херня
  {
    MB_Diagnostics_SlaveNAKCnt();
    modbus_msg->DeviceAddr = 0;
    return RS_SKIP;
  }

  if(0) {}
#ifdef MODBUS_ENABLE_DEVICE_IDENTIFICATIONS
  else if(modbus_msg->FuncCode == FC_R_DEVICE_ID) // if it device identifications request
  {
    modbus_msg->DevId.MEI_Type = modbus_uart_buff[ind++];
    modbus_msg->DevId.ReadDevId = modbus_uart_buff[ind++];
    modbus_msg->DevId.NextObjId = modbus_uart_buff[ind++];
    modbus_msg->ByteCnt = 0;
  }
#endif //MODBUS_ENABLE_DEVICE_IDENTIFICATIONS
#ifdef MODBUS_ENABLE_DIAGNOSTICS
  else if(modbus_msg->FuncCode == FC_R_DIAGNOSTICS)
  {
    // Diagnostics: читаем 4 байта в MbData[0] и MbData[1]
    // Sub-function
    modbus_msg->MbData[0] = modbus_uart_buff[ind++] << 8; 
    modbus_msg->MbData[0] |= modbus_uart_buff[ind++];
    // Data
    modbus_msg->MbData[1] = modbus_uart_buff[ind++] << 8; 
    modbus_msg->MbData[1] |= modbus_uart_buff[ind++];
    modbus_msg->Addr = 0;     // не использует Addr
    modbus_msg->Qnt = 0;      // не использует Qnt
  }
#endif //MODBUS_ENABLE_DIAGNOSTICS
  else // if its classic modbus request
  {
    // get address from CMD
    modbus_msg->Addr = modbus_uart_buff[ind++] << 8;
    modbus_msg->Addr |= modbus_uart_buff[ind++];
    
    // get address from CMD
    modbus_msg->Qnt = modbus_uart_buff[ind++] << 8;
    modbus_msg->Qnt |= modbus_uart_buff[ind++];
  }
  
  if((hmodbus->pMessagePtr->FuncCode == 0x0F) || (hmodbus->pMessagePtr->FuncCode == 0x10))
    hmodbus->pMessagePtr->ByteCnt = modbus_uart_buff[ind++];
  else
    hmodbus->pMessagePtr->ByteCnt = 0;
  
  expected_size = MB_Define_Size_of_Function(hmodbus, modbus_msg);
  // если размер меньше ожидаемого - продолжаем принимать
  if(hmodbus->RS_Message_Size < expected_size)
  {
    hmodbus->f.RX_Continue = 1;
    return RS_SKIP;
  }
  // если больше Ошибка
  else if (hmodbus->RS_Message_Size > expected_size)
  {
    MB_Diagnostics_CommunicationErrorCnt();
    return RS_PARSE_MSG_ERR;
  }
  
  //---------------DATA----------------
  //            (optional)
  if (modbus_msg->ByteCnt != 0)
  {
    //check that data size is correct
    if (modbus_msg->ByteCnt > MbData_size*2)
    {
      TrackerCnt_Err(hmodbus->rs_err);
      modbus_msg->FuncCode |= FC_ERR_VALUES_START;
      MB_Diagnostics_CommunicationErrorCnt();
      return RS_PARSE_MSG_ERR;
    }
    uint16_t *tmp_data_addr = (uint16_t *)modbus_msg->MbData;
    for(int i = 0; i < modbus_msg->ByteCnt; i++)
    { // set data
      if (i%2 == 0)
        *tmp_data_addr = ((uint16_t)modbus_uart_buff[ind++] << 8);
      else
      {
        *tmp_data_addr |= modbus_uart_buff[ind++];
        tmp_data_addr++;
      }
    }
  }
  
  //---------------CRC----------------
  //----------[last 16 bits]----------
#ifndef MODBUS_PROTOCOL_TCP
  // calc crc of received data
  uint16_t CRC_VALUE = crc16(modbus_uart_buff, ind);
  // get crc of received data
  modbus_msg->MbCRC = modbus_uart_buff[ind++];
  modbus_msg->MbCRC |= modbus_uart_buff[ind++] << 8;
  // compare crc
  if (modbus_msg->MbCRC != CRC_VALUE)
  {
    MB_Diagnostics_CommunicationErrorCnt();
    TrackerCnt_Err(hmodbus->rs_err);
    modbus_msg->FuncCode |= FC_ERR_VALUES_START;
  }  
#endif
  
  return RS_OK;
}

#endif //MODBUS_ENABLE_SLAVE
