#include "device.h"
#include "user_misc.h"
#include "rs485.h"
#include "cali.h"
#include "modbus.h"

const uint8_t crc_tabl[] = { //CRC low Value Table
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40,
};

const uint8_t crc_tabh[] = { //CRC high Value Table
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
    0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
    0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
    0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
    0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
    0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
    0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
    0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
    0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
    0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
    0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
    0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
    0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
    0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
    0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
    0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
    0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
    0x41, 0x81, 0x80, 0x40,
};

/* polynomial: x16+x15+x2+x1  */
void crc16_8005_calc(uint8_t * p, uint16_t len, struct crc16_result_s *ret)
{
    struct crc16_result_s crc = {0xFF, 0xFF};
    uint8_t i, index;

    for (i = 0; i < len; i++) {
        index = crc.crcl ^ p[i];
        crc.crcl = crc.crch ^ crc_tabl[index];
        crc.crch = crc_tabh[index];
    }
    ret->crcl = crc.crcl;
    ret->crch = crc.crch;
}

void modbus_init(void)
{
    rs485_init();
}

uint16_t modbus_data_big2little16(uint8_t *buff)
{
    uint16_t val;

    val = (uint16_t)(buff[0]);
    val <<= 8;
    val |= (uint16_t)(buff[1]);
    return val;
}

uint32_t modbus_data_big2little32(uint8_t *buff)
{
    uint32_t val;

    val = (uint32_t)(buff[0]);
    val <<= 8;
    val |= (uint32_t)(buff[1]);
    val <<= 8;
    val |= (uint32_t)(buff[2]);
    val <<= 8;
    val |= (uint32_t)(buff[3]);
    return val;
}

/* pr: point of rx buffer
   pt: point of tx buffer
   err_type: reference for MODBUS_ERROR_CODE_XXX
   return: length of tx data need send */
uint16_t modbus_error_process(uint8_t *pr, uint8_t *pt, uint8_t err_type)
{
    struct crc16_result_s crc;

    pt[0] = pr[0];
    pt[1] = pr[1] + 0x80;
    pt[2] = err_type;
    crc16_8005_calc(pt, 3, &crc);
    pt[3] = crc.crcl;
    pt[4] = crc.crch;
    return 5;
}

uint16_t modbus_heat_onoff(uint16_t data, uint8_t *pr, uint8_t *pt)
{
    uint16_t i;

    if (data == 0x0000) {
        /* heat off state */
        device_status.cali_activate_status = 0;
        heat_off();
    } else if (data == 0x00FF) {
        /* heat on state */
        device_status.cali_activate_status = 1;
        heat_on();
    } else {
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_VALUE);
    }
    for (i = 0; i < 8; i++) {
        pt[i] = pr[i];
    }
    return i;
}

/* pr: point of rx buffer
   pt: point of tx buffer
   len: length of rx data in buffer
   return: length of tx data need send */
uint16_t modbus_func_read_input(uint8_t *pr, uint8_t *pt, uint16_t len)
{
    uint16_t cnt, addr_s, addr_p, i;
    struct crc16_result_s crc;

    /* modbus register count */
    cnt = modbus_data_big2little16(pr + 4);
    if (cnt < 1 || cnt > 0x007D) {
        /* modbus register count error */
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_VALUE);
    }
    addr_s = modbus_data_big2little16(pr + 2);
    addr_p = addr_s + cnt;
    if (addr_s < MODBUS_ADDR_RO_S || addr_s >= MODBUS_ADDR_RO_P || \
        addr_p < MODBUS_ADDR_RO_S || addr_p >= MODBUS_ADDR_RO_P) {
        /* modbus register address error */
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_ADDR);
    }
    pt[0] = pr[0];
    pt[1] = pr[1];
    pt[2] = (uint8_t)(cnt * 2);
    for (i = 0; i < cnt * 2; i++) {
        pt[3 + i] = *(modbus_map_ro[addr_s * 2 - MODBUS_ADDR_RO_S + i]);
    }
    i += 3;
    crc16_8005_calc(pt, i, &crc);
    pt[i++] = crc.crcl;
    pt[i++] = crc.crch;

    return i;
}

uint16_t modbus_process_zero_record(uint8_t ch, uint16_t data, uint8_t *pr, uint8_t *pt)
{
    uint16_t i;
    uint8_t ret = 0;

    if (data & 1) { /* zero record not available */
        device_status.cali_zero_record_status[ch] = 0xFFFF;
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_VALUE);
    } else { /* zero record available */
        ret = cali_zero_record(ch);
        device_status.cali_zero_record_status[ch] = ret;
        if (ret) {
            return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_EXECUTE);
        } else {
            for (i = 0; i < 8; i++) {
                pt[i] = pr[i];
            }
            return i;
        }
    }
}

uint16_t modbus_process_span_record(uint8_t ch, uint32_t data, uint8_t *pr, uint8_t *pt)
{
    uint16_t i;
    uint8_t ret = 0;
    struct crc16_result_s crc;

    ret = cali_span_record(ch, data);
    device_status.cali_span_record_status[ch] = ret;
    if (ret) {
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_EXECUTE);
    } else {
        for (i = 0; i < 6; i++) {
            pt[i] = pr[i];
        }
        crc16_8005_calc(pt, i, &crc);
        pt[i++] = crc.crcl;
        pt[i++] = crc.crch;
        return i;
    }
}

uint16_t modbus_process_cali_active(uint8_t ch, uint16_t data, uint8_t *pr, uint8_t *pt)
{
    uint16_t i;
    uint8_t ret = 0;

    if ((data & 2) == 0) { /* activate span1 */
        ret = cali_span_activate(ch);
    } else if ((data & 1) == 0) { /* activate zero */
        ret = cali_zero_activate(ch);
    } else { /* activate not available */
        device_status.cali_activate_status |= (1 << ch);
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_VALUE);
    }
    /* check activate operation */
    if (ret) {
        device_status.cali_activate_status |= (1 << ch);
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_EXECUTE);
    } else {
        device_status.cali_activate_status &= ~(1 << ch);
        for (i = 0; i < 8; i++) {
            pt[i] = pr[i];
        }
        return i;
    }
}

uint16_t modbus_process_cali_restore(uint8_t ch, uint16_t data, uint8_t *pr, uint8_t *pt)
{
    uint16_t i;

    if (data != 0x00FF) {
        device_status.cali_restore_status |= (1 << ch);
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_VALUE);
    }
    cali_restore(ch);
    device_status.cali_restore_status &= ~(1 << ch);
    for (i = 0; i < 8; i++) {
        pt[i] = pr[i];
    }
    return i;
}

/* pr: point of rx buffer
   pt: point of tx buffer
   len: length of rx data in buffer
   return: length of tx data need send */
uint16_t modbus_func_write_single(uint8_t *pr, uint8_t *pt, uint16_t len)
{
    uint16_t addr, value;

    /* modbus register address */
    addr = modbus_data_big2little16(pr + 2);
    value = modbus_data_big2little16(pr + 4);

    if (len != 8) { /* write single register frame must 8 bytes */
        return 0;
    }
    if (addr == 0x1001) { /* head on/off command */
        return modbus_heat_onoff(value, pr, pt);
    } else if (addr == 0x1011) { /* gas2 zero calibration record command */
        return modbus_process_zero_record(1, value, pr, pt);
    } else if (addr == 0x1012) { /* gas3 zero calibration record command */
        return modbus_process_zero_record(2, value, pr, pt);
    } else if (addr == 0x1013) { /* gas4 zero calibration record command */
        return modbus_process_zero_record(3, value, pr, pt);
    } else if (addr == 0x103D) { /* gas2 calibration activate command */
        return modbus_process_cali_active(1, value, pr, pt);
    } else if (addr == 0x103E) { /* gas3 calibration activate command */
        return modbus_process_cali_active(2, value, pr, pt);
    } else if (addr == 0x103F) { /* gas4 calibration activate command */
        return modbus_process_cali_active(3, value, pr, pt);
    } else if (addr == 0x1041) { /* gas1 restore factory calibration command */
        return modbus_process_cali_restore(1, value, pr, pt);
    } else if (addr == 0x1042) { /* gas2 restore factory calibration command */
        return modbus_process_cali_restore(2, value, pr, pt);
    } else if (addr == 0x1043) { /* gas3 restore factory calibration command */
        return modbus_process_cali_restore(3, value, pr, pt);
    } else {
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_ADDR);
    }
}

/* pr: point of rx buffer
   pt: point of tx buffer
   len: length of rx data in buffer
   return: length of tx data need send */
uint16_t modbus_func_write_multi(uint8_t *pr, uint8_t *pt, uint16_t len)
{
    uint16_t addr, count;
    uint32_t value;

    /* modbus register address */
    addr = modbus_data_big2little16(pr + 2);
    count = modbus_data_big2little16(pr + 4);
    value = modbus_data_big2little32(pr + 7);

    if (len != 13) { /* write multi register frame must 13 bytes */
        return 0;
    }
    if (count != 2) { /* register count must 2 */
        return 0;
    }
    if (addr == 0x101E) { /* gas2 span1 calibration concentration */
        return modbus_process_span_record(1, value, pr, pt);
    } else if (addr == 0x1028) { /* gas3 span1 calibration concentration */
        return modbus_process_span_record(2, value, pr, pt);
    } else if (addr == 0x1032) { /* gas4 span1 calibration concentration */
        return modbus_process_span_record(3, value, pr, pt);
    } else {
        return modbus_error_process(pr, pt, MODBUS_ERROR_CODE_ADDR);
    }
}

void modbus_loop(void)
{
    static uint32_t frame_cnt = 0;
    struct crc16_result_s crc;

    /* no new complete frame */
    if (rs485_data.frame_cnt == frame_cnt) {
        return;
    }
    /* one new frame occur */
    frame_cnt = rs485_data.frame_cnt;
    /* step 1: calc CRC */
    crc16_8005_calc(rs485_data.buff_rx, rs485_data.rx_len - 2, &crc);
    if (crc.crcl != rs485_data.buff_rx[rs485_data.rx_len - 2] || \
        crc.crch != rs485_data.buff_rx[rs485_data.rx_len - 1]) {
        /* CRC calc error */
        rs485_data.rx_len = 0;
        rs485_mode_rx();
        rs485_rx_start();
        return;
    }

    /* step 2: process data according function code */
    if (rs485_data.buff_rx[1] == MODBUS_FUNCTION_CODE_READ_INPUT) {
        rs485_data.tx_len = modbus_func_read_input(rs485_data.buff_rx, rs485_data.buff_tx, rs485_data.rx_len);
    } else if (rs485_data.buff_rx[1] == MODBUS_FUNCTION_CODE_WRITE_SINGLE) {
        rs485_data.tx_len = modbus_func_write_single(rs485_data.buff_rx, rs485_data.buff_tx, rs485_data.rx_len);
    } else if (rs485_data.buff_rx[1] == MODBUS_FUNCTION_CODE_WRITE_MULTI) {
        rs485_data.tx_len = modbus_func_write_multi(rs485_data.buff_rx, rs485_data.buff_tx, rs485_data.rx_len);
    } else {
        rs485_data.tx_len = modbus_error_process(rs485_data.buff_rx, rs485_data.buff_tx, MODBUS_ERROR_CODE_FUNCTION);
    }
    if (rs485_data.tx_len == 0) {
        rs485_data.rx_len = 0;
        rs485_mode_rx();
        rs485_rx_start();
    } else {
        rs485_send_data(rs485_data.tx_len);
    }
}
