/**
 * @file modbus.c
 * @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, ООО "МНПП Сатурн"
 */

/*#include "satplc.h"
#include "satcrc.h"
#include "unistd.h"
#include "fastmem.h"
*/


#include "modbus.h"

#include "STRING.h"
#include "uart_5.h"

//int handle;
/**
 * @brief Таймаут операций обмена данными по протоколу Mosbus-RTU в режиме Master, мс
 */
idata int16_t ModbusRTUTimeout = 7;


/* Коды функций Modbus */
#define MODBUS_FC_READ_COILS                0x01
#define MODBUS_FC_READ_DISCRETE_INPUTS      0x02
#define MODBUS_FC_READ_HOLDING_REGISTERS    0x03
#define MODBUS_FC_READ_INPUT_REGISTERS      0x04
#define MODBUS_FC_WRITE_SINGLE_COIL         0x05
#define MODBUS_FC_WRITE_SINGLE_REGISTER     0x06
#define MODBUS_FC_READ_EXCEPTION_STATUS     0x07
#define MODBUS_FC_WRITE_MULTIPLE_COILS      0x0F
#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS  0x10
#define MODBUS_FC_REPORT_SLAVE_ID           0x11
#define MODBUS_FC_MASK_WRITE_REGISTER       0x16
#define MODBUS_FC_WRITE_AND_READ_REGISTERS  0x17

/* Ограничения (Modbus_Application_Protocol_V1_1b.pdf):
 *  Страница 12: Quantity of Coils to read (2 bytes): 1 to 2000 (0x7D0)
 *  Страница 29: Quantity of Coils to write (2 bytes): 1 to 1968 (0x7B0)
 *  Страница 15: Quantity of Registers to read (2 bytes): 1 to 125 (0x7D)
 *  Страница 31: Quantity of Registers to write (2 bytes) 1 to 123 (0x7B)
 *  Страница 38: Quantity of Registers to write in R/W registers (2 bytes) 1 to 121 (0x79)
 */
#define MODBUS_MAX_READ_BITS                2000
#define MODBUS_MAX_WRITE_BITS               1968

#define MODBUS_MAX_READ_REGISTERS          125
#define MODBUS_MAX_WRITE_REGISTERS          123
#define MODBUS_MAX_RW_READ_REGISTERS        125
#define MODBUS_MAX_RW_WRITE_REGISTERS       121

//#define MODBUS_TCP_MBAP_LEN                 7

//#define RTU_OVER_TCP_FLAG                   0x00010000
//#define RTU_OVER_TCP_FLAG                   0x00010000

#define  CRC_START_MODBUS 0xFFFF


/**
 * @brief Запись/чтение PDU Modbus RTU
 */
static mdbs_result_t pdu_data_exchange(uint8_t adr, uint16_t wlen, uint16_t rlen, uint8_t *pdu) {
    uint8_t adu[256];
    uint16_t crc;
    s8 res;
    //
    adu[0] = adr;                       // адрес ADU
    memmove(&adu[1], pdu, wlen);        // содержимое PDU
    //
    crc = crc_modbus(adu, wlen + 1);   // CRC
    adu[wlen + 1] = crc & 0xff;
    adu[wlen + 2] = (crc >> 8) & 0xff;
    //
    purge();               //чистим буфер                   
    write( adu, wlen + 3);// передача запроса
    // приём заголовка 5 байт: адрес, код функции
    res = read_block( adu, 5, ModbusRTUTimeout);
    if(res == 0) return MDBS_DEVICE_NOT_RESPOND;
    if(res < 0) return res;
    //
    // проверка адреса
    if(adr != adu[0]) return MDBS_MISMATCH_ADDR;
    //
    // проверка кода ошибки выполнения
    if(adu[1] & 0x80) {
        // ответ с кодом ошибки, проверяем CRC
        if(crc_modbus(adu, 5)) return MDBS_INVALID_CRC;
        // возвращаем код ошибки
        return adu[2];
    }
    // ошибок нет, дочитываем остаток
    res = read_block( &adu[5], rlen - 2, ModbusRTUTimeout);
    if(res == 0) return MDBS_DEVICE_NOT_RESPOND;
    if(res < 0) return res;
    //
    // проверяем CRC
    if(crc_modbus(adu, rlen + 3)) return MDBS_INVALID_CRC;
    //
    memmove(pdu, &adu[1], rlen);
    //
    return MDBS_OK;
}

/**
 * @brief Чтение регистров
 */
static mdbs_result_t internal_read_registers( uint8_t adr, uint8_t fc, uint16_t startadr, uint8_t cnt, uint16_t *reg) {
    uint8_t pdu[256];
    mdbs_result_t rc;	
	  uint8_t i;

    if((cnt == 0)||(cnt > MODBUS_MAX_READ_REGISTERS)) return MDBS_BAD_PARAMS;
    if(!reg) return MDBS_BAD_PARAMS;

    pdu[0] = fc;
    pdu[1] = (startadr >> 8) & 0xff;
    pdu[2] = startadr & 0xff;
    pdu[3] = 0;
    pdu[4] = cnt;
    //
    rc = pdu_data_exchange(adr, 5, cnt*2 + 2, pdu);
    if(rc != MDBS_OK) return rc;
    //
    // перенос результата с изменение порядка байт: (Hi)(Lo) -> (Lo)(Hi)
    for( i=0; i < cnt; i++) {
        reg[i] = ((uint16_t)pdu[i*2 + 2] << 8) | pdu[i*2 + 3];
    }
    //
    return MDBS_OK;
}

/**
 * @brief Чтение битовых флагов
 */
static mdbs_result_t internal_read_flags( uint8_t adr, uint8_t fc, uint16_t startadr, uint8_t cnt, unsigned char *dat) {
	
    unsigned char pdu[256];	
    mdbs_result_t rc;
	
	
    if((cnt == 0)||(cnt > MODBUS_MAX_READ_BITS)) return MDBS_BAD_PARAMS;
    if(!dat) return MDBS_BAD_PARAMS;

    pdu[0] = fc;
    pdu[1] = (startadr >> 8) & 0xff;
    pdu[2] = startadr & 0xff;
    pdu[3] = (cnt >> 8) & 0xff;
    pdu[4] = cnt & 0xff;
    //
    if(cnt % 8) cnt = cnt/8 + 1; else cnt = cnt/8;          // размер битового массива ответа
    rc = pdu_data_exchange( adr, 5, cnt + 2, pdu);
    if(rc != MDBS_OK) return rc;
    //
    // перенос результата
    memmove(dat, &pdu[2], cnt);
    //
    return MDBS_OK;
}

/**
 * @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) {
    return internal_read_registers( adr, MODBUS_FC_READ_HOLDING_REGISTERS, startadr, cnt, 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) {
    return internal_read_registers( adr, MODBUS_FC_READ_INPUT_REGISTERS, startadr, cnt, 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) {
    return internal_read_flags( adr, MODBUS_FC_READ_DISCRETE_INPUTS, startadr, cnt, 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) {
    return internal_read_flags( adr, MODBUS_FC_READ_COILS, startadr, cnt, 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) {

    //
    uint8_t pdu[8];
    mdbs_result_t rc;
    //
    pdu[0] = MODBUS_FC_WRITE_SINGLE_REGISTER;
    pdu[1] = (startadr >> 8) & 0xff;
    pdu[2] = startadr & 0xff;
    pdu[3] = (value >> 8) & 0xff;
    pdu[4] = value & 0xff;
    //
    rc = pdu_data_exchange( adr, 5, 5, pdu);
    if(rc != MDBS_OK) return rc;
    //
    return MDBS_OK;
}

/**
 * @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) {
    //
    uint8_t pdu[256];
    mdbs_result_t rc;
		uint8_t i;
    //	

    if((cnt == 0)||(cnt > MODBUS_MAX_WRITE_REGISTERS)) return MDBS_BAD_PARAMS;
    if(!dat) return MDBS_BAD_PARAMS;

    pdu[0] = MODBUS_FC_WRITE_MULTIPLE_REGISTERS;
    pdu[1] = (startadr >> 8) & 0xff;        // hi(startadr)
    pdu[2] = startadr & 0xff;               // lo(startadr)
    pdu[3] = 0;                             // hi(cnt)
    pdu[4] = cnt;                           // lo(cnt)
    pdu[5] = cnt * 2;                       // количество байт
    // копирование данных с перестановкой порядка байт
    for(i=0; i < cnt; i++) {
        pdu[i*2 + 6] = (dat[i] >> 8) & 0xff;
        pdu[i*2 + 7] = dat[i] & 0xff;
    }
    //
    rc = pdu_data_exchange( adr, cnt*2 + 6, 5, pdu);
    if(rc != MDBS_OK) return rc;
    //
    return MDBS_OK;
}

/**
 * @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) {

    //
    uint8_t pdu[8];
    mdbs_result_t rc;
    //
    pdu[0] = MODBUS_FC_WRITE_SINGLE_COIL;
    pdu[1] = (startadr >> 8) & 0xff;
    pdu[2] = startadr & 0xff;
    pdu[3] = value?0xff:0;
    pdu[4] = 0;
    //
    rc = pdu_data_exchange( adr, 5, 5, pdu);
    if(rc != MDBS_OK) return rc;
    //
    return MDBS_OK;
}

/**
 * @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) {
	
    uint8_t pdu[256];
    mdbs_result_t rc;	
	

    if((cnt == 0)||(cnt > MODBUS_MAX_WRITE_BITS)) return MDBS_BAD_PARAMS;
    if(!coils) return MDBS_BAD_PARAMS;
    //

    //
    pdu[0] = MODBUS_FC_WRITE_MULTIPLE_COILS;
    pdu[1] = (startadr >> 8) & 0xff;
    pdu[2] = startadr & 0xff;
    pdu[3] = (cnt >> 8) & 0xff;
    pdu[4] = cnt & 0xff;
    if(cnt % 8) cnt = cnt/8 + 1; else cnt = cnt/8;
    pdu[5] = cnt;   // max 246
    memmove(&pdu[6], coils, cnt);
    //
    rc = pdu_data_exchange( adr, cnt + 6, 5, pdu);
    if(rc != MDBS_OK) return rc;
    //
    return MDBS_OK;
}

/**
 * @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) {
    uint8_t pdu[256];
    mdbs_result_t rc;	
		uint8_t i;

    if((rcnt == 0)||(rcnt > MODBUS_MAX_RW_READ_REGISTERS)) return MDBS_BAD_PARAMS;
    if((wcnt == 0)||(rcnt > MODBUS_MAX_RW_WRITE_REGISTERS)) return MDBS_BAD_PARAMS;
    if(!rdata) return MDBS_BAD_PARAMS;
    if(!wdata) return MDBS_BAD_PARAMS;
    //

    //
    pdu[0] = MODBUS_FC_WRITE_AND_READ_REGISTERS;
    pdu[1] = (radr >> 8) & 0xff;                    // адрес чтения
    pdu[2] = radr & 0xff;                           //
    pdu[3] = 0;                                     // количество регистров чтения
    pdu[4] = rcnt;                                  //
    pdu[5] = (wadr >> 8) & 0xff;                    // адрес записи
    pdu[6] = wadr & 0xff;                           //
    pdu[7] = 0;                                     // количество регистров записи
    pdu[8] = wcnt;                                  //
    pdu[9] = wcnt*2;                                // количество записываемых байт
    // копирование записываемых данных с перестановкой порядка байт
    for( i=0; i < wcnt; i++) {
        pdu[i*2 + 10] = (wdata[i] >> 8) & 0xff;
        pdu[i*2 + 11] = wdata[i] & 0xff;
    }
    //
    rc = pdu_data_exchange( adr, wcnt*2 + 10, rcnt*2 + 2, pdu);
    if(rc != MDBS_OK) return rc;
    //
    // перенос результата с изменение порядка байт: (Hi)(Lo) -> (Lo)(Hi)
    for(i=0; i < rcnt; i++) {
        rdata[i] = ((uint16_t)pdu[i*2 + 2] << 8) | pdu[i*2 + 3];
    }
    //
    return MDBS_OK;
}
static uint16_t code crc_tab16[256] = {
    0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
    0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
    0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
    0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
    0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
    0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
    0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
    0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
    0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
    0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
    0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
    0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
    0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
    0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
    0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
    0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
};




/**
 * @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 ) {
	
    uint16_t crc;
    const unsigned char *ptr;
    size_t a;

    crc = CRC_START_MODBUS;
    ptr = input_str;

    if ( ptr != NULL ) for (a=0; a<num_bytes; a++) {

        crc = (crc >> 8) ^ crc_tab16[ (crc ^ (uint16_t) *ptr++) & 0x00FF ];
    }

    return crc;
}  /* crc_modbus */