From 882a0e0b514f3685af6a262fdde1067acdef8091 Mon Sep 17 00:00:00 2001 From: cxh Date: Sun, 4 Jan 2026 17:18:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AF=BB=E5=8F=96IMEI?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/serail_AT.hpp | 5 ++ include/serial_port.h | 54 ++++++++++++ src/main.cpp | 3 + src/serial_AT.cpp | 133 +++++++++++++++++++++++++++++ src/serial_port.cpp | 191 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 386 insertions(+) create mode 100644 include/serail_AT.hpp create mode 100644 include/serial_port.h create mode 100644 src/serial_AT.cpp create mode 100644 src/serial_port.cpp diff --git a/include/serail_AT.hpp b/include/serail_AT.hpp new file mode 100644 index 0000000..bf9efb9 --- /dev/null +++ b/include/serail_AT.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "serial_port.h" + +void init_serial_at(const std::string& device, int baudrate); diff --git a/include/serial_port.h b/include/serial_port.h new file mode 100644 index 0000000..19b11cc --- /dev/null +++ b/include/serial_port.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "logger.hpp" + +class SerialPort +{ + public: + using ReceiveCallback = std::function&)>; + using ReceiveStringCallback = std::function; + + SerialPort(const std::string& id, const std::string& device, int baudrate, int retry_interval = 5); + + ~SerialPort(); + + void start(); // 启动串口(含自动重连) + void stop(); // 停止串口 + + bool is_open() const; + bool send_data(const std::vector& data); + bool send_data(const std::string& data); + + void set_receive_callback(ReceiveCallback cb); + void set_receive_callback(ReceiveStringCallback cb); + + private: + bool open_port(); + void close_port(); + void reader_loop(); + void reconnect_loop(); + bool configure_port(int fd); + + private: + std::string id_; + std::string device_; + int baudrate_; + int fd_ = -1; + + std::atomic running_{false}; + std::atomic stop_flag_{false}; + + std::thread reader_thread_; + std::thread reconnect_thread_; + std::mutex send_mutex_; + + ReceiveCallback receive_callback_; + int retry_interval_; +}; diff --git a/src/main.cpp b/src/main.cpp index c24dda9..069fad8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "mqtt_client_wrapper.hpp" #include "record_manager.hpp" #include "rtmp_manager.hpp" +#include "serail_AT.hpp" std::atomic g_running(true); @@ -51,6 +52,8 @@ int main() return -1; } + init_serial_at("/dev/ttyUSB3", 115200); + // ---------- 初始化 GStreamer ---------- RTMPManager::init(); diff --git a/src/serial_AT.cpp b/src/serial_AT.cpp new file mode 100644 index 0000000..3db0b20 --- /dev/null +++ b/src/serial_AT.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include + +#include "serail_AT.hpp" + +// ================== 全局 IMEI ================== +std::string IMEI = ""; // 一般 15 位 + +// ================== AT 任务结构 ================== +struct AtTask +{ + std::string cmd; + int interval; // 周期,秒;0 表示一次性任务 + int max_retries; // -1 表示无限次 + int sent_count; + std::chrono::steady_clock::time_point last_sent; +}; + +static std::unique_ptr serial_at; +static std::thread serial_at_sender; + +static std::mutex at_tasks_mutex; + +// ================== AT 任务列表 ================== +static std::vector at_tasks = { + {"AT+GSN", 0, 3, 0, {}} // 查询 IMEI,最多重试 3 次 +}; + +// ================== 发送线程 ================== +static void serial_at_send_loop() +{ + while (true) + { + if (serial_at && serial_at->is_open()) + { + auto now = std::chrono::steady_clock::now(); + std::lock_guard lock(at_tasks_mutex); + + for (auto it = at_tasks.begin(); it != at_tasks.end();) + { + auto& task = *it; + bool should_send = false; + + if (task.interval > 0) + { + if (task.last_sent.time_since_epoch().count() == 0 || + std::chrono::duration_cast(now - task.last_sent).count() >= task.interval) + { + should_send = true; + } + } + else + { + if ((task.max_retries < 0 || task.sent_count < task.max_retries) && + (task.last_sent.time_since_epoch().count() == 0 || + std::chrono::duration_cast(now - task.last_sent).count() >= 5)) + { + should_send = true; + } + else if (task.max_retries > 0 && task.sent_count >= task.max_retries) + { + LOG_ERROR("[serial_at] AT+GSN reached max retries"); + + // IMEI 获取失败,仍继续初始化 + init_tcp_client_tbox_router(TboxConfigManager::instance().getPlatformIp(), + TboxConfigManager::instance().getPlatformPort()); + + it = at_tasks.erase(it); + continue; + } + } + + if (should_send) + { + serial_at->send_data(task.cmd + "\r\n"); + task.sent_count++; + task.last_sent = now; + } + + ++it; + } + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} + +// ================== 接收处理 ================== +static void handle_serial_at_data(const std::string& data) +{ + // 拆行处理,避免粘包 + std::istringstream iss(data); + std::string line; + + while (std::getline(iss, line)) + { + // 去空白 + line.erase(0, line.find_first_not_of(" \t\r\n")); + line.erase(line.find_last_not_of(" \t\r\n") + 1); + + // IMEI 通常是 15 位纯数字 + if (line.size() >= 14 && line.size() <= 17 && std::all_of(line.begin(), line.end(), ::isdigit)) + { + IMEI = line; + + LOG_INFO("[serial_at] Extracted IMEI = " + IMEI); + + init_tcp_client_tbox_router(TboxConfigManager::instance().getPlatformIp(), + TboxConfigManager::instance().getPlatformPort()); + + // 移除 AT+GSN 任务 + std::lock_guard lock(at_tasks_mutex); + at_tasks.erase(std::remove_if(at_tasks.begin(), at_tasks.end(), + [](const AtTask& task) { return task.cmd == "AT+GSN"; }), + at_tasks.end()); + return; + } + } +} + +// ================== 初始化接口 ================== +void init_serial_at(const std::string& device, int baudrate) +{ + serial_at = std::make_unique("serial_at", device, baudrate, 5); + + serial_at->set_receive_callback(handle_serial_at_data); + serial_at->start(); + + serial_at_sender = std::thread(serial_at_send_loop); + serial_at_sender.detach(); +} diff --git a/src/serial_port.cpp b/src/serial_port.cpp new file mode 100644 index 0000000..5d133d7 --- /dev/null +++ b/src/serial_port.cpp @@ -0,0 +1,191 @@ +#include "serial_port.h" + +#include +#include +#include + +#include +#include +#include + +// --------- 波特率映射 --------- +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; + + if (reader_thread_.joinable()) reader_thread_.join(); + + if (reconnect_thread_.joinable()) reconnect_thread_.join(); + + close_port(); +} + +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& data) +{ + if (fd_ < 0) return false; + + std::lock_guard lock(send_mutex_); + ssize_t n = write(fd_, data.data(), data.size()); + return n == static_cast(data.size()); +} + +bool SerialPort::send_data(const std::string& data) +{ + return send_data(std::vector(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& data) { cb(std::string(data.begin(), data.end())); }; +} + +void SerialPort::reader_loop() +{ + std::vector 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; +} + +void SerialPort::reconnect_loop() +{ + int current_interval = retry_interval_; + const int max_interval = 300; + + while (!stop_flag_) + { + if (open_port()) + { + // 等待读线程结束 + if (reader_thread_.joinable()) reader_thread_.join(); + + 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"); + + std::this_thread::sleep_for(std::chrono::seconds(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] = 1; + tty.c_cc[VTIME] = 1; + + 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; +}