From eb48f35a7d986bca5c379ae677e6e5498d76dc5c Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Wed, 15 Jul 2026 14:38:31 +0200 Subject: [PATCH 01/28] DemandHandler first try --- doc/graph.puml | 98 +++ out/doc/graph/ITCPressureOptimizer.png | Bin 0 -> 69567 bytes src/device/Average.cpp | 71 ++ src/device/Average.h | 28 + src/device/DemandHandler.cpp | 106 +++ src/device/DemandHandler.h | 37 + src/device/DemandHandlerConfig.h | 20 + src/device/FeedbackHandler.cpp | 31 + src/device/FeedbackHandler.h | 34 + src/device/InputHandler.cpp | 0 src/device/InputHandler.h | 15 + src/device/OutputHandler.cpp | 7 + src/device/OutputHandler.h | 11 + src/device/PressureCalculator.cpp | 46 ++ src/device/PressureCalculator.h | 27 + src/device/SettingsHandler.cpp | 0 src/device/SettingsHandler.h | 6 + src/device/itc_pressure_optimizer.cpp | 833 --------------------- src/device/itc_pressure_optimizer.h | 65 -- src/device/itc_pressure_optimizer_config.h | 85 --- src/device/itc_pressure_optimizer_info.h | 56 -- src/device/odb/OdbReader.tpp | 34 + src/device/odb/OdbWriter.tpp | 35 + 23 files changed, 606 insertions(+), 1039 deletions(-) create mode 100644 doc/graph.puml create mode 100644 out/doc/graph/ITCPressureOptimizer.png create mode 100644 src/device/Average.cpp create mode 100644 src/device/Average.h create mode 100644 src/device/DemandHandler.cpp create mode 100644 src/device/DemandHandler.h create mode 100644 src/device/DemandHandlerConfig.h create mode 100644 src/device/FeedbackHandler.cpp create mode 100644 src/device/FeedbackHandler.h create mode 100644 src/device/InputHandler.cpp create mode 100644 src/device/InputHandler.h create mode 100644 src/device/OutputHandler.cpp create mode 100644 src/device/OutputHandler.h create mode 100644 src/device/PressureCalculator.cpp create mode 100644 src/device/PressureCalculator.h create mode 100644 src/device/SettingsHandler.cpp create mode 100644 src/device/SettingsHandler.h delete mode 100644 src/device/itc_pressure_optimizer.cpp delete mode 100644 src/device/itc_pressure_optimizer.h delete mode 100644 src/device/itc_pressure_optimizer_config.h delete mode 100644 src/device/itc_pressure_optimizer_info.h create mode 100644 src/device/odb/OdbReader.tpp create mode 100644 src/device/odb/OdbWriter.tpp diff --git a/doc/graph.puml b/doc/graph.puml new file mode 100644 index 0000000..9056964 --- /dev/null +++ b/doc/graph.puml @@ -0,0 +1,98 @@ +@startuml ITCPressureOptimizer + +class OdbWrited{ + +} + +class OdbReader{ + +} + +class InputHandler { + equipmentName : string + spindex : int + tempindex : int + powIndex : int + + setEquipmentName(name : string) + setSPIndex(index : int) + setTemperatureIndex(index : int) + setPowerIndex(index : int) +} + +class SettingsHandler { + +} + +class PressureCalculator { + # From InputHandler + cachedSP : float + cachedTemperature : float + cachedPower : float + + # From DemandHandler + cachedMinimalPressure : float + cachedMaximalPressure : float + cachedConstante1 : float + cachedConstante2 : float + + trigger() : void + + updateSP(value : float) + updateTemperature(value : float) + updatePower(value : float) + updateMinimalPressure(value : float) + updateMaximalPressure(value : float) + updateConstante1(value : float) + updateConstante2(value : float) +} + +class Average { + addValue(float : value) + getAverage() : float +} + +class DemandHandler { + +} + +class FeedbackHandler { + bool isPressureControlModeEnable + enablePressureControlMode(enable : bool) + + setSP(value : float) + setTemperature(value : float) + setPower(value : float) + setAveragePower(value : float) + setPressure(value : float) + setConstante1(value : float) + setConstante2(value : float) +} + +class OutputHandler { + bool isSetPressureEnable + float pressure + enableSetPressure(enable : bool) + setPressure(value : float) +} + +circle ODB_Settings +circle ODB_Equipment_Input +circle ODB_Demand + +ODB_Settings ..> SettingsHandler : Hotlink +ODB_Equipment_Input ..> InputHandler : Hotlink +ODB_Demand ..> DemandHandler : Hotlink + + +SettingsHandler --> InputHandler +InputHandler --> PressureCalculator +DemandHandler --> PressureCalculator + +PressureCalculator --> Average +PressureCalculator --> OutputHandler + +InputHandler --> FeedbackHandler +DemandHandler --> FeedbackHandler + +@enduml \ No newline at end of file diff --git a/out/doc/graph/ITCPressureOptimizer.png b/out/doc/graph/ITCPressureOptimizer.png new file mode 100644 index 0000000000000000000000000000000000000000..35a06e2b4f468229807e21f65104d4515416965e GIT binary patch literal 69567 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 z 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 literal 0 HcmV?d00001 diff --git a/src/device/Average.cpp b/src/device/Average.cpp new file mode 100644 index 0000000..9617bca --- /dev/null +++ b/src/device/Average.cpp @@ -0,0 +1,71 @@ +#include "Average.h" +#include +#include + +void Average::setWindowSize(double seconds) { + windowSize = Duration(seconds); + cleanupAndUpdate(Clock::now()); +} + +void Average::addValue(double value) { + TimePoint now = Clock::now(); + cleanupAndUpdate(now); + + currentValue = value; + history.push_back({now, currentValue}); +} + +double Average::getAverage() { + TimePoint now = Clock::now(); + cleanupAndUpdate(now); + + if (history.empty()) { + return currentValue; + } + + // If only one element, it's all the window + if (history.size() == 1) { + return history.front().value; + } + + double totalWeightedValue = 0.0; + double totalDurationSeconds = 0.0; + + // Go throught the full historic + for (size_t i = 0; i < history.size() - 1; ++i) { + // How much time the value was present + Duration duration = history[i + 1].timestamp - history[i].timestamp; + + totalWeightedValue += history[i].value * duration.count(); + totalDurationSeconds += duration.count(); + } + + // add last segment : duration = now - timestamp + Duration lastDuration = now - history.back().timestamp; + totalWeightedValue += history.back().value * lastDuration.count(); + totalDurationSeconds += lastDuration.count(); + + /* + Safety to prevent dividing by 0 + This can occure if only one point is set and we compute the average too + fast + */ + if (totalDurationSeconds <= 0.0) { + return history.back().value; + } + + return totalWeightedValue / totalDurationSeconds; +} + +void Average::cleanupAndUpdate(TimePoint now) { + // Clean all element completly ouside the temporal window + while (history.size() > 1 && (now - history[1].timestamp) >= windowSize) { + history.pop_front(); + } + + // If last element is partialy outside, get his timestamp back + if (!history.empty() && (now - history.front().timestamp) > windowSize) { + history.front().timestamp = + now - std::chrono::duration_cast(windowSize); + } +} \ No newline at end of file diff --git a/src/device/Average.h b/src/device/Average.h new file mode 100644 index 0000000..3e76bbf --- /dev/null +++ b/src/device/Average.h @@ -0,0 +1,28 @@ +#include +#include + +class Average { + private: + using Clock = std::chrono::steady_clock; + using TimePoint = Clock::time_point; + using Duration = std::chrono::duration; + + struct TimeValue { + TimePoint timestamp; + double value; + }; + + std::deque history; + Duration windowSize; + double currentValue = 0.0; + + void cleanupAndUpdate(TimePoint now); + + public: + Average(double windowSizeSeconds) + : windowSize(Duration(windowSizeSeconds)) {}; + + void setWindowSize(double seconds); + void addValue(double value); + double getAverage(); +}; diff --git a/src/device/DemandHandler.cpp b/src/device/DemandHandler.cpp new file mode 100644 index 0000000..96e6b7a --- /dev/null +++ b/src/device/DemandHandler.cpp @@ -0,0 +1,106 @@ +#include "DemandHandler.h" +#include "DemandHandlerConfig.h" +#include +#include + +bool DemandHandler::init() { + midas::odb o(this->path); + + if (!o.exists()) { + // TODO : ERROR MESSAGE + return; + } + + bool indexValid = true; + + if (o.size() < ParameterIndex::MINIMAL_PRESSURE) { + indexValid = false; + } + if (o.size() < ParameterIndex::MAXIMAL_PRESSURE) { + indexValid = false; + } + if (o.size() < ParameterIndex::CONSTANTE_1) { + indexValid = false; + } + if (o.size() < ParameterIndex::CONSTANTE_2) { + indexValid = false; + } + + if (indexValid) + return; + + pullMinimalPressure(); + pullMaximalPressure(); + pullConstante1(); + pullConstante2(); + pullPressureControlMode(); +} + +bool DemandHandler::setHotlink() { + midas::odb to_watch(this->path); + to_watch([&](midas::odb &arg) { + switch (arg.get_last_index()) { + case ParameterIndex::MINIMAL_PRESSURE: + pullMinimalPressure(); + break; + case ParameterIndex::MAXIMAL_PRESSURE: + pullMaximalPressure(); + break; + case ParameterIndex::MINIMAL_PRESSURE: + pullConstante1(); + break; + case ParameterIndex::MINIMAL_PRESSURE: + pullConstante2(); + break; + case ParameterIndex::PRESSURE_CONTROL_MODE: + pullPressureControlMode(); + break; + default: + // Update out of scope, discarded + break; + } + }); +} + +void DemandHandler::pullMinimalPressure() { + midas::odb o(this->path); + float minimalPressure = o[ParameterIndex::MINIMAL_PRESSURE]; + pressureCalculator.get().updateMinimalPressure(minimalPressure); +} + +void DemandHandler::pullMaximalPressure() { + midas::odb o(this->path); + float maximalPressure = o[ParameterIndex::MAXIMAL_PRESSURE]; + pressureCalculator.get().updateMinimalPressure(maximalPressure); +} + +void DemandHandler::pullConstante1() { + midas::odb o(this->path); + float constante1 = o[ParameterIndex::CONSTANTE_1]; + pressureCalculator.get().updateMinimalPressure(constante1); + feedbackHandler.get().setConstante1(constante1); +} + +void DemandHandler::pullConstante2() { + midas::odb o(this->path); + float constance2 = o[ParameterIndex::CONSTANTE_2]; + pressureCalculator.get().updateMinimalPressure(constance2); + feedbackHandler.get().setConstante2(constante2); +} + +void DemandHandler::pullPressureControlMode() { + midas::odb o(this->path); + float pressureControlMode = o[ParameterIndex::PRESSURE_CONTROL_MODE]; + feedbackHandler.get().enablePressureControlMode(pressureControlMode); +} + +DemandHandler::DemandHandler( + std::string equipmentName, + std::reference_wrapper feedbackHandler, + std::reference_wrapper pressureCalculator) { + + this->path = DemanHandlerConfig::PATH_PREFIX + equipmentName + + DemanHandlerConfig::PATH_SUFFIX; + this->feedbackHandler = feedbackHandler; + this->pressureCalculator = pressureCalculator; +} diff --git a/src/device/DemandHandler.h b/src/device/DemandHandler.h new file mode 100644 index 0000000..9b8ca25 --- /dev/null +++ b/src/device/DemandHandler.h @@ -0,0 +1,37 @@ +#ifndef DEMAND_HANDLER_H +#define DEMAND_HANDLER_H + +#include "FeedbackHandler.h" +#include "PressureCalculator.h" +#include +#include + +class DemandHandler { + private: + std::string path; + std::reference_wrapper feedbackHandler; + std::reference_wrapper pressureCalculator; + + void pullMinimalPressure(); + void pullMaximalPressure(); + void pullConstante1(); + void pullConstante2(); + void pullPressureControlMode(); + + public: + DemandHandler( + std::string equipmentName, + std::reference_wrapper feedbackHandler, + std::reference_wrapper pressureCalculator); + /* + @brief Manualy get all the value, and update owned objects + */ + void init(); + /* + @brief Set all the hotlink, waiting for any update + @return true if succes, false if any error encountered + */ + bool setHotlink(); +}; + +#endif \ No newline at end of file diff --git a/src/device/DemandHandlerConfig.h b/src/device/DemandHandlerConfig.h new file mode 100644 index 0000000..1bdf8ff --- /dev/null +++ b/src/device/DemandHandlerConfig.h @@ -0,0 +1,20 @@ +#ifndef DEMAND_HANDLER_CONFIG_H +#define DEMAND_HANDLER_CONFIG_H + +#include + +namespace DemanHandlerConfig { +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Demand"; + +class enum ParameterIndex { + PRESSURE_CONTROL_MODE = 6, + MINIMAL_PRESSURE = 8, + MAXIMAL_PRESSURE = 9, + CONSTANTE_1 = 10, + CONSTANTE_2 = 11 +} + +} // namespace DemanHandlerConfig + +#endif diff --git a/src/device/FeedbackHandler.cpp b/src/device/FeedbackHandler.cpp new file mode 100644 index 0000000..c6b1865 --- /dev/null +++ b/src/device/FeedbackHandler.cpp @@ -0,0 +1,31 @@ +#include "FeedbackHandler.h" +#include + +FeedbackHandler::setValueToOdb(int index, float value) { + // TODO Check validity + midas::odb o(this->odbPath); + o[outputKeyName.c_str()][index] = value; +} + +FeedbackHandler::FeedbackHandler(std::string equipmentName) { + this->equipmentName = equipmentName; + this->odbPath = "/Equipment/" + equipmentName + "/Variables"; +} + +void FeedbackHandler::enablePressureControlMode(bool enable) { + this->isPressureControlModeEnable = enable; +} + +void FeedbackHandler::setSP(float value) {} + +void FeedbackHandler::setTemperature(float value) {} + +void FeedbackHandler::setPower(float value) {} + +void FeedbackHandler::setAveragePower(float value) {} + +void FeedbackHandler::setPressure(float value) {} + +void FeedbackHandler::setConstante1(float value) {} + +void FeedbackHandler::setConstante2(float value) {} diff --git a/src/device/FeedbackHandler.h b/src/device/FeedbackHandler.h new file mode 100644 index 0000000..1e11f22 --- /dev/null +++ b/src/device/FeedbackHandler.h @@ -0,0 +1,34 @@ +#include + +class FeedbackHandler { + private: + bool isPressureControlModeEnable; + std::string equipmentName; + std::string odbPath; + std::string outputKeyName = "Measured" + + enum class RegisterIndex : uint16_t { + SetPoint = 1, + Temperature = 2, + Power = 3, + AveragePower = 4, + Pressure = 5, + Constante1 = 10, + Constante2 = 11 + }; + + setValueToOdb(int index, float value); + + public: + FeedbackHandler(std::string equipmentName); + + void enablePressureControlMode(bool enable); + + void setSP(float value); + void setTemperature(float value); + void setPower(float value); + void setAveragePower(float value); + void setPressure(float value); + void setConstante1(float value); + void setConstante2(float value); +}; diff --git a/src/device/InputHandler.cpp b/src/device/InputHandler.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/device/InputHandler.h b/src/device/InputHandler.h new file mode 100644 index 0000000..fe2b064 --- /dev/null +++ b/src/device/InputHandler.h @@ -0,0 +1,15 @@ +#include + +class InputHandler { + private: + std::string equipmentName; + float cachedSP; + float cachedTemperature; + float cachedPower; + + public: + void updateEquipmentName(std::string name); + void updateSPIndex(int index); + void updateTemperatureIndex(int index); + void updatePowerIndex(int index); +}; diff --git a/src/device/OutputHandler.cpp b/src/device/OutputHandler.cpp new file mode 100644 index 0000000..4efe0b0 --- /dev/null +++ b/src/device/OutputHandler.cpp @@ -0,0 +1,7 @@ +#include "OutputHandler.h" + +void OutputHandler::enableSetPressure(bool enable) { + isSetPressureEnable = enable; +} + +void OutputHandler::setPressure(float value) {} diff --git a/src/device/OutputHandler.h b/src/device/OutputHandler.h new file mode 100644 index 0000000..6474b45 --- /dev/null +++ b/src/device/OutputHandler.h @@ -0,0 +1,11 @@ + + +class OutputHandler { + private: + bool isSetPressureEnable; + float pressure; + + public: + void enableSetPressure(bool enable); + void setPressure(float value); +}; diff --git a/src/device/PressureCalculator.cpp b/src/device/PressureCalculator.cpp new file mode 100644 index 0000000..7c171ab --- /dev/null +++ b/src/device/PressureCalculator.cpp @@ -0,0 +1,46 @@ +#include "PressureCalculator.h" +#include + +void PressureCalculator::update() { + double c2 = average.getAverage() * cachedConstante2; + double c1 = cachedConstante1 * (cachedTemperature - cachedSP - c2); + double uncapedPressure = cachedMinimalPressure + c1; + + if (c2 != 0) { + } +} + +void PressureCalculator::updateSP(float value) { + cachedSP = value; + update(); +} + +void PressureCalculator::updateTemperature(float value) { + cachedTemperature = value; + update(); +} + +void PressureCalculator::updatePower(float value) { + cachedPower = value; + update(); +} + +void PressureCalculator::updateMinimalPressure(float value) { + cachedMinimalPressure = value; + update(); +} + +void PressureCalculator::updateMaximalPressure(float value) { + cachedMaximalPressure = value; + update(); +} + +void PressureCalculator::updateConstante1(float value) { + cachedConstante1 = value; + update(); +} + +void PressureCalculator::updateConstante2(float value) { + cachedConstante2 = value; + update(); +} diff --git a/src/device/PressureCalculator.h b/src/device/PressureCalculator.h new file mode 100644 index 0000000..f53b128 --- /dev/null +++ b/src/device/PressureCalculator.h @@ -0,0 +1,27 @@ +#include "Average.h" + +class PressureCalculator { + private: + Average average; + OutputHandler outputHandler; + float cachedSP; + float cachedTemperature; + float cachedPower; + + float cachedMinimalPressure; + float cachedMaximalPressure; + float cachedConstante1; + float cachedConstante2; + + public: + void update(); + + void updateSP(float value); + void updateTemperature(float value); + void updatePower(float value); + + void updateMinimalPressure(float value); + void updateMaximalPressure(float value); + void updateConstante1(float value); + void updateConstante2(float value); +}; diff --git a/src/device/SettingsHandler.cpp b/src/device/SettingsHandler.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/device/SettingsHandler.h b/src/device/SettingsHandler.h new file mode 100644 index 0000000..8976ee6 --- /dev/null +++ b/src/device/SettingsHandler.h @@ -0,0 +1,6 @@ + + +class SettingsHandler { + private: + public: +}; diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp deleted file mode 100644 index ed0df3a..0000000 --- a/src/device/itc_pressure_optimizer.cpp +++ /dev/null @@ -1,833 +0,0 @@ -#include "itc_pressure_optimizer.h" -#include "itc_pressure_optimizer_config.h" -#include "itc_pressure_optimizer_info.h" - -#include "midas.h" -#include "odbxx.h" -#include "tmfe.h" - -#include -#include -#include - -itcPressureOptimizer::itcPressureOptimizer(std::string equipmentName, - const char *equipmentFilename, - int channel) - : TMFeEquipment(equipmentName.c_str(), equipmentFilename) { - fEqConfReadOnlyWhenRunning = false; - fEqConfPeriodMilliSec = 100; - this->equipmentPath = std::string("/Equipment/") + equipmentName; - info.num_channels = channel; -} - -TMFeResult -itcPressureOptimizer::HandleInit(const std::vector &args) { - this->mitc_pressc_init(); - return TMFeResult(); -} - -void itcPressureOptimizer::HandlePeriodic() { - midas::odb o(this->equipmentPath + "/" + Configuration::VARIABLE_DIR); - - for (int i = 0; i < info.num_channels; i++) { - info.last_measured[i] = o[Configuration::MEASURED_VARNAME.c_str()][i]; - info.last_demand[i] = o[Configuration::DEMAND_VARNAME.c_str()][i]; - } - - this->mitc_pressc_update_mitc_input(); - this->mitc_pressc_update_pressure_demanded(); - this->mitc_pressc_recalculate(); - - for (int i = 0; i < info.num_channels; i++) { - o[Configuration::MEASURED_VARNAME.c_str()][i] = info.last_measured[i]; - o[Configuration::DEMAND_VARNAME.c_str()][i] = info.last_demand[i]; - } -} - -INT itcPressureOptimizer::mitc_pressc_init() { - int status = SUCCESS; - bool isDefaultNameExists; - bool isDefaultThresholdExists; - bool isDemandExists; - bool isMeasuredExists; - isDefaultNameExists = midas::odb::exists(this->equipmentPath + "/" + - Configuration::SETTINGS_DIR + "/" + - Configuration::NAMES_VARNAME); - - isDefaultThresholdExists = midas::odb::exists( - this->equipmentPath + "/" + Configuration::SETTINGS_DIR + "/" + - Configuration::UPDATE_THRESHOLD_MEASURED_VARNAME); - - isDemandExists = midas::odb::exists(this->equipmentPath + "/" + - Configuration::DEMANDE_PATH); - - isMeasuredExists = midas::odb::exists(this->equipmentPath + "/" + - Configuration::MEASURED_PATH); - - if (!isDefaultNameExists || !isDefaultThresholdExists) { - midas::odb o = { - {Configuration::VARIABLE_DIR.c_str(), - {{Configuration::DEMAND_VARNAME.c_str(), {0.0}}, - {Configuration::MEASURED_VARNAME.c_str(), {0.0}}}}, - {Configuration::SETTINGS_DIR.c_str(), - {{Configuration::NAMES_VARNAME.c_str(), {""}}, - {Configuration::UPDATE_THRESHOLD_MEASURED_VARNAME.c_str(), {0.0}}, - {Configuration::DEVICES_DIR.c_str(), - {{Configuration::MIT_DIR.c_str(), - {{Configuration::ENABLE_VARNAME.c_str(), {true}}, - {Configuration::DD_DIR.c_str(), - {{Configuration::MITC_EQUIPMENT_VARNAME.c_str(), {""}}, - {Configuration::MITC_OUTPUT_PRESS_SP_INDEX_VARNAME.c_str(), - {0}}, - {Configuration::MITC_INPUT_VARIOX_SP_INDEX_VARNAME.c_str(), - {0}}, - {Configuration::MITC_INPUT_VARIOX_TEMP_INDEX_VARNAME - .c_str(), - {0}}, - {Configuration::MITC_INPUT_VARIOX_POW_INDEX_VARNAME.c_str(), - {0}}, - {Configuration::RECALC_INTERVAL_VARNAME.c_str(), {0}}, - {Configuration::MITC_READOUT_INTERVAL_VARNAME.c_str(), {0}}, - {Configuration::PRESS_READOUT_INTERVAL_VARNAME.c_str(), - {0}}}}}}}}}}}; - - o.connect(this->equipmentPath.c_str()); - } - - if (!isDefaultNameExists) { - midas::odb o(this->equipmentPath + "/" + Configuration::SETTINGS_DIR); - std::vector nameLists = - o[Configuration::NAMES_VARNAME.c_str()]; - nameLists.resize(info.num_channels); - for (int i = 0; i < info.num_channels; i++) { - std::string res = this->getDefaultName(i); - nameLists.at(i) = res; - printf("%s\n", res.c_str()); - } - o[Configuration::NAMES_VARNAME.c_str()] = nameLists; - } - - if (!isDefaultThresholdExists) { - midas::odb o(this->equipmentPath + "/" + Configuration::SETTINGS_DIR); - o[Configuration::UPDATE_THRESHOLD_MEASURED_VARNAME.c_str()].resize( - info.num_channels); - for (int i = 0; i < info.num_channels; i++) { - o[Configuration::UPDATE_THRESHOLD_MEASURED_VARNAME.c_str()][i] = - this->getDefaultThreshold(i); - } - } - - if (!isDemandExists) { - midas::odb o(this->equipmentPath + "/" + Configuration::VARIABLE_DIR); - std::vector demandLists = - o[Configuration::DEMAND_VARNAME.c_str()]; - demandLists.resize(info.num_channels); - o[Configuration::DEMAND_VARNAME.c_str()] = demandLists; - } - - if (!isMeasuredExists) { - midas::odb o(this->equipmentPath + "/" + Configuration::VARIABLE_DIR); - std::vector measuredLists = - o[Configuration::MEASURED_VARNAME.c_str()]; - measuredLists.resize(info.num_channels); - o[Configuration::MEASURED_VARNAME.c_str()] = measuredLists; - } - - /* - Caching all the value of /DD folder - */ - { - midas::odb o(this->equipmentPath.c_str()); - info.mitcpressc_settings.mitcequipment = - o[Configuration::MITC_EQUIPMENT_PATH.c_str()]; - info.mitcpressc_settings.setpressindex = - o[Configuration::MITC_OUTPUT_PRESS_SP_INDEX_PATH.c_str()]; - info.mitcpressc_settings.getvarioxspindex = - o[Configuration::MITC_INPUT_VARIOX_SP_INDEX_PATH.c_str()]; - info.mitcpressc_settings.getvarioxtempindex = - o[Configuration::MITC_INPUT_VARIOX_TEMP_INDEX_PATH.c_str()]; - info.mitcpressc_settings.getvarioxpowindex = - o[Configuration::MITC_INPUT_VARIOX_POW_INDEX_PATH.c_str()]; - info.mitcpressc_settings.recalcinterval = - o[Configuration::RECALC_INTERVAL_PATH.c_str()]; - info.mitcpressc_settings.mitcinterval = - o[Configuration::MITC_READOUT_INTERVAL_PATH.c_str()]; - info.mitcpressc_settings.pressinterval = - o[Configuration::PRESS_READOUT_INTERVAL_PATH.c_str()]; - } - - info.mitcPath = Configuration::SLASH + Configuration::EQUIPMENT_DIR + - Configuration::SLASH + - info.mitcpressc_settings.mitcequipment + - Configuration::SLASH + Configuration::VARIABLE_DIR; - - info.last_demand.resize(info.num_channels); - info.last_demand_set.resize(info.num_channels); - info.last_measured.resize(info.num_channels); - - // set invalid values to avoid calculation before init - for (int i = 0; i < info.num_channels; i++) { - info.last_measured.at(i) = -2.f; - } - - info.lastlog = 0; - info.last_press_mitc_output = 0; - info.recalculate = TRUE; - info.pending = TRUE; - info.press_mitc_output = -1.0f; - info.nvals_mitc_input = 0; - info.nvals_mitc_output = 0; - - info.ihis = 0; - - for (int i = 0; i < MAX_HIS; i++) { - info.history[i] = -1.f; // invalidate history - info.history_time[i] = 0; // no value archived yet - } - - // Check equipment Variables Input & Index - if (!(info.mitcpressc_settings.mitcequipment == "NONE") && - !info.mitcpressc_settings.mitcequipment.empty()) { - std::string mitc_input_path = std::string("/Equipment/") + - info.mitcpressc_settings.mitcequipment + - "/" + Configuration::VARIABLE_DIR; - midas::odb o(mitc_input_path.c_str()); - - // INPUTS - info.nvals_mitc_input = o[Configuration::INPUT_VARNAME.c_str()].size(); - - if (info.nvals_mitc_input > 0) { - info.mitc_input.resize(info.nvals_mitc_input); - cm_msg( - MLOG, "", - "Reading Mercury ITC Variables/Input values for Equipment %s", - info.mitcpressc_settings.mitcequipment.c_str()); - // check if index < nvals - if ((info.mitcpressc_settings.getvarioxspindex < 0) || - (info.mitcpressc_settings.getvarioxspindex >= - info.nvals_mitc_input)) { - status = CM_SET_ERROR; - cm_msg(MERROR, "mitc_pressc_init", - "ERROR array index %d to read temperature" - " setpoint is invalid", - info.mitcpressc_settings.getvarioxspindex); - } else { - cm_msg( - MLOG, "", - "Input Index to read Temperature SP of Equipment %s is %d", - info.mitcpressc_settings.mitcequipment.c_str(), - info.mitcpressc_settings.getvarioxspindex); - } - // check if index < nvals - if ((info.mitcpressc_settings.getvarioxtempindex < 0) || - (info.mitcpressc_settings.getvarioxtempindex >= - info.nvals_mitc_input)) { - status = CM_SET_ERROR; - cm_msg(MERROR, "mitc_pressc_init", - "ERROR array index %d to read temperature" - " is invalid", - info.mitcpressc_settings.getvarioxtempindex); - } else { - cm_msg(MLOG, "", - "Input Index to read Temperature of Equipment %s is %d", - info.mitcpressc_settings.mitcequipment.c_str(), - info.mitcpressc_settings.getvarioxtempindex); - } - // check if index < nvals - if ((info.mitcpressc_settings.getvarioxpowindex < 0) || - (info.mitcpressc_settings.getvarioxpowindex >= - info.nvals_mitc_input)) { - status = CM_SET_ERROR; - cm_msg(MERROR, "mitc_pressc_init", - "ERROR array index %d to read heater pow" - "er is invalid", - info.mitcpressc_settings.getvarioxpowindex); - } else { - cm_msg(MLOG, "", - "Input Index to read heater power of Equipment %s is %d", - info.mitcpressc_settings.mitcequipment.c_str(), - info.mitcpressc_settings.getvarioxpowindex); - } - } else { - cm_msg(MERROR, "mitc_pressc_init", - "ERROR Invalid number of channels %d for " - "Mercury ITC Variables/Input!", - info.nvals_mitc_input); - } - - // OUTPUTS - info.nvals_mitc_output = - o[Configuration::OUTPUT_VARNAME.c_str()].size(); - if (info.nvals_mitc_output > 0) { - // check if index < nvals - if ((info.mitcpressc_settings.setpressindex < 0) || - (info.mitcpressc_settings.setpressindex >= - info.nvals_mitc_output)) { - status = CM_SET_ERROR; - cm_msg(MERROR, "mitc_pressc_init", - "ERROR array index %d to read/set pres" - "sure setpoint is invalid", - info.mitcpressc_settings.setpressindex); - } else { - cm_msg(MLOG, "", - "Output Index to read/set Pressure SP of Equipment %s " - "is %d", - info.mitcpressc_settings.mitcequipment.c_str(), - info.mitcpressc_settings.setpressindex); - } - } else { - cm_msg(MERROR, "mitc_pressc_init", - "ERROR Invalid number of channels %d for " - "measured pressure!", - info.nvals_mitc_output); - status = DB_NO_KEY; - } - - } else { - cm_msg(MLOG, "", - "Equipment Name to Set/Get Mercury ITC Variables is not set-up " - "in MITCPRESSC/DD"); - } - - // FAILED PATH - - if (status != SUCCESS) { - cm_msg(MLOG, "", "mitc_pressc_init : ERROR initialising device"); - exit(EXIT_FAILURE); - } - printf("hey \n"); - - // SUCCESS PATH - // Init value - for (int i = 0; i < info.num_channels; i++) { - info.last_demand[i] = -1.f; - info.last_measured[i] = -2.f; - } - // NOTE: calculation will not be performed until info.initialized is TRUE - // this is done in mitc_pressc_set() when the last parameter is - // initialized - - /* - Hot link - */ - - midas::odb to_watch(this->equipmentPath + "/" + - Configuration::VARIABLE_DIR + "/" + - Configuration::DEMAND_VARNAME); - - to_watch.watch([&](midas::odb &arg) { - midas::odb o(this->equipmentPath + "/" + Configuration::VARIABLE_DIR); - int index = arg.get_last_index(); - float value = o[Configuration::DEMAND_VARNAME][arg.get_last_index()]; - this->mitc_pressc_set(index, value); - }); - - return FE_SUCCESS; -} - -INT itcPressureOptimizer::mitc_pressc_exit() { - return DB_SUCCESS; - // Nothing to do, since we do not manage memory with vector. All is drop - // when out of scope -} - -INT itcPressureOptimizer::mitc_pressc_update_mitc_input() { - if (info.last_mitc_input > ss_time()) // system time reset? - info.last_mitc_input = - ss_time() - info.mitcpressc_settings.mitcinterval + 1; - - // Time to read ? - // TODO : (info.hkey_mitc_input != 0) && (info.hkey_mitc_output != 0) - // condition is not checked, May need to add another check if necessary - - bool timeoutThreshold = - static_cast((ss_time() - info.last_mitc_input)) > - info.mitcpressc_settings.mitcinterval; - if (!timeoutThreshold) { - return FE_SUCCESS; - } - - float tpow; - { - std::string mitc_input_path = std::string("/Equipment/") + - info.mitcpressc_settings.mitcequipment + - "/" + Configuration::VARIABLE_DIR; - midas::odb o(mitc_input_path.c_str()); - - info.mitc_input = o[Configuration::INPUT_VARNAME.c_str()]; - } - - // TODO : Understand what this piece of code is used, and does the float - // condition correct ... "!=" is suspect - - float sum, avg; - int i, nvals; - - DWORD curtime; - - // measured MITCPRESSC_MercuryITC Loop0 Temperature Setpoint 1 - printf("update temps\n"); - if ((info.mitc_input[info.mitcpressc_settings.getvarioxspindex]) != -1) { - printf("last measured\n"); - printf("index %d value %f\n", info.mitcpressc_settings.getvarioxspindex, - info.mitc_input[info.mitcpressc_settings.getvarioxspindex]); - if (info.last_measured[1] != - info.mitc_input[info.mitcpressc_settings.getvarioxspindex]) { - - info.last_measured[1] = - info.mitc_input[info.mitcpressc_settings.getvarioxspindex]; - info.recalculate = TRUE; - } - } - - // measured MITCPRESSC_Mercury ITCTemperat(ure) 2 - if (info.mitcpressc_settings.getvarioxtempindex != -1) { - if (info.last_measured[2] != - (info.mitc_input[info.mitcpressc_settings.getvarioxtempindex])) { - info.last_measured[2] = - info.mitc_input[info.mitcpressc_settings.getvarioxtempindex]; - info.recalculate = TRUE; - } - } - - // measured MITCPRESSC_MercuryITC Loop0 Power 3 - // here in Watt - if (info.mitcpressc_settings.getvarioxpowindex != -1) { - tpow = info.mitc_input[info.mitcpressc_settings.getvarioxpowindex]; - } else { - tpow = -1.f; // invalid - should not occur - } - if (tpow < 0.f) { - tpow = -1.f; // invalid - should not occur - } else { - // archive current value - info.history[info.ihis] = tpow; - info.history_time[info.ihis] = ss_time(); - info.ihis += 1; - if (info.ihis >= MAX_HIS) - info.ihis = 0; // ring buffer - - // calculate average power - curtime = ss_time(); - } - - info.last_measured[3] = tpow; - - // sum valid power readings read in the last 30 secs - for (sum = 0.0, nvals = 0, i = 0; i < MAX_HIS; i++) { - // valid power? - if (info.history[i] >= 0.f) { - // measured in the last 30 secs - if (info.history_time[i] + 30 > curtime) { - sum += info.history[i]; - nvals++; - } - } - } - // calculate average - if (nvals > 0) - avg = sum / (float)nvals; - else - avg = -1.f; - - if (info.last_measured[4] != avg) { - // measured MITCPRESSC_AveragedPower 4 - info.last_measured[4] = avg; - info.recalculate = TRUE; - } - - // TODO : in case of error accessing the ODB, this should be executed - - // if (*(info.last_measured + 1) != -1.f) { - // cm_msg(MERROR, "mitc_pressc_update_mitc_input", - // "Error %d returned by " - // "db_get_data() reading Mercury ITC Measured from ODB!", - // status); - // *(info.last_measured + 1) = -1.f; - // *(info.last_measured + 2) = -1.f; - // *(info.last_measured + 3) = -1.f; - // *(info.last_measured + 4) = -1.f; - // } - - info.last_mitc_input = ss_time(); - - return FE_SUCCESS; -} - -INT itcPressureOptimizer::mitc_pressc_update_pressure_demanded() { - if (info.last_press_mitc_output > ss_time()) // time reset? - info.last_press_mitc_output = - ss_time() - info.mitcpressc_settings.pressinterval + 1; - - // time to read? - if (static_cast(ss_time() - info.last_press_mitc_output) > - info.mitcpressc_settings.pressinterval) { - - // read current demanded pressure from odb - - midas::odb o(info.mitcPath); - // TODO : Check if the variable is present to prevent crash - - info.press_mitc_output = o[Configuration::OUTPUT_VARNAME.c_str()] - [info.mitcpressc_settings.setpressindex]; - info.last_press_mitc_output = ss_time(); - } - return FE_SUCCESS; -} - -INT itcPressureOptimizer::mitc_pressc_recalculate() { - if (!info.initialised) { - printf("not initialised\n"); - return FE_SUCCESS; - } - - if (info.pending) { - info.recalculate = TRUE; - info.pending = FALSE; - } - - if (info.last_recalculated > ss_time()) - info.last_recalculated = - ss_time() - info.mitcpressc_settings.recalcinterval + 1; - - // recalculate flagged? or time to recalculate? - if (info.recalculate || - (static_cast(ss_time() - info.last_recalculated) > - info.mitcpressc_settings.recalcinterval)) { - - /* --- do pressure calculation --- */ - - if (info.last_measured[4] >= 0.f) { - // Calculate presure - printf("calculate pressure \n"); - - info.pending = FALSE; - - // Mercury ITC Temperature setpoint invalid ? - if (info.last_measured[1] < 0.f) - info.pending = TRUE; - // Mercury ITC Temperature measured invalid ? - if (info.last_measured[2] < .0f) - info.pending = TRUE; - - // COMMENT FROM ANDREAS - // NIY maybe check also validity of minpress, maxpress, c1, c2 - // in info.last_demand - - if (!info.pending) { - float press, c1contrib, c2contrib; - - // pressure calculation: - // p = pmin+c1*(Tmeas-Tset-avPower*c2) - // if (p < pmin) p = pmin - // if (p > pmax) p = pmax - - c2contrib = info.last_measured[4] * info.last_demand[11]; - c1contrib = - info.last_demand[10] * - (info.last_measured[2] - info.last_measured[1] - c2contrib); - press = info.last_demand[8] + c1contrib; - - info.last_measured[10] = c1contrib; - - if (c2contrib != 0.0f) - info.last_measured[11] = -c2contrib * info.last_demand[10]; - else - info.last_measured[11] = 0.0f; - - info.last_measured[8] = press; - info.last_measured[9] = press; - - // Pressure smaller than min? -> take min - if (press < info.last_demand[8]) - press = info.last_demand[8]; - - // Presure larger than max? -> take max - if (press > info.last_demand[9]) - press = info.last_demand[9]; - - // update measured of MITCPRESSC_CalcPressure 5 - info.last_measured[5] = press; - - // Pressure control mode is set to calculated? - if (info.last_demand[6] == 1.0f) { - float presst = std::roundf(press * 10.0f + 0.5f) / - 10.0f; // Round one decimal - - // only necessary to update when rounded value and demand - // value differ - // Check of hkey_demand is missing - if (fabsf(info.last_demand[0] - presst) >= 0.05f) { - midas::odb o(this->equipmentPath); - printf("%f is a new value\n", presst); - o[Configuration::DEMANDE_PATH.c_str()][0] = presst; - } - info.last_demand[0] = presst; - } - } - - /* NOT able to calculate pressure and calculated pressure expected - * as input */ - } else if (info.last_demand[6] == 1.0f) { - info.pending = TRUE; - info.last_measured[5] = -1.0f; - /* pressure calculation is not necessary as manual mode is set */ - } else if (info.last_demand[6] != 1.0f) { - info.pending = FALSE; - info.last_measured[5] = -1.0f; - } - - /* all conditions met to update setpoint of Mercury ITC in ODB? */ - if (!info.pending) { - info.last_recalculated = ss_time(); - - if (info.last_demand[7] == 1.0f) { - info.last_demand[0] = - std::roundf(info.last_demand[0] * 10.0f + 0.5f) / 10.0f; - info.last_measured[0] = info.last_demand[0]; - - /* is Mercury ITC already set? -> check readout*/ - if (info.press_mitc_output != info.last_demand[0]) { - // update Mercury ITC Demanded value - // NIY maybe check if info.hkey_mitc_output != 0 - - midas::odb o(info.mitcPath); - o[Configuration::OUTPUT_PATH.c_str()] - [info.mitcpressc_settings.setpressindex] = - info.last_demand[0]; - - info.last_mitc_input = ss_time(); - } - } - ss_sleep(100); - } - if (!info.recalculate) - ss_sleep(100); - - info.recalculate = FALSE; - } - return FE_SUCCESS; -} - -/* - _Pressure 0 D - _MercuryITCSetpoint 1 - _MercuryITCTemperat(ure) 2 - _MercuryITCPower 3 - _AveragedPower 4 - _CalcPressure 5 - _PressureControlMode 6 D - _SetRCPressure 7 D - _PressureCalc_minPressure 8 D - _PressureCalc_maxPressure 9 D - _PressureCalc_const1 10 D - _PressureCalc_const2 11 D - */ - -INT itcPressureOptimizer::mitc_pressc_set(INT channel, float value) { - switch (channel) { - case 0: // PRESSURE - only set pressure if not in calculated mode - if (info.last_demand[6] != 1.0f) { - if (info.last_demand[channel] != value) { - info.last_demand[channel] = value; - info.last_demand_set[channel] = ss_time(); - info.recalculate = TRUE; - } - } else { - if (std::abs(info.last_demand[channel] - value) > 0.2f) { - cm_msg(MLOG, "", - "PressureControlMode is set to calculated! " - "- Not setting pressure!"); - } - } - break; - case 6: // Pressure Control Mode : 1 = Calc(automatic), 0 = Man - if ((info.last_demand[channel] != 1.0f) && (value == 1.0f)) { - info.last_demand[channel] = 1.0f; - info.last_demand_set[channel] = ss_time(); - cm_msg(MLOG, "", "Pressure Control Mode is set to Calculated"); - - info.recalculate = TRUE; - } else if ((info.last_demand[channel] != 0.0f) && (value != 1.0f)) { - info.last_demand[channel] = 0.0f; - info.last_demand_set[channel] = ss_time(); - - cm_msg(MLOG, "", "Pressure Control Mode is set to Manual"); - - info.recalculate = TRUE; - } - break; - case 7: // set Pressure for Mercury ITC: 1 = Update Mercury ITC Pressure - // Setpoint - // 0 = do not - if ((info.last_demand[channel] != 1.0f) && (value == 1.0f)) { - info.last_demand[channel] = 1.0f; - info.last_demand_set[channel] = ss_time(); - - cm_msg(MLOG, "", - "MPC_SetPressure is enabled - Setting Mercury ITC " - "Pressure Setpoint in ODB"); - } else if ((info.last_demand[channel] != 0.0f) && (value != 1.0f)) { - info.last_demand[channel] = 0.0f; - info.last_demand_set[channel] = ss_time(); - cm_msg(MLOG, "", - "MPC_SetPressure is disabled - Not Setting Mercury ITC " - "Pressure Setpoint in ODB"); - - info.recalculate = TRUE; - } - break; - case 8: // min pressure - case 9: // max pressure - case 10: // pressure c1 - case 11: // pressure c2 - if (info.last_demand[channel] != value) { - - std::string name, name1; // TODO : change to std::string - - name = this->getDefaultName(channel); - if (info.initialised) - cm_msg(MLOG, "", "Channel %d (%s): output was %f now set to %f", - channel, name.c_str(), info.last_demand[channel], value); - else - cm_msg(MLOG, "", "%s (Channel %d): Output is set to %f", - name.c_str(), channel, value); - - if (channel == 9) { - if (info.last_demand[channel - 1] >= value) { - name1 = this->getDefaultName(channel); - - cm_msg(MERROR, "mitc_pressc_set", - "%s should be larger than %s (%f)", name.c_str(), - name1.c_str(), info.last_demand.at(channel - 1)); - } - } - info.last_demand[channel] = value; - info.last_demand_set[channel] = ss_time(); - info.recalculate = TRUE; - } - break; - } - - // all demand values set from ODB during init? - if ((channel == info.num_channels - 1) && !info.initialised) { - info.initialised = TRUE; // flag init done - info.recalculate = TRUE; // flag to recalculate - } - - mitc_pressc_recalculate(); - return FE_SUCCESS; -} - -float itcPressureOptimizer::getDemand(int channel) { - float pvalue; - if ((channel >= 0) && (channel < info.num_channels)) { - pvalue = info.last_demand.at(channel); - } else { - pvalue = -1.0f; - } - return pvalue; -} - -float itcPressureOptimizer::get(int channel) { - // update variox readout - mitc_pressc_update_mitc_input(); - - // update pressure demand readout - mitc_pressc_update_pressure_demanded(); - - // recalculate pressure and nv% - mitc_pressc_recalculate(); - - if ((channel == info.num_channels - 1) && !info.initialised) - ss_sleep(1000); - float pvalue; - if ((channel >= 0) && (channel < info.num_channels)) { - pvalue = info.last_measured[channel]; - - } else { - pvalue = -1.0f; - } - - return pvalue; -} - -INT itcPressureOptimizer::mitc_pressc_get(INT channel, float *pvalue) { - // update variox readout - mitc_pressc_update_mitc_input(); - - // update pressure demand readout - mitc_pressc_update_pressure_demanded(); - - // recalculate pressure and nv% - mitc_pressc_recalculate(); - - if ((channel == info.num_channels - 1) && !info.initialised) - ss_sleep(1000); - - if (pvalue) { - if ((channel >= 0) && (channel < info.num_channels)) { - if (*pvalue != info.last_measured[channel]) { - *pvalue = info.last_measured[channel]; - } - } else { - *pvalue = -1.0f; - } - } - return FE_SUCCESS; -} - -/* - Default threshold for measured values index threshold - ---------------------------------------------------------------------- - _Pressure 0 0.01 - _MercuryITCSetpoint 1 0.001 - _MercuryITCTemperat(ure) 2 0.0001 - _MercuryITCPower 3 0.1 - _AveragedPower 4 0.01 - _CalcPressure 5 0.01 - _PressureControlMode 6 - - _SetRCPressure 7 - - _PressureCalc_minPressure 8 0.01 - _PressureCalc_maxPressure 9 0.01 - _PressureCalc_const1 10 0.01 - _PressureCalc_const2 11 0.01 - */ - -float itcPressureOptimizer::getDefaultThreshold(int channel) { - switch (channel) { - case 0: - case 4: - case 5: - case 8: - case 9: - case 10: - case 11: - return 0.01f; - break; - case 3: - return 0.1f; - break; - case 1: - return 0.001f; - break; - case 2: - return 0.0001f; - break; - default: - return 1.0f; - } - return FE_SUCCESS; -} - -std::string itcPressureOptimizer::getDefaultName(int channel) { - - int maxSize = Configuration::defaultLabels.size() - 1; - if (channel > Configuration::defaultLabels.size() - 1 || channel < 0) { - channel = Configuration::defaultLabels.size() - 1; - } - - std::string res = Configuration::defaultLabels.at(channel); - printf("%s\n", res.c_str()); - return res; -} \ No newline at end of file diff --git a/src/device/itc_pressure_optimizer.h b/src/device/itc_pressure_optimizer.h deleted file mode 100644 index b7e6050..0000000 --- a/src/device/itc_pressure_optimizer.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef ITC_PRESSURE_OPTIMIZER_H -#define ITC_PRESSURE_OPTIMIZER_H - -#include "itc_pressure_optimizer_info.h" -#include "tmfe.h" -class itcPressureOptimizer : public TMFeEquipment { - public: - itcPressureOptimizer(std::string equipmentName, - const char *equipmentFilename, int channel); - - TMFeResult HandleInit(const std::vector &args); - - void HandlePeriodic(); - - private: - std::string equipmentPath; - itcPressureOptimizerInfo info; - /* - Initialize ODB record for settings and initialized its variables and - bus driver - */ - INT mitc_pressc_init(); - /* - Exit routine - */ - INT mitc_pressc_exit(); - - /* - read temperature setpoint, measured temperature and measured power - from Mercury ITC device in ODB and calculate average power of the last - 30 sec - */ - INT mitc_pressc_update_mitc_input(); - - /* - read demanded pressure (setpoint) from Mercury ITC (ODB Demand) - */ - INT mitc_pressc_update_pressure_demanded(); - /* - Calculate pressure - */ - INT mitc_pressc_recalculate(); - - /* - Set pressure to Mercury ITC - */ - INT mitc_pressc_set(INT channel, float value); - /* - ???? - */ - float getDemand(int channel); - - /* - - */ - INT mitc_pressc_get(INT channel, float *pvalue); - - std::string getDefaultName(int channel); - - float getDefaultThreshold(int channel); - - float get(int channel); -}; - -#endif \ No newline at end of file diff --git a/src/device/itc_pressure_optimizer_config.h b/src/device/itc_pressure_optimizer_config.h deleted file mode 100644 index 1721bb6..0000000 --- a/src/device/itc_pressure_optimizer_config.h +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef ITC_PRESSURE_OPTIMIZER_CONFIG_H -#define ITC_PRESSURE_OPTIMIZER_CONFIG_H - -#include - -namespace Configuration { -const std::string SLASH = "/"; -const std::string EQUIPMENT_DIR = "Equipment"; - -const std::string OUTPUT_VARNAME = "Output"; -const std::string INPUT_VARNAME = "Input"; - -const std::string SETTINGS_DIR = "Settings"; -const std::string VARIABLE_DIR = "Variables"; - -const std::string DEVICES_DIR = "Devices"; -const std::string MIT_DIR = "MITCPRESSC"; -const std::string DD_DIR = "DD"; - -const std::string MITC_EQUIPMENT_VARNAME = "MITC_Equipment"; -const std::string MITC_OUTPUT_PRESS_SP_INDEX_VARNAME = - "MITC_OutputPressSPIndex"; -const std::string MITC_INPUT_VARIOX_SP_INDEX_VARNAME = - "MITC_InputVarioxSPIndex"; -const std::string MITC_INPUT_VARIOX_TEMP_INDEX_VARNAME = - "MITC_InputVarioxTempIndex"; -const std::string MITC_INPUT_VARIOX_POW_INDEX_VARNAME = - "MITC_InputVarioxPowIndex"; -const std::string RECALC_INTERVAL_VARNAME = "RecalcInterval"; -const std::string MITC_READOUT_INTERVAL_VARNAME = "MitcReadoutInterval"; -const std::string PRESS_READOUT_INTERVAL_VARNAME = "PressReadoutInterval"; - -const std::string ENABLE_VARNAME = "Enable"; -const std::string NAMES_VARNAME = "Names"; -const std::string UPDATE_THRESHOLD_MEASURED_VARNAME = - "Update Threshold Measured"; - -const std::string DEMAND_VARNAME = "Demand"; -const std::string MEASURED_VARNAME = "Measured"; - -const std::string MITC_EQUIPMENT_PATH = SETTINGS_DIR + SLASH + DEVICES_DIR + - SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + MITC_EQUIPMENT_VARNAME; -const std::string MITC_OUTPUT_PRESS_SP_INDEX_PATH = - SETTINGS_DIR + SLASH + DEVICES_DIR + SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + MITC_OUTPUT_PRESS_SP_INDEX_VARNAME; -const std::string MITC_INPUT_VARIOX_SP_INDEX_PATH = - SETTINGS_DIR + SLASH + DEVICES_DIR + SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + MITC_INPUT_VARIOX_SP_INDEX_VARNAME; -const std::string MITC_INPUT_VARIOX_TEMP_INDEX_PATH = - SETTINGS_DIR + SLASH + DEVICES_DIR + SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + MITC_INPUT_VARIOX_TEMP_INDEX_VARNAME; -const std::string MITC_INPUT_VARIOX_POW_INDEX_PATH = - SETTINGS_DIR + SLASH + DEVICES_DIR + SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + MITC_INPUT_VARIOX_POW_INDEX_VARNAME; -const std::string RECALC_INTERVAL_PATH = SETTINGS_DIR + SLASH + DEVICES_DIR + - SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + RECALC_INTERVAL_VARNAME; -const std::string MITC_READOUT_INTERVAL_PATH = - SETTINGS_DIR + SLASH + DEVICES_DIR + SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + MITC_READOUT_INTERVAL_VARNAME; -const std::string PRESS_READOUT_INTERVAL_PATH = - SETTINGS_DIR + SLASH + DEVICES_DIR + SLASH + MIT_DIR + SLASH + DD_DIR + - SLASH + PRESS_READOUT_INTERVAL_VARNAME; - -const std::string OUTPUT_PATH = VARIABLE_DIR + SLASH + OUTPUT_VARNAME; -const std::string INPUT_PATH = VARIABLE_DIR + SLASH + INPUT_VARNAME; -const std::string DEMANDE_PATH = VARIABLE_DIR + SLASH + DEMAND_VARNAME; -const std::string MEASURED_PATH = VARIABLE_DIR + SLASH + MEASURED_VARNAME; - -const std::vector defaultLabels = {"Pressure Setpoint", - "MPC_ITC_VarioxSP", - "MPC_ITC_VarioxTemp", - "MPC_ITC_VarioxPower", - "MPC_AveragedPower", - "MPC_CalcPressure", - "Pressure Control Mode", - "MPC_SetPressure", - "MPC_PressCalcminPress", - "MPC_PressCalcmaxPress", - "MPC_PressCalc_const1", - "MPC_PressCalc_const2", - "NONE"}; -} // namespace Configuration -#endif \ No newline at end of file diff --git a/src/device/itc_pressure_optimizer_info.h b/src/device/itc_pressure_optimizer_info.h deleted file mode 100644 index 424f8fa..0000000 --- a/src/device/itc_pressure_optimizer_info.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef ITC_PRESSURE_OPTIMIZER_INFO_H -#define ITC_PRESSURE_OPTIMIZER_INFO_H - -#include "midas.h" -#include "tmfe.h" - -#include -#include - -#define MAX_HIS 100 - -class itcPressureOptimizerSettings { - public: - std::string mitcequipment; // equipment name of Mercury ITC - INT setpressindex; // index of demanded/output pressure - INT getvarioxspindex; // index of input setpoint (readout) - INT getvarioxtempindex; // index of input temperature (readout) - INT getvarioxpowindex; // index of input power (readout) - INT recalcinterval; // interval [sec] to recalculate - INT mitcinterval; // interval [sec] to update Mercury ITC readout - INT pressinterval; // interval [sec] to update demanded pressure -}; - -class itcPressureOptimizerInfo { - public: - itcPressureOptimizerSettings mitcpressc_settings; - INT num_channels; // number of channels of this fe - HNDLE hkey_demand; // ODB key to demand values of this equipment - std::vector - last_demand; // last demand values (used to calculate pressure) - std::vector last_demand_set; // last time a demand value was set - std::vector last_measured; // last "measured" values *** - INT nvals_mitc_input; // number of variables in Mercury ITC input - HNDLE hkey_mitc_input; // ODB key to Mercury ITC input - std::vector mitc_input; // readout of Mercury ITC equipment input - DWORD last_mitc_input; // last time Mercury ITC input readout - INT nvals_mitc_output; // number of variables in Mercury ITC output - HNDLE hkey_mitc_output; // ODB key to Mercury ITC output - float press_mitc_output; // readout of pressure setpoint from Mercury ITC - DWORD last_press_mitc_output; // last time readout of pressure setpoint - DWORD lastlog; - std::string name; // equipment name to access this odb variables - // directly - BOOL initialised; // to flag everything is initialised - BOOL recalculate; // to flag recalculate necessary after param change - BOOL pending; // recalculation is pending due to insufficient info - DWORD last_recalculated; // ss_time() when last recalculated - INT ihis; // position of next value in history array - float history[MAX_HIS]; // history[i] of Mercury ITC power output - DWORD history_time[MAX_HIS]; // time when Mercury ITC power was written to - // history[i] - - std::string mitcPath; -}; - -#endif \ No newline at end of file diff --git a/src/device/odb/OdbReader.tpp b/src/device/odb/OdbReader.tpp new file mode 100644 index 0000000..134ced1 --- /dev/null +++ b/src/device/odb/OdbReader.tpp @@ -0,0 +1,34 @@ +#include +#include + +template class OdbReader { + private: + std::string path; + + public: + OdbReader(std::string path); + std::optional readValue(); + std::optional readValueAtIndex(int index); +}; + +template inline OdbReader::OdbReader(std::string path) { + this->path = path; +} + +template inline std::optional OdbReader::readValue() { + midas::odb o(path); + if (o.exists()) { + return std::optional(o); + } + return std::optional(); +}; + +template +inline std::optional OdbReader::readValueAtIndex(int index) { + midas::odb o(path); + if (o.exists() && o.size() > index) { + return std::optional(o[index]); + } + + return std::optional(); +}; diff --git a/src/device/odb/OdbWriter.tpp b/src/device/odb/OdbWriter.tpp new file mode 100644 index 0000000..0b43e97 --- /dev/null +++ b/src/device/odb/OdbWriter.tpp @@ -0,0 +1,35 @@ +#include +#include + +template class OdbWriter { + private: + std::string path; + + public: + OdbWriter(std::string path); + bool writeValue(T &value); + bool writeValueAtIndex(T &value, int index); +}; + +template inline OdbWriter::OdbWriter(std::string path) { + this->path = path; +} + +template inline bool OdbWriter::writeValue(const T &value) { + midas::odb o(path); + if (o.exists()) { + o = value; + return true; + } + return false; +} + +template +inline bool OdbWriter::writeValueAtIndex(const T &value, int index) { + midas::odb o(path); + if (o.exists() && o.size() > index) { + o[index] = value; + return true; + } + return false; +} -- 2.54.0 From 87b90d51a989a49177a3819c10c820b5820cc748 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 16 Jul 2026 08:40:40 +0200 Subject: [PATCH 02/28] switching to eventbus --- doc/graph.puml | 14 +++--- main.cpp | 66 +++++++++++++++++++++++++ out/doc/graph/ITCPressureOptimizer.png | Bin 69567 -> 75865 bytes src/device/DemandHandler.cpp | 5 +- src/device/DemandHandler.h | 2 +- src/device/EventBus.cpp | 58 ++++++++++++++++++++++ src/device/SettingsHandler.cpp | 57 +++++++++++++++++++++ src/device/SettingsHandler.h | 18 +++++++ src/device/SettingsHandlerConfig.h | 19 +++++++ src/device/odb/OdbReader.tpp | 34 ------------- src/device/odb/OdbWriter.tpp | 35 ------------- 11 files changed, 228 insertions(+), 80 deletions(-) create mode 100644 main.cpp create mode 100644 src/device/EventBus.cpp create mode 100644 src/device/SettingsHandlerConfig.h delete mode 100644 src/device/odb/OdbReader.tpp delete mode 100644 src/device/odb/OdbWriter.tpp diff --git a/doc/graph.puml b/doc/graph.puml index 9056964..b9ca023 100644 --- a/doc/graph.puml +++ b/doc/graph.puml @@ -1,13 +1,5 @@ @startuml ITCPressureOptimizer -class OdbWrited{ - -} - -class OdbReader{ - -} - class InputHandler { equipmentName : string spindex : int @@ -72,6 +64,9 @@ class FeedbackHandler { class OutputHandler { bool isSetPressureEnable float pressure + int outputPressureSPIndex; + + updateOutputPressureSPIndex(index : int) enableSetPressure(enable : bool) setPressure(value : float) } @@ -94,5 +89,8 @@ PressureCalculator --> OutputHandler InputHandler --> FeedbackHandler DemandHandler --> FeedbackHandler +SettingsHandler --> OutputHandler + + @enduml \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..51e8b34 --- /dev/null +++ b/main.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include + +#include "src/device/EventBus.cpp" +class ProdA {}; +class ProdB {}; + +class ConsA { + public: + void surProdA(const ProdA &) { + std::cout << "ConsA : Reçu ProdA" << std::endl; + } +}; + +class ConsB { + public: + void surProdA(const ProdA &) { + std::cout << "ConsB : Reçu ProdA" << std::endl; + } + void surProdB(const ProdB &) { + std::cout << "ConsB : Reçu ProdB" << std::endl; + } +}; + +class ConsC { + public: + void surProdB(const ProdB &) { + std::cout << "ConsC : Reçu ProdB" << std::endl; + } +}; + +int main() { + EventBus bus; + + // Consumer + ConsA consA; + ConsB consB; + ConsC consC; + + // Setup abonnement + + // ConsA react to ProdA + bus.subscribe([&consA](const ProdA &e) { consA.surProdA(e); }); + + // ConsB react to ProdA and ProdB + bus.subscribe([&consB](const ProdA &e) { consB.surProdA(e); }); + bus.subscribe([&consB](const ProdB &e) { consB.surProdB(e); }); + + // ConsC react to ProdB + bus.subscribe([&consC](const ProdB &e) { consC.surProdB(e); }); + + // Test of publishement + std::cout << "--- Publishing of ProdA ---" << std::endl; + ProdA prodA; + bus.publish(prodA); + + std::cout << "\n--- Publishing of ProdB ---" << std::endl; + ProdB prodB; + bus.publish(prodB); + + return 0; +} \ No newline at end of file diff --git a/out/doc/graph/ITCPressureOptimizer.png b/out/doc/graph/ITCPressureOptimizer.png index 35a06e2b4f468229807e21f65104d4515416965e..f724bb06499076201e8e2f13cad9c3a029223d3e 100644 GIT binary patch literal 75865 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

    !Yz)4rj z9Fg`)m7cRZx?(+MOe6WX8XEf7C+c_HxbT@r_V9t2wp(|LV%IzPdL&+cy>WftJTXmm zkT9ngZE?Kt|1kF6@l^N!|G2hURwa@xBatLY6tW{TAtNdy8L7ysP-O3nsBA*XNP|db zB}XDE5gAQVHsAZ}9Ch`6f3DB(_xb0#bt&h(#`F1ntoyJwSOti<0%OiW0327D9sR=m zjPxXYLtPl0XCNc5a32d9P3V+&KD*!f&U+^eS*0rvSUEhhzLS}GaYTer(-TwPySF7E zlE@&~6T?^kM+U*i``;M^!Lj zPoKiL#@^0usE%bx+V1osK8>yEj@;?{$d{dS^~S+*w)1$LEiI~b))q_R58j8jA7&(z zuaQBhaT-xJKRMCZ`<9Yu%p+@)TxG7J(Qzr?D*PGf9h=P`uo1_i_c`riH!J(YBqI}( zVHo4?-@kjM3q+2gkwNs8cSF}6sS#wsAdLU0Oc2O^|BoQBxgAl@ccd^}wNvEQtyxo> zf5R?k71Q9oXj(5=zvJw#oS)lyDC8p`5F4Rlw!YJNLaFBxwVp7~hxC}dHv6eIAeA$< zuQrNhX?B3iHP@2$`v{mM@Y)|jxBH)w=}V?^OJQSM#v9A2o5wk-hZvr4O=OVZtaBlB{i z!o8bt^Z2{LobVV>BapN^`d5sz*Zp^bk!XwrLm=mWBpAKZ^4HdTkbB9_rhjrOcw#80 zj%m`Er4k23WMkM-;+{qS-194M{?&nG#Qilj!Yozwy`;lhyBDZp5h!!!^p!MTRm*EwEYS> z(J1RNiY6x-#E|-TbAX&^{Ql2hon|rtjRHcvxCvs7U@wwq>W@`BfguCU_vb{Q(qnb zs+y-b)96m0=ULUT%b}r9Tp;8{ZqQ_kJEGy_=Kc;4M$`b5()i6N=E0NG)2+Uv8<_7? zC{abF6BMayBD*a9W>N*boV`e z2yXw>)8hi(sxp8TmQ6c*i1s%|0DsU0829`jDJa235yb0e>JBcpaY(s z$A4cY;%DEt<1|5J9fPR-Pe2#xAOXZ?2w6=akzvtvB1kU>lY(g_zs%) z{^5_GKM$+Ce;UPheZ891W@!6~GHdbVzBB9;p*Vl7$q8VQ2q~2QWpR`#>=zYaFJp;p` z+lK=*GH#$^;`WhqKfPFuaT3}_j+1Q2Du*s5UnKb^ek{G2mzO34-GuUV`;P%Ujv+Di z%6TXPRgc+w2z?-492wAxsg4Trss(Ib`uY3QPZAR4mk(M} z1Or%}=$8@4qofJ|=m(RvyN&;dy!-ca{mv$mcS*I&>uep?ZQHhuJ#_iJDlt^zIBLhy zSl1rtC&#dFkWc&D_ct3Lsg4!-pFX`>u z4|pwe26Y5kH%q}$)Q5VB=CAd%m+)@`sOH$wqeW%dS*SGAmOeDU>%6C)e;X=4MiQX4 zZSCBYirPpGRf}njUBhmOipaR;$A)-)iw>ejrCc|UBj5`EnD5tb-<(7s6%x&Ja;@_~+PB+;@ux3Xe#oH4SPNcPg5x(Rm^v0;`nCpVO1=g#-zdv=b6#G6n8(}Sm ze7%j5;i{a+C{&TMozG@S-EU8EAAfrW2VlTS&#~2ehLG^y$w*$VW)E?5lLzj%Wsae; zHrVvAXgxMz!S($0+7uyY zU>Pq52PZgYx7nk{72ufzryDpzXdDUXiUl?{YD{gK)w(eTroE|E;K;YdOEVXNTt`|Z9jmdo`$nHmuSuVYB%|F2`YoD9ZNTld@FidN@6MteLHl_Ig1hZL}Wm(v3aFkdy+3CyHd1d&NQ4;~_Mzundeqr*pIjEb_m_oSSiUOiQB zNxnFNcQn82c#<@4dMMWy^{?zg8L^v&&qQRs^9hibWFszwCSG+>x+6XBN}AUjFMf15 zU*QX`Q;ZhvR~99f8D{jQ!=dEVy8S|q8iztBi>IbYrcoZ){KSzJOVU`wz+ca@ag1po zSW7{Qb>m7F(M#+XgQ&qhkCOJgFZ5*wNHo5K46n^j*=HH$y@l@^NmG+v5dtWPEK~Oa!)5AO6uHiC5g<_fXs%>%iQw0h zcAh!m<0EfsBSE_E>7`VNUnmV?Ul>wJTkRxI%2fBskjw(dm;udUqgLBH1|AGNsAedsi0?n`=Aa7eUo zeCO`8@!_$vyw0(K zA*|({iN}# zSZQe)6;sg)xz6_Y@kE|L!F244gK`~Ab<=l5u6)9@2B7XiJ}i;mCYu<(H_;~~e6EkO zUG7syzO6Wg6wr_3nlAmublxq#bA&D$&4H~-N=m}}3Xj`WDk-wB$AiA(Pnpq*rayRK zrry1Rc;tv7{L7CyRbH?lZa0l5Q)5*OFN7|#=tcgCd=X=YDt06OS?B%zUvd!_ zqYM`h1}7z@B|lNe69ypa-K(eaeg`&Ol=yaHDTE#xynh@sL~>0*n-Ll|hSVO7I;;tZ z839ChJkl&($;JN(RM_=tq9lRvc(S;3T?nzQ55tTa&`{KGLDd*zt*$*`V0?FJdBkPq zBS!?he_>J`GY8I{u$ce0A&1bYh=}DYR12Cm?NKa+qZ6@LbEGh$czOlDTZ+}62~q%BGeWX^X%qBcYt!-C()dv3dLgjeXJu2 z;O_{~!or^x2wB7jLzkmPMNcp4drWRc^(t;jWAI@>m7+Pta5xkbyBarb^yz%$eHIgM zsjD?wI%wCetOGE6vn}a_AXkEhaskM&u&Yi?Ok@&uWDora`P3zax}!u}fZLgUq38+O z`(KBw;FgJ0?_aN@1T>c)VN7AhUIjg7y_+42b}1*&)$`|wOb-2IOG%QEsp)+(N~#@d z5znsf+>Tf)hT`|f$OyJ%i>(womcv6sOsNN0UaY-9IYcE7Ou%jL?VC5y`jk21n#dDh z=)}`|X@$^DDY6)O5;LVm*}GLI280Wk?%g~6rL*WrlTf~14lf^Hz_Hw4jS5wVqWBJ! zD7Hp%48TePyBi5e10OAYsgZB=Pk9eETdb}Fb6yh@9D#b53g_J=86a$Y22o? zIaS<|RunWA>tpQz+`(M3CKUv)LViPxQZLb~mjh(5A+UJa^5uA0MP9CVQJ%~;vmX}& z2q*epxbN=Y70|lh6;P#*H=xwmbWK@W!5b+^m=@hsk}F9N^~{%^o;}MXS#D@a?)dxr zOG=LHgOEt(wq<+L4`|Q@fV{kL(hjE0aXXt-=a}#;CuWPcK|$-+PjvM!Mrg_Yr{s|k zu;csZ0Ii#YldtZos5d&l5^)irkkwMW`7F3H%|^&&LtiZJdtGgBq4>+mS=`mpE z} z4T7J%C7G-s^gz7v^F21`J>crlY7Ewee9G)gM1j&fclesFbBzx?zZz|}gtST}z*c9X zdfTCevlc~y+bx}i!*;<0MjIJ9;RA!&Cd7A3cOomgeXLkCURpcB^%)WvY#XvF#I^Yf zU^J&vW22?DGtjvL+G9?{ZziejLvt?ehA+J9gWhqNJ* z^#)f6ZA;4c0Ec@dmBUQz?8EQh?^ck*wngu`gO_=66g@>t31%_SQW9iy|Aojg6Ph53ZcO18Mn#rl!#x zU*6t7K)eYV!&SjpRYpqako#WwnrdUl5;se$qfxz_k zwy2{P_u14pc6qHs{%+3Bw`?N!==anJ^)A)9L4v!^jkGjS;?tnG?RUoX(~303;*DNC zXw4x(lsKKS&nrPsF6%0D)ZWkfwAQR6ej8zPMSycUiyNAob@GkwsM{o48U+6G7dN8- ztZE~Q)p^Al)H#9zl8c2ybkpVBNw9N0`t(YbLQq?Z1^d4(cwKBzsx8_p(9NHt}*CE2o~RR4J|FLBS$ib3Wmf?Hwk^J@h@E)=PaQv zMbu>V4O)v`!nKBX91O$lZzcJxr? ztjBP%5e<#IAm3obc4tw!(a-_0wfpxM*!1gi?66R!eik^7cTS;7J7#A%LJdmMpPeRo z@Z41E?I*1f0?yTo^oRoec%<{I%iSW+xBKy5yJptVm?rHiviJ8j5t+n(O;Z>x2M0GH z8t6M00YWzjUhW3ux(gD`i!;JY^EM(VjAm$Jr)mh0$p^w%uAzHTIyj_64uk$Eh^ zbUioM9*_m>1~+OQb#*Iq-nE>!bmCQUBa_=ttvepu$Je-ob|t@kT2)gTc}Za-y(ru$ zahIE|%@}CHg@YQ`9)%|x?JUTPiyzH+W|_xt$id*7ZQ-hLLz+D~@Emz@a69Vk?PT5X z+*h!6v zb!6>oxit()EGr|@j9l}h3>>9quKSRm%;}^4NByla2ISstaTTv9qVznSf5X*>p6m=N zkmp$ks0!7*RKvc@dlXP8sQdG~`(s4xmgSMY-9EpxYEbidMU;M1B( z*8JqeL|MpK+2=d^t{Pfd68i3W()Mspr-`e*tlHiAWMS5$jnnm9rY`4`bD2WyZywsP ztZ|R63L5kgrSqQYnD4JmtySurt>Jcr+-mczRvNqKOFletR{Z^8!if=> z3dcSWxU{uX*rx|G+U4^NJG}eC^Y8A~ZlhMgvo<+)y|4pTW~9U=C)yn_&@*t*ZgA|{d8Y}$i0m3%{Caa*=PGkCdj1fZFY3M zs%L(;n)n8*O2!b}S>JAIB|I=|%}yDs%R)Q}j{8Al*d*)H>yOGL;QGa>wFyB#g!@T? z?ZW*5qM!MEkFWkkSC!4}NV{G2z=3uY!FC@ocG6CiQfD5uW?XmIS4jN)>2_F@!1xda z^H-o@vN*4ZDN-xRtih0WF;Yy>-!f5)N$l+%WP6!|!ahg=@Sr`vtdSB94GFKDye{!_ zf8hl-wdIu``{XmsI%Tfv8UKCNnKmcg&YW4Xe0jyMP6u#zu?eTqtoSiH3aOQ2!z7hM z*TUd>)2b5&YZ3&;JA0|EsOv5ipWk!N=R?@ji$AyB{hIA187q96wkliYAu$YrLqH7w zDP(#?vfSK!ESO%UrFE5q>Q0)0Ek+MVt$z4(EYH?so`R&+P2<}aSW{a?W^;E~EoO*& zQ<)uF0}&$e)5mPY71>s*Qv>u%2C4WJpO1WUOA~sPT(h2l`M@waJ}&O!rAu^^Gacu9 zVG>BwfoKwiW6;vL%LkAXbJiZ}TUstzk4b8FqiTvIf7WKv78e-psXNM|l zX@yJ_Q29%c4|}r61w+YiCq#=%d01wzKOexgb3Zcd>W{iRdKjIlz+JIB=+C8~aI?A+xZlvQ1WizY8D#yl($gixb=h`C_!NQe9C$5$Es z6hF?R8XK~l&I{mfIPPpri2ZXnUEE%~LKid6L&3F%RGUp!a#KuX*{j3+D(si>bLFMl z9dZ@LI7xAqSS|jk`BndAEkw8g{`&Rnw7R?^TQv?qD$=jw^nK-~w>>81f&C3Qv?boy1Z? zZm=5y5Ni3LnJyP{RJ;LHJ0?tF>p^2(-#Bkt<9nf);dXH(7jy)m#s8v^umMJMqf~A4 zzi7v{t_tjsqCgJJ9~&sFZ6e&7x~8eCXOn-4d*nV zlgy}!%f<;8Gx7EQw`_l?kmycE9^sA-|^0|P&zsUQfn)uljKYf{dH;z z)9gNl9v^y`Ao|wT)zR(1r&dlrbMk-fI0lA73eJKdh$%dPu&0JStG^Bv@g~VZmuM~q zsV7a$S&Y1N(@t&W+!rbA?H}#|Cn77$4$*?oMHBj}# zf%S$g4fo%d#xX<^#IYJFU9@P2oj#krng^B07Fz#Ww`i4mt@OvF=Z%XoH>a(T8h?Xk+{(Wd7QLwj>Q z6Z{VgpvdcdjN?=s2{rRk&Di$937v@2tfg%waFKK8pyFek`dE4PrwGy1yg|OK^`lDp zM&G*1bY`kTLg+`O(J+8#nM?gDoaSfyt+_{*gf|4p@S=e|bO)%o>wiWa*KVm03wM{H z6GUdl2UACss2&BLuVC|?=vguAYpm8KM2v4=7abAgccd+>3f)HR+vRv5rMlXfNoi?8 zq6OU@3dElLy8bkKz?%Bqb4G4c^sCj)~!y+j7|Ew8Hl7?a!X= z%XVONfTig5sGQaKB=QvA?o;#a)K~P>)GwM-w;o%$F=n1ojl;7d4&@-ZU7wVrTnF`J zH!>G|-EC54i!CPH3g6iUQXjhZUykAy{O6OhiowXc3LZ$Zx|8rpW9_p;Nr#axpnayD z)pge;OiH(h{*?(u~3o?3R z_p^Rt-(r1}Z9)D9LF)Al9fjGK_%aChbl*pOruc$Dbu}#UYHDh8^MDg~KrAs(Ja$a~ z20B=;Umx`P7({y6j}c$RX$wlviTN8x?_qQwpmJurJ=&kns3NJw2dx97z{qFL#X#!S z3CrQ7S`yB`iQ8)MR$&D(a&^P5+RHby(X}^tk7+yn1WoRr&ab+RTtU@=B$}^YQDj+l zAw8YH^dshn2!4Py_$T8yShw}4+J>Xe%*991R4VDCgu~C&V&<3nKBIS8{2awf-swT^T4Ck_8Sm@amCT~Pr^s^pjH+y z7o}xjc<441Wqc09o+l23!mCy#{1<)t>Y4l&?*SZ?)+o$bP~Dfa=e5z&gOPn=JCdv8 z?$n?HtLLK(37XezdD1t20;S8CQKd?`*W7YFgofeDJSqZJpmsxMM?xu#{gOxT>&I-ljj-}&td_MH`@uv&(9ITOt7fdMd z0!aURm}AlNueywadsB)fIb+VOBpx~tDTl_FYEW;NTNfie^5c2`E30wMbzXV5`aj=G z!Xyb`Fh#}1TX`D!qxDHUr&z@f{b{642b#(IE)l*-SSM`*NF?bM>(WSbVKLSl>C>)a zJqB;XJzI=Bp*(k5Y~+N>4)4xq{+}uro~NQ+wAqCi(56o}swZh@@y&B{48qeKFu9$5 z(JFy$HI~s8Gowu?I!|=m;sJb;&qif{LG4wfr$cdnGDlzRcvyCBZs$YKGoa}+SZip| zDx_APXEn+-%0qSix@%%$qOZ?}XC*a*)BHX06Eu~Tn5P_HWpFnMqr+|N3^<>V>k5iH zQWQ}?J7P{gFlvkTL`zc|npM9?3!Azjor?d3WD|E$m&x6O`_;%1vEy=8Dn#VEu-H_WokiUUEV!sE=VmO5RkYAvbsA%xBGR7+q zs6Q2FelV=%>q&Gr&z@spvTbMncJy=Bh3dC}h6gh0JfO7h9~od8QG}kl+o@BAIR+Rg zx$T!?`(L6>IJN-_e!Ldh8elRHcu{34LOjQKCFjMV7hvKMG_f^nP~*!zKy5MVqsEbS zrf>V-qmE7Q7gpNeU9regPQV7z-VPL#H%6$MrY7OY43884nf-T@!2e%eb!rj1NS0TS z;b3N#7*WjNX<#TJ^><5^%n% zBYWbde=+LeG|e}#SzNW6uIjg?GpQzrvTsum@Bu_ArG8jw z`CyTPBq&nshV)&s4Z+BLG(P{w+$FWSTaQ*WG%O68ICBAE zHu01tBc};NI~?u&ss;7|nF8!1uNsgT8+93c^bW`Lf=_Y^~YF({JVH}+=mP9wrR+? z%uF`>f)e-iT~XFa_AiSqh#^e2fXe+L48={;ar8cXc(rr=h7HdVqJ;xubK8b738u3f zE}XcY41hYTcJE89)ZM4l@4ZHkfN{{~;!1x>V%&8EPA~5?Pr>U|Tp`7diJ8MqK zUX2iJkK82bPG12eJf{VN5i2|USrC?Qk3*m9a{COp(j6X=Xq9vAa8>@QCSDM*=@N;b zfkIdQ#)JlkplB5O8~Po~;_iZ;M)P}g!}~FD<^(}2csJZG6iMKFXn zjs$-D2@5b0$ZN*hy}*jW9QaepZkitXjcFxg@*XuZt0&MBW&g}{-=guoX}}@N_P00l z^O3*+)P03US1=(t>I5mvmeAKl%MXy*4%NQVNmC804202}Ily2PXL1LQY1mz76nGo& zK$A7>^Cq~KMDUu!gT}Wv4zmMQZ88`e^lhyRXhkAE#hH8;g_6YXRf)P3fI2VC9XUVR7f`2FtEGHJ$}UQRr??Oi zC4h)1Nq>5ywG!L5wc!+^93Iaa8xQ{wTM7`i#?_1ian{At$;A46ue`Lgu4bBJ`q?#S zU${o{*06|ECf4cCKqFsS@E@^n)Bh#*6_I1#n~a6n=RFkockH`q7BuE?{ju~mL$Aol zrBCYWRGCMWf{GhXUFRaz4hc@~Bg5GI00p+O;ir?~rEIW_mTQMWOR0T@SwVWJP(|YL zh3gM2rYRF4lwVCuG!%V=-gFn3KfL_@9kwaYWynQq$ptS(xXOr29x09defNgN)D1 zd$=adixLFqPQF=DUmZZV$YhF zMfAUR?><*mcNol39AzQKcCEHjzp+>5<*orNK}#0Ga#vtnDd5JB9zAL|_sqX{ZwiP9 zNM3nEvkMiG%K73UH`yYc*>JC4uaCheVqL?43=WQ;shV4a@(&?Aqoku8J&;R_l%BJ$ zKj?fYROKY(yGHDR`OVTo;hb+Iy0X9%T)-nuahPo8804angW0or3(Koz{@c`_z~~u` z@9bo-guv^jo`mBOP+0xz7s{q=d-Y_)o^n&7jLQX)V4;k&(#5zp76+Rwq;O4mz;d+A zqnA$6cTJsovhD|XAD=%T*N~EyhHP$dvz%^XCzNbz?2=mc<)j;WwK9+En%#U;y1#>+Uq_41hm@A{Dnj{_TgWvF_jWZHD+|-yO4+vrG9ZqVH>w)D$qO0-h-WC4AqOgRx#tw>B zYHVy9h{o;S+8_EgjNXYqzoH+XPz+9hKF@{2B+(3@uCo>{C8irOhH^0lfMF5A8;Ztv ziX#(+BBf=e(ep>Kq{Q2`P%P=wl2~ucsTb(FZVn#~UhJ@XKC=LD1Jnkpd-v+nd@di2 z3Ku#qry46W+iS4_Z|aB>U>3Ne02yu$>Eru5q>1;DyC+h*R4gXk1~dbeG$_(iMh(iH z6jIIWF5m;6_qSk7nij7L92R6d{Ko(~I#>Y+K47V@RT0Fo0{VF4Tm*T+Y$LQU%t|Ik zv!jB&DPmuid4257!UsJnCs>#yw{CrH>iWvAHBqJumF>H5WGX|9a7{EH zS(HM1`_?VX9d1`Pp1M_52G>BC)`~*RrF&ou04`4t55S+~emvb8_5L0(9-tNgojt{) z&}ZOxQ$R}UB?ui)o(MqYw4mTQFK?`l(b_aitRTJlTawt0(RQ`G9&cfZcaP2NNjBbgMy%Kyv=m%^|4g3gGDy{sUI** z@`Y*bHO(aFqA!p@r$ooyogr!*^Eru|?iQ$|?Qnj7&b@?yYiEK3W|X&jMLVDz1fB~C zt$~OkBKQuCLaqG=CD{NIMcpTI47w~Z2UhfPb(O@B2_A;`E?rt-n9pT4{;9AsW#^gA zhP$k){lQpAWltRpoy#08JQ5(#T46hYL=BL^#JFI0rQ{`$!cJBNe zSeCgBFiIK6t@ngimtM9u>6O169NV>~@ywFWLb4zuC7Ls+b;AGZ$tq#lzlNp!9#FYR zXNL&LJ2W(~Xc&y#K&dy0Hn-a+nY)Z`4ZncEZDyHOAqtRfNJ=6`SJ2XasAn?6Mo@$s z(~Zy_#hML!eGUV#Q`*iuS8sW0cd>{V^GlN;E?2~@5d_jY4lTiZeO zJUhBzM1<})k0ay@PYay!Vt~NLaLvk%K9*ka0yO1ME@392jhQ7f<>shMaxagH9oIx! z6*9qO2z1u&F2q7gy3d9)GT{xkUW5{+`Da(%s4gIuxqq33`|wtTp$I@ z2754dDR?zMv+*6F@uE07*|@yEgRMZBikS7DL*dV4)-t-mnnho7^^lXZP$^s(91W1{Ay7dgj12^D6WrS;E)zN z&T&ki-LcJ5odb(XwFsCruMEi2QpV+0b|=Yl*EUW~O-=YNNS}t9Zzw4%L+^)7SUEC@ zQwwnUV_Y<}v>KY4(ZR;G34x3hkeN$cZ&RAa=3f+Jbie#42e~Zd7`zZcuw4cJ(fB^q z1wKsQ&41`HR$VelQQf&dnjFW774vuffib%BKLit`R970jX=GDiE3)it5i*psvgj1ycp00)7jYeBg z-Wna{2z`dD+6VUUfAQvxRw^1GKoxKA5Gk)ciIkuanu8C&SJU-wjX9iP`kNT@Pi#px zVsPfYfK2Z$2P5p`fKU52q|Q)UQYIi+ly`ghen(~&b$L&=du=xk&o(} ze^X+fjPH0znM$G?*(~=Xg#Ydd$nc<(j*82te$|dPZU-6$2Kcc;{GGz9+4}oV#M!OX zpVA3BzqGLL0E^vt5<-tsrM(TbkN?$0hX+O^5h-uEKdk6lul$ZfZiHh&2iRX6atGq>qV%@UCXrdK)*xr(ONXz zNPcd@y5`C<^6fqAD|a73O;8cobS&)(#w+pWXK5C`Dv5^gsRFmtW5EqiAT@8C*M{cz z;osk62(PPlxWs?yQ@9>$;qZz*~O}I;A z3ybjIKU|6*-hPT|2krDN)1E3{S0 zY9*K`d=DaF9f(ef&_^z+0CZ*xOiF06ZqWkQ-3xpiweQC%yo{yUpOkVf;`tv*g7Ad9TI4Z;fa1k5#+p zvv1z?8p?<#RxX*Nw+xSFitI~hLckv{E~eaY79G~kBCN+;*!ujwRh_j@6{wc*^YKA~ zkdti%IiF^RYb74Mib^x0y z(QKRk+E~Ow@mqy&+(Ve%U@T4f@GJaLb}N|eIO5c4dM~9O#xE(uDsgE0!n+kzM`CIm zRYNiYTroO&>)1YkRBe7?AzM1rKxXvYck-$iTzEk{L$=uQS*OihU!|h4O&gMOYS#Ef zx6%`D&%&37a>-gzsKZZzR&}9_hNM#aacF4rlg0emL5}FuR2@g|SdcNF;BE%JV6=;k z_*jNm$dCAQZrLe#O((Z1v?NO$~>^S_+tr8kMu(9xwyKY%6@+V*P(ATV!H>BDJCOP>rO zWhj79_MK%E>zXz8B_^RNA9{MW9DBYVaxwEW&GYNy;v#PEOO$GWIMss($tEQ@hfyty zix+~ak<7WsPZ9kz9AlJ~AxJa%0(rFHtT^WWk4Yg&Oh>m~XNciGl~#i`8OBDpLmyeX zfO!E0iiO7%t1v&=e1RVa5rby>DqcV%(84TFGs&MDZ4$q2_vrm7j*euHK23`&y?1-acm(_g^Vv0|b3hq}nO%J~fzdMX|> z6Ss}>p8PyR{v~X}n6Nbusm}F$KoU>z?sRlkRb+(_y8}3XJQB-I8XU8Ybv!KRtOn4> z#^iV+MN;GR>0dbMDK|r9*)f4MY-HDwn7_9O5ro|!i_z8DNx6~1hsHibt9F)&nX&gM zjIX_un!0P?$quF_-nohAsg3ix=c^V0+jb1oDKwPo$ji?*Mz8VrDTbAkvE!fD1;_1;iJf1)WEmjB)ay7rILNP`BW-MK?D*^l zuA>SH(>iuL=_+?ReisM4@msg%_rx9E2Q+w9=mG2tMz{g9jQswkRYJeS8WwIco@z=n ztm_X6o#njuz5mVR`H{-E=UfmadU~I%wYXVWxNw1CVondsC1iOkKc?$iv&@Cfrk#5r zHsWVP&xzJ69fakYpFgjq+)FSr&s_=w9-|UL369f0QoR<>*5=iAb-X@1sqMRyPuI2L znZw7irr7xF>{BzJk10g+BT6|#Vb}=@dZFZiR{JWT#p9mGw+(Ojwv=X`#_l(Oy90S6K`U@j$L4y&@lo z9HPlPP9F7{@CpijI6b+cid4HJq^n$9_*)l?uU5C6gHxV@cohO|-nIjih7glFgRM-! z+xP7wgk1(tAtVx{467utSMwbO zz9+kKyJzg%JyniIeeJmI7q&S{tC6r+NPbPdBkG5j;&UjQ$UD5+gf0W6I~;b(g|^4` z71CS;_m6#j>14JR_U~(2$OHWZN<`QfmfvvNv$eCc;(|(~LP;HX<#}xzWZmko5iZA* z6BF2s*Q43o>k<#vz9V_XOC)A11{R4C5{bNWV^SYh2Q?Z-A!UygQ> zjaK06dF$tCe%ZXS@G#h`zLQNB{0Yd<`^>$%CEcT*zI{835Dgr|7)tp*D4+jIV+6@jcjf|nvAO&@5m%On&dAMW6Y84u| zf(d})6AhaVS>Or)@2g3W7E(*S3z z1c2j9AY;`9`=Q18^Kn@P+|9`v2U)E@(-{2*%Lva^G+jRHuUQ&^Z4dF&H>Y^plIZ|O&LMvTT|qog?27cfQ`%DdtUU6r1ugYtJS>LFR~ z?C1h67N)aRyIN2Gfy7uX1qz{q79%Bz2_}2Lbi*k_bdHSWs>pyyExl2!B+KJiKa9{} zg-i%#CkkGHk^P8hz-)!w*=zis+V7LA9?EH)kaD*Bu(mo<`l@z(M8tj+j%2FVlLWm- zuIo8DLp3-O9F&wEw7sU@dV2 z_+p{^O0cs&ZlTo217;f-R<8W2%Cr7t(LdF*A+Mvx=J4gqmf`giw0yrNAf3VYBcFs_ zS1eH!tLJ>vF!+4+^Y~J`I~>Q#zBK#Fn*1!Oir^Y6`^$66Fy$Bw>G%)6*!M!#0RnrMhZ+_EeFw zGp3~1l*=~SIv<+y^a=9?38uvaFVWn0_7nn=re+fXCJ){Ij&Vi1K={GmZB=lmCZ;|W zCe&eWVUTZRYHU1sY3AAtFy%iBvG5HI3??Y_DNlkv6;zCQ^141ppz}aObmdW~A5eSw zcMVLqa-P3~pox6>;zRGTL8c33L@Ag?*bA>Ucjw{je)Q16bpAuQ{7LV+9U*samyC>F zt+2L;vI$Vg_+rL(+g8Ywt`g038mOJVD>_2wIgNz6ch4w7ZC7?b6vVB1td^B@3wfD5 z_c16WW25(8KdX`2R-$`Cf4VLY(R?o`=|x)*i9yNRTEePf3=9S8}Q zL!#ydHN>7TILvU!p%XH*v*RzL(;h+e~c{amYQ; z`Z(A6&S}L*$qF+)t0aibTgCRKTFV+S6Svu^pJ2BEegzE>bg1;%{gx0H9y{G-?%oq{ zq(xUa)yKW>&4G;tS9x`t^5i2Axw_Y_xL>_C;F7<8vg|2Za=3hsW+`5Bd5(To#Yinj zx&wRYQ5XeX37}Nb_+XewN;`J4vaw|wvz}SZ zjjsKycoYNYO+mWh4aew#TQ#FSS~Pg%q~AyR`QU+pp^r~Cg-;Hqg`=ZCSxV+`D4Q>>d3n!a$I~Gi^J^K12@hwZhS@isfFjb4Ba>V@J1r zLYF_2-B6^BLZNFRk$5*PP{1)Vu+>HFkD9l|QKUevU<+2G(1W@A+t;Y5_O&NV*&na& zzU|98sHkLXbIX(b4jVK8!=1(k1iO6j6sjHa0GJcwE&sINGzZM5muY*N$E=uNfTD&$BB{!%8HW_10dlcdaB zdKJYUo}R7Tt;-3cEQE^V$8X*W)PPU>b<&CMiia5V;pkw0n|YI0+Zr45J*&5V-}D78 zk1LAS)&pHfpQQv$cah(#O-AVCn#iOiGF23goez%$tG&c*F=TVEW*4?nL&n&FYyUwS zEy8^M&R&28<2N5-&bxxZYSBpmEe;;s0WpY)3HMUqX*ARmT*YFV1~ikZHFk^_>%utT zf_E+j>s+}GtVW(w()acqyq#Q&gYJ=d$ns??9(p>5SH%5WLR?hMr8mdMu{Q744fMw` zz470MLtf%y`aZ8iMI|L|4Grpq4) zs-M>eY$)TXo%e4>x~^_aT(tCpd`E8ZKILm*q?Zb2X_c>Oq!9CGwi4nQ62^1?Ory<% zHKV&SKi?r=Kb7!i;gq#&ZaN*;b+C9f?{Y&y+^V!Nyjq6)&avj__4VIJMyfUtJT#Kv zwygreAE(pX) z2gLre8>VV%8FEXHLZcg_D-_wiXOZUkF2xJxo=I)rKKtvc7&C|G_>Wx|GD1(Xm7TQE zdsnt`?n*`Lt9M4-UwVDZM$*FNB~6;T=YDPPS*nm@3T29&tHe^aYTI>AVpesw{7lT{ zZ@XgegJIGatwX5HBNb?~AKGc3{+5Tc3e6nP0@&06;6*RI2xAtPAx{_*hihW5(;Reyt!jldi zf6;;iCz6Uzv4$aL{q$SAcmNe$Ot!JJav-;3Ubk7DWZ7KZX>U8vZ2sV#@tY2k96pEXjXcb9ExQbi2-ZRKyWUQ=UkCV5fO{stQ zG1LYETqj=|$Uc&ilEN^Zbmk#q9RzelzN8jUPEBQ|r@wINg#{;o#B19{%l5sNrL3tI zciXWn?z{3k_e7M2v?k09QS*DLjMpwm;#7ZGF~;!?GuX8C8UuZO{s95E+u$@nIP#iX zlV9R$z6wJ(fN@Ym9}6Dja9VhQT5240s}`8e=817}AA5QLiOb;{RXTyQsebh!-SfZZ z*V^?8*wZj7(CQ9C(vl*yty#4sexA z_L~UB>yYmJEo(9-a=={J7j%D%Kht*gCPGDcF7S=?>UhC+HY5n#=%tYkY!P)3wW@nQ z>sisVBfY|>Vn$@p*B6bb&tkKunV-+L7RpbpTK$o}MKojC?GN3$A{FBXUyXF9wnx4> zB|-Yi2zVfp0&>D(_9p8tSEXKulikk!QM2gAkLDQ{*#z~eJ}1lQP=;wSIAuF;S@}ie z%!KKco9-1LsSHJ)z9fVoPR>JKZJ!yKxlohW9j`j>HDZOjD{BV&%&{76@7`^$2vgrt zoqisHngXqO&F|KKCZ_SNh)B7d#(#Z`QC;Edsg~2AuRAb@Xem!T`?dWY1j@B1fucaY z8evoWl>C-ShbEd4Y^4mM>9A=JSGtMJYcTeScY7 zQv+RN^F%hT{#3Jas;%&1|DyeAOK{t1W}W=k%>>Hc5HP-j2h|S`uzN|QR%PGnH8H+< zU;&=SJEA{-wx6r#z`vrSeZ%foYb-*`=S7!MzRxz>x!|esvY`3XTyK5^pR;&?CeB1V zIa&K~)sCf!L6Zv^LUQKs4B=Udp1`=J{9G$Ffe6qsL>qzMl;3BVYk&!gcRnxY5FPCG zkd(Q*%P5fLT9zR-afZQLb#u2@1Mc3uW}d_P>X>f;B^q`t|1plHRI6@G^Qpss{)3|)9;BzJss zJVMW6Y))261<<>_wvthz&bu)NWxt2}*`>>u&HXx4na1TRQ$^NgedTKhGc+g-9GQ&C zT+bth`>7~aPkD_~s%A}4>QtSFzXj+m3#68~iJ&d6=vn9+3zPfiOQ^2E&h%N7r-Vgi z#=ZxF^GrVAJ7OLTuc#XzGt$$){}2#RaR0u$AcwE7{5&!AqbLH*mdl+b)KQl0JMCfc zwaSZ9ydQA)kdl${TE6bseB#A?O>57-ICJzHva~tD5@>?sLVv1nj19xcxfU@*L9N z$m&9T>^K%Po>@zkY_~+Fl?(_VYbjxgV|+@U-QsRTnD8HBHb$H#1kiNhNfMP7akVNJ zihC~J*|JP}J5eB0QKcR1Tt2I&Ahb+_cJjZJ);DI~}C3l*78wJ0vRp0PiKkE80fI@Apt$ckzSb&HQJ8FW7F2xFz&x}|3 z$DcWR!>1q#%v=}ccDb@=DWT-j*7B;wzU0zMPUf}Q-6>d|P?Y$G&UVNq7~HGEW(h5j%;yGGfG~q6mYMk;m#fDRQsF z6=i%_n3-LFeB9Ub1t**u<4~-b|MBAhqCoe*nitF-_6(c^!`i=3f^Io7S>eY(Gt_DN zq|Kl<*_TuJS>N@`_2sQ9>GBjl#N_(epgV4qr5>`$E-yRVa+1>XQwe_sKqn?0hcBVX zAHL+YrHwCp_f_q7r+*QPDm_Yp_tXgX5r|L%^y&2TzHofAQ%bc)P9-=rK&f+ikHL`q zH4s^=Pd5%;q+7W%j(*cLYt}BC<9&{OC&}D~+6=>mm6lw*3tKv9Ly!yQ*sf!h z@la=jla7GXZ`~RTD7LC+g`ALKS7~@uw0GuLdPaus$9M~$pd~z%CC;bY86(~c%O0ml zV|ugBiGccTdPVhk)SJwi|B;kBq^YjnGa%};p;B)P)&QaPKC0~^y?&dacIU*G*SD>K z**J3eFz-tL2%cmP6B1w@?jKDZr=>U?B8zjjWu+Wc_1=UlvifN1(V?CR@!0`o;~WDKANeeg*}Rv7 zuN&nZWuEuzYJ2`SnLrWX%bC519~i}k+O(iCL)6?^1DpRao~lg|XHU>`n#X!|8k&{6 zKEXig{TpuLs3@JERNMqx1v5VunT4aV?A}(*z&|=rh3%BHu6(hug=?kU$pHD)H@=$^ zfKZ$Vh|Jidpa%5`p@yyI{e#94QzxTw1_n+hW<4NbuLINI3*B1e>TZlTBrUvuwgDlV zyBFL$GtcaG@TV6N6(tK^klu2k10S%?o0{)udOSOi+OF|!*OUsY1&KMw5Lc$rG+R>+UvAv zv6M`hmJv#uXirJ1BQ4%`TS%isiIf&4l^iW}w4ozRkv92V-#E^Bf1kI1X0ptDzcbHs zKlgpz*LB_L+l$MfXPG>7=I`uRiF0Zb^*YkkX=~{I7cN|AGCasd{s+n&>Pz-mJ*Ws+ z1sfT@@7Tv?dys9zIQ`AmV2}jH1uB9q##0=Q&dmnu)L#%Eg!P_OA z&b6HU?!MLt6sem>)aS~vCJN7UWyX=;-(!X|P59pyEpy(XHhs!vqs)n74bhh-lDl4> znyeHmh-?cml~7nFu655d2J~uS=XNTW_ZVq2>8&e`#`$m;C_G)-3@eS3=uBaQc!6aP z=NdDfy?i0FAZG534EuP036kyshx}UC$#CY?9o60+H*A@`W1%K>y3JncTjBP~fHhx- zJ$z6ZD`qYX#=F5puVEANHbTuuI$7~3k0|qV_}VjPq_pGJ=A9b-%jjsF^zU4=SjDDa zE%Tlxcz*rk&WoX)PimlFP6@Le`d-YGKOF|?Z{+Qv$}f#ygP(@{eRR1ScK=J5#O1MI zht{s$TJcBEn{DOIP7i{48k@YRA}-|?x5DZKfGpNt1B)mc-O59-EV11Xsg($Ik@T}x z+mTzozhdAiarmvaCm4Ei6iSi@+JxZfDRJ^YaC$m3_4h(i{t+k>(K!DL4A(t!2bFhoS`aUM4$6b(W;4*ehZe4Aycx&;GG_BnIjgG1gzZSc z4-%>ADHQoK3 zaCihubBtnG6~5E_QtmVELc@ZMw5O{ytsTkhOqvQJ9x?gUM-4s(_cyN9?kf%QY`uB` zL|KE(=Es?9&$qf99uB#BqTqt3wok_bE^cnHnO@4DKl0+MZbKLvK1`pfO{}W0+K`id znoQ84K@!PPXl{PBEO#j>rpzm&vsZK&*&z~$YH-7j9V2yze(ofiMjsesX&z$+-z=@H ztUNphVV#y4O7U8(v1W}mq}I^%V~XFnxv_E7bsb8z)dweP*XZE{WB1aoN>>(b0?AT% z?-y33153L&8nJl9Xswfk>9laQ*WhYl9>iXszP^{PJ0j`7ka9@b^JMCo00ItWw#ix-uoQQSRvXT12tZo1^%)ohD%d)#Yo8m$}@SO0Fq7=JSjKMhZOaN3c8)sD5PAk+M7L{wf zdXs@MnxN9&R!wR2$eorH7Z(>1$+-cqz0;DdM=W!!tK3?>-vr2a1F!{)?ZQO1UQk+A zR?D!q#ge4)Xz@a&^d^&6PMf}vtD=L*-xwR_w?hLA!`QTbxR@>X~ zTg%@1^xs&PmBt2sL>bx=4j2QiK~8*g7yIkhK$sE9?K+!TSGC<+nBB~vNe-+}c%=Vv zy88F^1))Y?!{@451*@H3+PyO2Xj@D=OKp3&Sn8FwOKF#C1PGD}G^Wl9*w}R_4nMWB zv1!LDuFaK}kul_e#QQb6J!u|BYk*{}e6y-TCP1oNUO@rAtb{V?_uuQ2(c4TR?*lsa zsiP!ruz%{klBVfUCtVZy3g|oxZ|_ice`Jke$+6CTd!jiUtXvM05)JaxFBm*wnVyR+ zwxYEt-&G#ZE)J6F9;{^QKJzXwwb;MUxu6;;RX#gp?0}T3HV`}{S zOXzJfaXB9$EC;!-&h-O)Axn-)Zce5f49Q; z#EtF6f;HqMN=aU}Zs6_`N7nXY->-eGt*sZOSMZ1WV4rtYwUn_f8V%?WCUT|4!&x(wSI%*Z2tdVRy zg+_2!E1EV?;BhWDwUMvs;$SGLesTJ5iVurV z^CC3~c|Cr{tQlF~wpoeX zhJ+kYFpP?BI@s9MoN{=-vhTuK0vJU}*u-7OmzcTi-j~;i4e;ZGrL<~PKC;W#(Kp_Q z(`~aoO1pR#pKyqwa;aS0OcOyWOJhB6wC?Dm(5##fc! zz~Ug2%G})k%}(cy+vML;YdYQ|UHcN(lSj6+DjMum0+OV151soxtbV8Pehv?Yf=K#sGx1MRHwxJV%L-Sd)L zx3ofat6j;W38zk7yL1WFMO$rcM2lyhF41CNm7CD9U~pD?J&eyb9<2DNd=Ff4Lz&bX z2z(GhB%5w8X=OFa(QYUB7D|V%3^R_UCT}=ebvGv`Tt}Fp^zOzoxjp&Sffg2t6z36E zb3dG>>5|>bZ!agurHUEr9&~O;;2n4$&++tYt?TJ53q^5GX`~_EI3WMT+~J$sS7pgu zbF11uyYt#)kcg%hHl0oIpGe&zgg!u?T&w=lx!C?pBii960}epY1N`&M(Asn4N$}IW z)ILSW4;uQwoF=c<1K07_k;qC_gRkqixv%}F{$d8FIldy_FE%5`Q7v-l#+Zb3&L9j5 zx)MiKvo#HM<2^<>ZLkA^gMrOD8@8;hEb{CNgWR`G7jYH+qV+lYosCM24_Ak-C_n$& ziN(r6TesFPDbY{JHE(Bh_w?+YtUmHO@l3%M=&(`Qa zx5w12^lje8kA_q)I{rHdQD1GM;Utnx9Xuo1wAC;M6>4`%95u51mrAng)gb|-B#f7p zQ?e@L)Q0%961axBl^sa`4;Do_go8$kKw zW_`$Qgo=Mf%3YefbR#QJu^W8>`>QS@;^LJDx^AFeKEh7vymN2cizwtRmCiT;CS#1m zj^US$I2k?T=ntwxCwu{(YtFS87DGD9G>9Z?WhWX@z z4_AF?+K>O^>0RYLfH+o9;dGstOSuoqG8{y~y@CymMbV>%23UIg|Diae74$O~8+ zKKubhW+hz*co~Tz#ORsv^u;k$?S^OTQBqH`-5buGI!lz)m?d9TC2>X)H~+y>1WxMS zJ!PXHOCp+Swmex)M)DL+cVF@3XO30Z5QKUN)-l)aa!kM9lHpr23JM!9W91v_^oKB5 z_I=~lx4EzZI=dZbr$}c7az~K^VFW9jaTcF=1wso#<$=4E=*xqdYp2`^wip&2X68oZ zqf|vHi9su-Hyl-KYSvx*HsfBF(t~s>>+xNt;9Es@&2iJ&6xEP77sLSV0(ox;)(V}y zWl;oQ^;bdd^BL3C&V49Ox2^L<1|O;$Ml+2x{+jzps>x4x%O2mO-=W*RG=%kmL@3`k55r57QE838PU z6vm5AhsIvkAX~kgELzZS5IpJYNlmCx*(a`oxfGNkQ+?acwzQf9_a!ZopC`3=%F>o# zNs)G@#g%~)i*ai)awvrsWvD}RzHMZ5bSXFY!RB270cSm(*aA{%cDA&^?I36 zSpPsy3z1Pb>{#6F(?1m1>aEe%?&|Ko9r|^;vQjqEB!CY1g4B#9-RP5aW%)Hkq7_V0)A5N&U%1=4Q&d%Y?tdu{BP+fE)lnQSup?%k)j!gRs5LJEx;^mONHVl$J@ z7UVJrng5x)WC%J;cHLmd0)9ab37?*^EB=$^g`PQLiinen2>~&z^l_BU1bDAr#Li6B1saLl1lP-FrTQ>aulKd>ZY4NMbfTMwX0Tm!r-fF4go%`0Mn*qGltVYKpE_K07{|UEigzavJm)qmrn3V|Q0H}N< zR*HzAP~5~`>&D#LmSS0kqbf6W?1cKd!7<8Ia!QJ3*jzAb$`Z%t>xYK=X+sEJSYD~7 zH;;ba*DlV?O>j@)+SYa0-@o}@p|x7~;1yonH%N&WWmyvds#Zy-w zR8&?b1W6M4EH78oo&YOA zvg7qysUQDMfMK{3-`YO`xHfP;0JFRw8Z)O`N?FD)xre#Y~4D!yI1A^ROI*(PFXeRqQU+L8(G0J zQH;r+{k2;_lvtmaF6I8H3MZTRBQbygtaBXT-~cLKvAL)!xGgNKI!`;%FnMjUl~-fu z!N9CR2(reNHE5RV2I))vES9KR{g({LjUpSUu&D<{tc`EVEIS zkvXpZKPs?6rPom|x;t1(nh4^}aB4RQ)7RE^3VsS`oFX3;9Zi!~5@LvV_{er6U;=F}nk}6@|E&fj&^JK-ZJgJI{fb2i;X-_oN%eY&-KHlepIourH5bo; z9b%k!TEoLMMuyg?)M)#7X+^+Yx-H%Rai(^2XKXBIw=HiBtKfPP&~Zyu!FWi>o8CBb zid$+G>IDcUM1l+rxWY*{QpuR(&L{ieZ$d=k0&3*?SZXeEV&t;2vL}Y;fWOGoYw;fO z9BN+%=Wno*@*0jhO&1jtQC#PNVFJkZrv6J*sY15x1Ni z$}(=l{49Z^DtGnvtrnL0ai?eK?%$)HbS0r~zoAid2QwIfCe{4X_Hh2DCczAT`O+=T zAEk+Qc+Hw~>FG`|B@V@uDUi8S7P1&#UVJjp&H{gl{5aZEE>Y~OYwhTE#0YlmHK-mO zgU!vC-qBNjU7X(-A;eea)3@yBF39LQaQE=&_OLJm^9iV<;0MLu@AKkzf&FD#2k??l zpFg`+hw1^fGSLv`ftwbl^Y}Zk{KiraZdQZKvC4)GcZ!PYy~{z(gI7jlEHx2x7Mfs- z$9@ct7zDeVRq?S22?b{{)&)&9?62N7u6q8&x8=cnyP(afo|{(bn&WoKbLb%h}MW2m@cTm;zY^5uBB(^68W`LK3GMbBksx;Qx0Z$WhYGCqEA zsvxwUQlUH4n-s4gk^=f$=RR-&LByur^!5I=NkqmgZ0LxBEh_Z9ogjUHS) zJQRiFj32ac$F9)v0hacRp4C_|c^&yG?AqKjA!L(M z=>~d7jDjVRBon`T){2}UBPwJQ#|D1pLj)d)<@(RV{K1P~6=o$*8u9MG7J2M8@wYT1 Li!E0TcOL&gmH)II literal 69567 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 z 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 diff --git a/src/device/DemandHandler.cpp b/src/device/DemandHandler.cpp index 96e6b7a..3750a16 100644 --- a/src/device/DemandHandler.cpp +++ b/src/device/DemandHandler.cpp @@ -8,7 +8,7 @@ bool DemandHandler::init() { if (!o.exists()) { // TODO : ERROR MESSAGE - return; + return false; } bool indexValid = true; @@ -27,13 +27,14 @@ bool DemandHandler::init() { } if (indexValid) - return; + return false; pullMinimalPressure(); pullMaximalPressure(); pullConstante1(); pullConstante2(); pullPressureControlMode(); + return true; } bool DemandHandler::setHotlink() { diff --git a/src/device/DemandHandler.h b/src/device/DemandHandler.h index 9b8ca25..be08948 100644 --- a/src/device/DemandHandler.h +++ b/src/device/DemandHandler.h @@ -26,7 +26,7 @@ class DemandHandler { /* @brief Manualy get all the value, and update owned objects */ - void init(); + bool init(); /* @brief Set all the hotlink, waiting for any update @return true if succes, false if any error encountered diff --git a/src/device/EventBus.cpp b/src/device/EventBus.cpp new file mode 100644 index 0000000..9b846cb --- /dev/null +++ b/src/device/EventBus.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include + +class EventBus { + private: + // Non-templated base interface to store our callback lists + // generically inside the unordered_map. + struct CallbackListBase { + virtual ~CallbackListBase() = default; + }; + + // Specialized version holding the actual strongly-typed callbacks. + template + struct CallbackList : public CallbackListBase { + using CallbackType = std::function; + std::vector callbacks; + }; + + // Map associating each event type to its respective callback list + std::unordered_map> + subscribers; + + public: + // Subscribe to a specific event type + template + void subscribe(std::function callback) { + std::type_index typeIdx = std::type_index(typeid(EventType)); + + // If this is the first subscriber for this type, create the list + if (subscribers.find(typeIdx) == subscribers.end()) { + subscribers[typeIdx] = std::make_unique>(); + } + + // Safely cast to the specialized typed list and add the callback + auto *list = + static_cast *>(subscribers[typeIdx].get()); + list->callbacks.push_back(callback); + } + + // Publish an event to all registered subscribers + template void publish(const EventType &event) { + std::type_index typeIdx = std::type_index(typeid(EventType)); + + auto it = subscribers.find(typeIdx); + if (it != subscribers.end()) { + // Cast to the correct typed list to safely execute the callbacks + auto *list = + static_cast *>(it->second.get()); + for (const auto &callback : list->callbacks) { + callback(event); + } + } + } +}; \ No newline at end of file diff --git a/src/device/SettingsHandler.cpp b/src/device/SettingsHandler.cpp index e69de29..19a170f 100644 --- a/src/device/SettingsHandler.cpp +++ b/src/device/SettingsHandler.cpp @@ -0,0 +1,57 @@ +#include "SettingsHandler.h" +#include "SettingsHandlerConfig.h" + +void SettingsHandler::pullEquipmentName() { + midas::odb o(this->path); + std::string equipmentName = o[ParameterIndex::EQUIPMENT_NAME]; + inputHandler.get().updateEquipmentName(equipmentName); +} + +void SettingsHandler::pullOutputPressureSPIndex() { + midas::odb o(this->path); + int outputPressureSPIndex = o[ParameterIndex::OUTPUT_PRESSURE_SP_INDEX]; + inputHandler.get().updateEquipmentName(equipmentName); +} + +void SettingsHandler::pullInputVarioxSPIndex() { + midas::odb o(this->path); + std::string equipmentName = o[ParameterIndex::INPUT_VARIOX_SP_INDEX]; + inputHandler.get().updateEquipmentName(equipmentName); +} + +void SettingsHandler::pullInputVarioxTemperatureIndex() { + midas::odb o(this->path); + std::string equipmentName = + o[ParameterIndex::INPUT_VARIOX_TEMPERATURE_INDEX]; + inputHandler.get().updateEquipmentName(equipmentName); +} + +void SettingsHandler::pullInputVarioxPowerIndex() { + midas::odb o(this->path); + std::string equipmentName = o[ParameterIndex::INPUT_VARIOX_POWER_INDEX]; + inputHandler.get().updateEquipmentName(equipmentName); +} + +bool SettingsHandler::init() { + midas::odb o(this->path); + + bool areKeysValids = true; + + if (!o.exists(Configuration::EQUIPMENT_NAME)) { + areKeysValids = false; + } + if (!o.exists(Configuration::OUTPUT_PRESSURE_SP_INDEX)) { + areKeysValids = false; + } + if (!o.exists(Configuration::EQUIPMINPUT_VARIOX_SP_INDEXENT_NAME)) { + areKeysValids = false; + } + if (!o.exists(Configuration::INPUT_VARIOX_TEMPERATURE_INDEX)) { + areKeysValids = false; + } + if (!o.exists(Configuration::INPUT_VARIOX_POWER_INDEX)) { + areKeysValids = false; + } +} + +bool SettingsHandler::setHotlink() { return false; } diff --git a/src/device/SettingsHandler.h b/src/device/SettingsHandler.h index 8976ee6..78b0e4e 100644 --- a/src/device/SettingsHandler.h +++ b/src/device/SettingsHandler.h @@ -1,6 +1,24 @@ +#ifndef SETTINGS_HANDLER_H +#define SETTINGS_HANDLER_H +#include "InputHandler.h" +#include +#include class SettingsHandler { private: + std::string path; + std::reference_wrapper inputHandler; + + void pullEquipmentName(); + void pullOutputPressureSPIndex(); + void pullInputVarioxSPIndex(); + void pullInputVarioxTemperatureIndex(); + void pullInputVarioxPowerIndex(); + public: + bool init(); + bool setHotlink(); }; + +#endif \ No newline at end of file diff --git a/src/device/SettingsHandlerConfig.h b/src/device/SettingsHandlerConfig.h new file mode 100644 index 0000000..2960b14 --- /dev/null +++ b/src/device/SettingsHandlerConfig.h @@ -0,0 +1,19 @@ +#ifndef SETTINGS_HANDLER_CONFIG_H +#define SETTINGS_HANDLER_CONFIG_H + +#include + +namespace Configuration { + +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Devices/MITCPRESSC/DD/"; + +const std::string EQUIPMENT_NAME = "MITC_equipment"; +const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; +const std::string INPUT_VARIOX_SP_INDEX = "MITC_InputVarioxSPIndex"; +const std::string INPUT_VARIOX_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; +const std::string INPUT_VARIOX_POWER_INDEX = "MITC_InputVarioxPowIndex"; + +} // namespace Configuration + +#endif \ No newline at end of file diff --git a/src/device/odb/OdbReader.tpp b/src/device/odb/OdbReader.tpp deleted file mode 100644 index 134ced1..0000000 --- a/src/device/odb/OdbReader.tpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include - -template class OdbReader { - private: - std::string path; - - public: - OdbReader(std::string path); - std::optional readValue(); - std::optional readValueAtIndex(int index); -}; - -template inline OdbReader::OdbReader(std::string path) { - this->path = path; -} - -template inline std::optional OdbReader::readValue() { - midas::odb o(path); - if (o.exists()) { - return std::optional(o); - } - return std::optional(); -}; - -template -inline std::optional OdbReader::readValueAtIndex(int index) { - midas::odb o(path); - if (o.exists() && o.size() > index) { - return std::optional(o[index]); - } - - return std::optional(); -}; diff --git a/src/device/odb/OdbWriter.tpp b/src/device/odb/OdbWriter.tpp deleted file mode 100644 index 0b43e97..0000000 --- a/src/device/odb/OdbWriter.tpp +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include - -template class OdbWriter { - private: - std::string path; - - public: - OdbWriter(std::string path); - bool writeValue(T &value); - bool writeValueAtIndex(T &value, int index); -}; - -template inline OdbWriter::OdbWriter(std::string path) { - this->path = path; -} - -template inline bool OdbWriter::writeValue(const T &value) { - midas::odb o(path); - if (o.exists()) { - o = value; - return true; - } - return false; -} - -template -inline bool OdbWriter::writeValueAtIndex(const T &value, int index) { - midas::odb o(path); - if (o.exists() && o.size() > index) { - o[index] = value; - return true; - } - return false; -} -- 2.54.0 From bb596ca10051ff694b68ba4e115f1f7b98af1dbb Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 16 Jul 2026 13:11:01 +0200 Subject: [PATCH 03/28] demandHandler working --- CMakeLists.txt | 11 +-- main.cpp | 2 +- src/device/DemandHandler.cpp | 82 ++++++++++++-------- src/device/DemandHandler.h | 26 ++++--- src/device/DemandHandlerConfig.h | 6 +- src/device/{EventBus.cpp => EventBus.h} | 7 +- src/device/FeedbackHandler.h | 2 +- src/device/itc_pressure_optimizer.cpp | 33 ++++++++ src/device/itc_pressure_optimizer.h | 48 ++++++++++++ src/frontend/itc_pressure_optimizer_scfe.cpp | 2 +- 10 files changed, 159 insertions(+), 60 deletions(-) rename src/device/{EventBus.cpp => EventBus.h} (97%) create mode 100644 src/device/itc_pressure_optimizer.cpp create mode 100644 src/device/itc_pressure_optimizer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8716eb4..50a2740 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ find_package(Midas REQUIRED) add_library( itcPressureOptimizer src/device/itc_pressure_optimizer.cpp + src/device/DemandHandler.cpp ) set_property( @@ -49,11 +50,6 @@ target_include_directories( itcPressureOptimizer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} - PRIVATE - ${EPICSSYS}/include - ${EPICSSYS}/include/os/Linux - ${EPICSSYS}/include/compiler/gcc - ${EPICSSYS}/include/compiler/clang ) ################################################################################ @@ -75,11 +71,6 @@ target_include_directories( itcPressureOptimizerScfe PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} - PRIVATE - ${EPICSSYS}/include - ${EPICSSYS}/include/os/Linux - ${EPICSSYS}/include/compiler/gcc - ${EPICSSYS}/include/compiler/clang ) target_link_libraries( diff --git a/main.cpp b/main.cpp index 51e8b34..0779082 100644 --- a/main.cpp +++ b/main.cpp @@ -5,7 +5,7 @@ #include #include -#include "src/device/EventBus.cpp" +#include "src/device/EventBus.h" class ProdA {}; class ProdB {}; diff --git a/src/device/DemandHandler.cpp b/src/device/DemandHandler.cpp index 3750a16..4e9f7d9 100644 --- a/src/device/DemandHandler.cpp +++ b/src/device/DemandHandler.cpp @@ -1,28 +1,34 @@ #include "DemandHandler.h" #include "DemandHandlerConfig.h" +#include "itc_pressure_optimizer.h" #include #include -bool DemandHandler::init() { - midas::odb o(this->path); +#include "odbxx.h" +#include "tmfe.h" - if (!o.exists()) { - // TODO : ERROR MESSAGE +using namespace DemanHandlerConfig; + +bool DemandHandler::init() { + + if (!midas::odb::exists(this->path)) { + printf("[DEMANDE HANDLER] : Path doesn't exist\n"); return false; } + midas::odb o(this->path); bool indexValid = true; - if (o.size() < ParameterIndex::MINIMAL_PRESSURE) { + if (o.size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { indexValid = false; } - if (o.size() < ParameterIndex::MAXIMAL_PRESSURE) { + if (o.size() < static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { indexValid = false; } - if (o.size() < ParameterIndex::CONSTANTE_1) { + if (o.size() < static_cast(ParameterIndex::CONSTANTE_1)) { indexValid = false; } - if (o.size() < ParameterIndex::CONSTANTE_2) { + if (o.size() < static_cast(ParameterIndex::CONSTANTE_2)) { indexValid = false; } @@ -39,21 +45,21 @@ bool DemandHandler::init() { bool DemandHandler::setHotlink() { midas::odb to_watch(this->path); - to_watch([&](midas::odb &arg) { + to_watch.watch([&](midas::odb &arg) { switch (arg.get_last_index()) { - case ParameterIndex::MINIMAL_PRESSURE: + case static_cast(ParameterIndex::MINIMAL_PRESSURE): pullMinimalPressure(); break; - case ParameterIndex::MAXIMAL_PRESSURE: + case static_cast(ParameterIndex::MAXIMAL_PRESSURE): pullMaximalPressure(); break; - case ParameterIndex::MINIMAL_PRESSURE: + case static_cast(ParameterIndex::CONSTANTE_1): pullConstante1(); break; - case ParameterIndex::MINIMAL_PRESSURE: + case static_cast(ParameterIndex::CONSTANTE_2): pullConstante2(); break; - case ParameterIndex::PRESSURE_CONTROL_MODE: + case static_cast(ParameterIndex::PRESSURE_CONTROL_MODE): pullPressureControlMode(); break; default: @@ -61,47 +67,55 @@ bool DemandHandler::setHotlink() { break; } }); + return true; } void DemandHandler::pullMinimalPressure() { midas::odb o(this->path); - float minimalPressure = o[ParameterIndex::MINIMAL_PRESSURE]; - pressureCalculator.get().updateMinimalPressure(minimalPressure); + float minimalPressure = + o[static_cast(ParameterIndex::MINIMAL_PRESSURE)]; + Event event = {EventType::MINIMAL_PRESSURE, minimalPressure}; + eventBus.publish(event); } void DemandHandler::pullMaximalPressure() { midas::odb o(this->path); - float maximalPressure = o[ParameterIndex::MAXIMAL_PRESSURE]; - pressureCalculator.get().updateMinimalPressure(maximalPressure); + float maximalPressure = + o[static_cast(ParameterIndex::MAXIMAL_PRESSURE)]; + Event event = {EventType::MAXIMAL_PRESSURE, maximalPressure}; + eventBus.publish(event); } void DemandHandler::pullConstante1() { midas::odb o(this->path); - float constante1 = o[ParameterIndex::CONSTANTE_1]; - pressureCalculator.get().updateMinimalPressure(constante1); - feedbackHandler.get().setConstante1(constante1); + float constante1 = o[static_cast(ParameterIndex::CONSTANTE_1)]; + Event event = {EventType::CONSTANTE_1, constante1}; + eventBus.publish(event); } void DemandHandler::pullConstante2() { midas::odb o(this->path); - float constance2 = o[ParameterIndex::CONSTANTE_2]; - pressureCalculator.get().updateMinimalPressure(constance2); - feedbackHandler.get().setConstante2(constante2); + float constance2 = o[static_cast(ParameterIndex::CONSTANTE_2)]; + Event event = {EventType::CONSTANTE_2, constance2}; + eventBus.publish(event); } void DemandHandler::pullPressureControlMode() { midas::odb o(this->path); - float pressureControlMode = o[ParameterIndex::PRESSURE_CONTROL_MODE]; - feedbackHandler.get().enablePressureControlMode(pressureControlMode); + float pressureControlMode = + o[static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)]; + Event event = {EventType::PRESSURE_CONTROL_MODE, pressureControlMode != 0}; + eventBus.publish(event); } -DemandHandler::DemandHandler( - std::string equipmentName, - std::reference_wrapper feedbackHandler, - std::reference_wrapper pressureCalculator) { +DemandHandler::DemandHandler(EventBus &eventBusReference, + std::string equipmentName) + : path(DemanHandlerConfig::PATH_PREFIX + equipmentName + + DemanHandlerConfig::PATH_SUFFIX), + eventBus(eventBusReference) { - this->path = DemanHandlerConfig::PATH_PREFIX + equipmentName + - DemanHandlerConfig::PATH_SUFFIX; - this->feedbackHandler = feedbackHandler; - this->pressureCalculator = pressureCalculator; + eventBus.subscribe( + [this](const itcPressureOptimizer::EquipmentInitEvent &e) { + printf("[EquipmentInitEvent] : init recived\n"); + }); } diff --git a/src/device/DemandHandler.h b/src/device/DemandHandler.h index be08948..f694fb7 100644 --- a/src/device/DemandHandler.h +++ b/src/device/DemandHandler.h @@ -1,16 +1,27 @@ #ifndef DEMAND_HANDLER_H #define DEMAND_HANDLER_H -#include "FeedbackHandler.h" -#include "PressureCalculator.h" -#include +#include "EventBus.h" #include +#include class DemandHandler { private: std::string path; - std::reference_wrapper feedbackHandler; - std::reference_wrapper pressureCalculator; + EventBus &eventBus; + + enum class EventType { + MINIMAL_PRESSURE, + MAXIMAL_PRESSURE, + CONSTANTE_1, + CONSTANTE_2, + PRESSURE_CONTROL_MODE + }; + + struct Event { + EventType type; + std::variant value; + }; void pullMinimalPressure(); void pullMaximalPressure(); @@ -19,10 +30,7 @@ class DemandHandler { void pullPressureControlMode(); public: - DemandHandler( - std::string equipmentName, - std::reference_wrapper feedbackHandler, - std::reference_wrapper pressureCalculator); + DemandHandler(EventBus &eventBusReference, std::string equipmentName); /* @brief Manualy get all the value, and update owned objects */ diff --git a/src/device/DemandHandlerConfig.h b/src/device/DemandHandlerConfig.h index 1bdf8ff..855efd7 100644 --- a/src/device/DemandHandlerConfig.h +++ b/src/device/DemandHandlerConfig.h @@ -7,14 +7,14 @@ namespace DemanHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; const std::string PATH_SUFFIX = "/Demand"; -class enum ParameterIndex { +enum class ParameterIndex { PRESSURE_CONTROL_MODE = 6, MINIMAL_PRESSURE = 8, MAXIMAL_PRESSURE = 9, CONSTANTE_1 = 10, CONSTANTE_2 = 11 -} +}; -} // namespace DemanHandlerConfig +}; // namespace DemanHandlerConfig #endif diff --git a/src/device/EventBus.cpp b/src/device/EventBus.h similarity index 97% rename from src/device/EventBus.cpp rename to src/device/EventBus.h index 9b846cb..7259957 100644 --- a/src/device/EventBus.cpp +++ b/src/device/EventBus.h @@ -1,3 +1,6 @@ +#ifndef EVENT_BUS_H +#define EVENT_BUS_H + #include #include #include @@ -55,4 +58,6 @@ class EventBus { } } } -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/device/FeedbackHandler.h b/src/device/FeedbackHandler.h index 1e11f22..2f40ba0 100644 --- a/src/device/FeedbackHandler.h +++ b/src/device/FeedbackHandler.h @@ -17,7 +17,7 @@ class FeedbackHandler { Constante2 = 11 }; - setValueToOdb(int index, float value); + void setValueToOdb(int index, float value); public: FeedbackHandler(std::string equipmentName); diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp new file mode 100644 index 0000000..eac4626 --- /dev/null +++ b/src/device/itc_pressure_optimizer.cpp @@ -0,0 +1,33 @@ +#include "itc_pressure_optimizer.h" + +#include "DemandHandler.h" +#include "EventBus.h" +// #include "FeedbackHandler.h" +// #include "InputHandler.h" +// #include "OutputHandler.h" +// #include "PressureCalculator.h" +// #include "SettingsHandler.h" + +#include + +itcPressureOptimizer::itcPressureOptimizer(std::string equipmentName, + const char *equipmentFilename) + : TMFeEquipment(equipmentName.c_str(), equipmentFilename), eventBus(), + demandHandler(eventBus, equipmentName) { + fEqConfPeriodMilliSec = 1000; // refresh every seconds + fEqConfLogHistory = 1; + fEqConfReadOnlyWhenRunning = false; + fEqConfWriteEventsToOdb = true; +} + +/*, feedbackHandler(), inputHandler(), +outputHandler(), pressureCalculator(), + settingsHandler()*/ + +void itcPressureOptimizer::HandlePeriodic() {} + +TMFeResult +itcPressureOptimizer::HandleInit(const std::vector &args) { + eventBus.publish(EquipmentInitEvent{}); + return TMFeResult(); +} diff --git a/src/device/itc_pressure_optimizer.h b/src/device/itc_pressure_optimizer.h new file mode 100644 index 0000000..41ae383 --- /dev/null +++ b/src/device/itc_pressure_optimizer.h @@ -0,0 +1,48 @@ +#ifndef ITC_PRESSURE_OPTIMIZER_H +#define ITC_PRESSURE_OPTIMIZER_H + +#include "DemandHandler.h" +#include "EventBus.h" +// #include "FeedbackHandler.h" +// #include "InputHandler.h" +// #include "OutputHandler.h" +// #include "PressureCalculator.h" +// #include "SettingsHandler.h" + +#include "tmfe.h" +#include + +class itcPressureOptimizer : public TMFeEquipment { + + public: + struct EquipmentInitEvent {}; + + struct EquipmentPollEvent {}; + /** + * @brief Constructor + * @param equipmentName for debug purpose, cpp file of the equipment + */ + itcPressureOptimizer(std::string equipmentName, + const char *equipmentFilename); + + /** + * @brief Method overridden called periodically by Midas + */ + void HandlePeriodic(); + + /** + * @brief Method overridden called by Midas at initialization + */ + TMFeResult HandleInit(const std::vector &args); + + private: + EventBus eventBus; + DemandHandler demandHandler; + // FeedbackHandler feedbackHandler; + // InputHandler inputHandler; + // OutputHandler outputHandler; + // PressureCalculator pressureCalculator; + // SettingsHandler settingsHandler; +}; + +#endif \ No newline at end of file diff --git a/src/frontend/itc_pressure_optimizer_scfe.cpp b/src/frontend/itc_pressure_optimizer_scfe.cpp index 83b82f6..cbd1a36 100644 --- a/src/frontend/itc_pressure_optimizer_scfe.cpp +++ b/src/frontend/itc_pressure_optimizer_scfe.cpp @@ -8,7 +8,7 @@ itcPressureOptimizerScfe::itcPressureOptimizerScfe(std::string frontendName, std::string equipmentName) : TMFrontend() { FeSetName(frontendName.c_str()); - FeAddEquipment(new itcPressureOptimizer(equipmentName, __FILE__, 12)); + FeAddEquipment(new itcPressureOptimizer(equipmentName, __FILE__)); } int main(int argc, char *argv[]) { -- 2.54.0 From f2cdd29e0f98ebbb43b984e7596c73302f64ad54 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Mon, 20 Jul 2026 09:09:18 +0200 Subject: [PATCH 04/28] update --- CMakeLists.txt | 2 + src/device/DemandHandler.cpp | 15 +++-- src/device/FeedbackHandlerConfig.h | 39 ++++++++++++ src/device/InputHandler.cpp | 85 +++++++++++++++++++++++++++ src/device/InputHandler.h | 42 +++++++++++-- src/device/InputHandlerConfig.h | 14 +++++ src/device/PressureCalculator.cpp | 32 ++++++++-- src/device/PressureCalculator.h | 16 +++-- src/device/SettingsHandler.cpp | 67 ++++++++++++++------- src/device/SettingsHandler.h | 32 +++++++--- src/device/SettingsHandlerConfig.h | 10 ++-- src/device/itc_pressure_optimizer.cpp | 5 +- src/device/itc_pressure_optimizer.h | 8 +-- 13 files changed, 307 insertions(+), 60 deletions(-) create mode 100644 src/device/FeedbackHandlerConfig.h create mode 100644 src/device/InputHandlerConfig.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 50a2740..fb7a546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ add_library( itcPressureOptimizer src/device/itc_pressure_optimizer.cpp src/device/DemandHandler.cpp + src/device/InputHandler.cpp + src/device/SettingsHandler.cpp ) set_property( diff --git a/src/device/DemandHandler.cpp b/src/device/DemandHandler.cpp index 4e9f7d9..cee662d 100644 --- a/src/device/DemandHandler.cpp +++ b/src/device/DemandHandler.cpp @@ -19,6 +19,8 @@ bool DemandHandler::init() { bool indexValid = true; + // This is checked every start, in case of someone change the index in the + // config header if (o.size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { indexValid = false; } @@ -35,11 +37,11 @@ bool DemandHandler::init() { if (indexValid) return false; - pullMinimalPressure(); - pullMaximalPressure(); - pullConstante1(); - pullConstante2(); - pullPressureControlMode(); + this->pullMinimalPressure(); + this->pullMaximalPressure(); + this->pullConstante1(); + this->pullConstante2(); + this->pullPressureControlMode(); return true; } @@ -116,6 +118,7 @@ DemandHandler::DemandHandler(EventBus &eventBusReference, eventBus.subscribe( [this](const itcPressureOptimizer::EquipmentInitEvent &e) { - printf("[EquipmentInitEvent] : init recived\n"); + this->init(); + this->setHotlink(); }); } diff --git a/src/device/FeedbackHandlerConfig.h b/src/device/FeedbackHandlerConfig.h new file mode 100644 index 0000000..4b761b4 --- /dev/null +++ b/src/device/FeedbackHandlerConfig.h @@ -0,0 +1,39 @@ +#ifndef FEEDBACK_HANDLER_CONFIG_H +#define FEEDBACK_HANDLER_CONFIG_H + +#include + +class FeedbackHandler { + private: + bool isPressureControlModeEnable; + std::string equipmentName; + std::string odbPath; + std::string outputKeyName = "Measured" + + enum class RegisterIndex : uint16_t { + SetPoint = 1, + Temperature = 2, + Power = 3, + AveragePower = 4, + Pressure = 5, + Constante1 = 10, + Constante2 = 11 + }; + + void setValueToOdb(int index, float value); + + public: + FeedbackHandler(std::string equipmentName); + + void enablePressureControlMode(bool enable); + + void setSP(float value); + void setTemperature(float value); + void setPower(float value); + void setAveragePower(float value); + void setPressure(float value); + void setConstante1(float value); + void setConstante2(float value); +}; + +#endif \ No newline at end of file diff --git a/src/device/InputHandler.cpp b/src/device/InputHandler.cpp index e69de29..21d91d9 100644 --- a/src/device/InputHandler.cpp +++ b/src/device/InputHandler.cpp @@ -0,0 +1,85 @@ +#include "InputHandler.h" +#include "EventBus.h" +#include "InputHandlerConfig.h" +#include "SettingsHandler.h" + +#include +#include + +void InputHandler::updatePath(std::string name) { + this->path = InputHandlerConfig::PATH_PREFIX + name + + InputHandlerConfig::PATH_SUFFIX; +} + +void InputHandler::updateSPIndex(int index) { this->SPIndex = index; } + +void InputHandler::updateTemperatureIndex(int index) { + this->temperatureIndex = index; +} + +void InputHandler::updatePowerIndex(int index) { this->powerIndex = index; } + +void InputHandler::pullSPValue() { + midas::odb o(this->path); + float SPValue = o[SPIndex]; + Event event = {EventType::SP_VALUE, SPValue}; + eventBus.publish(event); +} +void InputHandler::pullTemperatureValue() { + midas::odb o(this->path); + float temperatureValue = o[temperatureIndex]; + Event event = {EventType::TEMPERATURE_VALUE, temperatureValue}; + eventBus.publish(event); +} +void InputHandler::pullPowerValue() { + midas::odb o(this->path); + float powerValue = o[powerIndex]; + Event event = {EventType::POWER_VALUE, powerValue}; + eventBus.publish(event); +} + +void InputHandler::updateHotlink() { + // TODO : Verified this code work + // This should change the target in case of a new name given + if (hotlink) { + hotlink->unwatch(); + hotlink.reset(); + } + + hotlink.emplace(path); + + hotlink->watch([&](midas::odb &arg) { + int hotlinkIndex = arg.get_last_index(); + if (hotlinkIndex == SPIndex) { + pullSPValue(); + } else if (hotlinkIndex == temperatureIndex) { + pullTemperatureValue(); + } else if (hotlinkIndex == powerIndex) { + pullPowerValue(); + } + }); +} + +InputHandler::InputHandler(EventBus &eventBusReference) + : eventBus(eventBusReference) { + eventBus.subscribe( + [this](const SettingsHandler::Event &e) { + switch (e.type) { + case SettingsHandler::EventType::EQUIPMENT_NAME: + updatePath(std::get(e.value)); + updateHotlink(); + break; + case SettingsHandler::EventType::INPUT_SP_INDEX: + updateSPIndex(std::get(e.value)); + break; + case SettingsHandler::EventType::INPUT_TEMPERATURE_INDEX: + updateTemperatureIndex(std::get(e.value)); + break; + case SettingsHandler::EventType::INPUT_POWER_INDEX: + updatePowerIndex(std::get(e.value)); + break; + default: + break; + } + }); +} diff --git a/src/device/InputHandler.h b/src/device/InputHandler.h index fe2b064..478c90f 100644 --- a/src/device/InputHandler.h +++ b/src/device/InputHandler.h @@ -1,15 +1,45 @@ +#ifndef INPUT_HANDLER_H +#define INPUT_HANDLER_H + +#include "EventBus.h" +#include "odbxx.h" +#include #include class InputHandler { private: - std::string equipmentName; - float cachedSP; - float cachedTemperature; - float cachedPower; + EventBus &eventBus; + std::string path; + int SPIndex; + int temperatureIndex; + int powerIndex; - public: - void updateEquipmentName(std::string name); + std::optional hotlink; + + enum class EventType { + SP_VALUE, + TEMPERATURE_VALUE, + POWER_VALUE, + }; + + struct Event { + EventType type; + float value; + }; + + void updatePath(std::string name); void updateSPIndex(int index); void updateTemperatureIndex(int index); void updatePowerIndex(int index); + + void updateHotlink(); + + void pullSPValue(); + void pullTemperatureValue(); + void pullPowerValue(); + + public: + InputHandler(EventBus &eventBusReference); }; + +#endif \ No newline at end of file diff --git a/src/device/InputHandlerConfig.h b/src/device/InputHandlerConfig.h new file mode 100644 index 0000000..258905e --- /dev/null +++ b/src/device/InputHandlerConfig.h @@ -0,0 +1,14 @@ +#ifndef INPUT_HANDLER_CONFIG_H +#define INPUT_HANDLER_CONFIG_H + +#include + +namespace InputHandlerConfig { + +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Variables"; + +const std::string INPUT_VARIABLE = "Input"; + +} // namespace InputHandlerConfig +#endif \ No newline at end of file diff --git a/src/device/PressureCalculator.cpp b/src/device/PressureCalculator.cpp index 7c171ab..c2dec03 100644 --- a/src/device/PressureCalculator.cpp +++ b/src/device/PressureCalculator.cpp @@ -2,12 +2,36 @@ #include void PressureCalculator::update() { - double c2 = average.getAverage() * cachedConstante2; - double c1 = cachedConstante1 * (cachedTemperature - cachedSP - c2); - double uncapedPressure = cachedMinimalPressure + c1; - if (c2 != 0) { + double c2contrib = average.getAverage() * cachedConstante2; + double c1contrib = + cachedConstante1 * (cachedTemperature - cachedSP - c2contrib); + double uncapedPressure = cachedMinimalPressure + c1contrib; + + double cappedPressure = uncapedPressure; + + if (c2contrib != 0) { + c2contrib = -c2contrib * cachedConstante1; + } else { + c2contrib = 0.0f; } + + if (cappedPressure < cachedMinimalPressure) + cappedPressure = cachedMinimalPressure; + + if (cappedPressure > cachedMaximalPressure) + cappedPressure = cachedMaximalPressure; + + Event event = {}; + + if (c2contrib != 0) { + event.c1contrib = c1contrib; + event.c2contrib = c2contrib; + event.uncapedPressure = uncapedPressure; + event.cappedPressure = cappedPressure; + } + + eventBus.publish(event); } void PressureCalculator::updateSP(float value) { diff --git a/src/device/PressureCalculator.h b/src/device/PressureCalculator.h index f53b128..d808acf 100644 --- a/src/device/PressureCalculator.h +++ b/src/device/PressureCalculator.h @@ -3,7 +3,15 @@ class PressureCalculator { private: Average average; - OutputHandler outputHandler; + EventBus &eventBus; + + struct Event { + float c1contrib; + float c2contrib; + float uncapedPressure; + float cappedPressure; + }; + float cachedSP; float cachedTemperature; float cachedPower; @@ -13,9 +21,6 @@ class PressureCalculator { float cachedConstante1; float cachedConstante2; - public: - void update(); - void updateSP(float value); void updateTemperature(float value); void updatePower(float value); @@ -24,4 +29,7 @@ class PressureCalculator { void updateMaximalPressure(float value); void updateConstante1(float value); void updateConstante2(float value); + + public: + void update(); }; diff --git a/src/device/SettingsHandler.cpp b/src/device/SettingsHandler.cpp index 19a170f..aae5a54 100644 --- a/src/device/SettingsHandler.cpp +++ b/src/device/SettingsHandler.cpp @@ -1,35 +1,55 @@ #include "SettingsHandler.h" #include "SettingsHandlerConfig.h" +#include "itc_pressure_optimizer.h" +#include + +using namespace SettingsHandlerConfig; void SettingsHandler::pullEquipmentName() { midas::odb o(this->path); - std::string equipmentName = o[ParameterIndex::EQUIPMENT_NAME]; - inputHandler.get().updateEquipmentName(equipmentName); + std::string equipmentName = o(SettingsHandlerConfig::EQUIPMENT_NAME); + Event event = {EventType::EQUIPMENT_NAME, equipmentName}; + eventBus.publish(event); } void SettingsHandler::pullOutputPressureSPIndex() { midas::odb o(this->path); - int outputPressureSPIndex = o[ParameterIndex::OUTPUT_PRESSURE_SP_INDEX]; - inputHandler.get().updateEquipmentName(equipmentName); + int outputPressureSPIndex = + o(SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX); + Event event = {EventType::OUTPUT_PRESSURE_SP_INDEX, outputPressureSPIndex}; + eventBus.publish(event); } -void SettingsHandler::pullInputVarioxSPIndex() { +void SettingsHandler::pullInputSPIndex() { midas::odb o(this->path); - std::string equipmentName = o[ParameterIndex::INPUT_VARIOX_SP_INDEX]; - inputHandler.get().updateEquipmentName(equipmentName); + int inputSPIndex = o(SettingsHandlerConfig::INPUT_SP_INDEX); + Event event = {EventType::INPUT_SP_INDEX, inputSPIndex}; + eventBus.publish(event); } -void SettingsHandler::pullInputVarioxTemperatureIndex() { +void SettingsHandler::pullInputTemperatureIndex() { midas::odb o(this->path); - std::string equipmentName = - o[ParameterIndex::INPUT_VARIOX_TEMPERATURE_INDEX]; - inputHandler.get().updateEquipmentName(equipmentName); + int temperatureIndex = o(SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX); + Event event = {EventType::INPUT_TEMPERATURE_INDEX, temperatureIndex}; + eventBus.publish(event); } -void SettingsHandler::pullInputVarioxPowerIndex() { +void SettingsHandler::pullInputPowerIndex() { midas::odb o(this->path); - std::string equipmentName = o[ParameterIndex::INPUT_VARIOX_POWER_INDEX]; - inputHandler.get().updateEquipmentName(equipmentName); + int powerIndex = o(SettingsHandlerConfig::INPUT_POWER_INDEX); + Event event = {EventType::INPUT_POWER_INDEX, powerIndex}; + eventBus.publish(event); +} + +SettingsHandler::SettingsHandler(EventBus &eventBusReference, + std::string equipmentName) + : path(SettingsHandlerConfig::PATH_PREFIX + equipmentName + + SettingsHandlerConfig::PATH_SUFFIX), + eventBus(eventBusReference) { + eventBus.subscribe( + [this](const itcPressureOptimizer::EquipmentInitEvent &e) { + this->init(); + }); } bool SettingsHandler::init() { @@ -37,21 +57,26 @@ bool SettingsHandler::init() { bool areKeysValids = true; - if (!o.exists(Configuration::EQUIPMENT_NAME)) { + if (!o.exists(SettingsHandlerConfig::EQUIPMENT_NAME)) { areKeysValids = false; } - if (!o.exists(Configuration::OUTPUT_PRESSURE_SP_INDEX)) { + if (!o.exists(SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX)) { areKeysValids = false; } - if (!o.exists(Configuration::EQUIPMINPUT_VARIOX_SP_INDEXENT_NAME)) { + if (!o.exists(SettingsHandlerConfig::INPUT_SP_INDEX)) { areKeysValids = false; } - if (!o.exists(Configuration::INPUT_VARIOX_TEMPERATURE_INDEX)) { + if (!o.exists(SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX)) { areKeysValids = false; } - if (!o.exists(Configuration::INPUT_VARIOX_POWER_INDEX)) { + if (!o.exists(SettingsHandlerConfig::INPUT_POWER_INDEX)) { areKeysValids = false; } -} -bool SettingsHandler::setHotlink() { return false; } + this->pullEquipmentName(); + this->pullOutputPressureSPIndex(); + this->pullInputSPIndex(); + this->pullInputTemperatureIndex(); + this->pullInputPowerIndex(); + return true; +} diff --git a/src/device/SettingsHandler.h b/src/device/SettingsHandler.h index 78b0e4e..cc51847 100644 --- a/src/device/SettingsHandler.h +++ b/src/device/SettingsHandler.h @@ -4,21 +4,37 @@ #include "InputHandler.h" #include #include +#include class SettingsHandler { + + public: + SettingsHandler(EventBus &eventBusReference, std::string equipmentName); + bool init(); + bool setHotlink(); + + enum class EventType { + EQUIPMENT_NAME, + OUTPUT_PRESSURE_SP_INDEX, + INPUT_SP_INDEX, + INPUT_TEMPERATURE_INDEX, + INPUT_POWER_INDEX + }; + + struct Event { + EventType type; + std::variant value; + }; + private: std::string path; - std::reference_wrapper inputHandler; + EventBus &eventBus; void pullEquipmentName(); void pullOutputPressureSPIndex(); - void pullInputVarioxSPIndex(); - void pullInputVarioxTemperatureIndex(); - void pullInputVarioxPowerIndex(); - - public: - bool init(); - bool setHotlink(); + void pullInputSPIndex(); + void pullInputTemperatureIndex(); + void pullInputPowerIndex(); }; #endif \ No newline at end of file diff --git a/src/device/SettingsHandlerConfig.h b/src/device/SettingsHandlerConfig.h index 2960b14..dc2cb8a 100644 --- a/src/device/SettingsHandlerConfig.h +++ b/src/device/SettingsHandlerConfig.h @@ -3,17 +3,17 @@ #include -namespace Configuration { +namespace SettingsHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; const std::string PATH_SUFFIX = "/Devices/MITCPRESSC/DD/"; const std::string EQUIPMENT_NAME = "MITC_equipment"; const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; -const std::string INPUT_VARIOX_SP_INDEX = "MITC_InputVarioxSPIndex"; -const std::string INPUT_VARIOX_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; -const std::string INPUT_VARIOX_POWER_INDEX = "MITC_InputVarioxPowIndex"; +const std::string INPUT_SP_INDEX = "MITC_InputVarioxSPIndex"; +const std::string INPUT_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; +const std::string INPUT_POWER_INDEX = "MITC_InputVarioxPowIndex"; -} // namespace Configuration +} // namespace SettingsHandlerConfig #endif \ No newline at end of file diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp index eac4626..40a7304 100644 --- a/src/device/itc_pressure_optimizer.cpp +++ b/src/device/itc_pressure_optimizer.cpp @@ -13,14 +13,15 @@ itcPressureOptimizer::itcPressureOptimizer(std::string equipmentName, const char *equipmentFilename) : TMFeEquipment(equipmentName.c_str(), equipmentFilename), eventBus(), - demandHandler(eventBus, equipmentName) { + demandHandler(eventBus, equipmentName), + settingsHandler(eventBus, equipmentName), inputHandler(eventBus) { fEqConfPeriodMilliSec = 1000; // refresh every seconds fEqConfLogHistory = 1; fEqConfReadOnlyWhenRunning = false; fEqConfWriteEventsToOdb = true; } -/*, feedbackHandler(), inputHandler(), +/*, feedbackHandler(), , outputHandler(), pressureCalculator(), settingsHandler()*/ diff --git a/src/device/itc_pressure_optimizer.h b/src/device/itc_pressure_optimizer.h index 41ae383..cc86a88 100644 --- a/src/device/itc_pressure_optimizer.h +++ b/src/device/itc_pressure_optimizer.h @@ -3,11 +3,11 @@ #include "DemandHandler.h" #include "EventBus.h" +#include "InputHandler.h" +#include "SettingsHandler.h" // #include "FeedbackHandler.h" -// #include "InputHandler.h" // #include "OutputHandler.h" // #include "PressureCalculator.h" -// #include "SettingsHandler.h" #include "tmfe.h" #include @@ -38,11 +38,11 @@ class itcPressureOptimizer : public TMFeEquipment { private: EventBus eventBus; DemandHandler demandHandler; + SettingsHandler settingsHandler; + InputHandler inputHandler; // FeedbackHandler feedbackHandler; - // InputHandler inputHandler; // OutputHandler outputHandler; // PressureCalculator pressureCalculator; - // SettingsHandler settingsHandler; }; #endif \ No newline at end of file -- 2.54.0 From 31e277726da4a2ac9e192ee83d2b29db3e82f440 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Mon, 20 Jul 2026 11:28:39 +0200 Subject: [PATCH 05/28] PressureCalculator finished --- CMakeLists.txt | 2 ++ src/device/Average.h | 5 +++ src/device/DemandHandler.cpp | 16 +++++++++ src/device/DemandHandler.h | 33 ++++++++++-------- src/device/DemandHandlerConfig.h | 2 ++ src/device/InputHandler.h | 21 ++++++----- src/device/PressureCalculator.cpp | 50 +++++++++++++++++++++++++++ src/device/PressureCalculator.h | 10 +++++- src/device/itc_pressure_optimizer.cpp | 3 +- src/device/itc_pressure_optimizer.h | 4 +-- 10 files changed, 116 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb7a546..4f93ef3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,8 @@ add_library( src/device/DemandHandler.cpp src/device/InputHandler.cpp src/device/SettingsHandler.cpp + src/device/PressureCalculator.cpp + src/device/Average.cpp ) set_property( diff --git a/src/device/Average.h b/src/device/Average.h index 3e76bbf..785b42e 100644 --- a/src/device/Average.h +++ b/src/device/Average.h @@ -1,3 +1,6 @@ +#ifndef AVERAGE_H +#define AVERAGE_H + #include #include @@ -26,3 +29,5 @@ class Average { void addValue(double value); double getAverage(); }; + +#endif \ No newline at end of file diff --git a/src/device/DemandHandler.cpp b/src/device/DemandHandler.cpp index cee662d..2c4be25 100644 --- a/src/device/DemandHandler.cpp +++ b/src/device/DemandHandler.cpp @@ -33,6 +33,12 @@ bool DemandHandler::init() { if (o.size() < static_cast(ParameterIndex::CONSTANTE_2)) { indexValid = false; } + if (o.size() < static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { + indexValid = false; + } + if (o.size() < static_cast(ParameterIndex::SET_PRESSURE)) { + indexValid = false; + } if (indexValid) return false; @@ -64,6 +70,9 @@ bool DemandHandler::setHotlink() { case static_cast(ParameterIndex::PRESSURE_CONTROL_MODE): pullPressureControlMode(); break; + case static_cast(ParameterIndex::SET_PRESSURE): + pullSetPressure(); + break; default: // Update out of scope, discarded break; @@ -110,6 +119,13 @@ void DemandHandler::pullPressureControlMode() { eventBus.publish(event); } +void DemandHandler::pullSetPressure() { + midas::odb o(this->path); + float setPressure = o[static_cast(ParameterIndex::SET_PRESSURE)]; + Event event = {EventType::SET_PRESSURE, setPressure != 0}; + eventBus.publish(event); +} + DemandHandler::DemandHandler(EventBus &eventBusReference, std::string equipmentName) : path(DemanHandlerConfig::PATH_PREFIX + equipmentName + diff --git a/src/device/DemandHandler.h b/src/device/DemandHandler.h index f694fb7..4054e72 100644 --- a/src/device/DemandHandler.h +++ b/src/device/DemandHandler.h @@ -10,27 +10,13 @@ class DemandHandler { std::string path; EventBus &eventBus; - enum class EventType { - MINIMAL_PRESSURE, - MAXIMAL_PRESSURE, - CONSTANTE_1, - CONSTANTE_2, - PRESSURE_CONTROL_MODE - }; - - struct Event { - EventType type; - std::variant value; - }; - void pullMinimalPressure(); void pullMaximalPressure(); void pullConstante1(); void pullConstante2(); void pullPressureControlMode(); + void pullSetPressure(); - public: - DemandHandler(EventBus &eventBusReference, std::string equipmentName); /* @brief Manualy get all the value, and update owned objects */ @@ -40,6 +26,23 @@ class DemandHandler { @return true if succes, false if any error encountered */ bool setHotlink(); + + public: + enum class EventType { + MINIMAL_PRESSURE, + MAXIMAL_PRESSURE, + CONSTANTE_1, + CONSTANTE_2, + PRESSURE_CONTROL_MODE, + SET_PRESSURE + }; + + struct Event { + EventType type; + std::variant value; + }; + + DemandHandler(EventBus &eventBusReference, std::string equipmentName); }; #endif \ No newline at end of file diff --git a/src/device/DemandHandlerConfig.h b/src/device/DemandHandlerConfig.h index 855efd7..2e13b68 100644 --- a/src/device/DemandHandlerConfig.h +++ b/src/device/DemandHandlerConfig.h @@ -8,7 +8,9 @@ const std::string PATH_PREFIX = "/Equipment/"; const std::string PATH_SUFFIX = "/Demand"; enum class ParameterIndex { + PRESSURE_CONTROL_MODE = 6, + SET_PRESSURE = 7, MINIMAL_PRESSURE = 8, MAXIMAL_PRESSURE = 9, CONSTANTE_1 = 10, diff --git a/src/device/InputHandler.h b/src/device/InputHandler.h index 478c90f..7076221 100644 --- a/src/device/InputHandler.h +++ b/src/device/InputHandler.h @@ -16,17 +16,6 @@ class InputHandler { std::optional hotlink; - enum class EventType { - SP_VALUE, - TEMPERATURE_VALUE, - POWER_VALUE, - }; - - struct Event { - EventType type; - float value; - }; - void updatePath(std::string name); void updateSPIndex(int index); void updateTemperatureIndex(int index); @@ -40,6 +29,16 @@ class InputHandler { public: InputHandler(EventBus &eventBusReference); + enum class EventType { + SP_VALUE, + TEMPERATURE_VALUE, + POWER_VALUE, + }; + + struct Event { + EventType type; + float value; + }; }; #endif \ No newline at end of file diff --git a/src/device/PressureCalculator.cpp b/src/device/PressureCalculator.cpp index c2dec03..5f2e05d 100644 --- a/src/device/PressureCalculator.cpp +++ b/src/device/PressureCalculator.cpp @@ -1,4 +1,8 @@ #include "PressureCalculator.h" +#include "Average.h" +#include "DemandHandler.h" +#include "InputHandler.h" +#include "itc_pressure_optimizer.h" #include void PressureCalculator::update() { @@ -34,6 +38,51 @@ void PressureCalculator::update() { eventBus.publish(event); } +PressureCalculator::PressureCalculator(EventBus &eventBusReference) + : average(30), eventBus(eventBusReference) { + eventBus.subscribe( + [this](const DemandHandler::Event &e) { + switch (e.type) { + case DemandHandler::EventType::MINIMAL_PRESSURE: + updateMinimalPressure(std::get(e.value)); + break; + case DemandHandler::EventType::MAXIMAL_PRESSURE: + updateMaximalPressure(std::get(e.value)); + break; + case DemandHandler::EventType::CONSTANTE_1: + updateConstante1(std::get(e.value)); + break; + case DemandHandler::EventType::CONSTANTE_2: + updateConstante2(std::get(e.value)); + break; + default: + break; + } + }); + + eventBus.subscribe( + [this](const InputHandler::Event &e) { + switch (e.type) { + case InputHandler::EventType::SP_VALUE: + updateSP(e.value); + break; + case InputHandler::EventType::TEMPERATURE_VALUE: + updateTemperature(e.value); + break; + case InputHandler::EventType::POWER_VALUE: + updatePower(e.value); + break; + default: + break; + } + }); + + eventBus.subscribe( + [this](const itcPressureOptimizer::EquipmentPollEvent &e) { + update(); + }); +} + void PressureCalculator::updateSP(float value) { cachedSP = value; update(); @@ -46,6 +95,7 @@ void PressureCalculator::updateTemperature(float value) { void PressureCalculator::updatePower(float value) { cachedPower = value; + average.addValue(static_cast(value)); update(); } diff --git a/src/device/PressureCalculator.h b/src/device/PressureCalculator.h index d808acf..5387650 100644 --- a/src/device/PressureCalculator.h +++ b/src/device/PressureCalculator.h @@ -1,4 +1,8 @@ +#ifndef PRESSURE_CALCULATOR_H +#define PRESSURE_CALCULATOR_H + #include "Average.h" +#include "EventBus.h" class PressureCalculator { private: @@ -30,6 +34,10 @@ class PressureCalculator { void updateConstante1(float value); void updateConstante2(float value); - public: void update(); + + public: + PressureCalculator(EventBus &eventBusReference); }; + +#endif \ No newline at end of file diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp index 40a7304..4a29043 100644 --- a/src/device/itc_pressure_optimizer.cpp +++ b/src/device/itc_pressure_optimizer.cpp @@ -14,7 +14,8 @@ itcPressureOptimizer::itcPressureOptimizer(std::string equipmentName, const char *equipmentFilename) : TMFeEquipment(equipmentName.c_str(), equipmentFilename), eventBus(), demandHandler(eventBus, equipmentName), - settingsHandler(eventBus, equipmentName), inputHandler(eventBus) { + settingsHandler(eventBus, equipmentName), inputHandler(eventBus), + pressureCalculator(eventBus) { fEqConfPeriodMilliSec = 1000; // refresh every seconds fEqConfLogHistory = 1; fEqConfReadOnlyWhenRunning = false; diff --git a/src/device/itc_pressure_optimizer.h b/src/device/itc_pressure_optimizer.h index cc86a88..5dabbe2 100644 --- a/src/device/itc_pressure_optimizer.h +++ b/src/device/itc_pressure_optimizer.h @@ -4,10 +4,10 @@ #include "DemandHandler.h" #include "EventBus.h" #include "InputHandler.h" +#include "PressureCalculator.h" #include "SettingsHandler.h" // #include "FeedbackHandler.h" // #include "OutputHandler.h" -// #include "PressureCalculator.h" #include "tmfe.h" #include @@ -40,9 +40,9 @@ class itcPressureOptimizer : public TMFeEquipment { DemandHandler demandHandler; SettingsHandler settingsHandler; InputHandler inputHandler; + PressureCalculator pressureCalculator; // FeedbackHandler feedbackHandler; // OutputHandler outputHandler; - // PressureCalculator pressureCalculator; }; #endif \ No newline at end of file -- 2.54.0 From 77c8c61c1c46a311d4a912d8a105784819562bd4 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Wed, 22 Jul 2026 08:42:18 +0200 Subject: [PATCH 06/28] bug fix --- CMakeLists.txt | 28 +++++ src/device/DemandHandlerConfig.h | 2 +- src/device/FeedbackHandler.cpp | 146 +++++++++++++++++++++++--- src/device/FeedbackHandler.h | 34 +++--- src/device/FeedbackHandlerConfig.h | 46 +++----- src/device/OutputHandler.cpp | 65 +++++++++++- src/device/OutputHandler.h | 20 +++- src/device/PressureCalculator.cpp | 6 +- src/device/PressureCalculator.h | 16 +-- src/device/SettingsHandler.cpp | 3 + src/device/SettingsHandlerConfig.h | 2 +- src/device/itc_pressure_optimizer.cpp | 7 +- src/device/itc_pressure_optimizer.h | 8 +- 13 files changed, 297 insertions(+), 86 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f93ef3..160025a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ add_compile_options( -Wall -Wformat=2 -g + -O0 -Wno-format-nonliteral -Wno-strict-aliasing -Wuninitialized @@ -33,8 +34,22 @@ add_library( src/device/InputHandler.cpp src/device/SettingsHandler.cpp src/device/PressureCalculator.cpp + src/device/FeedbackHandler.cpp + src/device/OutputHandler.cpp src/device/Average.cpp ) +target_compile_options( + itcPressureOptimizer + PRIVATE + -fsanitize=address + -fno-omit-frame-pointer +) + +target_link_options( + itcPressureOptimizer + PRIVATE + -fsanitize=address +) set_property( TARGET @@ -71,6 +86,19 @@ set_property( CXX_STANDARD 17 ) +target_compile_options( + itcPressureOptimizerScfe + PRIVATE + -fsanitize=address + -fno-omit-frame-pointer +) + +target_link_options( + itcPressureOptimizerScfe + PRIVATE + -fsanitize=address +) + target_include_directories( itcPressureOptimizerScfe PUBLIC diff --git a/src/device/DemandHandlerConfig.h b/src/device/DemandHandlerConfig.h index 2e13b68..8223d23 100644 --- a/src/device/DemandHandlerConfig.h +++ b/src/device/DemandHandlerConfig.h @@ -5,7 +5,7 @@ namespace DemanHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Demand"; +const std::string PATH_SUFFIX = "/Variables/Demand"; enum class ParameterIndex { diff --git a/src/device/FeedbackHandler.cpp b/src/device/FeedbackHandler.cpp index c6b1865..991c0cf 100644 --- a/src/device/FeedbackHandler.cpp +++ b/src/device/FeedbackHandler.cpp @@ -1,31 +1,145 @@ #include "FeedbackHandler.h" +#include "DemandHandler.h" +#include "EventBus.h" +#include "FeedbackHandlerConfig.h" +#include "InputHandler.h" +#include "PressureCalculator.h" + +#include #include -FeedbackHandler::setValueToOdb(int index, float value) { - // TODO Check validity - midas::odb o(this->odbPath); - o[outputKeyName.c_str()][index] = value; -} +FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, + std::string equipmentName) { + this->path = FeedbackHandlerConf::PATH_PREFIX + equipmentName + + FeedbackHandlerConf::PATH_SUFFIX; -FeedbackHandler::FeedbackHandler(std::string equipmentName) { - this->equipmentName = equipmentName; - this->odbPath = "/Equipment/" + equipmentName + "/Variables"; + eventBusReference.subscribe( + [this](const PressureCalculator::Event &e) { + setConstante2(e.c2contrib); + setConstante1(e.c1contrib); + setPressureMinimum(e.uncapedPressure); + setPressureMaximum(e.uncapedPressure); + setPressure(e.cappedPressure); + setAveragePower(e.averagePower); + }); + eventBusReference.subscribe( + [this](const DemandHandler::Event &e) { + switch (e.type) { + case DemandHandler::EventType::PRESSURE_CONTROL_MODE: + enablePressureControlMode(std::get(e.value)); + break; + case DemandHandler::EventType::SET_PRESSURE: + enableSetPressure(std::get(e.value)); + break; + default: + break; + } + }); + eventBusReference.subscribe( + [this](const InputHandler::Event &e) { + switch (e.type) { + case InputHandler::EventType::SP_VALUE: + setSP(e.value); + break; + case InputHandler::EventType::TEMPERATURE_VALUE: + setTemperature(e.value); + break; + case InputHandler::EventType::POWER_VALUE: + setPower(e.value); + break; + default: + break; + } + }); } void FeedbackHandler::enablePressureControlMode(bool enable) { - this->isPressureControlModeEnable = enable; + isPressureControlModeEnable = enable; + updatePressure(); } -void FeedbackHandler::setSP(float value) {} +void FeedbackHandler::enableSetPressure(bool enable) { + isSetPressureEnable = enable; + updatePressure(); +} -void FeedbackHandler::setTemperature(float value) {} +void FeedbackHandler::setPressure(float value) { + cachedCappedPressure = value; + updatePressure(); +} -void FeedbackHandler::setPower(float value) {} +void FeedbackHandler::setConstante1(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::CONSTANTE_1)] = + value; +} +void FeedbackHandler::setAveragePower(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::AVERAGE_POWER)] = + value; +} -void FeedbackHandler::setAveragePower(float value) {} +void FeedbackHandler::setConstante2(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::CONSTANTE_2)] = + value; +} +void FeedbackHandler::updatePressure() { + { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::PRESSURE)] = + cachedCappedPressure; + } + float cappedPressure = + std::roundf(cachedCappedPressure * 10.0f + 0.5f) / 10.0f; + // Pressure control mode is set to calculated? + if (isPressureControlModeEnable) { + midas::odb o(path); + o[FeedbackHandlerConf::INPUT_VARNAME][static_cast( + FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = + cappedPressure; + } + // update setpoint of Mercury ITC in ODB ? + if (isSetPressureEnable) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME][static_cast( + FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = + cappedPressure; + } +} -void FeedbackHandler::setPressure(float value) {} +void FeedbackHandler::setPressureMinimum(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::MINIMUM_PRESSURE)] = + value; +} +void FeedbackHandler::setPressureMaximum(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::MAXIMUM_PRESSURE)] = + value; +} -void FeedbackHandler::setConstante1(float value) {} +void FeedbackHandler::setSP(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::SP)] = value; +} -void FeedbackHandler::setConstante2(float value) {} +void FeedbackHandler::setTemperature(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::TEMPERATURE)] = + value; +} + +void FeedbackHandler::setPower(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::POWER)] = value; +} diff --git a/src/device/FeedbackHandler.h b/src/device/FeedbackHandler.h index 2f40ba0..05adda9 100644 --- a/src/device/FeedbackHandler.h +++ b/src/device/FeedbackHandler.h @@ -1,34 +1,34 @@ +#ifndef FEEDBACK_HANDLER_H +#define FEEDBACK_HANDLER_H + +#include "EventBus.h" #include class FeedbackHandler { private: bool isPressureControlModeEnable; - std::string equipmentName; - std::string odbPath; - std::string outputKeyName = "Measured" + bool isSetPressureEnable; + float cachedCappedPressure; - enum class RegisterIndex : uint16_t { - SetPoint = 1, - Temperature = 2, - Power = 3, - AveragePower = 4, - Pressure = 5, - Constante1 = 10, - Constante2 = 11 - }; - - void setValueToOdb(int index, float value); - - public: - FeedbackHandler(std::string equipmentName); + std::string path; void enablePressureControlMode(bool enable); + void enableSetPressure(bool enable); void setSP(float value); void setTemperature(float value); + void setPressureMinimum(float value); + void setPressureMaximum(float value); void setPower(float value); void setAveragePower(float value); void setPressure(float value); void setConstante1(float value); void setConstante2(float value); + + void updatePressure(); + + public: + FeedbackHandler(EventBus &eventBusReference, std::string equipmentName); }; + +#endif \ No newline at end of file diff --git a/src/device/FeedbackHandlerConfig.h b/src/device/FeedbackHandlerConfig.h index 4b761b4..0b4c522 100644 --- a/src/device/FeedbackHandlerConfig.h +++ b/src/device/FeedbackHandlerConfig.h @@ -3,37 +3,25 @@ #include -class FeedbackHandler { - private: - bool isPressureControlModeEnable; - std::string equipmentName; - std::string odbPath; - std::string outputKeyName = "Measured" +namespace FeedbackHandlerConf { - enum class RegisterIndex : uint16_t { - SetPoint = 1, - Temperature = 2, - Power = 3, - AveragePower = 4, - Pressure = 5, - Constante1 = 10, - Constante2 = 11 - }; +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Variables"; +const std::string OUTPUT_VARNAME = "Measured"; +const std::string INPUT_VARNAME = "Demand"; - void setValueToOdb(int index, float value); - - public: - FeedbackHandler(std::string equipmentName); - - void enablePressureControlMode(bool enable); - - void setSP(float value); - void setTemperature(float value); - void setPower(float value); - void setAveragePower(float value); - void setPressure(float value); - void setConstante1(float value); - void setConstante2(float value); +enum class RegisterIndex { + PRESSURE_SET_POINT = 0, + SP = 1, + TEMPERATURE = 2, + POWER = 3, + AVERAGE_POWER = 4, + PRESSURE = 5, + MINIMUM_PRESSURE = 8, + MAXIMUM_PRESSURE = 9, + CONSTANTE_1 = 10, + CONSTANTE_2 = 11 }; +} // namespace FeedbackHandlerConf #endif \ No newline at end of file diff --git a/src/device/OutputHandler.cpp b/src/device/OutputHandler.cpp index 4efe0b0..e99f197 100644 --- a/src/device/OutputHandler.cpp +++ b/src/device/OutputHandler.cpp @@ -1,7 +1,70 @@ #include "OutputHandler.h" +#include "DemandHandler.h" +#include "OutputHandlerConfig.h" +#include "PressureCalculator.h" +#include "SettingsHandler.h" +#include void OutputHandler::enableSetPressure(bool enable) { isSetPressureEnable = enable; + update(); } -void OutputHandler::setPressure(float value) {} +void OutputHandler::setPressure(float value) { + pressure = value; + update(); +} + +void OutputHandler::setOutputPressureSPIndex(int index) { + outputPressureSPIndex = index; + update(); +} + +void OutputHandler::setEquipmentPath(std::string equipmentName) { + equipmentPath = OutputHandlerConfig::PATH_PREFIX + equipmentName + + OutputHandlerConfig::PATH_SUFFIX; + update(); +} + +void OutputHandler::update() { + if (isSetPressureEnable) { + midas::odb o(equipmentPath); + if (o.exists(OutputHandlerConfig::OUTPUT_VARIABLE)) { + o(OutputHandlerConfig::OUTPUT_VARIABLE)[outputPressureSPIndex] = + pressure; + } + } +} + +OutputHandler::OutputHandler(EventBus &eventBusReference) { + + eventBusReference.subscribe( + [this](const DemandHandler::Event &e) { + switch (e.type) { + case DemandHandler::EventType::SET_PRESSURE: + enableSetPressure(std::get(e.value)); + break; + default: + break; + } + }); + + eventBusReference.subscribe( + [this](const SettingsHandler::Event &e) { + switch (e.type) { + case SettingsHandler::EventType::EQUIPMENT_NAME: + setEquipmentPath(std::get(e.value)); + break; + case SettingsHandler::EventType::OUTPUT_PRESSURE_SP_INDEX: + setOutputPressureSPIndex(std::get(e.value)); + break; + default: + break; + } + }); + + eventBusReference.subscribe( + [this](const PressureCalculator::Event &e) { + setPressure(e.cappedPressure); + }); +} diff --git a/src/device/OutputHandler.h b/src/device/OutputHandler.h index 6474b45..10ef142 100644 --- a/src/device/OutputHandler.h +++ b/src/device/OutputHandler.h @@ -1,11 +1,27 @@ +#ifndef OUTPUT_HANDLER_H +#define OUTPUT_HANDLER_H +#include "DemandHandler.h" +#include "EventBus.h" +#include "PressureCalculator.h" + +#include class OutputHandler { private: bool isSetPressureEnable; + std::string equipmentPath; float pressure; - - public: + int outputPressureSPIndex; void enableSetPressure(bool enable); void setPressure(float value); + void setOutputPressureSPIndex(int index); + void setEquipmentPath(std::string equipmentName); + + void update(); + + public: + OutputHandler(EventBus &eventBusReference); }; + +#endif diff --git a/src/device/PressureCalculator.cpp b/src/device/PressureCalculator.cpp index 5f2e05d..58cc47b 100644 --- a/src/device/PressureCalculator.cpp +++ b/src/device/PressureCalculator.cpp @@ -6,8 +6,8 @@ #include void PressureCalculator::update() { - - double c2contrib = average.getAverage() * cachedConstante2; + double averagePower = average.getAverage(); + double c2contrib = averagePower * cachedConstante2; double c1contrib = cachedConstante1 * (cachedTemperature - cachedSP - c2contrib); double uncapedPressure = cachedMinimalPressure + c1contrib; @@ -28,11 +28,13 @@ void PressureCalculator::update() { Event event = {}; + /// ERROR CHECK IF CONDITION IS ALREADY IN LEGACY CODE if (c2contrib != 0) { event.c1contrib = c1contrib; event.c2contrib = c2contrib; event.uncapedPressure = uncapedPressure; event.cappedPressure = cappedPressure; + event.averagePower = averagePower; } eventBus.publish(event); diff --git a/src/device/PressureCalculator.h b/src/device/PressureCalculator.h index 5387650..f1e1647 100644 --- a/src/device/PressureCalculator.h +++ b/src/device/PressureCalculator.h @@ -9,14 +9,7 @@ class PressureCalculator { Average average; EventBus &eventBus; - struct Event { - float c1contrib; - float c2contrib; - float uncapedPressure; - float cappedPressure; - }; - - float cachedSP; + float cachedSP; float cachedTemperature; float cachedPower; @@ -38,6 +31,13 @@ class PressureCalculator { public: PressureCalculator(EventBus &eventBusReference); + struct Event { + float c1contrib; + float c2contrib; + float uncapedPressure; + float cappedPressure; + float averagePower; + }; }; #endif \ No newline at end of file diff --git a/src/device/SettingsHandler.cpp b/src/device/SettingsHandler.cpp index aae5a54..4ee7480 100644 --- a/src/device/SettingsHandler.cpp +++ b/src/device/SettingsHandler.cpp @@ -73,6 +73,9 @@ bool SettingsHandler::init() { areKeysValids = false; } + if (!areKeysValids) + return false; + this->pullEquipmentName(); this->pullOutputPressureSPIndex(); this->pullInputSPIndex(); diff --git a/src/device/SettingsHandlerConfig.h b/src/device/SettingsHandlerConfig.h index dc2cb8a..47ea854 100644 --- a/src/device/SettingsHandlerConfig.h +++ b/src/device/SettingsHandlerConfig.h @@ -6,7 +6,7 @@ namespace SettingsHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Devices/MITCPRESSC/DD/"; +const std::string PATH_SUFFIX = "/Settings/Devices/MITCPRESSC/DD/"; const std::string EQUIPMENT_NAME = "MITC_equipment"; const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp index 4a29043..754dd5d 100644 --- a/src/device/itc_pressure_optimizer.cpp +++ b/src/device/itc_pressure_optimizer.cpp @@ -15,17 +15,14 @@ itcPressureOptimizer::itcPressureOptimizer(std::string equipmentName, : TMFeEquipment(equipmentName.c_str(), equipmentFilename), eventBus(), demandHandler(eventBus, equipmentName), settingsHandler(eventBus, equipmentName), inputHandler(eventBus), - pressureCalculator(eventBus) { + pressureCalculator(eventBus), feedbackHandler(eventBus, equipmentName), + outputHandler(eventBus) { fEqConfPeriodMilliSec = 1000; // refresh every seconds fEqConfLogHistory = 1; fEqConfReadOnlyWhenRunning = false; fEqConfWriteEventsToOdb = true; } -/*, feedbackHandler(), , -outputHandler(), pressureCalculator(), - settingsHandler()*/ - void itcPressureOptimizer::HandlePeriodic() {} TMFeResult diff --git a/src/device/itc_pressure_optimizer.h b/src/device/itc_pressure_optimizer.h index 5dabbe2..2bb339b 100644 --- a/src/device/itc_pressure_optimizer.h +++ b/src/device/itc_pressure_optimizer.h @@ -3,11 +3,11 @@ #include "DemandHandler.h" #include "EventBus.h" +#include "FeedbackHandler.h" #include "InputHandler.h" +#include "OutputHandler.h" #include "PressureCalculator.h" #include "SettingsHandler.h" -// #include "FeedbackHandler.h" -// #include "OutputHandler.h" #include "tmfe.h" #include @@ -41,8 +41,8 @@ class itcPressureOptimizer : public TMFeEquipment { SettingsHandler settingsHandler; InputHandler inputHandler; PressureCalculator pressureCalculator; - // FeedbackHandler feedbackHandler; - // OutputHandler outputHandler; + FeedbackHandler feedbackHandler; + OutputHandler outputHandler; }; #endif \ No newline at end of file -- 2.54.0 From 27a0a28fed972ebff3dc2becf5e74c59ce6be32b Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Wed, 22 Jul 2026 14:50:39 +0200 Subject: [PATCH 07/28] changing langauage --- src/device/Average.cpp | 63 +++++++++++++-------------- src/device/DemandHandler.cpp | 2 +- src/device/InputHandler.cpp | 13 ++++-- src/device/InputHandlerConfig.h | 4 +- src/device/OutputHandlerConfig.h | 13 ++++++ src/device/PressureCalculator.cpp | 16 ++++--- src/device/SettingsHandler.cpp | 22 +++++----- src/device/SettingsHandler.h | 4 +- src/device/SettingsHandlerConfig.h | 2 +- src/device/itc_pressure_optimizer.cpp | 1 + 10 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 src/device/OutputHandlerConfig.h diff --git a/src/device/Average.cpp b/src/device/Average.cpp index 9617bca..434d04e 100644 --- a/src/device/Average.cpp +++ b/src/device/Average.cpp @@ -1,7 +1,8 @@ -#include "Average.h" #include #include +#include "Average.h" + void Average::setWindowSize(double seconds) { windowSize = Duration(seconds); cleanupAndUpdate(Clock::now()); @@ -9,10 +10,12 @@ void Average::setWindowSize(double seconds) { void Average::addValue(double value) { TimePoint now = Clock::now(); - cleanupAndUpdate(now); - currentValue = value; + + // add a new value history.push_back({now, currentValue}); + + cleanupAndUpdate(now); } double Average::getAverage() { @@ -23,33 +26,32 @@ double Average::getAverage() { return currentValue; } - // If only one element, it's all the window - if (history.size() == 1) { - return history.front().value; - } - double totalWeightedValue = 0.0; double totalDurationSeconds = 0.0; - // Go throught the full historic - for (size_t i = 0; i < history.size() - 1; ++i) { - // How much time the value was present - Duration duration = history[i + 1].timestamp - history[i].timestamp; + TimePoint windowStart = + now - std::chrono::duration_cast(windowSize); - totalWeightedValue += history[i].value * duration.count(); - totalDurationSeconds += duration.count(); + for (size_t i = 0; i < history.size(); ++i) { + TimePoint segmentStart = (i == 0) ? windowStart : history[i].timestamp; + TimePoint segmentEnd = + (i < history.size() - 1) ? history[i + 1].timestamp : now; + + // remove time before the window + if (segmentEnd < windowStart) + continue; + if (segmentStart < windowStart) + segmentStart = windowStart; + + Duration duration = segmentEnd - segmentStart; + double seconds = duration.count(); + + if (seconds > 0.0) { + totalWeightedValue += history[i].value * seconds; + totalDurationSeconds += seconds; + } } - // add last segment : duration = now - timestamp - Duration lastDuration = now - history.back().timestamp; - totalWeightedValue += history.back().value * lastDuration.count(); - totalDurationSeconds += lastDuration.count(); - - /* - Safety to prevent dividing by 0 - This can occure if only one point is set and we compute the average too - fast - */ if (totalDurationSeconds <= 0.0) { return history.back().value; } @@ -58,14 +60,11 @@ double Average::getAverage() { } void Average::cleanupAndUpdate(TimePoint now) { - // Clean all element completly ouside the temporal window - while (history.size() > 1 && (now - history[1].timestamp) >= windowSize) { + TimePoint windowStart = + now - std::chrono::duration_cast(windowSize); + + // Remove all elements completely outside of the window + while (history.size() > 1 && history[1].timestamp <= windowStart) { history.pop_front(); } - - // If last element is partialy outside, get his timestamp back - if (!history.empty() && (now - history.front().timestamp) > windowSize) { - history.front().timestamp = - now - std::chrono::duration_cast(windowSize); - } } \ No newline at end of file diff --git a/src/device/DemandHandler.cpp b/src/device/DemandHandler.cpp index 2c4be25..572e99d 100644 --- a/src/device/DemandHandler.cpp +++ b/src/device/DemandHandler.cpp @@ -40,7 +40,7 @@ bool DemandHandler::init() { indexValid = false; } - if (indexValid) + if (!indexValid) return false; this->pullMinimalPressure(); diff --git a/src/device/InputHandler.cpp b/src/device/InputHandler.cpp index 21d91d9..a16b014 100644 --- a/src/device/InputHandler.cpp +++ b/src/device/InputHandler.cpp @@ -11,13 +11,20 @@ void InputHandler::updatePath(std::string name) { InputHandlerConfig::PATH_SUFFIX; } -void InputHandler::updateSPIndex(int index) { this->SPIndex = index; } +void InputHandler::updateSPIndex(int index) { + this->SPIndex = index; + pullSPValue(); +} void InputHandler::updateTemperatureIndex(int index) { this->temperatureIndex = index; + pullTemperatureValue(); } -void InputHandler::updatePowerIndex(int index) { this->powerIndex = index; } +void InputHandler::updatePowerIndex(int index) { + this->powerIndex = index; + pullPowerValue(); +} void InputHandler::pullSPValue() { midas::odb o(this->path); @@ -34,6 +41,7 @@ void InputHandler::pullTemperatureValue() { void InputHandler::pullPowerValue() { midas::odb o(this->path); float powerValue = o[powerIndex]; + printf("Sending power value %f\n", powerValue); Event event = {EventType::POWER_VALUE, powerValue}; eventBus.publish(event); } @@ -51,7 +59,6 @@ void InputHandler::updateHotlink() { hotlink->watch([&](midas::odb &arg) { int hotlinkIndex = arg.get_last_index(); if (hotlinkIndex == SPIndex) { - pullSPValue(); } else if (hotlinkIndex == temperatureIndex) { pullTemperatureValue(); } else if (hotlinkIndex == powerIndex) { diff --git a/src/device/InputHandlerConfig.h b/src/device/InputHandlerConfig.h index 258905e..d007c5b 100644 --- a/src/device/InputHandlerConfig.h +++ b/src/device/InputHandlerConfig.h @@ -6,9 +6,7 @@ namespace InputHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Variables"; - -const std::string INPUT_VARIABLE = "Input"; +const std::string PATH_SUFFIX = "/Variables/Input"; } // namespace InputHandlerConfig #endif \ No newline at end of file diff --git a/src/device/OutputHandlerConfig.h b/src/device/OutputHandlerConfig.h new file mode 100644 index 0000000..46a6087 --- /dev/null +++ b/src/device/OutputHandlerConfig.h @@ -0,0 +1,13 @@ +#ifndef OUTPUT_HANDLER_CONFIG_H +#define OUTPUT_HANDLER_CONFIG_H + +#include + +namespace OutputHandlerConfig { +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Variables"; + +const std::string OUTPUT_VARIABLE = "Output"; +} // namespace OutputHandlerConfig + +#endif \ No newline at end of file diff --git a/src/device/PressureCalculator.cpp b/src/device/PressureCalculator.cpp index 58cc47b..a7f1ab6 100644 --- a/src/device/PressureCalculator.cpp +++ b/src/device/PressureCalculator.cpp @@ -29,13 +29,14 @@ void PressureCalculator::update() { Event event = {}; /// ERROR CHECK IF CONDITION IS ALREADY IN LEGACY CODE - if (c2contrib != 0) { - event.c1contrib = c1contrib; - event.c2contrib = c2contrib; - event.uncapedPressure = uncapedPressure; - event.cappedPressure = cappedPressure; - event.averagePower = averagePower; - } + + printf("capped %f et uncapped %f\n", cappedPressure, uncapedPressure); + + event.c1contrib = c1contrib; + event.c2contrib = c2contrib; + event.uncapedPressure = uncapedPressure; + event.cappedPressure = cappedPressure; + event.averagePower = averagePower; eventBus.publish(event); } @@ -97,6 +98,7 @@ void PressureCalculator::updateTemperature(float value) { void PressureCalculator::updatePower(float value) { cachedPower = value; + printf("%f recieved\n", value); average.addValue(static_cast(value)); update(); } diff --git a/src/device/SettingsHandler.cpp b/src/device/SettingsHandler.cpp index 4ee7480..69a40e0 100644 --- a/src/device/SettingsHandler.cpp +++ b/src/device/SettingsHandler.cpp @@ -7,7 +7,7 @@ using namespace SettingsHandlerConfig; void SettingsHandler::pullEquipmentName() { midas::odb o(this->path); - std::string equipmentName = o(SettingsHandlerConfig::EQUIPMENT_NAME); + std::string equipmentName = o[SettingsHandlerConfig::EQUIPMENT_NAME]; Event event = {EventType::EQUIPMENT_NAME, equipmentName}; eventBus.publish(event); } @@ -15,28 +15,28 @@ void SettingsHandler::pullEquipmentName() { void SettingsHandler::pullOutputPressureSPIndex() { midas::odb o(this->path); int outputPressureSPIndex = - o(SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX); + o[SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX]; Event event = {EventType::OUTPUT_PRESSURE_SP_INDEX, outputPressureSPIndex}; eventBus.publish(event); } void SettingsHandler::pullInputSPIndex() { midas::odb o(this->path); - int inputSPIndex = o(SettingsHandlerConfig::INPUT_SP_INDEX); + int inputSPIndex = o[SettingsHandlerConfig::INPUT_SP_INDEX]; Event event = {EventType::INPUT_SP_INDEX, inputSPIndex}; eventBus.publish(event); } void SettingsHandler::pullInputTemperatureIndex() { midas::odb o(this->path); - int temperatureIndex = o(SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX); + int temperatureIndex = o[SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX]; Event event = {EventType::INPUT_TEMPERATURE_INDEX, temperatureIndex}; eventBus.publish(event); } void SettingsHandler::pullInputPowerIndex() { midas::odb o(this->path); - int powerIndex = o(SettingsHandlerConfig::INPUT_POWER_INDEX); + int powerIndex = o[SettingsHandlerConfig::INPUT_POWER_INDEX]; Event event = {EventType::INPUT_POWER_INDEX, powerIndex}; eventBus.publish(event); } @@ -57,19 +57,21 @@ bool SettingsHandler::init() { bool areKeysValids = true; - if (!o.exists(SettingsHandlerConfig::EQUIPMENT_NAME)) { + if (!midas::odb::exists(path + SettingsHandlerConfig::EQUIPMENT_NAME)) { areKeysValids = false; } - if (!o.exists(SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX)) { + if (!midas::odb::exists(path + + SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX)) { areKeysValids = false; } - if (!o.exists(SettingsHandlerConfig::INPUT_SP_INDEX)) { + if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_SP_INDEX)) { areKeysValids = false; } - if (!o.exists(SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX)) { + if (!midas::odb::exists(path + + SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX)) { areKeysValids = false; } - if (!o.exists(SettingsHandlerConfig::INPUT_POWER_INDEX)) { + if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_POWER_INDEX)) { areKeysValids = false; } diff --git a/src/device/SettingsHandler.h b/src/device/SettingsHandler.h index cc51847..11e99a5 100644 --- a/src/device/SettingsHandler.h +++ b/src/device/SettingsHandler.h @@ -10,8 +10,6 @@ class SettingsHandler { public: SettingsHandler(EventBus &eventBusReference, std::string equipmentName); - bool init(); - bool setHotlink(); enum class EventType { EQUIPMENT_NAME, @@ -30,6 +28,8 @@ class SettingsHandler { std::string path; EventBus &eventBus; + bool init(); + void pullEquipmentName(); void pullOutputPressureSPIndex(); void pullInputSPIndex(); diff --git a/src/device/SettingsHandlerConfig.h b/src/device/SettingsHandlerConfig.h index 47ea854..4fec93a 100644 --- a/src/device/SettingsHandlerConfig.h +++ b/src/device/SettingsHandlerConfig.h @@ -8,7 +8,7 @@ namespace SettingsHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; const std::string PATH_SUFFIX = "/Settings/Devices/MITCPRESSC/DD/"; -const std::string EQUIPMENT_NAME = "MITC_equipment"; +const std::string EQUIPMENT_NAME = "MITC_Equipment"; const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; const std::string INPUT_SP_INDEX = "MITC_InputVarioxSPIndex"; const std::string INPUT_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp index 754dd5d..821718e 100644 --- a/src/device/itc_pressure_optimizer.cpp +++ b/src/device/itc_pressure_optimizer.cpp @@ -28,5 +28,6 @@ void itcPressureOptimizer::HandlePeriodic() {} TMFeResult itcPressureOptimizer::HandleInit(const std::vector &args) { eventBus.publish(EquipmentInitEvent{}); + printf("EventInit\n"); return TMFeResult(); } -- 2.54.0 From 42336a509990fe5693b9d81e15a8342191b61b2d Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Wed, 22 Jul 2026 15:24:08 +0200 Subject: [PATCH 08/28] adding polled update to take into account the continuous average --- src/device/PressureCalculator.cpp | 5 ----- src/device/itc_pressure_optimizer.cpp | 5 +++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/device/PressureCalculator.cpp b/src/device/PressureCalculator.cpp index a7f1ab6..8ab8b91 100644 --- a/src/device/PressureCalculator.cpp +++ b/src/device/PressureCalculator.cpp @@ -28,10 +28,6 @@ void PressureCalculator::update() { Event event = {}; - /// ERROR CHECK IF CONDITION IS ALREADY IN LEGACY CODE - - printf("capped %f et uncapped %f\n", cappedPressure, uncapedPressure); - event.c1contrib = c1contrib; event.c2contrib = c2contrib; event.uncapedPressure = uncapedPressure; @@ -98,7 +94,6 @@ void PressureCalculator::updateTemperature(float value) { void PressureCalculator::updatePower(float value) { cachedPower = value; - printf("%f recieved\n", value); average.addValue(static_cast(value)); update(); } diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp index 821718e..9368f81 100644 --- a/src/device/itc_pressure_optimizer.cpp +++ b/src/device/itc_pressure_optimizer.cpp @@ -23,11 +23,12 @@ itcPressureOptimizer::itcPressureOptimizer(std::string equipmentName, fEqConfWriteEventsToOdb = true; } -void itcPressureOptimizer::HandlePeriodic() {} +void itcPressureOptimizer::HandlePeriodic() { + eventBus.publish(EquipmentPollEvent{}); +} TMFeResult itcPressureOptimizer::HandleInit(const std::vector &args) { eventBus.publish(EquipmentInitEvent{}); - printf("EventInit\n"); return TMFeResult(); } -- 2.54.0 From bc29541990627780b30dada13a60e136b78f1041 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 10:30:56 +0200 Subject: [PATCH 09/28] Cmake finished --- CMakeLists.txt | 43 ++------ doc/graph.puml | 112 +++++-------------- out/doc/graph/ITCPressureOptimizer.png | Bin 75865 -> 0 bytes src/device/Average.cpp | 70 ------------ src/device/Average.h | 33 ------ src/device/DemandHandler.cpp | 140 ------------------------ src/device/DemandHandler.h | 48 -------- src/device/DemandHandlerConfig.h | 22 ---- src/device/FeedbackHandler.cpp | 145 ------------------------- src/device/FeedbackHandler.h | 34 ------ src/device/FeedbackHandlerConfig.h | 27 ----- src/device/InputHandler.cpp | 92 ---------------- src/device/InputHandler.h | 44 -------- src/device/InputHandlerConfig.h | 12 -- src/device/OutputHandler.cpp | 70 ------------ src/device/OutputHandler.h | 27 ----- src/device/OutputHandlerConfig.h | 13 --- src/device/PressureCalculator.cpp | 119 -------------------- src/device/PressureCalculator.h | 43 -------- src/device/SettingsHandler.cpp | 87 --------------- src/device/SettingsHandler.h | 40 ------- src/device/SettingsHandlerConfig.h | 19 ---- src/device/itc_pressure_optimizer.cpp | 8 -- src/device/itc_pressure_optimizer.h | 12 +- 24 files changed, 39 insertions(+), 1221 deletions(-) delete mode 100644 out/doc/graph/ITCPressureOptimizer.png delete mode 100644 src/device/Average.cpp delete mode 100644 src/device/Average.h delete mode 100644 src/device/DemandHandler.cpp delete mode 100644 src/device/DemandHandler.h delete mode 100644 src/device/DemandHandlerConfig.h delete mode 100644 src/device/FeedbackHandler.cpp delete mode 100644 src/device/FeedbackHandler.h delete mode 100644 src/device/FeedbackHandlerConfig.h delete mode 100644 src/device/InputHandler.cpp delete mode 100644 src/device/InputHandler.h delete mode 100644 src/device/InputHandlerConfig.h delete mode 100644 src/device/OutputHandler.cpp delete mode 100644 src/device/OutputHandler.h delete mode 100644 src/device/OutputHandlerConfig.h delete mode 100644 src/device/PressureCalculator.cpp delete mode 100644 src/device/PressureCalculator.h delete mode 100644 src/device/SettingsHandler.cpp delete mode 100644 src/device/SettingsHandler.h delete mode 100644 src/device/SettingsHandlerConfig.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 160025a..52be0fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,13 +7,11 @@ add_compile_options( -Wformat=2 -g -O0 - -Wno-format-nonliteral - -Wno-strict-aliasing -Wuninitialized -Wno-unused-function ) -# Check if the required environment variables MIDASSYS and EPICSSYS are available +# Check if the required environment variables MIDASSYS is available if (NOT DEFINED ENV{MIDASSYS}) message(SEND_ERROR "MIDASSYS environment variable not defined.") endif() @@ -30,25 +28,13 @@ find_package(Midas REQUIRED) add_library( itcPressureOptimizer src/device/itc_pressure_optimizer.cpp - src/device/DemandHandler.cpp - src/device/InputHandler.cpp - src/device/SettingsHandler.cpp - src/device/PressureCalculator.cpp - src/device/FeedbackHandler.cpp - src/device/OutputHandler.cpp - src/device/Average.cpp -) -target_compile_options( - itcPressureOptimizer - PRIVATE - -fsanitize=address - -fno-omit-frame-pointer -) - -target_link_options( - itcPressureOptimizer - PRIVATE - -fsanitize=address + src/device/handlers/DemandHandler.cpp + src/device/handlers/InputHandler.cpp + src/device/handlers/SettingsHandler.cpp + src/device/handlers/PressureCalculator.cpp + src/device/handlers/FeedbackHandler.cpp + src/device/handlers/OutputHandler.cpp + src/device/handlers/Average.cpp ) set_property( @@ -86,19 +72,6 @@ set_property( CXX_STANDARD 17 ) -target_compile_options( - itcPressureOptimizerScfe - PRIVATE - -fsanitize=address - -fno-omit-frame-pointer -) - -target_link_options( - itcPressureOptimizerScfe - PRIVATE - -fsanitize=address -) - target_include_directories( itcPressureOptimizerScfe PUBLIC diff --git a/doc/graph.puml b/doc/graph.puml index b9ca023..c90dae7 100644 --- a/doc/graph.puml +++ b/doc/graph.puml @@ -1,96 +1,34 @@ -@startuml ITCPressureOptimizer - -class InputHandler { - equipmentName : string - spindex : int - tempindex : int - powIndex : int - - setEquipmentName(name : string) - setSPIndex(index : int) - setTemperatureIndex(index : int) - setPowerIndex(index : int) +@startuml +skinparam linetype ortho +scale 1.2 +class EventBus <> { + + publish() + + subscribe() } -class SettingsHandler { - +class Demand +class itc +class Settings +class Input +class Pressure { + class Average } +class Output +class Feedback -class PressureCalculator { - # From InputHandler - cachedSP : float - cachedTemperature : float - cachedPower : float +Demand -.> Pressure +Demand -.> Feedback - # From DemandHandler - cachedMinimalPressure : float - cachedMaximalPressure : float - cachedConstante1 : float - cachedConstante2 : float - - trigger() : void - - updateSP(value : float) - updateTemperature(value : float) - updatePower(value : float) - updateMinimalPressure(value : float) - updateMaximalPressure(value : float) - updateConstante1(value : float) - updateConstante2(value : float) -} - -class Average { - addValue(float : value) - getAverage() : float -} - -class DemandHandler { - -} - -class FeedbackHandler { - bool isPressureControlModeEnable - enablePressureControlMode(enable : bool) - - setSP(value : float) - setTemperature(value : float) - setPower(value : float) - setAveragePower(value : float) - setPressure(value : float) - setConstante1(value : float) - setConstante2(value : float) -} - -class OutputHandler { - bool isSetPressureEnable - float pressure - int outputPressureSPIndex; - - updateOutputPressureSPIndex(index : int) - enableSetPressure(enable : bool) - setPressure(value : float) -} - -circle ODB_Settings -circle ODB_Equipment_Input -circle ODB_Demand - -ODB_Settings ..> SettingsHandler : Hotlink -ODB_Equipment_Input ..> InputHandler : Hotlink -ODB_Demand ..> DemandHandler : Hotlink - - -SettingsHandler --> InputHandler -InputHandler --> PressureCalculator -DemandHandler --> PressureCalculator - -PressureCalculator --> Average -PressureCalculator --> OutputHandler - -InputHandler --> FeedbackHandler -DemandHandler --> FeedbackHandler -SettingsHandler --> OutputHandler +itc -.> Settings +itc -.> Pressure +itc -.> Demand +Settings -.> Output +Settings -.> Input +Input -.> Feedback +Input -.> Output +Input -.> Pressure +Pressure -.> Feedback @enduml \ No newline at end of file diff --git a/out/doc/graph/ITCPressureOptimizer.png b/out/doc/graph/ITCPressureOptimizer.png deleted file mode 100644 index f724bb06499076201e8e2f13cad9c3a029223d3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75865 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

      !Yz)4rj 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 Li!E0TcOL&gmH)II diff --git a/src/device/Average.cpp b/src/device/Average.cpp deleted file mode 100644 index 434d04e..0000000 --- a/src/device/Average.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include - -#include "Average.h" - -void Average::setWindowSize(double seconds) { - windowSize = Duration(seconds); - cleanupAndUpdate(Clock::now()); -} - -void Average::addValue(double value) { - TimePoint now = Clock::now(); - currentValue = value; - - // add a new value - history.push_back({now, currentValue}); - - cleanupAndUpdate(now); -} - -double Average::getAverage() { - TimePoint now = Clock::now(); - cleanupAndUpdate(now); - - if (history.empty()) { - return currentValue; - } - - double totalWeightedValue = 0.0; - double totalDurationSeconds = 0.0; - - TimePoint windowStart = - now - std::chrono::duration_cast(windowSize); - - for (size_t i = 0; i < history.size(); ++i) { - TimePoint segmentStart = (i == 0) ? windowStart : history[i].timestamp; - TimePoint segmentEnd = - (i < history.size() - 1) ? history[i + 1].timestamp : now; - - // remove time before the window - if (segmentEnd < windowStart) - continue; - if (segmentStart < windowStart) - segmentStart = windowStart; - - Duration duration = segmentEnd - segmentStart; - double seconds = duration.count(); - - if (seconds > 0.0) { - totalWeightedValue += history[i].value * seconds; - totalDurationSeconds += seconds; - } - } - - if (totalDurationSeconds <= 0.0) { - return history.back().value; - } - - return totalWeightedValue / totalDurationSeconds; -} - -void Average::cleanupAndUpdate(TimePoint now) { - TimePoint windowStart = - now - std::chrono::duration_cast(windowSize); - - // Remove all elements completely outside of the window - while (history.size() > 1 && history[1].timestamp <= windowStart) { - history.pop_front(); - } -} \ No newline at end of file diff --git a/src/device/Average.h b/src/device/Average.h deleted file mode 100644 index 785b42e..0000000 --- a/src/device/Average.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef AVERAGE_H -#define AVERAGE_H - -#include -#include - -class Average { - private: - using Clock = std::chrono::steady_clock; - using TimePoint = Clock::time_point; - using Duration = std::chrono::duration; - - struct TimeValue { - TimePoint timestamp; - double value; - }; - - std::deque history; - Duration windowSize; - double currentValue = 0.0; - - void cleanupAndUpdate(TimePoint now); - - public: - Average(double windowSizeSeconds) - : windowSize(Duration(windowSizeSeconds)) {}; - - void setWindowSize(double seconds); - void addValue(double value); - double getAverage(); -}; - -#endif \ No newline at end of file diff --git a/src/device/DemandHandler.cpp b/src/device/DemandHandler.cpp deleted file mode 100644 index 572e99d..0000000 --- a/src/device/DemandHandler.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "DemandHandler.h" -#include "DemandHandlerConfig.h" -#include "itc_pressure_optimizer.h" -#include -#include - -#include "odbxx.h" -#include "tmfe.h" - -using namespace DemanHandlerConfig; - -bool DemandHandler::init() { - - if (!midas::odb::exists(this->path)) { - printf("[DEMANDE HANDLER] : Path doesn't exist\n"); - return false; - } - midas::odb o(this->path); - - bool indexValid = true; - - // This is checked every start, in case of someone change the index in the - // config header - if (o.size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { - indexValid = false; - } - if (o.size() < static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { - indexValid = false; - } - if (o.size() < static_cast(ParameterIndex::CONSTANTE_1)) { - indexValid = false; - } - if (o.size() < static_cast(ParameterIndex::CONSTANTE_2)) { - indexValid = false; - } - if (o.size() < static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { - indexValid = false; - } - if (o.size() < static_cast(ParameterIndex::SET_PRESSURE)) { - indexValid = false; - } - - if (!indexValid) - return false; - - this->pullMinimalPressure(); - this->pullMaximalPressure(); - this->pullConstante1(); - this->pullConstante2(); - this->pullPressureControlMode(); - return true; -} - -bool DemandHandler::setHotlink() { - midas::odb to_watch(this->path); - to_watch.watch([&](midas::odb &arg) { - switch (arg.get_last_index()) { - case static_cast(ParameterIndex::MINIMAL_PRESSURE): - pullMinimalPressure(); - break; - case static_cast(ParameterIndex::MAXIMAL_PRESSURE): - pullMaximalPressure(); - break; - case static_cast(ParameterIndex::CONSTANTE_1): - pullConstante1(); - break; - case static_cast(ParameterIndex::CONSTANTE_2): - pullConstante2(); - break; - case static_cast(ParameterIndex::PRESSURE_CONTROL_MODE): - pullPressureControlMode(); - break; - case static_cast(ParameterIndex::SET_PRESSURE): - pullSetPressure(); - break; - default: - // Update out of scope, discarded - break; - } - }); - return true; -} - -void DemandHandler::pullMinimalPressure() { - midas::odb o(this->path); - float minimalPressure = - o[static_cast(ParameterIndex::MINIMAL_PRESSURE)]; - Event event = {EventType::MINIMAL_PRESSURE, minimalPressure}; - eventBus.publish(event); -} - -void DemandHandler::pullMaximalPressure() { - midas::odb o(this->path); - float maximalPressure = - o[static_cast(ParameterIndex::MAXIMAL_PRESSURE)]; - Event event = {EventType::MAXIMAL_PRESSURE, maximalPressure}; - eventBus.publish(event); -} - -void DemandHandler::pullConstante1() { - midas::odb o(this->path); - float constante1 = o[static_cast(ParameterIndex::CONSTANTE_1)]; - Event event = {EventType::CONSTANTE_1, constante1}; - eventBus.publish(event); -} - -void DemandHandler::pullConstante2() { - midas::odb o(this->path); - float constance2 = o[static_cast(ParameterIndex::CONSTANTE_2)]; - Event event = {EventType::CONSTANTE_2, constance2}; - eventBus.publish(event); -} - -void DemandHandler::pullPressureControlMode() { - midas::odb o(this->path); - float pressureControlMode = - o[static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)]; - Event event = {EventType::PRESSURE_CONTROL_MODE, pressureControlMode != 0}; - eventBus.publish(event); -} - -void DemandHandler::pullSetPressure() { - midas::odb o(this->path); - float setPressure = o[static_cast(ParameterIndex::SET_PRESSURE)]; - Event event = {EventType::SET_PRESSURE, setPressure != 0}; - eventBus.publish(event); -} - -DemandHandler::DemandHandler(EventBus &eventBusReference, - std::string equipmentName) - : path(DemanHandlerConfig::PATH_PREFIX + equipmentName + - DemanHandlerConfig::PATH_SUFFIX), - eventBus(eventBusReference) { - - eventBus.subscribe( - [this](const itcPressureOptimizer::EquipmentInitEvent &e) { - this->init(); - this->setHotlink(); - }); -} diff --git a/src/device/DemandHandler.h b/src/device/DemandHandler.h deleted file mode 100644 index 4054e72..0000000 --- a/src/device/DemandHandler.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef DEMAND_HANDLER_H -#define DEMAND_HANDLER_H - -#include "EventBus.h" -#include -#include - -class DemandHandler { - private: - std::string path; - EventBus &eventBus; - - void pullMinimalPressure(); - void pullMaximalPressure(); - void pullConstante1(); - void pullConstante2(); - void pullPressureControlMode(); - void pullSetPressure(); - - /* - @brief Manualy get all the value, and update owned objects - */ - bool init(); - /* - @brief Set all the hotlink, waiting for any update - @return true if succes, false if any error encountered - */ - bool setHotlink(); - - public: - enum class EventType { - MINIMAL_PRESSURE, - MAXIMAL_PRESSURE, - CONSTANTE_1, - CONSTANTE_2, - PRESSURE_CONTROL_MODE, - SET_PRESSURE - }; - - struct Event { - EventType type; - std::variant value; - }; - - DemandHandler(EventBus &eventBusReference, std::string equipmentName); -}; - -#endif \ No newline at end of file diff --git a/src/device/DemandHandlerConfig.h b/src/device/DemandHandlerConfig.h deleted file mode 100644 index 8223d23..0000000 --- a/src/device/DemandHandlerConfig.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef DEMAND_HANDLER_CONFIG_H -#define DEMAND_HANDLER_CONFIG_H - -#include - -namespace DemanHandlerConfig { -const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Variables/Demand"; - -enum class ParameterIndex { - - PRESSURE_CONTROL_MODE = 6, - SET_PRESSURE = 7, - MINIMAL_PRESSURE = 8, - MAXIMAL_PRESSURE = 9, - CONSTANTE_1 = 10, - CONSTANTE_2 = 11 -}; - -}; // namespace DemanHandlerConfig - -#endif diff --git a/src/device/FeedbackHandler.cpp b/src/device/FeedbackHandler.cpp deleted file mode 100644 index 991c0cf..0000000 --- a/src/device/FeedbackHandler.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "FeedbackHandler.h" -#include "DemandHandler.h" -#include "EventBus.h" -#include "FeedbackHandlerConfig.h" -#include "InputHandler.h" -#include "PressureCalculator.h" - -#include -#include - -FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, - std::string equipmentName) { - this->path = FeedbackHandlerConf::PATH_PREFIX + equipmentName + - FeedbackHandlerConf::PATH_SUFFIX; - - eventBusReference.subscribe( - [this](const PressureCalculator::Event &e) { - setConstante2(e.c2contrib); - setConstante1(e.c1contrib); - setPressureMinimum(e.uncapedPressure); - setPressureMaximum(e.uncapedPressure); - setPressure(e.cappedPressure); - setAveragePower(e.averagePower); - }); - eventBusReference.subscribe( - [this](const DemandHandler::Event &e) { - switch (e.type) { - case DemandHandler::EventType::PRESSURE_CONTROL_MODE: - enablePressureControlMode(std::get(e.value)); - break; - case DemandHandler::EventType::SET_PRESSURE: - enableSetPressure(std::get(e.value)); - break; - default: - break; - } - }); - eventBusReference.subscribe( - [this](const InputHandler::Event &e) { - switch (e.type) { - case InputHandler::EventType::SP_VALUE: - setSP(e.value); - break; - case InputHandler::EventType::TEMPERATURE_VALUE: - setTemperature(e.value); - break; - case InputHandler::EventType::POWER_VALUE: - setPower(e.value); - break; - default: - break; - } - }); -} - -void FeedbackHandler::enablePressureControlMode(bool enable) { - isPressureControlModeEnable = enable; - updatePressure(); -} - -void FeedbackHandler::enableSetPressure(bool enable) { - isSetPressureEnable = enable; - updatePressure(); -} - -void FeedbackHandler::setPressure(float value) { - cachedCappedPressure = value; - updatePressure(); -} - -void FeedbackHandler::setConstante1(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::CONSTANTE_1)] = - value; -} -void FeedbackHandler::setAveragePower(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::AVERAGE_POWER)] = - value; -} - -void FeedbackHandler::setConstante2(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::CONSTANTE_2)] = - value; -} -void FeedbackHandler::updatePressure() { - { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::PRESSURE)] = - cachedCappedPressure; - } - float cappedPressure = - std::roundf(cachedCappedPressure * 10.0f + 0.5f) / 10.0f; - // Pressure control mode is set to calculated? - if (isPressureControlModeEnable) { - midas::odb o(path); - o[FeedbackHandlerConf::INPUT_VARNAME][static_cast( - FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = - cappedPressure; - } - // update setpoint of Mercury ITC in ODB ? - if (isSetPressureEnable) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME][static_cast( - FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = - cappedPressure; - } -} - -void FeedbackHandler::setPressureMinimum(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::MINIMUM_PRESSURE)] = - value; -} -void FeedbackHandler::setPressureMaximum(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::MAXIMUM_PRESSURE)] = - value; -} - -void FeedbackHandler::setSP(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::SP)] = value; -} - -void FeedbackHandler::setTemperature(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::TEMPERATURE)] = - value; -} - -void FeedbackHandler::setPower(float value) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::POWER)] = value; -} diff --git a/src/device/FeedbackHandler.h b/src/device/FeedbackHandler.h deleted file mode 100644 index 05adda9..0000000 --- a/src/device/FeedbackHandler.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef FEEDBACK_HANDLER_H -#define FEEDBACK_HANDLER_H - -#include "EventBus.h" -#include - -class FeedbackHandler { - private: - bool isPressureControlModeEnable; - bool isSetPressureEnable; - float cachedCappedPressure; - - std::string path; - - void enablePressureControlMode(bool enable); - void enableSetPressure(bool enable); - - void setSP(float value); - void setTemperature(float value); - void setPressureMinimum(float value); - void setPressureMaximum(float value); - void setPower(float value); - void setAveragePower(float value); - void setPressure(float value); - void setConstante1(float value); - void setConstante2(float value); - - void updatePressure(); - - public: - FeedbackHandler(EventBus &eventBusReference, std::string equipmentName); -}; - -#endif \ No newline at end of file diff --git a/src/device/FeedbackHandlerConfig.h b/src/device/FeedbackHandlerConfig.h deleted file mode 100644 index 0b4c522..0000000 --- a/src/device/FeedbackHandlerConfig.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef FEEDBACK_HANDLER_CONFIG_H -#define FEEDBACK_HANDLER_CONFIG_H - -#include - -namespace FeedbackHandlerConf { - -const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Variables"; -const std::string OUTPUT_VARNAME = "Measured"; -const std::string INPUT_VARNAME = "Demand"; - -enum class RegisterIndex { - PRESSURE_SET_POINT = 0, - SP = 1, - TEMPERATURE = 2, - POWER = 3, - AVERAGE_POWER = 4, - PRESSURE = 5, - MINIMUM_PRESSURE = 8, - MAXIMUM_PRESSURE = 9, - CONSTANTE_1 = 10, - CONSTANTE_2 = 11 -}; -} // namespace FeedbackHandlerConf - -#endif \ No newline at end of file diff --git a/src/device/InputHandler.cpp b/src/device/InputHandler.cpp deleted file mode 100644 index a16b014..0000000 --- a/src/device/InputHandler.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "InputHandler.h" -#include "EventBus.h" -#include "InputHandlerConfig.h" -#include "SettingsHandler.h" - -#include -#include - -void InputHandler::updatePath(std::string name) { - this->path = InputHandlerConfig::PATH_PREFIX + name + - InputHandlerConfig::PATH_SUFFIX; -} - -void InputHandler::updateSPIndex(int index) { - this->SPIndex = index; - pullSPValue(); -} - -void InputHandler::updateTemperatureIndex(int index) { - this->temperatureIndex = index; - pullTemperatureValue(); -} - -void InputHandler::updatePowerIndex(int index) { - this->powerIndex = index; - pullPowerValue(); -} - -void InputHandler::pullSPValue() { - midas::odb o(this->path); - float SPValue = o[SPIndex]; - Event event = {EventType::SP_VALUE, SPValue}; - eventBus.publish(event); -} -void InputHandler::pullTemperatureValue() { - midas::odb o(this->path); - float temperatureValue = o[temperatureIndex]; - Event event = {EventType::TEMPERATURE_VALUE, temperatureValue}; - eventBus.publish(event); -} -void InputHandler::pullPowerValue() { - midas::odb o(this->path); - float powerValue = o[powerIndex]; - printf("Sending power value %f\n", powerValue); - Event event = {EventType::POWER_VALUE, powerValue}; - eventBus.publish(event); -} - -void InputHandler::updateHotlink() { - // TODO : Verified this code work - // This should change the target in case of a new name given - if (hotlink) { - hotlink->unwatch(); - hotlink.reset(); - } - - hotlink.emplace(path); - - hotlink->watch([&](midas::odb &arg) { - int hotlinkIndex = arg.get_last_index(); - if (hotlinkIndex == SPIndex) { - } else if (hotlinkIndex == temperatureIndex) { - pullTemperatureValue(); - } else if (hotlinkIndex == powerIndex) { - pullPowerValue(); - } - }); -} - -InputHandler::InputHandler(EventBus &eventBusReference) - : eventBus(eventBusReference) { - eventBus.subscribe( - [this](const SettingsHandler::Event &e) { - switch (e.type) { - case SettingsHandler::EventType::EQUIPMENT_NAME: - updatePath(std::get(e.value)); - updateHotlink(); - break; - case SettingsHandler::EventType::INPUT_SP_INDEX: - updateSPIndex(std::get(e.value)); - break; - case SettingsHandler::EventType::INPUT_TEMPERATURE_INDEX: - updateTemperatureIndex(std::get(e.value)); - break; - case SettingsHandler::EventType::INPUT_POWER_INDEX: - updatePowerIndex(std::get(e.value)); - break; - default: - break; - } - }); -} diff --git a/src/device/InputHandler.h b/src/device/InputHandler.h deleted file mode 100644 index 7076221..0000000 --- a/src/device/InputHandler.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef INPUT_HANDLER_H -#define INPUT_HANDLER_H - -#include "EventBus.h" -#include "odbxx.h" -#include -#include - -class InputHandler { - private: - EventBus &eventBus; - std::string path; - int SPIndex; - int temperatureIndex; - int powerIndex; - - std::optional hotlink; - - void updatePath(std::string name); - void updateSPIndex(int index); - void updateTemperatureIndex(int index); - void updatePowerIndex(int index); - - void updateHotlink(); - - void pullSPValue(); - void pullTemperatureValue(); - void pullPowerValue(); - - public: - InputHandler(EventBus &eventBusReference); - enum class EventType { - SP_VALUE, - TEMPERATURE_VALUE, - POWER_VALUE, - }; - - struct Event { - EventType type; - float value; - }; -}; - -#endif \ No newline at end of file diff --git a/src/device/InputHandlerConfig.h b/src/device/InputHandlerConfig.h deleted file mode 100644 index d007c5b..0000000 --- a/src/device/InputHandlerConfig.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef INPUT_HANDLER_CONFIG_H -#define INPUT_HANDLER_CONFIG_H - -#include - -namespace InputHandlerConfig { - -const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Variables/Input"; - -} // namespace InputHandlerConfig -#endif \ No newline at end of file diff --git a/src/device/OutputHandler.cpp b/src/device/OutputHandler.cpp deleted file mode 100644 index e99f197..0000000 --- a/src/device/OutputHandler.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "OutputHandler.h" -#include "DemandHandler.h" -#include "OutputHandlerConfig.h" -#include "PressureCalculator.h" -#include "SettingsHandler.h" -#include - -void OutputHandler::enableSetPressure(bool enable) { - isSetPressureEnable = enable; - update(); -} - -void OutputHandler::setPressure(float value) { - pressure = value; - update(); -} - -void OutputHandler::setOutputPressureSPIndex(int index) { - outputPressureSPIndex = index; - update(); -} - -void OutputHandler::setEquipmentPath(std::string equipmentName) { - equipmentPath = OutputHandlerConfig::PATH_PREFIX + equipmentName + - OutputHandlerConfig::PATH_SUFFIX; - update(); -} - -void OutputHandler::update() { - if (isSetPressureEnable) { - midas::odb o(equipmentPath); - if (o.exists(OutputHandlerConfig::OUTPUT_VARIABLE)) { - o(OutputHandlerConfig::OUTPUT_VARIABLE)[outputPressureSPIndex] = - pressure; - } - } -} - -OutputHandler::OutputHandler(EventBus &eventBusReference) { - - eventBusReference.subscribe( - [this](const DemandHandler::Event &e) { - switch (e.type) { - case DemandHandler::EventType::SET_PRESSURE: - enableSetPressure(std::get(e.value)); - break; - default: - break; - } - }); - - eventBusReference.subscribe( - [this](const SettingsHandler::Event &e) { - switch (e.type) { - case SettingsHandler::EventType::EQUIPMENT_NAME: - setEquipmentPath(std::get(e.value)); - break; - case SettingsHandler::EventType::OUTPUT_PRESSURE_SP_INDEX: - setOutputPressureSPIndex(std::get(e.value)); - break; - default: - break; - } - }); - - eventBusReference.subscribe( - [this](const PressureCalculator::Event &e) { - setPressure(e.cappedPressure); - }); -} diff --git a/src/device/OutputHandler.h b/src/device/OutputHandler.h deleted file mode 100644 index 10ef142..0000000 --- a/src/device/OutputHandler.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef OUTPUT_HANDLER_H -#define OUTPUT_HANDLER_H - -#include "DemandHandler.h" -#include "EventBus.h" -#include "PressureCalculator.h" - -#include - -class OutputHandler { - private: - bool isSetPressureEnable; - std::string equipmentPath; - float pressure; - int outputPressureSPIndex; - void enableSetPressure(bool enable); - void setPressure(float value); - void setOutputPressureSPIndex(int index); - void setEquipmentPath(std::string equipmentName); - - void update(); - - public: - OutputHandler(EventBus &eventBusReference); -}; - -#endif diff --git a/src/device/OutputHandlerConfig.h b/src/device/OutputHandlerConfig.h deleted file mode 100644 index 46a6087..0000000 --- a/src/device/OutputHandlerConfig.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef OUTPUT_HANDLER_CONFIG_H -#define OUTPUT_HANDLER_CONFIG_H - -#include - -namespace OutputHandlerConfig { -const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Variables"; - -const std::string OUTPUT_VARIABLE = "Output"; -} // namespace OutputHandlerConfig - -#endif \ No newline at end of file diff --git a/src/device/PressureCalculator.cpp b/src/device/PressureCalculator.cpp deleted file mode 100644 index 8ab8b91..0000000 --- a/src/device/PressureCalculator.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "PressureCalculator.h" -#include "Average.h" -#include "DemandHandler.h" -#include "InputHandler.h" -#include "itc_pressure_optimizer.h" -#include - -void PressureCalculator::update() { - double averagePower = average.getAverage(); - double c2contrib = averagePower * cachedConstante2; - double c1contrib = - cachedConstante1 * (cachedTemperature - cachedSP - c2contrib); - double uncapedPressure = cachedMinimalPressure + c1contrib; - - double cappedPressure = uncapedPressure; - - if (c2contrib != 0) { - c2contrib = -c2contrib * cachedConstante1; - } else { - c2contrib = 0.0f; - } - - if (cappedPressure < cachedMinimalPressure) - cappedPressure = cachedMinimalPressure; - - if (cappedPressure > cachedMaximalPressure) - cappedPressure = cachedMaximalPressure; - - Event event = {}; - - event.c1contrib = c1contrib; - event.c2contrib = c2contrib; - event.uncapedPressure = uncapedPressure; - event.cappedPressure = cappedPressure; - event.averagePower = averagePower; - - eventBus.publish(event); -} - -PressureCalculator::PressureCalculator(EventBus &eventBusReference) - : average(30), eventBus(eventBusReference) { - eventBus.subscribe( - [this](const DemandHandler::Event &e) { - switch (e.type) { - case DemandHandler::EventType::MINIMAL_PRESSURE: - updateMinimalPressure(std::get(e.value)); - break; - case DemandHandler::EventType::MAXIMAL_PRESSURE: - updateMaximalPressure(std::get(e.value)); - break; - case DemandHandler::EventType::CONSTANTE_1: - updateConstante1(std::get(e.value)); - break; - case DemandHandler::EventType::CONSTANTE_2: - updateConstante2(std::get(e.value)); - break; - default: - break; - } - }); - - eventBus.subscribe( - [this](const InputHandler::Event &e) { - switch (e.type) { - case InputHandler::EventType::SP_VALUE: - updateSP(e.value); - break; - case InputHandler::EventType::TEMPERATURE_VALUE: - updateTemperature(e.value); - break; - case InputHandler::EventType::POWER_VALUE: - updatePower(e.value); - break; - default: - break; - } - }); - - eventBus.subscribe( - [this](const itcPressureOptimizer::EquipmentPollEvent &e) { - update(); - }); -} - -void PressureCalculator::updateSP(float value) { - cachedSP = value; - update(); -} - -void PressureCalculator::updateTemperature(float value) { - cachedTemperature = value; - update(); -} - -void PressureCalculator::updatePower(float value) { - cachedPower = value; - average.addValue(static_cast(value)); - update(); -} - -void PressureCalculator::updateMinimalPressure(float value) { - cachedMinimalPressure = value; - update(); -} - -void PressureCalculator::updateMaximalPressure(float value) { - cachedMaximalPressure = value; - update(); -} - -void PressureCalculator::updateConstante1(float value) { - cachedConstante1 = value; - update(); -} - -void PressureCalculator::updateConstante2(float value) { - cachedConstante2 = value; - update(); -} diff --git a/src/device/PressureCalculator.h b/src/device/PressureCalculator.h deleted file mode 100644 index f1e1647..0000000 --- a/src/device/PressureCalculator.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef PRESSURE_CALCULATOR_H -#define PRESSURE_CALCULATOR_H - -#include "Average.h" -#include "EventBus.h" - -class PressureCalculator { - private: - Average average; - EventBus &eventBus; - - float cachedSP; - float cachedTemperature; - float cachedPower; - - float cachedMinimalPressure; - float cachedMaximalPressure; - float cachedConstante1; - float cachedConstante2; - - void updateSP(float value); - void updateTemperature(float value); - void updatePower(float value); - - void updateMinimalPressure(float value); - void updateMaximalPressure(float value); - void updateConstante1(float value); - void updateConstante2(float value); - - void update(); - - public: - PressureCalculator(EventBus &eventBusReference); - struct Event { - float c1contrib; - float c2contrib; - float uncapedPressure; - float cappedPressure; - float averagePower; - }; -}; - -#endif \ No newline at end of file diff --git a/src/device/SettingsHandler.cpp b/src/device/SettingsHandler.cpp deleted file mode 100644 index 69a40e0..0000000 --- a/src/device/SettingsHandler.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "SettingsHandler.h" -#include "SettingsHandlerConfig.h" -#include "itc_pressure_optimizer.h" -#include - -using namespace SettingsHandlerConfig; - -void SettingsHandler::pullEquipmentName() { - midas::odb o(this->path); - std::string equipmentName = o[SettingsHandlerConfig::EQUIPMENT_NAME]; - Event event = {EventType::EQUIPMENT_NAME, equipmentName}; - eventBus.publish(event); -} - -void SettingsHandler::pullOutputPressureSPIndex() { - midas::odb o(this->path); - int outputPressureSPIndex = - o[SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX]; - Event event = {EventType::OUTPUT_PRESSURE_SP_INDEX, outputPressureSPIndex}; - eventBus.publish(event); -} - -void SettingsHandler::pullInputSPIndex() { - midas::odb o(this->path); - int inputSPIndex = o[SettingsHandlerConfig::INPUT_SP_INDEX]; - Event event = {EventType::INPUT_SP_INDEX, inputSPIndex}; - eventBus.publish(event); -} - -void SettingsHandler::pullInputTemperatureIndex() { - midas::odb o(this->path); - int temperatureIndex = o[SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX]; - Event event = {EventType::INPUT_TEMPERATURE_INDEX, temperatureIndex}; - eventBus.publish(event); -} - -void SettingsHandler::pullInputPowerIndex() { - midas::odb o(this->path); - int powerIndex = o[SettingsHandlerConfig::INPUT_POWER_INDEX]; - Event event = {EventType::INPUT_POWER_INDEX, powerIndex}; - eventBus.publish(event); -} - -SettingsHandler::SettingsHandler(EventBus &eventBusReference, - std::string equipmentName) - : path(SettingsHandlerConfig::PATH_PREFIX + equipmentName + - SettingsHandlerConfig::PATH_SUFFIX), - eventBus(eventBusReference) { - eventBus.subscribe( - [this](const itcPressureOptimizer::EquipmentInitEvent &e) { - this->init(); - }); -} - -bool SettingsHandler::init() { - midas::odb o(this->path); - - bool areKeysValids = true; - - if (!midas::odb::exists(path + SettingsHandlerConfig::EQUIPMENT_NAME)) { - areKeysValids = false; - } - if (!midas::odb::exists(path + - SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX)) { - areKeysValids = false; - } - if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_SP_INDEX)) { - areKeysValids = false; - } - if (!midas::odb::exists(path + - SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX)) { - areKeysValids = false; - } - if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_POWER_INDEX)) { - areKeysValids = false; - } - - if (!areKeysValids) - return false; - - this->pullEquipmentName(); - this->pullOutputPressureSPIndex(); - this->pullInputSPIndex(); - this->pullInputTemperatureIndex(); - this->pullInputPowerIndex(); - return true; -} diff --git a/src/device/SettingsHandler.h b/src/device/SettingsHandler.h deleted file mode 100644 index 11e99a5..0000000 --- a/src/device/SettingsHandler.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef SETTINGS_HANDLER_H -#define SETTINGS_HANDLER_H - -#include "InputHandler.h" -#include -#include -#include - -class SettingsHandler { - - public: - SettingsHandler(EventBus &eventBusReference, std::string equipmentName); - - enum class EventType { - EQUIPMENT_NAME, - OUTPUT_PRESSURE_SP_INDEX, - INPUT_SP_INDEX, - INPUT_TEMPERATURE_INDEX, - INPUT_POWER_INDEX - }; - - struct Event { - EventType type; - std::variant value; - }; - - private: - std::string path; - EventBus &eventBus; - - bool init(); - - void pullEquipmentName(); - void pullOutputPressureSPIndex(); - void pullInputSPIndex(); - void pullInputTemperatureIndex(); - void pullInputPowerIndex(); -}; - -#endif \ No newline at end of file diff --git a/src/device/SettingsHandlerConfig.h b/src/device/SettingsHandlerConfig.h deleted file mode 100644 index 4fec93a..0000000 --- a/src/device/SettingsHandlerConfig.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef SETTINGS_HANDLER_CONFIG_H -#define SETTINGS_HANDLER_CONFIG_H - -#include - -namespace SettingsHandlerConfig { - -const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Settings/Devices/MITCPRESSC/DD/"; - -const std::string EQUIPMENT_NAME = "MITC_Equipment"; -const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; -const std::string INPUT_SP_INDEX = "MITC_InputVarioxSPIndex"; -const std::string INPUT_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; -const std::string INPUT_POWER_INDEX = "MITC_InputVarioxPowIndex"; - -} // namespace SettingsHandlerConfig - -#endif \ No newline at end of file diff --git a/src/device/itc_pressure_optimizer.cpp b/src/device/itc_pressure_optimizer.cpp index 9368f81..40f019f 100644 --- a/src/device/itc_pressure_optimizer.cpp +++ b/src/device/itc_pressure_optimizer.cpp @@ -1,13 +1,5 @@ #include "itc_pressure_optimizer.h" -#include "DemandHandler.h" -#include "EventBus.h" -// #include "FeedbackHandler.h" -// #include "InputHandler.h" -// #include "OutputHandler.h" -// #include "PressureCalculator.h" -// #include "SettingsHandler.h" - #include itcPressureOptimizer::itcPressureOptimizer(std::string equipmentName, diff --git a/src/device/itc_pressure_optimizer.h b/src/device/itc_pressure_optimizer.h index 2bb339b..9eca573 100644 --- a/src/device/itc_pressure_optimizer.h +++ b/src/device/itc_pressure_optimizer.h @@ -1,13 +1,13 @@ #ifndef ITC_PRESSURE_OPTIMIZER_H #define ITC_PRESSURE_OPTIMIZER_H -#include "DemandHandler.h" +#include "./handlers/DemandHandler.h" +#include "./handlers/FeedbackHandler.h" +#include "./handlers/InputHandler.h" +#include "./handlers/OutputHandler.h" +#include "./handlers/PressureCalculator.h" +#include "./handlers/SettingsHandler.h" #include "EventBus.h" -#include "FeedbackHandler.h" -#include "InputHandler.h" -#include "OutputHandler.h" -#include "PressureCalculator.h" -#include "SettingsHandler.h" #include "tmfe.h" #include -- 2.54.0 From c7826afe850849bea8bdbb5f20d94f6ad9d3bf51 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 10:31:19 +0200 Subject: [PATCH 10/28] adding all newly placed files --- out/doc/graph/graph.png | Bin 0 -> 24992 bytes src/device/handlers/Average.cpp | 70 ++++++++++ src/device/handlers/Average.h | 33 +++++ src/device/handlers/DemandHandler.cpp | 140 +++++++++++++++++++ src/device/handlers/DemandHandler.h | 48 +++++++ src/device/handlers/DemandHandlerConfig.h | 22 +++ src/device/handlers/FeedbackHandler.cpp | 145 ++++++++++++++++++++ src/device/handlers/FeedbackHandler.h | 34 +++++ src/device/handlers/FeedbackHandlerConfig.h | 27 ++++ src/device/handlers/InputHandler.cpp | 92 +++++++++++++ src/device/handlers/InputHandler.h | 44 ++++++ src/device/handlers/InputHandlerConfig.h | 12 ++ src/device/handlers/OutputHandler.cpp | 70 ++++++++++ src/device/handlers/OutputHandler.h | 27 ++++ src/device/handlers/OutputHandlerConfig.h | 13 ++ src/device/handlers/PressureCalculator.cpp | 119 ++++++++++++++++ src/device/handlers/PressureCalculator.h | 43 ++++++ src/device/handlers/SettingsHandler.cpp | 87 ++++++++++++ src/device/handlers/SettingsHandler.h | 40 ++++++ src/device/handlers/SettingsHandlerConfig.h | 19 +++ 20 files changed, 1085 insertions(+) create mode 100644 out/doc/graph/graph.png create mode 100644 src/device/handlers/Average.cpp create mode 100644 src/device/handlers/Average.h create mode 100644 src/device/handlers/DemandHandler.cpp create mode 100644 src/device/handlers/DemandHandler.h create mode 100644 src/device/handlers/DemandHandlerConfig.h create mode 100644 src/device/handlers/FeedbackHandler.cpp create mode 100644 src/device/handlers/FeedbackHandler.h create mode 100644 src/device/handlers/FeedbackHandlerConfig.h create mode 100644 src/device/handlers/InputHandler.cpp create mode 100644 src/device/handlers/InputHandler.h create mode 100644 src/device/handlers/InputHandlerConfig.h create mode 100644 src/device/handlers/OutputHandler.cpp create mode 100644 src/device/handlers/OutputHandler.h create mode 100644 src/device/handlers/OutputHandlerConfig.h create mode 100644 src/device/handlers/PressureCalculator.cpp create mode 100644 src/device/handlers/PressureCalculator.h create mode 100644 src/device/handlers/SettingsHandler.cpp create mode 100644 src/device/handlers/SettingsHandler.h create mode 100644 src/device/handlers/SettingsHandlerConfig.h diff --git a/out/doc/graph/graph.png b/out/doc/graph/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..f12381a8ac510c22b732dd807fa7d4e37521f363 GIT binary patch literal 24992 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 VlOr48ze=FHq@kz&R_&JW{{cF|N5cRB literal 0 HcmV?d00001 diff --git a/src/device/handlers/Average.cpp b/src/device/handlers/Average.cpp new file mode 100644 index 0000000..434d04e --- /dev/null +++ b/src/device/handlers/Average.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include "Average.h" + +void Average::setWindowSize(double seconds) { + windowSize = Duration(seconds); + cleanupAndUpdate(Clock::now()); +} + +void Average::addValue(double value) { + TimePoint now = Clock::now(); + currentValue = value; + + // add a new value + history.push_back({now, currentValue}); + + cleanupAndUpdate(now); +} + +double Average::getAverage() { + TimePoint now = Clock::now(); + cleanupAndUpdate(now); + + if (history.empty()) { + return currentValue; + } + + double totalWeightedValue = 0.0; + double totalDurationSeconds = 0.0; + + TimePoint windowStart = + now - std::chrono::duration_cast(windowSize); + + for (size_t i = 0; i < history.size(); ++i) { + TimePoint segmentStart = (i == 0) ? windowStart : history[i].timestamp; + TimePoint segmentEnd = + (i < history.size() - 1) ? history[i + 1].timestamp : now; + + // remove time before the window + if (segmentEnd < windowStart) + continue; + if (segmentStart < windowStart) + segmentStart = windowStart; + + Duration duration = segmentEnd - segmentStart; + double seconds = duration.count(); + + if (seconds > 0.0) { + totalWeightedValue += history[i].value * seconds; + totalDurationSeconds += seconds; + } + } + + if (totalDurationSeconds <= 0.0) { + return history.back().value; + } + + return totalWeightedValue / totalDurationSeconds; +} + +void Average::cleanupAndUpdate(TimePoint now) { + TimePoint windowStart = + now - std::chrono::duration_cast(windowSize); + + // Remove all elements completely outside of the window + while (history.size() > 1 && history[1].timestamp <= windowStart) { + history.pop_front(); + } +} \ No newline at end of file diff --git a/src/device/handlers/Average.h b/src/device/handlers/Average.h new file mode 100644 index 0000000..785b42e --- /dev/null +++ b/src/device/handlers/Average.h @@ -0,0 +1,33 @@ +#ifndef AVERAGE_H +#define AVERAGE_H + +#include +#include + +class Average { + private: + using Clock = std::chrono::steady_clock; + using TimePoint = Clock::time_point; + using Duration = std::chrono::duration; + + struct TimeValue { + TimePoint timestamp; + double value; + }; + + std::deque history; + Duration windowSize; + double currentValue = 0.0; + + void cleanupAndUpdate(TimePoint now); + + public: + Average(double windowSizeSeconds) + : windowSize(Duration(windowSizeSeconds)) {}; + + void setWindowSize(double seconds); + void addValue(double value); + double getAverage(); +}; + +#endif \ No newline at end of file diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp new file mode 100644 index 0000000..725d2ec --- /dev/null +++ b/src/device/handlers/DemandHandler.cpp @@ -0,0 +1,140 @@ +#include "DemandHandler.h" +#include "../itc_pressure_optimizer.h" +#include "DemandHandlerConfig.h" +#include +#include + +#include "odbxx.h" +#include "tmfe.h" + +using namespace DemanHandlerConfig; + +bool DemandHandler::init() { + + if (!midas::odb::exists(this->path)) { + printf("[DEMANDE HANDLER] : Path doesn't exist\n"); + return false; + } + midas::odb o(this->path); + + bool indexValid = true; + + // This is checked every start, in case of someone change the index in the + // config header + if (o.size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { + indexValid = false; + } + if (o.size() < static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { + indexValid = false; + } + if (o.size() < static_cast(ParameterIndex::CONSTANTE_1)) { + indexValid = false; + } + if (o.size() < static_cast(ParameterIndex::CONSTANTE_2)) { + indexValid = false; + } + if (o.size() < static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { + indexValid = false; + } + if (o.size() < static_cast(ParameterIndex::SET_PRESSURE)) { + indexValid = false; + } + + if (!indexValid) + return false; + + this->pullMinimalPressure(); + this->pullMaximalPressure(); + this->pullConstante1(); + this->pullConstante2(); + this->pullPressureControlMode(); + return true; +} + +bool DemandHandler::setHotlink() { + midas::odb to_watch(this->path); + to_watch.watch([&](midas::odb &arg) { + switch (arg.get_last_index()) { + case static_cast(ParameterIndex::MINIMAL_PRESSURE): + pullMinimalPressure(); + break; + case static_cast(ParameterIndex::MAXIMAL_PRESSURE): + pullMaximalPressure(); + break; + case static_cast(ParameterIndex::CONSTANTE_1): + pullConstante1(); + break; + case static_cast(ParameterIndex::CONSTANTE_2): + pullConstante2(); + break; + case static_cast(ParameterIndex::PRESSURE_CONTROL_MODE): + pullPressureControlMode(); + break; + case static_cast(ParameterIndex::SET_PRESSURE): + pullSetPressure(); + break; + default: + // Update out of scope, discarded + break; + } + }); + return true; +} + +void DemandHandler::pullMinimalPressure() { + midas::odb o(this->path); + float minimalPressure = + o[static_cast(ParameterIndex::MINIMAL_PRESSURE)]; + Event event = {EventType::MINIMAL_PRESSURE, minimalPressure}; + eventBus.publish(event); +} + +void DemandHandler::pullMaximalPressure() { + midas::odb o(this->path); + float maximalPressure = + o[static_cast(ParameterIndex::MAXIMAL_PRESSURE)]; + Event event = {EventType::MAXIMAL_PRESSURE, maximalPressure}; + eventBus.publish(event); +} + +void DemandHandler::pullConstante1() { + midas::odb o(this->path); + float constante1 = o[static_cast(ParameterIndex::CONSTANTE_1)]; + Event event = {EventType::CONSTANTE_1, constante1}; + eventBus.publish(event); +} + +void DemandHandler::pullConstante2() { + midas::odb o(this->path); + float constance2 = o[static_cast(ParameterIndex::CONSTANTE_2)]; + Event event = {EventType::CONSTANTE_2, constance2}; + eventBus.publish(event); +} + +void DemandHandler::pullPressureControlMode() { + midas::odb o(this->path); + float pressureControlMode = + o[static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)]; + Event event = {EventType::PRESSURE_CONTROL_MODE, pressureControlMode != 0}; + eventBus.publish(event); +} + +void DemandHandler::pullSetPressure() { + midas::odb o(this->path); + float setPressure = o[static_cast(ParameterIndex::SET_PRESSURE)]; + Event event = {EventType::SET_PRESSURE, setPressure != 0}; + eventBus.publish(event); +} + +DemandHandler::DemandHandler(EventBus &eventBusReference, + std::string equipmentName) + : path(DemanHandlerConfig::PATH_PREFIX + equipmentName + + DemanHandlerConfig::PATH_SUFFIX), + eventBus(eventBusReference) { + + eventBus.subscribe( + [this](const itcPressureOptimizer::EquipmentInitEvent &e) { + this->init(); + this->setHotlink(); + }); +} diff --git a/src/device/handlers/DemandHandler.h b/src/device/handlers/DemandHandler.h new file mode 100644 index 0000000..956c263 --- /dev/null +++ b/src/device/handlers/DemandHandler.h @@ -0,0 +1,48 @@ +#ifndef DEMAND_HANDLER_H +#define DEMAND_HANDLER_H + +#include "../EventBus.h" +#include +#include + +class DemandHandler { + private: + std::string path; + EventBus &eventBus; + + void pullMinimalPressure(); + void pullMaximalPressure(); + void pullConstante1(); + void pullConstante2(); + void pullPressureControlMode(); + void pullSetPressure(); + + /* + @brief Manualy get all the value, and update owned objects + */ + bool init(); + /* + @brief Set all the hotlink, waiting for any update + @return true if succes, false if any error encountered + */ + bool setHotlink(); + + public: + enum class EventType { + MINIMAL_PRESSURE, + MAXIMAL_PRESSURE, + CONSTANTE_1, + CONSTANTE_2, + PRESSURE_CONTROL_MODE, + SET_PRESSURE + }; + + struct Event { + EventType type; + std::variant value; + }; + + DemandHandler(EventBus &eventBusReference, std::string equipmentName); +}; + +#endif \ No newline at end of file diff --git a/src/device/handlers/DemandHandlerConfig.h b/src/device/handlers/DemandHandlerConfig.h new file mode 100644 index 0000000..8223d23 --- /dev/null +++ b/src/device/handlers/DemandHandlerConfig.h @@ -0,0 +1,22 @@ +#ifndef DEMAND_HANDLER_CONFIG_H +#define DEMAND_HANDLER_CONFIG_H + +#include + +namespace DemanHandlerConfig { +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Variables/Demand"; + +enum class ParameterIndex { + + PRESSURE_CONTROL_MODE = 6, + SET_PRESSURE = 7, + MINIMAL_PRESSURE = 8, + MAXIMAL_PRESSURE = 9, + CONSTANTE_1 = 10, + CONSTANTE_2 = 11 +}; + +}; // namespace DemanHandlerConfig + +#endif diff --git a/src/device/handlers/FeedbackHandler.cpp b/src/device/handlers/FeedbackHandler.cpp new file mode 100644 index 0000000..46f416c --- /dev/null +++ b/src/device/handlers/FeedbackHandler.cpp @@ -0,0 +1,145 @@ +#include "FeedbackHandler.h" +#include "../EventBus.h" +#include "DemandHandler.h" +#include "FeedbackHandlerConfig.h" +#include "InputHandler.h" +#include "PressureCalculator.h" + +#include +#include + +FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, + std::string equipmentName) { + this->path = FeedbackHandlerConf::PATH_PREFIX + equipmentName + + FeedbackHandlerConf::PATH_SUFFIX; + + eventBusReference.subscribe( + [this](const PressureCalculator::Event &e) { + setConstante2(e.c2contrib); + setConstante1(e.c1contrib); + setPressureMinimum(e.uncapedPressure); + setPressureMaximum(e.uncapedPressure); + setPressure(e.cappedPressure); + setAveragePower(e.averagePower); + }); + eventBusReference.subscribe( + [this](const DemandHandler::Event &e) { + switch (e.type) { + case DemandHandler::EventType::PRESSURE_CONTROL_MODE: + enablePressureControlMode(std::get(e.value)); + break; + case DemandHandler::EventType::SET_PRESSURE: + enableSetPressure(std::get(e.value)); + break; + default: + break; + } + }); + eventBusReference.subscribe( + [this](const InputHandler::Event &e) { + switch (e.type) { + case InputHandler::EventType::SP_VALUE: + setSP(e.value); + break; + case InputHandler::EventType::TEMPERATURE_VALUE: + setTemperature(e.value); + break; + case InputHandler::EventType::POWER_VALUE: + setPower(e.value); + break; + default: + break; + } + }); +} + +void FeedbackHandler::enablePressureControlMode(bool enable) { + isPressureControlModeEnable = enable; + updatePressure(); +} + +void FeedbackHandler::enableSetPressure(bool enable) { + isSetPressureEnable = enable; + updatePressure(); +} + +void FeedbackHandler::setPressure(float value) { + cachedCappedPressure = value; + updatePressure(); +} + +void FeedbackHandler::setConstante1(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::CONSTANTE_1)] = + value; +} +void FeedbackHandler::setAveragePower(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::AVERAGE_POWER)] = + value; +} + +void FeedbackHandler::setConstante2(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::CONSTANTE_2)] = + value; +} +void FeedbackHandler::updatePressure() { + { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::PRESSURE)] = + cachedCappedPressure; + } + float cappedPressure = + std::roundf(cachedCappedPressure * 10.0f + 0.5f) / 10.0f; + // Pressure control mode is set to calculated? + if (isPressureControlModeEnable) { + midas::odb o(path); + o[FeedbackHandlerConf::INPUT_VARNAME][static_cast( + FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = + cappedPressure; + } + // update setpoint of Mercury ITC in ODB ? + if (isSetPressureEnable) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME][static_cast( + FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = + cappedPressure; + } +} + +void FeedbackHandler::setPressureMinimum(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::MINIMUM_PRESSURE)] = + value; +} +void FeedbackHandler::setPressureMaximum(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::MAXIMUM_PRESSURE)] = + value; +} + +void FeedbackHandler::setSP(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::SP)] = value; +} + +void FeedbackHandler::setTemperature(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::TEMPERATURE)] = + value; +} + +void FeedbackHandler::setPower(float value) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::POWER)] = value; +} diff --git a/src/device/handlers/FeedbackHandler.h b/src/device/handlers/FeedbackHandler.h new file mode 100644 index 0000000..3b16df5 --- /dev/null +++ b/src/device/handlers/FeedbackHandler.h @@ -0,0 +1,34 @@ +#ifndef FEEDBACK_HANDLER_H +#define FEEDBACK_HANDLER_H + +#include "../EventBus.h" +#include + +class FeedbackHandler { + private: + bool isPressureControlModeEnable; + bool isSetPressureEnable; + float cachedCappedPressure; + + std::string path; + + void enablePressureControlMode(bool enable); + void enableSetPressure(bool enable); + + void setSP(float value); + void setTemperature(float value); + void setPressureMinimum(float value); + void setPressureMaximum(float value); + void setPower(float value); + void setAveragePower(float value); + void setPressure(float value); + void setConstante1(float value); + void setConstante2(float value); + + void updatePressure(); + + public: + FeedbackHandler(EventBus &eventBusReference, std::string equipmentName); +}; + +#endif \ No newline at end of file diff --git a/src/device/handlers/FeedbackHandlerConfig.h b/src/device/handlers/FeedbackHandlerConfig.h new file mode 100644 index 0000000..0b4c522 --- /dev/null +++ b/src/device/handlers/FeedbackHandlerConfig.h @@ -0,0 +1,27 @@ +#ifndef FEEDBACK_HANDLER_CONFIG_H +#define FEEDBACK_HANDLER_CONFIG_H + +#include + +namespace FeedbackHandlerConf { + +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Variables"; +const std::string OUTPUT_VARNAME = "Measured"; +const std::string INPUT_VARNAME = "Demand"; + +enum class RegisterIndex { + PRESSURE_SET_POINT = 0, + SP = 1, + TEMPERATURE = 2, + POWER = 3, + AVERAGE_POWER = 4, + PRESSURE = 5, + MINIMUM_PRESSURE = 8, + MAXIMUM_PRESSURE = 9, + CONSTANTE_1 = 10, + CONSTANTE_2 = 11 +}; +} // namespace FeedbackHandlerConf + +#endif \ No newline at end of file diff --git a/src/device/handlers/InputHandler.cpp b/src/device/handlers/InputHandler.cpp new file mode 100644 index 0000000..362acf1 --- /dev/null +++ b/src/device/handlers/InputHandler.cpp @@ -0,0 +1,92 @@ +#include "InputHandler.h" +#include "../EventBus.h" +#include "InputHandlerConfig.h" +#include "SettingsHandler.h" + +#include +#include + +void InputHandler::updatePath(std::string name) { + this->path = InputHandlerConfig::PATH_PREFIX + name + + InputHandlerConfig::PATH_SUFFIX; +} + +void InputHandler::updateSPIndex(int index) { + this->SPIndex = index; + pullSPValue(); +} + +void InputHandler::updateTemperatureIndex(int index) { + this->temperatureIndex = index; + pullTemperatureValue(); +} + +void InputHandler::updatePowerIndex(int index) { + this->powerIndex = index; + pullPowerValue(); +} + +void InputHandler::pullSPValue() { + midas::odb o(this->path); + float SPValue = o[SPIndex]; + Event event = {EventType::SP_VALUE, SPValue}; + eventBus.publish(event); +} +void InputHandler::pullTemperatureValue() { + midas::odb o(this->path); + float temperatureValue = o[temperatureIndex]; + Event event = {EventType::TEMPERATURE_VALUE, temperatureValue}; + eventBus.publish(event); +} +void InputHandler::pullPowerValue() { + midas::odb o(this->path); + float powerValue = o[powerIndex]; + printf("Sending power value %f\n", powerValue); + Event event = {EventType::POWER_VALUE, powerValue}; + eventBus.publish(event); +} + +void InputHandler::updateHotlink() { + // TODO : Verified this code work + // This should change the target in case of a new name given + if (hotlink) { + hotlink->unwatch(); + hotlink.reset(); + } + + hotlink.emplace(path); + + hotlink->watch([&](midas::odb &arg) { + int hotlinkIndex = arg.get_last_index(); + if (hotlinkIndex == SPIndex) { + } else if (hotlinkIndex == temperatureIndex) { + pullTemperatureValue(); + } else if (hotlinkIndex == powerIndex) { + pullPowerValue(); + } + }); +} + +InputHandler::InputHandler(EventBus &eventBusReference) + : eventBus(eventBusReference) { + eventBus.subscribe( + [this](const SettingsHandler::Event &e) { + switch (e.type) { + case SettingsHandler::EventType::EQUIPMENT_NAME: + updatePath(std::get(e.value)); + updateHotlink(); + break; + case SettingsHandler::EventType::INPUT_SP_INDEX: + updateSPIndex(std::get(e.value)); + break; + case SettingsHandler::EventType::INPUT_TEMPERATURE_INDEX: + updateTemperatureIndex(std::get(e.value)); + break; + case SettingsHandler::EventType::INPUT_POWER_INDEX: + updatePowerIndex(std::get(e.value)); + break; + default: + break; + } + }); +} diff --git a/src/device/handlers/InputHandler.h b/src/device/handlers/InputHandler.h new file mode 100644 index 0000000..837f57b --- /dev/null +++ b/src/device/handlers/InputHandler.h @@ -0,0 +1,44 @@ +#ifndef INPUT_HANDLER_H +#define INPUT_HANDLER_H + +#include "../EventBus.h" +#include "odbxx.h" +#include +#include + +class InputHandler { + private: + EventBus &eventBus; + std::string path; + int SPIndex; + int temperatureIndex; + int powerIndex; + + std::optional hotlink; + + void updatePath(std::string name); + void updateSPIndex(int index); + void updateTemperatureIndex(int index); + void updatePowerIndex(int index); + + void updateHotlink(); + + void pullSPValue(); + void pullTemperatureValue(); + void pullPowerValue(); + + public: + InputHandler(EventBus &eventBusReference); + enum class EventType { + SP_VALUE, + TEMPERATURE_VALUE, + POWER_VALUE, + }; + + struct Event { + EventType type; + float value; + }; +}; + +#endif \ No newline at end of file diff --git a/src/device/handlers/InputHandlerConfig.h b/src/device/handlers/InputHandlerConfig.h new file mode 100644 index 0000000..d007c5b --- /dev/null +++ b/src/device/handlers/InputHandlerConfig.h @@ -0,0 +1,12 @@ +#ifndef INPUT_HANDLER_CONFIG_H +#define INPUT_HANDLER_CONFIG_H + +#include + +namespace InputHandlerConfig { + +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Variables/Input"; + +} // namespace InputHandlerConfig +#endif \ No newline at end of file diff --git a/src/device/handlers/OutputHandler.cpp b/src/device/handlers/OutputHandler.cpp new file mode 100644 index 0000000..e99f197 --- /dev/null +++ b/src/device/handlers/OutputHandler.cpp @@ -0,0 +1,70 @@ +#include "OutputHandler.h" +#include "DemandHandler.h" +#include "OutputHandlerConfig.h" +#include "PressureCalculator.h" +#include "SettingsHandler.h" +#include + +void OutputHandler::enableSetPressure(bool enable) { + isSetPressureEnable = enable; + update(); +} + +void OutputHandler::setPressure(float value) { + pressure = value; + update(); +} + +void OutputHandler::setOutputPressureSPIndex(int index) { + outputPressureSPIndex = index; + update(); +} + +void OutputHandler::setEquipmentPath(std::string equipmentName) { + equipmentPath = OutputHandlerConfig::PATH_PREFIX + equipmentName + + OutputHandlerConfig::PATH_SUFFIX; + update(); +} + +void OutputHandler::update() { + if (isSetPressureEnable) { + midas::odb o(equipmentPath); + if (o.exists(OutputHandlerConfig::OUTPUT_VARIABLE)) { + o(OutputHandlerConfig::OUTPUT_VARIABLE)[outputPressureSPIndex] = + pressure; + } + } +} + +OutputHandler::OutputHandler(EventBus &eventBusReference) { + + eventBusReference.subscribe( + [this](const DemandHandler::Event &e) { + switch (e.type) { + case DemandHandler::EventType::SET_PRESSURE: + enableSetPressure(std::get(e.value)); + break; + default: + break; + } + }); + + eventBusReference.subscribe( + [this](const SettingsHandler::Event &e) { + switch (e.type) { + case SettingsHandler::EventType::EQUIPMENT_NAME: + setEquipmentPath(std::get(e.value)); + break; + case SettingsHandler::EventType::OUTPUT_PRESSURE_SP_INDEX: + setOutputPressureSPIndex(std::get(e.value)); + break; + default: + break; + } + }); + + eventBusReference.subscribe( + [this](const PressureCalculator::Event &e) { + setPressure(e.cappedPressure); + }); +} diff --git a/src/device/handlers/OutputHandler.h b/src/device/handlers/OutputHandler.h new file mode 100644 index 0000000..27b7df2 --- /dev/null +++ b/src/device/handlers/OutputHandler.h @@ -0,0 +1,27 @@ +#ifndef OUTPUT_HANDLER_H +#define OUTPUT_HANDLER_H + +#include "../EventBus.h" +#include "DemandHandler.h" +#include "PressureCalculator.h" + +#include + +class OutputHandler { + private: + bool isSetPressureEnable; + std::string equipmentPath; + float pressure; + int outputPressureSPIndex; + void enableSetPressure(bool enable); + void setPressure(float value); + void setOutputPressureSPIndex(int index); + void setEquipmentPath(std::string equipmentName); + + void update(); + + public: + OutputHandler(EventBus &eventBusReference); +}; + +#endif diff --git a/src/device/handlers/OutputHandlerConfig.h b/src/device/handlers/OutputHandlerConfig.h new file mode 100644 index 0000000..46a6087 --- /dev/null +++ b/src/device/handlers/OutputHandlerConfig.h @@ -0,0 +1,13 @@ +#ifndef OUTPUT_HANDLER_CONFIG_H +#define OUTPUT_HANDLER_CONFIG_H + +#include + +namespace OutputHandlerConfig { +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Variables"; + +const std::string OUTPUT_VARIABLE = "Output"; +} // namespace OutputHandlerConfig + +#endif \ No newline at end of file diff --git a/src/device/handlers/PressureCalculator.cpp b/src/device/handlers/PressureCalculator.cpp new file mode 100644 index 0000000..1dba962 --- /dev/null +++ b/src/device/handlers/PressureCalculator.cpp @@ -0,0 +1,119 @@ +#include "PressureCalculator.h" +#include "../itc_pressure_optimizer.h" +#include "Average.h" +#include "DemandHandler.h" +#include "InputHandler.h" +#include + +void PressureCalculator::update() { + double averagePower = average.getAverage(); + double c2contrib = averagePower * cachedConstante2; + double c1contrib = + cachedConstante1 * (cachedTemperature - cachedSP - c2contrib); + double uncapedPressure = cachedMinimalPressure + c1contrib; + + double cappedPressure = uncapedPressure; + + if (c2contrib != 0) { + c2contrib = -c2contrib * cachedConstante1; + } else { + c2contrib = 0.0f; + } + + if (cappedPressure < cachedMinimalPressure) + cappedPressure = cachedMinimalPressure; + + if (cappedPressure > cachedMaximalPressure) + cappedPressure = cachedMaximalPressure; + + Event event = {}; + + event.c1contrib = c1contrib; + event.c2contrib = c2contrib; + event.uncapedPressure = uncapedPressure; + event.cappedPressure = cappedPressure; + event.averagePower = averagePower; + + eventBus.publish(event); +} + +PressureCalculator::PressureCalculator(EventBus &eventBusReference) + : average(30), eventBus(eventBusReference) { + eventBus.subscribe( + [this](const DemandHandler::Event &e) { + switch (e.type) { + case DemandHandler::EventType::MINIMAL_PRESSURE: + updateMinimalPressure(std::get(e.value)); + break; + case DemandHandler::EventType::MAXIMAL_PRESSURE: + updateMaximalPressure(std::get(e.value)); + break; + case DemandHandler::EventType::CONSTANTE_1: + updateConstante1(std::get(e.value)); + break; + case DemandHandler::EventType::CONSTANTE_2: + updateConstante2(std::get(e.value)); + break; + default: + break; + } + }); + + eventBus.subscribe( + [this](const InputHandler::Event &e) { + switch (e.type) { + case InputHandler::EventType::SP_VALUE: + updateSP(e.value); + break; + case InputHandler::EventType::TEMPERATURE_VALUE: + updateTemperature(e.value); + break; + case InputHandler::EventType::POWER_VALUE: + updatePower(e.value); + break; + default: + break; + } + }); + + eventBus.subscribe( + [this](const itcPressureOptimizer::EquipmentPollEvent &e) { + update(); + }); +} + +void PressureCalculator::updateSP(float value) { + cachedSP = value; + update(); +} + +void PressureCalculator::updateTemperature(float value) { + cachedTemperature = value; + update(); +} + +void PressureCalculator::updatePower(float value) { + cachedPower = value; + average.addValue(static_cast(value)); + update(); +} + +void PressureCalculator::updateMinimalPressure(float value) { + cachedMinimalPressure = value; + update(); +} + +void PressureCalculator::updateMaximalPressure(float value) { + cachedMaximalPressure = value; + update(); +} + +void PressureCalculator::updateConstante1(float value) { + cachedConstante1 = value; + update(); +} + +void PressureCalculator::updateConstante2(float value) { + cachedConstante2 = value; + update(); +} diff --git a/src/device/handlers/PressureCalculator.h b/src/device/handlers/PressureCalculator.h new file mode 100644 index 0000000..7ae50cd --- /dev/null +++ b/src/device/handlers/PressureCalculator.h @@ -0,0 +1,43 @@ +#ifndef PRESSURE_CALCULATOR_H +#define PRESSURE_CALCULATOR_H + +#include "../EventBus.h" +#include "Average.h" + +class PressureCalculator { + private: + Average average; + EventBus &eventBus; + + float cachedSP; + float cachedTemperature; + float cachedPower; + + float cachedMinimalPressure; + float cachedMaximalPressure; + float cachedConstante1; + float cachedConstante2; + + void updateSP(float value); + void updateTemperature(float value); + void updatePower(float value); + + void updateMinimalPressure(float value); + void updateMaximalPressure(float value); + void updateConstante1(float value); + void updateConstante2(float value); + + void update(); + + public: + PressureCalculator(EventBus &eventBusReference); + struct Event { + float c1contrib; + float c2contrib; + float uncapedPressure; + float cappedPressure; + float averagePower; + }; +}; + +#endif \ No newline at end of file diff --git a/src/device/handlers/SettingsHandler.cpp b/src/device/handlers/SettingsHandler.cpp new file mode 100644 index 0000000..696eda5 --- /dev/null +++ b/src/device/handlers/SettingsHandler.cpp @@ -0,0 +1,87 @@ +#include "SettingsHandler.h" +#include "../itc_pressure_optimizer.h" +#include "SettingsHandlerConfig.h" +#include + +using namespace SettingsHandlerConfig; + +void SettingsHandler::pullEquipmentName() { + midas::odb o(this->path); + std::string equipmentName = o[SettingsHandlerConfig::EQUIPMENT_NAME]; + Event event = {EventType::EQUIPMENT_NAME, equipmentName}; + eventBus.publish(event); +} + +void SettingsHandler::pullOutputPressureSPIndex() { + midas::odb o(this->path); + int outputPressureSPIndex = + o[SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX]; + Event event = {EventType::OUTPUT_PRESSURE_SP_INDEX, outputPressureSPIndex}; + eventBus.publish(event); +} + +void SettingsHandler::pullInputSPIndex() { + midas::odb o(this->path); + int inputSPIndex = o[SettingsHandlerConfig::INPUT_SP_INDEX]; + Event event = {EventType::INPUT_SP_INDEX, inputSPIndex}; + eventBus.publish(event); +} + +void SettingsHandler::pullInputTemperatureIndex() { + midas::odb o(this->path); + int temperatureIndex = o[SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX]; + Event event = {EventType::INPUT_TEMPERATURE_INDEX, temperatureIndex}; + eventBus.publish(event); +} + +void SettingsHandler::pullInputPowerIndex() { + midas::odb o(this->path); + int powerIndex = o[SettingsHandlerConfig::INPUT_POWER_INDEX]; + Event event = {EventType::INPUT_POWER_INDEX, powerIndex}; + eventBus.publish(event); +} + +SettingsHandler::SettingsHandler(EventBus &eventBusReference, + std::string equipmentName) + : path(SettingsHandlerConfig::PATH_PREFIX + equipmentName + + SettingsHandlerConfig::PATH_SUFFIX), + eventBus(eventBusReference) { + eventBus.subscribe( + [this](const itcPressureOptimizer::EquipmentInitEvent &e) { + this->init(); + }); +} + +bool SettingsHandler::init() { + midas::odb o(this->path); + + bool areKeysValids = true; + + if (!midas::odb::exists(path + SettingsHandlerConfig::EQUIPMENT_NAME)) { + areKeysValids = false; + } + if (!midas::odb::exists(path + + SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX)) { + areKeysValids = false; + } + if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_SP_INDEX)) { + areKeysValids = false; + } + if (!midas::odb::exists(path + + SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX)) { + areKeysValids = false; + } + if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_POWER_INDEX)) { + areKeysValids = false; + } + + if (!areKeysValids) + return false; + + this->pullEquipmentName(); + this->pullOutputPressureSPIndex(); + this->pullInputSPIndex(); + this->pullInputTemperatureIndex(); + this->pullInputPowerIndex(); + return true; +} diff --git a/src/device/handlers/SettingsHandler.h b/src/device/handlers/SettingsHandler.h new file mode 100644 index 0000000..11e99a5 --- /dev/null +++ b/src/device/handlers/SettingsHandler.h @@ -0,0 +1,40 @@ +#ifndef SETTINGS_HANDLER_H +#define SETTINGS_HANDLER_H + +#include "InputHandler.h" +#include +#include +#include + +class SettingsHandler { + + public: + SettingsHandler(EventBus &eventBusReference, std::string equipmentName); + + enum class EventType { + EQUIPMENT_NAME, + OUTPUT_PRESSURE_SP_INDEX, + INPUT_SP_INDEX, + INPUT_TEMPERATURE_INDEX, + INPUT_POWER_INDEX + }; + + struct Event { + EventType type; + std::variant value; + }; + + private: + std::string path; + EventBus &eventBus; + + bool init(); + + void pullEquipmentName(); + void pullOutputPressureSPIndex(); + void pullInputSPIndex(); + void pullInputTemperatureIndex(); + void pullInputPowerIndex(); +}; + +#endif \ No newline at end of file diff --git a/src/device/handlers/SettingsHandlerConfig.h b/src/device/handlers/SettingsHandlerConfig.h new file mode 100644 index 0000000..4fec93a --- /dev/null +++ b/src/device/handlers/SettingsHandlerConfig.h @@ -0,0 +1,19 @@ +#ifndef SETTINGS_HANDLER_CONFIG_H +#define SETTINGS_HANDLER_CONFIG_H + +#include + +namespace SettingsHandlerConfig { + +const std::string PATH_PREFIX = "/Equipment/"; +const std::string PATH_SUFFIX = "/Settings/Devices/MITCPRESSC/DD/"; + +const std::string EQUIPMENT_NAME = "MITC_Equipment"; +const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; +const std::string INPUT_SP_INDEX = "MITC_InputVarioxSPIndex"; +const std::string INPUT_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; +const std::string INPUT_POWER_INDEX = "MITC_InputVarioxPowIndex"; + +} // namespace SettingsHandlerConfig + +#endif \ No newline at end of file -- 2.54.0 From 681dc318a84c0bca0c3265a8cd0a677bdba82648 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 13:23:54 +0200 Subject: [PATCH 11/28] demand handler cleaned --- src/device/handlers/Average.h | 16 ++++++++ src/device/handlers/DemandHandler.cpp | 53 ++++++++++++++++++++------- src/device/handlers/DemandHandler.h | 11 ++++-- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/device/handlers/Average.h b/src/device/handlers/Average.h index 785b42e..c995ff8 100644 --- a/src/device/handlers/Average.h +++ b/src/device/handlers/Average.h @@ -4,6 +4,10 @@ #include #include +/* + This class allows you to compute the average value inside a given time window. + It computes the average using continuous time. +*/ class Average { private: using Clock = std::chrono::steady_clock; @@ -25,8 +29,20 @@ class Average { Average(double windowSizeSeconds) : windowSize(Duration(windowSizeSeconds)) {}; + /* + @brief Modify the time window size + @param seconds time window duration + */ void setWindowSize(double seconds); + /* + @brief Add another value with the current timestamp to the average. + */ void addValue(double value); + /* + @brief Get the current average of all the values from [now - timewindow, + now]. + @return The average value + */ double getAverage(); }; diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp index 725d2ec..2b89f2c 100644 --- a/src/device/handlers/DemandHandler.cpp +++ b/src/device/handlers/DemandHandler.cpp @@ -1,47 +1,72 @@ #include "DemandHandler.h" #include "../itc_pressure_optimizer.h" #include "DemandHandlerConfig.h" -#include -#include - #include "odbxx.h" #include "tmfe.h" +#include +#include +#include using namespace DemanHandlerConfig; bool DemandHandler::init() { + bool initSuccess = true; if (!midas::odb::exists(this->path)) { - printf("[DEMANDE HANDLER] : Path doesn't exist\n"); - return false; + TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Key at %s doesn't exists", + path.c_str()); + initSuccess = false; } midas::odb o(this->path); - bool indexValid = true; - // This is checked every start, in case of someone change the index in the // config header if (o.size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { - indexValid = false; + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Minimal pressure index is %i, where array size is %i.", o.size(), + static_cast(ParameterIndex::MINIMAL_PRESSURE)); + initSuccess = false; } if (o.size() < static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { - indexValid = false; + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "maximal pressure index is %i, where array size is %i.", o.size(), + static_cast(ParameterIndex::MAXIMAL_PRESSURE)); + initSuccess = false; } if (o.size() < static_cast(ParameterIndex::CONSTANTE_1)) { - indexValid = false; + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Constante 1 index is %i, where array size is %i.", o.size(), + static_cast(ParameterIndex::CONSTANTE_1)); + initSuccess = false; } if (o.size() < static_cast(ParameterIndex::CONSTANTE_2)) { - indexValid = false; + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Constante 2 index is %i, where array size is %i.", o.size(), + static_cast(ParameterIndex::CONSTANTE_2)); + initSuccess = false; } if (o.size() < static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { - indexValid = false; + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Pressure control mode index is %i, where array size is %i.", + o.size(), static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)); + initSuccess = false; } if (o.size() < static_cast(ParameterIndex::SET_PRESSURE)) { - indexValid = false; + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Set pressure index is %i, where array size is %i.", o.size(), + static_cast(ParameterIndex::SET_PRESSURE)); + initSuccess = false; } if (!indexValid) - return false; + throw std::runtime_error("DemandHandler init failed - see MIDAS " + "console and take care of all the errors\n"); this->pullMinimalPressure(); this->pullMaximalPressure(); diff --git a/src/device/handlers/DemandHandler.h b/src/device/handlers/DemandHandler.h index 956c263..b08f31f 100644 --- a/src/device/handlers/DemandHandler.h +++ b/src/device/handlers/DemandHandler.h @@ -10,6 +10,9 @@ class DemandHandler { std::string path; EventBus &eventBus; + /* + Pull value from the ODB + */ void pullMinimalPressure(); void pullMaximalPressure(); void pullConstante1(); @@ -18,12 +21,12 @@ class DemandHandler { void pullSetPressure(); /* - @brief Manualy get all the value, and update owned objects - */ + Init process + */ bool init(); + /* - @brief Set all the hotlink, waiting for any update - @return true if succes, false if any error encountered + Setting ODB hotlink */ bool setHotlink(); -- 2.54.0 From e35081bc5f5d5405464233ff82dd190e1677b116 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 13:44:49 +0200 Subject: [PATCH 12/28] Feedbackhandler cleaned --- src/device/handlers/FeedbackHandler.cpp | 49 +++++++++++++------------ src/device/handlers/FeedbackHandler.h | 6 +++ 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/device/handlers/FeedbackHandler.cpp b/src/device/handlers/FeedbackHandler.cpp index 46f416c..3796fc8 100644 --- a/src/device/handlers/FeedbackHandler.cpp +++ b/src/device/handlers/FeedbackHandler.cpp @@ -53,6 +53,31 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, }); } +void FeedbackHandler::updatePressure() { + { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] + [static_cast(FeedbackHandlerConf::RegisterIndex::PRESSURE)] = + cachedCappedPressure; + } + float cappedPressure = + std::roundf(cachedCappedPressure * 10.0f + 0.5f) / 10.0f; + // Pressure control mode is set to calculated? + if (isPressureControlModeEnable) { + midas::odb o(path); + o[FeedbackHandlerConf::INPUT_VARNAME][static_cast( + FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = + cappedPressure; + } + // update setpoint of Mercury ITC in ODB ? + if (isSetPressureEnable) { + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME][static_cast( + FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = + cappedPressure; + } +} + void FeedbackHandler::enablePressureControlMode(bool enable) { isPressureControlModeEnable = enable; updatePressure(); @@ -87,30 +112,6 @@ void FeedbackHandler::setConstante2(float value) { [static_cast(FeedbackHandlerConf::RegisterIndex::CONSTANTE_2)] = value; } -void FeedbackHandler::updatePressure() { - { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] - [static_cast(FeedbackHandlerConf::RegisterIndex::PRESSURE)] = - cachedCappedPressure; - } - float cappedPressure = - std::roundf(cachedCappedPressure * 10.0f + 0.5f) / 10.0f; - // Pressure control mode is set to calculated? - if (isPressureControlModeEnable) { - midas::odb o(path); - o[FeedbackHandlerConf::INPUT_VARNAME][static_cast( - FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = - cappedPressure; - } - // update setpoint of Mercury ITC in ODB ? - if (isSetPressureEnable) { - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME][static_cast( - FeedbackHandlerConf::RegisterIndex::PRESSURE_SET_POINT)] = - cappedPressure; - } -} void FeedbackHandler::setPressureMinimum(float value) { midas::odb o(path); diff --git a/src/device/handlers/FeedbackHandler.h b/src/device/handlers/FeedbackHandler.h index 3b16df5..b1086f1 100644 --- a/src/device/handlers/FeedbackHandler.h +++ b/src/device/handlers/FeedbackHandler.h @@ -12,6 +12,9 @@ class FeedbackHandler { std::string path; + /* + Internal value setters + */ void enablePressureControlMode(bool enable); void enableSetPressure(bool enable); @@ -25,6 +28,9 @@ class FeedbackHandler { void setConstante1(float value); void setConstante2(float value); + /* + Update pressure value + */ void updatePressure(); public: -- 2.54.0 From c354025813413010ad0daa760a9b1b793f45feb1 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 16:28:59 +0200 Subject: [PATCH 13/28] cleaning up and bug fixing --- src/device/handlers/DemandHandler.cpp | 25 ++++++------ src/device/handlers/InputHandler.cpp | 56 +++++++++++++++++++++------ src/device/handlers/InputHandler.h | 4 +- 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp index 2b89f2c..daeb96e 100644 --- a/src/device/handlers/DemandHandler.cpp +++ b/src/device/handlers/DemandHandler.cpp @@ -4,7 +4,6 @@ #include "odbxx.h" #include "tmfe.h" #include -#include #include using namespace DemanHandlerConfig; @@ -24,47 +23,47 @@ bool DemandHandler::init() { if (o.size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, - "Minimal pressure index is %i, where array size is %i.", o.size(), - static_cast(ParameterIndex::MINIMAL_PRESSURE)); + "Minimal pressure index is %i, where array size is %i.", + static_cast(ParameterIndex::MINIMAL_PRESSURE), o.size()); initSuccess = false; } if (o.size() < static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, - "maximal pressure index is %i, where array size is %i.", o.size(), - static_cast(ParameterIndex::MAXIMAL_PRESSURE)); + "maximal pressure index is %i, where array size is %i.", + static_cast(ParameterIndex::MAXIMAL_PRESSURE), o.size()); initSuccess = false; } if (o.size() < static_cast(ParameterIndex::CONSTANTE_1)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, - "Constante 1 index is %i, where array size is %i.", o.size(), - static_cast(ParameterIndex::CONSTANTE_1)); + "Constante 1 index is %i, where array size is %i.", + static_cast(ParameterIndex::CONSTANTE_1), o.size()); initSuccess = false; } if (o.size() < static_cast(ParameterIndex::CONSTANTE_2)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, - "Constante 2 index is %i, where array size is %i.", o.size(), - static_cast(ParameterIndex::CONSTANTE_2)); + "Constante 2 index is %i, where array size is %i.", + static_cast(ParameterIndex::CONSTANTE_2), o.size()); initSuccess = false; } if (o.size() < static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, "Pressure control mode index is %i, where array size is %i.", - o.size(), static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)); + static_cast(ParameterIndex::PRESSURE_CONTROL_MODE), o.size()); initSuccess = false; } if (o.size() < static_cast(ParameterIndex::SET_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, - "Set pressure index is %i, where array size is %i.", o.size(), - static_cast(ParameterIndex::SET_PRESSURE)); + "Set pressure index is %i, where array size is %i.", + static_cast(ParameterIndex::SET_PRESSURE), o.size()); initSuccess = false; } - if (!indexValid) + if (!initSuccess) throw std::runtime_error("DemandHandler init failed - see MIDAS " "console and take care of all the errors\n"); diff --git a/src/device/handlers/InputHandler.cpp b/src/device/handlers/InputHandler.cpp index 362acf1..f239e73 100644 --- a/src/device/handlers/InputHandler.cpp +++ b/src/device/handlers/InputHandler.cpp @@ -3,12 +3,48 @@ #include "InputHandlerConfig.h" #include "SettingsHandler.h" +#include "tmfe.h" #include #include -void InputHandler::updatePath(std::string name) { +void InputHandler::setPath(std::string name) { this->path = InputHandlerConfig::PATH_PREFIX + name + InputHandlerConfig::PATH_SUFFIX; + + if (!midas::odb::exists(path)) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Key at %s doesn't exists", + path.c_str()); + + throw std::runtime_error("Key at " + path + " doesn't exists"); + } + + midas::odb o(path); + bool isKeyValid = true; + + if (o.size() < SPIndex) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "SP index is %i, where array size is %i.", + SPIndex, o.size()); + isKeyValid = false; + } + if (o.size() < temperatureIndex) { + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Temperature index is %i, where array size is %i.", + temperatureIndex, o.size()); + isKeyValid = false; + } + if (o.size() < powerIndex) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Power index is %i, where array size is %i.", + powerIndex, o.size()); + isKeyValid = false; + } + + if (!isKeyValid) { + throw std::runtime_error("InputHandler update path failed - see MIDAS " + "console and take care of all the errors\n"); + } } void InputHandler::updateSPIndex(int index) { @@ -46,16 +82,14 @@ void InputHandler::pullPowerValue() { eventBus.publish(event); } -void InputHandler::updateHotlink() { - // TODO : Verified this code work - // This should change the target in case of a new name given - if (hotlink) { - hotlink->unwatch(); - hotlink.reset(); +void InputHandler::setHotlink() { + if (hotlink.has_value()) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Hotlink already established. Will not update it " + "without restarting the frontend."); + return; } - hotlink.emplace(path); - hotlink->watch([&](midas::odb &arg) { int hotlinkIndex = arg.get_last_index(); if (hotlinkIndex == SPIndex) { @@ -73,8 +107,8 @@ InputHandler::InputHandler(EventBus &eventBusReference) [this](const SettingsHandler::Event &e) { switch (e.type) { case SettingsHandler::EventType::EQUIPMENT_NAME: - updatePath(std::get(e.value)); - updateHotlink(); + setPath(std::get(e.value)); + setHotlink(); break; case SettingsHandler::EventType::INPUT_SP_INDEX: updateSPIndex(std::get(e.value)); diff --git a/src/device/handlers/InputHandler.h b/src/device/handlers/InputHandler.h index 837f57b..73396f3 100644 --- a/src/device/handlers/InputHandler.h +++ b/src/device/handlers/InputHandler.h @@ -16,12 +16,12 @@ class InputHandler { std::optional hotlink; - void updatePath(std::string name); + void setPath(std::string name); void updateSPIndex(int index); void updateTemperatureIndex(int index); void updatePowerIndex(int index); - void updateHotlink(); + void setHotlink(); void pullSPValue(); void pullTemperatureValue(); -- 2.54.0 From 4f60fddd64be897a5bd5af2b9e1b014b915529cf Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 16:48:21 +0200 Subject: [PATCH 14/28] OutputHandler cleaned --- src/device/handlers/InputHandler.cpp | 2 ++ src/device/handlers/OutputHandler.cpp | 28 +++++++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/device/handlers/InputHandler.cpp b/src/device/handlers/InputHandler.cpp index f239e73..8799a4c 100644 --- a/src/device/handlers/InputHandler.cpp +++ b/src/device/handlers/InputHandler.cpp @@ -68,12 +68,14 @@ void InputHandler::pullSPValue() { Event event = {EventType::SP_VALUE, SPValue}; eventBus.publish(event); } + void InputHandler::pullTemperatureValue() { midas::odb o(this->path); float temperatureValue = o[temperatureIndex]; Event event = {EventType::TEMPERATURE_VALUE, temperatureValue}; eventBus.publish(event); } + void InputHandler::pullPowerValue() { midas::odb o(this->path); float powerValue = o[powerIndex]; diff --git a/src/device/handlers/OutputHandler.cpp b/src/device/handlers/OutputHandler.cpp index e99f197..e27d60a 100644 --- a/src/device/handlers/OutputHandler.cpp +++ b/src/device/handlers/OutputHandler.cpp @@ -3,6 +3,7 @@ #include "OutputHandlerConfig.h" #include "PressureCalculator.h" #include "SettingsHandler.h" +#include "tmfe.h" #include void OutputHandler::enableSetPressure(bool enable) { @@ -27,13 +28,28 @@ void OutputHandler::setEquipmentPath(std::string equipmentName) { } void OutputHandler::update() { - if (isSetPressureEnable) { - midas::odb o(equipmentPath); - if (o.exists(OutputHandlerConfig::OUTPUT_VARIABLE)) { - o(OutputHandlerConfig::OUTPUT_VARIABLE)[outputPressureSPIndex] = - pressure; - } + bool isKeyValid = true; + midas::odb o(equipmentPath); + + if (o.exists(OutputHandlerConfig::OUTPUT_VARIABLE)) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Key at %s doesn't exists", + equipmentPath.c_str()); + isKeyValid = false; } + + if (o.size() < outputPressureSPIndex) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "SP index is %i, where array size is %i.", + outputPressureSPIndex, o.size()); + isKeyValid = false; + } + + //// SHOULD I CRASH IF KEY NOT VALID ? + //// FOR NOW, FAILED SILENTLY + + if (isSetPressureEnable && isKeyValid) + o(OutputHandlerConfig::OUTPUT_VARIABLE)[outputPressureSPIndex] = + pressure; } OutputHandler::OutputHandler(EventBus &eventBusReference) { -- 2.54.0 From ed3b7db91a427d2a8aab8e05f421ad728ef30d66 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 16:50:18 +0200 Subject: [PATCH 15/28] cleaning up header file above OutputHandler --- src/device/handlers/InputHandler.h | 7 +++++++ src/device/handlers/OutputHandler.h | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/device/handlers/InputHandler.h b/src/device/handlers/InputHandler.h index 73396f3..1b11bf2 100644 --- a/src/device/handlers/InputHandler.h +++ b/src/device/handlers/InputHandler.h @@ -16,6 +16,9 @@ class InputHandler { std::optional hotlink; + /* + Internal value setters + */ void setPath(std::string name); void updateSPIndex(int index); void updateTemperatureIndex(int index); @@ -23,12 +26,16 @@ class InputHandler { void setHotlink(); + /* + Pull value from the ODB + */ void pullSPValue(); void pullTemperatureValue(); void pullPowerValue(); public: InputHandler(EventBus &eventBusReference); + enum class EventType { SP_VALUE, TEMPERATURE_VALUE, diff --git a/src/device/handlers/OutputHandler.h b/src/device/handlers/OutputHandler.h index 27b7df2..8f129d7 100644 --- a/src/device/handlers/OutputHandler.h +++ b/src/device/handlers/OutputHandler.h @@ -13,11 +13,17 @@ class OutputHandler { std::string equipmentPath; float pressure; int outputPressureSPIndex; + + /* + Internal value setters + */ void enableSetPressure(bool enable); void setPressure(float value); void setOutputPressureSPIndex(int index); void setEquipmentPath(std::string equipmentName); - + /* + Update value of the ODB + */ void update(); public: -- 2.54.0 From 2e044a2c450a13ccc422d7cab9e13980a7cf9d19 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 16:53:52 +0200 Subject: [PATCH 16/28] cleaning up pressure handler --- src/device/handlers/PressureCalculator.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/device/handlers/PressureCalculator.h b/src/device/handlers/PressureCalculator.h index 7ae50cd..89e9a7f 100644 --- a/src/device/handlers/PressureCalculator.h +++ b/src/device/handlers/PressureCalculator.h @@ -18,19 +18,28 @@ class PressureCalculator { float cachedConstante1; float cachedConstante2; + /* + Internal value setters (from InputHandler) + */ void updateSP(float value); void updateTemperature(float value); void updatePower(float value); - + /* + Internal value setters (from DemandHandler) + */ void updateMinimalPressure(float value); void updateMaximalPressure(float value); void updateConstante1(float value); void updateConstante2(float value); + /* + Update pressure value + */ void update(); public: PressureCalculator(EventBus &eventBusReference); + struct Event { float c1contrib; float c2contrib; -- 2.54.0 From db505106032aecce887277f457e3eb79b38aaf51 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 17:10:19 +0200 Subject: [PATCH 17/28] cleaning up all --- src/device/handlers/SettingsHandler.cpp | 18 ++++++++++++++- src/device/handlers/SettingsHandler.h | 29 +++++++++++++++---------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/device/handlers/SettingsHandler.cpp b/src/device/handlers/SettingsHandler.cpp index 696eda5..779b241 100644 --- a/src/device/handlers/SettingsHandler.cpp +++ b/src/device/handlers/SettingsHandler.cpp @@ -58,25 +58,41 @@ bool SettingsHandler::init() { bool areKeysValids = true; if (!midas::odb::exists(path + SettingsHandlerConfig::EQUIPMENT_NAME)) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Key at %s%s doesn't exists", path.c_str(), + SettingsHandlerConfig::EQUIPMENT_NAME.c_str()); areKeysValids = false; } if (!midas::odb::exists(path + SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX)) { + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, "Key at %s%s doesn't exists", path.c_str(), + SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX.c_str()); areKeysValids = false; } if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_SP_INDEX)) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Key at %s%s doesn't exists", path.c_str(), + SettingsHandlerConfig::INPUT_SP_INDEX.c_str()); areKeysValids = false; } if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX)) { + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, "Key at %s%s doesn't exists", path.c_str(), + SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX.c_str()); areKeysValids = false; } if (!midas::odb::exists(path + SettingsHandlerConfig::INPUT_POWER_INDEX)) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Key at %s%s doesn't exists", path.c_str(), + SettingsHandlerConfig::INPUT_POWER_INDEX.c_str()); areKeysValids = false; } if (!areKeysValids) - return false; + throw std::runtime_error("SettinsHandler init failed - see MIDAS " + "console and take care of all the errors\n"); this->pullEquipmentName(); this->pullOutputPressureSPIndex(); diff --git a/src/device/handlers/SettingsHandler.h b/src/device/handlers/SettingsHandler.h index 11e99a5..10a554b 100644 --- a/src/device/handlers/SettingsHandler.h +++ b/src/device/handlers/SettingsHandler.h @@ -7,6 +7,23 @@ #include class SettingsHandler { + private: + std::string path; + EventBus &eventBus; + + /* + Init process + */ + bool init(); + + /* + Pull value from the ODB + */ + void pullEquipmentName(); + void pullOutputPressureSPIndex(); + void pullInputSPIndex(); + void pullInputTemperatureIndex(); + void pullInputPowerIndex(); public: SettingsHandler(EventBus &eventBusReference, std::string equipmentName); @@ -23,18 +40,6 @@ class SettingsHandler { EventType type; std::variant value; }; - - private: - std::string path; - EventBus &eventBus; - - bool init(); - - void pullEquipmentName(); - void pullOutputPressureSPIndex(); - void pullInputSPIndex(); - void pullInputTemperatureIndex(); - void pullInputPowerIndex(); }; #endif \ No newline at end of file -- 2.54.0 From 53adb555664b8ac2404e038aefff35e1a751beb3 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Thu, 23 Jul 2026 17:16:03 +0200 Subject: [PATCH 18/28] adding a correct main --- src/frontend/itc_pressure_optimizer_scfe.cpp | 60 +++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/frontend/itc_pressure_optimizer_scfe.cpp b/src/frontend/itc_pressure_optimizer_scfe.cpp index cbd1a36..35e8e82 100644 --- a/src/frontend/itc_pressure_optimizer_scfe.cpp +++ b/src/frontend/itc_pressure_optimizer_scfe.cpp @@ -11,8 +11,64 @@ itcPressureOptimizerScfe::itcPressureOptimizerScfe(std::string frontendName, FeAddEquipment(new itcPressureOptimizer(equipmentName, __FILE__)); } +/** + * Print help when argument are wrong or are + */ +void help(std::string binaryName) { + TMFE::Instance()->Msg( + MINFO, __FUNCTION__, + "|HELP| itc pressure optimizer usage : %s " + "", + binaryName.c_str()); +} + +void wrong_argument(std::string binaryName) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Wrong usage of %s", + binaryName.c_str()); + help(binaryName); +} + int main(int argc, char *argv[]) { - auto front = itcPressureOptimizerScfe("itcPressureOptimizerScfe", - "itcPressureOptimizer"); + + if (argv[0][0] == '.') { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Relative paths are strongly discouraged; " + "please use an absolute path."); + } + + if (argc == 1) { + wrong_argument(std::string(argv[0])); + exit(EXIT_FAILURE); + } + + if (argc == 2) { + std::string argument = std::string(argv[1]); + if (argument == "--help" || argument == "-h") { + help(std::string(argv[0])); + exit(EXIT_SUCCESS); + } else { + wrong_argument(std::string(argv[0])); + exit(EXIT_FAILURE); + } + } + + std::string frontendName; + std::string equipmentName; + if (argc > 2) { + frontendName = std::string(argv[1]); + equipmentName = std::string(argv[2]); + argc -= 2; + argv += 2; + } + + if (argc > 1) { + TMFE::Instance()->Msg( + MINFO, __FUNCTION__, + "Unnecessary argument detected; this will be discarded"); + argv += (argc - 1); + argc -= (argc - 1); + } + + auto front = itcPressureOptimizerScfe(frontendName, equipmentName); front.FeMain(argc, argv); } -- 2.54.0 From aa8000d7fbafe773ce37cf7db2589ed6f516dd36 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Fri, 24 Jul 2026 09:14:27 +0200 Subject: [PATCH 19/28] update readme + adding pictures --- README.md | 45 +++++++++++++++++++++++++++++++++++----- doc/graph.puml | 15 +++++++------- out/doc/graph/graph.png | Bin 24992 -> 30419 bytes 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 65f39e1..9201b29 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,33 @@ # ITC Pressure Optimizer -Optimize the pressure setpoint on Mercury ITC depending on Temperature parameters read from the (same) Mercury ITC. +Optimize the pressure setpoint on a Mercury ITC depending on temperature parameters read from the (same) Mercury ITC. +Pressures are calculated as a function of temperature setpoint, actual temperature, heater power, and currently set pressure. +This driver is a port of mitcpresscio.c, with an event -Pressure are calculated in function of temperature setpoint, actual temperature, heater power and currently set pressure. +# Preview +![alt text](img/odb_equipment_interface.png) -This driver is only a port of mitcpresscio.c, using the c++ TRIUMF framework instead of legacy C +# Download -# ITC Pressure Optimizer DD Settings for associated Equipment +```bash +$ git clone .... +``` -Path : ```/Equipment//Settings/Devices/MITCPRESSC/DD/``` +# Compile +```bash +$ cmake -B build +$ cmake --build build +``` +# Run +```bash +$ $pwd/build/itcPressureOptimizer +``` +# Configuration + +In ```/Equipment//Settings/Devices/MITCPRESSC/DD/```, you can modify settings regarding where to read/write values to the equipment. + +Default values are : | Equipment | variox0 | heliox0 | |-----------|---------|---------| @@ -23,3 +41,20 @@ Path : ```/Equipment//Settings/Devices/MITCPRESSC/DD/``` *NOTE: OutputPressSPIndex and InputVarioxPowIndex are different for variox0 and heliox0* + + +# Usage + +In ```/Equipment//Variables/Demand``` you can specify demand to the equipment. See **Preview** + +# Project structure + +This is the execution flow graph. + +![alt text](out/doc/graph/graph.png) + +For cleaner code, all execution and data flow pass through an EventBus. Objects can subscribe to one or many specific event types. They can also publish an event, which will be dispatched to all subscribers by invoking their callbacks. + +`OdbHotlink` triggers the execution flow whenever a value changes. Only `DemandHandler` and `InputHandler` are registered. + +`itc_pressure_optimizer` instantiates all components and publishes an `InitEvent` first, allowing all handlers to initialize and connect to the ODB. It also periodically publishes a `PollEvent` every second to update time-dependent averages. \ No newline at end of file diff --git a/doc/graph.puml b/doc/graph.puml index c90dae7..c096fd7 100644 --- a/doc/graph.puml +++ b/doc/graph.puml @@ -6,15 +6,15 @@ class EventBus <> { + subscribe() } -class Demand -class itc -class Settings -class Input -class Pressure { +class Demand as "DemandHandler" +class itc as "itc_pressure_optimizer" +class Settings as "SettingsHandler" +class Input as "InputHandler" +class Pressure as "PressureCalculator" { class Average } -class Output -class Feedback +class Output as "OutputHandler" +class Feedback as "FeedbackHandler" Demand -.> Pressure Demand -.> Feedback @@ -31,4 +31,5 @@ Input -.> Output Input -.> Pressure Pressure -.> Feedback + @enduml \ No newline at end of file diff --git a/out/doc/graph/graph.png b/out/doc/graph/graph.png index f12381a8ac510c22b732dd807fa7d4e37521f363..070678dfb9aba34539c3078e3fb196e5557a972e 100644 GIT binary patch literal 30419 zcmagG1z3|`|36Nsh$x`62v}eM(jlp!Ly(e`?k<7BB*suckQAgFCLk>_x(q9EF3LdOrRF#*G)aHTSK9*T*Y{JUfG*CLfstf zADB5gxOca+5fKrW*=W3k{_8psF?i1FWCOJ^$I(aB@U&vajWcSUzDQrCm)^sQ8KTb}CkqFh?`jtY4`Dyg&m8=*yj9gg3 z`lQbAM$-Hz^c+6)P9NF=QA@=YwDJ4Gof&Jf@D^=RBl{9As`QgDI&?Hpj$wb^nC`8$ zGd(QlzR(6VH9p?Ds_Gx{aB&WkM&A_h;FFNp490)11OIGKie*BfXI>}utkIzTru5}# ztEZ-F!neOItRDR#EAXE+0w%NHp$B^-L5Wd88h#ER>#H0%K>o$HJU2h9G3%M<^xu!N>xB@QVGn zYYYR}qobo9>61hJ_VUoTvzC||w@a5UEp(^Mgq|VtZ?)Tg&J4hsqiH8(eh zwPjPXO1vJih+~oVx%SUn(J?V|^Yh_t*%c0B!dhXZgjP=5;%M$D+fOzeF$Z+aTXrTT zO1N##whbA=Ru7ZrrT8xb>L7Z*nw5z-)}zORJvXrvsQ{(d@bYHDOd>F?>B zh66?T@?f^)7!D!(z2%!`hgElK%!d!E6%{KItX{EPdhF@UV5m;Nakr zows-0@tr$&c6WAa?C9v}vvYFnv}|qjvg29E$$jn~nSp3B8bRWgu3VvU*;>a>)JhK@ zQKiTpwXIcRs$VJeM$fZ!A^Y|>W?I>&Dk_8nMuR`0jFdD~AB1#d&(BwqIKyK?9Xbn( zv$C=-w3D67EhtD9w288zw-?;hw2cLo=4JG*?-ji{(A_a=p}+S=Nd$L{AoER6l$gQPn-t%zg1L@&3VBJ=jSa|W@NdESPE?ft$7Gzt0foB-v8}@qHiI`=_H-~ zNlKD*z-FIXP9ZnjS0#LRw{r8>3k6=Dp17|u`G*T)V`flpu7gpX3#h{Pmozmsbteoh zzdSY=D=8gTs99+j^!5G%=B}H`CG2 z$Xn#^$$3!qE0x>g^o|)JJL09osyFi*b-&y#bGNEL**u8;bT0!IE$;mL ziGqTHtgK&2?Iq+L<>Z$yUt%i+XeGxeHiJ_!VbRgi=H}*ADruad74^gBFrlmEaBif# zpMxMR1H(beY6W?DY)&Ck)o_(OU{Ngo7t)n5?-<6gyA!ZUI48xSn3E(y>@lo z0!vBx@F)3?_P&l+$>!aw+;7v2vAXI$_lnGkxRgm`kalEOJv63tA_{+H1}Bs~(}Cr9 zgxIzcKa5I69FWYc_g4#4mcthcjq3Z#t@L(==+6pzf~o26@4w}tY;ZHqlg>P8VNgw7 z9iz8WimK08JNL`k**Tjh|(!CJqP0*9tN>V~CwT_tZJh+ev$dRhAK!wW>3aM)=06 zMbhB@cHLfscqqDYAGbtZ`;)Fn*Rblh4_7$o#;GfelK1ZAHn`;*^{5ve9l0&Ggs%r$ z<1SrdUf_-oY~}-Ntv(4-B~{|#lmHgNdce?vbznmUT0>D6ql#+t9DMH3ofv$ESL&J!F9}HNF1b%wTl5i#UPh z+546V;#JK$LVru))hFQ`@QG_Dt_x!%e7zaEEoWByT^Scn8u~Zvag#@DFt7|P><3Hu z@>v-h8~3D2a%vU)BC~by^xvukQ})gd@B6fZwMPc#45xlpV7_wRp3 z5w~<=$tbVIM+s)leb;KtSl~`}`XuE#Q4lw3=#y^+`@lETu)9XZp_1zT`^WpsO!c+3 zdS#uw`LSlL;Wn8bPiur<>gnm}>vJuR@{L`pudmltoZdb@I$-ba?NvMAaLWkT+T83c zx}@7{2KCD}x3a3`tf;8K7Z21jTtp#DOG`5|Zwj3s9UJqjQ{3sjiV=WFHocxHX=OzY zo?{e1hCB^n;5Jg9jl(gMO-6UfUK@hT`>Y+kc^60Si0kg|E*6_ap{i+~D=2U%#$PAy zD8N&*v85#?CB??hlV_uplp^`f%*@J4OV!lW#?K1EK^qdIaAf^Y{xBPGpHnJ9Hl#6M za~#}?(h?brs}m3qFf%pf=jZPX!Qgb1s6#1z(Zmnx(Yvc-IlKlNuqIfsX5F8?%7sy`|y|@@ixd;pF1dIR7Ap zftGgo{BYgqIDVMXAX^$t?1apKIqllHjG~$j{5)g%V-zWrXjD50BGSgU9?xgJo@#f| z#0rOyIF8UuYH(HgI%L_C1n=s%1KpAA?Xa1=@7lQ0RJtl{E0tD)C*~DvdMZ@&5@k8C z5{hFxSuJ`|#|A$j;|6QFWbJ;nuO4fjRWe=K;r&Z`oSYAa^qbc#LFSTHiz1A&*Q1P> zuyt`UvCf*WVQZNN8k(Bbab14Qn7W!SA^hux2&n?Jm#5ce9K*9|5z9_PosBjX%rG&V&YKf(O3tkq%_|*%#*>9oQnNy&y>?buU=OM-HVSXW;O(Rl2K;+1M;-4* zH@`^EczSx4KZ3*S_Y;$n^3(Unr}EoDUxgeqGGQ7pV}-;J(`~Tcd8fSu0<| zCMGA-l9LPEeJP8xv$Jb!#l6D}Q8s?|^|z&Wz1EMf@9~mWKF|?mw^7&%x4d|Uk;S-< z+2HQ^+L>-~+FY`_L)Jg*Sru_~DGXekdn;u<5C>=^c*VwAuGuD~>!ew%y{EZDzyGCJ zKpKG@8{1iPf6HtD@|{IS=xf^x)-{w9hQ{X^kZEftL&XR)8-=BE26spDjY=PU%o12O z%8E=4J~;Y<#LZZr5%Xjzf3QGJu)+1k^Uk%t_qC3`g z5@EAHBTXvlgVA)`%JIC9@$yPaU7ekRG+@4YJ&DWS{oO92-|hx_SbBy9||XmM{FSmK!*G_U!!4 zij*Q%eooHkE}9oHBO|%1)`PuS&n@uOVUA+$HZgd-=dK5I6+UyFot@osR&+O`>gZrM z=}te&Q%^4`{Dzdm??k=Ath;^*?*DVDjGBy=s+k&N_b#Q5Z(tW#E31<6AOXnEL9cyYVe-Bos{ccW49U6tV5LJ88*Ma8KH0g%9#?J}iwl zo69L_2(D26%-ef8f%=YWX>OK3bbr}5IHgSEiB^3ty{9p zxPz*ak{1X3UIn?ie0E0@K^RPSN9x1d4kH!3-@aj=Z?4sW5pfcXKOAF-r_Jh4sm@vz zcRE^1xsD{~t`qQD6%ALCDUVA?5CTJs|TRer&JL zbj09CjL(-In`YkF4Y(Y(r#D$6bc65fRx%!>3HJ5H=Tu+3xSaDV>)X21?hw;uI2^8v z-lDz-A#1{-e^&8(YHq&+Lj=d{xdDfT;tC~f%KF^b#usz(T`bf`5fKqaHPFS9$K!9_ zY@-Sb3sEScAG@vA9$VKQ!(g!I&pW2S2TF$B=l(T0Sxg1eJSRuTir?rc-3BD``w1m$ z?U(tQTNF&?wIGA*T3uRl5ptjJWLoWz)-N%Di1%tc=yyQf-A5^&TEgbJ5w|=Qum22o zH^QeQHDha3^sr?>y`U5k(d(~a!_-6yQ z3ltPFAKl-yw=2x$CH3rVTuORXrcSk{e&egFl<_f0fV zh?*DHde`NF_^10@r66q0aA-5Ot*!0GRg84M3Kp_k)k+u2SHrxSNPO`yLSfYM&0DXz z_w_MJDF9*!)YR6xy12|LcSWV)#&n874Pa2PAiRS+_C=^oGogN&+c-KF)$*uRO2G3f1t_WpO<%^dmZtuE8SpB zKBuxr3%-Hu2QI_&VU2X-msM5>1A=+&oPG=`bX%oO_3fSa59*o;+pflsW7ks00* z%RTq%G9QzaPpIKGbRe^?nfQ{V)3o=k?Sw7Md*O(G5?EcIuXU?ql~fkEm6esfz1IWl zjz+M1a?1RkT6L_RMzHq^#2cz`3}!VRhH3u+z+;Qm6z%l{QKX}1)k^Um5gAk-)eNaNQCU(vAh`AaT8%_?)qq- zfpe>5=@7yQWfaCU{s`G4S`7(zn>5`c4*duqPp^1(rr0)ZO!p4LN<(v;g4|jNTl@jK`sHy zgo?$*Uk?yTEN)Qf9>%P9m@lY)Wt~l+yD>KWt9_sJ69ekbcxU2>zR?%rn!3@7DvfZF zlE*Eft_)ie>`pnud^4T&?^pyJKPR)@M*xBj)DtO)Cu8$Y=dHKtskmIvggNq>-j2IM zLleO&>3P|uhfPW5!)uXif9H8bFE$hEwzv{HB7Hn&fc&+;L#3-`l{Gjp5T<#2jhTdI zL@p1lG{yWub>jV7G)Sev^NpN$Rz{N9A69ZTxP4MXtS%Jy=zSQ>oc_#c+_ zNX=NCC4dV)d=+oa`+n_W9Z7bZ_Dvy9k{zE)MDek`-1PU|I~*K?{r#a>cshZV1QV6Q z5&FFWen8J{c)NJPdo>))E$`gbxmC9EayYKyc&O6Rpf=Zg!{8y{I`;SX<-)H`51sj2 zjHpn}bkiHsBw>g48lmTeG%PAA3S@RmOG~dF8bf`Zt)MNTA)9S^AaZHs+5 zk(a>6WC}fBMkpTauuX&&6I28mN(p|Sh6vyzUa==3=tWBMnd;@cgp7hQg^?wQaG9{H zfi|}KhcSy7x{p{qroVgtJ|I=ZMD`pXa92BwRhU#VwR_b7u7j1Cc?^xt=mA6vVdvZj znHZsq8^1u}{I)YtC^yB>qO<%00;}5OJnZaqZ^JbKB|x}CAw3Qwwu?gtWV5X=LdM<`Vs&{*jZtJ=Ey3Luk>xfq?fuX@Dw&usLd~{gj0SoiDl8_WqiNxq zW!LxMIFJm$yE{Ak^;ZQSJm{`(EYbn5j~q(9Bpx0fG#rY*7G_!_#?JQk_JWR*Adj4P z`{M|9Nzf6#yuzL*Cos!x#DTK@5gf6pUv!L^=~|G&FPMD|dtd=b;9uUWSnMxaCfE~* z_AC1}ljTO;bNu;X%}0ZUG6Qsy~Gq_bh}?xuvE zqb7&&c;{PgKHuk@DAGTgY;YT5&R1`!JUq$M!%s>-yz&DJ2#z({#fMT-K2IQ%jfh6C z3`2E6_+8{Kz)Z5#PZK!aia&i^onm@sa(Ec4lMkqazNExNLcfs(ok5=1bQvIyEIn4= z=pMk}Go+QPuO)({B;7Rx3*K)!g3S-g53qBY!5E35+t3p@;Fv)dIoQ~2j9=c}4ehx4 zLz3u3{gvfG1^ZV^11lE>$j!Z-h%vwINN9Bz5zw2`hkPDSDlY4%ek3-icdW9)d zc}>2lYGb-qZ6nYaN4t5c%D3y+A+X2}y1rVfIPl?J!f1S?!FW{DLI-PT7+pHdEwVaU zj(uB0DotsM19_p>NL_KU6Ihc$-xhIT7V6?27%Yi+y1AhY9=#|XEADcFo`~}HdVu*6 z1d$ebVgzH6S5yqG);XPFkA|6#wo`Xc3g*|6D&r(J%ABNsHTY~kN5GfE+uxk*?Oun( zIhU1{%}oLF8w|Y4o<;(%R=QZva{cw$X!Hncto5LR|F#rKpIgU|4KqGaY2? z&QKQ}Nx+`tR8ezztk*>gzp9ie39}ZUR_G^Pun5vQoDc8;>7EH_Lc?wZHx@ zP*e+Do)8a`K-#T~l7cu#UzL3Ftsgs&vCC#l&O{urRy-5J&qPT3?5yaN6VH5vAM7|x z)FtRL6Cnf1;s&vD5v(9>IGX*avUkI{&KZweq~p}W?e41Ek?NZw*DEz z-eSLVr#0LRIWMEAsHmbs1!*;(B_%S(QGhw92bQXrOqe>S`9IU&-4%cW!*W26G~?eS zCi*sj1$xJ|<{)elS|wSOub+r#w6ucFT>SB%Q0qM9-jYY4-k)&%8mGCwi>oV!*!GF5 zYuPvGZi*ly%g0D6B5Gks++|Kq&aS;;8Mg6~HrxnO@@bpAyu8-O7Hn|p%*;#|^%tON z6eZ0W2y}FApTR{rzj*P&-o8NRx}+qgK^DUv|k{@w2GGE(wV!53k z=vQx`@h-Ln=p#FnH;HQ%$|{Sv=j*wIKg49*QA)g>aldI`$@ZGHqqB2K{X+-yaX<`I z0}#reZ}$t3@Ksf!)jF<#wJNWHwqD7>!Kr4VM{3!jJA5tX zN>;$Y)h|kySl(AmHAG@QfDL1ogXC}I^)-;|4|k-G!3<+kqLLVWL0Iih)@zCx8n(SZ ztXT$!2CjzE|G=(!4f~ryYkjYh;U)U#pyd4NS{QBsgQ*4tl)K$0iSlFLz#H>C3iHO8 z41W*ZuMnw~4>)-s`}pzKAMa^yjbVuhU7&K3V3`N+X?+ zrsRXb7(QgIDy@Oq!DDAIl-9BR{r%U&$Q>Sn*E6s#yce&ms(Ss+pIo?~zV(=KH=uAd zx?*ZF*Cw1mrZX}?PQZ+1vo>-HD{8D8Y@K(cIDF6z#Ja5KafxHYn_1EG?(7uJ*wl{x zB1bw|JHL6$3RCR2Z!+4eB_$=Pap`31PuUI^gAj*+TjrI+9UTeEH_i=QFMMQusSC+3P(**WrV#1Jn%3Vwy`E~r3wjfW)m{kL6 zZhUGB$h@drrOOv-*i6lTJJdIG_&IywiKo1ztVgR^UEW;VC@YsT7?0mD4j zBKL=T%n$0+dvg5lsi!9da_&@JGr^4yiRd3~imXkd3?n`FRYWE#W+J7>ymyltC#3s% zi;5!h^YX@>wG$H)$Ct;vcGraBen|rU(dR(prHe~xr){8?Tk&*dWhL0hij-Pk=Nq!f ze^}mUMJ_+H@Ay9cr4lyS6^p9lbYls{RD~;0#BbP++mVlEyMXM8sHut<|Na_IHI~_OJ#UpdpmxgvGTXf;4p1wh4fdrK6>N9a^LV0Qr?_DbDnW zurO4nP#m`FmdQM^0Q?oVp1L~neQ-_}iP)9p1CubvA~x$G-4{mNUr%R2MZwulcj#=Y z&vxSHFB1YBDOR)(2{KSzFIZhSnohh{PWClU0$J`Cp-`Gjng#j>2CIlz#mM}XyIn(r zgFtTm4T2k0R8Q(K(SI}Me&P^6fBBfz#Q5W(CnMS=#ir1ckM@$1lB1>czxb*s_9lsE z5b7bnT2PZfU_6O+yB+*Zs1nfbfeRjGAsdzAd?F%4Gp|o;FoEUey0Er-mb@>+>2u)B ztu`zj9UU!q>056ga9?f)Y*K!HzS65nkPwQdR^~$J%+Kx<`J$1uq2Yv;mXKjCB^6v} z`1dvB)vQ!LRg_gZy}}kXFpo?q&Al_e58^-`~vu# zC1%1}$Quhe0qT24d;9TX+A%rW@Y?a}+R@f16`MMd}~BR+fFZO8zf%>BX+DzswRlj@LFq%I>iG_+jYC~H~vVcE1K3F%yVa*SAR@Ji=`P8(>=nN6@yhb@FQOr-PXvH*i*MKHhmr^e`u&o~D+Q2$ou`^WoE+sB zH;kUaz7W;Fx_6J0Qy@a3Gex|zq$KhpD2tl)E=dxf8RYkMVA#1OJ5||uR0x%F7!?%S z{#yPiyP*3ghRH~sx+cd~e?wyJ8+ zTe#GHbu`Y<^Zkbh{Owbv79HIu(Y_(?6eb4?_3ptL;arQ$%U*lafzn5~K38f-`_b}6 ziAr8x-Y;Ms0+huETXz0-V_8|!xtsdDSt{JT3TzNIf%c9L;Ig=DfbIk`B3-V!!acuN z8~d$@LpiZZQyiZeFbvV&|6EY;DO&B^bkv_8G%BhWc=5wu+*frKXbbcz9~4l$eERh1 z9;<`}Z#(&N83R&1i3q}G_RX_XwEa4yHInftbC3f-?AP0Y#q&HJX&?C(5_8ox-*5o# zs_yL8g^&wI2WUG&uKUsyB(_Xv04d$-=HPEBImY} z2WmesS_PDF&yT=^h~_B)>r2@ITNMH*TLfKAEuspc(x?XC?zdy*MA#b}`>F+_`NS-! z0rAj?L%q-Qvk2(0wCbEVe*8R0^ZmX$BOkY0;(c0$dw{x$ehjvWquwEo9;w}en|?z+ z$M~SUD|#7s6{dRG{{8-yAr3&2$Ul4bI(O9qp|qjAp4yJTvoImH9?W^dKd)j|RJT-2 z&(&Ul(OGZAi2z;k46Jvg%6Vp@-iv3NxPNG<5`{|W0eXs9svj8@qzt{awFUMwhK!4; z5*}&`oj`u+PJSpbzmTI4*xeSybqoz^wMfme|gM{^B1zoE87~zw5 z9VF96dmujAF=rXcI}A8aS&70=pJpW-pd09~W2OZ+SAE8 zHG>Gr3~+FWz9zUr=zb7XsLeDn*nDiodfj$DEOE!a<9IaF7bOp0)^cb)2`!)pbj7&v zGmvCMrEGd{D{=Ag5bSA4BP{>x!@Uhailj()b#(<&v1ysC~t-s7dGpRYQGJ=j=ZFZSYXn0P6GIeSP;g z8L5|l6#?@IQzkmF5G8T&?Qd{KLP|S?3%9f{g{;?p@8_8G_H+xvxL(la=Ha@3Z4f(u z7#xg`cnY9uXLf<|)kNe%V(`h79{`$ry=ttFSBE zq4(Rd_~>LC6&o81pj{b7R&$@IR&!Kxvi;zIFzSyggsNmvEj$4}5(Z|uPloNs;&!!V zEyMut+~{rBL}7Pt=^vx!z})9SF&|(Gy&+XYnudAh%{n0S1S)WmGzM>Kz`hE7c zg96F&%8Ev0-_ZEDe&OeP_n+CaS!e1P8fm`&d}#gjsHd$BJy>qx_3VC&i#uQ{M&h!| zD=J2tzS--mLtp1vhf>IyL+LCvnZz!_2OcO2nHRg}CH@lDT>M=6OTR2rZ~2~q*wnjs z=U99xW#!dTjK8o7wRsW_s&ZKYKzrXFsPi4c#8qSSEbNhFNI7{XK^e8nUltl0Ta)jJ zi}gWm6W&<@XPpg0d%*k(=74Nv-#+K_&#F`k!-oFhfi*>OO)GmK9!9Ox-$hx>J!7eu zUy652t!3Df;j6hyWBVxII)3(Zx=qCVW=x?xRKvi}+|n|yU?lh%o5tJz^Qtw`?qliX z);LvL?yRTxmKcAuY!If>=TUQD#UxHc9BmCZ@}lI&YZMsfJw{hh=sUwTq5lm%ziyE+AzhrLJfbZLr@dTY`l&uSb> z1TOL6RWG`+8PEk5I^(_;K4}QvPXnHF^w) zT#cGyd3Ig;Rd>;({QYp~`w`)LiX$&_OI(%a%y=?#Z>+Ul5-KLWD)~eAIXCkf;I%_^ zlL!QMqixmUW{1*451=>%q2&8g9Y#m*hC1H<^z$Izk3>oHjN^!^!PL$VH0=oNGbLBB zg!xez;!zkhM$zk;tx_%7N1>p4MTgzx!Sg_!+z&&9I_C)0aOI1=Fco&C!wq*t>8crF z4B}fF^uBsL__*}p5Tc!xi;8~X*l>?oNc%=OCaxa(28}Gcn0TPdUzr#)uTVuI=Y@h? zQG4dO65-4f1Js+x?Q!H^C5H|%o9wri?6pZHjc0}#;Ycb#uN75HbqkH{4&9#jbubaoG!FH7%T*aQu#Cl;3QY(SYc z_1Q}mO$StMgzm|M^}{g(KHAJOt?6PKs3iA0g*A}fA2f~uj9-?Onhvn@vXAV$_jUu& z$ij=BMcN7ZI;B+(T<0VLR`!K2Ak$R#dvyv;?ba3ZZ~QiVaegkZRzE+sXi#&ei53xo zmK5p6kZL7tUprKloPoTsGXWF%tKCQcA5Am5p}$!tfV0O^xg{BhdKF(TL{#hBx4&D| z5!2{Nxy)oBteG`s7&5p91Y8-9%8pd>T{&Cm*_)$5NjE*L`w6L>vO;F3lU?7tVSaWDG+so|%8S@OOOhT(LEfhDgI|6TitUCF`FD^0$+ z3D@`HmpxB*0xP?1buCL3&>B{e*KY~c*xvQ%)=@Fe*KBUOxRUeHBJCRU)PXjjxMgIdG@0xR5jhGiuPO8^uS?=)lx*)X23{&9 zr7(w#B?e=udePWi_^Jeb3k;(V8mFl$qRj>+kQEx&%RX3mj8@$X^d&TByZz z+0|_{Sd|&iAOj=OJl0GaI$vZ(u@?n>ddU}e#MRSnl$qZPOG+BtN!2PbSTOhGUYF{c zPP+oO`$Y#i(_b54AE~(_6!B$QQJBhbsSLi!{o?@JTiuYCk$xp(;7D@jEhEN|?$=WL zr9#JsbKaf~pHTE5ZDgnuz8pf&quXE^(D?Q>&}#GKWUi@u0!tz(75&+3vS{|7KNW1Z z+}+(1yQf|94z4BsRxVuD(mcMGXCc_!!elF&q?GXN_tf>B4-%4Xm}>Xno6HVhB@Ap? z9bmB8^&q=cc%?Rv<|trJ-G~5Zo%mUD^nP!ncv;bcRHlx>V?P(j@~GKPjzRv=(2$)8 zOPS1>O4RHm10~+#TVhXtgm@(MJgr3V3|m9TGRE1x|wwcaciRhReP=WsFE{pKsWtM#0dH8Cd(<$ z!lK30;AV9VIgQhmg3p#fvEO^5Iwn2km3H6owYT!%IOp%8y`_4UkHyK2SdJ5~G{fO) z_8T{*v1sJNb;TxZ^w;MB3#s+LXMj*Qh5QU*{F?zkDbOk7fFCFSbt@)${B~fz{R37i_^S1_B^m`m(G>j1YT9k>hO?ky_- zL9of4!k3Eyz#s}T7+2T&FJFGKP=NCk5H`QjC*JtQoIF)p_A_-jhQEeDCN56>oe`_F z>1Uxnd^XnBLA*%yb9`dPIKF%(#po}VU5UV$6I-kUvV+1SFOV&-86>0uFuvm2L%A&` z0|=2*ti+?_U@E@IrCayRtsOXWfjp~0{pH9nwjyF;@B}|t2n@~`_XU}OThG_^JOQh4 z;SPlCUr>qW`x{cSJQ2;wWVh!srr&0zKLB>S{_es4sP59`P|8z0BPn4^6Q`b|4Bu?K zlcSQBeb*G4ei883SQC8!^!`3d*@1+S3U-qXJb1qGQSokmKtu4OW& z5QQ?yG5J4N!vQpPM~cW47sM}Sbse-BYD)v46Zp{IzX$~bHs)WT?)32>ID`xT_l*&9 z02&8?Pmn@#go#^1k)VcwLA+u!ntZS5AUPLF%vy-~#K6H73+kYMRZ^Mh2Ph<0iJ&bL ziX;3*D1#INY^0}6hwGR49C<3x`q!eVSj1=>$W*{bM6VztE~!ZE&yJK?-P1sK0&N`V z+23k>1GeLxqrY0ugXL-35YLMj{XJY_makQa2NKX-T{J=4X7m2@_4wEapj`hDBYbd3 z$oG1j3C>wNBRV!#YG>HYg4!ZFE)KD*H;Io!?8m+0F*b34wi`p$UcA`oS62b2Hf}r% zrECN&(VJ^ig6CmKSCWtBU?6BJQ+^;nbid8SD`w$$?K+L`WcY0m3HuME<|Kc{BF#a2 zF1h$B0!9XO6Z32nn{1$ovT@W!#+1^>ltiyi9|G=g*(7lZjs96}!VLe=bz{v}#RGa#6%c zb>W}H{KR{!&-lTxIrs1p2l&uGHJhru)#i)7$2?0!^!H;U*U5-P-^l;HlTZfIFXHgf z+r_u<^z#3%7NM%XKi_H_5vme&=JmDn1TqpZgP`)KzJXA3fPrR-a)==dXE;D%x$SNL z)W#V4w>FI|@-5g$pyZ!t2;<=mh|M9}>+Q4uy+)^Z6E%@5M)8TMf)zl$*q?WF+7!`17AS0wQ*9mM8&C53djF9H*em6lLl9it{i}21${|9;l0t!proYqRb>h+q44AV zg=n27?2FbSgW8|O%B1IS3EK~U0m?&$^3?8x7jQYhJ8gq;ohz{J+7;LD0GV^AC#t)+ z0lx$Yy{PAent$_UcB}%9Q!``ZCgc`4!14Ai$tD?y`J>kazEBx(5hJA*6>-VvYL4T1 zk;WSc0!!^r)_An)#J&#vP_7;NVk!y>QD|V4|9K5`(~C1U^3ZjaX6)k}uxZfLpvecKY)E5Zk<|uAZ8L*sC37k83t~1QlZ{ux_Nt%3)QSzEjg?$1E)C`xap5p@|L1;W7-Z{hx)IN29U3~xkeG3+7sL4fy zVFA`K(gf<*yLVOO<@qXmds4((7cVfT=WZU<~EZY(A``c)0d={N?K17Jcm z($)rvvP!hv1uBU@O`6&U+kd`O#mP|vru-|CnuCyHXQAYJ!2p&w2ZJP-}8CiGS=tkKmWQ*ny(EY znfs$Sueyev>R>RY5)-f1ojy5~Co^LUumF?5ECY6`n#AqzHRDHKz=*V%+t;XrSL#2# z%^3IGiEzqLF-;1-Enso0w|f5|X}3Zp?V;Z@!cGiKGjS7G7zVEG_|?Eiud%N-r}Vh3 zy0W|WcY9;#eqTIiF+Trn2)`X{@ZgqP^X|BMD_99y&w^eAGv#h(hfyoSX%9N@$l5jp?m%m!zLN*gQgrlZFY zM{DEzgqTY(_q4Z1O+N^4oKt3k4%f4ZyDQ2M1ClU6^c;$=+C0Wu>-wjOy6fm}KSvN%m0v zylnzFv;}(ujPqXE*nE!B20SnvwYRm6hzC`FW(`Ik92^jifZP}u8q#A5bm_EwlnQj{ zvZ7cjM_faL6t}V3LiLK%uG`)plQJHVg}up32Adqj7&k^gp>^z?ML3(K#1C;=+4I33 zO@uV;8{62}fa3!L1A~L$$iWSelGALXN=thm+zYf(0OEzde)3@NNqjtG8~?NOYj`!b za&Le~danc5%8jd%fd8_yFgLe0f&ZFDfrCE8un%ThcGm?2)M@(262OsAAZDmS)|Zxg zNk#+Qqrusb=DfT-iLY?rt5M6)jQ0Mgij0!zKT8x5X!Zz@hWUt`c&r#PA}itjtTpI- zBOk4|fJ#f~99GV7Ffqwku@H}BLUx4U>2R9pJQBxTjjwihA#6)|wf$AA5s?`N$mivw zaY9gn$qO!EF>I!FAWJda4a28b6NeRPJX;v8l254?%Vh7eAg|@&Ua6*h1M-|H0T&_} z{u-ZM;MUv2c<;}}F(bj4w>m@GU3Z+qBw%@l&l8B+;!iNz-0!5%{Aj@@VhuHhXb(Oe zU_*UIr~z?Pk3`D4b~PWGMCf##6%rpG*cM7h(|Oy~I9%AH8eYi-z`FQGpC>f>z9g(0bT~`)4WSP3A9USx5JOE z47cLM6tA%tmKqnwz|35`5rO7n$*{u#PbUZ_aatrPZ!8}kyM{!p-DR4(G(OE7C5lqQnPuPb&7(Go|hjv>A75}GU)WD_SAq3Cm4|P z*)A`bK?8oezX9V~sRV-o4(i)(yYjq!Wf-BF#ou5=C_Pydz7_kx;A$osIQ-sGwt4EZ zDI#7sqLp+HE8FxbtXY7c z{xkI$-rd!;y0$iG<)E*39)rzhPSJs%93SD+WiMDmvt-C>)Q>1^&`e%U6-PP3l&+AtOu$!;x7_TdAe7Yme_jwpBv*uw0|P z5p1iV;sXf=tD_d}+%;peS@dI_2^=B@CLUWi=7O>M+W4c<5MtZAj3FV($=BWwvJQM{ z<$^K-azs~~j04f}OEzp7oXc`btq?g#@43ULBKwo|*^K}tHB7fKOJIIL{Ko{8O~rxBs;PPQ z>Dki9j?W@U9F)fTW98}tE*BQ{@0&SK)V~AI=X&2Qf?64?R6$QqLLJ$fJJ73>x9r&J z_ve#(psvRX}D1qK$AQqnnEVr2*3E1~3FDEKxY> zQsd`fizjxIdw3vWu1w9ox)JKgIzR&yCTEow?Oc#RY#|}hy#dVPpA5CMVxyyR)*g%9 zYiwk&{UxUu0~p3_cic5%jD_U^Rud+Vk$g%>7Ql4&Mf;TiT^fYn<0#c@E7fx`0i4bx z=w4%TPRqapIJk{c8Ast9ySlo&cO*PdF`wo`kk$kD!`MQ@3NkAs zBm@!+0@VAv8IdAulf~SYKB`DPgyRK$ShV37Lcsu?Jkk)ja0 zI)52iAPPP{QV*MKfQlX(s+`bF2y~AF0bjOA;u9Fp(u4ek8Yu@%AB6CN;y_~Qi=Mv1 z1P5AP$v7}v`acoWh+~vAZ3!iOErIN}cxOTpgtI)SO(1x@2|)J0@IoLjzZgiVWN})A zB>hh#lL<$%T>P{{9;p18nV#;*+4>N8HgtkzkdB{vZ1~talAKU&Cmb0mDB?uN1p~mC7z#a zxYB8It4F#=9z36jbW!(Bdv6XF8=d?GmCiN5d+YllD1Jf{{fC~Wc{y|r%%khKGr!dQz<#<6 za(d5OZNuS$Am4rDbem}`5&{B;4Fgc|U)YmT@=EVB&>4S`C}3Ft`ZT-+fK*f{2&FM} zNr(VH8HIogVvYH(r~$}^LDnPtEuY61K#fxrN@PKQie8C`2$2McjObAUu&Xy(0k}!X z)lc6CKB&68?Wm-XU+`4s(|4Q%VBpaU%Q!r1%-rIkM zDXmsqD9Q7y;P!NpsL<7Cw?NZzkf;(Cn;&3IPY_^qDCHZIN9#asQYH-zU;TDBjF3^D zVo6{0tsDROzkl~a5ZBH5_Eqq0Ay1#4{Wt#h-RK5qq!z>Hx)*~_)2s9Jq><$RKW+LC zYzx{08kNiw%ESO61gGu|36k{DvnSK}JiKBygyR69x)uM~LEv^Us0b{>8IIqRqffxZ z31v00`P2#MrTcfl$O&a7|M&l7y8L^{reTC7lUHneR5Yux@dSLk!uRDjzylh->Dn;ZHV1%r254kKJ^la@53=Qk z09zL?AKwdS?^$+_rT$zzoODS7?y-;p3@asSl@HUGq@<*d(Q<5I z?-XBtF@AG#A+jcspZ_R|{rOG)w#rGLTAyX$2LiCYcc9t_cd%9)k@rtU4prHUH1b(R z)I=TvKNAJ;DRNWURtLeRCVE-2-8xZwP_$rx~ksO5-zo_r5<3Sb=C#?y$4h0|<@&NN(GH zcY4SGmh#C^L&JPj5G*?3Q@4Rh75I3-*J^NxX^$fRa0`LSdNU9V@|*1JPx+G$$!L=% zytlg0vE(39FGgdivXzjxU6yq83(sfMlSfd-|wj$BYm=PqCt7r9_*9hsTwM2QUgli-hA#NEdvq4towZ~Ps z#=TZqWnI_0*(+S@S{E6Y|LfN0`~Q9aqx0}coqO(ipVxVh*LXgk??M;)+7eP(5u%Eq zE4jJBW}9odTlyWN=R#$U-peq~d-MbEJjI&ozdjmut1J{8?#Fe%3FC1dw358M1^KN- zdBzX{)f4Dg@l(lS@frJsrJgfbClAOL;JC+otxc3HR<=)QHK(e@BnfrEoDLW(K3(iV zWCqZgnoghrl>;0(q@A4@;yY`|)D~nzQc4c6T@m%+IhD#=6I zrheEg#j$wP<;{x>arCNx|vfqt&C-0U&QXw%J9UTol>c z2@VfJN?LlxY22TQ^X#=c%Hfy0MAs@SD;Gu^#+%~_P*FJSY`@tJ8HhaPSq*h{rO6YV zSFQqX%N>dsOA29!Ufx)I5pUXi*TiItduyKi1ZQ&t1!j(bW|Q^RDagdo4YUfMc4q5A zLhBh5rmAKrerq)zfGk9(Ipq_&JbB%KMhrN{x49_4#e%8g3C7Mzoe>^l#AuleI-mvp zgOfw7YfU)%`!HK^N{h_>TzuCO#=mLf}EbTHqXKT2m z{!zi&dIB;x=dyPJ_W=Cn^AHO@2Eh+N@ZUEw>LZic>hXWkP~+%>XYA837z_?~45M5b z>By_{UGT0RK!cV1NhkVtQGi?LZ_>KxVDfMy_Wy-CDUk%(aaM*t-O_Cp&MF5o4+{}dZ5%M^@_S5P&h?G=)$l2jH# zlCd=uA$AO)8U^Z&rpb$AWMd(z&P~!I|y@gEKEiX_m zdZ#r9x){leKocXrl?JXyc1{iewHR)kf8b*V|GN(GNKCN(jmul5khmS?`vynFZTb$qO{xP7nH@#Fjb z4D|JT4-7)G#q#6LEaLLy3YZxuNEAb>brsshl%37V?e;cIp+{HAIgQ zAC+QJ@L4=%`Q=!>EOh0%P;P|N1@W1N$)<$M?+aG5@6qn{w+bVT&doM9HUi=;Upne; zyiusiTdBf9KW)DHi0*{jAx{$(Y01)>h|fftu6MP3?ln5Vw)jwM{cKGy2yE_bqJ#BxUTXo6d>}4i689J?9KB zGxsLdm5`*-oh3Z3@7_yp74Bx1-F*Fcu8tw2)-NG)=1iik-AT?dVRs#t+vc^G1-8du z-(sMMK=deG7KK5S&iDPvG~s=D(YGYWvV`%zrhWn|&pU#wYzwl0e*GZpXN%?mzL0|t z4hH3`g~_+~ng&ysf0ET@Ic%-1Nv!8?>FLEEyS+#7Q(VYy8&e*Y`}|blFYKb-(Hrc; zic5(>)B!^GspZqM^8RDd;%{Yd(a%cBAhc2m@mr*uL2~xNUc*HdWmEkGmJg}?Krh~S zr_)6*lx9=Et+KFUy&D|Fs#Z{KDw`3U{2!qJ#2RxyJyDz@j4x|My8G~T=ZA^vP5+PrtO@?GTS>abnPExcRlI+!a6$jEO~f_toKp zSZ88w^Gm~`ekF9^f>L`<8=I!2TH$*~`fnnlh5ZGtXV!lh0rbquNfR--_R=TJN+vlq z;fFQu$cHsJ3#DE*N0@yuSIaJ;E^TOW7%5v&k>BeF zAmQ-7Wn}*z%nU5loSd965cb>9%ijfn3oJ^Kx%}996sZ13D@c6?-=&UnR%|`YUTQKL zAAA>Fl0GQTdLFW3xY*Nda|#G|pvoVm8dp7mr1>epx2xn`ZR^XsdY@&+Ipnpd`>sL6 z8dSJhZE`)aW~h;ywHJ-Q(Mw{6)5`3ywQ?3m)pSWr^#x=%_CR7Sq9!i?ROSb^@N zeTo>_*{dewcw@*KCfrrtAJ(^BpC7{=1%aMD-0x$Zf$uYICD}>63mxno(-d~?q&ej9?{fd#QWx#F1(CW z&gGa`q|8)!0kp-M;5sYEjfwGk0vT>>j}Fs};zQ!drN4Z#~le%j)Dz z4OLeQcbv%z71cj8VToVpx)5%Zhl?q);Y+_@T4alTR9sj|F*QY9{#9FopiXvq#*N&B zB`DcM{Neh~Ft)&nLCGY8S*fwpMIV)3dTw+$@in1qqZdZ+i<#eg-KPWj&5A)KdC=?$ zOHO_fYsG-|1W+5@@{MggRw2Lnb;{eJV!;;pP-cFM!gwoNThXG{3yQ~rAU*Y?@rrqz zOB<>~I^g0`hg?~zg-(N&+r3jqOB{1N_{s_j+JEX~#RTs{;E#RQdl1>PK}}hQdW6%w ziM6g>iarswtw3O3zBXs3cz9G)#tHrF`2&8$^Yo(5wY+C&ws_P!1L|hx=QxQ#AcU(%C&^9@HocIvuiA?VMPuTnoO#a(CoU@s z#%)PxE#(+M_EeUbG4;GfFHs~7u))41ycaEn{_%+O|8|1u32X8Wa2m^$*aDr7%YO#2 zk8OqHoLY%Y^^`NrPO?+4^x}-$Qcrj7A#S9nzSX3kD^vZjP~dxBw&8VNSQ+-pCF_sg z&pMbg)q2NPe|)%vlU56xDX_nHFI<@zaH-v}g+Ir>zHr%oDZ8pgck9`-k&%(qKly6b zclHdvQKj8ewpz)ma&j<_iM0uhPPwMigD`5D+4+s`|3U zwBHZ^J({J=>F?p)n7f%dU0F<)Ci?w`tNH%Eq@mIvU_Jv9sy`~+X^JPg zd~zbsw|W=xfc`d2m7>TRh;MF{mu)5zZZD zU+TUl{+i^|tIQpcNW~!8Y#tQXO)rNFk4Q$V(%*kMnb}qLl*+U^$d%}J2;)J%7UIfW zHQmsb&y1;BWjA7As#K|l@8#rW2S-<4pj)?MS4%FprMbQ0*&ZaAKTR zhKrGJ#=DLy4k^g0@D|dAZ4Un&Xr6A^niEj+Ulaoc4RW@gh{Y2L%Amrn`q5AwPp3)T z57nm;zMIn$4{44!e1lJ>6_l>!bD9JGSr{bE^2Rd>9O3R*d{4x=&}PbV3A1i%=P*XZ zeQuqik8C}y5_YoTs-U1CAg+mVCoI$B9KZOSU;Rwnvj&J;)5ais>H5C%-!5*RTK&WS zmkdW%dW5`5#zeHoVop(M**a(SD+ccYu?97M?zuw&7>-MsYaYZa4?b7UWXgifD0w~( z`Q3%$H_w6rUu4+%O{eGEYQsrpS)~;jy0A_V_ReByU@IEOjj8hctKvb_K}#=wY=olx z5A?OO#L}g);0_Js6%{}kdwx1$q@=fgXEid2C8a`4SgU-d6JUC1|{eyvN^vhb;&|qH?g8_diK_q5(Ci4w^Grw@5AFaw@3>7!Z2;{l{FO zG%TJpvXAbDYHMqH-s#ADsmaGC@dUk>d&OBd!J*b-e=8XnsC{x9E>Mo1=?kfR3HShr znFDyChCl-mPFNRsBnP^d$`(&uQb7-(SMJ%>4@fGFeMkvPJajh$ee<#iHSueZ<1f5s z3{K-I^OB4^0R9QCB_{I)Q7L7}(71U6eFKB0h7$^CE+cD4?vMz_;1LB;{J0wpg2K~8 zOv7m0;{h>pdC@*h=z+7gjS|~}xH^^CrpUR(`evK0CBnFfde|nFuhBL4sfT7cC?^-C zq$nos|LuOB_cw#2fF>YtRu={HV z8`6>P5mBcm?2LqcoU`nnREeGt#P~^mBHCB~K^;(Khx$$S7Bh-&kX@!(A=UummPU*$ zchO#@{PX52+F;$oSfcRwVE|LDL7yKl?TP>aJ)lh*08D%_K^qeaGAsPgBl3m|nI#KHkJU`Da0 z){jIyBqti~jO?UEwUOm?kMvGKs9v}8f+9eI_J_GSo&`^lk#i0b#KdzjX%H{|dA+xYpHa>V z+BlK1^Qsx@1XQME+*-@OTY*Q)#`K#4s$A;I<07)<%=a^{OyAFdagpDajDzA0Sf+2i>VV)oA{N;{Vs3*7?9Nl33Dk^Go5ZN2=_Vu+%GhDPYZ*G3g}_0C${6R?X> zLQxUf(P6p8%zDr1yUnYo5SVl3Vc_J{i~WnKqz<)8PQ1u3OQ(001;^UIedg-m7kJ3l$uE%5>GRD(u@$QsylIEL z=09nD=$TNk4OkcaBqqSPTr; z?+-Y(uE8ucW`p-CF{BT*s!6 z$&yJv=zVmY(&B{(I8+h@6>XBvK1HG~59CX)8fO+>&TH;8MxCI(?@j>YkJzN1S~vgo ztmiia>I)sUjuKHxxvz6Q2z&VcT2OHCJyAo^*SoBPu7EV_j3+9i&oCHqt4RN6uW~$q zZXlf<979i6OQ=cSDnnn}PYPpL^BO27(KEL$@{8jSy`FgxA@-UeSYoJlDCNec_~ksk z#0z$H|jkhjql5A8}1ILf! z>hih+$88Eze@ai?2#+rD&)Hh`x;_Uu7<($6;BR`?YXk_DHl8jHEFiXD0t!M3)>;O-g4|`%#I93o@wwE)`{qt-qZntoWULg z%q;K@4-Q*?ybV&F4Gkhn2!*d!&U*LTQlv^+UbT-U+6i1vjP{sOF#4r&dv&XauocNO zyDM}g;+1Oe6!%PxG3un(UR-BQ4R=jqpnM@~PGFqDv{eCrVH;8azxgmZ=Yj1P<()>h z)4pG$m{@e+P7n?fk^9GDk+qIZ5}LF#*pq25 zagtL8K1S=;w9g<$bVS~YwTU7S$Xl7SQ^rma_E}Hxs!iG()A48967+m*kwcxWH%zj~ zkA|onV-v>6FWool-ZVX~!zDpV1ms?krS?k8iI;kib2*1PtH-Vgw0TL&G-0_lt;vTL zFS|`Ox+&~LaW+N9chAe91DI+l4}(H_j8MD2a39)mp3`1p_f{@H%Rh_pNn#vzr*g?L z@$nVbZ%c}^=@(oBnEo7G+@Of9s;YWyiBbBZcJ3LzEVUKGBlYK4i>($LxkkE9+8)5g zGEKg@LvfGd9v=5nUO~YM_a)xYs_HfpNlRN>0X}k_H2X0L#%xkS0SyofGOR4O9UNvw zh%$^CI7v7t$;obi?=M9mo;6O_HJY`er|eNb;&@M~TRzvPkgZ-;&jPCo5v4umr~sy3 zaroEyWwDWM0Zf{x0Co88%Tm|i2X0>>u(rMqx#VZOI~k2BwmkLki9PW5S`(k3V46~~ zn^iK+3ESs_DAA zZ0p-uITI+a9SlX77{4S?{+#83=5GIM;TM0bf6ljO#`2OTN)z=WX%Pi&+sPW6w3G39 zxB_cZ(d=i-FyZ!XjQ`%oc%*HN3wLhgk+T1_vfc%|bObO}OT`oG)Zy&Q;yMmKICYJ5 zWa_Uuelj!eto(c=+VM{l7)|{9;=qE80~*gbWcmg?Ww@B7U;d{7+Z59%f4=Fz?x+ZW zh9qqu0q}suw`rmda#!H?F>Wg{=t6*|@JJnAd1k)*uN{m|AD&x%KD~$WWar8mBLRMa zmj55tfTw`U*I215sg6f^L|^oFs~(ULxDZLLy7Ly90k<)<+&f9&xE zo1gErkpN2eB2w`}raR(wb}45hi|XB>yxBrh?+TNx%oZ6)Uu@`dr;yWixX%fBoR zi;cAlfGSzDq$A_?<;rc$DLA#R7g)DR{JrF76|;PHx|6!$jz3+f(#RB)H$Jf_GwfQ7 z0(T9Tqd>gah$kytXS*xVJjrkpycW3$^cq} zarJ{DFZvp=2m;*Ce1c3YK<>5ReP2r0wVtB}c!7)hulc`nEiyXi)}^m;2RgAvs$ULw zLAm;`v0;=$4}~9LZ9s-nU)2uq>lOZb7ZrNvdt^fF++g*dzEC`RzEKbo6HY_lcIPRR zMie0=R8>8oVUa*7o(k{+1jp!wh9zwcCk=GxibJkF8fa#*uL_muMnk!VU+~ZBY1{-u ztn(Dv@tceyVbDC-_Zv?A z?MqrN)OPffqf(AuLn9ztvRD5plprak00zLYiu>JfAPa<!xUDjjS_hYS!t;G00egwqfz3KPBlR09ESW)7^ zi>akE03`$NKe`=fuE)4$1z^=$S8&^hj$2%O{n^yA|4g9VFS&#IY~-MwJW|8Cl>EiV zxqbLEqC|i~eq*KA2q2SW6tT~of8-lG&=hqoGqbW_izMXzAYBhqnTFjh$|&$Z#ry1zOgb>ZG{Ufxxfl7uAK z=?J+mGj=U!0z@ElG>CjzP<@?@qQb^W_xr@=!})ZxaY%QyC#bgrv#$JjOWhc;FJkQ> zsTANpbo0{N*Dj+JZ?@|FCdJOZl34hlD6KcQ$6CD_!`w&`BneOw>4t~U^XFE{s-e9d$KmHR5Ev-{Y$@3R=> zV!>_Ih$}b~^E&p~SNm7w@LgKnmzF;;XM6YBIZ?D4vJhAc;iQ-IV9UGd6JPt};46ypwSfC+gvzlQfl< zUi!|(Lz5(SjjS%TGU<;i?42&qkf*nAW|o&@W~T~=lY7|j_K?#BIT^iJbgH!imRp`R zqpveAB1)l8gRo!B!*cQ9+Xcw9;*4YOXL-x*Wos&-s>DJKG4uSCG|b?FCm$)2=ff8h zg2|=Jl=JG52{HGB1hQ3bu$8uoxT6cwHmqMU@8Qs>3 zW*gAo*Ozm?XXRCDK@x}CjO7o&>gjtIp5d8PIBsNamesOs{?01E3-CcLy0cBozQ;Yk zd)|Uxp-wUNx0Iw=jyQQ}VbsTY;it&JQg8_(hul24M{BN7ZVRL`bc36MN^ z0BL~hK~~OrTBZkh`!+U{4T2S?5~r>! z%F`wi+Y8$Jw%S)A8%WvO0|A3sL! zNNLv*qLdZ{;ZuckAgTB*g+MuB-ZteQU}@{vQ) zeO{KIPFC~zM_bgM7I`@$O}jX$e=96rnM?}0D6Y(5X1ozvPSFqabg@g0>YHDryjvOC z8{jouy)U%xLtLEKVCD9>Qw#@6`Q1NHx(7iRRJK9dMV5xAI&sGvY&5)YxrKi6i`m94 zjO=VuKr<;r7#r6H9qFif5@9Z4*8`%#wOuN)1GCJY;Rf>F?6&HA5!{(B8!4CB1#PzF z9P83e(~QC!wD~socX!)Rua192s(ADl@Q7)!SRDX*39{^hgFu%TDrq({@w33&Av+$8&C0uO#4O;xjxIt*V&{uSW{nrimRip_J*9Gc zY(Y}F`}dxw2fU815y<`B`Ay;PpH8-4_F}dkri*s~AH|5^q84>^)Zgd3B)BkZGZ`Tt#-ZGie+@9^2jX}lWgVKIU^e@Q z2F8Dl1@#Nws^J`3a@Wn#Pw_Qi<~r2G_!^cEt1>~DCly6sQc^DT@(*lg6&Mi0^MBHRaHHJbIuuG4I2ZP%vH+-w{x0!xo#}FEE3pb+1S`X(?4qe z#h@I6qG;s?s5j-*g}#~~%C7W&=c<*u$TeRj@p@DF<}-mNEg7zNJ6Y+4SL6L1MTLY0 zB~-yFEY|y$bK&q_3H1R$at&Q@Rht8uIfI@2hdKfH_q#=b8At`A@J2(-QOM+So25(` z8N((YCF1Dz^wqw2SAnG~PASE(>Q@=@9-E&6J_a^D{d;|VJs@;`{P;3i3?)1$Iv!dv zwQUwY^|eYOjLY^2Qy!=FjEu?4q;tl=EQ|UyHSqv({EA_^W4QCDBZ(9xW#u~a-K&%C zUx(db5>wnc&*|@iK(8v(jmWg-?i`K<^m4KOX4rTJ%tA*hc6o6dg+>7v{iebq6!AD& zC%+`Wb2l|L_08S^?(UK#@%winpM`{dulVuv01+&^zvSNkNiU#C>}H#Y$JbKt?rwd3 zua!?z_Qrf`gFei_4tT`%4>;zsPGbkR$lz#dpaARVKGfPg3IMw-_q69(d7vQ;%IAR z@9Jh}%WdLd=RSzI2X5nIrKaWjulKK91^4mHP><`iZ+nIxyzPg3?>RZPN=MN^S?r?l@)5cw8Rj2>b^Dr@dL+N>njZaT& z2LqF>!1Yj7BYZ_!7588G2i~!R(H~EIJD|_bv&tP`u@8LEIGnEQ>ClntSRsT3KQa?% zGI)9SWHhZf(b}QuR5_^XSEp?JBgJ@#>l+6qs|prH*sz?%)KX^xYnUYLMqNU?RF%J+ z3t`dA8w=)xzounxvM-Z+iajIu{sa%ia`(gfiWEBB--@wJ*xPuH)U;3ZRjNh>=! z!k*G)Mf*-_(P)m&^L(`43=K`U^yHTDH(cS8^0Cfg)4=@oC*S8Z_jsPg3)7M^k7&)s zRb_l2;C+RA>Wo0V6W62oXudpzyC9jU@{@hJ<%)S@C)YyW=pEU-=n#jad|7RpFISM!|SZ1z_p zv&gzgk2gE0?5j`Sp!ZRy*0CigX9^!-#j%bEeLx>5&OoA!&-}XJzLfmKK%86n)-3ex z510)Da7al)|J%WP?y>`4W&nGW(vrCQwy`FYgc?BMlys8(g{2Vcuc z_lcWe##&*1Vb+%4+E}*t8ro^-LK>Uiw5>km-c|ay%J8>D;%E|a><=&;`nM>ie_kQw zz$GiFYAsGk%PFcF;OG`FW9FbD8gNMgq5|?f%v}I1ID;o}nfBN)k zAYEXz<*jD}j5wmYy82ZFX>THjCecNuGRe{~YG82i(`}ZlAB`SWm#=?Rm|&?3?yjt^ zg3m!45Xqn=Dxf`xWK64u5*?J3hQRl`3Y#ejg05eF;(^W1&VG7&+O?ug$*ouSGt1}6 zlP9cDacrdU4Iv>RCnqNp=Ym}6=%~bQHmG>djo#X-s;bILRM^UGxp#}a_`IJKGoQGO z_lKGhE7^Oaj&_#?>}T(==)RDW64&ze_5C@N-Qu~MGsgGh$B%ra>@NS^5#x#sQ6CDY z((&a-j~=V=4Se1hXA|wcSh-ZS>EUSlnCo6H9oq| zi{{m~mEr_+1jR4ch74Mv+>K)Vbr}=!0OQu~Zmf~-gw8{vhaKJBazYyF>geNR3NkWJ zA0I}lw-Jd!K|!8j9a<)Q)Qt@dYIKx+V`F2JwM|WWO`L3Ot#bbU{-*+W@RK1>jjhCt z3>pfGUf2ybW_u*ohsTnc>@lKpn`gc_TVSzlXjl6Kaiz)M4>E}uW5xA zQTyu3%2%gQ8B!9Gj~nvL@hCVQTXS>s`ucjsJI6cYs!s#t6cp4`sT)o5MwWMXca4mU z1f#6WM#Wwr)NK5TsTmc^N=nGDHWmwliKWkt+s>0qcD>rrUR`F@>AmD^;y0DW)Yrb< zcmZwlTXnnm;;&CltL@w*uX{mMKDCI*+C z#YUIvw)mZ`J}B|B(Geqv^jx5`u_T2=>!&vR-pSQyYhm~pbgz@KIx32QhU$YE7;gI= zVnc?F#6*(vJA5S~*W5$wiV6!$_38smRX@qc8kOzT6*P2)-B&>0C-7P1R9;JbPIO?b z-I!e2_FTlhJ%A_RZ{N$Xedf=9JStIQ)Zij7ysgc1p}bji?_yE%#qz6BlVJ^{u7-?6B83pQ&6`Pk&vedg(IIf^j*Rd z*%D{Yhah)|d*%JN?{Z;sa&nrb{8nb(3qJzImCi@q?wu{Zbyl_BGA~MO*`plvKWz38 zN+AHTjTHU@(=0Jatn$wv|5!9pMvU?`ht>otfvrR(ZuGHA9SFvR?Qf{gNO$c4;u4wb z7Ux5eUS92GGzr@VE=ETqj=0Gn^P*^V;HvqG)zdYH*|2l%p4x+TZ!9EU*j@91kB%Ul zu|h*ubWiqKbmHfHwwuM_EZhQ5Sm(pLas{b_;=B0&>X6r^KCMG5)Q<9Q`ZyGdrRIQvCewRR(Z49UJdF%H(>I&Nq`dI%UU03g> zWQ?~OemqU<;)t8^ve6uoz~C%!Nk0`R48I9UdNDZmxAbu|5U{lDJ-;A2S3J ztqIxXL}v+ma4X-_I(reR@oABR@vZ;Q5Qw1O$#l0J%jO?PpXE`3e8tS^sVO0FshB@q zx|)lgLA~=$j?NgkprMQm&Q|84w2*z^0t(spGWdOUUf#{9zW)9M#)|{OsMmD&?m-l} zp2;S{Zfco;lqNj>{ktUxM}h}J@Rg#XqO~>qtUeU#U?Dlsn^ zDOnb!IF(em{~L?)X@EN|%?$nHNA|hZbQA~3neX3S4^s{7N~}9*92^|#NJu&YT3Y<| z^zJ5cLi-#|;ZA%j&D^dIi`pFM!V=ogC*wk5( z@811Ep?IhxjeGbwXHxA`QF$;Nq>U_Ev$p>UMOf z!6(|WjfGu_Cz++q%|DW0Mgn8;+*FM1CFLpd(X5k0Ll7Y$syAHy=?MuBmSrE2m8s>& zKt6Kj6NnoZDAP@8RA0Zrg?fEme5&WQ^O5@rirhgOALqf>sm*@HZ4%U3>2f0@dD+vn zKa)8-FP`%TIAWQkSZUEztc0-$sKGV){r2`>!8=Bgc$GC~Ts*;N$A@l=cZOwzdt>R=}W$aJ#sSLSl^iZ?!v){hmKd6R4nFL=v zfBvR&?kgPD`QB0%{lq16I|gOfIGdCbcVmW|kGt|IbiNIJq&rQ2pEO@QnwqYn-a zO11Bvd|N}d?{7C|w?wZWghk%9rS-RDq-u_^DvGA?o>C>m5#crY! zb9Zsk><(!4pVX)~cQZJGaWn`RY@u+9h)%kGk`ZSY~D>16e1} z1F#pQ7d|Iw5%xwS*v*kn8-2C@j-M1)u1*D3R~Yb{Rm$}oenN(1i^USr@K{`1UC#wm zF8qhjCe**G#*Ds8W-7_L`YiC&=ftCX+)hz)_D|b6bh3q-HrBz*t3Kt9!uPds>dB)c z&z_0F)^1ri8jVII>Y&Bn7cTd#S;e~S+AOl8I;D>-M@;IVRKqY|7neO}kafDW;3#yu z)jm6`;1fT$a}FP(xh3|Jvt!cJZd%L1^UOM&AoqI;p)IbzM`EO1B;lb!0f` zrU=$Y(D@3JZh-2#4B(Y#+juWpLSd{VS((R-4^iRj9GGBXoL0vrmQ^34D=1C_@w&By z^uA$PR~>>yocfrbe^GEUAtvU2KAK7ezJ$K(&c?x^TC+zUZ~PkImEj?UsQ`t3C0m}< zavwW8F4V&IHUt?_6(O>q*F#QVc7aZ>>OF+5Cm)Gd(;{fId93Fl5j!8OvRf8OYj}m| zz`V}KIxcrb+Dw%GX!3HoanfeyZBtK^(Rw66$1@IoxOuvG4bGb~!dL8Y3uVH+e;DTcP#d z1nJ%M5d@)}h&cSs@w1}6{Yb-wQ-FBQV*82_6V@wtNlBNNmsQo(nF?(>-oL!OsdUf8 zcrg=W3J)$xHos3&36_1~6X@{kj&nvq4D9d7MB2E);lz^gY4q_%59M?x^GSPyDjZZM zbd=~Quybw6kKGCkM2#J|gNg+_`z2hXQS+TGqTnagpPqpsAQtSk5X_F8{Iu@-8q60w zikt<%|Ts#^-x!sNDAJe$}pegl0B1F=Afl2rJY%(#0GuaV64F+ya|hs zi^EI|NpuWEtHL^H41G+K&4cS7ExPNn`OLmDB>hI)*nwlwy|<#X01=DG!Lxl2QcJ*9 zr@}|)i?3f_!HnRoe?|e1r;3-7iZap@4v)D>pO=?sa{l}kW*F?b=Ru=VCR1VuCMe(@(5OHOH`@hB#cA<59VY3b>?n$i-jkD^P42PbMx{I}LZOaRX8=#cDYiORu@ zH@KNllqqVq-sR+QJ6ebC<`4pX{Kwl@US8hH>bIv205V$;=z-(j4xN3}!?eE*;6a(K z2m^dVfe8W3O#oN{jl#^-2iPUDL3miA8#+`qOye+ZFkwx&S=Oe zD2%P|p9M00WyrH0zg~1d)p?D&$ko;L=;(-#kI%5as%ms*Mwe6d^1j%ciGwz$2MD_*Z=X z{2J~9rB#zBak4;yriKPHvlN*jDTx**Mg6VABP~XY1Y*>S9&L5?#H_5W)KstIKUPJk z&gUKV9mmJV-VeGoP9YG;_S%|CFQp9z#3(B(zfT*xWdno363{oKa)(WMUSZl zl|e5FWJ^lQkWd~@PWfqy2FYuACkqnx5O>6->A5yNTqjv&zabS9} zs@_t$f3c%Nb1t?!EC;=SYHsvVx;Hv526#?=`}UeYX1RPNB1ZLl?UL970vrUkCI|G0 z7-vD&OMW9S9ClUQWS8WXkOAV)q<*S|49J}00{r}!-H5+GzY-mhPp2q<@<8lFU`X`z zx%w>B^Dg|_M&ek}(%IH<9;%1ua(T#(dn8=NB@ zoxnzAUAFgg$i(->i<_7*;lV+jY45g2`zPo+mr9-waQi!VR{U_6&nKZWmM?zGK$~1v znEEC=I~!(fUOj!4qr&##9UflZqM{=8>e^{6`&WUiyzlKQnWG?i+kPlBLILp%9XJ?) z**V^mMAQ#w)YQf1*If6NB9hdd&jluK%J4#y@MEYjDyVpIc4fuXwhaO5URD!}6A;0e zm}E(GnEUK2i)J2Q<%aS0MZprs%EXhw=+_j|N{$uE!V&pKIf@kYC4rfo@AV}S2&u*c zWZx4gD48r0wu@0;s<$oV_MZA5bYAQ!tNgBq&cs6OFU*>sdwA5Al{vs*O(T|wgDM;x z933%VaEM#gZrWeP3EyT6y_Hv*i#Fv220Q(v%%lRN^egj-bRVXHj{~$9G?uT%jW&b|VH=Jv~Ty%R(4*B1R@2;<;aR{5eG6 zv|Y~WFqvudq}F3IMKgtMjLy%AA&_e~WKjTFyDUcbcPeRuCVH&@pM<)}aUpD?+h zOAdaqoO|zl!;l3}`c|XUV>J`uukR2D(!+Sjpdv`=)F`*6kaP}&<~wU!czJot^cz?E?+s%ptC0~-3cNA{ zaWuie@6miDt5Am(60g*t8TVqVTuwtn!`1Zh_aXY(`JGs8{D;&XC4S<=_ zW4vXbj~N$cHMqXMKI0wo=WO$XgNnMoG>)c7t=~Eu8yu)l8#2Yzv;%&sw~?V@vLMMP z`0_miSS$cK277@?39R!@ATAQKvms9?u5+^bs2FLRv36wD9VSbWqF%_a0Yr36VPS60 z&X4;dT*0`AT|QXAX?c5jnUUmeP*Cq1rubW*%gO-%WL$uiN<}}9LZJY%q9N@Q3e)O_V*cWTB&I|JAdj-2dg)A-!$Kma+L`AxPI$l|_(1I*8-yPoHj)s3Euo1wq+D zNZmOe9DHkoJ47j7`f>WeWH@8N8mxkRm}A^TI(InKgT9k1>Rkterl+d$6G3#*>x=^P zd7`($!PB2#{4)<(u)AfaCt?(0JY`G0`FC;Y&hMXGu%3CLx*;=O;X$4*Jr;T(Nzg3f z-$y0lEI4gGpp;G4=6{ds0eoTY{@2^y>I8u}VSUe;J1651t3-}`?y;Lx9gW{Ks_u_V zZETN#>Rvt#);{OWyp+l1EHdAuda?l3-Gu}wI9<1|E*)*{(M-L3-f z%*&UdwA;%)03|EhV2!yx$F4ebLFmFbYyzUJzLCYJmsS4$?*~ zpHrR1Z)*pYhznK4s`mo23J|a{v3-Z=DhNCB#8u)=rqFE1GMnUQ2G2i-hsg?fG_eq4 zd9!Pe+$vDo6sO#4F2sxyb(LIPQ8bj40>L^jFsP*+(LQ~&B^<@Sou7gI=%d1?(j2Qj1QSZYoR#P}){)Ah7Y#Z|rk#w;<0KfQ&@YnCY zpck$g`~LlFTbT$jDe&{0zdQT#(k*WxqB4W}k?W@wRE9EkC`r zPX$Siq-0o+=FCQMNy%+#scS3GQ=bW&M!Or*z7AY&Qdd`>W@KauwX5J46T3I1db@*B ztw@iGlG4KeTmci4$r7?rSFuygQXpBR)7GvV;;7DOWdnn(<=?bQBE{`sl1S3^nmk_( zroCgmJNzglfhIlyBSi#$22dXZ&@e*@AT8K&7JEG=O7+ziZ|ohmT>N0l5(Fq1uycnS zQvmMC(JMZH!ggO+S+ODILZ$l-*T#x~3~+(YzK~&2#J@P3zi2k=1I0RcAW$I4&w&Qf z73s)LWs&83Fy(u4@Z6gFl_g-BNJvTloxp1oA@mp#f(S5f;@AMA{-cs$UR|mr|7a$Z zt;Q91BBi=Vg|j@hLI8iqly$R_k`z4ZatJWpxCFQW!v2GK|LX(r7FEFcxnI@PhNpKo zDdBoKru%=N_@B=J!Uj-cE_E49gTO0h$^%D6@WwwkzeLaAk(W=!%>93SaQQCy;HQb9 z;rr6k!QDSmEOg^WnD+cn!p}bsxa={m{XG1~^z_<)HLjz|5WuI)0_T5v)_;C**)-5~ zr>9%9_17;5_P|CgY(^Z<3X7;=w*MldEhxi%9&TOx$uY}3Ktgv#yK`FhKF zQC(dMlb6=UWW{n%EF&n0ImJl1=C`)C^0U0xj`!EkOppHT@AE%?{3$Q*Y`@Ik>`|tk z{cK%qR@PvgNF~p^=&18dOZ4jMYE5-@1$I<0Aet&Gc_>DEZxYkUp+eDHjoZQeSJ;rk zw@fW8s#YTDX4fiH6uGsvwK+p_7pE+;&DheE(F_KL6?a8vY8^ak!~&X|zZ(MNHJM_$(l=vqn{fIXS?NS9=}OZO=X&aYyS@6%fK2*Jwd~T)+ufRl&25GXyZDE4zAjeeZe>lc{Pmlq6z z(BiHep0rvV5TAZj+fahqLa_h_h5dff(X{P%eqN3!;;at!7cDt|gK|?uZ=H3zgnCKq z#RZ!8?_?ZfAv{n>pWh=)nTj5D2mE$UB2M*>T5N~nkc)>uE+7VCdky~6E!>e&oI(bl znr-mDV>RrmMzpoHjpi#wKZ9Stew|d~|4cIOB*>HPmX?Rdam5qsn+Yo%nx!BeR^;ZI zh=hZ<&<4d7a{W0H4fXC<(eZE026T-)9Nn_>LP!IkWPJGWfr^R> zNTH5gXokI=?edkGu`zW$z1i8>{t*!)RHVTF@Au!nWu6}SSKVSO&BG{7a6 z9fkVWxAQ-JQh{eNK#33FXN@M)Dt9WZKeJlU%dOrk^xa<;%P6oEvG(SiwvOd05(-$h z)Ky?`GBsTpe%^1++b?W}AS>=47;w`>`|S+~D2bi@`SW{JDf3BZ4tTcRneTc^;>r~_ znP9J2L3(=7)~l@$`#?|-2Y^ZjBCb0~g^4d!Gn#KmJ%hG$;P=-*I{f~yvs`((j2t0mVNu%@;mOXPJV&BsB@qrl8 z!B;?sC1L?IOHfGB*=23zDhbzUmIqMj4bb ze1rF-k``tVn~@loktXsH7Ub`Qg(blj5sRuJKvn|uU}akTd?O>9Bo3ze_vZnF`4FgkIfh!b;L>lrSzK z;iG<_sbEkYW5LWx6Aco-x?OUoy7eB=c-aNPN(f@)-96 zj71xM6zrIt$L0Tq_u^+dhr>}~;0yP%`iC&9I!4sZZ>*2?;V|u{j-adFw)C#bt!r8)`>*K2Ic+&?QiHmY z&36WaN*MlJ0X*T$E0Chf!Z-(S?O_2#29)XW~R`4migKV#?F&sKjG6 z=HxzLhbccJH!wt&7t*Ss)aPPwW)RE*^npFx^h@5cs24n(?bF43Ztm!Dzq7EK(;qQI$Z;+u*|gQcb1*bO_< zj>sOH>%ERfth}WBp<2N~NH1hiejLP^p^)+ud&*-w9RQpjD~x@cp0?g^EGQ~Ux4y0m zy~lm0=r?hXDtuWdZkIF)6&%ON$C7jPgV?J_LB_?LUx9Wy63iH1RxA~N6?Z%$god5} zIw&Z!R7rj;0QfyF3CZMf=)Wsan z=0?KNM^k>!6D)Y;WZfS^3I-^nd#Cqca^w$3*@0UTHg*>obg z>5?n>>}n-S<~??f-5A>M;_Q&Mwa%P^Q|tcPy@h#Tj2U6VOp;HP8j1o}l^&~;+vq3@(&{v^}13#>7 zY&L7%Un7A|4LAl-|MOEoA#3iIrMp3*JRcet%c7F>byZbVXkyD!f>kCT^76VEuW+>C zoieZ=l5e^cKb9p`;{TFNj}-3xw;H?N1Ju|kJ)p({aAAW?2O#|?&QCDVefBpIltZYwD zK{fI_cYd9oyYZr*>ejJ5@!L;HSYeWD-&JQPjJg&$Z|DUHN~fQtXS>_`UM+W0O_X-DX|5nh1*CKQp7Fg( z_|V~qTN(KJ9&@WT^XKZx-3r8cye?UDosUj;MnN$bI`$WFMX^?Q(`KaTR#WJ zA`j25hCTLU((Xu9PO(hlNQ>dP=k-AZk2cpcck=z6`Aua?5(d)yabLF>?&g$RO$c&B zUl&OjsP<$PG?HG8oTR14Cg7lMVq~$kUUfy?94^XLt;w^JTLhZ1xRG1AG|bj`Ha@-h z`)1ZTo4a=z{ka{GoY~pM9|fEzM0V}WMENG>NjKi=B(O2%i@fLW#5!y#&li%Qt)d`Q zI?FymMoGDh%@9DO5FEl^BVl91F(O#y|HDD83R#qtZ!1S@Cgbi>rKyh)Y5#Ox-s}U16!QRiuJGSit_N$!V zWnFwZhd6n~Ywu@bQc{B?&RaWP9v-5}C6}+2Wo2b+eo-j5+ZfHat28n)T>I&)%E{Kx zZ3qwd^wCO}A7LN{=H)z)E(`Osbo-7ii;4?nA{EM6W@Y2%mMXYQPHx7N>g?iTcN;E& ze$ohQ-Cv(rgm$!OY)==T)0X<)ezYCDa=X_4M-EJ%0Mu}XvOk+-?FJTu<7k@A`IqL6 z5=}dr*x1h%F1H20fxEAe5g zQ`hm)Kqeu#p16#J^E&QlA!_8D`FenzECq~w+kiLdSd<*$kx}RJGpb~6GOABAX(e%(Ibr43(U^u@rPU_8rHAj5 z_Yi0AWkRgE0A`Foz}Uf>>HLSH*}x(EakHMvQtxygtUJA-p`o&}5^%<^5a2G9;eNza z`|k@MFt)LBaqX|f$)lfqh9%C{IW13D+Xy(!ZLlP4zg5=O7VEXqS(cdR03o^Ke04D?oMmX;8~o#vm8C2ZeuS^NGO=y0I+ z;TI5?k(IcV#Xzs4m@Eb}TDyJr*VIpX|LG{({G(+sDg=Ygl7BB-kNZA0R_}WL&oeM2 zg9@CWCs}lMvVA~wr7y0Z&i);Y3QZyfe+L%LWfT8igsGkX+Z&>i@J9DdfKib_|qM zWqS2-iHUBT)BH&-u}*ud17E&;x#xHE29>D?2Q_1VzkJe4Y~msqc&tVzRk+CpG(l%p z@MnxsfbNfGpTl$XkiUDN`05(!Ch$}aiR|G=Aj^CD`h3pOKR5f?#nBDq=2fYwA7R=l zsa}ZW2_Z8{wD;1_E=VTIj2Z2Ru79+b0BLc)WC;G_9prk=j+X$2P%@7zf5;22xnvX2 zl}$YHZ;@|P^Z%Ro74Rs7()#+eVmAQg!85$mD)1jv4q*A{>N?bXvmR&PN3Op?zKu8c z&VWs|M{Xx)-{n<8qc|+;SKI&>iAYXeIYIvH>c@zZOyEw4WMpFM15TEH5inPmBIqeX zn;WMO2TuqXSi{1?`X3k>nXdv;=RbHSa3LT-!tS;{(g1&ZAAHr2zNA0?MfQ~W04GVR z3?exw74*a_Q&lRVjK$CXbc79b_uowdeY_k1XXAxC92f;ORi>@2|FUJEYsfwnLY^dv z)I1ACOV9o2-Rmh^^IpcSS1H;(n8y4kOM`jUd({5FGzL=U^6!VLz)w{!V;8A8=-hkQ zHxvnd0{V@TTf7_+OxHtRn-pWH95ZGdJYFz#z~qbJ&H6ZaWtj0II-P%y;<>c|zKya4M&6A5T#kJiZa^b?m=dAn1N0b!7U00{O| z20+5}-J?`_kU4ZN+M1qZ+Jgjg8VdNTp2_RIzzwX9gcbu*yZhJyjt;&@3?hTd2k=%^ zj*H?28po8Yx3RImrhx^zy9Y*riVn83DVUg;_~y+U?`FDl{`7%|kf)->LN2TLVl)`u z&!ef%_vG=H2qpY;^6J}1_o%3Pm4YuxS*Wa>oZFKh-|xMN5IfzG@Z(G6K?#iRmn8f0UH&pWZOute)~pjLIR)N^p~6*hJRf=*3aqdeY9W$h-9OfYq{Y z8%#+~ws@T0&HfD|vY9)hoc14SG&DBSx2{i=9V&5#5YVRejPCF6XFhRb7ZvrDKgHyh z{s&ZC!X8_;Ha6>r_|r2p(KJGB;)>t{Q&cYkv3B?XwYP@}7(}xCQ$WMgG0oEoyO+nP z!WlnjnJ@#p(r=U*{h=Vxb+u7oeT#abR>n(o6_`girmBdKff5H{X1>*!E$UNKRyLHo z0@kZ|mp+_mJ(f}0{0@GrOF(?>rw!lg9RMcE{8e6|<~W9Er)ueam+{;2LysmaOfLNe zRChrpKqkv2b7fLeR{|1g-~af}O0B|lOG8b(4Ac(S_5ug;y$o`x@a~1ckJZgEoi%3#r&CFmAM#j zVcx1O&mH{14NQh*N+{by)Gmt&n-8UCRzobbR>}SEX3bf?3$1for}(?-36$VP#+seX zUH|Xq-PBL#O4;1H#OQwBAq<&!V)!^6EOU=0f_nIPZN8=vA2 zvgm{DsNdIIrx78*y>L&|`wQSg#*ph)K^YkB8$?cwLx+T1Erm$#ZJ+Dy*~x$%w~fjI{V9gt05Ss|VibAHpUd<&+8khOJz&jH6zO zn%MyzG~BI_B>NqEPv%QB1qH>D-angNDgC^D5PEpGsG3K=*)K9?`;tFFQBYK9jqQoE zK~Eh(RQ>+_xuD=Rs}tk=j~`sB#-b#y1S%>j0XO*h`4N7uTG2ONGEW$K6cb~$#;U5S zcGlnEVY=M(PE|5q!HEgE27HB}HX~nCy@4bd3>9LX|3aIdeQGrjr~=mtjflV_AQ)fY z2j*%60|PDK-X>uK`&GaVP-B6Tbds8cBp~_Kt5+i}OUf?~T6CCzGDMh_o(`OO5y{8* ze*gOQ9CJW4Gvwf*Gk>aNhnKkQxGC}qM^e{f!XLM zw75l?P_)l8Aq>#K)`!$+hUS{9fd$Q&eP4W57XBo2%W>02Ctj*7g38_|1Y@2uw(784 z;V6226BzveIvUp2*FmuZ_CnKl$i_ddW|z+2KVKX3+O2;^wB&nOeERtDofI4pdzD^+ zoyI`U3NI!r{rvfP01#yJ(h^ZYb!N1eMvkQol0KkGiC zNLbtWJDHk;gF~7TDkj#lva$lyJ^&k@HG)|J$Rn}D8#va1_knn4>9r@Na+L}kc(Xji zenb;6aLHqK48h$0r~o-e3Atf6HlOg+TtD|i|JI$Rv&Yul_ZF28H{%3HP1tLhZ|8U~ z1oJy`CoPTx%a57&+h*~)`}FizYZ9?2LHz>+?nr52OlIiq2AN?vYSS?Mah|PQHFbYh z-E4}7P(;@nYhlg6#=R;{ph-#!Y^Et`mVmT$Hdrm(Mk=F%olpq@P?PSd+fQerU zO`_C#c5B?q-d<`)ci7YQMj$%8h4Ee!1rgD=xw)szC}d0=*re#_p)t)|!u@wX(HeBe zwiK=|?@{(cxVUH|888WzNGSoBh8;GhCks+A23DV+PeZk8__(<262gJj`*7P+zMG1c zR&**EK91UlFA`b9JdJGVyT%xY-SHLr>D=&{vMw7g=Q&mPrQ|em+s<`PgvDS;q zG{zx1)=*zpt}hWjE-cx6S(jkckFE3pbn4vW;{1$5qxiLj+09=d`79zwMpTl&asRW* z^_1YUKzec7oUTq6a5!kZ&zJ-b2@&S_fRHz1?)}(1k+)c2j>$Ox!pxYQYzBH_M_-hY zA_F{VIWX-WQAMC3hFn?!*QpjVD}5e;U7r!E8TpUQ^e-3!fKJE48U~>d7*}X*yn{($ zz@R4(!iEycz_%`CDRA*$8yh0IE)S-kgXkSy>tuxzCO|m+!Gkso`VWQaP-ZL=#v(nFN}|m^7+li5`W7Tt&)d;2FeR?z5M3@~^I=kZyyZXDV=$L@EsW;O#xcAcp^= zlc+O*h(U*cP+;zckB<+^7mR<5R|XsOqOU9i3A%WR1&zRrb(3f#JLE|)0OP-or**je zT3^3AV|AFjGA=7ED7Su2kwv3NrW2Xze1*gK36dfCFaDCA8UG)Zl8 znVg!CmrnB?$jg7v&0-g5<&*Zt?KzsjdX@Xbb9^L;G zKbQ3!63`mnkAa41;v)uBGhh?L2xpgY6592*{UcVTG6q&zx4;8H^@BlYmo>=W>WGNK z86yJ&;PvkUB!+lt?$WWEfp-}MZiQR!pg#Zyv!TrHfXTU>Qw)?s1$+dq;l2r`!VHlT zgLjl4u@K3LEp#lv4!V4j2g_Xiu?s>kRVLr3<$B;{FoQyuks<;)k|!r&URhs1B??>V zIoV%HvN%fWc28@4B!(IN1|{Xx%*+y0EPFgS)WOcp?Ves{2u@tM&jDw<7jOzfL~1|g z=d(O|6bphYGYD{lRJ`V_z90Kngtwb`{}uwZXxtq?P*Z=7^lznv!ib58UjIs=%$mVe z+=_2{O>yz@6Q0JUN`5UG{hj4^Y$b3)k;JWEc-(yT=*^o16B83J5UOucnfCz|IDg+{ zq=g~=A*R$XKOGR5ubZlMM6Jj)LGt{uZrw68Eq2YqmBm{Ut#bvesxiaJ|qBabg2Dsa3yPpeIb!M%fVES>uh}>)dIZX%W?{22_ z7(UDpa_Qolhxi#9&S+Pex3+x#{8=kF0-SBsCIRIb3z;PB+c&kF6%`c#zib`*In2W& zv#FXCjx@iQE+9dFTjQRAV@ktKr>MSA_dJ+{xcF00f(ZDvfMWot0fr6za>G^-(IQRw z3j&|3tH)9aU$R04z^FhA^{5mJlL^t$=xV zL8bLJ24tR`ocuUHJMZ>C#hQZ{!{U4Sey2JGbIcDg%r`ZO?549*sHFCT%%_4?u9prj*GSYqgjKz!M+W>8$naDv(?5Q`Ae|Fy}x z*#NdG9YxbxDeSiV5tfNfR+$X-^8(%y;oxf?OTh81TVp%5NF{Q9v|M)|PGo8}f45!43)bDr%o zi2RdY%131dqtHh8l9+7?LeaO$T1N|kgc_xKY-ygn8*ct+McL;aW!}2Fx?-sh;3r$c z$cKq_gS)!F#IQ?Gi;FpbvSr&(P18SqOyPx%hnN1;x509j>zHT&C@AB_+Mxced;Y;1 zP*g@UHFkUp4^Y5Qa|YZ88epYv<4YgV$TR#o^~~5fXH0)XgeT4V`cP`sa#0yv#rK@& z+zdPjkcz^>!T=Kr*v=Bs5kGBr^w>e~Rv(u0s;WXg&jV?ZRgus8^c}kAmyl&0jU!lUts`xX~}(=9QF`gzA1+ z00iq&FeRnGt1F+6x&nT-|9O3kgQH`)<>uFNkdmd)NeKzhik-p;TXuh^v$WV%?qd;` zX*KdFmz6!yc+n0laBq~$4a)p~eF#ye8yOw-%2dLSpoCB)az%@vuqj4HMvhWFV_+%Fs2T6H(sDo$PMuM~FEUdUJP!q8z~rfl7WP)T@bj2M_V;yLHt03k7- z@sZy_VZHkUG2yJqZM!9M8%_He`(_D!2?>dWfVXeo0-GS=XXb-RC*6z>A$PaR*rtfd z6cHW)D?S1nW54JAzmALE=&qPPKm0YEJITb%%ZoS>Re zMvNMWWI;|IJylh3aJ)j8LUI5nijg3+0~}`f1p6U{(ILql)E|hz?O%CneS3G`)Tc|n z9aIEj{tS!zwFu$!sz4OeG6Qt-WASR}_{TTA`{Hoq?G|TcI9L+EUPK!Uc^Ax&QGh*q zx)Rt_f!Q9|Fo54ab`l(DKjuvz05UjHHozUOtWEEdwZaqxJzu>dRzU0=e%-kIO z5f^exq_FG7^k9aS@M9Cvn$nqa`2Jx@LRrz$7tz_6M@4o%7rj5TgJ2jLAISHW!P>kb z2@vi+;Ce59BasK&RiVnJ#=mt(`DsGCxT)!bVitq8nwJjiv9$7Ov9U%!yUkysy?uP- zY8g1&Y0MqM?;_^ekR?Lr3cvm%_x>ur_xc-=h0r}~j!lnGfHeZq8usM-! z**6P{d5#sICFk5ga5m(1?o^zEr6ubFyv;T<&#*cbul%y_^XR>wu~&T92pQUj0694v z{M-Jefq{L6)|dQtwgr0{(+(hAJ_TS{Iy4-P@d}jnJxa zt#I0%fch%{crkbfeQN-_k_n1k?3j`0<$V5&6O+3tUc&dF2B{EhLlC%6UaFH-oP>* zZ2&K4A7RXLE5{G3A+0gLkleu&?n?&0QLs~8nAR2rd$F3hKMYjX1{8oaE?Q?{bW=nv z?wdNo&?mSLx8{_c)fTUT`=7tfIeSsGY*Jjk$3<%|JRj8c!hz1$Z`Zs0 zyte;l4Eo&?PaE(guU@Z)K%SNFIL*iiGoiqbICS`^&7DL`!Rtx_iRcHwY0gPizN18| z@>}APFVoZ0BNV{zaD-aq(QF%t)6>5UECIG3rcto0ZnOb_^YS&(9lV^YWn5_7`V)cx z7b8dprGgS>*OVsi>OgwFwL{>%$!TeO4Pf3F1O@%iMcS}w>T`11 zoHVU%x}cM3{nu4dCZP1OqDL zGH?jx+b3>|MnU- zqp9A=@bCwA5zny1N?XSJ&pyL>LMQR?j43CZ8X9!mCW1I_;;8N4)naTTHhm@{+3N2k z?^WR$4qIPa%h32=PEo6dUa~!-PlE)(&sQks+c-E_Br6~$tdMt`pG<>^CKl0!&t&uZ z<~lFX?YYH!LUvIdM*jS)IrNYd+A0Ea)F^<=v% zIh_o|Op9a?pxbGa{DdV@)TRFOXQyW%s+DYKpACV6ha0M^Fb$>~+dpL9-wi;xLgug< zI@_b;C>wJ$RM+d62S&3>WEp2Gp9`Bw)&zd)e(?qvh*Sp5S%J1KC#u|hp!%D|E3$|?}& zY%?k)HU7#6HDt7N(8&wD!7LfHwNo3NeGS8W>=+t;uy_|px=C1+KfaaJSbvp%qX4XO zxv^!|kt6vS1gH7bpZ7)j`vstD18VZQX9Pn;%+-I%;Uex#UrsE=n}qjW@?k#=;8(9+ z)ev@xdM`$N9`3Jg49@jOGTMnclKw!NMNrqN^T)rolo1M!E&C>lT=zyLZu;Z-S2#>b z^es$%7Q4m>fmWY(UWYU&9J(VWTvi%kXp{w%?G6aSBOi8Q69Cc*SJwbx$=Z1jO15Ryu^RU)FC8AZ@-&^pIfQeELMH8l;T zfO1;Dh3Xvmf2z9jcqq63FGO7}BiqfsM6wo@iOQ0th!(~+Wg=3RvP5Vs*S<@d5aLGJ zvt$d6ZG;I$S+eh3YsQkv((m&O_kLf$KU%~*=XuUK&pGGw-s0u8UNQd&4fgMUJ}40| z1lu15r*JaeE`sIB<&GBSc{T=PmK1}IFM86_(y*##xe^_UD2*@(WR)^FuP|{Xpsebu zvLas`Cte0y3_j_75O|x;sS*Xn#O^GuN*j7k-|`mT=H_Y>eTYI9s4f>p0YVRJb_18sEDvIEJuY6<5ml`z7@*w4mU+Mx@M&u$2M`CCY1ax ziqzGEt`q*=tiKh_e$(kIKV6>cb9d%TtRacg2!Ytk8F-D2gqWDrt{H^~gBDftM~l`U zs!o8?TEKmbHM^9MT$>Os=XYkyL7I+4#Ly3G^82*1p)KuCtsx83%9NGtIPV z-v-Ubbu?vgHLw}1E(}(|RD;|49?zy}R^X)J=y;-}+;(`G9m)7WF6^jAl~~MZO`J7D zz;Keu*vz$UY=s7a^3A3j8ynwEw?6VWK8Qp!EYJ?5K{b%9^epwk;L zIPBjah6n^qhk=ko=Q-7Kz|Zo1z<+Q0YIyK6`O(wyZ0@N>5Ngut^!ye3i#f>GP&Xti zs|JPTTQuqV2UkVd43Ff zHXPrCqU2^}wa=_L`CmO#9CDoqvn=?66*nj|z(wm0B06Enz<7^2b6$CjgN;o@LFlEl zj7&+);oyDuAhO1x4d(0Q*x1E;Bl%><)OzyJ&=71Ui!x6n&pgeXmU8p!F3rzJHL!~$ z3V%i-W)6eNQQw1eLGOMf>zk20zIIURu5fR#({*kSrBY-!uo$K@I+`)7=BS}@cvbYdV6{bKE2bU*cTvhu-@Q7sL;;MzXNA7I|14Pg>j`t&eU zw5lZ3wspBn97>^RgSghtB%&+7#Co+4lb>o}GTKR3H^S}Mos_J%Z=Z+gim^pTHCMN- zox94ZSPGPa-WQt=e3s*gg_Qx+Nqcl zK>s*X!qlLTQL6r1Eg;iReBrBAQeNd@qfRRMSbDyWo?fKfD}!rSuliW-QM>XxIAich z)Y`2})WWhd<0%Ydu3>a>QO~uEVT+u7EG%{Or$a*TE#6tw!-NpN5<{cvC0tDd-E#2t z_5ffvbxB$q6pP|W?;SEIno4N2F&M5gkMl`|4;dITOJbFVq=GpMnhd3b;uL$c zzKwmAj~2ZaFDWlL5J1_L!b!lx3T^O~-GjsS2d6=F9y;KOte2ia%%Q@}_!t__cogTx zc)Gc{nfE$TqCc1qB*lmcDpQ9e7+=s~)1QqJCV7mfgEg`C9a(!As!*$dkj+Qt7~-qz zYTs)wH;+mmhAY*r}#?$dC~SE>bt!ml7HBXRei`_eU*6Si($7V#99J5<<2(tkQQx7K`Poe7!T3dUOD82nwnMBqbylt4IH=_I^} zBd_@k}S&OKh1d*JrPKxdOVrK7_7W$!q3uu!l$IrPm3G z289)_GH@PXVTs9&(n4T%WPlF8fYr(0pHaQIOxDl!LuKV`>6`7{<0|nscylqZP#PFK zYqBK6^w>=KxYMzghk$INy$`Tun@~7L2Uxe?j={ZF3j>g}@oC&ke71F16mzJB9TIrGw+(W@}H0C2oXPb zuHW#eFpv%k4X+=sscUIf+--d{*Ml)O?(XkzIRr>_Z8D~au5JmH&A6*Jf`bh4g=^hX~aK zj{*#g=`fsWS``NIsWGmkgl4wsqVSd8-aw@;VoF1p92%!$m6dHfP&`KL?i>5L+i3DUwe~a4<-~D^4Il5-J}U zZVc&m=>l;*QUH__fB}89DBwjq2+GPns0c&x@Kp4>pdU_s8QfY`;FL$E(R7%Qh{!0- zr^pNB=r#_!K7;#BuGNIuH|G7950?U^XsoOq2x3FSkFM-sK=J3)gXx z5&IwrOjXRCL^|{`_x55!JGGL#G{*k*8``_!3mh+f2|wIeV&2*OsH>+dFep-Zz%rbso_FT(zZU(h?0>b!$~!H{wceFR6_R+v)bK zt-*MUo6ug!g+Cih)6&$rQm_es{`{E!;N?b1Q|BNyU82ig4}{WJ&TW2!+^h`9Xo2Bg z*fY)&bUyyqSl^TP%uxb`sW-<5d#jX69@ZB++(usjMKAJ07sq)Lj(#qunDT9q>4_Zr;%J+dO7x1PQ^(>~SsbkwTn*&+`=mZ#LUonQn+ z4Q#hK5J6eAxFz0g6rsf!U1$XD^)J$LL-G0lDYtYtwyL^X6HhGxRg&eWrJ-@}5nyi1 z=q4!YFnL{{V$ock;SC{yXXTJ7VR3~hJnIQLUq$C5jEfD*)E2M7|6AGcG&Vs73!g4TF7C&hOI6Va#&mW|3VHa zxwcAY>TV%mp*B&Q$F$y};08qK{k9jKnxEJ7MHm)rf!TdHFIjO*^YQU@XZ`wqHR&ed z9Jx!iyDowv+BsgKc*g}I!c1e{npJ5w{*3qh9;xi!8~5@RcUc1wFH{Wp73AbN%1CAV zjzH{7P(VOPE+~&cY^|+faYjLbYo@*vihyj2w3L*=n``{a$Bs2YWz8=T55KIV!o!~x zi#A?@V=IPgXkai@IX*s4+cysdjj>n(m(yl*(&3HSpXrsuMvJtkK zP#xRY*xVWhyB#FFz>!OPv&IutVCY&%0FlKJ_Z1IYK}BJ#HeKI;bh8+Ma_Vb#>= z2lyBP070~*pwX?7&gao0=579pfW!_Z<2gY|EGD?uJ!6%hr+Vn3&+i^XTkv|JesMmo z$zM}Uaob?3PP=%_#w}o~fR3*>Ixi#3@U-OiwcH&`=IasU1~R^KT7SM)I3TzRS~&nS zv+@l(=xc~upizrf`PfE}HV_?z%E*ddoqV0R$4%%9@YIdDciusc=DT-nEW3B_b{z{7 z8GVRWgWy`)>c%fI2&5%g%N{dQz}M)GSg^Pw%(&!zI}iM0f-Sk!Y8A)KQtseZMWIuc&8F_VG^9RRWy zmb;E&jf{e@x615{Dg9O6-m{;(Ol>EzihZs`kflC^1^4Kcm+(Zd+Bqpt4;Bx}sTVqh z?_muRUL$;N_%W}Z0BoHLgmHhkeMu0TAiu zfzD1b>^k|6{dO%)O*M{LSy_RigTOQR4?r&Dyyq?vsp_9P(btIg&E|G28UR1lL5j#v zi1W(FtZ*clJUch%QknEXLnGj)VDO2$+FFRR zx>W$ka=ak0I<~ir{i{I+C|n@7R$miMS26kx;G*C+7)nG)$Xh{Ij!8FYA78Hth)TK1+~AtA`737kLI13ta(WU$JKofOF6@@!$ z6@?~oC#BK?bA#DH#ECiAg2L1cb%**1%4j-do$ZG=y+HF#jWuyeP>2u2a)E|*9Q%3HSznl_YU#sOXTYtT^()hor|&jbJ)DdB(6Q{QxPC-T%(P!rR`zr2W+FyR>YE^J;jAr=ITqO~ z^VQWpxi?9M{x{nzL8V}9R_0*t=(q?6F}q_7i?XW#5M`c8$N`|(=8S>WeW5apd9f{E`rrAEcpUn$yHqwh{LBU zg~282`ybfZNo{0IUpSS9w_3;bDJiqBJdsg3x**K3@+*Vm6Out-_u4Gz)L&wD>ov>* zXMatlj>OymIS!(rxdBz581Fd`_5zOkPB<^B74|3L0NJQ1f=2<#WM7)lm2e?Z2oGIc z_|D_qhHC?u{B|ivNEN}M-5xanLmw36Z~aa3v$0yD;+Ix+~*4)L{6AJUG&NX0-a zZ=jN)$%=PAvaP&B&eyam1o&b<$i>V5 z0D00$5n6581`P4?>gx5#QSI$SFXL`#VvEZt&#Z+^E_a>d<~XC*SLrS09O^ z&t$KyE6B^U{j5J@nbXwL0>SeOAaJzil!y9g$I}8;*mhdTzcmk>`cZ0qiN6Kh_gAYV VlOr48ze=FHq@kz&R_&JW{{cF|N5cRB -- 2.54.0 From 0d47360f417c1db9035e1d2c66a2b7f12c76e212 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Fri, 24 Jul 2026 11:55:07 +0200 Subject: [PATCH 20/28] pushing picture for itc pressure optimizer --- img/odb_equipment_interface.png | Bin 0 -> 53154 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 img/odb_equipment_interface.png diff --git a/img/odb_equipment_interface.png b/img/odb_equipment_interface.png new file mode 100644 index 0000000000000000000000000000000000000000..424876d1733c917ada73c0e6692ea97f70859b6d GIT binary patch literal 53154 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 R%^T6J$&2Cj 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 literal 0 HcmV?d00001 -- 2.54.0 From fc733bd37f94dd86633b3c861cd6ee586b8e85a8 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Mon, 27 Jul 2026 14:55:35 +0200 Subject: [PATCH 21/28] creating defautl template if missing --- src/device/handlers/DemandHandler.cpp | 5 +++++ src/device/handlers/FeedbackHandler.cpp | 9 ++++++++ src/device/handlers/FeedbackHandlerConfig.h | 5 ++++- src/device/handlers/SettingsHandler.cpp | 23 ++++++++++++++++++++- src/device/handlers/SettingsHandlerConfig.h | 5 +++++ 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp index daeb96e..17babc7 100644 --- a/src/device/handlers/DemandHandler.cpp +++ b/src/device/handlers/DemandHandler.cpp @@ -14,6 +14,11 @@ bool DemandHandler::init() { if (!midas::odb::exists(this->path)) { TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Key at %s doesn't exists", path.c_str()); + + midas::odb o(this->path); + std::vector v = {0.0}; + o = v; + initSuccess = false; } midas::odb o(this->path); diff --git a/src/device/handlers/FeedbackHandler.cpp b/src/device/handlers/FeedbackHandler.cpp index 3796fc8..27297f1 100644 --- a/src/device/handlers/FeedbackHandler.cpp +++ b/src/device/handlers/FeedbackHandler.cpp @@ -54,6 +54,15 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, } void FeedbackHandler::updatePressure() { + // If the key doesn't exist, we create the template. + if (!midas::odb::exists(path + FeedbackHandlerConf::OUTPUT_VARNAME)) { + std::vector v; + v.resize(static_cast( + FeedbackHandlerConf::RegisterIndex::MAXIMUM_REGISTRY_INDEX)); + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] = v; + } + { midas::odb o(path); o[FeedbackHandlerConf::OUTPUT_VARNAME] diff --git a/src/device/handlers/FeedbackHandlerConfig.h b/src/device/handlers/FeedbackHandlerConfig.h index 0b4c522..16df396 100644 --- a/src/device/handlers/FeedbackHandlerConfig.h +++ b/src/device/handlers/FeedbackHandlerConfig.h @@ -20,7 +20,10 @@ enum class RegisterIndex { MINIMUM_PRESSURE = 8, MAXIMUM_PRESSURE = 9, CONSTANTE_1 = 10, - CONSTANTE_2 = 11 + CONSTANTE_2 = 11, + MAXIMUM_REGISTRY_INDEX = + 11 // Used as a safety guard for undersize array. Put this enum member + // always as the same value as the highest member }; } // namespace FeedbackHandlerConf diff --git a/src/device/handlers/SettingsHandler.cpp b/src/device/handlers/SettingsHandler.cpp index 779b241..3312478 100644 --- a/src/device/handlers/SettingsHandler.cpp +++ b/src/device/handlers/SettingsHandler.cpp @@ -90,9 +90,30 @@ bool SettingsHandler::init() { areKeysValids = false; } - if (!areKeysValids) + if (!areKeysValids) { + midas::odb o = { + {SettingsHandlerConfig::EQUIPMENT_NAME.c_str(), + {SettingsHandlerConfig::DEFAULT_EQUIPMENT_NAME.c_str()}}, + {SettingsHandlerConfig::OUTPUT_PRESSURE_SP_INDEX.c_str(), + {SettingsHandlerConfig::DEFAULT_INDEX_VALUE}}, + {SettingsHandlerConfig::INPUT_SP_INDEX.c_str(), + {SettingsHandlerConfig::DEFAULT_INDEX_VALUE}}, + {SettingsHandlerConfig::INPUT_TEMPERATURE_INDEX.c_str(), + {SettingsHandlerConfig::DEFAULT_INDEX_VALUE}}, + {SettingsHandlerConfig::INPUT_POWER_INDEX.c_str(), + {SettingsHandlerConfig::DEFAULT_INDEX_VALUE}}, + {SettingsHandlerConfig::TIME_WINDOW.c_str(), + {SettingsHandlerConfig::DEFAULT_TIME_WINDOWS_SIZE}}}; + + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Some keys are missing at %s. Please fill the generated template.", + path.c_str()); + + o.connect(this->path); throw std::runtime_error("SettinsHandler init failed - see MIDAS " "console and take care of all the errors\n"); + } this->pullEquipmentName(); this->pullOutputPressureSPIndex(); diff --git a/src/device/handlers/SettingsHandlerConfig.h b/src/device/handlers/SettingsHandlerConfig.h index 4fec93a..404b5b8 100644 --- a/src/device/handlers/SettingsHandlerConfig.h +++ b/src/device/handlers/SettingsHandlerConfig.h @@ -13,6 +13,11 @@ const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; const std::string INPUT_SP_INDEX = "MITC_InputVarioxSPIndex"; const std::string INPUT_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; const std::string INPUT_POWER_INDEX = "MITC_InputVarioxPowIndex"; +const std::string TIME_WINDOW = "Average_Power_Time_Window"; + +const std::string DEFAULT_EQUIPMENT_NAME = ""; +const int DEFAULT_INDEX_VALUE = -1; +const float DEFAULT_TIME_WINDOWS_SIZE = 30.0f; } // namespace SettingsHandlerConfig -- 2.54.0 From 4a42f980d08b51b6f81b2fbc48838c03d78dac76 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Mon, 27 Jul 2026 16:26:32 +0200 Subject: [PATCH 22/28] adding defautl template for feedback --- src/device/handlers/DemandHandler.cpp | 52 +++++++++++++++-------- src/device/handlers/DemandHandlerConfig.h | 6 ++- src/device/handlers/FeedbackHandler.cpp | 26 +++++++----- src/device/handlers/FeedbackHandler.h | 2 + 4 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp index 17babc7..576e23b 100644 --- a/src/device/handlers/DemandHandler.cpp +++ b/src/device/handlers/DemandHandler.cpp @@ -11,13 +11,19 @@ using namespace DemanHandlerConfig; bool DemandHandler::init() { bool initSuccess = true; - if (!midas::odb::exists(this->path)) { - TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Key at %s doesn't exists", - path.c_str()); + if (!midas::odb::exists(this->path + "/" + DemanHandlerConfig::DEMAND)) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Key at %s/%s doesn't exists", path.c_str(), + DemanHandlerConfig::DEMAND.c_str()); midas::odb o(this->path); - std::vector v = {0.0}; - o = v; + std::vector v; + v.resize(static_cast(ParameterIndex::MAXIMUM_PARAMETER_INDEX)); + o[DemanHandlerConfig::DEMAND] = v; + + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Template generated at %s. Please fill it.", + path.c_str()); initSuccess = false; } @@ -25,42 +31,48 @@ bool DemandHandler::init() { // This is checked every start, in case of someone change the index in the // config header - if (o.size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { + if (o[DemanHandlerConfig::DEMAND].size() < + static_cast(ParameterIndex::MINIMAL_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, "Minimal pressure index is %i, where array size is %i.", static_cast(ParameterIndex::MINIMAL_PRESSURE), o.size()); initSuccess = false; } - if (o.size() < static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { + if (o[DemanHandlerConfig::DEMAND].size() < + static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, "maximal pressure index is %i, where array size is %i.", static_cast(ParameterIndex::MAXIMAL_PRESSURE), o.size()); initSuccess = false; } - if (o.size() < static_cast(ParameterIndex::CONSTANTE_1)) { + if (o[DemanHandlerConfig::DEMAND].size() < + static_cast(ParameterIndex::CONSTANTE_1)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, "Constante 1 index is %i, where array size is %i.", static_cast(ParameterIndex::CONSTANTE_1), o.size()); initSuccess = false; } - if (o.size() < static_cast(ParameterIndex::CONSTANTE_2)) { + if (o[DemanHandlerConfig::DEMAND].size() < + static_cast(ParameterIndex::CONSTANTE_2)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, "Constante 2 index is %i, where array size is %i.", static_cast(ParameterIndex::CONSTANTE_2), o.size()); initSuccess = false; } - if (o.size() < static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { + if (o[DemanHandlerConfig::DEMAND].size() < + static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, "Pressure control mode index is %i, where array size is %i.", static_cast(ParameterIndex::PRESSURE_CONTROL_MODE), o.size()); initSuccess = false; } - if (o.size() < static_cast(ParameterIndex::SET_PRESSURE)) { + if (o[DemanHandlerConfig::DEMAND].size() < + static_cast(ParameterIndex::SET_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, "Set pressure index is %i, where array size is %i.", @@ -113,7 +125,8 @@ bool DemandHandler::setHotlink() { void DemandHandler::pullMinimalPressure() { midas::odb o(this->path); float minimalPressure = - o[static_cast(ParameterIndex::MINIMAL_PRESSURE)]; + o[DemanHandlerConfig::DEMAND] + [static_cast(ParameterIndex::MINIMAL_PRESSURE)]; Event event = {EventType::MINIMAL_PRESSURE, minimalPressure}; eventBus.publish(event); } @@ -121,21 +134,24 @@ void DemandHandler::pullMinimalPressure() { void DemandHandler::pullMaximalPressure() { midas::odb o(this->path); float maximalPressure = - o[static_cast(ParameterIndex::MAXIMAL_PRESSURE)]; + o[DemanHandlerConfig::DEMAND] + [static_cast(ParameterIndex::MAXIMAL_PRESSURE)]; Event event = {EventType::MAXIMAL_PRESSURE, maximalPressure}; eventBus.publish(event); } void DemandHandler::pullConstante1() { midas::odb o(this->path); - float constante1 = o[static_cast(ParameterIndex::CONSTANTE_1)]; + float constante1 = o[DemanHandlerConfig::DEMAND] + [static_cast(ParameterIndex::CONSTANTE_1)]; Event event = {EventType::CONSTANTE_1, constante1}; eventBus.publish(event); } void DemandHandler::pullConstante2() { midas::odb o(this->path); - float constance2 = o[static_cast(ParameterIndex::CONSTANTE_2)]; + float constance2 = o[DemanHandlerConfig::DEMAND] + [static_cast(ParameterIndex::CONSTANTE_2)]; Event event = {EventType::CONSTANTE_2, constance2}; eventBus.publish(event); } @@ -143,14 +159,16 @@ void DemandHandler::pullConstante2() { void DemandHandler::pullPressureControlMode() { midas::odb o(this->path); float pressureControlMode = - o[static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)]; + o[DemanHandlerConfig::DEMAND] + [static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)]; Event event = {EventType::PRESSURE_CONTROL_MODE, pressureControlMode != 0}; eventBus.publish(event); } void DemandHandler::pullSetPressure() { midas::odb o(this->path); - float setPressure = o[static_cast(ParameterIndex::SET_PRESSURE)]; + float setPressure = o[DemanHandlerConfig::DEMAND] + [static_cast(ParameterIndex::SET_PRESSURE)]; Event event = {EventType::SET_PRESSURE, setPressure != 0}; eventBus.publish(event); } diff --git a/src/device/handlers/DemandHandlerConfig.h b/src/device/handlers/DemandHandlerConfig.h index 8223d23..a6ea7f9 100644 --- a/src/device/handlers/DemandHandlerConfig.h +++ b/src/device/handlers/DemandHandlerConfig.h @@ -5,7 +5,8 @@ namespace DemanHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Variables/Demand"; +const std::string PATH_SUFFIX = "/Variables"; +const std::string DEMAND = "Demand"; enum class ParameterIndex { @@ -14,7 +15,8 @@ enum class ParameterIndex { MINIMAL_PRESSURE = 8, MAXIMAL_PRESSURE = 9, CONSTANTE_1 = 10, - CONSTANTE_2 = 11 + CONSTANTE_2 = 11, + MAXIMUM_PARAMETER_INDEX = 11 }; }; // namespace DemanHandlerConfig diff --git a/src/device/handlers/FeedbackHandler.cpp b/src/device/handlers/FeedbackHandler.cpp index 27297f1..b890f8f 100644 --- a/src/device/handlers/FeedbackHandler.cpp +++ b/src/device/handlers/FeedbackHandler.cpp @@ -15,8 +15,9 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, eventBusReference.subscribe( [this](const PressureCalculator::Event &e) { - setConstante2(e.c2contrib); + generateOdbKeyIfNeeded(); setConstante1(e.c1contrib); + setConstante2(e.c2contrib); setPressureMinimum(e.uncapedPressure); setPressureMaximum(e.uncapedPressure); setPressure(e.cappedPressure); @@ -24,6 +25,7 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, }); eventBusReference.subscribe( [this](const DemandHandler::Event &e) { + generateOdbKeyIfNeeded(); switch (e.type) { case DemandHandler::EventType::PRESSURE_CONTROL_MODE: enablePressureControlMode(std::get(e.value)); @@ -37,6 +39,7 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, }); eventBusReference.subscribe( [this](const InputHandler::Event &e) { + generateOdbKeyIfNeeded(); switch (e.type) { case InputHandler::EventType::SP_VALUE: setSP(e.value); @@ -54,15 +57,6 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, } void FeedbackHandler::updatePressure() { - // If the key doesn't exist, we create the template. - if (!midas::odb::exists(path + FeedbackHandlerConf::OUTPUT_VARNAME)) { - std::vector v; - v.resize(static_cast( - FeedbackHandlerConf::RegisterIndex::MAXIMUM_REGISTRY_INDEX)); - midas::odb o(path); - o[FeedbackHandlerConf::OUTPUT_VARNAME] = v; - } - { midas::odb o(path); o[FeedbackHandlerConf::OUTPUT_VARNAME] @@ -87,6 +81,18 @@ void FeedbackHandler::updatePressure() { } } +void FeedbackHandler::generateOdbKeyIfNeeded() { + // If the key doesn't exist, we create the template. + + if (!midas::odb::exists(path + FeedbackHandlerConf::OUTPUT_VARNAME)) { + std::vector v; + v.resize(static_cast( + FeedbackHandlerConf::RegisterIndex::MAXIMUM_REGISTRY_INDEX)); + midas::odb o(path); + o[FeedbackHandlerConf::OUTPUT_VARNAME] = v; + } +} + void FeedbackHandler::enablePressureControlMode(bool enable) { isPressureControlModeEnable = enable; updatePressure(); diff --git a/src/device/handlers/FeedbackHandler.h b/src/device/handlers/FeedbackHandler.h index b1086f1..d1b5728 100644 --- a/src/device/handlers/FeedbackHandler.h +++ b/src/device/handlers/FeedbackHandler.h @@ -12,6 +12,8 @@ class FeedbackHandler { std::string path; + void generateOdbKeyIfNeeded(); + /* Internal value setters */ -- 2.54.0 From 7671005c1f0915ba1bcc5990dc1dc6ff6f68dbb7 Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Tue, 28 Jul 2026 09:17:05 +0200 Subject: [PATCH 23/28] bug fix + adding fast setup --- src/device/handlers/DemandHandler.cpp | 16 ++++++++++++- src/device/handlers/FeedbackHandler.cpp | 23 ++++++++++--------- src/device/handlers/InputHandler.cpp | 1 + src/device/handlers/OutputHandler.cpp | 30 ++++++++++++++++++------- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp index 576e23b..ce4e2a7 100644 --- a/src/device/handlers/DemandHandler.cpp +++ b/src/device/handlers/DemandHandler.cpp @@ -93,28 +93,42 @@ bool DemandHandler::init() { } bool DemandHandler::setHotlink() { - midas::odb to_watch(this->path); + midas::odb to_watch(this->path + "/" + DemanHandlerConfig::DEMAND); to_watch.watch([&](midas::odb &arg) { switch (arg.get_last_index()) { case static_cast(ParameterIndex::MINIMAL_PRESSURE): + printf("Out of scope : DEMAND HANDLER MINIMAL_PRESSURE\n"); + pullMinimalPressure(); break; case static_cast(ParameterIndex::MAXIMAL_PRESSURE): + printf("Out of scope : DEMAND HANDLER MAXIMAL_PRESSURE\n"); + pullMaximalPressure(); break; case static_cast(ParameterIndex::CONSTANTE_1): + printf("Out of scope : DEMAND HANDLER CONSTANTE_1\n"); + pullConstante1(); break; case static_cast(ParameterIndex::CONSTANTE_2): + printf("Out of scope : DEMAND HANDLER CONSTANTE_2\n"); + pullConstante2(); break; case static_cast(ParameterIndex::PRESSURE_CONTROL_MODE): + printf("Out of scope : DEMAND HANDLER PRESSURE_CONTROL_MODE\n"); + pullPressureControlMode(); break; case static_cast(ParameterIndex::SET_PRESSURE): + printf("Out of scope : DEMAND HANDLER SET_PRESSURE\n"); + pullSetPressure(); break; default: + printf("Out of scope : DEMAND HANDLER\n"); + // Update out of scope, discarded break; } diff --git a/src/device/handlers/FeedbackHandler.cpp b/src/device/handlers/FeedbackHandler.cpp index b890f8f..1e34df6 100644 --- a/src/device/handlers/FeedbackHandler.cpp +++ b/src/device/handlers/FeedbackHandler.cpp @@ -13,16 +13,6 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, this->path = FeedbackHandlerConf::PATH_PREFIX + equipmentName + FeedbackHandlerConf::PATH_SUFFIX; - eventBusReference.subscribe( - [this](const PressureCalculator::Event &e) { - generateOdbKeyIfNeeded(); - setConstante1(e.c1contrib); - setConstante2(e.c2contrib); - setPressureMinimum(e.uncapedPressure); - setPressureMaximum(e.uncapedPressure); - setPressure(e.cappedPressure); - setAveragePower(e.averagePower); - }); eventBusReference.subscribe( [this](const DemandHandler::Event &e) { generateOdbKeyIfNeeded(); @@ -54,6 +44,17 @@ FeedbackHandler::FeedbackHandler(EventBus &eventBusReference, break; } }); + + eventBusReference.subscribe( + [this](const PressureCalculator::Event &e) { + generateOdbKeyIfNeeded(); + setConstante1(e.c1contrib); + setConstante2(e.c2contrib); + setPressureMinimum(e.uncapedPressure); + setPressureMaximum(e.uncapedPressure); + setPressure(e.cappedPressure); + setAveragePower(e.averagePower); + }); } void FeedbackHandler::updatePressure() { @@ -84,7 +85,7 @@ void FeedbackHandler::updatePressure() { void FeedbackHandler::generateOdbKeyIfNeeded() { // If the key doesn't exist, we create the template. - if (!midas::odb::exists(path + FeedbackHandlerConf::OUTPUT_VARNAME)) { + if (!midas::odb::exists(path + "/" + FeedbackHandlerConf::OUTPUT_VARNAME)) { std::vector v; v.resize(static_cast( FeedbackHandlerConf::RegisterIndex::MAXIMUM_REGISTRY_INDEX)); diff --git a/src/device/handlers/InputHandler.cpp b/src/device/handlers/InputHandler.cpp index 8799a4c..5fcb120 100644 --- a/src/device/handlers/InputHandler.cpp +++ b/src/device/handlers/InputHandler.cpp @@ -105,6 +105,7 @@ void InputHandler::setHotlink() { InputHandler::InputHandler(EventBus &eventBusReference) : eventBus(eventBusReference) { + eventBus.subscribe( [this](const SettingsHandler::Event &e) { switch (e.type) { diff --git a/src/device/handlers/OutputHandler.cpp b/src/device/handlers/OutputHandler.cpp index e27d60a..4ec7d83 100644 --- a/src/device/handlers/OutputHandler.cpp +++ b/src/device/handlers/OutputHandler.cpp @@ -28,20 +28,34 @@ void OutputHandler::setEquipmentPath(std::string equipmentName) { } void OutputHandler::update() { - bool isKeyValid = true; + if (equipmentPath.empty()) + return; + static bool isIndexValid = true; + static bool isKeyValid = true; midas::odb o(equipmentPath); - if (o.exists(OutputHandlerConfig::OUTPUT_VARIABLE)) { - TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Key at %s doesn't exists", - equipmentPath.c_str()); + if (!midas::odb::exists(equipmentPath + "/" + + OutputHandlerConfig::OUTPUT_VARIABLE) && + isKeyValid) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Key at %s%s doesn't exists", + equipmentPath.c_str(), + OutputHandlerConfig::OUTPUT_VARIABLE.c_str()); isKeyValid = false; + } else { + isKeyValid = true; } - if (o.size() < outputPressureSPIndex) { + if ((o[OutputHandlerConfig::OUTPUT_VARIABLE].size() < + outputPressureSPIndex) && + isIndexValid) { TMFE::Instance()->Msg(MERROR, __FUNCTION__, - "SP index is %i, where array size is %i.", - outputPressureSPIndex, o.size()); - isKeyValid = false; + "SP index is %i, where array size is %i at %s.", + outputPressureSPIndex, o.size(), + equipmentPath.c_str()); + isIndexValid = false; + } else { + isIndexValid = true; } //// SHOULD I CRASH IF KEY NOT VALID ? -- 2.54.0 From cbd11e758fc0a9d8741449522601706bbfbbd96f Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Tue, 28 Jul 2026 09:23:23 +0200 Subject: [PATCH 24/28] renaming in settings --- src/device/handlers/DemandHandler.cpp | 14 -------------- src/device/handlers/SettingsHandlerConfig.h | 14 +++++++------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp index ce4e2a7..ca894a2 100644 --- a/src/device/handlers/DemandHandler.cpp +++ b/src/device/handlers/DemandHandler.cpp @@ -97,38 +97,24 @@ bool DemandHandler::setHotlink() { to_watch.watch([&](midas::odb &arg) { switch (arg.get_last_index()) { case static_cast(ParameterIndex::MINIMAL_PRESSURE): - printf("Out of scope : DEMAND HANDLER MINIMAL_PRESSURE\n"); - pullMinimalPressure(); break; case static_cast(ParameterIndex::MAXIMAL_PRESSURE): - printf("Out of scope : DEMAND HANDLER MAXIMAL_PRESSURE\n"); - pullMaximalPressure(); break; case static_cast(ParameterIndex::CONSTANTE_1): - printf("Out of scope : DEMAND HANDLER CONSTANTE_1\n"); - pullConstante1(); break; case static_cast(ParameterIndex::CONSTANTE_2): - printf("Out of scope : DEMAND HANDLER CONSTANTE_2\n"); - pullConstante2(); break; case static_cast(ParameterIndex::PRESSURE_CONTROL_MODE): - printf("Out of scope : DEMAND HANDLER PRESSURE_CONTROL_MODE\n"); - pullPressureControlMode(); break; case static_cast(ParameterIndex::SET_PRESSURE): - printf("Out of scope : DEMAND HANDLER SET_PRESSURE\n"); - pullSetPressure(); break; default: - printf("Out of scope : DEMAND HANDLER\n"); - // Update out of scope, discarded break; } diff --git a/src/device/handlers/SettingsHandlerConfig.h b/src/device/handlers/SettingsHandlerConfig.h index 404b5b8..60d048d 100644 --- a/src/device/handlers/SettingsHandlerConfig.h +++ b/src/device/handlers/SettingsHandlerConfig.h @@ -6,14 +6,14 @@ namespace SettingsHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; -const std::string PATH_SUFFIX = "/Settings/Devices/MITCPRESSC/DD/"; +const std::string PATH_SUFFIX = "/Settings/Devices/ItcPressureOptimizer/DD/"; -const std::string EQUIPMENT_NAME = "MITC_Equipment"; -const std::string OUTPUT_PRESSURE_SP_INDEX = "MITC_OutputPressSPIndex"; -const std::string INPUT_SP_INDEX = "MITC_InputVarioxSPIndex"; -const std::string INPUT_TEMPERATURE_INDEX = "MITC_InputVarioxTempIndex"; -const std::string INPUT_POWER_INDEX = "MITC_InputVarioxPowIndex"; -const std::string TIME_WINDOW = "Average_Power_Time_Window"; +const std::string EQUIPMENT_NAME = "Equipment name"; +const std::string OUTPUT_PRESSURE_SP_INDEX = "Output Pressure SetPoint Index"; +const std::string INPUT_SP_INDEX = "Input SetPoint Index "; +const std::string INPUT_TEMPERATURE_INDEX = "Input Temperature Index"; +const std::string INPUT_POWER_INDEX = "Input Power Index"; +const std::string TIME_WINDOW = "Average Power Time Window"; const std::string DEFAULT_EQUIPMENT_NAME = ""; const int DEFAULT_INDEX_VALUE = -1; -- 2.54.0 From c23bc4fa6d8e055c07fcfc1710f5a1637f946b2e Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Tue, 28 Jul 2026 09:29:21 +0200 Subject: [PATCH 25/28] bug fix --- src/device/handlers/SettingsHandlerConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/handlers/SettingsHandlerConfig.h b/src/device/handlers/SettingsHandlerConfig.h index 60d048d..606f9e9 100644 --- a/src/device/handlers/SettingsHandlerConfig.h +++ b/src/device/handlers/SettingsHandlerConfig.h @@ -10,7 +10,7 @@ const std::string PATH_SUFFIX = "/Settings/Devices/ItcPressureOptimizer/DD/"; const std::string EQUIPMENT_NAME = "Equipment name"; const std::string OUTPUT_PRESSURE_SP_INDEX = "Output Pressure SetPoint Index"; -const std::string INPUT_SP_INDEX = "Input SetPoint Index "; +const std::string INPUT_SP_INDEX = "Input SetPoint Index"; const std::string INPUT_TEMPERATURE_INDEX = "Input Temperature Index"; const std::string INPUT_POWER_INDEX = "Input Power Index"; const std::string TIME_WINDOW = "Average Power Time Window"; -- 2.54.0 From 9b484d815eba5ae3eb1b947f200397fa1400525c Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Tue, 28 Jul 2026 10:23:26 +0200 Subject: [PATCH 26/28] Explain that dotted line is data & execution going through the even bus --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9201b29..8415f2c 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,12 @@ Default values are : | Equipment | variox0 | heliox0 | |-----------|---------|---------| -| MITC_OutputPressSPIndex | 3 (0x3) | 8 (0x8) | -| MITC_InputVarioxSPIndex | 1 (0x1) | 1 (0x1) | -| MITC_InputVarioxTempIndex | 0 (0x0) | 0 (0x0) | -| MITC_InputVarioxPowIndex | 10 (0xA) | 8 (0x8) | -| RecalcInterval | 10 (0xA) | 10 (0xA) | -| MitcReadoutInterval | 2 (0x2) | 2 (0x2) | -| PressReadoutInterval | 3 (0x3) | 3 (0x3) | +| Output Pressure SetPoint Index | 3 (0x3) | 8 (0x8) | +| Input SetPoint Index | 1 (0x1) | 1 (0x1) | +| Input Temperature Index | 0 (0x0) | 0 (0x0) | +| Input Power Index | 10 (0xA) | 8 (0x8) | +| Average Power Time Window | 30f | 30f | + *NOTE: OutputPressSPIndex and InputVarioxPowIndex are different for variox0 and heliox0* @@ -52,6 +51,7 @@ In ```/Equipment//Variables/Demand``` you can specify demand to the equipm This is the execution flow graph. ![alt text](out/doc/graph/graph.png) +All dotted lines represent data and execution flow going through the EventBus (Broker) with the publish / subscribe method. For cleaner code, all execution and data flow pass through an EventBus. Objects can subscribe to one or many specific event types. They can also publish an event, which will be dispatched to all subscribers by invoking their callbacks. -- 2.54.0 From f768118aab9c6ca314bd08f274f92cc5be07ba0c Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Tue, 28 Jul 2026 10:42:12 +0200 Subject: [PATCH 27/28] demandhandler typo fixed --- src/device/handlers/DemandHandler.cpp | 38 +++++++++++------------ src/device/handlers/DemandHandlerConfig.h | 4 +-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/device/handlers/DemandHandler.cpp b/src/device/handlers/DemandHandler.cpp index ca894a2..8c3765d 100644 --- a/src/device/handlers/DemandHandler.cpp +++ b/src/device/handlers/DemandHandler.cpp @@ -6,20 +6,20 @@ #include #include -using namespace DemanHandlerConfig; +using namespace DemandHandlerConfig; bool DemandHandler::init() { bool initSuccess = true; - if (!midas::odb::exists(this->path + "/" + DemanHandlerConfig::DEMAND)) { + if (!midas::odb::exists(this->path + "/" + DemandHandlerConfig::DEMAND)) { TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Key at %s/%s doesn't exists", path.c_str(), - DemanHandlerConfig::DEMAND.c_str()); + DemandHandlerConfig::DEMAND.c_str()); midas::odb o(this->path); std::vector v; v.resize(static_cast(ParameterIndex::MAXIMUM_PARAMETER_INDEX)); - o[DemanHandlerConfig::DEMAND] = v; + o[DemandHandlerConfig::DEMAND] = v; TMFE::Instance()->Msg(MERROR, __FUNCTION__, "Template generated at %s. Please fill it.", @@ -31,7 +31,7 @@ bool DemandHandler::init() { // This is checked every start, in case of someone change the index in the // config header - if (o[DemanHandlerConfig::DEMAND].size() < + if (o[DemandHandlerConfig::DEMAND].size() < static_cast(ParameterIndex::MINIMAL_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, @@ -39,7 +39,7 @@ bool DemandHandler::init() { static_cast(ParameterIndex::MINIMAL_PRESSURE), o.size()); initSuccess = false; } - if (o[DemanHandlerConfig::DEMAND].size() < + if (o[DemandHandlerConfig::DEMAND].size() < static_cast(ParameterIndex::MAXIMAL_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, @@ -47,7 +47,7 @@ bool DemandHandler::init() { static_cast(ParameterIndex::MAXIMAL_PRESSURE), o.size()); initSuccess = false; } - if (o[DemanHandlerConfig::DEMAND].size() < + if (o[DemandHandlerConfig::DEMAND].size() < static_cast(ParameterIndex::CONSTANTE_1)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, @@ -55,7 +55,7 @@ bool DemandHandler::init() { static_cast(ParameterIndex::CONSTANTE_1), o.size()); initSuccess = false; } - if (o[DemanHandlerConfig::DEMAND].size() < + if (o[DemandHandlerConfig::DEMAND].size() < static_cast(ParameterIndex::CONSTANTE_2)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, @@ -63,7 +63,7 @@ bool DemandHandler::init() { static_cast(ParameterIndex::CONSTANTE_2), o.size()); initSuccess = false; } - if (o[DemanHandlerConfig::DEMAND].size() < + if (o[DemandHandlerConfig::DEMAND].size() < static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, @@ -71,7 +71,7 @@ bool DemandHandler::init() { static_cast(ParameterIndex::PRESSURE_CONTROL_MODE), o.size()); initSuccess = false; } - if (o[DemanHandlerConfig::DEMAND].size() < + if (o[DemandHandlerConfig::DEMAND].size() < static_cast(ParameterIndex::SET_PRESSURE)) { TMFE::Instance()->Msg( MERROR, __FUNCTION__, @@ -93,7 +93,7 @@ bool DemandHandler::init() { } bool DemandHandler::setHotlink() { - midas::odb to_watch(this->path + "/" + DemanHandlerConfig::DEMAND); + midas::odb to_watch(this->path + "/" + DemandHandlerConfig::DEMAND); to_watch.watch([&](midas::odb &arg) { switch (arg.get_last_index()) { case static_cast(ParameterIndex::MINIMAL_PRESSURE): @@ -125,7 +125,7 @@ bool DemandHandler::setHotlink() { void DemandHandler::pullMinimalPressure() { midas::odb o(this->path); float minimalPressure = - o[DemanHandlerConfig::DEMAND] + o[DemandHandlerConfig::DEMAND] [static_cast(ParameterIndex::MINIMAL_PRESSURE)]; Event event = {EventType::MINIMAL_PRESSURE, minimalPressure}; eventBus.publish(event); @@ -134,7 +134,7 @@ void DemandHandler::pullMinimalPressure() { void DemandHandler::pullMaximalPressure() { midas::odb o(this->path); float maximalPressure = - o[DemanHandlerConfig::DEMAND] + o[DemandHandlerConfig::DEMAND] [static_cast(ParameterIndex::MAXIMAL_PRESSURE)]; Event event = {EventType::MAXIMAL_PRESSURE, maximalPressure}; eventBus.publish(event); @@ -142,7 +142,7 @@ void DemandHandler::pullMaximalPressure() { void DemandHandler::pullConstante1() { midas::odb o(this->path); - float constante1 = o[DemanHandlerConfig::DEMAND] + float constante1 = o[DemandHandlerConfig::DEMAND] [static_cast(ParameterIndex::CONSTANTE_1)]; Event event = {EventType::CONSTANTE_1, constante1}; eventBus.publish(event); @@ -150,7 +150,7 @@ void DemandHandler::pullConstante1() { void DemandHandler::pullConstante2() { midas::odb o(this->path); - float constance2 = o[DemanHandlerConfig::DEMAND] + float constance2 = o[DemandHandlerConfig::DEMAND] [static_cast(ParameterIndex::CONSTANTE_2)]; Event event = {EventType::CONSTANTE_2, constance2}; eventBus.publish(event); @@ -159,7 +159,7 @@ void DemandHandler::pullConstante2() { void DemandHandler::pullPressureControlMode() { midas::odb o(this->path); float pressureControlMode = - o[DemanHandlerConfig::DEMAND] + o[DemandHandlerConfig::DEMAND] [static_cast(ParameterIndex::PRESSURE_CONTROL_MODE)]; Event event = {EventType::PRESSURE_CONTROL_MODE, pressureControlMode != 0}; eventBus.publish(event); @@ -167,7 +167,7 @@ void DemandHandler::pullPressureControlMode() { void DemandHandler::pullSetPressure() { midas::odb o(this->path); - float setPressure = o[DemanHandlerConfig::DEMAND] + float setPressure = o[DemandHandlerConfig::DEMAND] [static_cast(ParameterIndex::SET_PRESSURE)]; Event event = {EventType::SET_PRESSURE, setPressure != 0}; eventBus.publish(event); @@ -175,8 +175,8 @@ void DemandHandler::pullSetPressure() { DemandHandler::DemandHandler(EventBus &eventBusReference, std::string equipmentName) - : path(DemanHandlerConfig::PATH_PREFIX + equipmentName + - DemanHandlerConfig::PATH_SUFFIX), + : path(DemandHandlerConfig::PATH_PREFIX + equipmentName + + DemandHandlerConfig::PATH_SUFFIX), eventBus(eventBusReference) { eventBus.subscribe( diff --git a/src/device/handlers/DemandHandlerConfig.h b/src/device/handlers/DemandHandlerConfig.h index a6ea7f9..d08975c 100644 --- a/src/device/handlers/DemandHandlerConfig.h +++ b/src/device/handlers/DemandHandlerConfig.h @@ -3,7 +3,7 @@ #include -namespace DemanHandlerConfig { +namespace DemandHandlerConfig { const std::string PATH_PREFIX = "/Equipment/"; const std::string PATH_SUFFIX = "/Variables"; const std::string DEMAND = "Demand"; @@ -19,6 +19,6 @@ enum class ParameterIndex { MAXIMUM_PARAMETER_INDEX = 11 }; -}; // namespace DemanHandlerConfig +}; // namespace DemandHandlerConfig #endif -- 2.54.0 From b55668f05683e0a532818295a4f54b57c410848b Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Tue, 28 Jul 2026 10:45:14 +0200 Subject: [PATCH 28/28] swap to put public in front of private --- src/device/EventBus.h | 36 ++++++++++++------------ src/device/handlers/DemandHandler.h | 34 +++++++++++----------- src/device/handlers/FeedbackHandler.h | 6 ++-- src/device/handlers/InputHandler.h | 28 +++++++++--------- src/device/handlers/OutputHandler.h | 6 ++-- src/device/handlers/PressureCalculator.h | 22 +++++++-------- src/device/handlers/SettingsHandler.h | 32 ++++++++++----------- 7 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/device/EventBus.h b/src/device/EventBus.h index 7259957..b6a68c4 100644 --- a/src/device/EventBus.h +++ b/src/device/EventBus.h @@ -9,24 +9,6 @@ #include class EventBus { - private: - // Non-templated base interface to store our callback lists - // generically inside the unordered_map. - struct CallbackListBase { - virtual ~CallbackListBase() = default; - }; - - // Specialized version holding the actual strongly-typed callbacks. - template - struct CallbackList : public CallbackListBase { - using CallbackType = std::function; - std::vector callbacks; - }; - - // Map associating each event type to its respective callback list - std::unordered_map> - subscribers; - public: // Subscribe to a specific event type template @@ -58,6 +40,24 @@ class EventBus { } } } + + private: + // Non-templated base interface to store our callback lists + // generically inside the unordered_map. + struct CallbackListBase { + virtual ~CallbackListBase() = default; + }; + + // Specialized version holding the actual strongly-typed callbacks. + template + struct CallbackList : public CallbackListBase { + using CallbackType = std::function; + std::vector callbacks; + }; + + // Map associating each event type to its respective callback list + std::unordered_map> + subscribers; }; #endif \ No newline at end of file diff --git a/src/device/handlers/DemandHandler.h b/src/device/handlers/DemandHandler.h index b08f31f..d9f4f00 100644 --- a/src/device/handlers/DemandHandler.h +++ b/src/device/handlers/DemandHandler.h @@ -6,6 +6,23 @@ #include class DemandHandler { + public: + enum class EventType { + MINIMAL_PRESSURE, + MAXIMAL_PRESSURE, + CONSTANTE_1, + CONSTANTE_2, + PRESSURE_CONTROL_MODE, + SET_PRESSURE + }; + + struct Event { + EventType type; + std::variant value; + }; + + DemandHandler(EventBus &eventBusReference, std::string equipmentName); + private: std::string path; EventBus &eventBus; @@ -29,23 +46,6 @@ class DemandHandler { Setting ODB hotlink */ bool setHotlink(); - - public: - enum class EventType { - MINIMAL_PRESSURE, - MAXIMAL_PRESSURE, - CONSTANTE_1, - CONSTANTE_2, - PRESSURE_CONTROL_MODE, - SET_PRESSURE - }; - - struct Event { - EventType type; - std::variant value; - }; - - DemandHandler(EventBus &eventBusReference, std::string equipmentName); }; #endif \ No newline at end of file diff --git a/src/device/handlers/FeedbackHandler.h b/src/device/handlers/FeedbackHandler.h index d1b5728..c3ef9e8 100644 --- a/src/device/handlers/FeedbackHandler.h +++ b/src/device/handlers/FeedbackHandler.h @@ -5,6 +5,9 @@ #include class FeedbackHandler { + public: + FeedbackHandler(EventBus &eventBusReference, std::string equipmentName); + private: bool isPressureControlModeEnable; bool isSetPressureEnable; @@ -34,9 +37,6 @@ class FeedbackHandler { Update pressure value */ void updatePressure(); - - public: - FeedbackHandler(EventBus &eventBusReference, std::string equipmentName); }; #endif \ No newline at end of file diff --git a/src/device/handlers/InputHandler.h b/src/device/handlers/InputHandler.h index 1b11bf2..4f526e6 100644 --- a/src/device/handlers/InputHandler.h +++ b/src/device/handlers/InputHandler.h @@ -7,6 +7,20 @@ #include class InputHandler { + public: + InputHandler(EventBus &eventBusReference); + + enum class EventType { + SP_VALUE, + TEMPERATURE_VALUE, + POWER_VALUE, + }; + + struct Event { + EventType type; + float value; + }; + private: EventBus &eventBus; std::string path; @@ -32,20 +46,6 @@ class InputHandler { void pullSPValue(); void pullTemperatureValue(); void pullPowerValue(); - - public: - InputHandler(EventBus &eventBusReference); - - enum class EventType { - SP_VALUE, - TEMPERATURE_VALUE, - POWER_VALUE, - }; - - struct Event { - EventType type; - float value; - }; }; #endif \ No newline at end of file diff --git a/src/device/handlers/OutputHandler.h b/src/device/handlers/OutputHandler.h index 8f129d7..5ba7f61 100644 --- a/src/device/handlers/OutputHandler.h +++ b/src/device/handlers/OutputHandler.h @@ -8,6 +8,9 @@ #include class OutputHandler { + public: + OutputHandler(EventBus &eventBusReference); + private: bool isSetPressureEnable; std::string equipmentPath; @@ -25,9 +28,6 @@ class OutputHandler { Update value of the ODB */ void update(); - - public: - OutputHandler(EventBus &eventBusReference); }; #endif diff --git a/src/device/handlers/PressureCalculator.h b/src/device/handlers/PressureCalculator.h index 89e9a7f..e5a1f15 100644 --- a/src/device/handlers/PressureCalculator.h +++ b/src/device/handlers/PressureCalculator.h @@ -5,6 +5,17 @@ #include "Average.h" class PressureCalculator { + public: + PressureCalculator(EventBus &eventBusReference); + + struct Event { + float c1contrib; + float c2contrib; + float uncapedPressure; + float cappedPressure; + float averagePower; + }; + private: Average average; EventBus &eventBus; @@ -36,17 +47,6 @@ class PressureCalculator { Update pressure value */ void update(); - - public: - PressureCalculator(EventBus &eventBusReference); - - struct Event { - float c1contrib; - float c2contrib; - float uncapedPressure; - float cappedPressure; - float averagePower; - }; }; #endif \ No newline at end of file diff --git a/src/device/handlers/SettingsHandler.h b/src/device/handlers/SettingsHandler.h index 10a554b..4733802 100644 --- a/src/device/handlers/SettingsHandler.h +++ b/src/device/handlers/SettingsHandler.h @@ -7,6 +7,22 @@ #include class SettingsHandler { + public: + SettingsHandler(EventBus &eventBusReference, std::string equipmentName); + + enum class EventType { + EQUIPMENT_NAME, + OUTPUT_PRESSURE_SP_INDEX, + INPUT_SP_INDEX, + INPUT_TEMPERATURE_INDEX, + INPUT_POWER_INDEX + }; + + struct Event { + EventType type; + std::variant value; + }; + private: std::string path; EventBus &eventBus; @@ -24,22 +40,6 @@ class SettingsHandler { void pullInputSPIndex(); void pullInputTemperatureIndex(); void pullInputPowerIndex(); - - public: - SettingsHandler(EventBus &eventBusReference, std::string equipmentName); - - enum class EventType { - EQUIPMENT_NAME, - OUTPUT_PRESSURE_SP_INDEX, - INPUT_SP_INDEX, - INPUT_TEMPERATURE_INDEX, - INPUT_POWER_INDEX - }; - - struct Event { - EventType type; - std::variant value; - }; }; #endif \ No newline at end of file -- 2.54.0