OrangePi3588Media/plugins/alarm/rule_engine.cpp
2025-12-26 16:58:18 +08:00

207 lines
6.8 KiB
C++

#include "rule_engine.h"
#include <algorithm>
#include <ctime>
#include <iostream>
#include <sstream>
namespace rk3588 {
bool RuleEngine::Init(const SimpleJson& rules_config, const std::vector<std::string>& labels) {
labels_ = labels;
rules_.clear();
duration_start_.clear();
last_trigger_.clear();
if (!rules_config.IsArray()) {
std::cerr << "[RuleEngine] rules must be an array\n";
return false;
}
for (const auto& rule_json : rules_config.AsArray()) {
AlarmRule rule;
rule.name = rule_json.ValueOr<std::string>("name", "unnamed_rule");
rule.min_duration_ms = rule_json.ValueOr<int>("min_duration_ms", 0);
rule.cooldown_ms = rule_json.ValueOr<int>("cooldown_ms", 5000);
rule.schedule = rule_json.ValueOr<std::string>("schedule", "");
// Parse class_ids
if (const SimpleJson* ids = rule_json.Find("class_ids")) {
for (const auto& id_val : ids->AsArray()) {
rule.class_ids.insert(id_val.AsInt(-1));
}
}
// Parse objects (class names) and convert to class_ids
if (const SimpleJson* objects = rule_json.Find("objects")) {
for (const auto& obj_val : objects->AsArray()) {
std::string obj_name = obj_val.AsString("");
for (size_t i = 0; i < labels_.size(); ++i) {
if (labels_[i] == obj_name) {
rule.class_ids.insert(static_cast<int>(i));
break;
}
}
}
}
// Parse ROI
if (const SimpleJson* roi_json = rule_json.Find("roi")) {
rule.roi.x = roi_json->ValueOr<float>("x", 0.0f);
rule.roi.y = roi_json->ValueOr<float>("y", 0.0f);
rule.roi.w = roi_json->ValueOr<float>("w", 1.0f);
rule.roi.h = roi_json->ValueOr<float>("h", 1.0f);
}
rules_.push_back(rule);
std::cout << "[RuleEngine] loaded rule: " << rule.name
<< " class_ids=" << rule.class_ids.size()
<< " roi=(" << rule.roi.x << "," << rule.roi.y << ","
<< rule.roi.w << "," << rule.roi.h << ")"
<< " min_duration=" << rule.min_duration_ms << "ms"
<< " cooldown=" << rule.cooldown_ms << "ms\n";
}
return true;
}
RuleMatchResult RuleEngine::Evaluate(const std::shared_ptr<Frame>& frame) {
RuleMatchResult result;
if (!frame || !frame->det) return result;
const auto& detections = frame->det->items;
int img_w = frame->det->img_w > 0 ? frame->det->img_w : frame->width;
int img_h = frame->det->img_h > 0 ? frame->det->img_h : frame->height;
for (auto& rule : rules_) {
// Check schedule
if (!rule.schedule.empty() && !IsInSchedule(rule.schedule)) {
duration_start_.erase(rule.name);
continue;
}
// Check cooldown
if (!CheckCooldown(rule.name)) {
continue;
}
// Find matching detections
std::vector<Detection> matched;
for (const auto& det : detections) {
// Check class_id (if empty, match all)
if (!rule.class_ids.empty() &&
rule.class_ids.find(det.cls_id) == rule.class_ids.end()) {
continue;
}
// Check ROI
if (!IsInRoi(det.bbox, rule.roi, img_w, img_h)) {
continue;
}
matched.push_back(det);
}
bool currently_matched = !matched.empty();
// Check duration
if (CheckDuration(rule.name, currently_matched)) {
result.matched = true;
result.rule_name = rule.name;
result.matched_detections = matched;
TriggerCooldown(rule.name);
duration_start_.erase(rule.name);
return result;
}
}
return result;
}
void RuleEngine::Reset() {
duration_start_.clear();
last_trigger_.clear();
}
bool RuleEngine::IsInRoi(const Rect& bbox, const RoiRect& roi, int img_w, int img_h) const {
// Convert bbox to normalized coordinates
float bbox_cx = (bbox.x + bbox.w / 2.0f) / img_w;
float bbox_cy = (bbox.y + bbox.h / 2.0f) / img_h;
// Check if center is in ROI
return bbox_cx >= roi.x && bbox_cx <= (roi.x + roi.w) &&
bbox_cy >= roi.y && bbox_cy <= (roi.y + roi.h);
}
bool RuleEngine::IsInSchedule(const std::string& schedule) const {
// Parse "HH:MM-HH:MM" format
if (schedule.length() != 11 || schedule[5] != '-') {
return true; // Invalid format, assume always active
}
int start_h = 0, start_m = 0, end_h = 0, end_m = 0;
std::sscanf(schedule.c_str(), "%d:%d-%d:%d", &start_h, &start_m, &end_h, &end_m);
std::time_t now = std::time(nullptr);
std::tm* local = std::localtime(&now);
int curr_min = local->tm_hour * 60 + local->tm_min;
int start_min = start_h * 60 + start_m;
int end_min = end_h * 60 + end_m;
if (start_min <= end_min) {
return curr_min >= start_min && curr_min <= end_min;
} else {
// Overnight schedule (e.g., "22:00-06:00")
return curr_min >= start_min || curr_min <= end_min;
}
}
bool RuleEngine::CheckDuration(const std::string& rule_name, bool currently_matched) {
auto it = std::find_if(rules_.begin(), rules_.end(),
[&](const AlarmRule& r) { return r.name == rule_name; });
if (it == rules_.end()) return false;
int min_duration_ms = it->min_duration_ms;
auto now = std::chrono::steady_clock::now();
if (!currently_matched) {
duration_start_.erase(rule_name);
return false;
}
auto start_it = duration_start_.find(rule_name);
if (start_it == duration_start_.end()) {
duration_start_[rule_name] = now;
if (min_duration_ms <= 0) {
return true; // No duration requirement
}
return false;
}
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - start_it->second);
return elapsed.count() >= min_duration_ms;
}
bool RuleEngine::CheckCooldown(const std::string& rule_name) {
auto it = std::find_if(rules_.begin(), rules_.end(),
[&](const AlarmRule& r) { return r.name == rule_name; });
if (it == rules_.end()) return false;
auto last_it = last_trigger_.find(rule_name);
if (last_it == last_trigger_.end()) {
return true; // Never triggered
}
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_it->second);
return elapsed.count() >= it->cooldown_ms;
}
void RuleEngine::TriggerCooldown(const std::string& rule_name) {
last_trigger_[rule_name] = std::chrono::steady_clock::now();
}
} // namespace rk3588