LCOV - code coverage report
Current view: top level - wirestead/transport/serial - boost_serial_port.hpp (source / functions) Coverage Total Hit
Test: Wirestead Coverage Report Lines: 35.2 % 71 25
Test Date: 2026-10-29 11:36:05 Functions: 64.7 % 17 11
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 3.8 % 26 1

             Branch data     Line data    Source code
       1                 :             : /*
       2                 :             :  * Copyright 2025 Jinwoo Sung
       3                 :             :  *
       4                 :             :  * Licensed under the Apache License, Version 2.0 (the "License");
       5                 :             :  * you may not use this file except in compliance with the License.
       6                 :             :  * You may obtain a copy of the License at
       7                 :             :  *
       8                 :             :  *     http://www.apache.org/licenses/LICENSE-2.0
       9                 :             :  *
      10                 :             :  * Unless required by applicable law or agreed to in writing, software
      11                 :             :  * distributed under the License is distributed on an "AS IS" BASIS,
      12                 :             :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      13                 :             :  * See the License for the specific language governing permissions and
      14                 :             :  * limitations under the License.
      15                 :             :  */
      16                 :             : 
      17                 :             : #pragma once
      18                 :             : 
      19                 :             : #include "wirestead/base/visibility.hpp"
      20                 :             : #include "wirestead/interface/iserial_port.hpp"
      21                 :             : 
      22                 :             : #ifdef __linux__
      23                 :             : #include <linux/serial.h>
      24                 :             : #include <sys/ioctl.h>
      25                 :             : #include <termios.h>
      26                 :             : #endif
      27                 :             : #if defined(__APPLE__)
      28                 :             : #include <sys/ioctl.h>
      29                 :             : #endif
      30                 :             : 
      31                 :             : #include <optional>
      32                 :             : 
      33                 :             : namespace wirestead {
      34                 :             : namespace transport {
      35                 :             : 
      36                 :             : namespace net = boost::asio;
      37                 :             : 
      38                 :             : class WIRESTEAD_API BoostSerialPort : public interface::SerialPortInterface {
      39                 :             :  public:
      40                 :          43 :   explicit BoostSerialPort(net::io_context& ioc) : port_(ioc) {}
      41                 :             : 
      42                 :          36 :   void open(const std::string& device, boost::system::error_code& ec) override { port_.open(device, ec); }
      43                 :         101 :   bool is_open() const override { return port_.is_open(); }
      44                 :          24 :   void close(boost::system::error_code& ec) override { port_.close(ec); }
      45                 :             : 
      46                 :          24 :   void set_option(const net::serial_port_base::baud_rate& option, boost::system::error_code& ec) override {
      47                 :          24 :     port_.set_option(option, ec);
      48                 :          24 :   }
      49                 :          24 :   void set_option(const net::serial_port_base::character_size& option, boost::system::error_code& ec) override {
      50                 :          24 :     port_.set_option(option, ec);
      51                 :          24 :   }
      52                 :          24 :   void set_option(const net::serial_port_base::stop_bits& option, boost::system::error_code& ec) override {
      53                 :          24 :     port_.set_option(option, ec);
      54                 :          24 :   }
      55                 :          24 :   void set_option(const net::serial_port_base::parity& option, boost::system::error_code& ec) override {
      56                 :          24 :     port_.set_option(option, ec);
      57                 :          24 :   }
      58                 :          24 :   void set_option(const net::serial_port_base::flow_control& option, boost::system::error_code& ec) override {
      59                 :          24 :     port_.set_option(option, ec);
      60                 :          24 :   }
      61                 :             : 
      62                 :             :   // Linux only. TIOCGSERIAL fails with ENOTTY on drivers that have no latency
      63                 :             :   // timer to clear, which is the common case for native UARTs and CDC-ACM, so
      64                 :             :   // the caller treats false as "nothing to do" rather than as a failure.
      65                 :          24 :   bool set_low_latency() override {
      66                 :             : #ifdef __linux__
      67                 :             :     struct serial_struct info;
      68                 :          24 :     const int fd = port_.native_handle();
      69         [ +  - ]:          24 :     if (::ioctl(fd, TIOCGSERIAL, &info) != 0) return false;
      70                 :           0 :     const auto low_latency = static_cast<decltype(info.flags)>(ASYNC_LOW_LATENCY);
      71         [ #  # ]:           0 :     if (info.flags & low_latency) return true;
      72                 :           0 :     info.flags |= low_latency;
      73                 :           0 :     return ::ioctl(fd, TIOCSSERIAL, &info) == 0;
      74                 :             : #else
      75                 :             :     return false;
      76                 :             : #endif
      77                 :             :   }
      78                 :             : 
      79                 :             :   // Linux only. A driver with no RS-485 support answers ENOTTY, which the
      80                 :             :   // caller treats as "this adapter does the switching itself".
      81                 :           0 :   bool set_rs485(bool rts_on_send, bool rx_during_tx, unsigned delay_before_ms, unsigned delay_after_ms) override {
      82                 :             : #ifdef __linux__
      83                 :           0 :     struct serial_rs485 rs485 {};
      84                 :           0 :     const int fd = port_.native_handle();
      85                 :             :     // Read first: some drivers carry board-specific bits in flags that a
      86                 :             :     // blind write would clear.
      87         [ #  # ]:           0 :     if (::ioctl(fd, TIOCGRS485, &rs485) != 0) return false;
      88                 :           0 :     rs485.flags |= SER_RS485_ENABLED;
      89         [ #  # ]:           0 :     if (rts_on_send) {
      90                 :           0 :       rs485.flags |= SER_RS485_RTS_ON_SEND;
      91                 :           0 :       rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RTS_AFTER_SEND);
      92                 :             :     } else {
      93                 :           0 :       rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RTS_ON_SEND);
      94                 :           0 :       rs485.flags |= SER_RS485_RTS_AFTER_SEND;
      95                 :             :     }
      96         [ #  # ]:           0 :     if (rx_during_tx) {
      97                 :           0 :       rs485.flags |= SER_RS485_RX_DURING_TX;
      98                 :             :     } else {
      99                 :           0 :       rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RX_DURING_TX);
     100                 :             :     }
     101                 :           0 :     rs485.delay_rts_before_send = delay_before_ms;
     102                 :           0 :     rs485.delay_rts_after_send = delay_after_ms;
     103                 :           0 :     return ::ioctl(fd, TIOCSRS485, &rs485) == 0;
     104                 :             : #else
     105                 :             :     (void)rts_on_send;
     106                 :             :     (void)rx_during_tx;
     107                 :             :     (void)delay_before_ms;
     108                 :             :     (void)delay_after_ms;
     109                 :             :     return false;
     110                 :             : #endif
     111                 :             :   }
     112                 :             : 
     113                 :           0 :   bool set_modem_lines(std::optional<bool> dtr, std::optional<bool> rts) override {
     114                 :             : #if defined(__linux__) || defined(__APPLE__)
     115                 :           0 :     const int fd = port_.native_handle();
     116                 :           0 :     bool ok = true;
     117                 :           0 :     auto apply = [&](std::optional<bool> want, int bit) {
     118         [ #  # ]:           0 :       if (!want) return;
     119                 :           0 :       int lines = bit;
     120   [ #  #  #  # ]:           0 :       if (::ioctl(fd, *want ? TIOCMBIS : TIOCMBIC, &lines) != 0) ok = false;
     121                 :           0 :     };
     122                 :           0 :     apply(dtr, TIOCM_DTR);
     123                 :           0 :     apply(rts, TIOCM_RTS);
     124                 :           0 :     return ok;
     125                 :             : #else
     126                 :             :     (void)dtr;
     127                 :             :     (void)rts;
     128                 :             :     return false;
     129                 :             : #endif
     130                 :             :   }
     131                 :             : 
     132                 :           0 :   bool keep_dtr_on_close() override {
     133                 :             : #ifdef __linux__
     134                 :           0 :     const int fd = port_.native_handle();
     135                 :           0 :     struct termios settings {};
     136         [ #  # ]:           0 :     if (::tcgetattr(fd, &settings) != 0) return false;
     137                 :           0 :     int line = TIOCM_DTR;
     138         [ #  # ]:           0 :     if (::ioctl(fd, TIOCMBIS, &line) != 0) return false;
     139                 :           0 :     settings.c_cflag &= ~static_cast<tcflag_t>(HUPCL);
     140         [ #  # ]:           0 :     if (::tcsetattr(fd, TCSANOW, &settings) != 0) return false;
     141                 :             :     // Some drivers accept settings without applying them.
     142   [ #  #  #  # ]:           0 :     return ::tcgetattr(fd, &settings) == 0 && !(settings.c_cflag & HUPCL);
     143                 :             : #else
     144                 :             :     return false;
     145                 :             : #endif
     146                 :             :   }
     147                 :             : 
     148                 :          33 :   void async_read_some(const net::mutable_buffer& buffer,
     149                 :             :                        std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
     150                 :          33 :     port_.async_read_some(buffer, std::move(handler));
     151                 :          33 :   }
     152                 :             : 
     153                 :           0 :   void async_write(const net::const_buffer& buffer,
     154                 :             :                    std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
     155                 :           0 :     net::async_write(port_, buffer, std::move(handler));
     156                 :           0 :   }
     157                 :             : 
     158                 :             :   // Real scatter-gather write: hands the sequence to asio, which issues one
     159                 :             :   // sendmsg/writev instead of one send per queued buffer.
     160                 :           0 :   void async_write(const std::vector<net::const_buffer>& buffers,
     161                 :             :                    std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
     162                 :           0 :     net::async_write(port_, buffers, std::move(handler));
     163                 :           0 :   }
     164                 :             : 
     165                 :             :  private:
     166                 :             :   net::serial_port port_;
     167                 :             : };
     168                 :             : 
     169                 :             : }  // namespace transport
     170                 :             : }  // namespace wirestead
        

Generated by: LCOV version 2.0-1