/**
******************************************************************************
* @file modbus_data.c
* @brief Функции доступа к данным Modbus
******************************************************************************
* @details
Модуль реализует функции валидации адресов и доступа к данным:
- Проверка корректности запрашиваемых адресов
- Определение указателей на реальные данные в памяти
- Поддержка пользовательских массивов регистров и coils

@section Валидация адресов:
- MB_Check_Address_For_Arr() - проверка принадлежности адреса массиву
- MB_DefineRegistersAddress() - получение указателя на регистры
- MB_DefineCoilsAddress() - получение указателя на coils

******************************************************************************/

#include "modbus_core.h"
#include "modbus_coils.h"
#include "modbus_holdregs.h"
#include "modbus_inputregs.h"
#include "modbus_devid.h"

/* DEFINE DATA FOR MODBUS */
MB_DataStructureTypeDef MB_DATA = {0};   ///< Coils & Registers
MB_DataInternalTypeDef MB_INTERNAL;

/** 
  * @brief    Check is address valid for certain array.
  * @param    Addr          Начальный адресс.
  * @param    Qnt           Количество запрашиваемых элементов.
  * @param    R_ARR_ADDR    Начальный адресс массива R_ARR.
  * @param    R_ARR_NUMB    Количество элементов в массиве R_ARR.
  * @return   ExceptionCode - ET_ILLEGAL_DATA_ADRESS если адресс недействителен, и ET_NO_ERRORS если все ок.
  *
  * @details  Позволяет определить, принадлежит ли адресс Addr массиву R_ARR:
  *           Если адресс Addr находится в диапазоне адрессов массива R_ARR, то возвращаем NO_ERROR.
  *           Если адресс Addr находится за пределами адрессов массива R_ARR - ET_ILLEGAL_DATA_ADDRESSю.
  */
MB_ExceptionTypeDef MB_Check_Address_For_Arr(uint16_t Addr, uint16_t Qnt, uint16_t R_ARR_ADDR, uint16_t R_ARR_NUMB)
{
  // if address from this array
  if(Addr >= R_ARR_ADDR) 
  {   
    // if quantity too big return error
    if ((Addr - R_ARR_ADDR) + Qnt > R_ARR_NUMB) 
    {
      return ET_ILLEGAL_DATA_ADDRESS;  // return exception code
    }
    // if all ok - return no errors
    return ET_NO_ERRORS;
  }
  // if address isnt from this array return error
  else 
    return ET_ILLEGAL_DATA_ADDRESS;  // return exception code    
}
/** 
  * @brief    Define Address Origin for Input/Holding Registers
  * @param    pRegs         Указатель на указатель регистров.
  * @param    Addr          Адрес начального регистра.
  * @param    Qnt           Количество запрашиваемых регистров.
  * @param    WriteFlag     Флаг регистр нужны для чтения или записи.
  * @return   ExceptionCode Код исключения если есть, и ET_NO_ERRORS если нет.
  *
  * @details  Определение адреса начального регистра.
  * @note     WriteFlag пока не используется.
  */
MB_ExceptionTypeDef MB_DefineRegistersAddress(uint16_t **pRegs, uint16_t Addr, uint16_t Qnt, uint8_t RegisterType, uint8_t WriteFlag)
{ 
  
  /* check quantity error */
  if (Qnt > MbData_size)  
  {   
      return ET_ILLEGAL_DATA_VALUE;  // return exception code
  }
  
  if(RegisterType == RegisterType_Holding)
  {
    // Default holding registers
    if(MB_Check_Address_For_Arr(Addr, Qnt, R_HOLDING_ADDR, R_HOLDING_QNT) == ET_NO_ERRORS)
    {
      *pRegs = MB_Set_Register_Ptr(&MB_DATA.HoldRegs, Addr - R_HOLDING_ADDR);  // указатель на выбранный по Addr регистр
    }
    else if(MB_Check_Address_For_Arr(Addr, Qnt, R_HOLDING_PRVT_PARAMS_ADR, R_HOLDING_PRVT_PARAMS_QNT) == ET_NO_ERRORS)
    {
      *pRegs = MB_Set_Register_Ptr(&MB_INTERNAL, Addr - R_HOLDING_PRVT_PARAMS_ADR);  // указатель на выбранный по Addr регистр
    }
    // if address doesnt match any array - return illegal data address response
    else  
    {
      return ET_ILLEGAL_DATA_ADDRESS;
    }
  }
  else if(RegisterType == RegisterType_Input)
  {
    // Default input registers
    if(MB_Check_Address_For_Arr(Addr, Qnt, R_INPUT_ADDR, R_INPUT_QNT) == ET_NO_ERRORS)
    {
      *pRegs = MB_Set_Register_Ptr(&MB_DATA.InRegs, Addr - R_INPUT_ADDR);  // указатель на выбранный по Addr регистр
    }
    // if address doesnt match any array - return illegal data address response
    else  
    {
      return ET_ILLEGAL_DATA_ADDRESS;
    }
  }
  else
  {
      return ET_ILLEGAL_FUNCTION;
  }
  // if found requeried array return no err
  return ET_NO_ERRORS; // return no errors
}
/** 
  * @brief    Define Address Origin for coils
  * @param    pCoils        Указатель на указатель коилов.
  * @param    Addr          Адресс начального коила.
  * @param    Qnt           Количество запрашиваемых коилов.
  * @param    start_shift   Указатель на переменную содержащую сдвиг внутри регистра для начального коила.
  * @param    WriteFlag     Флаг коилы нужны для чтения или записи.
  * @return   ExceptionCode Код исключения если есть, и ET_NO_ERRORS если нет.
  *
  * @details  Определение адреса начального регистра запрашиваемых коилов.
  * @note     WriteFlag используется для определния регистров GPIO: ODR или IDR.
  */
MB_ExceptionTypeDef MB_DefineCoilsAddress(uint16_t **pCoils, uint16_t Addr, uint16_t Qnt, uint16_t *start_shift, uint8_t WriteFlag)
{ 
  /* В режиме работа ничего не записываем */
  
  /* check quantity error */
  if (Qnt > 2000) 
  {
    return ET_ILLEGAL_DATA_VALUE;  // return exception code
  }
    
  // Default coils
  if(MB_Check_Address_For_Arr(Addr, Qnt, C_COILS_ADDR, C_COILS_QNT) == ET_NO_ERRORS)
  {
    *pCoils = MB_Set_Coil_Reg_Ptr(&MB_DATA.Coils, Addr - C_COILS_ADDR);  // указатель на выбранный по Addr массив коилов
  }
  // if address doesnt match any array - return illegal data address response
  else  
  {
    return ET_ILLEGAL_DATA_ADDRESS;
  }
  
  *start_shift = Addr % 16; // set shift to requested coil
  // if found requeried array return no err
  return ET_NO_ERRORS; // return no errors
}
