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