sweeper_video/src/serial_AT.cpp

373 lines
10 KiB
C++
Raw Normal View History

2026-01-04 17:29:49 +08:00
#include "serial_AT.hpp"
2026-01-04 17:18:24 +08:00
#include <algorithm>
#include <atomic>
2026-01-04 17:18:24 +08:00
#include <chrono>
#include <mutex>
2026-01-04 17:24:15 +08:00
#include <sstream>
2026-01-04 17:18:24 +08:00
#include <thread>
2026-01-05 13:48:52 +08:00
#include "app_config.hpp"
2026-01-05 10:24:18 +08:00
#include "httplib.h"
#include "logger.hpp"
2026-01-05 10:31:54 +08:00
#include "nlohmann/json.hpp"
#include "serial_port.h"
2026-01-04 17:18:24 +08:00
// ================== 全局 IMEI ==================
std::string IMEI;
2026-01-05 10:24:18 +08:00
std::mutex imei_mutex;
2026-01-22 15:37:26 +08:00
RadioInfo g_radio_info;
static std::mutex radio_mutex;
// ================== 运行控制 ==================
static std::atomic<bool> serial_at_running{false};
2026-01-04 17:18:24 +08:00
// ================== AT 任务结构 ==================
struct AtTask
{
std::string cmd;
2026-01-04 17:24:15 +08:00
int max_retries;
2026-01-04 17:18:24 +08:00
int sent_count;
std::chrono::steady_clock::time_point last_sent;
};
static std::unique_ptr<SerialPort> serial_at;
static std::thread serial_at_sender;
static std::mutex at_tasks_mutex;
2026-01-05 10:24:18 +08:00
static std::unique_ptr<httplib::Server> http_server;
static std::thread http_thread;
2026-01-22 15:47:17 +08:00
// ---------- 安全 stoi ----------
static bool safe_stoi(const std::string& s, int& v)
{
try
{
size_t idx;
v = std::stoi(s, &idx);
return idx == s.size();
}
catch (...)
{
return false;
}
}
// ---------- split CSV ----------
static std::vector<std::string> split_csv(const std::string& line)
{
std::vector<std::string> out;
std::string cur;
std::stringstream ss(line);
while (std::getline(ss, cur, ','))
{
// 去引号和空白
cur.erase(std::remove(cur.begin(), cur.end(), '"'), cur.end());
while (!cur.empty() && isspace(cur.front())) cur.erase(cur.begin());
while (!cur.empty() && isspace(cur.back())) cur.pop_back();
out.push_back(cur);
}
return out;
}
// ---------- 解析入口 ----------
static void parse_qeng_servingcell(const std::string& line)
{
auto t = split_csv(line);
if (t.size() < 5) return;
// t[0] = +QENG:
// t[1] = servingcell
if (t[0].find("+QENG") != 0) return;
if (t[1] != "servingcell") return;
// t[2] = CONNECT/NOCONN
// t[3] = RAT: LTE / NR5G-SA / NR5G-NSA
std::string rat = t[3];
int pci = -1, rsrp = 0, rsrq = 0, sinr = 0, arfcn = 0;
// ------------------------------------------
// NR5G SA 模式字段表(你的情况)
// ------------------------------------------
// +QENG:"servingcell","CONNECT","NR5G-SA","TDD",
// 460,00, <NR CellID>, TAC, ARFCN, PCI,
// SSB, Beam, RSRP, RSRQ, SINR, ...
//
// 索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
//
if (rat == "NR5G-SA")
{
if (t.size() >= 15)
{
safe_stoi(t[9], pci);
safe_stoi(t[8], arfcn);
safe_stoi(t[12], rsrp);
safe_stoi(t[13], rsrq);
safe_stoi(t[14], sinr);
}
}
// ------------------------------------------
// LTE 模式字段表(兼容)
// ------------------------------------------
// LTE 一般字段:...,PCI= t[6], RSRP=t[11], RSRQ=t[12], SINR=t[14]
//
else if (rat == "LTE")
{
if (t.size() >= 15)
{
safe_stoi(t[6], pci);
safe_stoi(t[11], rsrp);
safe_stoi(t[12], rsrq);
safe_stoi(t[14], sinr);
}
}
// ------------------------------------------
// NSA 模式字段表(简单支持)
// ------------------------------------------
else if (rat == "NR5G-NSA")
{
// NSA 包含 LTE + NR 两部分字段,先取 LTE anchor
if (t.size() >= 15)
{
safe_stoi(t[6], pci);
safe_stoi(t[11], rsrp);
safe_stoi(t[12], rsrq);
safe_stoi(t[14], sinr);
}
}
{
std::lock_guard<std::mutex> lk(radio_mutex);
g_radio_info.pci = pci;
g_radio_info.rsrp = rsrp;
g_radio_info.rsrq = rsrq;
g_radio_info.sinr = sinr;
g_radio_info.arfcn = arfcn;
g_radio_info.raw = line;
}
LOG_INFO("[serial_at] QENG parsed: RAT=" + rat + " PCI=" + std::to_string(pci) + " RSRP=" + std::to_string(rsrp) +
" RSRQ=" + std::to_string(rsrq) + " SINR=" + std::to_string(sinr));
}
2026-01-05 10:26:07 +08:00
static bool is_imei_ready()
{
std::lock_guard<std::mutex> lock(imei_mutex);
return !IMEI.empty();
}
static std::string get_imei()
{
std::lock_guard<std::mutex> lock(imei_mutex);
return IMEI;
}
2026-01-05 10:24:18 +08:00
void start_http_server(int port)
{
http_server = std::make_unique<httplib::Server>();
http_server->Post("/api/v1/device/register",
[](const httplib::Request& req, httplib::Response& res)
{
2026-01-05 13:46:03 +08:00
LOG_INFO("[http] /api/v1/device/register called");
2026-01-05 10:24:18 +08:00
2026-01-05 13:46:03 +08:00
// ---------- 1. IMEI 是否就绪 ----------
2026-01-05 10:24:18 +08:00
if (!is_imei_ready())
{
res.status = 503;
res.set_content(R"({"ok":false,"error":"imei_not_ready"})", "application/json");
return;
}
// ---------- 2. 请求体当前不承载身份(忽略即可) ----------
2026-01-05 13:46:03 +08:00
if (!req.body.empty())
{
LOG_INFO("[http] register payload ignored");
2026-01-05 13:46:03 +08:00
}
2026-01-05 10:24:18 +08:00
// ---------- 3. 返回 B 的身份(唯一事实源) ----------
2026-01-05 13:46:03 +08:00
nlohmann::json jres;
jres["ok"] = true;
jres["device"] = {{"device_type", "neardi"},
{"imei", get_imei()},
{"vid", g_app_config.mqtt.vehicle_id},
{"fw", "b-v2.1.0"}};
2026-01-05 10:31:54 +08:00
2026-01-05 13:46:03 +08:00
res.set_content(jres.dump(), "application/json");
2026-01-05 10:24:18 +08:00
});
http_thread = std::thread(
[port]()
{
LOG_INFO("[http] Server listening on port " + std::to_string(port));
http_server->listen("0.0.0.0", port);
});
}
void stop_http_server()
{
if (http_server)
{
http_server->stop();
}
if (http_thread.joinable())
{
http_thread.join();
}
http_server.reset();
}
2026-01-22 15:37:26 +08:00
// IMEI 需要无限重试
// 信号 AT 命令周期查询5 秒一次)
// max_retries = -1 → 永不删除
static std::vector<AtTask> at_tasks = {
{"AT+GSN", -1, 0, {}}, // 直到成功为止
{"AT+QENG=\"servingcell\"", -1, 0, {}} // RSRP/RSRQ/SINR 查询(周期)
};
2026-01-04 17:18:24 +08:00
// ================== 发送线程 ==================
static void serial_at_send_loop()
{
LOG_INFO("[serial_at] Sender thread started");
while (serial_at_running.load(std::memory_order_relaxed))
2026-01-04 17:18:24 +08:00
{
2026-01-04 17:24:15 +08:00
if (!serial_at || !serial_at->is_open())
2026-01-04 17:18:24 +08:00
{
2026-01-04 17:24:15 +08:00
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
auto now = std::chrono::steady_clock::now();
{
std::lock_guard<std::mutex> lock(at_tasks_mutex);
2026-01-04 17:24:15 +08:00
for (auto it = at_tasks.begin(); it != at_tasks.end();)
2026-01-04 17:24:15 +08:00
{
auto& task = *it;
2026-01-22 15:37:26 +08:00
// ============================
// FIX: max_retries = -1 → 无限发送
// ============================
if (task.max_retries >= 0 && task.sent_count >= task.max_retries)
{
it = at_tasks.erase(it);
continue;
}
2026-01-22 15:37:26 +08:00
// 5 秒一次发送
if (task.last_sent.time_since_epoch().count() == 0 ||
std::chrono::duration_cast<std::chrono::seconds>(now - task.last_sent).count() >= 5)
{
serial_at->send_data(task.cmd + "\r\n");
task.sent_count++;
task.last_sent = now;
}
++it;
2026-01-04 17:24:15 +08:00
}
2026-01-04 17:18:24 +08:00
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
LOG_INFO("[serial_at] Sender thread exiting");
2026-01-04 17:18:24 +08:00
}
// ================== 接收处理 ==================
static void handle_serial_at_data(const std::string& data)
{
std::istringstream iss(data);
std::string line;
while (std::getline(iss, line))
{
2026-01-04 17:24:15 +08:00
// trim
2026-01-04 17:18:24 +08:00
line.erase(0, line.find_first_not_of(" \t\r\n"));
line.erase(line.find_last_not_of(" \t\r\n") + 1);
2026-01-22 15:54:23 +08:00
LOG_INFO("[serial_at][RAW] " + line);
// IMEI14~17 位纯数字
2026-01-04 17:18:24 +08:00
if (line.size() >= 14 && line.size() <= 17 && std::all_of(line.begin(), line.end(), ::isdigit))
{
2026-01-05 10:24:18 +08:00
{
std::lock_guard<std::mutex> lock(imei_mutex);
IMEI = line;
}
LOG_INFO("[serial_at] IMEI = " + line);
2026-01-04 17:18:24 +08:00
2026-01-22 15:37:26 +08:00
{
std::lock_guard<std::mutex> lock(at_tasks_mutex);
// 保留非 IMEI 的任务
at_tasks.erase(
std::remove_if(at_tasks.begin(), at_tasks.end(), [](const AtTask& t) { return t.cmd == "AT+GSN"; }),
at_tasks.end());
}
2026-01-04 17:18:24 +08:00
return;
}
2026-01-22 15:37:26 +08:00
// =======================
// QENG="servingcell" 解析
// =======================
2026-01-22 15:56:37 +08:00
if (line.rfind("+QENG:", 0) == 0)
2026-01-22 15:37:26 +08:00
{
2026-01-22 15:47:17 +08:00
parse_qeng_servingcell(line);
continue;
2026-01-22 15:37:26 +08:00
}
2026-01-04 17:18:24 +08:00
}
}
// ================== 初始化 ==================
2026-01-04 17:18:24 +08:00
void init_serial_at(const std::string& device, int baudrate)
{
if (serial_at_running.load())
{
LOG_WARN("[serial_at] Already running");
return;
}
serial_at_running.store(true, std::memory_order_relaxed);
2026-01-04 17:18:24 +08:00
serial_at = std::make_unique<SerialPort>("serial_at", device, baudrate, 5);
2026-01-04 17:18:24 +08:00
serial_at->set_receive_callback(handle_serial_at_data);
serial_at->start();
serial_at_sender = std::thread(serial_at_send_loop);
}
// ================== 停止 ==================
void stop_serial_at()
{
if (!serial_at_running.load()) return;
LOG_INFO("[serial_at] Stopping...");
serial_at_running.store(false, std::memory_order_relaxed);
if (serial_at)
{
serial_at->stop(); // 关闭串口,解除阻塞
}
if (serial_at_sender.joinable())
{
serial_at_sender.join();
}
serial_at.reset();
LOG_INFO("[serial_at] Stopped cleanly");
2026-01-04 17:18:24 +08:00
}