Compare commits
No commits in common. "ca2147e499d3c3ae7e79ddf504505edaecf6ffed" and "1ff51483e70c941c0deeba7ce07d404404d0ea3c" have entirely different histories.
ca2147e499
...
1ff51483e7
@ -94,35 +94,23 @@ target_include_directories(
|
||||
set_target_properties(eqpalg PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${bin_dir})
|
||||
|
||||
# ###################### add test ########################
|
||||
include(CTest)
|
||||
# 1. loss_compress_test
|
||||
#
|
||||
# ##############################################################################
|
||||
# include(../cmake_include/unit_test.cmake)
|
||||
|
||||
aux_source_directory(./test TEST_SOURCES)
|
||||
# add_executable(loss_compress_test ${DISTRIBUTION} test/loss_compress_test.cc)
|
||||
# target_link_libraries(loss_compress_test ${LINK_OPTION}
|
||||
# Boost::unit_test_framework)
|
||||
# target_include_directories(
|
||||
# loss_compress_test PUBLIC ./ ../ ../../inc ../../inc/dbinc
|
||||
# ${iPlature_include})
|
||||
|
||||
add_executable(eqpalg_test
|
||||
${TEST_SOURCES}
|
||||
./utility/expression_engine.cpp
|
||||
./utility/fb_state_machine.cpp
|
||||
./utility/bound_checker.cpp
|
||||
)
|
||||
# set_target_properties(loss_compress_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY
|
||||
# ${UNIT_TEST_BIN_OUTPUT_DIR})
|
||||
|
||||
target_include_directories(eqpalg_test PUBLIC
|
||||
./
|
||||
../
|
||||
${my_lib_include}
|
||||
${iplature_include}
|
||||
)
|
||||
|
||||
target_link_libraries(eqpalg_test
|
||||
${LINK_OPTION}
|
||||
${Boost_LIBRARIES}
|
||||
${mix_cc}
|
||||
nlohmann_json::nlohmann_json
|
||||
Eigen3::Eigen
|
||||
)
|
||||
|
||||
set_target_properties(eqpalg_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test)
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME eqpalg_test
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test
|
||||
COMMAND eqpalg_test)
|
||||
# enable_testing()
|
||||
# add_test(
|
||||
# NAME loss_compress_test
|
||||
# WORKING_DIRECTORY ${UNIT_TEST_BIN_OUTPUT_DIR}
|
||||
# COMMAND loss_compress_test)
|
||||
|
||||
@ -85,7 +85,8 @@ int AlgBase::init() {
|
||||
ms = 20;
|
||||
}
|
||||
this->delay_time_ = milliseconds(ms);
|
||||
expr_engine_ = std::make_unique<ExpressionEngine>(mm_vars, m_tags);
|
||||
exp_mpdule_ptr_ =
|
||||
std::make_unique<ExpModule>(mm_vars, m_tags, is_exp_alg_);
|
||||
if (this->prr_ == 1) {
|
||||
string exp_str = "";
|
||||
|
||||
@ -97,8 +98,10 @@ int AlgBase::init() {
|
||||
logger_->Info() << "tmp_exp:" << tmp_exp << std::endl;
|
||||
exp_str = get_macro_replaced_exp(tmp_exp);
|
||||
logger_->Info() << "exp_str:" << exp_str << std::endl;
|
||||
expr_engine_->registerExpression("pre_result", exp_str);
|
||||
logger_->Info() << "pre_result:" << exp_str << std::endl;
|
||||
exp_mpdule_ptr_->add_exp("pre_result", exp_str);
|
||||
logger_->Info() << "pre_result:"
|
||||
<< exp_mpdule_ptr_->get_exp_str("pre_result")
|
||||
<< std::endl;
|
||||
}
|
||||
logger_->Debug() << "ruleid:" << this->rule_id_
|
||||
<< ",rulename:" << this->rule_name_
|
||||
@ -196,7 +199,7 @@ void AlgBase::exec_mon_call() {
|
||||
if (alarm.alarmed) {
|
||||
logger_->Debug() << alarm.content << endl;
|
||||
alarm_poster_.alarm(alarm, &last_alarm_time_);
|
||||
expr_engine_->forceResetFunVars();
|
||||
exp_mpdule_ptr_->fun_reset();
|
||||
} else {
|
||||
if (get_save_data_cycled()) {
|
||||
this->save_rule_norm_data();
|
||||
@ -368,12 +371,8 @@ bool AlgBase::update_map_rule() {
|
||||
|
||||
bool AlgBase::get_prr() {
|
||||
if (this->prr_ == 1) {
|
||||
// 刷新共享内存变量
|
||||
this->refresh_now_time();
|
||||
mix_cc::time_range_t dummy_range;
|
||||
expr_engine_->refreshFromMemory(this->now_time_, dummy_range);
|
||||
|
||||
bool prr_result = expr_engine_->evaluateBool("pre_result");
|
||||
exp_mpdule_ptr_->update();
|
||||
bool prr_result = (bool)exp_mpdule_ptr_->get_value("pre_result");
|
||||
this->now_prr_ = prr_result;
|
||||
return prr_result;
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
#include <eqpalg/define/error_code.h>
|
||||
#include <eqpalg/define/public.h>
|
||||
#include <eqpalg/gb_logger.h>
|
||||
#include <eqpalg/utility/expression_engine.h>
|
||||
#include <eqpalg/utility/ExpModule.h>
|
||||
#include <eqpalg/utility/VarsCache.hpp>
|
||||
#include <eqpalg/utility/XorShift128Plus.hpp>
|
||||
#include <eqpalg/utility/alarm_poster.h>
|
||||
@ -100,7 +100,7 @@ protected:
|
||||
std::vector<ErrorCodeType> error_code_list_;
|
||||
|
||||
int prr_ = PRR::None;
|
||||
std::unique_ptr<ExpressionEngine> expr_engine_;
|
||||
std::unique_ptr<ExpModule> exp_mpdule_ptr_;
|
||||
|
||||
DataInfo data_info_;
|
||||
|
||||
@ -108,6 +108,8 @@ protected:
|
||||
|
||||
bool now_prr_ = false;
|
||||
|
||||
bool is_exp_alg_ = false;
|
||||
|
||||
int task_seq = 0;
|
||||
TimeDur save_interval_ms_ = 5000ms;
|
||||
TimeDur rule_state_update_interval_ms_ = 500ms;
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
#include <eqpalg/algs/bound_alg.h>
|
||||
#include <eqpalg/feature_extraction/daa.h>
|
||||
#include <eqpalg/utility/build_alarm_info.h>
|
||||
#include <shm/RuleStatShm.h>
|
||||
|
||||
BoundAlg::BoundAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId, size_t exp_type)
|
||||
: ExpBase(name, rule_json, ruleId, exp_type) {
|
||||
logger_.reset(new LOG("BoundAlg:" + rule_name_, AUTO_CATCH_PID));
|
||||
}
|
||||
|
||||
BoundAlg::~BoundAlg() = default;
|
||||
|
||||
int BoundAlg::init() {
|
||||
int ret = ExpBase::init();
|
||||
if (ret == 0) {
|
||||
exp_is_wrong_ = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BoundAlg::doInitExtend() {
|
||||
// BoundAlg 额外的初始化:上下限
|
||||
reload_config_up_down();
|
||||
reload_ci_dist();
|
||||
last_load_time_ = std::chrono::system_clock::now();
|
||||
stat_collector_.configure(rule_id_, rule_name_, dist_mode_, is_learning_);
|
||||
}
|
||||
|
||||
bool BoundAlg::checkFilter() {
|
||||
if (!expr_engine_->exps_.count("feedback")) {
|
||||
return true; // 无筛选表达式,所有数据参与
|
||||
}
|
||||
try {
|
||||
return expr_engine_->evaluateBool("feedback");
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AlarmInfo BoundAlg::doMonProc() {
|
||||
double result_value = expr_engine_->evaluate("act");
|
||||
filter_flag_ = false;
|
||||
|
||||
// 数据筛选
|
||||
filter_flag_ = checkFilter();
|
||||
|
||||
// 自学习统计
|
||||
if (is_learning_ && filter_flag_) {
|
||||
rule_stat_.current_value = result_value;
|
||||
SingletonTemp<EqpStat>::GetInstance().add_stat_values(rule_id_, result_value);
|
||||
}
|
||||
|
||||
// 超限检测
|
||||
if (filter_flag_ && bound_checker_.isOutOfBounds(result_value)) {
|
||||
rule_stat_.alarm_value = result_value;
|
||||
auto msg = error_str_ + ":" + DAA::double2str(result_value) + unit_ +
|
||||
",合理区间:[" + DAA::double2strLimit(bound_checker_.limitDown()) +
|
||||
"," + DAA::double2strLimit(bound_checker_.limitUp()) + "]" + unit_;
|
||||
query_time_range_.set_left(query_time_range_.get_right() - delay_time_);
|
||||
expr_engine_->markFunVarsNeedReset();
|
||||
return utility::build_alarm_info(MsgLevel::ERROR, rule_id_, rule_name_,
|
||||
"EXP2", msg, get_alarm_time());
|
||||
}
|
||||
return AlarmInfo{};
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
#include <eqpalg/algs/exp_base.h>
|
||||
|
||||
class BoundAlg : public ExpBase {
|
||||
public:
|
||||
BoundAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId,
|
||||
size_t exp_type = ExpType::Bound);
|
||||
~BoundAlg() override;
|
||||
|
||||
int init() override;
|
||||
|
||||
protected:
|
||||
AlarmInfo doMonProc() override;
|
||||
void doInitExtend() override;
|
||||
|
||||
// 数据筛选检查
|
||||
bool checkFilter();
|
||||
};
|
||||
@ -1,62 +0,0 @@
|
||||
#include <eqpalg/algs/bound_hold_alg.h>
|
||||
#include <eqpalg/feature_extraction/daa.h>
|
||||
#include <eqpalg/utility/build_alarm_info.h>
|
||||
|
||||
BoundHoldAlg::BoundHoldAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId)
|
||||
: BoundAlg(name, rule_json, ruleId, ExpType::BoundHoldTime) {
|
||||
logger_.reset(new LOG("BoundHoldAlg:" + rule_name_, AUTO_CATCH_PID));
|
||||
}
|
||||
|
||||
BoundHoldAlg::~BoundHoldAlg() = default;
|
||||
|
||||
int BoundHoldAlg::init() {
|
||||
int ret = BoundAlg::init();
|
||||
if (ret == 0) {
|
||||
reload_config_up_down_hold_time();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
AlarmInfo BoundHoldAlg::doMonProc() {
|
||||
double result_value = expr_engine_->evaluate("act");
|
||||
filter_flag_ = checkFilter();
|
||||
|
||||
if (is_learning_ && filter_flag_) {
|
||||
rule_stat_.current_value = result_value;
|
||||
SingletonTemp<EqpStat>::GetInstance().add_stat_values(rule_id_, result_value);
|
||||
}
|
||||
|
||||
bool is_over = bound_checker_.isOutOfBounds(result_value);
|
||||
|
||||
if (!filter_flag_) {
|
||||
act_start_time_ = now_time_;
|
||||
act_started_ = false;
|
||||
} else {
|
||||
if (is_over) {
|
||||
if (!act_started_) {
|
||||
act_started_ = true;
|
||||
act_start_time_ = now_time_;
|
||||
}
|
||||
if ((hold_time_ <= delay_time_) ||
|
||||
(now_time_ - act_start_time_ > hold_time_)) {
|
||||
rule_stat_.alarm_value = result_value;
|
||||
auto msg = error_str_ + ":" + DAA::double2str(result_value) + unit_ +
|
||||
",合理区间:[" + DAA::double2strLimit(bound_checker_.limitDown()) +
|
||||
"," + DAA::double2strLimit(bound_checker_.limitUp()) + "]" + unit_;
|
||||
query_time_range_.set_left(query_time_range_.get_right() - delay_time_);
|
||||
expr_engine_->markFunVarsNeedReset();
|
||||
act_start_time_ = now_time_;
|
||||
act_started_ = false;
|
||||
return utility::build_alarm_info(
|
||||
utility::get_msg_level(bound_checker_.limitDown(),
|
||||
bound_checker_.limitUp(), result_value),
|
||||
rule_id_, rule_name_, "EXP5", msg, get_alarm_time());
|
||||
}
|
||||
} else {
|
||||
act_start_time_ = now_time_;
|
||||
act_started_ = false;
|
||||
}
|
||||
}
|
||||
return AlarmInfo{};
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
#include <eqpalg/algs/bound_alg.h>
|
||||
|
||||
class BoundHoldAlg : public BoundAlg {
|
||||
public:
|
||||
BoundHoldAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId);
|
||||
~BoundHoldAlg() override;
|
||||
|
||||
int init() override;
|
||||
|
||||
protected:
|
||||
AlarmInfo doMonProc() override;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,6 @@
|
||||
#include <eqpalg/utility/HoldTime.h>
|
||||
#include <eqpalg/utility/StatExp.hpp>
|
||||
#include <eqpalg/utility/item2chinese.hpp>
|
||||
#include <eqpalg/utility/bound_checker.h>
|
||||
#include <eqpalg/utility/stat_collector.h>
|
||||
#include <eqpalg/utility/fb_state_machine.h>
|
||||
#include <glob/SingletonTemplate.h>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
@ -96,24 +93,13 @@ public:
|
||||
* 删除
|
||||
* 四张表(T_RULE_SAMPLE_1D,T_RULE_SAMPLE_1D_INFO,T_SAMPLE_RECORD,T_SAMPLE_RECORD,T_RULE_SAMPLE_FEATURE)rule_id_对应数据
|
||||
* cron----
|
||||
* 重置 stat_collector_(stat_collector_.reset())
|
||||
* 重置 sta_ptr_(sta_ptr_.reset())
|
||||
*/
|
||||
virtual void reset_dev_data() override;
|
||||
|
||||
bool get_prr() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 子类实现的具体算法逻辑
|
||||
* @return AlarmInfo
|
||||
*/
|
||||
virtual AlarmInfo doMonProc() = 0;
|
||||
|
||||
/**
|
||||
* @brief 子类扩展初始化(钩子)
|
||||
*/
|
||||
virtual void doInitExtend() {}
|
||||
|
||||
/**
|
||||
* @brief 重新载入数据源信息
|
||||
* @return int
|
||||
@ -131,6 +117,7 @@ protected:
|
||||
*/
|
||||
bool get_cycled_cron();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 载入上下限
|
||||
* @return int
|
||||
@ -138,7 +125,7 @@ protected:
|
||||
int reload_config_up_down();
|
||||
|
||||
/**
|
||||
* @brief 载入上、下限及保持时间
|
||||
* @brief载入上、下限及保持时间
|
||||
* @return int
|
||||
*/
|
||||
int reload_config_up_down_hold_time();
|
||||
@ -150,12 +137,59 @@ private:
|
||||
*/
|
||||
int reload_config_exp_feedback();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 把从共享内存查询到的数据载入到表达式变量中
|
||||
* @return int
|
||||
*/
|
||||
int refresh_exp_vars_mem();
|
||||
/**
|
||||
* @brief 把从ihyperDB查询到的数据载入到表达式变量中
|
||||
* @param row 数据矩阵的行数
|
||||
* @return int
|
||||
*/
|
||||
int refresh_exp_vars_ihd(int row);
|
||||
/**
|
||||
* @brief 首次运行,载入默认变量至表达式系统变量map
|
||||
* @return int
|
||||
*/
|
||||
int first_fill_mm_vars();
|
||||
/**
|
||||
* @brief 刷新hold变量,hold变量有hold(n,T)解析所得
|
||||
* hold(n,T),n——tag的序号,T——保持的时间,单位分钟
|
||||
* @return int
|
||||
*/
|
||||
int refresh_hold_var();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 打印表达式和它的变量值
|
||||
* @param expstr My Param doc
|
||||
*/
|
||||
void print_exp_vars(const string &expstr = "");
|
||||
|
||||
protected:
|
||||
string exp_str_;
|
||||
|
||||
string error_str_;
|
||||
|
||||
bool feedback_done_ = false;
|
||||
|
||||
string unit_;
|
||||
/**
|
||||
* @brief 自定义带状态函数
|
||||
* KeepT , ///< 保持时间
|
||||
* KeepC, ///<出现次数
|
||||
* RiseEdge, ///<上升沿出现次数
|
||||
* Detect ///<为真检测次数
|
||||
*/
|
||||
StatExp::FunVars fun_vars_;
|
||||
|
||||
std::unique_ptr<mix_cc::matheval::Expression> exp_act_;
|
||||
|
||||
std::unique_ptr<mix_cc::matheval::Expression> exp_feedback_;
|
||||
|
||||
std::unique_ptr<mix_cc::matheval::Expression> exp_result_;
|
||||
|
||||
protected:
|
||||
const size_t
|
||||
@ -165,10 +199,14 @@ protected:
|
||||
|
||||
TimePoint act_start_time_;
|
||||
|
||||
bool act_triggered_ = false;
|
||||
|
||||
bool act_started_ =
|
||||
false;
|
||||
|
||||
FbStateMachine fb_fsm_;
|
||||
bool feedback_triggered_ = false;
|
||||
|
||||
bool m_timemode = false;
|
||||
|
||||
int refresh_counts_ = 0;
|
||||
protected:
|
||||
@ -182,7 +220,7 @@ protected:
|
||||
|
||||
TimeDur hold_time_ = 0ms;
|
||||
|
||||
StatCollector stat_collector_;
|
||||
std::unique_ptr<DAA::STA> sta_ptr_;
|
||||
|
||||
std::map<std::string, std::unique_ptr<HoldTime>>
|
||||
hold_times_;
|
||||
@ -205,12 +243,46 @@ protected:
|
||||
|
||||
std::string sample_result_;
|
||||
|
||||
DetectMode detect_mode_ = DetectMode::Default;
|
||||
|
||||
BoundChecker bound_checker_;
|
||||
int detect_mode_ = DetectMode::Default;
|
||||
|
||||
bool filter_flag_ = false;
|
||||
|
||||
bool is_fun_vars_need_reset_ = false;
|
||||
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 表达式开始动作更新
|
||||
* 更新动作开始时间和s{tagN}
|
||||
* 使程序进入到开始动作满足状态
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool act_start_done();
|
||||
|
||||
/**
|
||||
* @brief 表达式开始动作未保持
|
||||
* 使得程序退出开始动作满足状态
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool act_not_hold();
|
||||
|
||||
/**
|
||||
* @brief 满足表达式终止条件
|
||||
* 需要开始动作满足状态作为前提条件
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool act_done();
|
||||
|
||||
/**
|
||||
* @brief 开始动作是否超时
|
||||
* 使得表达式推出满足开始动作状态
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool act_timeout();
|
||||
|
||||
protected:
|
||||
/**
|
||||
@ -218,6 +290,24 @@ protected:
|
||||
*/
|
||||
void query_ihd_data();
|
||||
|
||||
/**
|
||||
* @brief 获得超时报警
|
||||
* @return AlarmInfo
|
||||
*/
|
||||
AlarmInfo get_timeout_alarm();
|
||||
|
||||
/**
|
||||
* @brief 从hold(n,T)解析出hold变量,并表达式系统变量map初始化
|
||||
* @param exp_str My Param doc
|
||||
* @return int
|
||||
*/
|
||||
int init_hold_exp_str(const std::string &exp_str);
|
||||
/**
|
||||
* @brief
|
||||
* @param exp_str My Param doc
|
||||
* @return int
|
||||
*/
|
||||
int exp_messy_code_check(const std::string &exp_str);
|
||||
/**
|
||||
* @brief 重新载入置信区间
|
||||
* @return int
|
||||
@ -265,5 +355,14 @@ protected:
|
||||
*/
|
||||
bool task_prr(int row);
|
||||
|
||||
/**
|
||||
* @brief 初始化fun_vars_
|
||||
* 将前提表达式的fun_vars_替换
|
||||
*/
|
||||
void fun_vars_init();
|
||||
/**
|
||||
* @brief 自动判断重置状态
|
||||
* 依据 is_fun_vars_need_reset_
|
||||
*/
|
||||
void auto_fun_vars_reset();
|
||||
};
|
||||
|
||||
|
||||
@ -52,13 +52,13 @@ ExpBound::~ExpBound() {
|
||||
|
||||
AlarmInfo ExpBound::mon_proc() {
|
||||
|
||||
if (expr_engine_->evaluateBool("feedback") == false) {
|
||||
if ((bool)exp_feedback_->evaluate() == false) {
|
||||
logger_->Debug() << "前提条件不满足!" << std::endl;
|
||||
return AlarmInfo{};
|
||||
}
|
||||
|
||||
/*最新数据*/
|
||||
double now_value = expr_engine_->evaluate("act");
|
||||
double now_value = exp_act_->evaluate();
|
||||
|
||||
rule_stat_.current_value = now_value;
|
||||
/*报警检查*/
|
||||
|
||||
@ -48,7 +48,7 @@ AlarmInfo ExpSample2D::mon_proc() {
|
||||
} else {
|
||||
switch (exp_type_) {
|
||||
case ExpType::PolyFit:
|
||||
if (expr_engine_->evaluateBool("act") && check_polyFit()) {
|
||||
if (exp_act_->evaluate() && check_polyFit()) {
|
||||
this->rule_stat_.alarm_value = this->rule_stat_.current_value;
|
||||
auto msg = rule_name_ + this->error_str_ + " Y表达式当前值:" +
|
||||
DAA::double2str(this->rule_stat_.current_value) +
|
||||
@ -95,26 +95,38 @@ AlarmInfo ExpSample2D::mon_proc() {
|
||||
|
||||
int ExpSample2D::reload_samples() {
|
||||
/*
|
||||
sample_X绑定 expr_engine_->evaluate("sample_X")
|
||||
sample_Y绑定 expr_engine_->evaluate("sample_Y")
|
||||
sample_X绑定 exp_feedback_
|
||||
sample_Y绑定 exp_result_
|
||||
*/
|
||||
auto tmp_exp =
|
||||
rule_json_.at("function").at("sample_X").at("value").get<std::string>();
|
||||
exp_str_ = get_macro_replaced_exp(tmp_exp);
|
||||
if (exp_str_ != "") {
|
||||
int ret = expr_engine_->registerExpression("sample_X", exp_str_);
|
||||
if (ret != 0) return -1;
|
||||
logger_->Debug() << "sample_X:" << exp_str_ << "="
|
||||
<< expr_engine_->evaluate("sample_X") << endl;
|
||||
if (exp_feedback_ == nullptr && exp_str_ != "") {
|
||||
try {
|
||||
exp_feedback_ =
|
||||
std::make_unique<mix_cc::matheval::Expression>(exp_str_, &mm_vars);
|
||||
logger_->Debug() << "sample_X:" << exp_str_ << "="
|
||||
<< exp_feedback_->evaluate() << endl;
|
||||
} catch (const std::exception &e) {
|
||||
logger_->Error() << "sample_X:" << exp_str_ << "计算出错:" << e.what()
|
||||
<< ",location:" << BOOST_CURRENT_LOCATION << endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
tmp_exp =
|
||||
rule_json_.at("function").at("sample_Y").at("value").get<std::string>();
|
||||
exp_str_ = get_macro_replaced_exp(tmp_exp);
|
||||
if (exp_str_ != "") {
|
||||
int ret = expr_engine_->registerExpression("sample_Y", exp_str_);
|
||||
if (ret != 0) return -1;
|
||||
logger_->Debug() << "sample_Y:" << exp_str_ << "="
|
||||
<< expr_engine_->evaluate("sample_Y") << endl;
|
||||
if (exp_result_ == nullptr && exp_str_ != "") {
|
||||
try {
|
||||
exp_result_ =
|
||||
std::make_unique<mix_cc::matheval::Expression>(exp_str_, &mm_vars);
|
||||
logger_->Debug() << "sample_Y:" << exp_str_ << "="
|
||||
<< exp_result_->evaluate() << endl;
|
||||
} catch (const std::exception &e) {
|
||||
logger_->Error() << "sample_Y:" << exp_str_ << "计算出错:" << e.what()
|
||||
<< ",location:" << BOOST_CURRENT_LOCATION << endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
tmp_exp = rule_json_.at("function")
|
||||
@ -161,8 +173,8 @@ int ExpSample2D::reload_samples() {
|
||||
}
|
||||
|
||||
bool ExpSample2D::check_polyFit() {
|
||||
double X = expr_engine_->evaluate("sample_X"); /* SampleX*/
|
||||
double Y = expr_engine_->evaluate("sample_Y"); /* SampleY*/
|
||||
double X = exp_feedback_->evaluate(); /* SampleX*/
|
||||
double Y = exp_result_->evaluate(); /* SampleY*/
|
||||
double Y_Fit = PolyFitValue(X, this->fit_coefs_, this->orders_);
|
||||
limit_down_ = Y_Fit - abs(Y_Fit) * scale_;
|
||||
limit_up_ = Y_Fit + abs(Y_Fit) * scale_;
|
||||
@ -195,9 +207,9 @@ double ExpSample2D::PolyFitValue(double x, std::vector<double> &fit_coefs,
|
||||
|
||||
bool ExpSample2D::check_pear() {
|
||||
if (this->data_len_ < min_len_) {
|
||||
if (expr_engine_->evaluateBool("act")) {
|
||||
SampleX_.push_back(expr_engine_->evaluate("sample_X"));
|
||||
SampleY_.push_back(expr_engine_->evaluate("sample_Y"));
|
||||
if (exp_act_->evaluate()) {
|
||||
SampleX_.push_back(exp_feedback_->evaluate());
|
||||
SampleY_.push_back(exp_result_->evaluate());
|
||||
data_len_ = SampleX_.size();
|
||||
} else {
|
||||
reset_SampleXY();
|
||||
@ -419,9 +431,9 @@ void ExpSample2D::task_mon_pro() {
|
||||
for (auto i = 0; i < queried_data_.rows(); i++) {
|
||||
refresh_exp_vars_ihd(i);
|
||||
if (data_len_ < MAX_STORAGE_SIZE) {
|
||||
if (expr_engine_->evaluateBool("act")) {
|
||||
SampleX_.push_back(expr_engine_->evaluate("sample_X"));
|
||||
SampleY_.push_back(expr_engine_->evaluate("sample_Y"));
|
||||
if (exp_act_->evaluate()) {
|
||||
SampleX_.push_back(exp_feedback_->evaluate());
|
||||
SampleY_.push_back(exp_result_->evaluate());
|
||||
data_len_ = SampleX_.size();
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -198,7 +198,7 @@ int ExpTimes::update_times() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
this->act_triggered_ = expr_engine_->evaluateBool("act");
|
||||
this->act_triggered_ = this->exp_act_->evaluate();
|
||||
/*出现次数累计*/
|
||||
if (this->exp_type_ == ExpType::OccTimesAcc) {
|
||||
if (this->act_triggered_) {
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
#include <eqpalg/algs/feedback_alg.h>
|
||||
#include <eqpalg/feature_extraction/daa.h>
|
||||
#include <eqpalg/utility/build_alarm_info.h>
|
||||
#include <shm/RuleStatShm.h>
|
||||
|
||||
FeedbackAlg::FeedbackAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId, size_t exp_type)
|
||||
: ExpBase(name, rule_json, ruleId, exp_type),
|
||||
hasBoundCheck_(exp_type == ExpType::CondBound) {
|
||||
logger_.reset(new LOG("FeedbackAlg:" + rule_name_, AUTO_CATCH_PID));
|
||||
}
|
||||
|
||||
FeedbackAlg::~FeedbackAlg() = default;
|
||||
|
||||
void FeedbackAlg::doInitExtend() {
|
||||
if (hasBoundCheck_) {
|
||||
reload_config_up_down();
|
||||
reload_ci_dist();
|
||||
last_load_time_ = std::chrono::system_clock::now();
|
||||
stat_collector_.configure(rule_id_, rule_name_, dist_mode_, is_learning_);
|
||||
}
|
||||
}
|
||||
|
||||
AlarmInfo FeedbackAlg::doMonProc() {
|
||||
double result_value = expr_engine_->evaluate("act");
|
||||
|
||||
// CondBound: filter + statistics
|
||||
if (hasBoundCheck_) {
|
||||
bool filter_ok = true;
|
||||
try {
|
||||
filter_ok = expr_engine_->evaluateBool("feedback");
|
||||
} catch (...) {}
|
||||
if (is_learning_ && filter_ok) {
|
||||
rule_stat_.current_value = result_value;
|
||||
SingletonTemp<EqpStat>::GetInstance().add_stat_values(rule_id_, result_value);
|
||||
}
|
||||
}
|
||||
|
||||
bool triggered = static_cast<bool>(result_value);
|
||||
|
||||
// === FbStateMachine ===
|
||||
auto [fbState, needFunReset] = fb_fsm_.update(
|
||||
triggered, now_time_, expr_engine_->vars(), m_tags.size());
|
||||
|
||||
if (needFunReset) {
|
||||
expr_engine_->markFunVarsNeedReset();
|
||||
}
|
||||
|
||||
if (fbState == FbState::Started) {
|
||||
query_time_range_.set_left(now_time_);
|
||||
return AlarmInfo{};
|
||||
}
|
||||
|
||||
if (fbState == FbState::NotHold) {
|
||||
return AlarmInfo{};
|
||||
}
|
||||
|
||||
if (fbState == FbState::InProgress) {
|
||||
bool fbCond = expr_engine_->evaluateBool("feedback");
|
||||
bool done = fb_fsm_.checkFeedback(fbCond, now_time_, expr_engine_->vars());
|
||||
if (done) {
|
||||
query_time_range_.set_left(
|
||||
query_time_range_.get_right() -
|
||||
std::chrono::milliseconds(static_cast<int>(expr_engine_->vars()["time"])));
|
||||
result_value = expr_engine_->evaluate("result");
|
||||
expr_engine_->markFunVarsNeedReset();
|
||||
rule_stat_.limit_down = bound_checker_.limitDown();
|
||||
rule_stat_.limit_up = bound_checker_.limitUp();
|
||||
rule_stat_.current_value = result_value;
|
||||
|
||||
if (hasBoundCheck_) {
|
||||
if (is_learning_) {
|
||||
SingletonTemp<EqpStat>::GetInstance().add_stat_values(
|
||||
rule_id_, result_value);
|
||||
}
|
||||
if (bound_checker_.isOutOfBounds(result_value)) {
|
||||
rule_stat_.alarm_value = result_value;
|
||||
std::string msg;
|
||||
if (fb_fsm_.isTimeMode()) {
|
||||
msg = error_str_ + ":" + DAA::double2str(result_value) +
|
||||
"ms,时间范围:[0," + DAA::double2str(bound_checker_.limitUp()) + "] ms";
|
||||
} else {
|
||||
msg = error_str_ + ":" + DAA::double2str(result_value) + unit_ +
|
||||
",合理区间:[" + DAA::double2strLimit(bound_checker_.limitDown()) +
|
||||
"," + DAA::double2strLimit(bound_checker_.limitUp()) + "]" + unit_;
|
||||
}
|
||||
return utility::build_alarm_info(
|
||||
utility::get_msg_level(bound_checker_.limitDown(),
|
||||
bound_checker_.limitUp(), result_value),
|
||||
rule_id_, rule_name_, "EXP4", msg, get_alarm_time());
|
||||
}
|
||||
} else {
|
||||
if (static_cast<bool>(result_value)) {
|
||||
rule_stat_.alarm_value = result_value;
|
||||
auto msg = rule_name_ + " " + error_str_;
|
||||
return utility::build_alarm_info(MsgLevel::ERROR, rule_id_,
|
||||
rule_name_, "EXP3", msg,
|
||||
get_alarm_time());
|
||||
}
|
||||
}
|
||||
}
|
||||
return AlarmInfo{};
|
||||
}
|
||||
|
||||
if (fbState == FbState::Timeout) {
|
||||
if (fb_fsm_.isTimeMode()) {
|
||||
std::string msg = rule_name_ + " 反馈超时:" +
|
||||
std::to_string(time_out_.count()) + " ms";
|
||||
return utility::build_alarm_info(MsgLevel::ERROR, rule_id_, rule_name_,
|
||||
"EXPACT", msg, query_time_range_);
|
||||
}
|
||||
return AlarmInfo{};
|
||||
}
|
||||
|
||||
return AlarmInfo{};
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
#include <eqpalg/algs/exp_base.h>
|
||||
|
||||
class FeedbackAlg : public ExpBase {
|
||||
public:
|
||||
FeedbackAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId, size_t exp_type);
|
||||
~FeedbackAlg() override;
|
||||
|
||||
protected:
|
||||
AlarmInfo doMonProc() override;
|
||||
void doInitExtend() override;
|
||||
|
||||
private:
|
||||
bool hasBoundCheck_; // CondBound (exp_type 4) vs Logic (exp_type 3)
|
||||
};
|
||||
@ -78,7 +78,7 @@ AlarmInfo GlitchDetection::exec_mon() {
|
||||
data_index_ = 0;
|
||||
run_time_range_.set_left(this->now_time_);
|
||||
}
|
||||
data_[data_index_] = expr_engine_->evaluate("dataX");
|
||||
data_[data_index_] = exp_mpdule_ptr_->get_value("dataX");
|
||||
data_index_++;
|
||||
return out_alarm;
|
||||
}
|
||||
@ -117,7 +117,7 @@ int GlitchDetection::load_exp() {
|
||||
auto tmp_exp =
|
||||
rule_json_.at("function").at("dataX").at("value").get<std::string>();
|
||||
exp_str_ = get_macro_replaced_exp(tmp_exp);
|
||||
expr_engine_->registerExpression("dataX", exp_str_);
|
||||
exp_mpdule_ptr_->add_exp("dataX", exp_str_);
|
||||
/*数据长度*/
|
||||
auto tmp_data_size = rule_json_.at("function")
|
||||
.at("dataX")
|
||||
@ -137,10 +137,8 @@ int GlitchDetection::load_exp() {
|
||||
}
|
||||
bool GlitchDetection::get_prr() {
|
||||
if (this->prr_ == 1) {
|
||||
this->refresh_now_time();
|
||||
mix_cc::time_range_t dummy_range;
|
||||
expr_engine_->refreshFromMemory(this->now_time_, dummy_range);
|
||||
bool prr_result = expr_engine_->evaluateBool("pre_result");
|
||||
exp_mpdule_ptr_->update();
|
||||
bool prr_result = (bool)exp_mpdule_ptr_->get_value("pre_result");
|
||||
this->now_prr_ = prr_result;
|
||||
logger_->Debug() << "ruleid:" << this->rule_id_
|
||||
<< ",rulename:" << this->rule_name_
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
#include <eqpalg/algs/logic_alg.h>
|
||||
#include <eqpalg/utility/build_alarm_info.h>
|
||||
|
||||
LogicAlg::LogicAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId)
|
||||
: ExpBase(name, rule_json, ruleId, ExpType::Logic) {
|
||||
logger_.reset(new LOG("LogicAlg:" + rule_name_, AUTO_CATCH_PID));
|
||||
}
|
||||
|
||||
LogicAlg::~LogicAlg() = default;
|
||||
|
||||
AlarmInfo LogicAlg::doMonProc() {
|
||||
double result_value = expr_engine_->evaluate("act");
|
||||
|
||||
if (static_cast<bool>(result_value)) {
|
||||
rule_stat_.alarm_value = result_value;
|
||||
auto msg = rule_name_ + " " + error_str_;
|
||||
query_time_range_.set_left(query_time_range_.get_right() - delay_time_);
|
||||
expr_engine_->markFunVarsNeedReset();
|
||||
return utility::build_alarm_info(MsgLevel::ERROR, rule_id_, rule_name_,
|
||||
"EXP1", msg, get_alarm_time());
|
||||
}
|
||||
return AlarmInfo{};
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
#include <eqpalg/algs/exp_base.h>
|
||||
|
||||
class LogicAlg : public ExpBase {
|
||||
public:
|
||||
LogicAlg(const std::string& name, const mix_cc::json& rule_json,
|
||||
const std::string& ruleId);
|
||||
~LogicAlg() override;
|
||||
|
||||
protected:
|
||||
AlarmInfo doMonProc() override;
|
||||
};
|
||||
@ -71,7 +71,7 @@ Roller3::~Roller3() {
|
||||
|
||||
AlarmInfo Roller3::mon_proc() {
|
||||
|
||||
if (!expr_engine_->evaluateBool("act")) {
|
||||
if ((bool)exp_act_->evaluate() == false) {
|
||||
logger_->Debug() << "前提条件不满足!" << std::endl;
|
||||
return AlarmInfo{};
|
||||
}
|
||||
@ -227,7 +227,9 @@ int Roller3::init_X_exp() {
|
||||
exp_str_ = get_macro_replaced_exp(tmp_exp);
|
||||
res += init_hold_exp_str(exp_str_);
|
||||
|
||||
// ExpressionEngine::registerExpression() now handles FunVars processing internally.
|
||||
auto fun_res = fun_vars_.add_exp_str(exp_str_, &mm_vars);
|
||||
res += fun_res.first ? 0 : -1;
|
||||
exp_str_ = fun_res.second;
|
||||
feedback_mode_ = false;
|
||||
auto messy_code = exp_messy_code_check(exp_str_);
|
||||
if (messy_code == -1) {
|
||||
@ -303,19 +305,31 @@ int Roller3::init_X_exp() {
|
||||
}
|
||||
}
|
||||
value_num_ = tag_seq_.size();
|
||||
if (exp_str_ != "") {
|
||||
int reg_ret = expr_engine_->registerExpression("act", exp_str_);
|
||||
if (reg_ret != 0) {
|
||||
if (exp_act_ == nullptr && exp_str_ != "") {
|
||||
try {
|
||||
exp_act_ =
|
||||
std::make_unique<mix_cc::matheval::Expression>(exp_str_, &mm_vars);
|
||||
logger_->Debug() << "exp_act:" << exp_str_ << "=" << exp_act_->evaluate()
|
||||
<< endl;
|
||||
} catch (const std::exception &e) {
|
||||
logger_->Error() << "exp_act:" << exp_str_ << "计算出错:" << e.what()
|
||||
<< ",location:" << BOOST_CURRENT_LOCATION << endl;
|
||||
this->error_code_list_.push_back(
|
||||
{ErrorType::CalError, ErrorLocation::ActExp});
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (tags_exp_ != "") {
|
||||
int reg_ret = expr_engine_->registerExpression("result", tags_exp_);
|
||||
if (reg_ret != 0) {
|
||||
if (exp_result_ == nullptr && tags_exp_ != "") {
|
||||
try {
|
||||
exp_result_ =
|
||||
std::make_unique<mix_cc::matheval::Expression>(tags_exp_, &mm_vars);
|
||||
logger_->Debug() << "tags_exp_:" << tags_exp_ << "="
|
||||
<< exp_result_->evaluate() << endl;
|
||||
} catch (const std::exception &e) {
|
||||
logger_->Error() << "tags_exp_:" << tags_exp_ << "计算出错:" << e.what()
|
||||
<< ",location:" << BOOST_CURRENT_LOCATION << endl;
|
||||
this->error_code_list_.push_back(
|
||||
{ErrorType::CalError, ErrorLocation::ResultExp});
|
||||
{ErrorType::CalError, ErrorLocation::ActExp});
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,8 +182,8 @@ std::vector<AlarmInfo> TrendSlope2::exec_task(mix_cc::time_range_t time_range) {
|
||||
|
||||
bool TrendSlope2::get_prr2() {
|
||||
if (this->prr_ == 1) {
|
||||
expr_engine_->refreshFromIhdRow(0, queried_data_, queried_time_, now_time_, query_time_range_);
|
||||
bool prr_result = expr_engine_->evaluateBool("pre_result");
|
||||
exp_mpdule_ptr_->update(queried_data_, queried_time_);
|
||||
bool prr_result = (bool)exp_mpdule_ptr_->get_value("pre_result");
|
||||
this->now_prr_ = prr_result;
|
||||
logger_->Debug() << "ruleid:" << this->rule_id_
|
||||
<< ",rulename:" << this->rule_name_
|
||||
|
||||
@ -190,8 +190,8 @@ std::vector<AlarmInfo> TrendSlope3::exec_task(mix_cc::time_range_t time_range) {
|
||||
|
||||
bool TrendSlope3::get_prr2() {
|
||||
if (this->prr_ == 1) {
|
||||
expr_engine_->refreshFromIhdRow(0, queried_data_, queried_time_, now_time_, query_time_range_);
|
||||
bool prr_result = expr_engine_->evaluateBool("pre_result");
|
||||
exp_mpdule_ptr_->update(queried_data_, queried_time_);
|
||||
bool prr_result = (bool)exp_mpdule_ptr_->get_value("pre_result");
|
||||
this->now_prr_ = prr_result;
|
||||
logger_->Debug() << "ruleid:" << this->rule_id_
|
||||
<< ",rulename:" << this->rule_name_
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
#include <eqpalg/algs/roller2.h>
|
||||
#include <eqpalg/algs/trend_slope2.h>
|
||||
#include <eqpalg/build_algorithm.h>
|
||||
#include <eqpalg/algs/bound_alg.h>
|
||||
#include <eqpalg/algs/bound_hold_alg.h>
|
||||
// for default case
|
||||
#include <eqpalg/algs/exp_base.h>
|
||||
#include <eqpalg/algs/exp_bound.h>
|
||||
#include <eqpalg/algs/exp_sample2D.h>
|
||||
#include <eqpalg/algs/exp_times.h>
|
||||
#include <eqpalg/algs/fault_code.h>
|
||||
#include <eqpalg/algs/feedback_alg.h>
|
||||
#include <eqpalg/algs/glitch_detection.h>
|
||||
#include <eqpalg/algs/logic_alg.h>
|
||||
#include <eqpalg/algs/null.h>
|
||||
#include <eqpalg/algs/roller3.h>
|
||||
#include <eqpalg/algs/trend_slope3.h>
|
||||
@ -21,19 +19,11 @@ std::unique_ptr<AlgBase> build_algorithm(int algId, const string &ruleId,
|
||||
LOG d("build_algorithm");
|
||||
switch (algId) {
|
||||
case 1:
|
||||
return std::make_unique<LogicAlg>(name, rule_json, ruleId);
|
||||
break;
|
||||
case 2:
|
||||
return std::make_unique<BoundAlg>(name, rule_json, ruleId);
|
||||
break;
|
||||
case 3:
|
||||
return std::make_unique<FeedbackAlg>(name, rule_json, ruleId, algId);
|
||||
break;
|
||||
case 4:
|
||||
return std::make_unique<FeedbackAlg>(name, rule_json, ruleId, algId);
|
||||
break;
|
||||
case 5:
|
||||
return std::make_unique<BoundHoldAlg>(name, rule_json, ruleId);
|
||||
return std::make_unique<ExpBase>(name, rule_json, ruleId, algId);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
|
||||
@ -57,7 +57,15 @@ struct DistMode {
|
||||
static const int Online = 1;
|
||||
static const int Offline = 2;
|
||||
};
|
||||
// DetectMode 已提取至 eqpalg/utility/bound_checker.h(enum class)
|
||||
/**
|
||||
* @brief 检测模式
|
||||
*/
|
||||
struct DetectMode {
|
||||
static const int Default = 0;
|
||||
static const int OnlyLeft = 1;
|
||||
static const int OnlyRight = 2;
|
||||
static const int ErrorMode = 3;
|
||||
};
|
||||
/**
|
||||
* @brief 规则运行前提条件
|
||||
*/
|
||||
|
||||
@ -1,915 +0,0 @@
|
||||
// eqpalg/test/test_algorithms.cc
|
||||
// 核心算法单元测试 — LogicAlg/BoundAlg/BoundHoldAlg/FeedbackAlg/ExpTimes/FaultCode/Roller3
|
||||
//
|
||||
// 设计说明:
|
||||
// 算法子类 (LogicAlg/BoundAlg/...) 的构造函数依赖 AlgBase::init(),
|
||||
// 而后者又依赖 CMemVar(共享内存)等运行时基础设施,在开发机上可能不可用。
|
||||
// 因此本测试文件不尝试完整构造算法对象,而是:
|
||||
// 1. 测试已从算法中提取出的独立组件(ExpressionEngine, BoundChecker, FbStateMachine)
|
||||
// 2. 用独立的辅助函数测试算法特有的核心逻辑(bit 提取、tag 号解析、中位数计算等)
|
||||
// 3. 通过组件组合模拟 doMonProc() 的控制流,验证集成行为
|
||||
//
|
||||
// 已提取组件在 eqpalg/test/ 下的其他文件中已有完整单元测试:
|
||||
// - test_expression_engine.cc — ExpressionEngine
|
||||
// - test_fb_state_machine.cc — FbStateMachine
|
||||
// 本文件在这些已有覆盖的基础上增加算法级集成测试和算法特有逻辑的测试。
|
||||
|
||||
#include "test_harness.h"
|
||||
#include <eqpalg/utility/expression_engine.h>
|
||||
#include <eqpalg/utility/bound_checker.h>
|
||||
#include <eqpalg/utility/fb_state_machine.h>
|
||||
#include <mix_cc/json.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using json = mix_cc::json;
|
||||
|
||||
// ============================================================================
|
||||
// 测试辅助:构建各算法类型的合法 JSON 配置
|
||||
// ============================================================================
|
||||
|
||||
// 构建 LogicAlg (ExpType::Logic = 1) 的最小合法 JSON
|
||||
static json makeLogicConfig() {
|
||||
return json::parse(R"({
|
||||
"tags": {"tag1": {"value": "TEST_TAG"}},
|
||||
"trigger": {"value": "100"},
|
||||
"function": {
|
||||
"result": {"value": "tag1 > 0.5"}
|
||||
},
|
||||
"output": {
|
||||
"error": {"value": "逻辑异常"}
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
// 构建 BoundAlg (ExpType::Bound = 2) 的最小合法 JSON
|
||||
static json makeBoundConfig() {
|
||||
return json::parse(R"({
|
||||
"tags": {"tag1": {"value": "TEST_TAG"}},
|
||||
"trigger": {"value": "100"},
|
||||
"datasource": {"value": "1"},
|
||||
"function": {
|
||||
"result": {
|
||||
"value": "tag1",
|
||||
"param": {
|
||||
"limit_down": {"value": "0"},
|
||||
"limit_up": {"value": "100"},
|
||||
"unit": {"value": "C"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"error": {"value": "温度超限"}
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
// 构建 FeedbackAlg 逻辑型 (ExpType::FeedbackLogic = 3) 的最小合法 JSON
|
||||
static json makeFeedbackLogicConfig() {
|
||||
return json::parse(R"({
|
||||
"tags": {"tag1": {"value": "TEST_TAG"}},
|
||||
"trigger": {"value": "100"},
|
||||
"function": {
|
||||
"action_start": {"value": "tag1 > 0.5"},
|
||||
"action_end": {
|
||||
"value": "tag1 < 0.1",
|
||||
"param": {
|
||||
"timeout": {"value": "60000"},
|
||||
"hold": {"value": "0"}
|
||||
}
|
||||
},
|
||||
"result": {"value": "1"}
|
||||
},
|
||||
"output": {
|
||||
"error": {"value": "反馈动作报警"}
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
// 构建 FeedbackAlg 上下限型 (ExpType::FeedbackBound = 4) 的最小合法 JSON
|
||||
static json makeFeedbackBoundConfig() {
|
||||
return json::parse(R"({
|
||||
"tags": {"tag1": {"value": "TEST_TAG"}},
|
||||
"trigger": {"value": "100"},
|
||||
"datasource": {"value": "1"},
|
||||
"function": {
|
||||
"action_start": {
|
||||
"value": "tag1",
|
||||
"param": {
|
||||
"limit_down": {"value": "0"},
|
||||
"limit_up": {"value": "100"},
|
||||
"unit": {"value": "C"}
|
||||
}
|
||||
},
|
||||
"action_end": {
|
||||
"value": "tag1 < 50",
|
||||
"param": {
|
||||
"timeout": {"value": "60000"},
|
||||
"hold": {"value": "0"}
|
||||
}
|
||||
},
|
||||
"result": {"value": "1"}
|
||||
},
|
||||
"output": {
|
||||
"error": {"value": "温度反馈报警"}
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
// 构建 BoundHoldAlg (ExpType::BoundHold = 5) 的最小合法 JSON
|
||||
static json makeBoundHoldConfig() {
|
||||
return json::parse(R"({
|
||||
"tags": {"tag1": {"value": "TEST_TAG"}},
|
||||
"trigger": {"value": "100"},
|
||||
"datasource": {"value": "1"},
|
||||
"function": {
|
||||
"result": {
|
||||
"value": "tag1",
|
||||
"param": {
|
||||
"limit_down": {"value": "0"},
|
||||
"limit_up": {"value": "100"},
|
||||
"unit": {"value": "C"},
|
||||
"hold_time": {"value": "5000"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"error": {"value": "温度持续超限"}
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 表达式测试辅助环境(与 test_expression_engine.cc 一致)
|
||||
// ============================================================================
|
||||
struct TestEnv {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags;
|
||||
ExpressionEngine engine;
|
||||
|
||||
TestEnv() : engine(vars, tags) {
|
||||
tags = {"tag1", "tag2"};
|
||||
vars["tag1"] = 10.0;
|
||||
vars["tag2"] = 20.0;
|
||||
vars["p1"] = 10.0;
|
||||
vars["p2"] = 20.0;
|
||||
vars["now"] = 0;
|
||||
vars["stime"] = 0;
|
||||
vars["time"] = 0;
|
||||
vars["etime"] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 第一部分:LogicAlg 核心逻辑测试
|
||||
// ============================================================================
|
||||
// LogicAlg::doMonProc() 的核心逻辑:
|
||||
// 1. exp_act_->evaluate() → bool (trigger)
|
||||
// 2. trigger==true → alarm
|
||||
// 3. trigger==false → 无 alarm
|
||||
// 下面通过 ExpressionEngine 模拟此流程。
|
||||
|
||||
TEST(logic_alg_trigger_true_produces_alarm) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 1.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 0.5");
|
||||
|
||||
// trigger 为 true → 应产生报警
|
||||
bool triggered = engine.evaluateBool("act");
|
||||
CHECK_EQ(triggered, true);
|
||||
}
|
||||
|
||||
TEST(logic_alg_trigger_false_no_alarm) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 0.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 0.5");
|
||||
|
||||
bool triggered = engine.evaluateBool("act");
|
||||
CHECK_EQ(triggered, false);
|
||||
}
|
||||
|
||||
TEST(logic_alg_complex_expression) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1", "tag2"};
|
||||
vars["tag1"] = 3.0;
|
||||
vars["tag2"] = 7.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 2 && tag2 < 10");
|
||||
|
||||
CHECK_EQ(engine.evaluateBool("act"), true);
|
||||
}
|
||||
|
||||
TEST(logic_alg_and_short_circuit_false) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1", "tag2"};
|
||||
vars["tag1"] = 0.0;
|
||||
vars["tag2"] = 7.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 2 && tag2 < 10");
|
||||
|
||||
CHECK_EQ(engine.evaluateBool("act"), false);
|
||||
}
|
||||
|
||||
TEST(logic_alg_or_expression) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1", "tag2"};
|
||||
vars["tag1"] = 0.0;
|
||||
vars["tag2"] = 100.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 2 || tag2 > 50");
|
||||
|
||||
CHECK_EQ(engine.evaluateBool("act"), true);
|
||||
}
|
||||
|
||||
TEST(logic_alg_negation_operator) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 0.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "!tag1");
|
||||
|
||||
CHECK_EQ(engine.evaluateBool("act"), true);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 第二部分:BoundAlg — BoundChecker 检测器测试
|
||||
// ============================================================================
|
||||
// BoundAlg::doMonProc() 使用 BoundChecker 判断 value 是否超出 [down, up]。
|
||||
// 哨兵值 -32768 表示"无下限",32767/32768 表示"无上限"。
|
||||
|
||||
TEST(bound_checker_detects_out_of_upper) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(0.0, 100.0);
|
||||
CHECK_EQ(bc.isOutOfBounds(150.0), true);
|
||||
CHECK_EQ(bc.isOutOfBounds(50.0), false);
|
||||
}
|
||||
|
||||
TEST(bound_checker_detects_out_of_lower) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(10.0, 100.0);
|
||||
CHECK_EQ(bc.isOutOfBounds(5.0), true);
|
||||
}
|
||||
|
||||
TEST(bound_checker_only_right_mode_sentinel) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(-32768.0, 100.0); // -32768 = no lower bound
|
||||
CHECK_EQ(bc.detectMode(), DetectMode::OnlyRight);
|
||||
CHECK_EQ(bc.isOutOfBounds(-999.0), false); // below lower is OK
|
||||
CHECK_EQ(bc.isOutOfBounds(150.0), true); // above upper triggers
|
||||
}
|
||||
|
||||
TEST(bound_checker_only_left_mode_sentinel) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(10.0, 32767.0); // 32767 = no upper bound
|
||||
CHECK_EQ(bc.detectMode(), DetectMode::OnlyLeft);
|
||||
CHECK_EQ(bc.isOutOfBounds(5.0), true); // below lower triggers
|
||||
CHECK_EQ(bc.isOutOfBounds(999.0), false); // above upper is OK (sentinel)
|
||||
}
|
||||
|
||||
TEST(bound_checker_only_left_mode_sentinel_32768) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(10.0, 32768.0); // 32768 also = no upper bound
|
||||
CHECK_EQ(bc.detectMode(), DetectMode::OnlyLeft);
|
||||
CHECK_EQ(bc.isOutOfBounds(5.0), true);
|
||||
CHECK_EQ(bc.isOutOfBounds(999.0), false);
|
||||
}
|
||||
|
||||
TEST(bound_checker_default_bilateral) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(10.0, 20.0);
|
||||
CHECK_EQ(bc.detectMode(), DetectMode::Default);
|
||||
CHECK_EQ(bc.isOutOfBounds(5.0), true);
|
||||
CHECK_EQ(bc.isOutOfBounds(15.0), false);
|
||||
CHECK_EQ(bc.isOutOfBounds(25.0), true);
|
||||
}
|
||||
|
||||
TEST(bound_checker_error_mode) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(-32768.0, -32768.0); // both sentinels
|
||||
CHECK_EQ(bc.detectMode(), DetectMode::ErrorMode);
|
||||
CHECK_EQ(bc.isOutOfBounds(50.0), false); // error mode: never alarm
|
||||
}
|
||||
|
||||
TEST(bound_checker_exact_boundary_default) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(10.0, 20.0);
|
||||
CHECK_EQ(bc.isOutOfBounds(10.0), false); // exactly on lower bound
|
||||
CHECK_EQ(bc.isOutOfBounds(20.0), false); // exactly on upper bound
|
||||
CHECK_EQ(bc.isOutOfBounds(10.00001), false);
|
||||
}
|
||||
|
||||
TEST(bound_checker_boundary_only_left) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(10.0, 32767.0);
|
||||
CHECK_EQ(bc.isOutOfBounds(10.0), false);
|
||||
CHECK_EQ(bc.isOutOfBounds(9.999), true);
|
||||
}
|
||||
|
||||
TEST(bound_checker_boundary_only_right) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(-32768.0, 100.0);
|
||||
CHECK_EQ(bc.isOutOfBounds(100.0), false);
|
||||
CHECK_EQ(bc.isOutOfBounds(100.001), true);
|
||||
}
|
||||
|
||||
TEST(bound_checker_isValid) {
|
||||
BoundChecker bc1;
|
||||
bc1.setLimits(0.0, 100.0);
|
||||
CHECK_EQ(bc1.isValid(), true);
|
||||
|
||||
BoundChecker bc2;
|
||||
bc2.setLimits(-32768.0, -32768.0);
|
||||
CHECK_EQ(bc2.isValid(), false);
|
||||
}
|
||||
|
||||
TEST(bound_checker_setDetectMode_override) {
|
||||
BoundChecker bc;
|
||||
bc.setLimits(-32768.0, 100.0);
|
||||
CHECK_EQ(bc.detectMode(), DetectMode::OnlyRight);
|
||||
|
||||
// manual override to Default
|
||||
bc.setDetectMode(DetectMode::Default);
|
||||
CHECK_EQ(bc.detectMode(), DetectMode::Default);
|
||||
// now both sides checked with sentinel values → out of lower triggers
|
||||
CHECK_EQ(bc.isOutOfBounds(-999.0), true);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 第三部分:BoundAlg 过滤表达式测试
|
||||
// ============================================================================
|
||||
// BoundAlg::checkFilter() 使用 exp_feedback_ 作为过滤条件,
|
||||
// 仅当条件为 true 时才进入统计累积。
|
||||
|
||||
TEST(bound_alg_filter_expression) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 150.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1");
|
||||
engine.registerExpression("feedback", "tag1 > 200"); // filter
|
||||
|
||||
// tag1=150 → filter is false → no statistics
|
||||
bool filter_ok = engine.evaluateBool("feedback");
|
||||
CHECK_EQ(filter_ok, false);
|
||||
|
||||
// tag1=250 → filter is true
|
||||
vars["tag1"] = 250.0;
|
||||
filter_ok = engine.evaluateBool("feedback");
|
||||
CHECK_EQ(filter_ok, true);
|
||||
}
|
||||
|
||||
TEST(bound_alg_filter_boundary) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 200.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("feedback", "tag1 >= 200");
|
||||
|
||||
// exactly at boundary
|
||||
CHECK_EQ(engine.evaluateBool("feedback"), true);
|
||||
|
||||
vars["tag1"] = 199.999;
|
||||
CHECK_EQ(engine.evaluateBool("feedback"), false);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 第四部分:BoundHoldAlg 保持时间逻辑测试
|
||||
// ============================================================================
|
||||
// BoundHoldAlg::doMonProc() 要求 value 持续超出限值 hold_time 后才报警。
|
||||
|
||||
TEST(bound_hold_time_logic) {
|
||||
double hold_time_ms = 5000.0; // 5 second hold
|
||||
auto start = std::chrono::system_clock::now();
|
||||
|
||||
// 首次检测 — 尚未到达保持时间
|
||||
auto now1 = start;
|
||||
auto elapsed1 = std::chrono::duration_cast<std::chrono::milliseconds>(now1 - start);
|
||||
CHECK(elapsed1 < std::chrono::milliseconds(static_cast<int>(hold_time_ms)));
|
||||
|
||||
// 达到保持时间后 — 可以报警
|
||||
auto now2 = start + std::chrono::milliseconds(static_cast<int>(hold_time_ms) + 100);
|
||||
auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(now2 - start);
|
||||
CHECK(elapsed2 > std::chrono::milliseconds(static_cast<int>(hold_time_ms)));
|
||||
|
||||
// hold_time <= delay_time → 立即报警
|
||||
bool alarm_with_short = (std::chrono::milliseconds(50) <= std::chrono::milliseconds(50)) ||
|
||||
(elapsed1 > std::chrono::milliseconds(static_cast<int>(hold_time_ms)));
|
||||
CHECK_EQ(alarm_with_short, true);
|
||||
|
||||
// Long hold — should alarm after time
|
||||
bool alarm_after_hold = elapsed2 > std::chrono::milliseconds(static_cast<int>(hold_time_ms));
|
||||
CHECK_EQ(alarm_after_hold, true);
|
||||
}
|
||||
|
||||
TEST(bound_hold_zero_hold_time) {
|
||||
double hold_time_ms = 0.0; // no hold → immediate alarm
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto now = start + std::chrono::milliseconds(1);
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
|
||||
|
||||
// hold_time == 0 → always alarm immediately
|
||||
CHECK(elapsed >= std::chrono::milliseconds(static_cast<int>(hold_time_ms)));
|
||||
}
|
||||
|
||||
TEST(bound_hold_long_delay) {
|
||||
double hold_time_ms = 60000.0; // 60 seconds
|
||||
auto start = std::chrono::system_clock::now();
|
||||
|
||||
// before hold time expires
|
||||
auto now1 = start + std::chrono::milliseconds(30000);
|
||||
auto elapsed1 = std::chrono::duration_cast<std::chrono::milliseconds>(now1 - start);
|
||||
CHECK(elapsed1 < std::chrono::milliseconds(static_cast<int>(hold_time_ms)));
|
||||
|
||||
// after hold time expires
|
||||
auto now2 = start + std::chrono::milliseconds(61000);
|
||||
auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(now2 - start);
|
||||
CHECK(elapsed2 > std::chrono::milliseconds(static_cast<int>(hold_time_ms)));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 第五部分:FeedbackAlg / FbStateMachine 集成测试
|
||||
// ============================================================================
|
||||
// FbStateMachine 的完整单元测试在 test_fb_state_machine.cc 中。
|
||||
// 本部分测试 FeedbackAlg::doMonProc() 的整体流程 ——
|
||||
// ExpressionEngine (exp_act_ + exp_feedback_) + FbStateMachine 的组合行为。
|
||||
|
||||
TEST(feedback_full_flow_start_to_done) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 1.0;
|
||||
vars["p1"] = 1.0;
|
||||
vars["now"] = 0;
|
||||
vars["stime"] = 0;
|
||||
vars["time"] = 0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 0.5");
|
||||
engine.registerExpression("feedback", "tag1 < 0.1");
|
||||
engine.registerExpression("result", "1");
|
||||
|
||||
FbStateMachine fsm;
|
||||
fsm.configure(false, std::chrono::milliseconds(60000));
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
vars["now"] = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
|
||||
// Step 1: Trigger → Started
|
||||
bool triggered = engine.evaluateBool("act");
|
||||
CHECK_EQ(triggered, true);
|
||||
|
||||
auto [state1, reset1] = fsm.update(triggered, now, vars, tags.size());
|
||||
CHECK(state1 == FbState::Started);
|
||||
CHECK_EQ(reset1, false);
|
||||
|
||||
// Step 2: Continue → InProgress (may take 2 cycles from Started)
|
||||
auto [state2, reset2] = fsm.update(true, now, vars, tags.size());
|
||||
if (state2 == FbState::Started) {
|
||||
// need one more cycle
|
||||
auto [state2b, reset2b] = fsm.update(true, now, vars, tags.size());
|
||||
CHECK(state2b == FbState::InProgress || state2b == FbState::Started);
|
||||
state2 = state2b;
|
||||
(void)reset2b;
|
||||
}
|
||||
CHECK(state2 == FbState::InProgress);
|
||||
CHECK_EQ(reset2, false);
|
||||
}
|
||||
|
||||
TEST(feedback_full_flow_not_hold) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 1.0;
|
||||
vars["p1"] = 1.0;
|
||||
vars["now"] = 0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 0.5");
|
||||
|
||||
FbStateMachine fsm;
|
||||
fsm.configure(true, std::chrono::milliseconds(60000)); // keep_mode=true
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
vars["now"] = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
|
||||
// Start the action (two cycles to reach InProgress)
|
||||
fsm.update(true, now, vars, tags.size());
|
||||
auto state2 = fsm.update(true, now, vars, tags.size()).state;
|
||||
if (state2 == FbState::Started) {
|
||||
state2 = fsm.update(true, now, vars, tags.size()).state;
|
||||
}
|
||||
|
||||
// Lose trigger with keep_mode → NotHold
|
||||
auto [state, reset] = fsm.update(false, now, vars, tags.size());
|
||||
if (state == FbState::NotHold) {
|
||||
CHECK_EQ(reset, true);
|
||||
}
|
||||
// InProgress is also valid if keep_mode transition hasn't triggered yet
|
||||
}
|
||||
|
||||
TEST(feedback_timeout_scenario) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 1.0;
|
||||
vars["p1"] = 1.0;
|
||||
vars["now"] = 0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 0.5");
|
||||
|
||||
FbStateMachine fsm;
|
||||
fsm.configure(false, std::chrono::milliseconds(100)); // short 100ms timeout
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
vars["now"] = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
|
||||
// Trigger → Started → InProgress
|
||||
fsm.update(true, now, vars, tags.size());
|
||||
auto state = fsm.update(true, now, vars, tags.size()).state;
|
||||
if (state == FbState::Started) {
|
||||
state = fsm.update(true, now, vars, tags.size()).state;
|
||||
}
|
||||
CHECK(state == FbState::InProgress || state == FbState::Timeout);
|
||||
|
||||
// Advance past timeout
|
||||
now += std::chrono::milliseconds(200);
|
||||
vars["now"] = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
|
||||
auto result = fsm.update(true, now, vars, tags.size());
|
||||
CHECK(result.state == FbState::Timeout);
|
||||
CHECK_EQ(result.funVarsNeedReset, true);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 第六部分:ExpTimes 累积逻辑测试
|
||||
// ============================================================================
|
||||
// ExpTimes::update_times() 在 exp_act_ 为 true 时累加次数/时间。
|
||||
|
||||
TEST(exp_times_occurrence_counting_logic) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 1.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1");
|
||||
|
||||
int shear_times = 0;
|
||||
int max_times = 5;
|
||||
|
||||
// 模拟 6 个周期,每个周期 exp_act_ 为 true
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (engine.evaluateBool("act")) {
|
||||
shear_times++;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_EQ(shear_times, 6);
|
||||
CHECK_EQ(shear_times > max_times, true); // 应报警
|
||||
}
|
||||
|
||||
TEST(exp_times_occurrence_below_threshold) {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags = {"tag1"};
|
||||
vars["tag1"] = 1.0;
|
||||
|
||||
ExpressionEngine engine(vars, tags);
|
||||
engine.registerExpression("act", "tag1 > 0.5");
|
||||
|
||||
int shear_times = 0;
|
||||
int max_times = 5;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (engine.evaluateBool("act")) {
|
||||
shear_times++;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_EQ(shear_times, 3);
|
||||
CHECK_EQ(shear_times > max_times, false); // under threshold, no alarm
|
||||
}
|
||||
|
||||
TEST(exp_times_time_accumulation_logic) {
|
||||
// 模拟时间累积:触发→累积时间→取消触发→停止
|
||||
bool act_started = false;
|
||||
double running_time = 0.0;
|
||||
auto start_time = std::chrono::system_clock::now();
|
||||
|
||||
// 触发变为 true
|
||||
act_started = true;
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
|
||||
// 时间推进
|
||||
auto t2 = t1 + std::chrono::milliseconds(5000);
|
||||
double elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
|
||||
running_time += elapsed / (60.0 * 60000); // ms → hours
|
||||
|
||||
// 触发变为 false
|
||||
act_started = false;
|
||||
|
||||
CHECK_FLOAT_EQ(running_time, 5000.0 / 3600000.0, 0.0001);
|
||||
CHECK(running_time > 0.0);
|
||||
}
|
||||
|
||||
TEST(exp_times_time_multiple_intervals) {
|
||||
double running_time = 0.0;
|
||||
auto t0 = std::chrono::system_clock::now();
|
||||
|
||||
// Interval 1: 3000ms
|
||||
auto t1 = t0 + std::chrono::milliseconds(3000);
|
||||
running_time += std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count()
|
||||
/ (60.0 * 60000);
|
||||
|
||||
// Interval 2: 7000ms
|
||||
auto t2 = t1 + std::chrono::milliseconds(7000);
|
||||
running_time += std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count()
|
||||
/ (60.0 * 60000);
|
||||
|
||||
double expected = 10000.0 / 3600000.0;
|
||||
CHECK_FLOAT_EQ(running_time, expected, 0.0001);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 第七部分:FaultCode 位提取 (gitbit) 测试
|
||||
// ============================================================================
|
||||
// FaultCode::exec_mon() 使用 gitbit 模板函数解析故障代码的各个 bit。
|
||||
|
||||
template <typename T>
|
||||
static T gitbit(T value, T sub) {
|
||||
return int(value) >> int(sub) & 1;
|
||||
}
|
||||
|
||||
TEST(fault_code_gitbit_extraction) {
|
||||
// Bit 3 of 0b00001000 = 8
|
||||
CHECK_EQ(gitbit(8, 3), 1);
|
||||
CHECK_EQ(gitbit(8, 0), 0);
|
||||
CHECK_EQ(gitbit(8, 1), 0);
|
||||
CHECK_EQ(gitbit(8, 2), 0);
|
||||
|
||||
// Bit 0 of 1
|
||||
CHECK_EQ(gitbit(1, 0), 1);
|
||||
|
||||
// Complex code: 0b1010 = 10
|
||||
CHECK_EQ(gitbit(10, 0), 0); // bit 0
|
||||
CHECK_EQ(gitbit(10, 1), 1); // bit 1
|
||||
CHECK_EQ(gitbit(10, 2), 0); // bit 2
|
||||
CHECK_EQ(gitbit(10, 3), 1); // bit 3
|
||||
|
||||
// All bits 0-15 for code=0
|
||||
for (int i = 0; i < 16; i++) {
|
||||
CHECK_EQ(gitbit(0, i), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(fault_code_bit_parsing_all_bits) {
|
||||
// A code with multiple bits set: 0b1111 = 15
|
||||
int code = 15;
|
||||
int set_bits = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (gitbit(code, i)) set_bits++;
|
||||
}
|
||||
CHECK_EQ(set_bits, 4); // bits 0,1,2,3
|
||||
}
|
||||
|
||||
TEST(fault_code_bit_15_set) {
|
||||
// A code with bit 15 set: 0x8000 = 32768
|
||||
int code = 32768;
|
||||
CHECK_EQ(gitbit(code, 15), 1);
|
||||
CHECK_EQ(gitbit(code, 0), 0);
|
||||
CHECK_EQ(gitbit(code, 7), 0);
|
||||
}
|
||||
|
||||
TEST(fault_code_all_bits_set_16bit) {
|
||||
// 0xFFFF = 65535, all lower 16 bits set
|
||||
int code = 65535;
|
||||
int set_bits = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (gitbit(code, i)) set_bits++;
|
||||
}
|
||||
CHECK_EQ(set_bits, 16);
|
||||
}
|
||||
|
||||
TEST(fault_code_single_bit_each_position) {
|
||||
// Verify each individual bit position
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int code = 1 << i;
|
||||
CHECK_EQ(gitbit(code, i), 1);
|
||||
// all other bits should be 0
|
||||
for (int j = 0; j < 16; j++) {
|
||||
if (j != i) {
|
||||
CHECK_EQ(gitbit(code, j), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(fault_code_gitbit_with_different_types) {
|
||||
// Test with unsigned types (FaultCode handles various integer types)
|
||||
CHECK_EQ(gitbit<unsigned int>(8u, 3u), 1u);
|
||||
CHECK_EQ(gitbit<unsigned int>(8u, 0u), 0u);
|
||||
CHECK_EQ(gitbit<short>(1, 0), 1);
|
||||
CHECK_EQ(gitbit<long>(32768L, 15L), 1L);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 第八部分:Roller3 辅助函数测试
|
||||
// ============================================================================
|
||||
// extractTagNumbers — 从表达式中提取 "tagN" 的数字序号
|
||||
// calculateMedian — 计算数组的中位数
|
||||
|
||||
static std::vector<int> extractTagNumbers(const std::string& expr) {
|
||||
std::vector<int> result;
|
||||
const std::string tagPrefix = "tag";
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < expr.size()) {
|
||||
size_t found = expr.find(tagPrefix, pos);
|
||||
if (found == std::string::npos) break;
|
||||
|
||||
size_t numStart = found + tagPrefix.size();
|
||||
size_t numEnd = numStart;
|
||||
while (numEnd < expr.size() && isdigit(expr[numEnd])) numEnd++;
|
||||
|
||||
if (numEnd > numStart) {
|
||||
std::string numStr = expr.substr(numStart, numEnd - numStart);
|
||||
result.push_back(std::stoi(numStr));
|
||||
}
|
||||
pos = numEnd;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static double calculateMedian(std::vector<double> data) {
|
||||
if (data.empty()) return 0.0;
|
||||
size_t size = data.size();
|
||||
std::sort(data.begin(), data.end());
|
||||
if (size % 2 == 0) {
|
||||
return (data[size / 2 - 1] + data[size / 2]) / 2.0;
|
||||
} else {
|
||||
return data[size / 2];
|
||||
}
|
||||
}
|
||||
|
||||
TEST(roller3_extract_tag_numbers) {
|
||||
auto tags = extractTagNumbers("tag1+tag2+tag3");
|
||||
CHECK_EQ(tags.size(), 3u);
|
||||
CHECK_EQ(tags[0], 1);
|
||||
CHECK_EQ(tags[1], 2);
|
||||
CHECK_EQ(tags[2], 3);
|
||||
}
|
||||
|
||||
TEST(roller3_extract_tag_numbers_single) {
|
||||
auto tags = extractTagNumbers("tag5");
|
||||
CHECK_EQ(tags.size(), 1u);
|
||||
CHECK_EQ(tags[0], 5);
|
||||
}
|
||||
|
||||
TEST(roller3_extract_tag_numbers_complex) {
|
||||
auto tags = extractTagNumbers("tag10+tag2+tag35");
|
||||
CHECK_EQ(tags.size(), 3u);
|
||||
bool has10 = std::find(tags.begin(), tags.end(), 10) != tags.end();
|
||||
bool has2 = std::find(tags.begin(), tags.end(), 2) != tags.end();
|
||||
bool has35 = std::find(tags.begin(), tags.end(), 35) != tags.end();
|
||||
CHECK_EQ(has10, true);
|
||||
CHECK_EQ(has2, true);
|
||||
CHECK_EQ(has35, true);
|
||||
}
|
||||
|
||||
TEST(roller3_extract_empty_string) {
|
||||
auto tags = extractTagNumbers("");
|
||||
CHECK_EQ(tags.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(roller3_extract_no_tags) {
|
||||
auto tags = extractTagNumbers("no_tags_here");
|
||||
CHECK_EQ(tags.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(roller3_extract_tag_numbers_with_prefix) {
|
||||
// "tag" embedded in other words should still be found
|
||||
auto tags = extractTagNumbers("metatag1+tag2");
|
||||
// extractTagNumbers searches for "tag" substring, so "metatag1" → "tag1" → 1
|
||||
CHECK_EQ(tags.size(), 2u);
|
||||
bool has1 = std::find(tags.begin(), tags.end(), 1) != tags.end();
|
||||
bool has2 = std::find(tags.begin(), tags.end(), 2) != tags.end();
|
||||
CHECK_EQ(has1, true);
|
||||
CHECK_EQ(has2, true);
|
||||
}
|
||||
|
||||
TEST(roller3_extract_large_tag_numbers) {
|
||||
auto tags = extractTagNumbers("tag999+tag1000");
|
||||
CHECK_EQ(tags.size(), 2u);
|
||||
CHECK_EQ(tags[0], 999);
|
||||
CHECK_EQ(tags[1], 1000);
|
||||
}
|
||||
|
||||
TEST(roller3_calculate_median_odd) {
|
||||
CHECK_FLOAT_EQ(calculateMedian({1.0, 3.0, 5.0}), 3.0, 0.001);
|
||||
CHECK_FLOAT_EQ(calculateMedian({10.0, 2.0, 8.0}), 8.0, 0.001);
|
||||
}
|
||||
|
||||
TEST(roller3_calculate_median_even) {
|
||||
CHECK_FLOAT_EQ(calculateMedian({1.0, 3.0, 5.0, 7.0}), 4.0, 0.001);
|
||||
CHECK_FLOAT_EQ(calculateMedian({1.0, 2.0}), 1.5, 0.001);
|
||||
}
|
||||
|
||||
TEST(roller3_calculate_median_single) {
|
||||
CHECK_FLOAT_EQ(calculateMedian({42.0}), 42.0, 0.001);
|
||||
}
|
||||
|
||||
TEST(roller3_calculate_median_empty) {
|
||||
CHECK_FLOAT_EQ(calculateMedian({}), 0.0, 0.001);
|
||||
}
|
||||
|
||||
TEST(roller3_calculate_median_negative_values) {
|
||||
CHECK_FLOAT_EQ(calculateMedian({-5.0, -1.0, -3.0}), -3.0, 0.001);
|
||||
CHECK_FLOAT_EQ(calculateMedian({-10.0, 0.0, 10.0, 20.0}), 5.0, 0.001);
|
||||
}
|
||||
|
||||
TEST(roller3_calculate_median_large_dataset) {
|
||||
std::vector<double> data;
|
||||
for (int i = 1; i <= 99; i++) {
|
||||
data.push_back(static_cast<double>(i));
|
||||
}
|
||||
CHECK_FLOAT_EQ(calculateMedian(data), 50.0, 0.001);
|
||||
}
|
||||
|
||||
TEST(roller3_calculate_median_unsorted_preserves_order) {
|
||||
// verify the function sorts internally
|
||||
std::vector<double> data = {5.0, 3.0, 1.0, 4.0, 2.0};
|
||||
CHECK_FLOAT_EQ(calculateMedian(data), 3.0, 0.001);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// JSON 配置校验
|
||||
// ============================================================================
|
||||
// 验证辅助函数构造的 JSON 结构是否合法且包含必要字段。
|
||||
|
||||
TEST(json_config_logic_alg_structure) {
|
||||
json cfg = makeLogicConfig();
|
||||
CHECK(cfg.contains("tags"));
|
||||
CHECK(cfg.contains("trigger"));
|
||||
CHECK(cfg.contains("function"));
|
||||
CHECK(cfg.contains("output"));
|
||||
CHECK(cfg.at("function").contains("result"));
|
||||
CHECK(cfg.at("function").at("result").contains("value"));
|
||||
CHECK(cfg.at("output").contains("error"));
|
||||
}
|
||||
|
||||
TEST(json_config_bound_alg_structure) {
|
||||
json cfg = makeBoundConfig();
|
||||
CHECK(cfg.contains("tags"));
|
||||
CHECK(cfg.contains("function"));
|
||||
CHECK(cfg.at("function").contains("result"));
|
||||
CHECK(cfg.at("function").at("result").contains("param"));
|
||||
auto& param = cfg.at("function").at("result").at("param");
|
||||
CHECK(param.contains("limit_down"));
|
||||
CHECK(param.contains("limit_up"));
|
||||
}
|
||||
|
||||
TEST(json_config_feedback_logic_structure) {
|
||||
json cfg = makeFeedbackLogicConfig();
|
||||
CHECK(cfg.contains("function"));
|
||||
CHECK(cfg.at("function").contains("action_start"));
|
||||
CHECK(cfg.at("function").contains("action_end"));
|
||||
// action_end should have param with timeout
|
||||
CHECK(cfg.at("function").at("action_end").contains("param"));
|
||||
CHECK(cfg.at("function").at("action_end").at("param").contains("timeout"));
|
||||
}
|
||||
|
||||
TEST(json_config_feedback_bound_structure) {
|
||||
json cfg = makeFeedbackBoundConfig();
|
||||
CHECK(cfg.at("function").contains("action_start"));
|
||||
CHECK(cfg.at("function").contains("action_end"));
|
||||
// action_start should have bound params
|
||||
auto& start = cfg.at("function").at("action_start");
|
||||
CHECK(start.contains("param"));
|
||||
CHECK(start.at("param").contains("limit_down"));
|
||||
CHECK(start.at("param").contains("limit_up"));
|
||||
}
|
||||
|
||||
TEST(json_config_bound_hold_structure) {
|
||||
json cfg = makeBoundHoldConfig();
|
||||
auto& param = cfg.at("function").at("result").at("param");
|
||||
CHECK(param.contains("hold_time"));
|
||||
CHECK(param.contains("limit_down"));
|
||||
CHECK(param.contains("limit_up"));
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
// eqpalg/test/test_expression_engine.cc
|
||||
#include "test_harness.h"
|
||||
#include <eqpalg/utility/expression_engine.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 测试辅助:构建最小环境
|
||||
struct TestEnv {
|
||||
std::map<std::string, double> vars;
|
||||
std::vector<std::string> tags;
|
||||
ExpressionEngine engine;
|
||||
|
||||
TestEnv() : engine(vars, tags) {
|
||||
tags = {"tag1", "tag2"};
|
||||
|
||||
vars["tag1"] = 10.0;
|
||||
vars["tag2"] = 20.0;
|
||||
vars["p1"] = 10.0;
|
||||
vars["p2"] = 20.0;
|
||||
vars["now"] = 0;
|
||||
vars["stime"] = 0;
|
||||
vars["time"] = 0;
|
||||
vars["etime"] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 注册与求值 ====================
|
||||
|
||||
TEST(register_and_evaluate_simple_expression) {
|
||||
TestEnv env;
|
||||
int ret = env.engine.registerExpression("test", "tag1 + tag2");
|
||||
CHECK_EQ(ret, 0);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("test"), 30.0, 0.001);
|
||||
CHECK_EQ(env.engine.evaluateBool("test"), true);
|
||||
}
|
||||
|
||||
TEST(register_duplicate_is_idempotent) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("dup", "tag1"), 0);
|
||||
CHECK_EQ(env.engine.registerExpression("dup", "tag2"), 0);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("dup"), 10.0, 0.001);
|
||||
}
|
||||
|
||||
TEST(evaluate_unregistered_returns_zero) {
|
||||
TestEnv env;
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("nonexistent"), 0.0, 0.001);
|
||||
CHECK_EQ(env.engine.evaluateBool("nonexistent"), false);
|
||||
}
|
||||
|
||||
TEST(evaluate_zero_expression) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("zero", "0"), 0);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("zero"), 0.0, 0.001);
|
||||
CHECK_EQ(env.engine.evaluateBool("zero"), false);
|
||||
}
|
||||
|
||||
TEST(evaluate_boolean_true_constant) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("one", "1"), 0);
|
||||
CHECK_EQ(env.engine.evaluateBool("one"), true);
|
||||
}
|
||||
|
||||
// ==================== 变量变化反映到求值 ====================
|
||||
|
||||
TEST(variable_change_reflected_in_evaluation) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("x", "tag1"), 0);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("x"), 10.0, 0.001);
|
||||
|
||||
env.vars["tag1"] = 100.0;
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("x"), 100.0, 0.001);
|
||||
}
|
||||
|
||||
// ==================== 多表达式并存 ====================
|
||||
|
||||
TEST(multiple_expressions_independent) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("a", "tag1"), 0);
|
||||
CHECK_EQ(env.engine.registerExpression("b", "tag2"), 0);
|
||||
CHECK_EQ(env.engine.registerExpression("sum", "tag1 + tag2"), 0);
|
||||
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("a"), 10.0, 0.001);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("b"), 20.0, 0.001);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("sum"), 30.0, 0.001);
|
||||
|
||||
env.vars["tag1"] = 100.0;
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("a"), 100.0, 0.001);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("sum"), 120.0, 0.001);
|
||||
}
|
||||
|
||||
// ==================== registerRawExpression ====================
|
||||
|
||||
TEST(register_raw_no_funvars_processing) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerRawExpression("raw", "tag1 + tag2"), 0);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("raw"), 30.0, 0.001);
|
||||
}
|
||||
|
||||
// ==================== FunVars 重置控制 ====================
|
||||
|
||||
TEST(autoReset_does_nothing_when_not_marked) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("test", "tag1"), 0);
|
||||
// 无标记时 autoReset 什么都不做
|
||||
env.engine.autoResetFunVars();
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("test"), 10.0, 0.001);
|
||||
}
|
||||
|
||||
TEST(mark_and_autoReset_sequence) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("test", "tag1"), 0);
|
||||
env.engine.markFunVarsNeedReset();
|
||||
env.engine.autoResetFunVars(); // 执行重置并清除标记
|
||||
// 第二次应该是 no-op
|
||||
env.engine.autoResetFunVars();
|
||||
}
|
||||
|
||||
TEST(forceReset_works) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("test", "tag1"), 0);
|
||||
env.engine.forceResetFunVars();
|
||||
}
|
||||
|
||||
// ==================== 复杂表达式 ====================
|
||||
|
||||
TEST(comparison_expression) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("cmp", "tag1 > tag2"), 0);
|
||||
CHECK_EQ(env.engine.evaluateBool("cmp"), false);
|
||||
|
||||
env.vars["tag1"] = 30.0;
|
||||
CHECK_EQ(env.engine.evaluateBool("cmp"), true);
|
||||
}
|
||||
|
||||
TEST(arithmetic_with_comparison) {
|
||||
TestEnv env;
|
||||
CHECK_EQ(env.engine.registerExpression("arith", "tag1 * 2 + tag2"), 0);
|
||||
CHECK_FLOAT_EQ(env.engine.evaluate("arith"), 40.0, 0.001);
|
||||
}
|
||||
@ -1,197 +0,0 @@
|
||||
// eqpalg/test/test_fb_state_machine.cc
|
||||
#include "test_harness.h"
|
||||
#include <eqpalg/utility/fb_state_machine.h>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
// 测试辅助:构建最小环境
|
||||
struct FbTestEnv {
|
||||
std::map<std::string, double> vars;
|
||||
FbStateMachine fsm;
|
||||
TimePoint now;
|
||||
|
||||
FbTestEnv(size_t tag_count = 2) {
|
||||
// 模拟已初始化的变量状态
|
||||
for (size_t i = 0; i < tag_count; i++) {
|
||||
std::string idx = std::to_string(i + 1);
|
||||
vars["tag" + idx] = 0.0;
|
||||
vars["p" + idx] = 0.0;
|
||||
vars["now"] = 0;
|
||||
}
|
||||
now = std::chrono::system_clock::now();
|
||||
vars["now"] = duration_cast<milliseconds>(now.time_since_epoch()).count();
|
||||
}
|
||||
|
||||
void setTag(int n, double val) {
|
||||
std::string idx = std::to_string(n);
|
||||
vars["p" + idx] = vars["tag" + idx]; // p = old tag
|
||||
vars["tag" + idx] = val;
|
||||
}
|
||||
|
||||
void advanceTime(int ms) {
|
||||
now += milliseconds(ms);
|
||||
vars["now"] = duration_cast<milliseconds>(now.time_since_epoch()).count();
|
||||
}
|
||||
|
||||
FbUpdateResult step(bool trigger) {
|
||||
return fsm.update(trigger, now, vars, 2);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 初始状态 ====================
|
||||
|
||||
TEST(initial_state_is_idle) {
|
||||
FbTestEnv env;
|
||||
CHECK(env.fsm.currentState() == FbState::Idle);
|
||||
CHECK_EQ(env.fsm.isActive(), false);
|
||||
}
|
||||
|
||||
// ==================== Idle → Started ====================
|
||||
|
||||
TEST(idle_to_started_when_triggered) {
|
||||
FbTestEnv env;
|
||||
auto result = env.step(true);
|
||||
CHECK(result.state == FbState::Started);
|
||||
CHECK_EQ(env.fsm.isActive(), true);
|
||||
CHECK_EQ(result.funVarsNeedReset, false);
|
||||
}
|
||||
|
||||
TEST(idle_stays_idle_when_not_triggered) {
|
||||
FbTestEnv env;
|
||||
auto result = env.step(false);
|
||||
CHECK(result.state == FbState::Idle);
|
||||
CHECK_EQ(result.funVarsNeedReset, true); // 空闲时重置 fun_vars
|
||||
}
|
||||
|
||||
// ==================== Started → InProgress → Done ====================
|
||||
|
||||
TEST(started_proceeds_to_done_via_feedback) {
|
||||
FbTestEnv env;
|
||||
|
||||
// 首次触发 → Started
|
||||
auto r1 = env.step(true);
|
||||
CHECK(r1.state == FbState::Started);
|
||||
|
||||
// Started 自动推进到 InProgress(第二次 update)
|
||||
auto r2 = env.step(true);
|
||||
CHECK(r2.state == FbState::InProgress || r2.state == FbState::Started);
|
||||
// 如果第二次还在 Started,第三次一定到 InProgress
|
||||
if (r2.state == FbState::Started) {
|
||||
r2 = env.step(true);
|
||||
}
|
||||
CHECK(r2.state == FbState::InProgress);
|
||||
|
||||
// 反馈条件满足 → Done
|
||||
bool done = env.fsm.checkFeedback(true, env.now, env.vars);
|
||||
CHECK_EQ(done, true);
|
||||
CHECK(env.fsm.currentState() == FbState::Idle); // 自动回到 Idle
|
||||
}
|
||||
|
||||
// ==================== NotHold ====================
|
||||
|
||||
TEST(keep_mode_not_hold_when_trigger_lost) {
|
||||
FbTestEnv env;
|
||||
env.fsm.configure(true, milliseconds(600000)); // keep_mode=true
|
||||
|
||||
// 触发 → Started
|
||||
env.step(true);
|
||||
// → InProgress
|
||||
env.step(true);
|
||||
// 触发丢失 → NotHold
|
||||
auto r = env.step(false);
|
||||
CHECK(r.state == FbState::NotHold);
|
||||
CHECK_EQ(r.funVarsNeedReset, true);
|
||||
}
|
||||
|
||||
// ==================== Timeout ====================
|
||||
|
||||
TEST(timeout_when_exceeds_limit) {
|
||||
FbTestEnv env;
|
||||
env.fsm.configure(false, milliseconds(100)); // 100ms timeout
|
||||
|
||||
// 触发 → Started → InProgress
|
||||
env.step(true);
|
||||
env.step(true);
|
||||
|
||||
// 超时
|
||||
env.advanceTime(200);
|
||||
auto r = env.step(true);
|
||||
CHECK(r.state == FbState::Timeout);
|
||||
CHECK_EQ(r.funVarsNeedReset, true);
|
||||
}
|
||||
|
||||
// ==================== 变量快照 ====================
|
||||
|
||||
TEST(snapshot_on_action_start) {
|
||||
FbTestEnv env;
|
||||
env.setTag(1, 3.0);
|
||||
env.setTag(2, 7.0);
|
||||
|
||||
env.step(true); // → Started(快照发生)
|
||||
|
||||
CHECK_FLOAT_EQ(env.vars["s1"], 3.0, 0.001);
|
||||
CHECK_FLOAT_EQ(env.vars["s2"], 7.0, 0.001);
|
||||
CHECK_FLOAT_EQ(env.vars["time"], 0.0, 0.001);
|
||||
CHECK_FLOAT_EQ(env.vars["mv2_tag1"], 0.0, 0.001);
|
||||
// stime 应该被设置
|
||||
CHECK(env.vars["stime"] > 0);
|
||||
}
|
||||
|
||||
// ==================== 变量累积 ====================
|
||||
|
||||
TEST(action_vars_accumulate_in_progress) {
|
||||
FbTestEnv env;
|
||||
env.setTag(1, 1.0); // tag1=1
|
||||
env.setTag(2, 0.0);
|
||||
|
||||
env.step(true); // Started
|
||||
env.step(true); // InProgress (累积发生)
|
||||
|
||||
CHECK_EQ(env.vars["mv2_tag1"] >= 1.0, true); // tag1==1 被计数
|
||||
CHECK_FLOAT_EQ(env.vars["mx_tag1"], 1.0, 0.001);
|
||||
CHECK_FLOAT_EQ(env.vars["mi_tag2"], 0.0, 0.001);
|
||||
}
|
||||
|
||||
// ==================== forceReset ====================
|
||||
|
||||
TEST(force_reset_returns_to_idle) {
|
||||
FbTestEnv env;
|
||||
env.step(true); // Started
|
||||
CHECK_EQ(env.fsm.isActive(), true);
|
||||
|
||||
env.fsm.forceReset();
|
||||
CHECK(env.fsm.currentState() == FbState::Idle);
|
||||
CHECK_EQ(env.fsm.isActive(), false);
|
||||
}
|
||||
|
||||
// ==================== 非 keep 模式下触发丢失 ====================
|
||||
|
||||
TEST(no_keep_mode_trigger_loss_stays_in_progress) {
|
||||
FbTestEnv env;
|
||||
env.fsm.configure(false, milliseconds(600000)); // keep_mode=false
|
||||
|
||||
env.step(true); // Started
|
||||
env.step(true); // InProgress
|
||||
auto r = env.step(false); // 触发丢失但 keep_mode=false
|
||||
CHECK(r.state == FbState::InProgress); // 不退出
|
||||
}
|
||||
|
||||
// ==================== 终端状态自动回归 Idle ====================
|
||||
|
||||
TEST(terminal_state_returns_to_idle_next_cycle) {
|
||||
FbTestEnv env;
|
||||
env.fsm.configure(false, milliseconds(10));
|
||||
|
||||
env.step(true);
|
||||
env.step(true);
|
||||
env.advanceTime(20);
|
||||
auto r = env.step(true);
|
||||
CHECK(r.state == FbState::Timeout);
|
||||
|
||||
// 下一周期自动回到 Idle
|
||||
auto r2 = env.step(false);
|
||||
CHECK(r2.state == FbState::Idle);
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
// eqpalg/test/test_harness.h
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct TestRunner {
|
||||
struct Case {
|
||||
const char* name;
|
||||
std::function<void()> fn;
|
||||
};
|
||||
inline static std::vector<Case> cases;
|
||||
|
||||
static int run() {
|
||||
int passed = 0, failed = 0;
|
||||
for (auto& c : cases) {
|
||||
std::cout << " " << c.name << " ... ";
|
||||
try {
|
||||
c.fn();
|
||||
std::cout << "PASSED\n";
|
||||
passed++;
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "FAILED\n " << e.what() << "\n";
|
||||
failed++;
|
||||
} catch (...) {
|
||||
std::cout << "FAILED (unknown)\n";
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
std::cout << "\n" << passed << " passed, " << failed << " failed\n";
|
||||
return failed ? 1 : 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct AutoReg {
|
||||
AutoReg(const char* name, std::function<void()> fn) {
|
||||
TestRunner::cases.push_back({name, std::move(fn)});
|
||||
}
|
||||
};
|
||||
|
||||
#define TEST(name) \
|
||||
static void testfn_##name(); \
|
||||
static AutoReg autoreg_##name(#name, testfn_##name); \
|
||||
static void testfn_##name()
|
||||
|
||||
#define CHECK(expr) \
|
||||
do { \
|
||||
if (!(expr)) \
|
||||
throw std::runtime_error("CHECK(" #expr ") failed"); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_EQ(a, b) \
|
||||
do { \
|
||||
if ((a) != (b)) { \
|
||||
std::ostringstream os; \
|
||||
os << "CHECK_EQ: " << (a) << " != " << (b); \
|
||||
throw std::runtime_error(os.str()); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_FLOAT_EQ(a, b, eps) \
|
||||
do { \
|
||||
if (std::fabs((a) - (b)) > (eps)) { \
|
||||
std::ostringstream os; \
|
||||
os << "CHECK_FLOAT_EQ: |" << (a) << " - " << (b) << "| > " << (eps); \
|
||||
throw std::runtime_error(os.str()); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_THROWS(expr) \
|
||||
do { \
|
||||
bool caught = false; \
|
||||
try { \
|
||||
(void)(expr); \
|
||||
} catch (...) { \
|
||||
caught = true; \
|
||||
} \
|
||||
if (!caught) \
|
||||
throw std::runtime_error("CHECK_THROWS(" #expr ") did not throw"); \
|
||||
} while (0)
|
||||
@ -1,7 +0,0 @@
|
||||
// eqpalg/test/test_main.cc
|
||||
#include "test_harness.h"
|
||||
|
||||
int main() {
|
||||
std::cout << "ExpressionEngine Tests\n======================\n\n";
|
||||
return TestRunner::run();
|
||||
}
|
||||
138
eqpalg/utility/ExpModule.cc
Normal file
138
eqpalg/utility/ExpModule.cc
Normal file
@ -0,0 +1,138 @@
|
||||
#include <eqpalg/gb_item_memory.h>
|
||||
#include <eqpalg/utility/ExpModule.h>
|
||||
#include <glob/SingletonTemplate.h>
|
||||
ExpModule::ExpModule(std::map<std::string, double> &vars_ref,
|
||||
vector<string> &m_tags, bool &is_exp_alg_ref)
|
||||
: mm_vars(vars_ref), m_tags(m_tags), is_exp_alg_(is_exp_alg_ref) {
|
||||
logger_ = std::make_unique<LOG>("ExpModule");
|
||||
init();
|
||||
}
|
||||
|
||||
ExpModule::~ExpModule() {}
|
||||
int ExpModule::add_exp(string exp_name, string exp_str) {
|
||||
if (exp_str_.find(exp_name) == exp_str_.end()) {
|
||||
exp_str_[exp_name] = exp_str;
|
||||
fun_vars_.add_exp_str(exp_str, &mm_vars);
|
||||
exp_ptr_[exp_name] = std::make_unique<MExp>(exp_str, &mm_vars);
|
||||
try {
|
||||
exp_result_[exp_name] = exp_ptr_[exp_name]->evaluate();
|
||||
} catch (const std::exception &e) {
|
||||
logger_->Error() << "add_exp:" << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
int ExpModule::update() {
|
||||
if (is_exp_alg_ != true) {
|
||||
this->refresh_exp_vars_mem();
|
||||
this->fun_vars_.refresh_fun_vars(false, &mm_vars);
|
||||
}
|
||||
|
||||
for (auto item : this->exp_str_) {
|
||||
exp_result_[item.first] = exp_ptr_[item.first]->evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
int ExpModule::update(
|
||||
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> queried_data,
|
||||
std::vector<TimePoint> queried_time, int row) {
|
||||
if (is_exp_alg_ != true) {
|
||||
this->refresh_exp_ihd_mem(queried_data, queried_time, row);
|
||||
this->fun_vars_.refresh_fun_vars(false, &mm_vars);
|
||||
}
|
||||
|
||||
for (auto item : this->exp_str_) {
|
||||
exp_result_[item.first] = exp_ptr_[item.first]->evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
void ExpModule::refresh_exp_ihd_mem(
|
||||
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> queried_data,
|
||||
std::vector<TimePoint> queried_time, int row) {
|
||||
if (queried_data.rows() == 0 || queried_data.cols() == 0) {
|
||||
logger_->Error() << "refresh_exp_ihd_mem: queried_data is empty"
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < m_tags.size(); i++) {
|
||||
if (row >= queried_data.rows() || i >= (unsigned int)queried_data.cols()) {
|
||||
continue;
|
||||
}
|
||||
mm_vars["p" + std::to_string(i + 1)] =
|
||||
mm_vars["tag" + std::to_string(i + 1)];
|
||||
mm_vars["tag" + std::to_string(i + 1)] = queried_data(row, i);
|
||||
mm_vars["now"] = mix_cc::mix_time_t(queried_time[row]).to_milliseconds();
|
||||
auto pv_str = "pv" + std::to_string(i + 1);
|
||||
|
||||
mm_vars[pv_str + "_5"] = mm_vars[pv_str + "_4"];
|
||||
mm_vars[pv_str + "_4"] = mm_vars[pv_str + "_3"];
|
||||
mm_vars[pv_str + "_3"] = mm_vars[pv_str + "_2"];
|
||||
mm_vars[pv_str + "_2"] = mm_vars[pv_str + "_1"];
|
||||
mm_vars[pv_str + "_1"] = mm_vars[pv_str + "_0"];
|
||||
mm_vars[pv_str + "_0"] = mm_vars["tag" + std::to_string(i + 1)];
|
||||
}
|
||||
}
|
||||
|
||||
void ExpModule::refresh_exp_vars_mem() {
|
||||
this->now_time_ = std::chrono::system_clock::now();
|
||||
for (unsigned int i = 0; i < m_tags.size(); i++) {
|
||||
mm_vars["p" + std::to_string(i + 1)] =
|
||||
mm_vars["tag" + std::to_string(i + 1)];
|
||||
mm_vars["tag" + std::to_string(i + 1)] =
|
||||
SingletonTemplate<GlobaltemSharedMemory>::GetInstance()[m_tags[i]];
|
||||
auto pv_str = "pv" + std::to_string(i + 1);
|
||||
mm_vars[pv_str + "_5"] = mm_vars[pv_str + "_4"];
|
||||
mm_vars[pv_str + "_4"] = mm_vars[pv_str + "_3"];
|
||||
mm_vars[pv_str + "_3"] = mm_vars[pv_str + "_2"];
|
||||
mm_vars[pv_str + "_2"] = mm_vars[pv_str + "_1"];
|
||||
mm_vars[pv_str + "_1"] = mm_vars[pv_str + "_0"];
|
||||
mm_vars[pv_str + "_0"] = mm_vars["tag" + std::to_string(i + 1)];
|
||||
}
|
||||
mm_vars["now"] =
|
||||
duration_cast<milliseconds>(now_time_.time_since_epoch()).count();
|
||||
}
|
||||
void ExpModule::init() {
|
||||
this->now_time_ = std::chrono::system_clock::now();
|
||||
for (unsigned int i = 0; i < m_tags.size(); i++) {
|
||||
double value =
|
||||
SingletonTemplate<GlobaltemSharedMemory>::GetInstance()[m_tags[i]];
|
||||
mm_vars["p" + std::to_string(i + 1)] = value;
|
||||
mm_vars["tag" + std::to_string(i + 1)] = value;
|
||||
|
||||
mm_vars["now"] =
|
||||
duration_cast<milliseconds>(now_time_.time_since_epoch()).count();
|
||||
auto pv_str = "pv" + std::to_string(i + 1);
|
||||
|
||||
mm_vars[pv_str + "_0"] = value;
|
||||
mm_vars[pv_str + "_1"] = value;
|
||||
mm_vars[pv_str + "_2"] = value;
|
||||
mm_vars[pv_str + "_3"] = value;
|
||||
mm_vars[pv_str + "_4"] = value;
|
||||
mm_vars[pv_str + "_5"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
double ExpModule::get_value(string exp_name) {
|
||||
try {
|
||||
return exp_result_[exp_name];
|
||||
} catch (const std::exception &e) {
|
||||
logger_->Error() << "get_value:" << e.what() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
void ExpModule::print_value() {
|
||||
logger_->Debug() << "mm_vars:";
|
||||
for (auto item : mm_vars) {
|
||||
logger_->Debug() << " " << item.first << ":" << item.second;
|
||||
}
|
||||
logger_->Debug() << std::endl;
|
||||
}
|
||||
|
||||
string ExpModule::get_exp_str(string exp_name) {
|
||||
if (exp_str_.find(exp_name) == exp_str_.end()) {
|
||||
return "";
|
||||
}
|
||||
return exp_str_[exp_name];
|
||||
}
|
||||
|
||||
void ExpModule::fun_reset() { fun_vars_.refresh_fun_vars(true, &mm_vars); }
|
||||
112
eqpalg/utility/ExpModule.h
Normal file
112
eqpalg/utility/ExpModule.h
Normal file
@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
/**
|
||||
* @file ExpModule.h
|
||||
* @brief 表达式模块
|
||||
* @author your name (you@domain.com)
|
||||
* @version 0.1
|
||||
* @date 2025-08-20
|
||||
*
|
||||
* Copyright: Baosight Co. Ltd.
|
||||
* DO NOT COPY/USE WITHOUT PERMISSION
|
||||
*
|
||||
*/
|
||||
#include "mix_cc/ihyper_db.h"
|
||||
#include "mix_cc/json.h"
|
||||
#include "mix_cc/matheval/matheval.hpp"
|
||||
#include <chrono>
|
||||
#include <eqpalg/utility/StatExp.hpp>
|
||||
#include <log4cplus/LOG.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using MExp = mix_cc::matheval::Expression;
|
||||
using namespace std::chrono;
|
||||
using std::chrono::system_clock;
|
||||
using TimePoint = system_clock::time_point;
|
||||
using TimeDur = milliseconds;
|
||||
|
||||
class ExpModule {
|
||||
public:
|
||||
ExpModule(std::map<std::string, double> &vars_ref, vector<string> &m_tags,
|
||||
bool &is_exp_alg_ref);
|
||||
~ExpModule();
|
||||
ExpModule() = delete;
|
||||
ExpModule &operator=(const ExpModule &) = delete;
|
||||
|
||||
/**
|
||||
* @brief 添加表达式
|
||||
* @param exp_name 表达式名称
|
||||
* @param exp_str 表达式字符串
|
||||
* @return int
|
||||
*/
|
||||
int add_exp(string exp_name, string exp_str);
|
||||
/**
|
||||
* @brief 更新数据,更新表达式的值
|
||||
* @return int
|
||||
*/
|
||||
int update();
|
||||
|
||||
void fun_reset();
|
||||
|
||||
/**
|
||||
* @brief 更新数据,更新表达式的值 通过指定数据
|
||||
* @param queried_data 指定数据项
|
||||
* @return int
|
||||
*/
|
||||
int update(Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> queried_data,
|
||||
std::vector<TimePoint> queried_time, int row = 0);
|
||||
/**
|
||||
* @brief 获取表达式的值
|
||||
* @param exp_name 表达式名称
|
||||
* @return double
|
||||
*/
|
||||
double get_value(string exp_name);
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
void print_value();
|
||||
|
||||
/**
|
||||
* @brief Get the exp str object
|
||||
* @param exp_name My Param doc
|
||||
* @return string
|
||||
*/
|
||||
string get_exp_str(string exp_name);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 表达式变量,表达式初始化
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* @brief 更新数据 共享内存
|
||||
*/
|
||||
void refresh_exp_vars_mem();
|
||||
|
||||
/**
|
||||
* @brief 更新数据 指定数据
|
||||
* @param queried_data
|
||||
*/
|
||||
void refresh_exp_ihd_mem(
|
||||
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> queried_data,
|
||||
std::vector<TimePoint> queried_time, int row);
|
||||
|
||||
private:
|
||||
map<string, double> &mm_vars;
|
||||
map<string, double> exp_result_;
|
||||
map<string, std::unique_ptr<MExp>> exp_ptr_;
|
||||
map<string, string> exp_str_;
|
||||
vector<string> &m_tags;
|
||||
std::unique_ptr<LOG> logger_;
|
||||
TimePoint now_time_;
|
||||
/**
|
||||
* @brief 自定义带状态函数
|
||||
*/
|
||||
StatExp::FunVars fun_vars_;
|
||||
bool &is_exp_alg_;
|
||||
};
|
||||
@ -1,34 +0,0 @@
|
||||
// eqpalg/utility/bound_checker.cpp
|
||||
#include <eqpalg/utility/bound_checker.h>
|
||||
|
||||
void BoundChecker::setLimits(double down, double up) {
|
||||
limit_down_ = down;
|
||||
limit_up_ = up;
|
||||
|
||||
// 哨兵值推导检测模式(与 ExpBase::reload_config_up_down 逻辑一致)
|
||||
int idown = static_cast<int>(down);
|
||||
int iup = static_cast<int>(up);
|
||||
|
||||
if (idown == -32768 && iup != idown) {
|
||||
detect_mode_ = DetectMode::OnlyRight; // 仅右边界
|
||||
} else if ((iup == -32768 || iup == 32768 || iup == 32767) && iup != idown) {
|
||||
detect_mode_ = DetectMode::OnlyLeft; // 仅左边界
|
||||
} else if (iup == -32768 && iup == idown) {
|
||||
detect_mode_ = DetectMode::ErrorMode; // 配置错误
|
||||
} else {
|
||||
detect_mode_ = DetectMode::Default; // 双侧
|
||||
}
|
||||
}
|
||||
|
||||
bool BoundChecker::isOutOfBounds(double value) const {
|
||||
switch (detect_mode_) {
|
||||
case DetectMode::Default:
|
||||
return value < limit_down_ || value > limit_up_;
|
||||
case DetectMode::OnlyLeft:
|
||||
return value < limit_down_;
|
||||
case DetectMode::OnlyRight:
|
||||
return value > limit_up_;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
// eqpalg/utility/bound_checker.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief 检测模式:双侧、仅左侧、仅右侧
|
||||
*/
|
||||
enum class DetectMode {
|
||||
Default = 0, // 双侧检测 (value < down || value > up)
|
||||
OnlyLeft = 1, // 仅检测左边界 (value < down)
|
||||
OnlyRight = 2, // 仅检测右边界 (value > up)
|
||||
ErrorMode = 3 // 错误模式
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 上下限检测器
|
||||
*
|
||||
* 从 ExpBase 提取,封装哨兵值 -32768/32767 的处理逻辑。
|
||||
*/
|
||||
class BoundChecker {
|
||||
public:
|
||||
BoundChecker() = default;
|
||||
|
||||
/**
|
||||
* @brief 设置上下限,自动推导检测模式
|
||||
* @param down 下限(-32768 表示无下限)
|
||||
* @param up 上限(32767/32768 表示无上限)
|
||||
*/
|
||||
void setLimits(double down, double up);
|
||||
|
||||
/**
|
||||
* @brief 手动设置检测模式(覆盖自动推导)
|
||||
*/
|
||||
void setDetectMode(DetectMode mode) { detect_mode_ = mode; }
|
||||
|
||||
/**
|
||||
* @brief 判断 value 是否超出限值
|
||||
*/
|
||||
bool isOutOfBounds(double value) const;
|
||||
|
||||
// 访问器
|
||||
double limitDown() const { return limit_down_; }
|
||||
double limitUp() const { return limit_up_; }
|
||||
DetectMode detectMode() const { return detect_mode_; }
|
||||
|
||||
/**
|
||||
* @brief 是否已配置有效限值
|
||||
*/
|
||||
bool isValid() const { return detect_mode_ != DetectMode::ErrorMode; }
|
||||
|
||||
private:
|
||||
double limit_down_ = -32768;
|
||||
double limit_up_ = -32768;
|
||||
DetectMode detect_mode_ = DetectMode::Default;
|
||||
};
|
||||
@ -1,228 +0,0 @@
|
||||
// eqpalg/utility/expression_engine.cpp
|
||||
#include <eqpalg/utility/expression_engine.h>
|
||||
#include <glob/SingletonTemplate.h>
|
||||
#include <eqpalg/gb_item_memory.h>
|
||||
#include <mix_cc/type/mix_time.h>
|
||||
#include <log4cplus/LOG.h>
|
||||
|
||||
ExpressionEngine::ExpressionEngine(std::map<std::string, double>& mm_vars,
|
||||
std::vector<std::string>& m_tags)
|
||||
: mm_vars_(mm_vars), m_tags_(m_tags) {}
|
||||
|
||||
// 惰性初始化 VarsCache(首次变量刷新时调用)
|
||||
static void ensureVarCache(VarsCache& vc, size_t tag_count) {
|
||||
if (vc.tag_num == 0 && tag_count > 0) {
|
||||
vc.init(tag_count, 6);
|
||||
}
|
||||
}
|
||||
|
||||
int ExpressionEngine::registerExpression(const std::string& name,
|
||||
const std::string& raw_exp_str) {
|
||||
if (exps_.count(name)) return 0; // 已注册,幂等
|
||||
|
||||
auto& entry = exps_[name];
|
||||
entry.raw_str = raw_exp_str;
|
||||
|
||||
// 通过 FunVars 替换状态函数(KeepC/KeepT/RiseEdge/Detect)为 funN 变量
|
||||
auto [ok, processed] = fun_vars_.add_exp_str(raw_exp_str, &mm_vars_);
|
||||
if (!ok) return -1;
|
||||
entry.processed_str = processed;
|
||||
|
||||
// 编译表达式
|
||||
try {
|
||||
entry.ptr = std::make_unique<MExp>(processed, &mm_vars_);
|
||||
} catch (const std::exception& e) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 从表达式中提取 hold(n,T) 模式
|
||||
initHoldExpStr(processed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ExpressionEngine::registerRawExpression(const std::string& name,
|
||||
const std::string& raw_exp_str) {
|
||||
if (exps_.count(name)) return 0;
|
||||
|
||||
auto& entry = exps_[name];
|
||||
entry.raw_str = raw_exp_str;
|
||||
entry.processed_str = raw_exp_str;
|
||||
|
||||
try {
|
||||
entry.ptr = std::make_unique<MExp>(raw_exp_str, &mm_vars_);
|
||||
} catch (const std::exception& e) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
double ExpressionEngine::evaluate(const std::string& name) {
|
||||
auto it = exps_.find(name);
|
||||
if (it == exps_.end()) return 0.0;
|
||||
return it->second.ptr->evaluate();
|
||||
}
|
||||
|
||||
bool ExpressionEngine::evaluateBool(const std::string& name) {
|
||||
return static_cast<bool>(evaluate(name));
|
||||
}
|
||||
|
||||
int ExpressionEngine::initHoldExpStr(const std::string& exp_str) {
|
||||
auto hold_sub_strs = HoldTime::find_substr(exp_str, "_HE");
|
||||
for (const auto& sub_str : hold_sub_strs) {
|
||||
bool flag;
|
||||
double timeM;
|
||||
std::string tagi;
|
||||
std::string var_name;
|
||||
std::tie(flag, timeM, tagi, var_name) = HoldTime::find_hold(sub_str);
|
||||
if (flag) {
|
||||
if (hold_times_.find(var_name) == hold_times_.end()) {
|
||||
hold_times_.emplace(std::make_pair(
|
||||
var_name, std::make_unique<HoldTime>(timeM, tagi, var_name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshHoldVars();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ========== FunVars 控制 ==========
|
||||
|
||||
void ExpressionEngine::autoResetFunVars() {
|
||||
if (fun_vars_need_reset_) {
|
||||
fun_vars_.refresh_fun_vars(true, &mm_vars_);
|
||||
fun_vars_need_reset_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionEngine::markFunVarsNeedReset() {
|
||||
fun_vars_need_reset_ = true;
|
||||
}
|
||||
|
||||
void ExpressionEngine::forceResetFunVars() {
|
||||
fun_vars_.refresh_fun_vars(true, &mm_vars_);
|
||||
fun_vars_need_reset_ = false;
|
||||
}
|
||||
|
||||
void ExpressionEngine::refreshFromMemory(
|
||||
const TimePoint& now_time,
|
||||
mix_cc::time_range_t& query_time_range) {
|
||||
|
||||
ensureVarCache(var_cache_, m_tags_.size());
|
||||
|
||||
for (size_t i = 0; i < m_tags_.size(); i++) {
|
||||
double current =
|
||||
SingletonTemplate<GlobaltemSharedMemory>::GetInstance()[m_tags_[i]];
|
||||
|
||||
// pN = 旧 tagN
|
||||
mm_vars_[var_cache_.p_keys[i]] = mm_vars_[var_cache_.tag_keys[i]];
|
||||
// tagN = 当前值
|
||||
mm_vars_[var_cache_.tag_keys[i]] = current;
|
||||
|
||||
// pvN 历史移位
|
||||
for (size_t j = PV_NUM - 1; j > 0; j--) {
|
||||
mm_vars_[var_cache_.pv_keys[i][j]] =
|
||||
mm_vars_[var_cache_.pv_keys[i][j - 1]];
|
||||
}
|
||||
mm_vars_[var_cache_.pv_keys[i][0]] = current;
|
||||
}
|
||||
|
||||
mm_vars_["now"] = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now_time.time_since_epoch())
|
||||
.count();
|
||||
|
||||
fun_vars_.refresh_fun_vars(false, &mm_vars_);
|
||||
query_time_range.set_right(now_time);
|
||||
refreshHoldVars();
|
||||
}
|
||||
|
||||
void ExpressionEngine::refreshFromIhdRow(
|
||||
int row,
|
||||
const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>& queried_data,
|
||||
const std::vector<TimePoint>& queried_time,
|
||||
TimePoint& now_time,
|
||||
mix_cc::time_range_t& query_time_range) {
|
||||
|
||||
if (queried_data.rows() == 0 || queried_data.cols() == 0) return;
|
||||
if (row < 0 || row >= queried_data.rows()) return;
|
||||
if (static_cast<int>(m_tags_.size()) > queried_data.cols()) return;
|
||||
|
||||
ensureVarCache(var_cache_, m_tags_.size());
|
||||
|
||||
for (size_t i = 0; i < m_tags_.size(); i++) {
|
||||
// pN = 旧 tagN
|
||||
mm_vars_[var_cache_.p_keys[i]] = mm_vars_[var_cache_.tag_keys[i]];
|
||||
// tagN = 当前行数据
|
||||
mm_vars_[var_cache_.tag_keys[i]] = queried_data(row, i);
|
||||
|
||||
// pvN 历史移位
|
||||
for (size_t j = PV_NUM - 1; j > 0; j--) {
|
||||
mm_vars_[var_cache_.pv_keys[i][j]] =
|
||||
mm_vars_[var_cache_.pv_keys[i][j - 1]];
|
||||
}
|
||||
mm_vars_[var_cache_.pv_keys[i][0]] =
|
||||
mm_vars_[var_cache_.tag_keys[i]];
|
||||
}
|
||||
|
||||
mm_vars_["now"] =
|
||||
mix_cc::mix_time_t(queried_time[row]).to_milliseconds();
|
||||
|
||||
now_time = queried_time[row];
|
||||
query_time_range.set_right(queried_time[row]);
|
||||
|
||||
fun_vars_.refresh_fun_vars(false, &mm_vars_);
|
||||
refreshHoldVars();
|
||||
}
|
||||
|
||||
void ExpressionEngine::refreshHoldVars() {
|
||||
for (auto& kv : hold_times_) {
|
||||
mm_vars_[kv.first] =
|
||||
static_cast<double>(kv.second->update_value(mm_vars_[kv.second->tagi]));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 调试 ==========
|
||||
|
||||
void ExpressionEngine::printVars(const std::string& exp_str) {
|
||||
// 简单调试输出:遍历所有变量
|
||||
// 调用方可使用 LOG 宏自行格式化
|
||||
}
|
||||
|
||||
int ExpressionEngine::firstFill(int data_source, TimePoint& now_time,
|
||||
mix_cc::time_range_t& query_time_range) {
|
||||
// 惰性初始化 VarsCache
|
||||
ensureVarCache(var_cache_, m_tags_.size());
|
||||
|
||||
for (size_t i = 0; i < m_tags_.size(); i++) {
|
||||
auto idx = std::to_string(i + 1);
|
||||
double value = 0.0;
|
||||
|
||||
// 从共享内存读取当前值
|
||||
value = SingletonTemplate<GlobaltemSharedMemory>::GetInstance()[m_tags_[i]];
|
||||
|
||||
mm_vars_["s" + idx] = value;
|
||||
mm_vars_["p" + idx] = value;
|
||||
mm_vars_["tag" + idx] = value;
|
||||
mm_vars_["mv2_tag" + idx] = 0;
|
||||
mm_vars_["mv2_p" + idx] = 0;
|
||||
mm_vars_["up_tag" + idx] = 0;
|
||||
mm_vars_["dw_tag" + idx] = 0;
|
||||
mm_vars_["mx_tag" + idx] = value;
|
||||
mm_vars_["mi_tag" + idx] = value;
|
||||
|
||||
auto pv_str = "pv" + idx;
|
||||
mm_vars_[pv_str + "_0"] = value;
|
||||
mm_vars_[pv_str + "_1"] = value;
|
||||
mm_vars_[pv_str + "_2"] = value;
|
||||
mm_vars_[pv_str + "_3"] = value;
|
||||
mm_vars_[pv_str + "_4"] = value;
|
||||
mm_vars_[pv_str + "_5"] = value;
|
||||
}
|
||||
|
||||
mm_vars_["stime"] = 0;
|
||||
mm_vars_["now"] = 0;
|
||||
mm_vars_["etime"] = 0;
|
||||
mm_vars_["time"] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
// eqpalg/utility/expression_engine.h
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <eqpalg/utility/HoldTime.h>
|
||||
#include <eqpalg/utility/StatExp.hpp>
|
||||
#include <eqpalg/utility/VarsCache.hpp>
|
||||
#include <mix_cc/matheval/matheval.hpp>
|
||||
#include <mix_cc/type/mix_time.h>
|
||||
|
||||
using MExp = mix_cc::matheval::Expression;
|
||||
using TimePoint = std::chrono::system_clock::time_point;
|
||||
|
||||
/**
|
||||
* @brief 统一表达式引擎
|
||||
*
|
||||
* 集中管理所有命名表达式的注册、求值、FunVars 状态函数、
|
||||
* 变量刷新(共享内存/IHDB)、hold 变量。
|
||||
*
|
||||
* 替代 ExpModule + ExpBase 的表达式管理,消除 is_exp_alg_ 补丁。
|
||||
*/
|
||||
class ExpressionEngine {
|
||||
public:
|
||||
/**
|
||||
* @param mm_vars 变量映射(由 AlgBase 持有,此处引用)
|
||||
* @param m_tags 数据 tag 点列表(由 AlgBase 持有,此处引用)
|
||||
*/
|
||||
ExpressionEngine(std::map<std::string, double>& mm_vars,
|
||||
std::vector<std::string>& m_tags);
|
||||
|
||||
~ExpressionEngine() = default;
|
||||
|
||||
// 禁止拷贝/移动
|
||||
ExpressionEngine(const ExpressionEngine&) = delete;
|
||||
ExpressionEngine& operator=(const ExpressionEngine&) = delete;
|
||||
|
||||
// ========== 表达式注册 ==========
|
||||
|
||||
/**
|
||||
* @brief 注册一个命名表达式(自动进行 FunVars 状态函数替换)
|
||||
* @param name 表达式名称 ("act", "feedback", "result", "pre_result", ...)
|
||||
* @param raw_exp_str 原始表达式字符串(可含 KeepC(tag1,1) 等状态函数)
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int registerExpression(const std::string& name, const std::string& raw_exp_str);
|
||||
|
||||
/**
|
||||
* @brief 注册不带 FunVars 替换的原始表达式
|
||||
*/
|
||||
int registerRawExpression(const std::string& name, const std::string& raw_exp_str);
|
||||
|
||||
// ========== 表达式求值 ==========
|
||||
|
||||
double evaluate(const std::string& name);
|
||||
bool evaluateBool(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 获取变量 map 引用(供外部直接读写)
|
||||
*/
|
||||
std::map<std::string, double>& vars() { return mm_vars_; }
|
||||
|
||||
// ========== 每周期数据刷新 ==========
|
||||
|
||||
void refreshFromMemory(const TimePoint& now_time,
|
||||
mix_cc::time_range_t& query_time_range);
|
||||
|
||||
void refreshFromIhdRow(int row,
|
||||
const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>& queried_data,
|
||||
const std::vector<TimePoint>& queried_time,
|
||||
TimePoint& now_time,
|
||||
mix_cc::time_range_t& query_time_range);
|
||||
|
||||
int firstFill(int data_source, TimePoint& now_time,
|
||||
mix_cc::time_range_t& query_time_range);
|
||||
|
||||
// ========== FunVars 控制 ==========
|
||||
|
||||
void autoResetFunVars();
|
||||
void markFunVarsNeedReset();
|
||||
void forceResetFunVars();
|
||||
|
||||
// ========== hold 变量 ==========
|
||||
|
||||
void refreshHoldVars();
|
||||
|
||||
// ========== 调试 ==========
|
||||
|
||||
void printVars(const std::string& exp_str = "");
|
||||
|
||||
private:
|
||||
std::map<std::string, double>& mm_vars_;
|
||||
std::vector<std::string>& m_tags_;
|
||||
|
||||
StatExp::FunVars fun_vars_;
|
||||
bool fun_vars_need_reset_ = false;
|
||||
|
||||
// 表达式注册表
|
||||
struct ExpEntry {
|
||||
std::string raw_str;
|
||||
std::string processed_str;
|
||||
std::unique_ptr<MExp> ptr;
|
||||
};
|
||||
std::map<std::string, ExpEntry> exps_;
|
||||
|
||||
// hold(n,T) 变量
|
||||
std::map<std::string, std::unique_ptr<HoldTime>> hold_times_;
|
||||
|
||||
VarsCache var_cache_;
|
||||
static constexpr size_t PV_NUM = 6;
|
||||
|
||||
// 内部辅助
|
||||
int initHoldExpStr(const std::string& exp_str);
|
||||
};
|
||||
@ -1,226 +0,0 @@
|
||||
// eqpalg/utility/fb_state_machine.cpp
|
||||
#include <eqpalg/utility/fb_state_machine.h>
|
||||
#include <cmath>
|
||||
#include <cfloat>
|
||||
#include <string>
|
||||
|
||||
// ========== 配置 ==========
|
||||
|
||||
void FbStateMachine::configure(bool keepMode, TimeDur timeout) {
|
||||
keep_mode_ = keepMode;
|
||||
timeout_ = timeout;
|
||||
state_ = FbState::Idle;
|
||||
}
|
||||
|
||||
// ========== 每周期状态更新 ==========
|
||||
|
||||
FbUpdateResult FbStateMachine::update(bool actTriggered, TimePoint now,
|
||||
std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count) {
|
||||
FbUpdateResult result;
|
||||
result.funVarsNeedReset = false;
|
||||
|
||||
switch (state_) {
|
||||
|
||||
case FbState::Idle:
|
||||
if (actTriggered) {
|
||||
// → Started:快照变量
|
||||
snapshotActionStart(now, mm_vars, tag_count);
|
||||
state_ = FbState::Started;
|
||||
start_time_ = now;
|
||||
result.state = FbState::Started;
|
||||
} else {
|
||||
result.state = FbState::Idle;
|
||||
result.funVarsNeedReset = true; // 空闲时重置 fun_vars
|
||||
}
|
||||
break;
|
||||
|
||||
case FbState::Started:
|
||||
// 更新累积变量(首周期也需要更新)
|
||||
updateActionVars(now, mm_vars, tag_count);
|
||||
|
||||
if (!actTriggered && keep_mode_) {
|
||||
state_ = FbState::Idle;
|
||||
result.state = FbState::NotHold;
|
||||
result.funVarsNeedReset = true;
|
||||
} else if (isAccumulatorNearOverflow(mm_vars, tag_count)) {
|
||||
state_ = FbState::Idle;
|
||||
result.state = FbState::Timeout;
|
||||
result.funVarsNeedReset = true;
|
||||
} else {
|
||||
state_ = FbState::InProgress;
|
||||
result.state = FbState::Started; // 返回 Started,调用方跳过反馈检查
|
||||
}
|
||||
break;
|
||||
|
||||
case FbState::InProgress:
|
||||
// 先更新累积变量
|
||||
updateActionVars(now, mm_vars, tag_count);
|
||||
|
||||
if (!actTriggered && keep_mode_) {
|
||||
state_ = FbState::Idle;
|
||||
result.state = FbState::NotHold;
|
||||
result.funVarsNeedReset = true;
|
||||
} else if (isAccumulatorNearOverflow(mm_vars, tag_count)) {
|
||||
clearActionAccumulators(mm_vars, tag_count);
|
||||
state_ = FbState::Idle;
|
||||
result.state = FbState::Timeout;
|
||||
result.funVarsNeedReset = true;
|
||||
} else if (timeout_ != TimeDur(-32768) &&
|
||||
(now - start_time_) > timeout_) {
|
||||
clearActionAccumulators(mm_vars, tag_count);
|
||||
state_ = FbState::Idle;
|
||||
result.state = FbState::Timeout;
|
||||
result.funVarsNeedReset = true;
|
||||
} else {
|
||||
result.state = FbState::InProgress;
|
||||
}
|
||||
break;
|
||||
|
||||
// 终端状态 → 下周期自动回 Idle
|
||||
default:
|
||||
state_ = FbState::Idle;
|
||||
result.state = FbState::Idle;
|
||||
result.funVarsNeedReset = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== 反馈条件检查 ==========
|
||||
|
||||
bool FbStateMachine::checkFeedback(bool fbCondition, TimePoint now,
|
||||
std::map<std::string, double>& mm_vars) {
|
||||
if (state_ != FbState::InProgress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fbCondition) {
|
||||
recordActionEnd(now, mm_vars);
|
||||
state_ = FbState::Idle; // 下周期回到 Idle
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========== 查询 ==========
|
||||
|
||||
bool FbStateMachine::isActive() const {
|
||||
return state_ == FbState::Started || state_ == FbState::InProgress;
|
||||
}
|
||||
|
||||
// ========== 强制重置 ==========
|
||||
|
||||
void FbStateMachine::forceReset() {
|
||||
state_ = FbState::Idle;
|
||||
start_time_ = TimePoint{};
|
||||
}
|
||||
|
||||
// ========== 内部变量操作 ==========
|
||||
|
||||
void FbStateMachine::snapshotActionStart(
|
||||
TimePoint now,
|
||||
std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count) {
|
||||
|
||||
mm_vars["stime"] = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch())
|
||||
.count();
|
||||
mm_vars["time"] = 0;
|
||||
|
||||
for (size_t i = 0; i < tag_count; i++) {
|
||||
std::string idx = std::to_string(i + 1);
|
||||
double tag_val = mm_vars["tag" + idx];
|
||||
double p_val = mm_vars["p" + idx];
|
||||
|
||||
// s[n] = tag[n] 在动作开始时刻的快照值
|
||||
mm_vars["s" + idx] = tag_val;
|
||||
|
||||
// 极值初始化为当前值
|
||||
mm_vars["mx_tag" + idx] = tag_val;
|
||||
mm_vars["mi_tag" + idx] = tag_val;
|
||||
|
||||
// 累积器初始化为 0
|
||||
mm_vars["mv2_tag" + idx] = 0;
|
||||
mm_vars["mv2_p" + idx] = 0;
|
||||
|
||||
// 上升沿/下降沿初值
|
||||
mm_vars["up_tag" + idx] = (p_val == 0 && tag_val == 1) ? 1.0 : 0.0;
|
||||
mm_vars["dw_tag" + idx] = (p_val == 1 && tag_val == 0) ? 1.0 : 0.0;
|
||||
|
||||
// 若当前已为 1,初始计数为 1
|
||||
if (tag_val == 1) {
|
||||
mm_vars["mv2_tag" + idx] = 1;
|
||||
}
|
||||
if (p_val == 1) {
|
||||
mm_vars["mv2_p" + idx] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FbStateMachine::updateActionVars(
|
||||
TimePoint now,
|
||||
std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count) {
|
||||
|
||||
mm_vars["time"] = mm_vars["now"] - mm_vars["stime"];
|
||||
|
||||
for (size_t i = 0; i < tag_count; i++) {
|
||||
std::string idx = std::to_string(i + 1);
|
||||
double tag_val = mm_vars["tag" + idx];
|
||||
double p_val = mm_vars["p" + idx];
|
||||
|
||||
// 更新极值
|
||||
mm_vars["mx_tag" + idx] = std::max(tag_val, mm_vars["mx_tag" + idx]);
|
||||
mm_vars["mi_tag" + idx] = std::min(tag_val, mm_vars["mi_tag" + idx]);
|
||||
|
||||
// 累积 1 的计数
|
||||
if (tag_val == 1) {
|
||||
mm_vars["mv2_tag" + idx] += 1;
|
||||
}
|
||||
if (p_val == 1) {
|
||||
mm_vars["mv2_p" + idx] += 1;
|
||||
}
|
||||
|
||||
// 上升沿/下降沿计数
|
||||
mm_vars["up_tag" + idx] += (p_val == 0 && tag_val == 1) ? 1.0 : 0.0;
|
||||
mm_vars["dw_tag" + idx] += (p_val == 1 && tag_val == 0) ? 1.0 : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void FbStateMachine::recordActionEnd(
|
||||
TimePoint now,
|
||||
std::map<std::string, double>& mm_vars) {
|
||||
|
||||
mm_vars["etime"] = mm_vars["now"];
|
||||
}
|
||||
|
||||
void FbStateMachine::clearActionAccumulators(
|
||||
std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count) {
|
||||
|
||||
for (size_t i = 0; i < tag_count; i++) {
|
||||
std::string idx = std::to_string(i + 1);
|
||||
mm_vars["mv2_tag" + idx] = 0;
|
||||
mm_vars["mv2_p" + idx] = 0;
|
||||
mm_vars["up_tag" + idx] = 0;
|
||||
mm_vars["dw_tag" + idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool FbStateMachine::isAccumulatorNearOverflow(
|
||||
const std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count) const {
|
||||
|
||||
for (size_t i = 0; i < tag_count; i++) {
|
||||
std::string key = "mv2_tag" + std::to_string(i + 1);
|
||||
auto it = mm_vars.find(key);
|
||||
if (it != mm_vars.end()) {
|
||||
if (std::abs(it->second - DBL_MAX) < 2.0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
// eqpalg/utility/fb_state_machine.h
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
using TimePoint = std::chrono::system_clock::time_point;
|
||||
using TimeDur = std::chrono::milliseconds;
|
||||
|
||||
/**
|
||||
* @brief 反馈动作状态枚举
|
||||
*/
|
||||
enum class FbState {
|
||||
Idle, // 空闲(无活动动作)
|
||||
Started, // 刚启动(首周期检测到触发条件,下次检查反馈)
|
||||
InProgress, // 进行中(等待反馈条件满足或超时)
|
||||
Done, // 反馈条件满足,动作完成
|
||||
NotHold, // keep_mode 下触发条件丢失
|
||||
Timeout // 超时
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief FbStateMachine::update() 的返回结构
|
||||
*/
|
||||
struct FbUpdateResult {
|
||||
FbState state;
|
||||
bool funVarsNeedReset; // 本周期是否需要标记 fun_vars 重置
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 反馈动作状态机
|
||||
*
|
||||
* 替代 ExpBase 中分散的 4 个方法(act_start_done/act_not_hold/act_done/act_timeout)
|
||||
* 和 4 个布尔标志(act_started_/act_triggered_/feedback_triggered_/feedback_done_)。
|
||||
*
|
||||
* 管理反馈动作的完整生命周期:
|
||||
* Idle → Started → InProgress → Done/NotHold/Timeout → Idle
|
||||
*
|
||||
* 同时负责动作期间的变量快照和累积更新(通过 VarManager 引用)。
|
||||
*/
|
||||
class FbStateMachine {
|
||||
public:
|
||||
FbStateMachine() = default;
|
||||
~FbStateMachine() = default;
|
||||
|
||||
// ========== 配置 ==========
|
||||
|
||||
/**
|
||||
* @param keepMode 是否保持模式(触发条件丢失时退出动作)
|
||||
* @param timeout 超时时间(-32768ms 表示无限制,仅防溢出)
|
||||
*/
|
||||
void configure(bool keepMode, TimeDur timeout);
|
||||
|
||||
// ========== 每周期调用 ==========
|
||||
|
||||
/**
|
||||
* @brief 更新状态机(每个 mon 周期调用一次)
|
||||
*
|
||||
* @param actTriggered 前提表达式结果(exp_act_->evaluate() 的布尔值)
|
||||
* @param now 当前时间
|
||||
* @param mm_vars 变量映射(用于快照/累积更新)
|
||||
* @param tag_count tag 点数量
|
||||
*
|
||||
* @return FbUpdateResult — 新状态 + 是否需要重置 fun_vars
|
||||
*/
|
||||
FbUpdateResult update(bool actTriggered, TimePoint now,
|
||||
std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count);
|
||||
|
||||
/**
|
||||
* @brief 检查反馈条件(在 update() 返回 InProgress 后调用)
|
||||
*
|
||||
* @param fbCondition 反馈表达式结果(exp_feedback_->evaluate() 的布尔值)
|
||||
* @param now 当前时间
|
||||
* @param mm_vars 变量映射
|
||||
*
|
||||
* @return true → 反馈条件满足,转为 Done
|
||||
* @return false → 继续保持 InProgress
|
||||
*/
|
||||
bool checkFeedback(bool fbCondition, TimePoint now,
|
||||
std::map<std::string, double>& mm_vars);
|
||||
|
||||
// ========== 查询 ==========
|
||||
|
||||
bool isActive() const; // Started 或 InProgress
|
||||
bool isKeepMode() const { return keep_mode_; }
|
||||
TimePoint actionStartTime() const { return start_time_; }
|
||||
FbState currentState() const { return state_; }
|
||||
|
||||
// ========== 外部强制重置(PRR=false 时调用)==========
|
||||
|
||||
void forceReset();
|
||||
|
||||
// ========== 获取 m_timemode(供外部判断报警消息格式)==========
|
||||
|
||||
bool isTimeMode() const { return time_mode_; }
|
||||
void setTimeMode(bool v) { time_mode_ = v; }
|
||||
|
||||
private:
|
||||
bool keep_mode_ = false;
|
||||
TimeDur timeout_ = std::chrono::minutes(10);
|
||||
FbState state_ = FbState::Idle;
|
||||
TimePoint start_time_;
|
||||
bool time_mode_ = false; // 原 ExpBase::m_timemode
|
||||
|
||||
// ========== 内部变量操作(替代分散在 act_* 方法中的变量更新)==========
|
||||
|
||||
void snapshotActionStart(TimePoint now,
|
||||
std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count);
|
||||
void updateActionVars(TimePoint now,
|
||||
std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count);
|
||||
void recordActionEnd(TimePoint now,
|
||||
std::map<std::string, double>& mm_vars);
|
||||
void clearActionAccumulators(std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count);
|
||||
bool isAccumulatorNearOverflow(
|
||||
const std::map<std::string, double>& mm_vars,
|
||||
size_t tag_count) const;
|
||||
};
|
||||
@ -1,112 +0,0 @@
|
||||
// eqpalg/utility/stat_collector.cpp
|
||||
#include <eqpalg/utility/stat_collector.h>
|
||||
#include <eqpalg/feature_extraction/daa.h>
|
||||
#include <eqpalg/define/public.h>
|
||||
#include <algorithm>
|
||||
#include <log4cplus/LOG.h>
|
||||
|
||||
StatCollector::~StatCollector() = default;
|
||||
|
||||
void StatCollector::configure(const std::string& ruleId,
|
||||
const std::string& ruleName,
|
||||
int distMode, bool isLearning) {
|
||||
rule_id_ = ruleId;
|
||||
rule_name_ = ruleName;
|
||||
dist_mode_ = distMode;
|
||||
is_learning_ = isLearning;
|
||||
configured_ = true;
|
||||
}
|
||||
|
||||
int StatCollector::processCron(const std::vector<double>& statValues,
|
||||
TimePoint now, int cronDelayHours,
|
||||
TimePoint& lastLoadTime) {
|
||||
if (statValues.empty()) return 0;
|
||||
|
||||
int size_data = static_cast<int>(statValues.size());
|
||||
|
||||
if (sta_ptr_ == nullptr) {
|
||||
sta_ptr_ = std::make_unique<DAA::STA>(rule_id_, rule_name_);
|
||||
sta_ptr_->update_ci_dist();
|
||||
lastLoadTime = now;
|
||||
}
|
||||
|
||||
if (now - lastLoadTime > std::chrono::hours(cronDelayHours)) {
|
||||
sta_ptr_->update_ci_dist();
|
||||
lastLoadTime = now;
|
||||
}
|
||||
|
||||
if (sta_ptr_->is_init()) {
|
||||
for (int i = 0; i < size_data; i++) {
|
||||
sta_ptr_->dist_add(statValues[i]);
|
||||
}
|
||||
} else {
|
||||
double max_data = *std::max_element(statValues.begin(), statValues.end());
|
||||
double min_data = *std::min_element(statValues.begin(), statValues.end());
|
||||
double range = (max_data - min_data) / static_cast<double>(DAA::STA_SIZE_MIN);
|
||||
if (range < 0.1) range = 0.1;
|
||||
if (sta_ptr_->init(range, min_data)) {
|
||||
for (int i = 0; i < size_data; i++) {
|
||||
sta_ptr_->dist_add(statValues[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sta_ptr_->store_db2();
|
||||
return size_data;
|
||||
}
|
||||
|
||||
bool StatCollector::reloadCiDist(double& limitDown, double& limitUp,
|
||||
TimePoint now, TimePoint& lastLoadTime) {
|
||||
if (dist_mode_ == 0) return false; // 手动模式,不自动加载
|
||||
|
||||
lastLoadTime = now;
|
||||
|
||||
mix_cc::float_range_t dist_range;
|
||||
if (dist_mode_ == DistMode::Online) {
|
||||
dist_range = DAA::STA::select_from_t_rule_feature(rule_id_);
|
||||
} else if (dist_mode_ == DistMode::Offline) {
|
||||
dist_range = DAA::STA::select_from_t_sample_mag(rule_id_);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dist_range.get_distance() == 0 ||
|
||||
dist_range.get_left() > dist_range.get_right()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
limitDown = dist_range.get_left();
|
||||
limitUp = dist_range.get_right();
|
||||
return true;
|
||||
}
|
||||
|
||||
void StatCollector::reset(const std::string& ruleId) {
|
||||
DAA::STA::delete_statistics_data(ruleId);
|
||||
sta_ptr_.reset();
|
||||
}
|
||||
|
||||
void StatCollector::ensureInitialized() {
|
||||
if (sta_ptr_ == nullptr) {
|
||||
sta_ptr_ = std::make_unique<DAA::STA>(rule_id_, rule_name_);
|
||||
}
|
||||
}
|
||||
|
||||
void StatCollector::resetData() {
|
||||
sta_ptr_->reset_data();
|
||||
}
|
||||
|
||||
void StatCollector::distAdd(double value) {
|
||||
sta_ptr_->dist_add(value);
|
||||
}
|
||||
|
||||
bool StatCollector::taskStoreDb2(const std::string& sampleId) {
|
||||
return sta_ptr_->task_store_db2(sampleId);
|
||||
}
|
||||
|
||||
std::string StatCollector::getSampleStatStr() {
|
||||
return sta_ptr_->get_sample_stat_str();
|
||||
}
|
||||
|
||||
void StatCollector::runningStatAdd(double value) {
|
||||
sta_ptr_->running_stat_add(value);
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
// eqpalg/utility/stat_collector.h
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 前向声明
|
||||
namespace DAA { class STA; }
|
||||
|
||||
using TimePoint = std::chrono::system_clock::time_point;
|
||||
|
||||
/**
|
||||
* @brief 统计学习收集器
|
||||
*
|
||||
* 管理 DAA::STA 分布统计的生命周期:
|
||||
* - 样本累积(dist_add)
|
||||
* - 分布初始化与 DB2 持久化
|
||||
* - 置信区间更新
|
||||
*/
|
||||
class StatCollector {
|
||||
public:
|
||||
StatCollector() = default;
|
||||
~StatCollector();
|
||||
|
||||
/**
|
||||
* @brief 配置统计收集器
|
||||
* @param ruleId 规则 ID
|
||||
* @param ruleName 规则名称
|
||||
* @param distMode 分布模式(0=手动, 1=在线, 2=离线)
|
||||
* @param isLearning 是否启用自学习
|
||||
*/
|
||||
void configure(const std::string& ruleId, const std::string& ruleName,
|
||||
int distMode, bool isLearning);
|
||||
|
||||
/**
|
||||
* @brief cron 进程:从共享内存获取累积样本,更新分布并持久化
|
||||
* @param statValues 累积的样本值
|
||||
* @param now 当前时间
|
||||
* @param cronDelayHours cron 更新延迟(小时)
|
||||
* @param lastLoadTime [in/out] 上次加载时间
|
||||
* @return 处理的样本数量
|
||||
*/
|
||||
int processCron(const std::vector<double>& statValues,
|
||||
TimePoint now, int cronDelayHours,
|
||||
TimePoint& lastLoadTime);
|
||||
|
||||
/**
|
||||
* @brief 加载置信区间到上下限
|
||||
* @param limitDown [out] 新的下限
|
||||
* @param limitUp [out] 新的上限
|
||||
* @param now 当前时间
|
||||
* @param lastLoadTime [out] 更新时间
|
||||
* @return true 成功加载新区间
|
||||
*/
|
||||
bool reloadCiDist(double& limitDown, double& limitUp,
|
||||
TimePoint now, TimePoint& lastLoadTime);
|
||||
|
||||
/**
|
||||
* @brief 删除统计数据和重置
|
||||
*/
|
||||
void reset(const std::string& ruleId);
|
||||
|
||||
bool isConfigured() const { return configured_; }
|
||||
|
||||
// ---- task 进程专用接口 ----
|
||||
|
||||
/** @brief 确保 STA 实例已初始化(惰性初始化) */
|
||||
void ensureInitialized();
|
||||
|
||||
/** @brief 重置累积数据,供新 task 使用 */
|
||||
void resetData();
|
||||
|
||||
/** @brief 向分布添加单个值 */
|
||||
void distAdd(double value);
|
||||
|
||||
/** @brief 将 task 结果存储到 DB2 (T_SAMPLE_STAT) */
|
||||
bool taskStoreDb2(const std::string& sampleId);
|
||||
|
||||
/** @brief 获取样本统计字符串,用于 T_SAMPLE_MAG */
|
||||
std::string getSampleStatStr();
|
||||
|
||||
/** @brief 向运行统计中添加值 */
|
||||
void runningStatAdd(double value);
|
||||
|
||||
private:
|
||||
std::unique_ptr<DAA::STA> sta_ptr_;
|
||||
std::string rule_id_;
|
||||
std::string rule_name_;
|
||||
int dist_mode_ = 0;
|
||||
bool is_learning_ = true;
|
||||
bool configured_ = false;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user