#ifndef LIB_MB_SERVER_H
#define LIB_MB_SERVER_H

/***************************************************************************//**
 @addtogroup lip_mb_server
 @{
 @file lip_mb_server.h
 @brief Файл содержит определения функций и макросов сервера Modbus (без привязки к TCP),
   которые могут быть использованы в приложении.
 @author Borisov Alexey <borisov@lcard.ru>
*******************************************************************************/

#include "lip_mb_defs.h"
#include "lip/transport/tcp/lip_tcp.h"

#define LIP_MB_GET_WORD(data) (uint16_t)(((uint16_t)(data)[0] << 8) | (data)[1])
#define LIP_MB_PUT_WORD(data, word) do { \
        (data)[0] = ((word) >> 8) & 0xFF; \
        (data)[1] = (word) & 0xFF; \
    } while(0)

/** Тип протокола, используемого при подключении */
typedef enum {
    LIP_MB_PROTO_TCP = 1  /**< Modbus-TCP */
} t_lip_mb_proto;

/** Информация о подключении, по которому пришел запрос */
typedef struct {
    t_lip_mb_proto proto; /**< используемый протокол */
    union {
        struct {
            t_lsock_id socket;  /** хэндл сокета при работе по Modbus-TCP */
        } tcp;
    };
} t_lip_mb_con_info;

/****************************************************************************//**
    @brief Пользовательская функция выбора подключения.

    Данный тип определяет функцию обрабтного вызова, которая служит для выбора
    подходящих для какой-либо операций клиентских подключений modbus.

    @param[in] con     Указатель на проверяемое пользовательское подключение.
    @param[in] ctx     Указатель на контест проверки.
    @return            Если подключение не подходит условию, то возвращает 0,
                       если подходит - любое другое значение
*******************************************************************************/
typedef int (*t_lip_mb_check_con_cb)(const t_lip_mb_con_info *con, void *ctx) ;


/****************************************************************************//**
   @brief Сохранение в массиве байт значения регистра Modbus

   Макрос сохраняет в массиве байт значение 16-битного регистра Modbus с заданным
   адресом.

   @param data        Указатель на начало байтового массива для сохранения значения регистров
   @param first_addr  Адрес регистра, которому соответствует начало массива data
   @param reg_addr    Адрес сохраняемого регистра
   @param word        16-битное значение регистра
*******************************************************************************/
#define LIP_MB_PUT_REG(data, first_addr, reg_addr, word) LIP_MB_PUT_WORD(&((data)[((reg_addr) - (first_addr)) << 1]), word)
/****************************************************************************//**
    @brief Получение значения регистра Modbus из массива байт

    Макрос извлекает из массива байт со значениями регистров Modbus 16-битное
    значение нужного регистра и подставляет в месте использования макроса.
    @param data        Указатель на начало байтового массива с значениями регистров
    @param first_addr  Адрес регистра, которому соответствует начало массива data
    @param reg_addr    Адрес извлекаемого регистра
*******************************************************************************/
#define LIP_MB_GET_REG(data, first_addr, reg_addr) LIP_MB_GET_WORD(&((data)[((reg_addr) - (first_addr)) << 1]))


/****************************************************************************//**
   @brief Сохранение в массиве байт значения битового поля Modbus

   Макрос сохраняет в массиве байт значение одного битового поля Modbus с заданным
   адресом.

   @param data        Указатель на начало байтового массива для сохранения битовых полей
   @param first_addr  Адрес битового поля, которому соответствует начало массива data
   @param bit_addr    Адрес сохраняемого битового поля
   @param bit_val     Значение битового поля (0 или 1)
*******************************************************************************/
#define LIP_MB_PUT_BIT(data, first_addr, bit_addr, bit_val) do { \
        uint16_t byte_pos = ((bit_addr) - (first_addr)) / 8; \
        uint16_t bit_pos = ((bit_addr) - (first_addr))  & 0x7; \
        if (bit_val) { \
            (data)[byte_pos] |= (1 << bit_pos); \
        } else { \
            (data)[byte_pos] &= ~(1 << bit_pos); \
        } \
    } while(0)

/****************************************************************************//**
   @brief Получение значения битового поля Modbus из массива байт

   Макрос извлекает из массива байт со значениями битов Modbus
   значение нужного битового поля и подставляет в месте использования макроса.

   @param data        Указатель на начало байтового массива с значениями битовых полей
   @param first_addr  Адрес битового поля, которому соответствует начало массива data
   @param bit_addr    Адрес извлекаемого битового поля
*******************************************************************************/
#define LIP_MB_GET_BIT(data, first_addr, bit_addr) ((data)[((bit_addr) - (first_addr)) / 8] & \
    (1 << (((bit_addr) - (first_addr))  & 0x7)) ? 1 : 0)


/** Тип регистров modbus */
typedef enum {
    LIP_MB_REGTYPE_INPUT, /**< Input register (только чтение) */
    LIP_MB_REGTYPE_HOLDING /**< Holding register (чтение и запись) */
} t_lip_mb_reg_type;

/** Тип битовых полей modbus */
typedef enum {
    LIP_MB_BITTYPE_INPUT, /**< Discrete input (только чтение) */
    LIP_MB_BITTYPE_COIL   /**< Coil (чтение и запись) */
} t_lip_mb_bit_type;





/***************************************************************************//**
   @brief Пользовательская функция чтения регистров.

   Функция данного типа обрабатывает запрос клиента на чтение из сервера значений
   Input или Holding регистров. Функция должна заполнить массив regs_data с помощью
   макроса LIP_MB_PUT_REG() значениями соответствующих регистров или вернуть код ошибки.
   @param[in] coninfo       Информация о подулючении, по которому пришел запрос
   @param[in] devaddr       Передаваемый Modbus-адрес устройства (значение поля Unit Identifier)
   @param[in] reg_addr      Адрес первого регистра (от 1 до 65536)
   @param[in] reg_cnt       Запрашиваемое количество регистров
   @param[in] regtype       Тип запрашиваемых регистров (input/holding)
   @param[out] regs_data    Массив для сохранения значений считанных регистров
   @return                  Код ошибки Modbus (#LIP_MB_EXC_OK при успешном выполнении)
*******************************************************************************/
typedef t_lip_mb_exception_code (* t_lip_mb_cb_read_regs)(const t_lip_mb_con_info *coninfo, uint8_t devaddr,
                                                          uint32_t reg_addr, uint16_t reg_cnt,
                                                          t_lip_mb_reg_type regtype, uint8_t *regs_data);
/***************************************************************************//**
   @brief Пользовательская функция записи регистров.

   Функция данного типа обрабатывает запрос клиента на запись в сервер значений
   Holding регистров. Функция должна извлечь значения регистров из массива regs_data
   c помощью макроса LIP_MB_GET_REG() и выполнить операцию записи этих значений или
   вернуть код ошибки.
   @param[in] coninfo       Информация о подулючении, по которому пришел запрос
   @param[in] devaddr       Передаваемый Modbus-адрес устройства (значение поля Unit Identifier)
   @param[in] reg_addr      Адрес первого регистра (от 1 до 65536)
   @param[in] reg_cnt       Количество записываемых регистров
   @param[in] regs_data     Массив, содержащий записываемые значения регистров
   @return                  Код ошибки Modbus (#LIP_MB_EXC_OK при успешном выполнении)
*******************************************************************************/
typedef t_lip_mb_exception_code (* t_lip_mb_cb_write_regs)(const t_lip_mb_con_info *coninfo, uint8_t devaddr,
                                                           uint32_t reg_addr, uint16_t reg_cnt,
                                                           const uint8_t *regs_data);

/***************************************************************************//**
   @brief Пользовательская функция чтения битовых значений.

   Функция данного типа обрабатывает запрос клиента на чтение из сервера значений
   битовых полей Discrete Inputs или Coils. Функция должна заполнить массив
   bit_data с помощью макроса LIP_MB_PUT_BIT() значениями соответствующих битовых полей
   или вернуть код ошибки.
   @param[in] coninfo       Информация о подулючении, по которому пришел запрос
   @param[in] devaddr       Передаваемый Modbus-адрес устройства (значение поля Unit Identifier)
   @param[in] reg_addr      Адрес первого битового поля (от 1 до 65536)
   @param[in] reg_cnt       Запрашиваемое количество битовых полей
   @param[in] regtype       Тип запрашиваемых битовых полей (discrete input/coil)
   @param[out] bit_data     Массив для сохранения значений считанных значений битовых полей
   @return                  Код ошибки Modbus (#LIP_MB_EXC_OK при успешном выполнении)
*******************************************************************************/
typedef t_lip_mb_exception_code (* t_lip_mb_cb_read_bits)(const t_lip_mb_con_info *coninfo, uint8_t devaddr,
                                                          uint32_t bit_addr, uint16_t bit_cnt,
                                                          t_lip_mb_bit_type bittype, uint8_t *bit_data);
/***************************************************************************//**
   @brief Пользовательская функция записи битовых значений.

   Функция данного типа обрабатывает запрос клиента на запись в сервер значений
   битовых полей типа Coil. Функция должна извлечь значения битовых полей из массива
   bit_data c помощью макроса LIP_MB_GET_BIT() и выполнить операцию записи этих
   значений или вернуть код ошибки.
   @param[in] coninfo       Информация о подулючении, по которому пришел запрос
   @param[in] devaddr       Передаваемый Modbus-адрес устройства (значение поля Unit Identifier)
   @param[in] reg_addr      Адрес первого битового поля (от 1 до 65536)
   @param[in] reg_cnt       Количество записываемых битовых полей
   @param[in] regs_data     Массив, содержащий записываемые значения битовых полей
   @return                  Код ошибки Modbus (#LIP_MB_EXC_OK при успешном выполнении)
*******************************************************************************/
typedef t_lip_mb_exception_code (* t_lip_mb_cb_write_bits)(const t_lip_mb_con_info *coninfo, uint8_t devaddr,
                                                           uint32_t bit_addr, uint16_t bit_cnt,
                                                           const uint8_t *bit_data);

/**************************************************************************//**
   @brief Стркутура с указателями на Callback-функции для сервера ModbusTCP.

    С помощью данной структуры пользователь передает ModbusTCP серверу указатели на
    пользовательские функции, выполняющие чтение и запись регистров и битовых полей.
    Данная структура должна быть перед работой проинициализирована с помощью
    lip_mb_server_cb_init(), после чего должны быть заполнены нужные поля и вызвана
    функция lip_mb_server_cb_set().
 *********************************************************************************/
typedef struct {
    t_lip_mb_cb_read_regs   read_regs; /**< Указатель на функцию чтения Input или Holding регистров */
    t_lip_mb_cb_write_regs  write_regs; /**< Указатель на функцию чтения Holding регистров */
    t_lip_mb_cb_read_bits   read_bits; /**< Указатель на функцию чтения дискретных входов или coils */
    t_lip_mb_cb_write_bits  write_bits; /**< Указатель на функцию записи coils */
} t_lip_mb_server_cbs;

/**************************************************************************//**
   @brief Инициализация структуры #t_lip_mb_server_cbs

   Данная функция инициализирует поля структуры #t_lip_mb_server_cbs значениями
   по умолчанию и должна вызываться перед началом заполнения нужных полей
   @param[out]  cbs   Структура, поля которой будут проинициализированы
 ******************************************************************************/
void lip_mb_server_cb_init(t_lip_mb_server_cbs *cbs);

/**************************************************************************//**
   @brief Назначение пользовательских обработчиков

   Данная функция назначает пользовательские Callback-функции серверу. После
   вызова этой функции, сервер будет использовать переданные указатели на функции
   при преме действительных запросов.
   @param[in] cbs     Структура, с заполненными указателями на пользовательские
                      обработчики запросов.
 ******************************************************************************/
void lip_mb_server_cb_set(const t_lip_mb_server_cbs *cbs);


/** @}*/
#endif // LIB_MB_SERVER_H
