diff --git a/bus/m_epics_ca.h b/bus/m_epics_ca.h index f2b1aba..8331e45 100644 --- a/bus/m_epics_ca.h +++ b/bus/m_epics_ca.h @@ -7,6 +7,7 @@ #include #include #include +#include /** * @brief Holds information about the CA channel: Value type and element count. @@ -305,6 +306,10 @@ template class mEpicsCa { */ std::optional _cached; + // Vector holding the possible enum variants, if the record is an enum + // record like e.g. bo. For all other record types, this is empty. + std::vector _enumVariants; + // Status and severity of the last read (or subscription callback) menuAlarmStat _status; menuAlarmSevr _severity; diff --git a/bus/m_epics_ca.tpp b/bus/m_epics_ca.tpp index 1eb62da..ebf1689 100644 --- a/bus/m_epics_ca.tpp +++ b/bus/m_epics_ca.tpp @@ -171,7 +171,8 @@ template mEpicsCa::mEpicsCa(std::string_view chanName, bool subscribe, double timeout) : _timeout(timeout), _pChanID(nullptr), _chanName(chanName), _subscribe(subscribe), _eventId(nullptr), _cached(std::nullopt), - _status(menuAlarmStatUDF), _severity(menuAlarmSevrNO_ALARM) { + _enumVariants(std::vector()), _status(menuAlarmStatUDF), + _severity(menuAlarmSevrNO_ALARM) { // Compile time check of T dbfFromType(); @@ -580,23 +581,36 @@ template int mEpicsCa::putRaw(const char *buf, u_long len) { if constexpr (dbfFromType() == DBF_ENUM) { - // Check if the given string corresponds to one of the enum variants - // and fetch the corresponding index, if it does. Otherwise, create - // a nice error message. - struct dbr_ctrl_enum data; - ca_get(DBR_CTRL_ENUM, this->_pChanID, &data); - ca_pend_io(_timeout); + // Prefetch all enum variants if the vector is currently empty. We + // cannot do this at connection time, because this results in a segfault + // (likely because we are trying to use ca_get while being inside a + // callback). + if (_enumVariants.empty()) { + struct dbr_ctrl_enum data; + ca_get(DBR_CTRL_ENUM, _pChanID, &data); + ca_pend_io(_timeout); + + for (int i = 0; i < data.no_str; ++i) { + _enumVariants.push_back(data.strs[i]); + } + } + + // Check if the given string matches one of the enum variants + for (size_t i = 0; i < _enumVariants.size(); i++) { + if (_enumVariants[i] == buf) { + uint16_t idx = i; + return putRaw(&idx); + } + } + + // If we didn't exit from the loop, the given buffer is not one of the + // enum variants. Hence, we now create an error message (looping again). std::string variants; - - for (int i = 0; i < data.no_str; ++i) { + for (size_t i = 0; i < _enumVariants.size(); i++) { if (i > 0) variants += ", "; - - variants += data.strs[i]; - - if (strcmp(data.strs[i], buf) == 0) - return putRaw(&i); + variants += _enumVariants[i]; } // Given string did not match any of the variants -> return an @@ -692,6 +706,7 @@ void mEpicsCa::connStateCallback(struct connection_handler_args args) { } else { // Any other event -> assume the channel is disconnected and reset the // internal fields + self->_enumVariants.clear(); self->_channelInfo.reset(); self->_cached.reset(); cm_msg(MDEBUG, __FILE__, "PV %s disconnected", self->_chanName.c_str());