#include "serial_AT.hpp" #include #include #include #include #include #include #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 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 serial_at; static std::thread serial_at_sender; static std::mutex at_tasks_mutex; static std::unique_ptr 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 split_csv(const std::string& line) { std::vector 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, , 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 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 lock(imei_mutex); return !IMEI.empty(); } static std::string get_imei() { std::lock_guard lock(imei_mutex); return IMEI; } void start_http_server(int port) { http_server = std::make_unique(); 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 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 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(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); LOG_INFO("[serial_at][RAW] " + line); // IMEI:14~17 位纯数字 if (line.size() >= 14 && line.size() <= 17 && std::all_of(line.begin(), line.end(), ::isdigit)) { { std::lock_guard lock(imei_mutex); IMEI = line; } LOG_INFO("[serial_at] IMEI = " + line); { std::lock_guard 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.rfind("+QENG:", 0) == 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("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"); }