/**
 * @file modbus.h
 * @brief Стандартная библиотека контроллера "Сатурн-PLC".
 * Функции поддержки работы с протоколом обмена Modbus.
 * Реализация соответствует спецификациям:
 * "MODBUS Application Protocol Specification V1.1b3"
 * "MODBUS Messaging on TCP/IP Implementation Guide V1.0b"
 * @version 0.1
 * @date 2024-04-01
 * 
 * @copyright Copyright (C) 2024, ООО "МНПП Сатурн"
 */

#ifndef MODBUS_H
#define MODBUS_H

//#include "satplc.h"
#include "sys.h"

/**
 * @brief Таймаут операций обмена данными по протоколу Mosbus-RTU в режиме Client, мс
 */
extern idata int16_t ModbusRTUTimeout;


/**
 * @brief Код завершения функции Modbus
 */
typedef enum _mdbs_result  {
    MDBS_OK                               = 0,        // операция выполнена успешно
    //
    MDBS_BAD_PARAMS                       = -1,       // некорректные значения параметров при вызове функции
    MDBS_DISCONNECTED                     = -2,       // соединение не установлено или было потеряно
    MDBS_DEVICE_NOT_RESPOND               = -3,       // устройство Modbus не отвечает
    MDBS_MISMATCH_ADDR                    = -4,       // адрес в ответе не совпадает с адресом в запросе
    MDBS_MISMATCH_TRANSACTION_ID          = -5,       // номер транзакции в ответе не совпадает с номером в запросе
    MDBS_INVALID_CRC                      = -6,       // неправильное значение контрольной суммы в ответе
    //
    MDBS_ILLEGAL_FUNCTION                 = 1,        // Неправильный номер функции для заданного slave-устройства
    MDBS_ILLEGAL_DATA_ADDRESS             = 2,        // Неправильный адрес для заданного slave-устройства
    MDBS_ILLEGAL_DATA_VALUE               = 3,        // Неправильные данные для заданного slave-устройства
    MDBS_SERVER_DEVICE_FAILURE            = 4,        // Произошла ошибка в slave-устройстве при выполнении функции
    MDBS_ACKNOWLEDGE                      = 5,        // Для выполнения команды slave-устройству необходимо дополнительное время
    MDBS_SERVER_DEVICE_BUSY               = 6,        // Slave-устройство занято и не может выполнить команду
    MDBS_NEGATIVE_ACKNOWLEDGE             = 7,        // Отказ выполнения функции (NAK)
    MDBS_MEMORY_PARITY_ERROR              = 8,        // Ошибка чётности памяти slave
    MDBS_GATEWAY_PATH_UNAVAILABLE         = 10,       // Ошибка шлюза (Gateway paths not available)
    MDBS_GATEWAY_TARGET_DEVIC_NOT_RESPOND = 11        // Ошибка шлюза: устройство не отвечает
} mdbs_result_t;

/**
 * @brief Вариант протокола Modbus: RTU или TCP
 */
typedef enum {
    MDBS_PROTO_TCP                        = 0,
    MDBS_PROTO_RTU_OVER_TCP               = 1
} mdbs_proto_t;

/* Функции MODBUS Server */

/**
 * @brief Начало работы сервера MODBUS RTU. Для работы сервера используется указанный последовательный порт.
 * Последовательный порт должен быть предварительно открыт функцией SerialOpen().
 * В качестве параметров передаются адреса и количество регистров хранения и входных регистров используемых для
 * работы сервера.
 * При успешном выполнении функция возвращает значение 1, при ошибке - значение 0.
 * @param handle Дескриптор последовательного порта.
 * @param hld Указатель на область регистров хранения (Holding Registers).
 * @param hldcnt Количество регистров хранения
 * @param inp Указатель на область входных регистров (Input Registers)
 * @param inpcnt Количество входных регистров
 * @return int Результат выполнения: 1 - успех, 0 - ошибка
 */
int StartModbusRTUServer( uint16_t *hld, uint16_t hldcnt, uint16_t *inp, uint16_t inpcnt);

/**
 * @brief Функция возвращает количество корректных запросов к серверу MODBUS RTU работающему на указанном последовательном порту.
 * @param handle Дескриптор последовательного порта.
 * @return unsigned int 
 */
unsigned int ModbusRTUCount(void);

/**
 * @brief Завершение работы сервера MODBUS RTU использующего указанный последовательный порт.
 * @param handle Дескриптор последовательного порта.
 */
void StopModbusRTUServer(void);




/* Функции MODBUS Client */


/**
 * @brief Чтение значений регистров хранения (Holding Registers).
 * Функция выполняет чтение значений регистров "Holding Registers" начиная с адреса regadr в количестве cnt.
 * При успешном выполнении прочитанные данные помещаются в массив по указателю reg. Размер массива
 * должен быть не менее cnt*2 байт. Порядок байт в прочитанных регистрах: "Little Endian".
 * При успешном выполнении функция возвращает значение MDBS_OK, иначе - код ошибки.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Адрес первого читаемого регистра (0..65535).
 * @param cnt Количество читаемых регистров (1..125).
 * @param reg Указатель на массив данных.
 * @return mdbs_result_t Код завершения.
 */
mdbs_result_t ModbusReadHoldingRegisters( uint8_t adr, uint16_t startadr, uint8_t cnt, uint16_t *reg);

/**
 * @brief Чтение значений входных регистров (Input Registers).
 * Функция выполняет чтение значений регистров "Input Registers" начиная с адреса regadr в количестве cnt.
 * При успешном выполнении прочитанные данные помещаются в массив по указателю reg. Размер массива
 * должен быть не менее cnt*2 байт. Порядок байт в прочитанных регистрах: "Little Endian".
 * При успешном выполнении функция возвращает значение MDBS_OK, иначе - код ошибки.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Адрес первого читаемого регистра (0..65535).
 * @param cnt Количество читаемых регистров (1..125).
 * @param reg Указатель на массив данных.
 * @return mdbs_result_t Код завершения.
 */
mdbs_result_t ModbusReadInputRegisters( uint8_t adr, uint16_t startadr, uint8_t cnt, uint16_t *reg);

/**
 * @brief Чтение значений дискретных входов (Discrete Inputs).
 * Функция выполняет чтение состояния до 2000 последовательно расположенных дискретных входов.
 * При успешном выполнении функция возвращает значение MDBS_OK, прочитанные данные помещаются в массив по указателю dat.
 * Размер массива dat должен быть не менее чем (cnt/8+1) байт.
 * В случае ошибки функция возврвращает её код.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Адрес первого читаемого дискретного входа (0..65535).
 * @param cnt Количество читаемых входов (1..2000)
 * @param dat Указатель на массив данных.
 * @return mdbs_result_t 
 */
mdbs_result_t ModbusReadDiscreteInputs( uint8_t adr, uint16_t startadr, uint8_t cnt, unsigned char *dat);

/**
 * @brief Чтение значений дискретных выходов (Coils).
 * Функция выполняет чтение состояния до 2000 последовательно расположенных дискретных выходов (coils).
 * При успешном выполнении функция возвращает значение MDBS_OK, прочитанные данные помещаются в массив по указателю dat.
 * Размер массива dat должен быть не менее чем (cnt/8+1) байт.
 * В случае ошибки функция возврвращает её код.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Адрес первого читаемого дискретного выхода (0..65535).
 * @param cnt Количество читаемых выходов (1..2000).
 * @param dat Указатель на массив данных.
 * @return mdbs_result_t 
 */
mdbs_result_t ModbusReadCoils( uint8_t adr, uint16_t startadr, uint8_t cnt, unsigned char *dat);

/**
 * @brief Запись значения одного регистра хранения (Holding Register).
 * Функция выполняет запись значения value в регистр с адресом startadr.
 * Для записи используется функция 0x06.
 * Перед записью функция изменяет порядок байтов значения value.
 * При успешном выполнении функция возвращает значение MDBS_OK, иначе - код ошибки.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Адрес регистра записываемого регистра (0..65535).
 * @param value Записываемое значение.
 * @return mdbs_result_t 
 */
mdbs_result_t ModbusWriteSingleRegister( uint8_t adr, uint16_t startadr, uint16_t value);

/**
 * @brief Запись значений нескольких регистров хранения (Holding Registers).
 * Функция выполняет запись значений из массива dat в регистры хранения, начиная с адреса startadr.
 * Для записи используется функция 0x10.
 * При записи функция изменяет порядок байтов записываемых значений.
 * При успешном выполнении функция возвращает значение MDBS_OK, иначе - код ошибки.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Начальный адрес регистра (0..65535).
 * @param cnt Количество записываемых регистров (1..123).
 * @param dat Указатель на массив записываемых данных.
 * @return mdbs_result_t 
 */
mdbs_result_t ModbusWriteRegisters( uint8_t adr, uint16_t startadr, uint8_t cnt, uint16_t *dat);

/**
 * @brief Запись значения одного дискретного выхода (coil).
 * Для записи используется функция 0x05.
 * При успешном выполнении функция возвращает значение MDBS_OK, иначе - код ошибки.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Начальный адрес дискретного выхода (0..65535).
 * @param value Значение: 0 - выключить, !0 - включить.
 * @return mdbs_result_t 
 */
mdbs_result_t ModbusWriteSingleCoil( uint8_t adr, uint16_t startadr, uint16_t value);

/**
 * @brief Запись значений дискретных выходов (coils).
 * Каждый дискретный выход может находиться в состоянии "1" (включен) или "0" (выключен).
 * Записываемые состояния определяются содержимым массива coils. Логическая "1" в битовой
 * позиции массива coils требует выключение соответствующего дискретного выхода.
 * Для записи используется функция 0x0f.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param startadr Начальный адрес дискретных выходов (0..65535).
 * @param cnt Количество записываемых дискретных выходов (1..1968)
 * @param coils Указатель на массив значений дискретных выходов
 * @return mdbs_result_t 
 */
mdbs_result_t ModbusWriteCoils( uint8_t adr, uint16_t startadr, uint8_t cnt, uint8_t *coils);

/**
 * @brief Запись и последующее чтение значений нескольких регистров хранения (Holding Registers).
 * Функция выполняет запись wcnt значений из массива wdata в регистры хранения, начиная с адреса wadr.
 * При записи функция изменяет порядок байтов записываемых значений.
 * Функция выполняет чтение значений регистров "Holding Registers" начиная с адреса radr в количестве rcnt.
 * При успешном выполнении прочитанные данные помещаются в массив по указателю rdata. Размер массива
 * должен быть не менее cnt*2 байт. Порядок байт в прочитанных регистрах: "Little Endian".
 * Для чтения и записи используется функция 0x17.
 * При успешном выполнении функция возвращает значение MDBS_OK, иначе - код ошибки.
 * @param handle Дескриптор последовательного порта или соединения TCP.
 * @param adr Адрес устройства Modbus.
 * @param radr Начальный адрес регистра чтения (0..65535).
 * @param rcnt Количество читаемых регистров (1..125).
 * @param rdata Указатель на массив прочитанных данных.
 * @param wadr Начальный адрес регистра запис (0..65535).
 * @param wcnt Количество записываемых регистров (1..125).
 * @param wdata Указатель на массив записываемых данных.
 * @return mdbs_result_t 
 */
mdbs_result_t ModbusReadWriteRegisters( uint8_t adr, uint16_t radr, uint16_t rcnt, uint16_t *rdata, uint16_t wadr, uint16_t wcnt, uint16_t *wdata);

/* Функции преобразования формата данных */

/**
 * @brief Преобразование значения 2-х последовательных регистров Modbus (4 байта) в значение float
 * без преобразования (ABCD)
 * @param src Указатель на первый регистр
 * @return float 
 */
float ModbusGetFloatABCD(const uint16_t *src);

/**
 * @brief Преобразование значения 2-х последовательных регистров Modbus (4 байта) в значение float
 * в обратном порядке (DCBA)
 * @param src Указатель на первый регистр
 * @return float 
 */
float ModbusGetFloatDCBA(const uint16_t *src);

/**
 * @brief Преобразование значения 2-х последовательных регистров Modbus (4 байта) в значение float
 * с изменённым порядком байт в регистрах (BADC): Используется в "Сатурн-PLC"
 * @param src 
 * @return float 
 */
float ModbusGetFloatBADC(const uint16_t *src);

/**
 * @brief Преобразование значения 2-х последовательных регистров Modbus (4 байта) в значение float
 * с изменённым порядком регистров (CDAB)
 * @param src 
 * @return float 
 */
float ModbusGetFloatCDAB(const uint16_t *src);

/**
 * @brief Преобразование значения float в два последовательных регистра Modbus (4 байта)
 * без изменения порядка (ABCD)
 * @param f 
 * @param dest 
 */
void ModbusSetFloatABCD(float f, uint16_t *dest);

/**
 * @brief Преобразование значения float в два последовательных регистра Modbus (4 байта)
 * в обратном порядке (DCBA)
 * @param f 
 * @param dest 
 */
void ModbusSetFloatDCBA(float f, uint16_t *dest);

/**
 * @brief Преобразование значения float в два последовательных регистра Modbus (4 байта)
 * с изменённым порядком байт в регистрах (BADC): Используется в "Сатурн-PLC"
 * @param f 
 * @param dest 
 */
void ModbusSetFloatBADC(float f, uint16_t *dest);

/**
 * @brief Преобразование значения float в два последовательных регистра Modbus (4 байта)
 * с изменённым порядком регистров (CDAB)
 * @param f 
 * @param dest 
 */
void ModbusSetFloatCDAB(float f, uint16_t *dest);

/**
 * @brief Расчёт значения контрольной суммы crc16 по алгоритму, используемому в протоколе "Modbus RTU"
 * 
 * @param input_str Указатель на массив данных
 * @param num_bytes Длина массива
 * @return uint16_t 
 */
uint16_t crc_modbus( const unsigned char *input_str, unsigned int num_bytes );




#endif
