Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions include/DoocsProcessScalar.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,31 @@ namespace ChimeraTK {
*/
void auto_init() override;

/** Set the EGU axis configuration for this scalar.
* Applied in auto_init() after the DOOCS framework loads persisted values from the .conf file.
* If set, the .EGU sub-property is made read-only, preventing runtime modification. */
void setAxisConfig(int logarithmic, float start, float stop, const std::string& label);

protected:
void updateDoocsBuffer(const TransferElementID& transferElementId) override;

/// Apply EGU axis configuration from XML after auto_init().
/// If the EGU is defined in XML, force-sets the value via set_plot_value() and
/// makes the .EGU sub-property read-only. On mismatch with the .conf value,
/// logs a warning and overwrites with the XML value.
void applyAxisConfig();

ScalarRegisterAccessor<T> _processScalar;

/// EGU axis configuration from the XML file, applied after auto_init()
struct AxisConfig {
std::string label;
int logarithmic{};
float start{};
float stop{};
};
AxisConfig _axisConfig;
bool _axisConfigSet{false};
};

/********************************************************************************************************************/
Expand Down Expand Up @@ -101,11 +122,80 @@ namespace ChimeraTK {

/********************************************************************************************************************/

/********************************************************************************************************************/

template<typename T, typename DOOCS_T>
void DoocsProcessScalar<T, DOOCS_T>::setAxisConfig(
int logarithmic, float start, float stop, const std::string& label) {
_axisConfig.logarithmic = logarithmic;
_axisConfig.start = start;
_axisConfig.stop = stop;
_axisConfig.label = label;
_axisConfigSet = true;
}

/********************************************************************************************************************/

template<typename T, typename DOOCS_T>
void DoocsProcessScalar<T, DOOCS_T>::applyAxisConfig() {
if(!_axisConfigSet) {
return;
}
if constexpr(std::is_base_of_v<D_text, DOOCS_T>) {
return;
}
else {
auto* hist = this->get_histPointer();
if(hist == nullptr) {
return;
}
// Read the EGU loaded from the .conf file (may be empty on first start)
std::string confEgu = hist->egu();
if(!confEgu.empty() && confEgu == _axisConfig.label) {
// .conf EGU matches XML → nothing to do, but make .EGU read-only to prevent
// runtime modification by clients.
auto* eguProp = getEqFct()->find_property(this->basename() + ".EGU");
if(eguProp != nullptr) {
eguProp->set_ro_access();
}
return;
}
if(!confEgu.empty() && confEgu != _axisConfig.label) {
// .conf EGU differs from XML → this indicates the .conf was manually modified
// or the XML was changed after the server was first started.
// Log a warning, force-set the XML value, and make .EGU read-only.
std::cerr << "WARNING: " << this->basename() << ": .conf EGU (\"" << confEgu << "\") differs from XML EGU (\""
<< _axisConfig.label << "\"). Overwriting with XML value." << std::endl;
// TODO: Uncomment to throw an error instead of silently overwriting:
// throw ChimeraTK::logic_error(
// this->basename() + ": .conf EGU (\"" + confEgu + "\") differs from XML EGU (\"" +
// _axisConfig.label + "\"). Cannot overwrite read-only XML unit.");
}
// Force-set the XML value (bypasses D_hist::egu()'s "set only if empty" guard)
hist->set_plot_value(_axisConfig.logarithmic, _axisConfig.start, _axisConfig.stop,
doocs::Timestamp::now().to_time_t(), _axisConfig.label.c_str());
// Make .EGU read-only so runtime client writes are rejected
auto* eguProp = getEqFct()->find_property(this->basename() + ".EGU");
if(eguProp != nullptr) {
eguProp->set_ro_access();
}
}
}

/********************************************************************************************************************/

template<typename T, typename DOOCS_T>
void DoocsProcessScalar<T, DOOCS_T>::auto_init() {
doocsAdapter.beforeAutoInit();

DOOCS_T::auto_init();

// Apply EGU axis configuration from XML after DOOCS loaded persisted values from .conf.
// If the EGU is defined in XML, the .EGU sub-property is made read-only to prevent
// runtime modification by clients. On first start the XML value is applied; on
// subsequent restarts the XML value is compared with the .conf value and re-applied
// if different.
applyAxisConfig();
// send the current value to the device
// property is writeable OR the target accessor is writable and the only one connected to this property
// The second case is to have bi-directional variables that are used to persist settings into the config file
Expand Down
26 changes: 25 additions & 1 deletion include/DoocsSpectrum.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ namespace ChimeraTK {

class DoocsSpectrum : public D_spectrum, public boost::noncopyable, public PropertyBase {
public:
/** Struct to hold EGU axis configuration from XML */
struct AxisConfig {
std::string label;
int logarithmic{};
float start{};
float stop{};
};

/** The constructor expects an NDRegisterAccessor of float, which usually will
* be a decorator to the implementation type. The decorator cannot be
* generated in the constructor because the ProcessVariable aka
Expand All @@ -42,7 +50,7 @@ namespace ChimeraTK {
* generated in the constructor because the ProcessVariable aka
* TransferElement does not know about it's size, which is needed by the
* D_spectrum constructor. This is not a big drawback because the properties
* are generated by a factory function anyway.
* are created by a factory function anyway.
*
* This version of the constructor shall be used for read-only spectra, as they
* do not need to be persisted but might require multiple buffers for short-term
Expand All @@ -54,6 +62,12 @@ namespace ChimeraTK {
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<float>> startAccessor,
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<float>> incrementAccessor, size_t numberOfBuffers);

/** Set the EGU axis configuration for this spectrum.
* Stores the config so it can be re-applied in auto_init() after the DOOCS framework
* overwrites it with persisted values from the .conf file.
* If set, the .EGU/.XEGU sub-properties are made read-only, preventing runtime modification. */
void setAxisConfig(const std::string& axis, AxisConfig config);

/**
* Overload the set function which is called by DOOCS to inject sending to the
* device.
Expand All @@ -80,6 +94,13 @@ namespace ChimeraTK {
/// unit testing.
void sendToDevice(bool getLock);

/// Re-apply EGU after .conf loading in auto_init(). EGU is set initially in
/// DoocsPVFactory but then D_spectrum::read() loads .conf data.
/// If EGU is defined in XML, force-sets via set_plot_y_value()/set_plot_x_value() and
/// makes the .EGU/.XEGU sub-properties read-only. On mismatch with .conf values,
/// logs a warning and overwrites with XML values.
void applyAxisConfig();

public:
/// Flag whether the value has been modified since the content has been saved to disk the last time (see write()).
bool modified{false};
Expand All @@ -89,6 +110,9 @@ namespace ChimeraTK {
ScalarRegisterAccessor<float> _startAccessor;
ScalarRegisterAccessor<float> _incrementAccessor;
size_t _nBuffers;

/// EGU axis configuration from the XML file, re-applied after auto_init()
std::map<std::string, AxisConfig> _axisConfig;
};

} // namespace ChimeraTK
10 changes: 10 additions & 0 deletions include/PropertyDescription.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,17 @@ namespace ChimeraTK {
// FIXME: should sort by name to put it into a set?
struct AutoPropertyDescription : public PropertyDescription {
enum class DataType { Byte, Short, Int, Long, Float, Double, Bool, Void, Auto };

struct Axis {
std::string label;
int logarithmic{};
float start{};
float stop{};
};

ChimeraTK::RegisterPath source;
std::map<std::string, Axis> axis;

explicit AutoPropertyDescription(ChimeraTK::RegisterPath const& source_ = "", std::string location_ = "",
std::string name_ = "", DataType dataType_ = DataType::Auto, bool hasHistory_ = true, bool isWriteable_ = true)
: PropertyDescription(std::move(location_), std::move(name_), PropertyAttributes(hasHistory_, isWriteable_)),
Expand Down
24 changes: 22 additions & 2 deletions src/DoocsPVFactory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ namespace ChimeraTK {
doocsPV->setMacroPulseNumberSource(propertyDescription.macroPulseNumberSource);
doocsPV->setIsWriteableSource(propertyDescription.isWriteableSource);

// set engineering units (EGU) if configured in the xml file
auto const yIt = propertyDescription.axis.find("y");
if(yIt != propertyDescription.axis.cend()) {
auto const& axis = yIt->second;
doocsPV->setAxisConfig(axis.logarithmic, axis.start, axis.stop, axis.label);
}

return doocsPV;
}

Expand Down Expand Up @@ -161,16 +168,29 @@ namespace ChimeraTK {
spectrum->description(spectrumDescription.description);
}

// Store EGU axis configuration so it can be re-applied in auto_init() after .conf loading
auto const xIt = spectrumDescription.axis.find("x");
if(xIt != spectrumDescription.axis.cend()) {
auto const& axis = xIt->second;
spectrum->xegu(axis.logarithmic, axis.start, axis.stop, axis.label.c_str());
DoocsSpectrum::AxisConfig cfg;
cfg.label = axis.label;
cfg.logarithmic = axis.logarithmic;
cfg.start = axis.start;
cfg.stop = axis.stop;
doocsPV->setAxisConfig("x", cfg);
doocsPV->xegu(axis.logarithmic, axis.start, axis.stop, axis.label.c_str());
}

auto const yIt = spectrumDescription.axis.find("y");
if(yIt != spectrumDescription.axis.cend()) {
auto const& axis = yIt->second;
spectrum->egu(axis.logarithmic, axis.start, axis.stop, axis.label.c_str());
DoocsSpectrum::AxisConfig cfg;
cfg.label = axis.label;
cfg.logarithmic = axis.logarithmic;
cfg.start = axis.start;
cfg.stop = axis.stop;
doocsPV->setAxisConfig("y", cfg);
doocsPV->egu(axis.logarithmic, axis.start, axis.stop, axis.label.c_str());
}

doocsPV->setMacroPulseNumberSource(spectrumDescription.macroPulseNumberSource);
Expand Down
71 changes: 71 additions & 0 deletions src/DoocsSpectrum.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,70 @@ namespace ChimeraTK {

/********************************************************************************************************************/

void DoocsSpectrum::setAxisConfig(const std::string& axis, AxisConfig config) {
_axisConfig[axis] = std::move(config);
}

/********************************************************************************************************************/

void DoocsSpectrum::applyAxisConfig() {
auto applyAxis = [&](const std::string& axisName, auto setPlotValue, auto getUnit) {
auto const it = _axisConfig.find(axisName);
if(it == _axisConfig.cend()) {
return;
}
auto const& c = it->second;

// Read the EGU loaded from the .conf file
std::string confEgu = getUnit();
if(!confEgu.empty() && confEgu == c.label) {
// .conf EGU matches XML → nothing to do, make sub-property read-only
std::string subPropName = this->basename() + (axisName == "x" ? ".XEGU" : ".EGU");
auto* prop = getEqFct()->find_property(subPropName);
if(prop != nullptr) {
prop->set_ro_access();
}
return;
}
if(!confEgu.empty() && confEgu != c.label) {
// Improve this by adding another lamda.
std::string subPropName = this->basename() + (axisName == "x" ? ".XEGU" : ".EGU");
// .conf EGU differs from XML
// Comment / Uncomment to warn and silently overwrite instead of thrwoing:
// std::cerr << "WARNING: " << subPropName
// << ": .conf value (\"" << confEgu << "\") differs from XML value (\""
// << c.label << "\"). Overwriting with XML value."
// << std::endl;
throw ChimeraTK::logic_error(subPropName + ": .conf EGU (\"" + confEgu + "\") differs from XML EGU (\"" +
c.label + "\"). Cannot overwrite read-only XML unit.");
}
// Force-set the XML value (bypasses egu()/xegu()'s "set only if empty" guard)
setPlotValue(c.logarithmic, c.start, c.stop, doocs::Timestamp::now().to_time_t(), c.label.c_str());
// Make the sub-property read-only
std::string subPropName = this->basename() + (axisName == "x" ? ".XEGU" : ".EGU");
auto* prop = getEqFct()->find_property(subPropName);
if(prop != nullptr) {
prop->set_ro_access();
}
};

applyAxis(
"x",
[this](int linlog, float start, float stop, time_t tm, const char* label) {
this->set_plot_x_value(linlog, start, stop, tm, label);
},
[this]() { return this->plot_x_unit(); });

applyAxis(
"y",
[this](int linlog, float start, float stop, time_t tm, const char* label) {
this->set_plot_y_value(linlog, start, stop, tm, label);
},
[this]() { return this->plot_y_unit(); });
}

/********************************************************************************************************************/

void DoocsSpectrum::auto_init() {
doocsAdapter.beforeAutoInit();

Expand All @@ -79,6 +143,13 @@ namespace ChimeraTK {
// send the current value to the device
D_spectrum::read();
modified = false;

// Re-apply EGU after .conf loading. EGU was set initially in DoocsPVFactory, but
// D_spectrum::read() above loads persisted EGU from the .conf file.
// If EGU is defined in XML, force-sets via set_plot_y_value()/set_plot_x_value()
// (bypassing the "set only if empty" guard) and makes sub-properties read-only.
applyAxisConfig();

if(this->get_access() == 1 ||
(_processArray.isWriteable() && !hasOtherPropertiesToUpdate())) { // property is writeable
sendToDevice(false);
Expand Down
33 changes: 33 additions & 0 deletions src/VariableMapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,39 @@ namespace ChimeraTK {

processHistoryAndWritableAttributes(*autoPropertyDescription, property);

auto unitNodes = property->get_children("unit");
for(auto* const unit : unitNodes) {
const auto* unitElement = asXmlElement(unit);
auto axis = getAttributeValue(unitElement, "axis");
if(axis != "y") {
throw std::invalid_argument(R"(Unsupported axis in property, must be "y": )" + axis);
}

std::string label;
if(not unit->get_children().empty()) {
label = getContentString(unit);
}

autoPropertyDescription->axis[axis].label = label;
try {
autoPropertyDescription->axis[axis].logarithmic = std::stoi(getAttributeValue(unitElement, "logarithmic"));
}
catch(std::invalid_argument&) {
}

try {
autoPropertyDescription->axis[axis].start = std::stof(getAttributeValue(unitElement, "start"));
}
catch(std::invalid_argument&) {
}

try {
autoPropertyDescription->axis[axis].stop = std::stof(getAttributeValue(unitElement, "stop"));
}
catch(std::invalid_argument&) {
}
}

addDescription(autoPropertyDescription);
}

Expand Down