sweeper_video/src/serial_port.cpp
2026-01-05 09:15:49 +08:00

198 lines
4.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "serial_port.h"
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <chrono>
#include <cstring>
#include <thread>
// --------- 波特率映射 ---------
static speed_t baud_to_speed(int baud)
{
switch (baud)
{
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
#ifdef B230400
case 230400:
return B230400;
#endif
default:
return B115200;
}
}
SerialPort::SerialPort(const std::string& id, const std::string& device, int baudrate, int retry_interval)
: id_(id), device_(device), baudrate_(baudrate), retry_interval_(retry_interval)
{
}
SerialPort::~SerialPort() { stop(); }
void SerialPort::start()
{
stop_flag_ = false;
reconnect_thread_ = std::thread(&SerialPort::reconnect_loop, this);
}
void SerialPort::stop()
{
stop_flag_ = true;
running_ = false;
// 先关闭 fd打断 reader 的阻塞 read()
close_port();
if (reader_thread_.joinable()) reader_thread_.join();
if (reconnect_thread_.joinable()) reconnect_thread_.join();
}
bool SerialPort::open_port()
{
fd_ = open(device_.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
if (fd_ < 0)
{
LOG_ERROR("[" + id_ + "] Failed to open " + device_ + ": " + strerror(errno));
return false;
}
if (!configure_port(fd_))
{
LOG_ERROR("[" + id_ + "] Failed to configure " + device_);
close(fd_);
fd_ = -1;
return false;
}
running_ = true;
reader_thread_ = std::thread(&SerialPort::reader_loop, this);
LOG_INFO("[" + id_ + "] Opened serial port " + device_ + " at " + std::to_string(baudrate_) + " baud");
return true;
}
void SerialPort::close_port()
{
running_ = false;
if (fd_ >= 0)
{
close(fd_);
fd_ = -1;
}
}
bool SerialPort::is_open() const { return fd_ >= 0; }
bool SerialPort::send_data(const std::vector<uint8_t>& data)
{
if (fd_ < 0) return false;
std::lock_guard<std::mutex> lock(send_mutex_);
ssize_t n = write(fd_, data.data(), data.size());
return n == static_cast<ssize_t>(data.size());
}
bool SerialPort::send_data(const std::string& data)
{
return send_data(std::vector<uint8_t>(data.begin(), data.end()));
}
void SerialPort::set_receive_callback(ReceiveCallback cb) { receive_callback_ = std::move(cb); }
void SerialPort::set_receive_callback(ReceiveStringCallback cb)
{
receive_callback_ = [cb](const std::vector<uint8_t>& data) { cb(std::string(data.begin(), data.end())); };
}
void SerialPort::reader_loop()
{
std::vector<uint8_t> buffer(1024);
while (running_ && !stop_flag_)
{
int n = read(fd_, buffer.data(), buffer.size());
if (n > 0)
{
if (receive_callback_) receive_callback_({buffer.begin(), buffer.begin() + n});
}
else if (n < 0)
{
LOG_ERROR("[" + id_ + "] Read error: " + std::string(strerror(errno)));
break;
}
// n == 0超时正常情况继续读
}
running_ = false;
}
static void interruptible_sleep(std::atomic<bool>& stop_flag, int seconds)
{
using namespace std::chrono;
for (int i = 0; i < seconds * 10 && !stop_flag.load(std::memory_order_relaxed); ++i)
std::this_thread::sleep_for(100ms);
}
void SerialPort::reconnect_loop()
{
int current_interval = retry_interval_;
const int max_interval = 300;
while (!stop_flag_)
{
if (open_port())
{
// 不 join reader只等 stop_flag_
while (running_ && !stop_flag_) std::this_thread::sleep_for(std::chrono::milliseconds(200));
close_port();
LOG_WARN("[" + id_ + "] Port closed, will retry");
current_interval = retry_interval_;
}
else
{
LOG_INFO("[" + id_ + "] Connect failed, retry in " + std::to_string(current_interval) + "s");
interruptible_sleep(stop_flag_, current_interval);
current_interval = std::min(current_interval * 2, max_interval);
}
}
}
bool SerialPort::configure_port(int fd)
{
struct termios tty{};
if (tcgetattr(fd, &tty) != 0) return false;
speed_t spd = baud_to_speed(baudrate_);
cfsetospeed(&tty, spd);
cfsetispeed(&tty, spd);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
tty.c_iflag &= ~IGNBRK;
tty.c_lflag = 0;
tty.c_oflag = 0;
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 10;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_cflag |= (CLOCAL | CREAD);
tty.c_cflag &= ~(PARENB | PARODD);
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
return tcsetattr(fd, TCSANOW, &tty) == 0;
}