kunlang_video/src/serial_AT.cpp
2026-01-05 13:48:52 +08:00

252 lines
7.1 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;
// ================== 运行控制 ==================
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;
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. 解析请求体(尝试获取 VID ----------
std::string vid;
if (!req.body.empty())
{
try
{
auto jreq = nlohmann::json::parse(req.body);
if (jreq.contains("vid") && jreq["vid"].is_string())
{
vid = jreq["vid"].get<std::string>();
LOG_INFO("[http] register vid = " + vid);
// ⭐ 运行期 VID只记录不判断
g_app_config.runtime_vid = vid;
}
}
catch (const std::exception& e)
{
LOG_WARN(std::string("[http] invalid json payload: ") + e.what());
}
}
else
{
LOG_WARN("[http] empty register payload");
}
// ---------- 3. 返回 B 的身份 ----------
std::string imei = get_imei();
nlohmann::json jres;
jres["ok"] = true;
jres["server"] = {{"device_type", "neardi"}, {"imei", imei}, {"fw", "b-v2.1.0"}};
// 可选:把 vid 回显(方便 A / 调试)
if (!vid.empty())
{
jres["peer"] = {{"vid", vid}};
}
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();
}
// 只保留一个任务AT+GSN
static std::vector<AtTask> at_tasks = {{"AT+GSN", 3, 0, {}}};
// ================== 发送线程 ==================
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;
if (task.sent_count >= task.max_retries)
{
it = at_tasks.erase(it);
continue;
}
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);
at_tasks.clear();
return;
}
}
}
// ================== 初始化 ==================
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");
}