告警手动清除,告警api端点开发

This commit is contained in:
GuanYuankai 2025-11-04 05:46:53 +00:00
parent cff2c9fae7
commit f69a389353
6 changed files with 762 additions and 449 deletions

View File

@ -1,46 +1,58 @@
[
{
"rule_id": "RTU_TEMP_HIGH_CRITICAL",
"device_id": "rtu_temp_sensor_lab",
"data_point_name": "temperature",
"compare_type": "GT",
"threshold": 30.0,
"level": "CRITICAL",
"device_id": "rtu_temp_sensor_lab",
"data_point_name": "temperature",
"compare_type": "GT",
"threshold": 30.0,
"level": "CRITICAL",
"debounce_seconds": 60,
"alarm_mqtt_topic": "alarms/rtu_lab",
"alarm_mqtt_topic": "alarms/rtu_lab",
"message_template": "RTU温度传感器'{device_id}' 检测到温度过高 ({value}°C),已达临界值 {threshold}°C!",
"clear_message_template": "RTU温度传感器'{device_id}' 温度已恢复正常 ({value}°C)。"
"clear_message_template": "RTU温度传感器'{device_id}' 温度已恢复正常 ({value}°C)。"
},
{
"rule_id": "TEST_TEMP_GT_10_WARNING",
"device_id": "rtu_temp_sensor_lab",
"data_point_name": "temperature",
"compare_type": "GT",
"threshold": 10.0,
"level": "CRITICAL",
"debounce_seconds": 0,
"alarm_mqtt_topic": "alarms/rtu_lab",
"message_template": "测试告警 设备 '{device_id}' 温度 ({value}°C) 高于测试阈值 {threshold}°C!",
"clear_message_template": "【测试告警解除】设备 '{device_id}' 温度 ({value}°C) 已恢复正常。"
},
{
"rule_id": "ANY_TEMP_CRITICAL",
"device_id": "*",
"device_id": "*",
"data_point_name": "temperature",
"compare_type": "GT",
"compare_type": "GT",
"threshold": 40.0,
"level": "CRITICAL",
"alarm_mqtt_topic": "alarms/critical",
"alarm_mqtt_topic": "alarms/critical",
"message_template": "【全局报警】任意设备 '{device_id}' 的温度 ({value}°C) 达到或超过 {threshold}°C。",
"clear_message_template": "【全局报警解除】设备 '{device_id}' 的温度 ({value}°C) 已恢复。"
},
{
"rule_id": "SYSTEM_CPU_HIGH",
"device_id": "proxy_system",
"data_point_name": "cpu_usage",
"compare_type": "GT",
"threshold": 90.0,
"level": "WARNING",
"debounce_seconds": 30,
"device_id": "proxy_system",
"data_point_name": "cpu_usage",
"compare_type": "GT",
"threshold": 90.0,
"level": "WARNING",
"debounce_seconds": 30,
"message_template": "代理系统 CPU 利用率 ({value}%) 超过 {threshold}%,请检查!",
"clear_message_template": "代理系统 CPU 利用率已恢复 ({value}%)。"
},
{
"rule_id": "SYSTEM_MEM_HIGH",
"device_id": "proxy_system",
"data_point_name": "mem_usage_percentage",
"compare_type": "GT",
"threshold": 80.0,
"level": "WARNING",
"debounce_seconds": 30,
"device_id": "proxy_system",
"data_point_name": "mem_usage_percentage",
"compare_type": "GT",
"threshold": 80.0,
"level": "WARNING",
"debounce_seconds": 30,
"message_template": "代理系统内存使用率 ({value}%) 超过 {threshold}%,请检查!",
"clear_message_template": "代理系统内存使用率已恢复 ({value}%)。"
}

View File

@ -23,7 +23,7 @@
]
},
{
"enabled": true,
"enabled": false,
"device_id": "rotary encoder",
"port_path": "/dev/ttyS7",
"baud_rate": 9600,

View File

@ -1,10 +1,11 @@
// alarm/alarm_service.cpp
#include "alarm_service.h"
#include "dataStorage/data_storage.h"
#include <chrono>
#include <fstream>
#include "dataStorage/data_storage.h"
AlarmService::AlarmService(boost::asio::io_context& io,
PiperTTSInterface& tts_service,
MqttClient& mqtt_client)
@ -270,7 +271,7 @@ void AlarmService::trigger_alarm_action(AlarmRule& rule, double value,
.count();
// 1. 语音播报 (加入队列)
schedule_tts(message);
// schedule_tts(message);
// 2. 发布到MQTT
std::string topic =
@ -429,5 +430,51 @@ bool AlarmService::parse_rules_from_file(
return false;
}
return true;
}
bool AlarmService::manually_clear_alarm(const std::string& rule_id,
const std::string& device_id) {
auto it = m_alarm_states.find(rule_id);
if (it == m_alarm_states.end()) {
spdlog::warn(
"Cannot manually clear alarm: Rule '{}' not found in state map.",
rule_id);
return false;
}
AlarmState& state = it->second;
state.current_state = AlarmStateType::NORMAL;
state.debounce_timer.cancel(); // 确保计时器被取消
spdlog::info("[ALARM MANUALLY CLEARED] (Rule: {}) for Device: {}", rule_id,
device_id);
// 2. 写入数据库日志
AlarmEvent event;
event.rule_id = rule_id;
event.device_id = device_id;
event.status = AlarmEventStatus::CLEARED; // 标记为 CLEARED
event.message = "Alarm manually cleared by user.";
event.trigger_value = 0.0;
event.timestamp_ms = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
for (const auto& rule : m_rules) {
if (rule.rule_id == rule_id) {
event.level = AlarmRule::levelToString(rule.level);
break;
}
}
if (event.level.empty()) {
event.level = "INFO";
}
if (!DataStorage::getInstance().storeAlarmEvent(event)) {
spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}",
rule_id);
return false;
}
return true;
}

View File

@ -15,7 +15,7 @@
#include "alarm_event.h"
#include "mqtt/mqtt_client.h"
#include "spdlog/spdlog.h"
#include "tts/piper_tts_interface.h"
#include "tts/piper_tts_interface.h"
using json = nlohmann::json;
@ -39,6 +39,15 @@ class AlarmService {
nlohmann::json getActiveAlarmsJson();
nlohmann::json getAlarmHistoryJson(int limit);
/**
* @brief []
* @param rule_id ID
* @param device_id ID
* @return true
*/
bool manually_clear_alarm(const std::string& rule_id,
const std::string& device_id);
private:
void check_rule_against_value(AlarmRule& rule, double value,
const std::string& actual_device_id);
@ -62,8 +71,8 @@ class AlarmService {
std::vector<AlarmRule> m_rules;
std::map<std::string, AlarmState> m_alarm_states;
std::string m_rules_config_path;
std::mutex m_rules_mutex;
std::string m_rules_config_path;
std::mutex m_rules_mutex;
// TTS 播报队列
std::mutex m_tts_queue_mutex;

File diff suppressed because it is too large Load Diff

View File

@ -154,4 +154,69 @@ void WebServer::setup_routes() {
return res;
}
});
CROW_ROUTE((*this), "/api/alarms/reload").methods("POST"_method)([this]() {
spdlog::info("Web API: Received request to reload alarm rules...");
bool success = m_alarm_service.reload_rules();
if (success) {
crow::json::wvalue response_json;
response_json["status"] = "success";
response_json["message"] = "Alarm rules reloaded successfully.";
auto res = crow::response(200, response_json.dump());
res.set_header("Content-Type", "application/json");
return res;
} else {
crow::json::wvalue error_json;
error_json["status"] = "error";
error_json["message"] =
"Failed to reload alarm rules. Check service logs for details.";
auto res = crow::response(500, error_json.dump());
res.set_header("Content-Type", "application/json");
return res;
}
});
CROW_ROUTE((*this), "/api/alarms/clear")
.methods("POST"_method)([this](const crow::request& req) {
crow::json::rvalue j_body;
try {
j_body = crow::json::load(req.body);
} catch (const std::exception& e) {
spdlog::warn("Failed to parse request body for /api/alarms/clear: {}",
e.what());
auto res = crow::response(400, "{\"error\":\"Invalid JSON body.\"}");
res.set_header("Content-Type", "application/json");
return res;
}
if (!j_body.has("rule_id") || !j_body.has("device_id")) {
auto res = crow::response(
400, "{\"error\":\"Missing 'rule_id' or 'device_id'.\"}");
res.set_header("Content-Type", "application/json");
return res;
}
std::string rule_id = j_body["rule_id"].s();
std::string device_id = j_body["device_id"].s();
bool success = m_alarm_service.manually_clear_alarm(rule_id, device_id);
if (success) {
auto res = crow::response(
200, "{\"status\":\"success\", \"message\":\"Alarm cleared.\"}");
res.set_header("Content-Type", "application/json");
return res;
} else {
auto res =
crow::response(404,
"{\"status\":\"error\", \"message\":\"Alarm not "
"found or not active.\"}");
res.set_header("Content-Type", "application/json");
return res;
}
});
}