#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; 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); // 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 || line.find("servingcell") != std::string::npos) { std::vector tokens; std::string tmp; std::stringstream ss(line); while (std::getline(ss, tmp, ',')) { // 去除双引号和空白 tmp.erase(std::remove(tmp.begin(), tmp.end(), '"'), tmp.end()); tmp.erase(0, tmp.find_first_not_of(" \t")); tmp.erase(tmp.find_last_not_of(" \t") + 1); tokens.push_back(tmp); } // LTE 格式固定至少 15~17 字段 if (tokens.size() >= 15) { // 对号入座 int pci = std::stoi(tokens[6]); // 物理小区编号 int band = std::stoi(tokens[8]); // LTE band int rsrp = std::stoi(tokens[11]); // dBm int rsrq = std::stoi(tokens[12]); // dB int sinr = std::stoi(tokens[14]); // dB { std::lock_guard lk(radio_mutex); g_radio_info.pci = pci; g_radio_info.band = band; g_radio_info.rsrp = rsrp; g_radio_info.rsrq = rsrq; g_radio_info.sinr = sinr; g_radio_info.raw = line; // 保留原始字符串用于调试 } LOG_INFO("[serial_at] QENG parsed: RSRP=" + std::to_string(rsrp) + " RSRQ=" + std::to_string(rsrq) + " SINR=" + std::to_string(sinr) + " PCI=" + std::to_string(pci) + " BAND=" + std::to_string(band)); 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"); }