/*
 * Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "modbus-private.h"
#include "myLog.h"
#include <assert.h>

#include "modbus-rtu-private.h"
#include "modbus-rtu.h"

#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS
#include <sys/ioctl.h>
#endif

#if HAVE_DECL_TIOCSRS485
#include <linux/serial.h>
#endif

/* CRC高位字节查找表 */
static const uint8_t table_crc_hi[] = {
    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};

/* CRC低位字节查找表 */
static const uint8_t table_crc_lo[] = {
    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};

/* 定义远程设备的从站ID：在主站模式下用于指定要与之通信的远程设备，
 * 在从站模式下用于设置内部从站ID */
static int _modbus_set_slave(modbus_t *ctx, int slave)
{
    int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;

    /* 广播地址为 0（MODBUS_BROADCAST_ADDRESS） */
    if (slave >= 0 && slave <= max_slave) {
        ctx->slave = slave;
    } else {
        errno = EINVAL;
        return -1;
    }

    return 0;
}

/* 构建 RTU 请求报文头 */
static int _modbus_rtu_build_request_basis(
    modbus_t *ctx, int function, int addr, int nb, uint8_t *req)
{
    assert(ctx->slave != -1);
    req[0] = ctx->slave;
    req[1] = function;
    req[2] = addr >> 8;
    req[3] = addr & 0x00ff;
    req[4] = nb >> 8;
    req[5] = nb & 0x00ff;

    return _MODBUS_RTU_PRESET_REQ_LENGTH;
}

/* 构建 RTU 响应报文头 */
static int _modbus_rtu_build_response_basis(sft_t *sft, uint8_t *rsp)
{
    /* 在这种情况下，从站地址肯定是有效的，
     * 因为 _modbus_rtu_listen 中已经做了检查 */
    rsp[0] = sft->slave;
    rsp[1] = sft->function;

    return _MODBUS_RTU_PRESET_RSP_LENGTH;
}

static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length)
{
    uint8_t crc_hi = 0xFF; /* 初始化 CRC 高位字节 */
    uint8_t crc_lo = 0xFF; /* 初始化 CRC 低位字节 */
    unsigned int i;        /* 用于索引 CRC 查找表 */

    /* 遍历消息缓冲区 */
    while (buffer_length--) {
        i = crc_lo ^ *buffer++; /* 计算 CRC */
        crc_lo = crc_hi ^ table_crc_hi[i];
        crc_hi = table_crc_lo[i];
    }

    return (crc_hi << 8 | crc_lo);
}

static int _modbus_rtu_get_response_tid(const uint8_t *req)
{
    /* RTU 协议中没有事务ID（TID） */
    return 0;
}

static int _modbus_rtu_send_msg_pre(uint8_t *req, int req_length)
{
    uint16_t crc = crc16(req, req_length);

    /* 根据 MODBUS 规范（第14页），CRC 的低位字节在 RTU 消息中先发送 */
    req[req_length++] = crc & 0x00FF;
    req[req_length++] = crc >> 8;

    return req_length;
}

#if HAVE_DECL_TIOCM_RTS
static void _modbus_rtu_ioctl_rts(modbus_t *ctx, int on)
{
    int fd = ctx->s;
    int flags;

    ioctl(fd, TIOCMGET, &flags);
    if (on) {
        flags |= TIOCM_RTS;
    } else {
        flags &= ~TIOCM_RTS;
    }
    ioctl(fd, TIOCMSET, &flags);
}
#endif

static ssize_t _modbus_rtu_send(modbus_t *ctx, const uint8_t *req, int req_length)
{
#if HAVE_DECL_TIOCM_RTS
    modbus_rtu_t *ctx_rtu = ctx->backend_data;
    if (ctx_rtu->rts != MODBUS_RTU_RTS_NONE) {
        ssize_t size;

        if (ctx->debug) {
            LOG_I("Sending request using RTS signal");
        }

        ctx_rtu->set_rts(ctx, ctx_rtu->rts == MODBUS_RTU_RTS_UP);
        usleep(ctx_rtu->rts_delay);

        size = write(ctx->s, req, req_length);

        usleep(ctx_rtu->onebyte_time * req_length + ctx_rtu->rts_delay);
        ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP);

        return size;
    } else {
#endif
        return write(ctx->s, req, req_length);
#if HAVE_DECL_TIOCM_RTS
    }
#endif
}

static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
{
    int rc;
    modbus_rtu_t *ctx_rtu = ctx->backend_data;

    if (ctx_rtu->confirmation_to_ignore) {
        (void) _modbus_receive_msg(ctx, req, MSG_CONFIRMATION);
        /* 忽略错误并重置标志 */
        ctx_rtu->confirmation_to_ignore = FALSE;
        rc = 0;
        if (ctx->debug) {
            LOG_I("Confirmation to ignore\n");
        }
    } else {
        rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
        if (rc == 0) {
            /* 下一条期望的消息是需要忽略的确认消息 */
            ctx_rtu->confirmation_to_ignore = TRUE;
        }
    }
    return rc;
}

static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length)
{
    return read(ctx->s, rsp, rsp_length);
}

static int _modbus_rtu_flush(modbus_t *);

static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx,
                                              const uint8_t *req,
                                              const uint8_t *rsp,
                                              int rsp_length)
{
    /* 检查响应的从站是否是我们请求的从站（广播请求除外） */
    if (req[0] != rsp[0] && req[0] != MODBUS_BROADCAST_ADDRESS) {
        if (ctx->debug) {
            LOG_E(
                    "The responding slave %d isn't the requested slave %d",
                    rsp[0],
                    req[0]);
        }
        errno = EMBBADSLAVE;
        return -1;
    } else {
        return 0;
    }
}

/* check_crc16 函数在消息被忽略时应返回 0，
   如果 CRC 有效则返回消息长度。否则返回 -1 并设置 errno 为 EMBBADCRC。 */
static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length)
{
    uint16_t crc_calculated;
    uint16_t crc_received;
    int slave = msg[0];

    crc_calculated = crc16(msg, msg_length - 2);
    crc_received = (msg[msg_length - 1] << 8) | msg[msg_length - 2];

    /* 检查消息的 CRC */
    if (crc_calculated != crc_received) {
        if (ctx->debug) {
            LOG_E(
                    "ERROR CRC received 0x%0X != CRC calculated 0x%0X",
                    crc_received,
                    crc_calculated);
        }

        if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
            _modbus_rtu_flush(ctx);
        }
        errno = EMBBADCRC;
        return -1;
    }

    /* 在 RTU 模式下按 Modbus 单元标识符（从站地址）进行过滤 */
    if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) {
        if (ctx->debug) {
            LOG_I("Request for slave %d ignored (not %d)\n", slave, ctx->slave);
        }
        /* 后续对 check_confirmation 的调用会处理这个错误 */
        return 0;
    }

    return msg_length;
}

/* 配置串口以进行 RTU 通信 */
/* POSIX 系统 */
static int _modbus_rtu_connect(modbus_t *ctx)
{
    struct termios2 tios;
    int flags;
    modbus_rtu_t *ctx_rtu = ctx->backend_data;

    if (ctx->debug) {
        LOG_I("Opening %s at %d bauds (%c, %d, %d)\n",
               ctx_rtu->device,
               ctx_rtu->baud,
               ctx_rtu->parity,
               ctx_rtu->data_bit,
               ctx_rtu->stop_bit);
    }

    /* O_NOCTTY 标志告诉 UNIX 此程序不希望成为该端口的"控制终端"。
       如果不指定此标志，任何输入（如键盘中止信号等）都会影响你的进程。

       在规范输入模式或通过 open 或 fcntl 设置了 NONBLOCK 选项时，
       超时会被忽略 */
    flags = O_RDWR | O_NOCTTY | O_NONBLOCK;
    flags |= O_CLOEXEC;

    ctx->s = open(ctx_rtu->device, flags);
    if (ctx->s < 0) {
        if (ctx->debug) {
            LOG_E(
                    "ERROR Can't open the device %s (%s)",
                    ctx_rtu->device,
                    strerror(errno));
        }
        return -1;
    }

    /* 保存原始设置 */
    ioctl(ctx->s, TCGETS2, &ctx_rtu->old_tios);

    memset(&tios, 0, sizeof(tios));

    /* C_ISPEED     输入波特率（新接口）
       C_OSPEED     输出波特率（新接口）
    */

    /* 设置波特率 */

	tios.c_cflag |= BOTHER;  /* 允许自定义波特率 */
	tios.c_ispeed = ctx_rtu->baud;  /* 设置输入波特率 */
	tios.c_ospeed = ctx_rtu->baud;  /* 设置输出波特率 */

    /* C_CFLAG      控制选项
       CLOCAL       本地线路 - 不改变端口的"所有者"
       CREAD        启用接收器
    */
    tios.c_cflag |= (CREAD | CLOCAL);
    /* CSIZE, HUPCL, CRTSCTS（硬件流控制） */

    /* 设置数据位（5、6、7、8位）
       CSIZE        数据位的位掩码
    */
    tios.c_cflag &= ~CSIZE;
    switch (ctx_rtu->data_bit) {
    case 5:
        tios.c_cflag |= CS5;
        break;
    case 6:
        tios.c_cflag |= CS6;
        break;
    case 7:
        tios.c_cflag |= CS7;
        break;
    case 8:
    default:
        tios.c_cflag |= CS8;
        break;
    }

    /* 停止位（1 或 2） */
    if (ctx_rtu->stop_bit == 1)
        tios.c_cflag &= ~CSTOPB;
    else /* 2 */
        tios.c_cflag |= CSTOPB;

    /* PARENB       启用奇偶校验位
       PARODD       使用奇校验而非偶校验 */
    if (ctx_rtu->parity == 'N') {
        /* 无校验 */
        tios.c_cflag &= ~PARENB;
    } else if (ctx_rtu->parity == 'E') {
        /* 偶校验 */
        tios.c_cflag |= PARENB;
        tios.c_cflag &= ~PARODD;
    } else {
        /* 奇校验 */
        tios.c_cflag |= PARENB;
        tios.c_cflag |= PARODD;
    }

    /* 如需更多信息，请阅读 termios 的手册页。 */

    /* 此字段在 POSIX 系统上不使用
       tios.c_line = 0;
    */

    /* C_LFLAG      行选项

       ISIG 启用 SIGINTR、SIGSUSP、SIGDSUSP 和 SIGQUIT 信号
       ICANON       启用规范输入（否则为原始输入）
       XCASE        映射大写\小写（已过时）
       ECHO 启用输入字符的回显
       ECHOE        将擦除字符回显为 BS-SP-BS
       ECHOK        在终止字符后回显 NL
       ECHONL       回显 NL
       NOFLSH       禁用中断或退出字符后的输入缓冲区刷新
       IEXTEN       启用扩展功能
       ECHOCTL      将控制字符回显为 ^char，删除为 ~?
       ECHOPRT      将擦除字符回显为被擦除的字符
       ECHOKE       行终止时 BS-SP-BS 整行
       FLUSHO       输出正在刷新
       PENDIN       在下一次读取或输入字符时重新输入挂起的输入
       TOSTOP       为后台输出发送 SIGTTOU

       规范输入是面向行的。输入字符被放入缓冲区，
       用户可以交互式编辑，直到收到 CR（回车）或 LF（换行）字符。

       原始输入是未经处理的。输入字符按接收到的原样传递。
       使用原始输入时，通常需要取消 ICANON、ECHO、ECHOE 和 ISIG 选项
    */

    /* 原始输入 */
    tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    /* C_IFLAG      输入选项

       常量     描述
       INPCK        启用奇偶校验检查
       IGNPAR       忽略奇偶校验错误
       PARMRK       标记奇偶校验错误
       ISTRIP       剥离奇偶校验位
       IXON 启用软件流控制（输出方向）
       IXOFF        启用软件流控制（输入方向）
       IXANY        允许任意字符重新启动流
       IGNBRK       忽略中断条件
       BRKINT       检测到中断条件时发送 SIGINT
       INLCR        将 NL 映射为 CR
       IGNCR        忽略 CR
       ICRNL        将 CR 映射为 NL
       IUCLC        将大写映射为小写
       IMAXBEL      输入行过长时回显 BEL
    */
    if (ctx_rtu->parity == 'N') {
        /* 无校验 */
        tios.c_iflag &= ~INPCK;
    } else {
        tios.c_iflag |= INPCK;
    }

    /* 禁用软件流控制 */
    tios.c_iflag &= ~(IXON | IXOFF | IXANY);

    /* C_OFLAG      输出选项
       OPOST        后处理输出（未设置 = 原始输出）
       ONLCR        将 NL 映射为 CR-NL

       ONCLR 和其他选项需要启用 OPOST
    */

    /* 原始输出 */
    tios.c_oflag &= ~OPOST;

    /* C_CC         控制字符
       VMIN         读取的最小字符数
       VTIME        等待数据的时间（十分之一秒）

       UNIX 串行接口驱动程序提供指定字符和包超时的能力。
       c_cc 数组中的两个元素用于超时：VMIN 和 VTIME。
       在规范输入模式或通过 open 或 fcntl 设置了 NONBLOCK 选项时，超时会被忽略。

       VMIN 指定要读取的最小字符数。如果设置为 0，
       则 VTIME 值指定等待每个字符读取的时间。
       注意，这并不意味着对 N 字节的读取调用会等待 N 个字符到来。
       相反，超时仅应用于第一个字符，读取调用将返回
       立即可用的字符数（最多达到你请求的数量）。

       如果 VMIN 非零，VTIME 指定等待第一个字符读取的时间。
       如果在给定时间内读到了一个字符，任何读取操作将阻塞（等待）
       直到所有 VMIN 个字符都被读取。也就是说，一旦第一个字符被读取，
       串行接口驱动程序期望接收一个完整的字符包（总共 VMIN 字节）。
       如果在允许的时间内没有读到字符，则读取调用返回 0。
       这种方法允许你告诉串行驱动程序你需要正好 N 个字节，
       任何读取调用将返回 0 或 N 个字节。然而，超时仅应用于
       第一个字符的读取，因此如果由于某种原因驱动程序在 N 字节包中
       漏掉了一个字符，读取调用可能会永远阻塞等待额外的输入字符。

       VTIME 指定等待输入字符的时间，以十分之一秒为单位。
       如果 VTIME 设置为 0（默认值），读取将无限期阻塞（等待），
       除非在端口上通过 open 或 fcntl 设置了 NONBLOCK 选项。
    */
    /* 因为我们使用 open 并设置了 NONBLOCK 选项，所以这里不使用 */
    tios.c_cc[VMIN] = 0;
    tios.c_cc[VTIME] = 0;

    if (ioctl(ctx->s, TCSETS2, &tios) < 0) {
        close(ctx->s);
        ctx->s = -1;
        return -1;
    }
    if (ctx->debug) {
        ioctl(ctx->s, TCGETS2, &tios);
        if (tios.c_ispeed != (unsigned int)ctx_rtu->baud) {
            LOG_E(
                    "WARNING Failed to set baud rate %d (%d used)",
                    ctx_rtu->baud,
                    tios.c_ispeed);
        }
    }

    return 0;
}

// FIXME 在重写 Windows RTU 后端之前的临时解决方案
static unsigned int _modbus_rtu_is_connected(modbus_t *ctx)
{
    return ctx->s >= 0;
}

int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
#if HAVE_DECL_TIOCSRS485
        modbus_rtu_t *ctx_rtu = ctx->backend_data;
        struct serial_rs485 rs485conf;

        if (mode == MODBUS_RTU_RS485) {
            // 获取当前 RS485 配置
            if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
                return -1;
            }
            // 设置 RS485 模式
            rs485conf.flags |= SER_RS485_ENABLED;
            if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
                return -1;
            }

            ctx_rtu->serial_mode = MODBUS_RTU_RS485;
            return 0;
        } else if (mode == MODBUS_RTU_RS232) {
            /* 仅在需要时关闭 RS485 模式 */
            if (ctx_rtu->serial_mode == MODBUS_RTU_RS485) {
                /* 避免调用 ioctl，因为在某些 RS232 端口上可能失败 */
                if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
                    return -1;
                }
                rs485conf.flags &= ~SER_RS485_ENABLED;
                if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
                    return -1;
                }
            }
            ctx_rtu->serial_mode = MODBUS_RTU_RS232;
            return 0;
        }
#else
        if (ctx->debug) {
            LOG_E("This function isn't supported on your platform");
        }
        errno = ENOTSUP;
        return -1;
#endif
    }

    /* 错误的后端类型或指定了无效的模式 */
    errno = EINVAL;
    return -1;
}

int modbus_rtu_get_serial_mode(modbus_t *ctx)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
#if HAVE_DECL_TIOCSRS485
        modbus_rtu_t *ctx_rtu = ctx->backend_data;
        return ctx_rtu->serial_mode;
#else
        if (ctx->debug) {
            LOG_E("This function isn't supported on your platform");
        }
        errno = ENOTSUP;
        return -1;
#endif
    } else {
        errno = EINVAL;
        return -1;
    }
}

int modbus_rtu_get_rts(modbus_t *ctx)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
#if HAVE_DECL_TIOCM_RTS
        modbus_rtu_t *ctx_rtu = ctx->backend_data;
        return ctx_rtu->rts;
#else
        if (ctx->debug) {
            LOG_E("This function isn't supported on your platform");
        }
        errno = ENOTSUP;
        return -1;
#endif
    } else {
        errno = EINVAL;
        return -1;
    }
}

int modbus_rtu_set_rts(modbus_t *ctx, int mode)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
#if HAVE_DECL_TIOCM_RTS
        modbus_rtu_t *ctx_rtu = ctx->backend_data;

        if (mode == MODBUS_RTU_RTS_NONE || mode == MODBUS_RTU_RTS_UP ||
            mode == MODBUS_RTU_RTS_DOWN) {
            ctx_rtu->rts = mode;

            /* 设置 RTS 位，以避免占用 RS485 总线 */
            ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP);

            return 0;
        } else {
            errno = EINVAL;
            return -1;
        }
#else
        if (ctx->debug) {
            LOG_E("This function isn't supported on your platform");
        }
        errno = ENOTSUP;
        return -1;
#endif
    }
    /* 错误的后端类型或指定了无效的模式 */
    errno = EINVAL;
    return -1;
}

int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts)(modbus_t *ctx, int on))
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
#if HAVE_DECL_TIOCM_RTS
        modbus_rtu_t *ctx_rtu = ctx->backend_data;
        ctx_rtu->set_rts = set_rts;
        return 0;
#else
        if (ctx->debug) {
            LOG_E("This function isn't supported on your platform");
        }
        errno = ENOTSUP;
        return -1;
#endif
    } else {
        errno = EINVAL;
        return -1;
    }
}

int modbus_rtu_get_rts_delay(modbus_t *ctx)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
#if HAVE_DECL_TIOCM_RTS
        modbus_rtu_t *ctx_rtu;
        ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
        return ctx_rtu->rts_delay;
#else
        if (ctx->debug) {
            LOG_E("This function isn't supported on your platform");
        }
        errno = ENOTSUP;
        return -1;
#endif
    } else {
        errno = EINVAL;
        return -1;
    }
}

int modbus_rtu_set_rts_delay(modbus_t *ctx, int us)
{
    if (ctx == NULL || us < 0) {
        errno = EINVAL;
        return -1;
    }

    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
#if HAVE_DECL_TIOCM_RTS
        modbus_rtu_t *ctx_rtu;
        ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
        ctx_rtu->rts_delay = us;
        return 0;
#else
        if (ctx->debug) {
            LOG_E("This function isn't supported on your platform");
        }
        errno = ENOTSUP;
        return -1;
#endif
    } else {
        errno = EINVAL;
        return -1;
    }
}

static void _modbus_rtu_close(modbus_t *ctx)
{
    /* 在 RTU 模式下恢复线路设置并关闭文件描述符 */
    modbus_rtu_t *ctx_rtu = ctx->backend_data;

    if (ctx->s >= 0) {
        ioctl(ctx->s, TCSETS2, &ctx_rtu->old_tios);
        close(ctx->s);
        ctx->s = -1;
    }
}

static int _modbus_rtu_flush(modbus_t *ctx)
{
    return tcflush(ctx->s, TCIOFLUSH);
}

static int
_modbus_rtu_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read)
{
    int s_rc;
    while ((s_rc = select(ctx->s + 1, rset, NULL, NULL, tv)) == -1) {
        if (errno == EINTR) {
            if (ctx->debug) {
                LOG_E("A non blocked signal was caught");
            }
            /* 发生错误后需要重新设置 */
            FD_ZERO(rset);
            if (ctx->s < 0 || ctx->s >= FD_SETSIZE) {
                errno = EINVAL;
                return -1;
            }
            FD_SET(ctx->s, rset);
        } else {
            return -1;
        }
    }

    if (s_rc == 0) {
        /* 超时 */
        errno = ETIMEDOUT;
        return -1;
    }

    return s_rc;
}

static void _modbus_rtu_free(modbus_t *ctx)
{
    if (ctx->backend_data) {
        free(((modbus_rtu_t *) ctx->backend_data)->device);
        free(ctx->backend_data);
    }

    free(ctx);
}

// clang-format off
const modbus_backend_t _modbus_rtu_backend = {
    _MODBUS_BACKEND_TYPE_RTU,
    _MODBUS_RTU_HEADER_LENGTH,
    _MODBUS_RTU_CHECKSUM_LENGTH,
    MODBUS_RTU_MAX_ADU_LENGTH,
    _modbus_set_slave,
    _modbus_rtu_build_request_basis,
    _modbus_rtu_build_response_basis,
    _modbus_rtu_get_response_tid,
    _modbus_rtu_send_msg_pre,
    _modbus_rtu_send,
    _modbus_rtu_receive,
    _modbus_rtu_recv,
    _modbus_rtu_check_integrity,
    _modbus_rtu_pre_check_confirmation,
    _modbus_rtu_connect,
    _modbus_rtu_is_connected,
    _modbus_rtu_close,
    _modbus_rtu_flush,
    _modbus_rtu_select,
    _modbus_rtu_free
};

// clang-format on

modbus_t *
modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit)
{
    modbus_t *ctx;
    modbus_rtu_t *ctx_rtu;

    /* 检查设备参数 */
    if (device == NULL || *device == 0) {
        LOG_E("The device string is empty");
        errno = EINVAL;
        return NULL;
    }

    /* 检查波特率参数 */
    if (baud == 0) {
        LOG_E("The baud rate value must not be zero");
        errno = EINVAL;
        return NULL;
    }

    ctx = (modbus_t *) malloc(sizeof(modbus_t));
    if (ctx == NULL) {
        return NULL;
    }

    _modbus_init_common(ctx);
    ctx->backend = &_modbus_rtu_backend;
    ctx->backend_data = (modbus_rtu_t *) malloc(sizeof(modbus_rtu_t));
    if (ctx->backend_data == NULL) {
        modbus_free(ctx);
        errno = ENOMEM;
        return NULL;
    }
    ctx_rtu = (modbus_rtu_t *) ctx->backend_data;

    /* 设备名称和字符串结束符 \0 */
    ctx_rtu->device = (char *) malloc((strlen(device) + 1) * sizeof(char));
    if (ctx_rtu->device == NULL) {
        modbus_free(ctx);
        errno = ENOMEM;
        return NULL;
    }

    strcpy(ctx_rtu->device, device);

    ctx_rtu->baud = baud;
    if (parity == 'N' || parity == 'E' || parity == 'O') {
        ctx_rtu->parity = parity;
    } else {
        modbus_free(ctx);
        errno = EINVAL;
        return NULL;
    }
    ctx_rtu->data_bit = data_bit;
    ctx_rtu->stop_bit = stop_bit;

#if HAVE_DECL_TIOCSRS485
    /* 默认设置为 RS232 模式 */
    ctx_rtu->serial_mode = MODBUS_RTU_RS232;
#endif

#if HAVE_DECL_TIOCM_RTS
    /* 默认情况下 RTS 功能已设置 */
    ctx_rtu->rts = MODBUS_RTU_RTS_NONE;

    /* 计算发送一个字节的估计时间（微秒） */
    ctx_rtu->onebyte_time =
        1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;

    /* 默认使用内部函数来设置 RTS */
    ctx_rtu->set_rts = _modbus_rtu_ioctl_rts;

    /* 切换 RTS 引脚时发送前后的延迟时间 */
    ctx_rtu->rts_delay = ctx_rtu->onebyte_time;
#endif

    ctx_rtu->confirmation_to_ignore = FALSE;

    return ctx;
}
