Skip to content

Commit

Permalink
Add VirtualSerialPort class
Browse files Browse the repository at this point in the history
  • Loading branch information
janekbaraniewski committed Apr 28, 2024
1 parent 12ce7aa commit 2db5c07
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 47 deletions.
21 changes: 12 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,27 @@ target_link_libraries(logging PUBLIC
Boost::date_time
)

add_executable(serial_server src/server.cpp)
target_link_libraries(serial_server PRIVATE
serial_server_lib
logging
add_library(serial_server_lib src/logging.cpp src/server.cpp src/VirtualSerialPort.cpp) # Include VirtualSerialPort.cpp
target_include_directories(serial_server_lib PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(serial_server_lib PRIVATE
Boost::system
Boost::program_options
Boost::log
Boost::log_setup
Boost::date_time
logging
pthread
)

add_library(serial_server_lib src/logging.cpp src/server.cpp)
target_include_directories(serial_server_lib PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(serial_server_lib PRIVATE
add_executable(serial_server src/server.cpp)
target_link_libraries(serial_server PRIVATE
serial_server_lib
logging
Boost::system
Boost::program_options
Boost::log
Boost::log_setup
Boost::date_time
logging
pthread
)

add_executable(serial_client src/client.cpp)
Expand All @@ -60,6 +60,9 @@ target_link_libraries(serial_client PRIVATE
logging
Boost::system
Boost::program_options
Boost::log
Boost::log_setup
Boost::date_time
)

# # TODO: REENABLE TESTS
Expand Down
23 changes: 23 additions & 0 deletions include/VirtualSerialPort.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef VIRTUALSERIALPORT_H
#define VIRTUALSERIALPORT_H

#include <string>
#include <functional>

class VirtualSerialPort {
public:
VirtualSerialPort(const std::string& device);
~VirtualSerialPort();

void open();
void close();
bool write(const std::string& data);
std::string read();

private:
int master_fd_;
int slave_fd_;
std::string device_name_;
};

#endif // VIRTUALSERIALPORT_H
57 changes: 57 additions & 0 deletions src/VirtualSerialPort.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "VirtualSerialPort.h"
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdexcept>
#include <iostream>
#include <sys/stat.h>

VirtualSerialPort::VirtualSerialPort(const std::string& device) : device_name_("/dev/" + device) {
master_fd_ = posix_openpt(O_RDWR | O_NOCTTY);
if (master_fd_ == -1 || grantpt(master_fd_) != 0 || unlockpt(master_fd_) != 0) {
throw std::runtime_error("Failed to open PTY master");
}

char* slave_name = ptsname(master_fd_);
if (!slave_name) {
throw std::runtime_error("Failed to get PTY slave name");
}

unlink(device_name_.c_str()); // Ensure no stale symlink exists
if (symlink(slave_name, device_name_.c_str()) != 0) {
throw std::runtime_error("Failed to create symlink for PTY slave");
}

slave_fd_ = ::open(slave_name, O_RDWR);
if (slave_fd_ == -1) {
throw std::runtime_error("Failed to open PTY slave");
}

// Set permissions to allow external applications to access the virtual serial port
chmod(device_name_.c_str(), 0666);
}

VirtualSerialPort::~VirtualSerialPort() {
close();
}

void VirtualSerialPort::close() {
::close(master_fd_);
::close(slave_fd_);
unlink(device_name_.c_str()); // Clean up the symlink on destruction
}

bool VirtualSerialPort::write(const std::string& data) {
ssize_t size = ::write(master_fd_, data.c_str(), data.size());
return size >= 0;
}

std::string VirtualSerialPort::read() {
char buffer[256];
ssize_t len = ::read(master_fd_, buffer, sizeof(buffer) - 1);
if (len > 0) {
buffer[len] = '\0';
return std::string(buffer);
}
return "";
}
77 changes: 39 additions & 38 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,57 @@
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>

#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/support/date_time.hpp> // Ensure this is included for date-time support


#include "logging.h"
#include "VirtualSerialPort.h"

using namespace boost::asio;
using namespace boost::program_options;
using ip::tcp;
using std::string;
using std::cout;
using std::cerr;
using std::endl;

void init_logging() {
boost::log::register_simple_formatter_factory<boost::log::trivial::severity_level, char>("Severity");
boost::log::add_console_log(std::cout, boost::log::keywords::format = "[%TimeStamp%] [%ThreadID%] [%Severity%] %Message%");
boost::log::add_file_log(boost::log::keywords::file_name = "serial_client_%N.log",
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
boost::log::keywords::time_based_rotation = boost::log::sinks::file::rotation_at_time_point(0, 0, 0),
boost::log::keywords::format = "[%TimeStamp%] [%Severity%] %Message%");
boost::log::add_common_attributes();
}



class SerialClient {
private:
io_service io_service_;
tcp::socket socket_;
int master_fd_, slave_fd_;
std::array<char, 256> buffer_;
VirtualSerialPort vsp_;

public:
SerialClient(const std::string& server_ip, unsigned short server_port)
: socket_(io_service_) {
// Connect to server
SerialClient(const std::string& server_ip, unsigned short server_port, const std::string& vsp_name)
: socket_(io_service_), vsp_(vsp_name) {
tcp::resolver resolver(io_service_);
auto endpoint_iterator = resolver.resolve({server_ip, std::to_string(server_port)});
connect(socket_, endpoint_iterator);

// Setup PTY
master_fd_ = posix_openpt(O_RDWR | O_NOCTTY);
if (master_fd_ == -1 || grantpt(master_fd_) != 0 || unlockpt(master_fd_) != 0) {
BOOST_LOG_TRIVIAL(error) << "Failed to open or configure PTY master";
throw std::runtime_error("Failed to open or configure PTY master");
}

char* slave_name = ptsname(master_fd_);
if (!slave_name) {
BOOST_LOG_TRIVIAL(error) << "Failed to get PTY slave name";
throw std::runtime_error("Failed to get PTY slave name");
}
slave_fd_ = open(slave_name, O_RDWR);
if (slave_fd_ == -1) {
BOOST_LOG_TRIVIAL(error) << "Failed to open PTY slave";
throw std::runtime_error("Failed to open PTY slave");
}
BOOST_LOG_TRIVIAL(info) << "PTY setup completed. Slave device: " << slave_name;
// vsp_.open(); // Ensure the virtual port is ready for use
}

void run() {
Expand All @@ -60,34 +65,29 @@ class SerialClient {
void do_read_write() {
socket_.async_read_some(boost::asio::buffer(buffer_), [this](boost::system::error_code ec, std::size_t length) {
if (!ec) {
if (write(master_fd_, buffer_.data(), length) < 0) {
BOOST_LOG_TRIVIAL(error) << "Write to PTY master failed";
std::string data(buffer_.begin(), buffer_.begin() + length);
if (!vsp_.write(data)) {
cerr << "Write to virtual serial port failed" << endl;
return;
}
async_write(socket_, boost::asio::buffer(buffer_, length), [this](boost::system::error_code ec, std::size_t) {
if (!ec) {
do_read_write();
} else {
BOOST_LOG_TRIVIAL(error) << "Write back to TCP socket failed: " << ec.message();
}
});
do_read_write(); // Continue reading
} else {
BOOST_LOG_TRIVIAL(error) << "Read error: " << ec.message();
cerr << "Read error: " << ec.message() << endl;
}
});
}
};

#ifndef UNIT_TEST
int main(int argc, char* argv[]) {
init_logging(); // Initialize logging at the start of the main function
init_logging();

try {
options_description desc{"Options"};
desc.add_options()
("help,h", "Help screen")
("server,s", value<std::string>()->default_value("127.0.0.1"), "Server IP address")
("port,p", value<unsigned short>()->default_value(12345), "Server port");
("port,p", value<unsigned short>()->default_value(12345), "Server port")
("vsp,v", value<std::string>()->required(), "Virtual serial port name");

variables_map vm;
store(parse_command_line(argc, argv, desc), vm);
Expand All @@ -100,12 +100,13 @@ int main(int argc, char* argv[]) {

std::string server_ip = vm["server"].as<std::string>();
unsigned short server_port = vm["port"].as<unsigned short>();
std::string vsp_name = vm["vsp"].as<std::string>();

SerialClient client(server_ip, server_port);
SerialClient client(server_ip, server_port, vsp_name);
client.run();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Exception: " << e.what();
cerr << "Exception: " << e.what() << endl;
}
return 0;
}
#endif

0 comments on commit 2db5c07

Please sign in to comment.