#include "light_controller.hpp" #include #include #include "spdlog/spdlog.h" LightController::LightController(std::string baseUrl, std::string token) : baseUrl(baseUrl), token(token) { // 启动后台工作线程 running_ = true; InitializeDevices(); worker_thread_ = std::thread(&LightController::WorkerLoop, this); spdlog::info("[LightController] Worker thread started."); } LightController::~LightController() { // 优雅停止线程 { std::lock_guard lock(queue_mutex_); running_ = false; } cv_.notify_all(); // 唤醒线程让它退出 if (worker_thread_.joinable()) { worker_thread_.join(); } } size_t LightController::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { ((std::string*)userp)->append((char*)contents, size * nmemb); return size * nmemb; } // 底层 HTTP 发送逻辑 bool LightController::SendHttpCommand(std::string deviceId, std::string switchType) { CURL* curl; CURLcode res; std::string readBuffer; long httpCode = 0; bool success = false; spdlog::info("Send Http Command:{}|{} ", deviceId, switchType); curl = curl_easy_init(); if (curl) { spdlog::info("Start Send"); std::string url = this->baseUrl + "/hk_service/modbus/lightControl"; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_POST, 1L); struct curl_slist* headers = NULL; std::string tokenHeader = "token: " + this->token; headers = curl_slist_append(headers, tokenHeader.c_str()); headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); std::string postData = "deviceId=" + deviceId + "&type=" + switchType; curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); res = curl_easy_perform(curl); if (res != CURLE_OK) { spdlog::error("[LightControl] Request failed: {}", curl_easy_strerror(res)); } else { curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); if (httpCode == 200) success = true; } curl_slist_free_all(headers); curl_easy_cleanup(curl); } return success; } // 手动控制:直接执行,并清空该设备的投票箱(打断自动逻辑) // 手动控制:强制覆盖,并清空该设备的投票箱 bool LightController::ControlLight(std::string deviceId, std::string switchType) { std::lock_guard lock(vote_mutex_); light_votes_.erase(deviceId); // 清除自动控制的状态,避免干扰 // 这里直接同步发送,或者你也可以扔进队列 // 为了手动响应速度,直接发是OK的,因为手动点击频率低 return SendHttpCommand(deviceId, switchType); } // [核心] 投票逻辑 void LightController::UpdateAutoLight(std::string deviceId, std::string sourceId, bool voteOn) { std::lock_guard lock(vote_mutex_); auto& votes = light_votes_[deviceId]; bool was_on = !votes.empty(); if (voteOn) { votes.insert(sourceId); } else { votes.erase(sourceId); } bool is_on = !votes.empty(); // 只有状态改变才触发 if (was_on != is_on || deviceStatus[deviceId]) { if (deviceStatus[deviceId]) { spdlog::info("[AutoLight] ForceControll."); } deviceStatus[deviceId] = false; std::string action = is_on ? "1" : "0"; spdlog::info("[AutoLight] Logic Change: Device {} -> {}", deviceId, is_on ? "ON" : "OFF"); // --- 关键修改开始 --- { std::lock_guard q_lock(queue_mutex_); // 存入 map。如果 map 里已经有这个设备的指令,直接覆盖! // 这就完美解决了“短时间反复开关”的问题,只执行最后一次。 pending_actions_[deviceId] = action; } cv_.notify_one(); // 唤醒工作线程 // --- 关键修改结束 --- } } // [核心] 会话清理:摄像头掉线时自动撤票 void LightController::RemoveSession(std::string sourceId) { std::lock_guard lock(vote_mutex_); // ... (逻辑同前,只是最后发指令改成扔进队列) ... for (auto& pair : light_votes_) { std::string deviceId = pair.first; std::set& votes = pair.second; if (votes.count(sourceId)) { bool was_on = !votes.empty(); votes.erase(sourceId); bool is_on = !votes.empty(); if ((was_on && !is_on) || deviceStatus[deviceId]) { if (deviceStatus[deviceId]) { spdlog::info("[AutoLight] ForceControll."); } deviceStatus[deviceId] = false; spdlog::info("[AutoLight] Source {} disconnect. Turning OFF {}.", sourceId, deviceId); { std::lock_guard q_lock(queue_mutex_); pending_actions_[deviceId] = "0"; } cv_.notify_one(); } } } } // [新增] 核心工作线程:串行处理网络请求 void LightController::WorkerLoop() { while (true) { std::string target_device; std::string target_action; { std::unique_lock lock(queue_mutex_); // 等待,直到有任务 或者 要求停止 cv_.wait(lock, [this] { return !running_ || !pending_actions_.empty(); }); if (!running_ && pending_actions_.empty()) { break; // 退出线程 } // 取出一个任务 auto it = pending_actions_.begin(); target_device = it->first; target_action = it->second; // 从 map 中移除,表示开始处理 pending_actions_.erase(it); } // 释放锁后再执行耗时的网络操作! // 这样不会阻塞 UpdateAutoLight SendHttpCommand(target_device, target_action); // 可选:这里可以加一个微小的 sleep,比如 50ms // 防止对老旧硬件发起过于密集的连续请求(如果控制多个灯的话) std::this_thread::sleep_for(std::chrono::milliseconds(50)); } }