/**
*******************************************************************************
* @file modbus_core.h
* @brief Ядро Modbus протокола - определения и структуры
*******************************************************************************
@addtogroup MODBUS_INTERNAL Modbus Internal Tools
@ingroup MODBUS
@brief Внутренние штуки библиотеки
@{
*******************************************************************************
* @details
Базовые определения для реализации Modbus RTU устройства:
- Структуры сообщений Modbus
Коды функций и исключений
Константы размеров полей
Вспомогательные макросы

@section msg Структура сообщения:
[ADDR][FUNC][DATA...][CRC]
- Адрес: 1 байт
- Функция: 1 байт  
- Данные: переменной длины
- CRC: 2 байта

******************************************************************************/
#ifndef __MODBUS_CORE_H_
#define __MODBUS_CORE_H_
 
#include "modbus_config.h"
#include "__crc_algs.h"

/** 
  * @addtogroup MODBUS_MESSAGE_DEFINES Modbus Message Tools
  * @ingroup    MODBUS
  * @brief      Определения протокола модбас
  * @{
  */

/////////////////////////////////////////////////////////////////////
////////////////////---MODBUS MESSAGE DEFINES---/////////////////////
//-------------DEFINES FOR STRUCTURE----------------
/* defines for structure of modbus message */
#define MbTransactionID_size      2           ///< size of (Transaction ID)
#define MbProtocolID_size         2           ///< size of (Protocol ID)
#define MbPDULength_size          2           ///< size of (PDU Length)
#define MbDeviceAddr_size             1           ///< size of (Slave Addr)
#define MbFuncCode_size          1           ///< size of (Function Code)
#define MbAddr_size               2           ///< size of (Address)
#define MbQnt_size                2           ///< size of (Quantity)
#define MbByteCnt_size            1           ///< size of (Byte Count)
#define MbData_size               125         ///< maximum number of data: DWORD (NOT MESSAGE SIZE)
#define MbCRC_size                2           ///< size of (MbCRC) in bytes

#ifndef MODBUS_PROTOCOL_TCP
/** @brief Size of whole RTU message */
#define RS_INFO_SIZE_MAX           (MbDeviceAddr_size+MbFuncCode_size+MbAddr_size+MbQnt_size+MbByteCnt_size)
#else
/** @brief Size of whole TCP message */
#define RS_INFO_SIZE_MAX           (MbTransactionID_size+MbProtocolID_size+MbPDULength_size+MbDeviceAddr_size+MbFuncCode_size+MbAddr_size+MbQnt_size)
#endif

/** @brief Size of first part of message that will be received
first receive info part of message, than defines size of rest message*/
#define RS_RX_FIRST_PART_SIZE      RS_INFO_SIZE_MAX

/** @brief Size of buffer: max size of whole message */
#define RS_MSG_SIZE_MAX            (RS_INFO_SIZE_MAX + MbData_size*2 + MbCRC_size) // max possible size of message

/** 
  * @brief Enum for modbus exception codes 
  * @details Prefix ET for Error Type
  */
typedef enum //MB_ExceptionTypeDef
{
  // reading
  ET_NO_ERRORS                 = 0x00,   ///< no errors
  ET_ILLEGAL_FUNCTION          = 0x01,   ///< Принятый код функции не может быть обработан
  ET_ILLEGAL_DATA_ADDRESS      = 0x02,   ///< Адрес данных, указанный в запросе, недоступен
  ET_ILLEGAL_DATA_VALUE        = 0x03,   ///< Значение, содержащееся в поле данных запроса, является недопустимой величиной
  ET_SLAVE_DEVICE_FAILURE      = 0x04,   ///< Невосстанавливаемая ошибка имела место, пока ведомое устройство пыталось выполнить затребованное действие
//  ET_ACKNOWLEDGE =           0x05,   ///< idk
//  ET_SLAVE_DEVICE_BUSY =     0x06,   ///< idk
//  ET_MEMORY_PARITY_ERROR =   0x08,   ///< idk
}MB_ExceptionTypeDef;

#define FC_ERR_VALUES_START        0x80U       ///< from this value starts error func codes
/** 
  * @brief Enum for modbus func codes 
  * @details Prefix FC for Function Code
  */
typedef enum //MB_FunctonTypeDef
{
  /* COMMANDS */
  // reading
  FC_R_COILS            = 0x01,     ///< Чтение битовых ячеек
  FC_R_DISC_IN          = 0x02,     ///< Чтение дискретных входов
#ifndef MODBUS_SWITCH_COMMAND_R_IN_REGS_AND_R_HOLD_REGS
  FC_R_HOLD_REGS        = 0x03,     ///< Чтение входных регистров
  FC_R_IN_REGS          = 0x04,     ///< Чтение регистров хранения
#else
  FC_R_HOLD_REGS        = 0x04,     ///< Чтение входных регистров
  FC_R_IN_REGS          = 0x03,     ///< Чтение регистров хранения
#endif
  
  // writting
  FC_W_COIL             = 0x05,     ///< Запись битовой ячейки
  FC_W_HOLD_REG         = 0x06,     ///< Запись одиночного регистра
  FC_W_COILS            = 0x0F,     ///< Запись нескольких битовых ячеек
  FC_W_HOLD_REGS        = 0x10,     ///< Запись нескольких регистров
  
  FC_R_DIAGNOSTICS      = 0x08,     ///< Чтение диагностической информации устройства
  FC_R_DEVICE_ID        = 0x2B,     ///< Чтение информации об устройстве
  
  /* ERRORS */
  // error reading
  FC_ERR_R_COILS        = FC_R_COILS      + FC_ERR_VALUES_START,        ///< Ошибка чтения битовых ячеек
  FC_ERR_R_DISC_IN      = FC_R_DISC_IN    + FC_ERR_VALUES_START,      ///< Ошибка чтения дискретных входов
  FC_ERR_R_IN_REGS      = FC_R_IN_REGS    + FC_ERR_VALUES_START,      ///< Ошибка чтения регистров хранения
  FC_ERR_R_HOLD_REGS    = FC_R_HOLD_REGS  + FC_ERR_VALUES_START,    ///< Ошибка чтения входных регистров
  
  // error writting
  FC_ERR_W_COIL         = FC_W_COIL       + FC_ERR_VALUES_START,         ///< Ошибка записи битовой ячейки
  FC_ERR_W_HOLD_REG     = FC_W_HOLD_REG   + FC_ERR_VALUES_START,     ///< Ошибка записи одиночного регистра
  FC_ERR_W_COILS        = FC_W_COILS      + FC_ERR_VALUES_START,        ///< Ошибка записи нескольких битовых ячеек
  FC_ERR_W_HOLD_REGS    = FC_W_HOLD_REGS  + FC_ERR_VALUES_START,    ///< Ошибка записи нескольких регистров
  
  FC_ERR_R_DIAGNOSTIC   = FC_R_DIAGNOSTICS  + FC_ERR_VALUES_START,   ///< Ошибка чтения диагностической информации устройства
  FC_ERR_R_DEVICE_INFO  = FC_R_DEVICE_ID    + FC_ERR_VALUES_START,   ///< Ошибка чтения информации об устройстве
}MB_FunctonTypeDef;

/** 
  * @brief Enum for MEI func codes 
  */
typedef enum //MB_FunctonTypeDef
{
  MEI_DEVICE_IDENTIFICATIONS = 0x0E,
}MB_MEITypeDef;

/** 
  * @brief Enum for Read Device Id codes 
  * @details Prefix RID for Read ID
  */
typedef enum //MB_FunctonTypeDef
{
  RID_BASIC_IDENTIFICATIONS         = 0x01,   /*!<  @brief Basic Device Identifications. 
                                                  @details All objects of this category are mandatory: 
                                                  VendorName, Product code, and revision number */
  
  RID_REGULAR_IDENTIFICATIONS       = 0x02,   /*!<  @brief Regular Device Identifications. 
                                                  @details The device provides additional and optional 
                                                  identifications and description data objects */
  
  RID_EXTENDED_IDENTIFICATIONS      = 0x03,   /*!<  @brief Extended Device Identifications. 
                                                  @details The device provides additional and optional 
                                                  identifications and description private data about the physical
                                                  device itself. All of these data are device dependent. */
  
  RID_SPEDIFIC_IDENTIFICATIONS      = 0x04,   /*!<  @brief Specific Device Identifications. 
                                                  @details The device provides one specific identifications object. */
  
}MB_ReadDevId;

/** @brief Structure for device identifications message type */
typedef struct
{
  MB_MEITypeDef             MEI_Type;       ///< MEI Type assigned number for Device Identifications Interface
  MB_ReadDevId              ReadDevId;
  uint8_t                   Conformity;     ///< Identification conformity level of the device and type of supported access @ref MODBUS_DEVICE_CONFORMITY
  uint8_t                   MoreFollows;
  uint8_t                   NextObjId;
  uint8_t                   NumbOfObj;  
}MB_DevIdMsgTypeDef;


/** @brief Structure for modbus messsage */
typedef struct  // RS_MsgTypeDef
{
#ifdef MODBUS_PROTOCOL_TCP
  uint16_t              TransactionID;     ///< Modbus TCP: ID Transaction
  uint16_t              ProtocolID;        ///< Modbus TCP: ID Protocol
  uint16_t              PDULength;         ///< Modbus TCP: PDU Length
#endif
  
  uint8_t               DeviceAddr;             ///< Modbus Slave Address
  MB_FunctonTypeDef     FuncCode;          ///< Modbus Function Code
  MB_DevIdMsgTypeDef    DevId;              ///< Read Device Identifications Header struct
  uint16_t              Addr;               ///< Modbus Address of data
  uint16_t              Qnt;                ///< Quantity of modbus data
  uint8_t               ByteCnt;            ///< Quantity of bytes of data in message to transmit/receive
  
  uint16_t              MbData[MbData_size];  ///< Modbus Data
  MB_ExceptionTypeDef   Except_Code;        ///< Exception Code for the command
  
  uint16_t              MbCRC;             ///< Modbus CRC
}RS_MsgTypeDef;
//--------------------------------------------------
extern RS_MsgTypeDef MODBUS_MSG;
////////////////////---MODBUS MESSAGE DEFINES---/////////////////////



/////////////////////////////////////////////////////////////////////
////////////////////---MODBUS FUNCTION DEFINES---////////////////////

/** 
  * @brief  Macros to set pointer to 16-bit array
  * @param  _arr_         - массив регистров (16-бит).
  */
#define MB_Set_Arr16_Ptr(_arr_)                       ((uint16_t*)(&(_arr_)))
/** 
  * @brief  Macros to set pointer to register
  * @param  _parr_        - массив регистров.
  * @param  _addr_        - Номер регистра (его индекс) от начала массива _arr_.
  */
#define MB_Set_Register_Ptr(_parr_, _addr_)           ((uint16_t *)(_parr_)+(_addr_))

/** 
  * @brief  Макрос для установки указателя на регистр, содержащий запрашиваемый коил
  * @param  _parr_  - массив коилов.
  * @param  _coil_  - Номер коила от начала массива _arr_.
  * @note   Используется вместе с @ref MB_Set_Coil_Mask
  @code Пояснение выражений
  - (_coil_/16) - индекс регистра, в котором содержится коил по адресу _coil_

Визуальный пример: 30 коил будет в 30/16 = 1 регистре (индексация с 0)
  xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxCx
  |register[0]----| |register[1]----|
  |skip this------| |get this-------|
                    |shift to 14 bit|
  @endcode
  */
#define MB_Set_Coil_Reg_Ptr(_parr_, _coil_)           ((uint16_t *)(_parr_)+((_coil_)/16))
/** 
  * @brief  Макрос для установки маски, чтобы выделить запрашиваемый коил из регистра
  * @param  _coil_  - Номер коила от начала массива _arr_.
  * @note   Используется вместе с @ref MB_Set_Coil_Reg_Ptr
  @code Пояснение выражений
  - (16*(_coil_/16) - сколько коилов нужно пропустить. прим. (16*30/16) - первые 16 коилов находятся вне регистра
  - _coil_-(16*(_coil_/16)) - сдвинуть бит на место запрашиваемого коила в регистре
  
  Визуальный пример: 30 коил будет регистре[1], на 14 бите:
    register = 30/16 = 1
    bit = 30 - (16*30/16) = 30 - 16 = 14
    
  xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxCx
  |register[0]----| |register[1]----|
  |skip this------| |get this-------|
                    |shift to 14 bit|
  @endcode
  */
#define MB_Set_Coil_Mask(_coil_)                      (1 << ( _coil_ - (16*((_coil_)/16)) ))
  
/** GENERAL_MODBUS_STUFF
  * @} 
  */


//------------------OTHER DEFINES-------------------
#define RegisterType_Holding        0
#define RegisterType_Input          1
#define RegisterType_Discrete       2
// create hadnles and settings for uart, tim, rs with _modbus_ name
//--------------------------------------------------

#ifndef Divide_Up
/**
  * @brief  Calc dividing including remainder
  * @param  _val_   - делимое.
  * @param  _div_   - делитель.
  * @details  Если результат деления без остатка: он возвращается как есть
            Если с остатком - округляется вверх
  */
//#define Divide_Up(_val_, _div_)                       (((_val_)%(_div_))? (_val_)/(_div_)+1 : (_val_)/_div_)  /* через тернарный оператор */
#define Divide_Up(_val_, _div_)                       ((_val_ - 1) / _div_) + 1                                 /* через мат выражение */ 
#endif

#ifndef ByteSwap16
/** 
  * @brief  Swap between Little Endian and Big Endian
  * @param  v       - Переменная для свапа.
  * @return v (new) - Свапнутая переменная.
  * @details  Переключения между двумя типами хранения слова: HI-LO байты и LO-HI байты.
  */
#define ByteSwap16(v)                                 (((v&0xFF00) >> (8)) | ((v&0x00FF) << (8)))
#endif
////////////////////---MODBUS MESSAGE DEFINES---/////////////////////


/////////////////////////////////////////////////////////////////////
/////////////////////////---FUNCTIONS---/////////////////////////////
  
/** 
  * @addtogroup MODBUS_CMD_PROCESS_FUNCTIONS
  * @{
  */
  
/* Реализация этих функций лежит в modbus_data.c */

/* Check is address valid for certain array */
MB_ExceptionTypeDef MB_Check_Address_For_Arr(uint16_t Addr, uint16_t Qnt, uint16_t R_ARR_ADDR, uint16_t R_ARR_NUMB);
/* Define Address Origin for Input/Holding Registers */
MB_ExceptionTypeDef MB_DefineRegistersAddress(uint16_t **pRegs, uint16_t Addr, uint16_t Qnt, uint8_t RegisterType, uint8_t WriteFlag);
/* Define Address Origin for coils */
MB_ExceptionTypeDef MB_DefineCoilsAddress(uint16_t **pCoils, uint16_t Addr, uint16_t Qnt, uint16_t *start_shift, uint8_t WriteFlag);
/** MODBUS_CMD_PROCESS_FUNCTIONS
  * @} 
  */

/////////////////////////---FUNCTIONS---/////////////////////////////
#include "__modbus_compat.h"
#endif //__MODBUS_CORE_H_
/** MODBUS_INTERNAL
  * @} 
  */

