Created
June 3, 2022 17:36
-
-
Save Tosainu/9e36ce0bb9113efb1f6c66e69f3573e4 to your computer and use it in GitHub Desktop.
serial port <-> TCP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <array> | |
#include <charconv> | |
#include <cstdlib> | |
#include <iostream> | |
#include <memory> | |
#include <stdexcept> | |
#include <string_view> | |
#include <boost/asio/ip/tcp.hpp> | |
#include <boost/asio/serial_port.hpp> | |
class connection : public std::enable_shared_from_this<connection> { | |
public: | |
connection(boost::asio::ip::tcp::socket&& sock, boost::asio::serial_port& serial) | |
: sock_{std::move(sock)}, remote_{sock_.remote_endpoint()}, serial_{serial} {} | |
~connection() { | |
std::cout << "[*] closing connection " << remote_ << std::endl; | |
serial_.close(); | |
} | |
void start() { | |
read(shared_from_this(), sock_, serial_, std::get<0>(buf_)); | |
read(shared_from_this(), serial_, sock_, std::get<1>(buf_)); | |
} | |
private: | |
using buffer_type = std::array<std::uint8_t, 4096>; | |
template <class S1, class S2> | |
static void read(std::shared_ptr<connection> self, S1& src, S2& dst, buffer_type& buf) { | |
auto f = [self, &src, &dst, &buf](const auto& ec, std::size_t len) { | |
if (!ec) { | |
write(self, src, dst, buf, len); | |
} else if (ec != boost::asio::error::operation_aborted) { | |
self->close(); | |
} | |
}; | |
src.async_read_some(boost::asio::buffer(buf), std::move(f)); | |
} | |
template <class S1, class S2> | |
static void write(std::shared_ptr<connection> self, S1& src, S2& dst, buffer_type& buf, | |
std::size_t len) { | |
auto f = [self, &src, &dst, &buf](const auto& ec, [[maybe_unused]] std::size_t len) { | |
if (!ec) { | |
read(self, src, dst, buf); | |
} else if (ec != boost::asio::error::operation_aborted) { | |
self->close(); | |
} | |
}; | |
dst.async_write_some(boost::asio::buffer(buf, len), std::move(f)); | |
} | |
void close() { | |
sock_.cancel(); | |
serial_.cancel(); | |
} | |
std::array<buffer_type, 2> buf_; | |
boost::asio::ip::tcp::socket sock_; | |
boost::asio::ip::tcp::endpoint remote_; | |
boost::asio::serial_port& serial_; | |
}; | |
class server { | |
public: | |
template <class ExecutionContext> | |
server(ExecutionContext& ex, const boost::asio::ip::tcp::endpoint& ep, std::string&& device, | |
unsigned int baud) | |
: acceptor_{ex, ep}, serial_{ex}, device_{std::move(device)}, baud_{baud} { | |
accept(); | |
} | |
private: | |
void accept() { | |
acceptor_.async_accept([this](const auto& ec, auto sock) { | |
if (ec) { | |
std::cerr << "[ERROR] async_accept: " << ec.what() << std::endl; | |
return; | |
} | |
std::cout << "[+] new connection from " << sock.remote_endpoint() << std::endl; | |
if (serial_.is_open()) { | |
std::cerr << "[WARN] serial port is already in use, closing " << sock.remote_endpoint() | |
<< std::endl; | |
sock.close(); | |
} else { | |
std::cout << "[*] opening serial port " << device_ << ", baud = " << baud_ << std::endl; | |
serial_.open(device_); | |
serial_.set_option(boost::asio::serial_port::baud_rate(baud_)); | |
std::make_shared<connection>(std::move(sock), serial_)->start(); | |
} | |
accept(); | |
}); | |
} | |
boost::asio::ip::tcp::acceptor acceptor_; | |
boost::asio::serial_port serial_; | |
const std::string device_; | |
const unsigned int baud_; | |
}; | |
auto main(int argc, char** argv) -> int { | |
if (argc != 5) { | |
std::cerr << "usage:" << argv[0] << " <addr> <port> <device> <baudrate>" << std::endl; | |
return -1; | |
} | |
const auto addr = [arg = argv[1]] { | |
boost::system::error_code ec{}; | |
auto addr = boost::asio::ip::make_address(arg, ec); | |
if (ec) { | |
std::cerr << "[ERROR] invalid address '" << arg << "': " << ec.what() << std::endl; | |
std::exit(-1); | |
} | |
return addr; | |
}(); | |
const auto port = [arg = argv[2]] { | |
boost::asio::ip::port_type port{}; | |
if (std::string_view s{arg}; | |
std::from_chars(s.data(), s.data() + s.size(), port).ec != std::errc()) { | |
std::cerr << "[ERROR] invalid port number '" << s << "'" << std::endl; | |
std::exit(-1); | |
} | |
return port; | |
}(); | |
const auto device = argv[3]; | |
const auto baud = [arg = argv[4]] { | |
unsigned int baud{}; | |
if (std::string_view s{arg}; | |
std::from_chars(s.data(), s.data() + s.size(), baud).ec != std::errc()) { | |
std::cerr << "[ERROR] invalid baud rate '" << s << "'" << std::endl; | |
std::exit(-1); | |
} | |
return baud; | |
}(); | |
const auto endpoint = boost::asio::ip::tcp::endpoint{addr, port}; | |
std::cout << "[*] starting server on " << endpoint << " ..." << std::endl; | |
boost::asio::io_context ctx{1}; | |
try { | |
auto s = server{ctx, endpoint, device, baud}; | |
ctx.run(); | |
} catch (const std::exception& e) { | |
std::cerr << "[ERROR] " << e.what() << std::endl; | |
return -1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment