From eb48f35a7d986bca5c379ae677e6e5498d76dc5c Mon Sep 17 00:00:00 2001 From: Hugo Jean Ponsin Date: Wed, 15 Jul 2026 14:38:31 +0200 Subject: [PATCH] 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; +}