2025-09-08 15:09:33 +08:00
|
|
|
|
// mqtt_client_wrapper.cpp
|
2025-09-08 10:59:08 +08:00
|
|
|
|
#include "mqtt_client_wrapper.hpp"
|
2025-10-15 15:01:55 +08:00
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
2025-09-10 10:34:36 +08:00
|
|
|
|
#include <chrono>
|
2025-10-15 17:01:43 +08:00
|
|
|
|
#include <condition_variable>
|
|
|
|
|
|
#include <mutex>
|
2025-09-10 10:34:36 +08:00
|
|
|
|
#include <nlohmann/json.hpp>
|
2025-10-15 17:01:43 +08:00
|
|
|
|
#include <queue>
|
2025-10-15 15:01:55 +08:00
|
|
|
|
#include <thread>
|
2025-09-08 10:59:08 +08:00
|
|
|
|
|
2025-11-14 15:40:14 +08:00
|
|
|
|
#include "record_manager.hpp"
|
|
|
|
|
|
|
2025-09-08 10:59:08 +08:00
|
|
|
|
std::shared_ptr<MQTTClient> mqtt_client;
|
2025-11-14 15:40:14 +08:00
|
|
|
|
|
2025-09-08 15:45:22 +08:00
|
|
|
|
extern std::atomic<bool> g_running;
|
2025-09-10 10:34:36 +08:00
|
|
|
|
std::atomic<bool> g_streaming{false};
|
2025-09-09 16:10:56 +08:00
|
|
|
|
std::string g_dispatch_id;
|
|
|
|
|
|
std::mutex g_dispatch_id_mutex;
|
|
|
|
|
|
|
2025-09-09 16:04:58 +08:00
|
|
|
|
static void send_heartbeat()
|
|
|
|
|
|
{
|
2025-10-15 15:01:55 +08:00
|
|
|
|
if (!mqtt_client || !mqtt_client->isConnected()) return;
|
2025-09-09 16:04:58 +08:00
|
|
|
|
|
2025-10-16 13:19:58 +08:00
|
|
|
|
// 获取当前时间戳(13位毫秒)
|
|
|
|
|
|
auto now = std::chrono::system_clock::now();
|
|
|
|
|
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
2025-09-09 16:45:54 +08:00
|
|
|
|
|
2025-10-17 15:52:47 +08:00
|
|
|
|
// 获取 RTMP 状态
|
|
|
|
|
|
auto channels_info = RTMPManager::get_all_channels_status();
|
2025-10-17 15:26:08 +08:00
|
|
|
|
|
|
|
|
|
|
nlohmann::json channels = nlohmann::json::array();
|
2025-10-17 15:52:47 +08:00
|
|
|
|
int total = static_cast<int>(channels_info.size());
|
|
|
|
|
|
int running_count = 0;
|
2025-10-17 15:26:08 +08:00
|
|
|
|
|
2025-11-13 13:11:24 +08:00
|
|
|
|
for (const auto& ch : channels_info)
|
2025-10-17 15:26:08 +08:00
|
|
|
|
{
|
|
|
|
|
|
nlohmann::json item;
|
2025-10-17 15:52:47 +08:00
|
|
|
|
item["loc"] = ch.loc;
|
|
|
|
|
|
item["running"] = ch.running;
|
|
|
|
|
|
|
|
|
|
|
|
if (ch.running)
|
|
|
|
|
|
{
|
2025-11-13 13:11:24 +08:00
|
|
|
|
item["reason"] = nullptr;
|
2025-10-17 15:52:47 +08:00
|
|
|
|
running_count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
item["reason"] = ch.reason.empty() ? "Unknown error" : ch.reason;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 15:26:08 +08:00
|
|
|
|
channels.push_back(item);
|
|
|
|
|
|
}
|
2025-09-09 16:04:58 +08:00
|
|
|
|
|
2025-10-16 13:19:58 +08:00
|
|
|
|
nlohmann::json hb;
|
|
|
|
|
|
hb["timestamp"] = ms;
|
2025-10-17 15:52:47 +08:00
|
|
|
|
hb["status"] = (running_count == 0) ? 0 // 全部失败
|
|
|
|
|
|
: (running_count == total ? 1 : 2); // 全部正常 or 部分异常
|
2025-10-17 15:26:08 +08:00
|
|
|
|
hb["channels"] = channels;
|
2025-09-09 16:04:58 +08:00
|
|
|
|
|
2025-10-17 15:52:47 +08:00
|
|
|
|
// 发布心跳
|
|
|
|
|
|
mqtt_client->publish(g_app_config.mqtt.topics.heartbeat_up, hb.dump(), 0);
|
2025-11-13 13:21:10 +08:00
|
|
|
|
// LOG_INFO("[MQTT] Sent video heartbeat (" + std::to_string(running_count) + "/" + std::to_string(total) +
|
|
|
|
|
|
// " running): " + hb.dump());
|
2025-09-09 16:04:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 13:21:43 +08:00
|
|
|
|
// MQTT 回调
|
2025-11-13 13:21:10 +08:00
|
|
|
|
static void on_mqtt_connected()
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_INFO("[MQTT] Connected to broker: " + g_app_config.mqtt.server_ip);
|
2025-11-13 13:22:17 +08:00
|
|
|
|
mqtt_client->subscribe(g_app_config.mqtt.topics.video_down);
|
2025-11-14 15:04:12 +08:00
|
|
|
|
mqtt_client->subscribe(g_app_config.mqtt.topics.record_query);
|
|
|
|
|
|
mqtt_client->subscribe(g_app_config.mqtt.topics.record_play);
|
2025-11-13 13:21:10 +08:00
|
|
|
|
}
|
2025-09-08 10:59:08 +08:00
|
|
|
|
|
2025-10-15 15:01:55 +08:00
|
|
|
|
static void on_mqtt_disconnected() { LOG_WARN("[MQTT] Disconnected from broker: " + g_app_config.mqtt.server_ip); }
|
2025-09-08 10:59:08 +08:00
|
|
|
|
|
2025-11-14 15:13:05 +08:00
|
|
|
|
static void handle_video_down_request(const nlohmann::json& req)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!req.contains("data") || !req["data"].is_object())
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_WARN("[video_down] Missing data");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int sw = req["data"].value("switch", 0);
|
|
|
|
|
|
if (sw != 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_INFO("[video_down] switch != 1, ignore");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取得当前所有 RTMP 播放地址
|
|
|
|
|
|
auto channels_info = RTMPManager::get_all_channels_status();
|
|
|
|
|
|
|
|
|
|
|
|
// 构造响应
|
|
|
|
|
|
nlohmann::json resp;
|
|
|
|
|
|
resp["type"] = "response";
|
|
|
|
|
|
resp["seqNo"] = req.value("seqNo", "0");
|
|
|
|
|
|
resp["data"] = nlohmann::json::array();
|
|
|
|
|
|
|
|
|
|
|
|
int loc = 0;
|
|
|
|
|
|
for (const auto& ch : channels_info)
|
|
|
|
|
|
{
|
|
|
|
|
|
resp["data"].push_back({{"loc", loc}, {"url", ch.url}});
|
|
|
|
|
|
loc++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发布回复
|
|
|
|
|
|
mqtt_client->publish(g_app_config.mqtt.topics.video_down, resp.dump(-1), 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void handle_record_query_request(const nlohmann::json& req) {}
|
|
|
|
|
|
|
|
|
|
|
|
static void handle_record_play_request(const nlohmann::json& req) {}
|
|
|
|
|
|
|
2025-11-13 13:11:24 +08:00
|
|
|
|
static void on_mqtt_message_received(const std::string& topic, const std::string& message)
|
2025-09-08 10:59:08 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-10-14 17:13:08 +08:00
|
|
|
|
auto j = nlohmann::json::parse(message);
|
|
|
|
|
|
|
2025-11-14 15:13:05 +08:00
|
|
|
|
// 必须是 request
|
|
|
|
|
|
if (!j.contains("type") || j["type"] != "request")
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_INFO("[MQTT] Ignored non-request message");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("[MQTT] Received request on [" + topic + "]");
|
|
|
|
|
|
LOG_INFO("[MQTT] Payload: " + j.dump(-1));
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 topic 分发
|
|
|
|
|
|
if (topic == g_app_config.mqtt.topics.video_down)
|
|
|
|
|
|
{
|
|
|
|
|
|
handle_video_down_request(j);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (topic == g_app_config.mqtt.topics.record_query)
|
|
|
|
|
|
{
|
|
|
|
|
|
handle_record_query_request(j);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (topic == g_app_config.mqtt.topics.record_play)
|
2025-11-13 13:41:24 +08:00
|
|
|
|
{
|
2025-11-14 15:13:05 +08:00
|
|
|
|
handle_record_play_request(j);
|
2025-11-13 13:41:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-11-14 15:13:05 +08:00
|
|
|
|
LOG_WARN("[MQTT] Unknown topic: " + topic);
|
2025-11-13 13:41:24 +08:00
|
|
|
|
}
|
2025-10-14 17:40:48 +08:00
|
|
|
|
}
|
2025-11-13 13:11:24 +08:00
|
|
|
|
catch (const std::exception& e)
|
2025-10-14 17:40:48 +08:00
|
|
|
|
{
|
|
|
|
|
|
LOG_ERROR(std::string("[MQTT] Failed to process incoming JSON: ") + e.what());
|
2025-09-08 10:59:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void mqtt_client_thread_func()
|
|
|
|
|
|
{
|
2025-11-13 13:11:24 +08:00
|
|
|
|
const auto& cfg = g_app_config.mqtt;
|
2025-09-09 16:32:28 +08:00
|
|
|
|
auto heartbeat_interval = std::chrono::milliseconds(static_cast<int>(cfg.keep_alive * 0.9));
|
2025-09-08 15:09:33 +08:00
|
|
|
|
|
2025-09-08 15:45:22 +08:00
|
|
|
|
while (g_running)
|
|
|
|
|
|
{
|
2025-09-08 10:59:08 +08:00
|
|
|
|
mqtt_client = std::make_unique<MQTTClient>(cfg);
|
|
|
|
|
|
mqtt_client->setConnectCallback(on_mqtt_connected);
|
|
|
|
|
|
mqtt_client->setDisconnectCallback(on_mqtt_disconnected);
|
|
|
|
|
|
mqtt_client->setMessageCallback(on_mqtt_message_received);
|
|
|
|
|
|
|
2025-09-10 13:33:29 +08:00
|
|
|
|
// 等待连接
|
2025-09-10 10:34:36 +08:00
|
|
|
|
while (g_running && !mqtt_client->isConnected())
|
|
|
|
|
|
{
|
2025-09-10 13:33:29 +08:00
|
|
|
|
mqtt_client->connect();
|
2025-09-10 11:06:42 +08:00
|
|
|
|
for (int i = 0; i < 10 && g_running && !mqtt_client->isConnected(); i++)
|
2025-09-10 13:21:43 +08:00
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
|
|
|
|
|
2025-10-15 15:01:55 +08:00
|
|
|
|
if (!g_running && !mqtt_client->isConnected()) mqtt_client->force_disconnect();
|
2025-09-10 10:34:36 +08:00
|
|
|
|
}
|
2025-09-08 10:59:08 +08:00
|
|
|
|
|
2025-09-10 13:33:29 +08:00
|
|
|
|
// 主循环:心跳
|
2025-09-10 10:34:36 +08:00
|
|
|
|
while (g_running && mqtt_client->isConnected())
|
2025-09-08 10:59:08 +08:00
|
|
|
|
{
|
2025-09-10 13:33:29 +08:00
|
|
|
|
send_heartbeat();
|
2025-09-10 13:21:43 +08:00
|
|
|
|
|
2025-09-10 11:06:42 +08:00
|
|
|
|
auto sleep_time = heartbeat_interval;
|
|
|
|
|
|
while (sleep_time.count() > 0 && g_running && mqtt_client->isConnected())
|
|
|
|
|
|
{
|
2025-09-10 13:21:43 +08:00
|
|
|
|
auto chunk = std::min(sleep_time, std::chrono::milliseconds(50));
|
2025-09-10 11:06:42 +08:00
|
|
|
|
std::this_thread::sleep_for(chunk);
|
|
|
|
|
|
sleep_time -= chunk;
|
|
|
|
|
|
}
|
2025-09-10 13:21:43 +08:00
|
|
|
|
|
2025-10-15 15:01:55 +08:00
|
|
|
|
if (!g_running && mqtt_client->isConnected()) mqtt_client->force_disconnect();
|
2025-09-08 10:59:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 13:33:29 +08:00
|
|
|
|
// 清理
|
2025-09-10 11:06:42 +08:00
|
|
|
|
if (mqtt_client)
|
|
|
|
|
|
{
|
2025-09-10 11:18:29 +08:00
|
|
|
|
if (g_running)
|
|
|
|
|
|
mqtt_client->disconnect();
|
|
|
|
|
|
else
|
|
|
|
|
|
mqtt_client->force_disconnect();
|
2025-09-10 11:06:42 +08:00
|
|
|
|
mqtt_client.reset();
|
|
|
|
|
|
}
|
2025-09-10 11:18:29 +08:00
|
|
|
|
|
2025-10-15 15:01:55 +08:00
|
|
|
|
if (!g_running) break;
|
2025-09-10 11:18:29 +08:00
|
|
|
|
|
2025-09-10 13:33:29 +08:00
|
|
|
|
// 短暂等待再重连
|
2025-10-15 15:01:55 +08:00
|
|
|
|
for (int i = 0; i < 5 && g_running; i++) std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
2025-09-08 10:59:08 +08:00
|
|
|
|
}
|
2025-09-08 15:45:22 +08:00
|
|
|
|
LOG_INFO("[MQTT] Client thread exiting.");
|
2025-09-08 10:59:08 +08:00
|
|
|
|
}
|