kunlang_video/src/serial_AT.cpp
2026-01-22 15:47:17 +08:00

371 lines
10 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_AT.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <mutex>
#include <sstream>
#include <thread>
#include "app_config.hpp"
#include "httplib.h"
#include "logger.hpp"
#include "nlohmann/json.hpp"
#include "serial_port.h"
// ================== 全局 IMEI ==================
std::string IMEI;
std::mutex imei_mutex;
RadioInfo g_radio_info;
static std::mutex radio_mutex;
// ================== 运行控制 ==================
static std::atomic<bool> serial_at_running{false};
// ================== AT 任务结构 ==================
struct AtTask
{
std::string cmd;
int max_retries;
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;
static std::unique_ptr<httplib::Server> http_server;
static std::thread http_thread;
// ---------- 安全 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));
}
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;
}
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)
{
LOG_INFO("[http] /api/v1/device/register called");
// ---------- 1. IMEI 是否就绪 ----------
if (!is_imei_ready())
{
res.status = 503;
res.set_content(R"({"ok":false,"error":"imei_not_ready"})", "application/json");
return;
}
// ---------- 2. 请求体当前不承载身份(忽略即可) ----------
if (!req.body.empty())
{
LOG_INFO("[http] register payload ignored");
}
// ---------- 3. 返回 B 的身份(唯一事实源) ----------
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"}};
res.set_content(jres.dump(), "application/json");
});
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();
}
// IMEI 需要无限重试
// 信号 AT 命令周期查询5 秒一次)
// max_retries = -1 → 永不删除
static std::vector<AtTask> at_tasks = {
{"AT+GSN", -1, 0, {}}, // 直到成功为止
{"AT+QENG=\"servingcell\"", -1, 0, {}} // RSRP/RSRQ/SINR 查询(周期)
};
// ================== 发送线程 ==================
static void serial_at_send_loop()
{
LOG_INFO("[serial_at] Sender thread started");
while (serial_at_running.load(std::memory_order_relaxed))
{
if (!serial_at || !serial_at->is_open())
{
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);
for (auto it = at_tasks.begin(); it != at_tasks.end();)
{
auto& task = *it;
// ============================
// FIX: max_retries = -1 → 无限发送
// ============================
if (task.max_retries >= 0 && task.sent_count >= task.max_retries)
{
it = at_tasks.erase(it);
continue;
}
// 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;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
LOG_INFO("[serial_at] Sender thread exiting");
}
// ================== 接收处理 ==================
static void handle_serial_at_data(const std::string& data)
{
std::istringstream iss(data);
std::string line;
while (std::getline(iss, line))
{
// trim
line.erase(0, line.find_first_not_of(" \t\r\n"));
line.erase(line.find_last_not_of(" \t\r\n") + 1);
// IMEI14~17 位纯数字
if (line.size() >= 14 && line.size() <= 17 && std::all_of(line.begin(), line.end(), ::isdigit))
{
{
std::lock_guard<std::mutex> lock(imei_mutex);
IMEI = line;
}
LOG_INFO("[serial_at] IMEI = " + line);
{
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());
}
return;
}
// =======================
// QENG="servingcell" 解析
// =======================
if (line.find("+QENG:") == 0)
{
parse_qeng_servingcell(line);
continue;
}
}
}
// ================== 初始化 ==================
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);
serial_at = std::make_unique<SerialPort>("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);
}
// ================== 停止 ==================
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");
}