繁体   English   中英

Boost::Asio 无法使用本机句柄配置 Windows 和 Linux 串口结构

[英]Boost::Asio not able to configure Windows & Linux serial port structures using native handle

我正在使用 Boost::Asio 处理串行端口例程。
我正在使用 Boost::Asio 提供的包装器配置端口。
在我的应用程序中,数据结束由接收超时或\r\n行终止序列表示。
由于 Boost::Asio 不提供包装器来访问和配置DCB - Windows, termios - Linux 结构以配置超时和/或行结束; 我正在访问通过native_handle包装器返回的本机端口句柄/文件描述符并手动配置结构。
但是,我似乎无法正确配置端口。
即使我将数据的结尾配置为用\n表示,数据也会以块的形式部分返回。
同样,数据也会在超时发生之前返回。


更新

系统工作于 2 种模式

  1. Line Mode -> 逐行读取数据并进行处理。 行结束字符\r\n\r\n
  2. 批量模式 -> 读取多行数据。 一旦在预定时间间隔内没有接收到数据,则称数据已完全接收。 例如,如果我在 50 毫秒内没有收到新数据,我认为传输已完成。

代码

bool SerialPort::open_port(void)
{
    try
    {
        this->port.open(this->port_name);
        this->native_port = this->port.native_handle();
        return true;
    }
    catch (const std::exception& ex)
    {
        PLOG_FATAL << ex.what();
    }
    return false;
}

bool SerialPort::open_port(const std::string& port_name, std::uint32_t baud_rate, std::uint8_t data_bits, std::uint8_t stop_bits,
    parity_t parity, flow_control_t flow_control, std::uint32_t read_timeout, std::uint32_t read_inter_byte_timeout,
    std::uint32_t write_timeout)
{
    try
    {
        this->port_name = port_name;
        if (not this->open_port())
            return false;
        if (not this->set_baud_rate(baud_rate).has_value())
            return false;
        if (not this->set_data_bits(data_bits).has_value())
            return false;
        if (not this->set_stop_bits(stop_bits).has_value())
            return false;
        if (not this->set_parity(parity).has_value())
            return false;
        if (not this->set_flow_control(flow_control).has_value())
            return false;
        this->read_timeout = read_timeout;
        if (read_inter_byte_timeout <= 0)
            this->read_inter_byte_timeout = 1;

#ifdef _WIN64
        BOOL            return_value;
        DCB             dcb = { 0 };
        COMMTIMEOUTS    timeouts = { 0 };
        if (this->line_mode)    //Set COM port to return data either at \n or \r
        {
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = GetCommState(this->native_port, &dcb);
            if (return_value)
            {
                if(this->new_line_character == '\r')
                    dcb.EofChar = '\r'; //Specify end of data character as carriage-return (\r)
                else // --> Default
                    dcb.EofChar = '\n'; //Specify end of data character as new-line (\n) 
            }
            else
            {
                PLOG_ERROR << "Error GetCommState : " << GetLastErrorAsString();
                return false;
            }
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = SetCommState(this->native_port, &dcb);
            if (not return_value)
            {
                PLOG_ERROR << "Error SetCommState : " << GetLastErrorAsString();
                return false;
            }
        }
        else    //Set COM port to return data on timeout
        {
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = GetCommTimeouts(this->native_port, &timeouts);
            if (return_value)
            {
                timeouts.ReadIntervalTimeout = this->read_inter_byte_timeout; // Timeout in miliseconds
                //timeouts.ReadTotalTimeoutConstant = 0;   //MAXDWORD; // in milliseconds - not needed
                //timeouts.ReadTotalTimeoutMultiplier = 0; // in milliseconds - not needed
                //timeouts.WriteTotalTimeoutConstant = 50; // in milliseconds - not needed
                //timeouts.WriteTotalTimeoutMultiplier = write_timeout; // in milliseconds - not needed
            }
            else
            {
                PLOG_ERROR << "Error GetCommTimeouts : " << GetLastErrorAsString();
                return false;
            }
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = SetCommTimeouts(this->native_port, &timeouts);
            if (not return_value)
            {
                PLOG_ERROR << "Error SetCommTimeouts : " << GetLastErrorAsString();
                return false;
            }
        }
#else //For Linux termios
#endif // _WIN64

        return true;
    }
    catch (const std::exception& ex)
    {
        PLOG_ERROR << ex.what();
        return false;
    }
}

void SerialPort::read_handler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    this->read_async(); // I realized I was calling read_async before reading data
    bool receive_complete{ false };
    try
    {
        if (error not_eq boost::system::errc::success)  //Error in serial port read
        {
            PLOG_ERROR << error.to_string();
            this->async_signal.emit(this->port_number, SerialPortEvents::read_error, error.to_string());
            return;
        }

        if (this->line_mode)
        {
            std::string temporary_recieve_data;
            std::transform(this->read_buffer.begin(), this->read_buffer.begin() + bytes_transferred, //Data is added to temporary buffer
                std::back_inserter(temporary_recieve_data), [](std::byte character) {
                    return static_cast<char>(character);
                }
            );
            boost::algorithm::trim(temporary_recieve_data); // Trim handles space character, tab, carriage return, newline, vertical tab and form feed
            //Data is further processed based on the Process logic
            receive_complete = true;
        }
        else    // Bulk-Data. Just append data to end of received_data string buffer.
                // Wait for timeout to trigger recevive_complete
        {
            //Test Function
            std::transform(this->read_buffer.begin(), this->read_buffer.begin() + bytes_transferred,
                std::back_inserter(this->received_data), [](std::byte character) {
                    return static_cast<char>(character);
                }
            );
            this->async_signal.emit(this->port_number, SerialPortEvents::read_data, this->received_data); //Data has been recieved send to server via MQTT
        }
            
    }
    catch (const std::exception& ex)
    {
        PLOG_ERROR << ex.what();
        this->async_signal.emit(this->port_number, SerialPortEvents::read_error, ex.what());
    }
}

配套 Function

std::optional<std::uint32_t> SerialPort::set_baud_rate(std::uint32_t baud_rate)
{
    boost::system::error_code                   error;
    std::uint32_t                               _baud_rate = 1200;
    switch (baud_rate)
    {
    case 1200:
    case 2400:
    case 4800:
    case 9600:
    case 115200:
        _baud_rate = baud_rate;
        break;
    default:
        _baud_rate = 1200;
        break;
    }

    this->port.set_option(boost::asio::serial_port_base::baud_rate(_baud_rate), error);

    if (error)
    {
        PLOG_FATAL << error.message();
        return std::nullopt;
    }

    return baud_rate;
}

std::optional<std::uint8_t> SerialPort::set_data_bits(std::uint8_t data_bits)
{
    boost::system::error_code                   error;
    std::uint32_t                               _data_bits = 8;
    switch (data_bits)
    {
    case 7:
    case 8:
        _data_bits = data_bits;
        break;
    default:
        _data_bits = 8;
        break;
    }

    this->port.set_option(boost::asio::serial_port_base::character_size(_data_bits), error);

    if (error)
    {
        PLOG_FATAL << error.message();
        return std::nullopt;
    }

    return data_bits;
}

std::optional<std::uint8_t> SerialPort::set_stop_bits(std::uint8_t stop_bits)
{
    boost::system::error_code                   error;
    switch (stop_bits)
    {
    case 1:
        this->port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one), error);
        break;
    case 2:
        this->port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::two), error);
        break;
    default:
        this->port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one), error);
        break;
    }

    if (error)
    {
        PLOG_FATAL << error.message();
        return std::nullopt;
    }

    return stop_bits;
}


std::optional<parity_t> SerialPort::set_parity(parity_t parity)
{
    boost::system::error_code                   error;
    switch (parity)
    {
    case Parity::none:
        this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none), error);
        break;
    case Parity::even:
        this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::even), error);
        break;
    case Parity::odd:
        this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::odd), error);
        break;
    default:
        this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none), error);
        break;
    }

    if (error)
    {
        PLOG_FATAL << error.message();
        return std::nullopt;
    }

    return parity;
}

std::optional<flow_control_t> SerialPort::set_flow_control(flow_control_t flow_control)
{
    boost::system::error_code                   error;
    switch (flow_control)
    {
    case FlowControl::none:
        this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none), error);
        break;
    case FlowControl::hardware:
        this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::hardware), error);
        break;
    case FlowControl::software:
        this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::software), error);
        break;
    default:
        this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none), error);
        break;
    }

    if (error)
    {
        PLOG_FATAL << error.message();
        return std::nullopt;
    }
    return flow_control;
}

对您的意见:

传入的数据是多行的,因此我在串行端口结构中设置了读取超时 .netrval

这有什么意义。 无论哪种方式,在 Asio 中处理超时都没有真正的区别/

关于过载:

但我收到 E0304 no instance of overloaded function "boost::asio::async_read_until" matches the argument list 错误。

这是我的十便士:

async_read_until(port,
    asio::dynamic_buffer(read_buffer),
    "\r\n",
    bind(&SerialPort::read_handler, this, error, bytes_transferred));

请注意,您需要动态缓冲区。 我能想到的最简单的事情就是接近你原来的样子

std::vector<std::byte> read_buffer;

现在,我们将更新读取处理程序以擦除“已消耗”部分,因为接收缓冲区可能包含超出定界符的数据。

void read_handler(boost::system::error_code ec, size_t const bytes_transferred) {
    std::cerr << "received " << bytes_transferred << " bytes (" << ec.message() << ")"
              << std::endl;

    auto b = reinterpret_cast<char const*>(read_buffer.data()),
         e = b + std::min(bytes_transferred, read_buffer.size());

    if (std::all_of(
            b, e,                                                             //
            [](uint8_t ch) { return std::isspace(ch) || std::isgraph(ch); })) //
    {
        std::cerr << "ascii: " << quoted(std::string_view(b, e)) << std::endl;
    } else {
        std::cerr << "binary: ";
        auto fmt = std::cerr.flags();
        for (auto it = b; it != e; ++it) {
            std::cerr << " " << std::hex << std::showbase << std::setfill('0')
                      << std::setw(4) << static_cast<unsigned>(*it);
        }
        std::cerr.flags(fmt);
    }
    std::cerr << std::endl;

    read_buffer.erase(begin(read_buffer), begin(read_buffer) + bytes_transferred);

    if (!ec)
        read_async(ignore_timeout);
}

完整列表: Live On Coliru

#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
#include <ranges>
namespace asio = boost::asio;

static inline std::ostream PLOG_ERROR(std::cerr.rdbuf());

struct SerialPort {
    static constexpr uint32_t ignore_timeout = -1;

    SerialPort(asio::any_io_executor ex, std::string dev) : port(ex, dev) {}

    bool read_async(uint32_t timeout_override) {
        try {
            // not necessary: std::ranges::fill(read_buffer, std::byte{});

            if (timeout_override not_eq SerialPort::ignore_timeout) {
                read_timeout = timeout_override;
            }
            using namespace asio::placeholders;

            async_read_until(port,
                asio::dynamic_buffer(read_buffer),
                "\r\n",
                bind(&SerialPort::read_handler, this, error, bytes_transferred));

            return true;
        } catch (std::exception const& ex) {
            PLOG_ERROR << ex.what() << std::endl;
            return false;
        }
    }

  private:
    void read_handler(boost::system::error_code ec, size_t const bytes_transferred) {
        std::cerr << "received " << bytes_transferred << " bytes (" << ec.message() << ")"
                  << std::endl;

        auto b = reinterpret_cast<char const*>(read_buffer.data()),
             e = b + std::min(bytes_transferred, read_buffer.size());

        if (std::all_of(
                b, e,                                                             //
                [](uint8_t ch) { return std::isspace(ch) || std::isgraph(ch); })) //
        {
            std::cerr << "ascii: " << quoted(std::string_view(b, e)) << std::endl;
        } else {
            std::cerr << "binary: ";
            auto fmt = std::cerr.flags();
            for (auto it = b; it != e; ++it) {
                std::cerr << " " << std::hex << std::showbase << std::setfill('0')
                          << std::setw(4) << static_cast<unsigned>(*it);
            }
            std::cerr.flags(fmt);
        }
        std::cerr << std::endl;

        read_buffer.erase(begin(read_buffer), begin(read_buffer) + bytes_transferred);

        if (!ec)
            read_async(ignore_timeout);
    }

    uint32_t               read_timeout = 10;
    std::vector<std::byte> read_buffer;
    asio::serial_port      port;
};

int main(int argc, char** argv) {
    asio::io_context ioc;

    SerialPort sp(make_strand(ioc), argc > 1 ? argv[1] : "/dev/ttyS0");
    sp.read_async(SerialPort::ignore_timeout);

    ioc.run();
}

本地演示:

在此处输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM