2026-01-04 17:29:49 +08:00
|
|
|
|
#include "serial_AT.hpp"
|
|
|
|
|
|
|
2026-01-04 17:18:24 +08:00
|
|
|
|
#include <algorithm>
|
2026-01-04 17:42:16 +08:00
|
|
|
|
#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"
|
2026-01-04 17:42:16 +08:00
|
|
|
|
#include "logger.hpp"
|
2026-01-05 10:31:54 +08:00
|
|
|
|
#include "nlohmann/json.hpp"
|
2026-01-04 17:42:16 +08:00
|
|
|
|
#include "serial_port.h"
|
|
|
|
|
|
|
2026-01-04 17:18:24 +08:00
|
|
|
|
// ================== 全局 IMEI ==================
|
2026-01-04 17:42:16 +08:00
|
|
|
|
std::string IMEI;
|
2026-01-05 10:24:18 +08:00
|
|
|
|
std::mutex imei_mutex;
|
2026-01-04 17:42:16 +08:00
|
|
|
|
|
2026-01-22 15:37:26 +08:00
|
|
|
|
RadioInfo g_radio_info;
|
|
|
|
|
|
static std::mutex radio_mutex;
|
|
|
|
|
|
|
2026-01-04 17:42:16 +08:00
|
|
|
|
// ================== 运行控制 ==================
|
|
|
|
|
|
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-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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-05 14:46:19 +08:00
|
|
|
|
// ---------- 2. 请求体当前不承载身份(忽略即可) ----------
|
2026-01-05 13:46:03 +08:00
|
|
|
|
if (!req.body.empty())
|
|
|
|
|
|
{
|
2026-01-05 14:46:19 +08:00
|
|
|
|
LOG_INFO("[http] register payload ignored");
|
2026-01-05 13:46:03 +08:00
|
|
|
|
}
|
2026-01-05 10:24:18 +08:00
|
|
|
|
|
2026-01-05 14:46:19 +08:00
|
|
|
|
// ---------- 3. 返回 B 的身份(唯一事实源) ----------
|
2026-01-05 13:46:03 +08:00
|
|
|
|
nlohmann::json jres;
|
|
|
|
|
|
jres["ok"] = true;
|
2026-01-05 14:46:19 +08:00
|
|
|
|
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()
|
|
|
|
|
|
{
|
2026-01-04 17:42:16 +08:00
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
{
|
2026-01-04 17:42:16 +08:00
|
|
|
|
std::lock_guard<std::mutex> lock(at_tasks_mutex);
|
2026-01-04 17:24:15 +08:00
|
|
|
|
|
2026-01-04 17:42:16 +08:00
|
|
|
|
for (auto it = at_tasks.begin(); it != at_tasks.end();)
|
2026-01-04 17:24:15 +08:00
|
|
|
|
{
|
2026-01-04 17:42:16 +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)
|
2026-01-04 17:42:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
it = at_tasks.erase(it);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 15:37:26 +08:00
|
|
|
|
// 5 秒一次发送
|
2026-01-04 17:42:16 +08:00
|
|
|
|
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));
|
|
|
|
|
|
}
|
2026-01-04 17:42:16 +08:00
|
|
|
|
|
|
|
|
|
|
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-04 17:42:16 +08:00
|
|
|
|
// IMEI:14~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" 解析
|
|
|
|
|
|
// =======================
|
|
|
|
|
|
if (line.rfind("+QENG:", 0) == 0 || line.find("servingcell") != std::string::npos)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::vector<std::string> 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<std::mutex> 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-04 17:18:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-04 17:42:16 +08:00
|
|
|
|
// ================== 初始化 ==================
|
2026-01-04 17:18:24 +08:00
|
|
|
|
void init_serial_at(const std::string& device, int baudrate)
|
|
|
|
|
|
{
|
2026-01-04 17:42:16 +08:00
|
|
|
|
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
|
|
|
|
|
2026-01-04 17:42:16 +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);
|
2026-01-04 17:42:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================== 停止 ==================
|
|
|
|
|
|
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
|
|
|
|
}
|