告警管理模块
This commit is contained in:
parent
f69a389353
commit
367f3a12a7
|
|
@ -1,10 +1,22 @@
|
|||
[
|
||||
{
|
||||
"rule_id": "TeST",
|
||||
"device_id": "rtu_temp_sensor_lab",
|
||||
"data_point_name": "temperature",
|
||||
"compare_type": "EQ",
|
||||
"threshold": 5,
|
||||
"level": "CRITICAL",
|
||||
"debounce_seconds": 0,
|
||||
"message_template": "测试用",
|
||||
"clear_message_template": "测试解除",
|
||||
"alarm_mqtt_topic": "alarms/rtu_test"
|
||||
},
|
||||
{
|
||||
"rule_id": "RTU_TEMP_HIGH_CRITICAL",
|
||||
"device_id": "rtu_temp_sensor_lab",
|
||||
"data_point_name": "temperature",
|
||||
"compare_type": "GT",
|
||||
"threshold": 30.0,
|
||||
"threshold": 30,
|
||||
"level": "CRITICAL",
|
||||
"debounce_seconds": 60,
|
||||
"alarm_mqtt_topic": "alarms/rtu_lab",
|
||||
|
|
@ -16,43 +28,22 @@
|
|||
"device_id": "rtu_temp_sensor_lab",
|
||||
"data_point_name": "temperature",
|
||||
"compare_type": "GT",
|
||||
"threshold": 10.0,
|
||||
"threshold": 10,
|
||||
"level": "CRITICAL",
|
||||
"debounce_seconds": 0,
|
||||
"alarm_mqtt_topic": "alarms/rtu_lab",
|
||||
"alarm_mqtt_topic": "alarms/rtu_temp_sensor_lab",
|
||||
"message_template": "测试告警 设备 '{device_id}' 温度 ({value}°C) 高于测试阈值 {threshold}°C!",
|
||||
"clear_message_template": "【测试告警解除】设备 '{device_id}' 温度 ({value}°C) 已恢复正常。"
|
||||
},
|
||||
{
|
||||
"rule_id": "ANY_TEMP_CRITICAL",
|
||||
"device_id": "*",
|
||||
"data_point_name": "temperature",
|
||||
"compare_type": "GT",
|
||||
"threshold": 40.0,
|
||||
"level": "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,
|
||||
"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,
|
||||
"threshold": 80,
|
||||
"level": "WARNING",
|
||||
"debounce_seconds": 30,
|
||||
"alarm_mqtt_topic": "alarms/proxy",
|
||||
"message_template": "代理系统内存使用率 ({value}%) 超过 {threshold}%,请检查!",
|
||||
"clear_message_template": "代理系统内存使用率已恢复 ({value}%)。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,14 @@
|
|||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "dataStorage/data_storage.h"
|
||||
|
||||
AlarmService::AlarmService(boost::asio::io_context& io,
|
||||
PiperTTSInterface& tts_service,
|
||||
MqttClient& mqtt_client)
|
||||
: m_io_context(io),
|
||||
m_tts_service(tts_service),
|
||||
m_mqtt_client(mqtt_client),
|
||||
AlarmService::AlarmService(boost::asio::io_context &io,
|
||||
PiperTTSInterface &tts_service,
|
||||
MqttClient &mqtt_client)
|
||||
: m_io_context(io), m_tts_service(tts_service), m_mqtt_client(mqtt_client),
|
||||
m_tts_running(true) {
|
||||
m_tts_thread = std::thread(&AlarmService::tts_worker, this);
|
||||
spdlog::info("AlarmService created and TTS worker thread started.");
|
||||
|
|
@ -32,10 +31,10 @@ void AlarmService::stop() {
|
|||
std::lock_guard<std::mutex> lock(m_tts_queue_mutex);
|
||||
m_tts_running = false;
|
||||
}
|
||||
m_tts_cv.notify_one(); // 唤醒工作线程使其退出
|
||||
m_tts_cv.notify_one(); // 唤醒工作线程使其退出
|
||||
}
|
||||
|
||||
bool AlarmService::load_rules(const std::string& config_path) {
|
||||
bool AlarmService::load_rules(const std::string &config_path) {
|
||||
m_rules_config_path = config_path;
|
||||
|
||||
std::vector<AlarmRule> new_rules;
|
||||
|
|
@ -88,14 +87,15 @@ bool AlarmService::reload_rules() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void AlarmService::process_device_data(const std::string& device_id,
|
||||
const std::string& data_json) {
|
||||
void AlarmService::process_device_data(const std::string &device_id,
|
||||
const std::string &data_json) {
|
||||
std::lock_guard<std::mutex> lock(m_rules_mutex);
|
||||
try {
|
||||
json j_data = json::parse(data_json);
|
||||
if (!j_data.is_object()) return;
|
||||
if (!j_data.is_object())
|
||||
return;
|
||||
|
||||
for (auto& rule : m_rules) {
|
||||
for (auto &rule : m_rules) {
|
||||
if (rule.device_id == device_id || rule.device_id == "*") {
|
||||
if (j_data.contains(rule.data_point_name)) {
|
||||
if (j_data[rule.data_point_name].is_number()) {
|
||||
|
|
@ -105,20 +105,21 @@ void AlarmService::process_device_data(const std::string& device_id,
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (const json::exception& e) {
|
||||
} catch (const json::exception &e) {
|
||||
spdlog::error("Failed to parse device data for alarm: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmService::process_system_data(const std::string& system_data_json) {
|
||||
void AlarmService::process_system_data(const std::string &system_data_json) {
|
||||
std::lock_guard<std::mutex> lock(m_rules_mutex);
|
||||
try {
|
||||
json j_data = json::parse(system_data_json);
|
||||
if (!j_data.is_object()) return;
|
||||
if (!j_data.is_object())
|
||||
return;
|
||||
|
||||
const std::string device_id = "proxy_system"; // 系统数据的固定ID
|
||||
const std::string device_id = "proxy_system"; // 系统数据的固定ID
|
||||
|
||||
for (auto& rule : m_rules) {
|
||||
for (auto &rule : m_rules) {
|
||||
if (rule.device_id == device_id) {
|
||||
if (j_data.contains(rule.data_point_name)) {
|
||||
if (j_data[rule.data_point_name].is_number()) {
|
||||
|
|
@ -128,26 +129,26 @@ void AlarmService::process_system_data(const std::string& system_data_json) {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (const json::exception& e) {
|
||||
} catch (const json::exception &e) {
|
||||
spdlog::error("Failed to parse system data for alarm: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmService::check_rule_against_value(
|
||||
AlarmRule& rule, double value, const std::string& actual_device_id) {
|
||||
AlarmRule &rule, double value, const std::string &actual_device_id) {
|
||||
bool condition_met = false;
|
||||
switch (rule.compare_type) {
|
||||
case CompareType::GT:
|
||||
condition_met = (value > rule.threshold);
|
||||
break;
|
||||
case CompareType::LT:
|
||||
condition_met = (value < rule.threshold);
|
||||
break;
|
||||
case CompareType::EQ:
|
||||
condition_met = (std::abs(value - rule.threshold) < 1e-9);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case CompareType::GT:
|
||||
condition_met = (value > rule.threshold);
|
||||
break;
|
||||
case CompareType::LT:
|
||||
condition_met = (value < rule.threshold);
|
||||
break;
|
||||
case CompareType::EQ:
|
||||
condition_met = (std::abs(value - rule.threshold) < 1e-9);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = m_alarm_states.find(rule.rule_id);
|
||||
|
|
@ -163,7 +164,7 @@ void AlarmService::check_rule_against_value(
|
|||
it = result.first;
|
||||
}
|
||||
|
||||
AlarmState& state = it->second;
|
||||
AlarmState &state = it->second;
|
||||
|
||||
if (condition_met) {
|
||||
if (state.current_state == AlarmStateType::NORMAL) {
|
||||
|
|
@ -181,8 +182,8 @@ void AlarmService::check_rule_against_value(
|
|||
|
||||
state.debounce_timer.async_wait([this, rule_id, value,
|
||||
actual_device_id](
|
||||
const boost::system::error_code&
|
||||
ec) {
|
||||
const boost::system::error_code
|
||||
&ec) {
|
||||
std::lock_guard<std::mutex> lock(m_rules_mutex);
|
||||
|
||||
if (ec == boost::asio::error::operation_aborted) {
|
||||
|
|
@ -196,10 +197,10 @@ void AlarmService::check_rule_against_value(
|
|||
rule_id);
|
||||
return;
|
||||
}
|
||||
AlarmState& current_state = it->second;
|
||||
AlarmState ¤t_state = it->second;
|
||||
|
||||
AlarmRule* current_rule = nullptr;
|
||||
for (auto& r : m_rules) {
|
||||
AlarmRule *current_rule = nullptr;
|
||||
for (auto &r : m_rules) {
|
||||
if (r.rule_id == rule_id) {
|
||||
current_rule = &r;
|
||||
break;
|
||||
|
|
@ -226,10 +227,10 @@ void AlarmService::check_rule_against_value(
|
|||
}
|
||||
}
|
||||
|
||||
std::string AlarmService::format_message(const AlarmRule& rule,
|
||||
const std::string& template_str,
|
||||
std::string AlarmService::format_message(const AlarmRule &rule,
|
||||
const std::string &template_str,
|
||||
double value,
|
||||
const std::string& actual_device_id) {
|
||||
const std::string &actual_device_id) {
|
||||
std::string msg = template_str;
|
||||
|
||||
size_t pos = msg.find("{device_id}");
|
||||
|
|
@ -253,8 +254,8 @@ std::string AlarmService::format_message(const AlarmRule& rule,
|
|||
return msg;
|
||||
}
|
||||
|
||||
void AlarmService::trigger_alarm_action(AlarmRule& rule, double value,
|
||||
const std::string& actual_device_id) {
|
||||
void AlarmService::trigger_alarm_action(AlarmRule &rule, double value,
|
||||
const std::string &actual_device_id) {
|
||||
std::string message =
|
||||
format_message(rule, rule.message_template, value, actual_device_id);
|
||||
spdlog::warn("[ALARM ACTIVE] (Rule: {}) {}", rule.rule_id, message);
|
||||
|
|
@ -285,13 +286,13 @@ void AlarmService::trigger_alarm_action(AlarmRule& rule, double value,
|
|||
}
|
||||
}
|
||||
|
||||
void AlarmService::clear_alarm(AlarmRule& rule, double value,
|
||||
const std::string& actual_device_id) {
|
||||
void AlarmService::clear_alarm(AlarmRule &rule, double value,
|
||||
const std::string &actual_device_id) {
|
||||
spdlog::info("[ALARM CLEARED] (Rule: {})", rule.rule_id);
|
||||
|
||||
// 检查是否有解除消息模板
|
||||
if (rule.clear_message_template.empty()) {
|
||||
return; // 没有模板,安静地解除
|
||||
return; // 没有模板,安静地解除
|
||||
}
|
||||
|
||||
std::string message = format_message(rule, rule.clear_message_template, value,
|
||||
|
|
@ -335,14 +336,14 @@ void AlarmService::tts_worker() {
|
|||
[this] { return !m_tts_queue.empty() || !m_tts_running; });
|
||||
|
||||
if (!m_tts_running && m_tts_queue.empty()) {
|
||||
break; // 退出
|
||||
break; // 退出
|
||||
}
|
||||
|
||||
if (!m_tts_queue.empty()) {
|
||||
message_to_play = m_tts_queue.front();
|
||||
m_tts_queue.pop();
|
||||
}
|
||||
} // 释放锁
|
||||
} // 释放锁
|
||||
|
||||
if (!message_to_play.empty()) {
|
||||
spdlog::debug("TTS worker playing: {}", message_to_play);
|
||||
|
|
@ -353,8 +354,9 @@ void AlarmService::tts_worker() {
|
|||
spdlog::info("TTS worker thread stopped.");
|
||||
}
|
||||
|
||||
void AlarmService::schedule_tts(const std::string& text) {
|
||||
if (text.empty() || !m_tts_running) return;
|
||||
void AlarmService::schedule_tts(const std::string &text) {
|
||||
if (text.empty() || !m_tts_running)
|
||||
return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_tts_queue_mutex);
|
||||
m_tts_queue.push(text);
|
||||
|
|
@ -365,7 +367,7 @@ void AlarmService::schedule_tts(const std::string& text) {
|
|||
nlohmann::json AlarmService::getActiveAlarmsJson() {
|
||||
auto alarms = DataStorage::getInstance().getActiveAlarms();
|
||||
json j_alarms = json::array();
|
||||
for (const auto& alarm : alarms) {
|
||||
for (const auto &alarm : alarms) {
|
||||
j_alarms.push_back(AlarmEvent::toJson(alarm));
|
||||
}
|
||||
return j_alarms;
|
||||
|
|
@ -374,29 +376,94 @@ nlohmann::json AlarmService::getActiveAlarmsJson() {
|
|||
nlohmann::json AlarmService::getAlarmHistoryJson(int limit) {
|
||||
auto alarms = DataStorage::getInstance().getAlarmHistory(limit);
|
||||
json j_alarms = json::array();
|
||||
for (const auto& alarm : alarms) {
|
||||
for (const auto &alarm : alarms) {
|
||||
j_alarms.push_back(AlarmEvent::toJson(alarm));
|
||||
}
|
||||
return j_alarms;
|
||||
}
|
||||
|
||||
bool AlarmService::parse_rules_from_file(
|
||||
const std::string& config_path, std::vector<AlarmRule>& out_rules,
|
||||
std::map<std::string, AlarmState>& out_states) {
|
||||
std::ifstream ifs(config_path);
|
||||
if (!ifs.is_open()) {
|
||||
spdlog::error("Failed to open alarm rules file: {}", config_path);
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
json j_rules = json::parse(ifs);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
std::string AlarmService::get_rules_as_json_string() {
|
||||
std::lock_guard<std::mutex> lock(m_rules_mutex);
|
||||
|
||||
if (m_rules_config_path.empty()) {
|
||||
spdlog::error("Cannot get rules: config path not set.");
|
||||
return "[]";
|
||||
}
|
||||
|
||||
std::ifstream ifs(m_rules_config_path);
|
||||
if (!ifs.is_open()) {
|
||||
spdlog::error("Failed to open alarm rules file for reading: {}",
|
||||
m_rules_config_path);
|
||||
return "[]";
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << ifs.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
bool AlarmService::parse_rules_from_content(
|
||||
const std::string &json_content, std::vector<AlarmRule> &out_rules,
|
||||
std::map<std::string, AlarmState> &out_states) {
|
||||
|
||||
try {
|
||||
json j_rules = json::parse(json_content);
|
||||
|
||||
if (!j_rules.is_array()) {
|
||||
spdlog::error("Failed to parse rules: JSON content is not an array.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清空临时容器,准备接收新规则
|
||||
out_rules.clear();
|
||||
out_states.clear();
|
||||
|
||||
for (const auto& j_rule : j_rules) {
|
||||
for (const auto &j_rule : j_rules) {
|
||||
AlarmRule rule;
|
||||
rule.rule_id = j_rule.at("rule_id");
|
||||
rule.device_id = j_rule.at("device_id");
|
||||
|
|
@ -417,64 +484,65 @@ bool AlarmService::parse_rules_from_file(
|
|||
|
||||
out_rules.push_back(std::move(rule));
|
||||
|
||||
// 为每条规则准备一个状态机
|
||||
out_states.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(rule.rule_id),
|
||||
std::forward_as_tuple(
|
||||
m_io_context) // m_io_context 是类成员,可以访问
|
||||
);
|
||||
std::forward_as_tuple(m_io_context));
|
||||
}
|
||||
} catch (const json::exception& e) {
|
||||
spdlog::error("Failed to parse alarm rules file '{}': {}", config_path,
|
||||
e.what());
|
||||
} catch (const json::exception &e) {
|
||||
spdlog::error("Failed to parse alarm rules content: {}", e.what());
|
||||
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);
|
||||
bool AlarmService::parse_rules_from_file(
|
||||
const std::string &config_path, std::vector<AlarmRule> &out_rules,
|
||||
std::map<std::string, AlarmState> &out_states) {
|
||||
|
||||
std::ifstream ifs(config_path);
|
||||
if (!ifs.is_open()) {
|
||||
spdlog::error("Failed to open alarm rules file: {}", config_path);
|
||||
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);
|
||||
std::stringstream buffer;
|
||||
buffer << ifs.rdbuf();
|
||||
std::string file_content = buffer.str();
|
||||
|
||||
// 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();
|
||||
return parse_rules_from_content(file_content, out_rules, out_states);
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
bool AlarmService::save_rules_from_json_string(
|
||||
const std::string &json_content) {
|
||||
|
||||
if (!DataStorage::getInstance().storeAlarmEvent(event)) {
|
||||
spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}",
|
||||
rule_id);
|
||||
std::vector<AlarmRule> dummy_rules;
|
||||
std::map<std::string, AlarmState> dummy_states;
|
||||
|
||||
if (!parse_rules_from_content(json_content, dummy_rules, dummy_states)) {
|
||||
spdlog::warn("Failed to save rules: Invalid content or schema.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_rules_mutex);
|
||||
|
||||
if (m_rules_config_path.empty()) {
|
||||
spdlog::error("Cannot save rules: config path not set.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream ofs(m_rules_config_path, std::ios::trunc);
|
||||
if (!ofs.is_open()) {
|
||||
spdlog::error("Failed to open alarm rules file for writing: {}",
|
||||
m_rules_config_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
ofs << json_content;
|
||||
ofs.close();
|
||||
|
||||
spdlog::info(
|
||||
"Successfully saved new alarm rules to {}. (Reload is required to apply)",
|
||||
m_rules_config_path);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -20,53 +20,70 @@
|
|||
using json = nlohmann::json;
|
||||
|
||||
class AlarmService {
|
||||
public:
|
||||
AlarmService(boost::asio::io_context& io, PiperTTSInterface& tts_service,
|
||||
MqttClient& mqtt_client);
|
||||
public:
|
||||
AlarmService(boost::asio::io_context &io, PiperTTSInterface &tts_service,
|
||||
MqttClient &mqtt_client);
|
||||
|
||||
~AlarmService();
|
||||
|
||||
void stop();
|
||||
|
||||
bool load_rules(const std::string& config_path);
|
||||
bool load_rules(const std::string &config_path);
|
||||
bool reload_rules();
|
||||
|
||||
void process_device_data(const std::string& device_id,
|
||||
const std::string& data_json);
|
||||
void process_device_data(const std::string &device_id,
|
||||
const std::string &data_json);
|
||||
|
||||
void process_system_data(const std::string& system_data_json);
|
||||
void process_system_data(const std::string &system_data_json);
|
||||
|
||||
nlohmann::json getActiveAlarmsJson();
|
||||
nlohmann::json getAlarmHistoryJson(int limit);
|
||||
|
||||
/**
|
||||
* @brief [新增] 手动清除一个当前激活的告警
|
||||
* @param rule_id 要清除的规则ID
|
||||
* @param rule_id 要清除的规则IDparse_rules_from_fil
|
||||
* @param device_id 触发该规则的设备ID
|
||||
* @return 成功则返回 true
|
||||
*/
|
||||
bool manually_clear_alarm(const std::string& rule_id,
|
||||
const std::string& device_id);
|
||||
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);
|
||||
std::string format_message(const AlarmRule& rule,
|
||||
const std::string& template_str, double value,
|
||||
const std::string& actual_device_id);
|
||||
void trigger_alarm_action(AlarmRule& rule, double value,
|
||||
const std::string& actual_device_id);
|
||||
void clear_alarm(AlarmRule& rule, double value,
|
||||
const std::string& actual_device_id);
|
||||
std::string get_rules_as_json_string();
|
||||
|
||||
/**
|
||||
* @brief [新增] 从 JSON 字符串内容中解析和校验规则
|
||||
* 这是所有解析器的核心
|
||||
*/
|
||||
bool parse_rules_from_content(const std::string &json_content,
|
||||
std::vector<AlarmRule> &out_rules,
|
||||
std::map<std::string, AlarmState> &out_states);
|
||||
|
||||
/**
|
||||
* @brief [新增] 验证并保存告警规则到文件
|
||||
* @param json_content 包含新规则的完整JSON字符串
|
||||
* @return 成功 (JSON有效且写入成功) 则返回 true
|
||||
*/
|
||||
bool save_rules_from_json_string(const std::string &json_content);
|
||||
|
||||
private:
|
||||
void check_rule_against_value(AlarmRule &rule, double value,
|
||||
const std::string &actual_device_id);
|
||||
std::string format_message(const AlarmRule &rule,
|
||||
const std::string &template_str, double value,
|
||||
const std::string &actual_device_id);
|
||||
void trigger_alarm_action(AlarmRule &rule, double value,
|
||||
const std::string &actual_device_id);
|
||||
void clear_alarm(AlarmRule &rule, double value,
|
||||
const std::string &actual_device_id);
|
||||
void tts_worker();
|
||||
void schedule_tts(const std::string& text);
|
||||
bool parse_rules_from_file(const std::string& config_path,
|
||||
std::vector<AlarmRule>& out_rules,
|
||||
std::map<std::string, AlarmState>& out_states);
|
||||
void schedule_tts(const std::string &text);
|
||||
bool parse_rules_from_file(const std::string &config_path,
|
||||
std::vector<AlarmRule> &out_rules,
|
||||
std::map<std::string, AlarmState> &out_states);
|
||||
|
||||
boost::asio::io_context& m_io_context;
|
||||
PiperTTSInterface& m_tts_service;
|
||||
MqttClient& m_mqtt_client;
|
||||
boost::asio::io_context &m_io_context;
|
||||
PiperTTSInterface &m_tts_service;
|
||||
MqttClient &m_mqtt_client;
|
||||
|
||||
std::vector<AlarmRule> m_rules;
|
||||
std::map<std::string, AlarmState> m_alarm_states;
|
||||
|
|
|
|||
|
|
@ -7,16 +7,13 @@
|
|||
#include "config/config_manager.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
WebServer::WebServer(SystemMonitor::SystemMonitor& monitor,
|
||||
DeviceManager& deviceManager, LiveDataCache& liveDataCache,
|
||||
AlarmService& alarm_service, uint16_t port)
|
||||
: crow::Crow<crow::CORSHandler>(),
|
||||
m_monitor(monitor),
|
||||
m_device_manager(deviceManager),
|
||||
m_live_data_cache(liveDataCache),
|
||||
m_alarm_service(alarm_service),
|
||||
m_port(port) {
|
||||
auto& cors = this->get_middleware<crow::CORSHandler>();
|
||||
WebServer::WebServer(SystemMonitor::SystemMonitor &monitor,
|
||||
DeviceManager &deviceManager, LiveDataCache &liveDataCache,
|
||||
AlarmService &alarm_service, uint16_t port)
|
||||
: crow::Crow<crow::CORSHandler>(), m_monitor(monitor),
|
||||
m_device_manager(deviceManager), m_live_data_cache(liveDataCache),
|
||||
m_alarm_service(alarm_service), m_port(port) {
|
||||
auto &cors = this->get_middleware<crow::CORSHandler>();
|
||||
cors.global()
|
||||
.origin("*")
|
||||
.headers("Content-Type", "Authorization")
|
||||
|
|
@ -77,7 +74,7 @@ void WebServer::setup_routes() {
|
|||
auto devices_info = m_device_manager.get_all_device_info();
|
||||
|
||||
std::vector<crow::json::wvalue> devices_json;
|
||||
for (const auto& info : devices_info) {
|
||||
for (const auto &info : devices_info) {
|
||||
crow::json::wvalue device_obj;
|
||||
|
||||
device_obj["id"] = info.id;
|
||||
|
|
@ -85,7 +82,7 @@ void WebServer::setup_routes() {
|
|||
device_obj["is_running"] = info.is_running;
|
||||
|
||||
crow::json::wvalue details_obj;
|
||||
for (const auto& pair : info.connection_details) {
|
||||
for (const auto &pair : info.connection_details) {
|
||||
details_obj[pair.first] = pair.second;
|
||||
}
|
||||
device_obj["connection_details"] = std::move(details_obj);
|
||||
|
|
@ -100,7 +97,7 @@ void WebServer::setup_routes() {
|
|||
auto latest_data_map = m_live_data_cache.get_all_data();
|
||||
|
||||
crow::json::wvalue response;
|
||||
for (const auto& pair : latest_data_map) {
|
||||
for (const auto &pair : latest_data_map) {
|
||||
response[pair.first] = crow::json::load(pair.second);
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +112,7 @@ void WebServer::setup_routes() {
|
|||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error("Error processing /api/alarms/active: {}", e.what());
|
||||
crow::json::wvalue error_resp;
|
||||
error_resp["error"] = "Failed to retrieve active alarms.";
|
||||
|
|
@ -127,15 +124,16 @@ void WebServer::setup_routes() {
|
|||
});
|
||||
|
||||
CROW_ROUTE((*this), "/api/alarms/history")
|
||||
.methods("GET"_method)([this](const crow::request& req) {
|
||||
.methods("GET"_method)([this](const crow::request &req) {
|
||||
int limit = 100;
|
||||
if (req.url_params.get("limit")) {
|
||||
try {
|
||||
limit = std::stoi(req.url_params.get("limit"));
|
||||
} catch (const std::exception&) { /* ignore invalid */
|
||||
} catch (const std::exception &) { /* ignore invalid */
|
||||
}
|
||||
}
|
||||
if (limit <= 0) limit = 100;
|
||||
if (limit <= 0)
|
||||
limit = 100;
|
||||
|
||||
try {
|
||||
auto json_string = m_alarm_service.getAlarmHistoryJson(limit).dump();
|
||||
|
|
@ -144,7 +142,7 @@ void WebServer::setup_routes() {
|
|||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error("Error processing /api/alarms/history: {}", e.what());
|
||||
crow::json::wvalue error_resp;
|
||||
error_resp["error"] = "Failed to retrieve alarm history.";
|
||||
|
|
@ -181,11 +179,11 @@ void WebServer::setup_routes() {
|
|||
});
|
||||
|
||||
CROW_ROUTE((*this), "/api/alarms/clear")
|
||||
.methods("POST"_method)([this](const crow::request& req) {
|
||||
.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) {
|
||||
} 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.\"}");
|
||||
|
|
@ -211,10 +209,45 @@ void WebServer::setup_routes() {
|
|||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
} else {
|
||||
auto res =
|
||||
crow::response(404,
|
||||
"{\"status\":\"error\", \"message\":\"Alarm not "
|
||||
"found or not active.\"}");
|
||||
auto res = crow::response(
|
||||
404, "{\"status\":\"error\", \"message\":\"Alarm not "
|
||||
"found or not active.\"}");
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
CROW_ROUTE((*this), "/api/alarms/config").methods("GET"_method)([this]() {
|
||||
std::string rules_json_string = m_alarm_service.get_rules_as_json_string();
|
||||
auto res = crow::response(200, rules_json_string);
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
});
|
||||
|
||||
CROW_ROUTE((*this), "/api/alarms/config")
|
||||
.methods("POST"_method)([this](const crow::request &req) {
|
||||
const std::string &new_rules_content = req.body;
|
||||
|
||||
bool success =
|
||||
m_alarm_service.save_rules_from_json_string(new_rules_content);
|
||||
|
||||
if (success) {
|
||||
crow::json::wvalue response_json;
|
||||
response_json["status"] = "success";
|
||||
response_json["message"] =
|
||||
"Rules saved successfully. A reload is required to apply.";
|
||||
|
||||
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 save rules. Invalid JSON format or server error.";
|
||||
|
||||
auto res = crow::response(400, error_json.dump());
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue