Skip to content

Instantly share code, notes, and snippets.

@Tosainu
Created June 3, 2022 17:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tosainu/9e36ce0bb9113efb1f6c66e69f3573e4 to your computer and use it in GitHub Desktop.
Save Tosainu/9e36ce0bb9113efb1f6c66e69f3573e4 to your computer and use it in GitHub Desktop.
serial port <-> TCP
#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