bonus-edge-proxy/src/light_control/light_controller.cc

184 lines
5.7 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 "light_controller.hpp"
#include <iostream>
#include <thread>
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> q_lock(queue_mutex_);
// 存入 map。如果 map 里已经有这个设备的指令,直接覆盖!
// 这就完美解决了“短时间反复开关”的问题,只执行最后一次。
pending_actions_[deviceId] = action;
}
cv_.notify_one(); // 唤醒工作线程
// --- 关键修改结束 ---
}
}
// [核心] 会话清理:摄像头掉线时自动撤票
void LightController::RemoveSession(std::string sourceId) {
std::lock_guard<std::mutex> lock(vote_mutex_);
// ... (逻辑同前,只是最后发指令改成扔进队列) ...
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();
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<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; // 退出线程
}
// 取出一个任务
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));
}
}