Removal of code duplication
CI / build-and-test (push) Failing after 9s

- Collapsed putRaw and getRaw into single, templated methods
- Simplified readout of status and severity in callback.
This commit is contained in:
2026-04-29 09:04:06 +02:00
parent 54a33bff42
commit af3c8f1ae4
2 changed files with 84 additions and 271 deletions
+6 -16
View File
@@ -153,15 +153,15 @@ template <typename T> class mEpicsCa {
double _timeout;
/**
* @brief When using the subscription mechanism, this method returns the
* cached value, if there is any.
* @brief When using the subscription mechanism, this method returns a copy
* of the cached value, if there is any.
*
* If the subscription mechanism is not used or the driver is not connected
* to an EPICS channel, this returns `std::nullopt`.
*
* @return const std::optional<T>&
* @return const std::optional<T>
*/
const std::optional<T> &cached() const;
const std::optional<T> cached() const;
/**
* @brief Returns the last read value of the `STAT` field belonging to the
@@ -208,22 +208,12 @@ template <typename T> class mEpicsCa {
// The following methods hold the actual implementations of `mEpicsCa::get`
// for different types of `T`.
int getRaw(char *buf, size_t len);
int getRaw(int *value);
int getRaw(short *value);
int getRaw(long *value);
int getRaw(float *value);
int getRaw(double *value);
int getRaw(uint16_t *value);
template <typename V> int getRaw(V *value);
// The following methods hold the actual implementations of `mEpicsCa::put`
// for different types of `T`.
int putRaw(const char *buf, size_t len);
int putRaw(int *value);
int putRaw(short *value);
int putRaw(long *value);
int putRaw(float *value);
int putRaw(double *value);
int putRaw(uint16_t *value);
template <typename V> int putRaw(V *value);
/**
* @brief Adjusts the status argument according to the values of the
+78 -255
View File
@@ -11,59 +11,88 @@
/**
* @brief Struct for type checking the `T` of `mEpicsCa<T>`
*
* This struct is instantiated in the `dbfFromType` method and makes sure that
* `T` (and `V` in case of `mEpicsCa::get` / `mEpicsCa::put`) can only be an
* EPICS-compatible type. The `type` field maps `T` to the corresponding EPICS
* type.
* This struct is instantiated in the `dbfFromType` method and makes
* sure that `T` (and `V` in case of `mEpicsCa::get` / `mEpicsCa::put`) can only
* be an EPICS-compatible type. The `type` field maps `T` to the corresponding
* EPICS type.
*
* @tparam T
*/
template <typename T> struct dbf_type;
template <typename T> struct channelType;
template <> struct dbf_type<int> {
template <> struct channelType<int> {
// On modern systems, int is long
static constexpr int type = DBF_LONG;
static constexpr int dbf = DBF_LONG;
static constexpr int dbr = DBR_LONG;
static constexpr int dbr_time = DBR_TIME_LONG;
using dbr_time_t = dbr_time_long;
};
template <> struct dbf_type<short> {
static constexpr int type = DBF_SHORT;
template <> struct channelType<short> {
static constexpr int dbf = DBF_SHORT;
static constexpr int dbr = DBR_SHORT;
static constexpr int dbr_time = DBR_TIME_SHORT;
using dbr_time_t = dbr_time_short;
};
template <> struct dbf_type<long> {
static constexpr int type = DBF_LONG;
template <> struct channelType<long> {
static constexpr int dbf = DBF_LONG;
static constexpr int dbr = DBR_LONG;
static constexpr int dbr_time = DBR_TIME_LONG;
using dbr_time_t = dbr_time_long;
};
template <> struct dbf_type<float> {
static constexpr int type = DBF_FLOAT;
template <> struct channelType<float> {
static constexpr int dbf = DBF_FLOAT;
static constexpr int dbr = DBR_FLOAT;
static constexpr int dbr_time = DBR_TIME_FLOAT;
using dbr_time_t = dbr_time_float;
};
template <> struct dbf_type<double> {
static constexpr int type = DBF_DOUBLE;
template <> struct channelType<double> {
static constexpr int dbf = DBF_DOUBLE;
static constexpr int dbr = DBR_DOUBLE;
static constexpr int dbr_time = DBR_TIME_DOUBLE;
using dbr_time_t = dbr_time_double;
};
template <> struct dbf_type<char *> {
static constexpr int type = DBF_STRING;
template <> struct channelType<char *> {
static constexpr int dbf = DBF_STRING;
static constexpr int dbr = DBR_STRING;
static constexpr int dbr_time = DBR_TIME_STRING;
using dbr_time_t = dbr_time_string;
};
template <> struct dbf_type<const char *> {
static constexpr int type = DBF_STRING;
template <> struct channelType<const char *> {
static constexpr int dbf = DBF_STRING;
static constexpr int dbr = DBR_STRING;
static constexpr int dbr_time = DBR_TIME_STRING;
using dbr_time_t = dbr_time_string;
};
template <> struct dbf_type<std::string> {
static constexpr int type = DBF_STRING;
template <> struct channelType<std::string> {
static constexpr int dbf = DBF_STRING;
static constexpr int dbr = DBR_STRING;
static constexpr int dbr_time = DBR_TIME_STRING;
using dbr_time_t = dbr_time_string;
};
template <> struct dbf_type<uint16_t> {
static constexpr int type = DBF_ENUM;
template <> struct channelType<uint16_t> {
static constexpr int dbf = DBF_ENUM;
static constexpr int dbr = DBR_ENUM;
static constexpr int dbr_time = DBR_TIME_ENUM;
using dbr_time_t = dbr_time_enum;
};
/**
* @brief Returns the `type` field of `dbf_type<T>`
* @brief Returns the `dbf` field of `channelType<T>`
*
* @tparam T
* @return constexpr int
*/
template <typename T> constexpr int dbfFromType() { return dbf_type<T>::type; }
template <typename T> constexpr int dbfFromType() {
return channelType<T>::dbf;
}
template <typename T, typename V> constexpr void assertEqual() {
static_assert(dbfFromType<T>() == dbfFromType<V>());
@@ -195,51 +224,18 @@ void mEpicsCa<T>::eventCallback(struct event_handler_args args) {
if (args.status != ECA_NORMAL)
return;
// Read out status and severity
switch (args.type) {
case DBR_TIME_STRING: {
auto *v = static_cast<const dbr_time_string *>(args.dbr);
self->_status = static_cast<menuAlarmStat>(v->status);
self->_severity = static_cast<menuAlarmSevr>(v->severity);
break;
}
// DBR_TIME_INT == DBR_TIME_SHORT
case DBR_TIME_SHORT: {
/*
Independently of the value type (dbr_time_string, dbr_time_short,
dbr_time_float, ...), status and severity are always the first two fields
of the structs. This allows us to interpret args.dbr as any one of these
types (because the memory layout at the start of the struct is always the
same). However, we must not read any other fields of v! Therefore, the
lifetime of v is aggressively scoped.
*/
{
auto *v = static_cast<const dbr_time_short *>(args.dbr);
self->_status = static_cast<menuAlarmStat>(v->status);
self->_severity = static_cast<menuAlarmSevr>(v->severity);
break;
}
case DBR_TIME_FLOAT: {
auto *v = static_cast<const dbr_time_float *>(args.dbr);
self->_status = static_cast<menuAlarmStat>(v->status);
self->_severity = static_cast<menuAlarmSevr>(v->severity);
break;
}
case DBR_TIME_ENUM: {
auto *v = static_cast<const dbr_time_enum *>(args.dbr);
self->_status = static_cast<menuAlarmStat>(v->status);
self->_severity = static_cast<menuAlarmSevr>(v->severity);
break;
}
case DBR_TIME_CHAR: {
auto *v = static_cast<const dbr_time_char *>(args.dbr);
self->_status = static_cast<menuAlarmStat>(v->status);
self->_severity = static_cast<menuAlarmSevr>(v->severity);
break;
}
case DBR_TIME_LONG: {
auto *v = static_cast<const dbr_time_long *>(args.dbr);
self->_status = static_cast<menuAlarmStat>(v->status);
self->_severity = static_cast<menuAlarmSevr>(v->severity);
break;
}
case DBR_TIME_DOUBLE: {
auto *v = static_cast<const dbr_time_double *>(args.dbr);
self->_status = static_cast<menuAlarmStat>(v->status);
self->_severity = static_cast<menuAlarmSevr>(v->severity);
break;
}
}
// Read out the actual value and cache it.
@@ -267,7 +263,7 @@ void mEpicsCa<T>::eventCallback(struct event_handler_args args) {
}
}
template <typename T> const std::optional<T> &mEpicsCa<T>::cached() const {
template <typename T> const std::optional<T> mEpicsCa<T>::cached() const {
return _cached;
}
@@ -294,7 +290,8 @@ template <typename T> template <typename V> int mEpicsCa<T>::get(V *value) {
(t == DBF_STRING && v == DBF_ENUM),
"Incompatible EPICS types");
} else {
// Assert T and V are compatible (dbfFromType<T> == dbfFromType<V>)
// Assert T and V are compatible (dbfFromType<T> ==
// dbfFromType<V>)
assertEqual<T, V>();
}
@@ -367,144 +364,29 @@ template <typename T> int mEpicsCa<T>::get(char *buf, u_long len) {
return getRaw(buf, len);
}
template <typename T> int mEpicsCa<T>::getRaw(int *value) {
dbr_time_short dbr = {};
template <typename T> template <typename V> int mEpicsCa<T>::getRaw(V *value) {
using traits = channelType<V>;
using dbr_t = typename traits::dbr_time_t;
int status = convertAndHandleEcaCode(
ca_get(DBR_TIME_INT, this->_pChanID, &dbr), this->_chanName.c_str());
dbr_t dbr{};
int status =
convertAndHandleEcaCode(ca_get(traits::dbr_time, this->_pChanID, &dbr),
this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
status =
convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// Interpret answer AFTER caget actually populated dbr (before that, it is
// uninitialized).
*value = dbr.value;
_status = static_cast<menuAlarmStat>(dbr.status);
_severity = static_cast<menuAlarmSevr>(dbr.severity);
return processStatAndSevr(status);
}
template <typename T> int mEpicsCa<T>::getRaw(short *value) {
dbr_time_short dbr = {};
int status = convertAndHandleEcaCode(
ca_get(DBR_TIME_SHORT, this->_pChanID, &dbr), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
status =
convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// Interpret answer AFTER caget actually populated dbr (before that, it is
// uninitialized).
*value = dbr.value;
_status = static_cast<menuAlarmStat>(dbr.status);
_severity = static_cast<menuAlarmSevr>(dbr.severity);
return processStatAndSevr(status);
}
template <typename T> int mEpicsCa<T>::getRaw(long *value) {
dbr_time_long dbr = {};
int status = convertAndHandleEcaCode(
ca_get(DBR_TIME_LONG, this->_pChanID, &dbr), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
status =
convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// Interpret answer AFTER caget actually populated dbr (before that, it is
// uninitialized).
*value = dbr.value;
_status = static_cast<menuAlarmStat>(dbr.status);
_severity = static_cast<menuAlarmSevr>(dbr.severity);
return processStatAndSevr(status);
}
template <typename T> int mEpicsCa<T>::getRaw(float *value) {
dbr_time_float dbr = {};
int status = convertAndHandleEcaCode(
ca_get(DBR_TIME_FLOAT, this->_pChanID, &dbr), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
status =
convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// Interpret answer AFTER caget actually populated dbr (before that, it is
// uninitialized).
*value = dbr.value;
_status = static_cast<menuAlarmStat>(dbr.status);
_severity = static_cast<menuAlarmSevr>(dbr.severity);
return processStatAndSevr(status);
}
template <typename T> int mEpicsCa<T>::getRaw(double *value) {
dbr_time_double dbr = {};
int status = convertAndHandleEcaCode(
ca_get(DBR_TIME_DOUBLE, this->_pChanID, &dbr), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
status =
convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// Interpret answer AFTER caget actually populated dbr (before that, it is
// uninitialized).
*value = dbr.value;
_status = static_cast<menuAlarmStat>(dbr.status);
_severity = static_cast<menuAlarmSevr>(dbr.severity);
return processStatAndSevr(status);
}
template <typename T> int mEpicsCa<T>::getRaw(uint16_t *value) {
dbr_time_enum dbr = {};
int status = convertAndHandleEcaCode(
ca_get(DBR_TIME_ENUM, this->_pChanID, &dbr), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
status =
convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// Interpret answer AFTER caget actually populated dbr (before that, it is
// uninitialized).
*value = dbr.value;
_status = static_cast<menuAlarmStat>(dbr.status);
_severity = static_cast<menuAlarmSevr>(dbr.severity);
return processStatAndSevr(status);
}
@@ -624,69 +506,10 @@ template <typename T> int mEpicsCa<T>::put(const char *buf, u_long len) {
return putRaw(buf, len);
}
template <typename T> int mEpicsCa<T>::putRaw(int *value) {
int status = convertAndHandleEcaCode(ca_put(DBR_INT, this->_pChanID, value),
this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
return convertAndHandleEcaCode(ca_pend_io(_timeout),
this->_chanName.c_str());
}
template <typename T> int mEpicsCa<T>::putRaw(short *value) {
template <typename T> template <typename V> int mEpicsCa<T>::putRaw(V *value) {
int status = convertAndHandleEcaCode(
ca_put(DBR_SHORT, this->_pChanID, value), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
return convertAndHandleEcaCode(ca_pend_io(_timeout),
this->_chanName.c_str());
}
template <typename T> int mEpicsCa<T>::putRaw(long *value) {
int status = convertAndHandleEcaCode(
ca_put(DBR_LONG, this->_pChanID, value), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
return convertAndHandleEcaCode(ca_pend_io(_timeout),
this->_chanName.c_str());
}
template <typename T> int mEpicsCa<T>::putRaw(float *value) {
int status = convertAndHandleEcaCode(
ca_put(DBR_FLOAT, this->_pChanID, value), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
return convertAndHandleEcaCode(ca_pend_io(_timeout),
this->_chanName.c_str());
}
template <typename T> int mEpicsCa<T>::putRaw(double *value) {
int status = convertAndHandleEcaCode(
ca_put(DBR_DOUBLE, this->_pChanID, value), this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;
// caget is asynchronous, so the pointer value is only updated when
// ca_pend_io returns ECA_NORMAL
return convertAndHandleEcaCode(ca_pend_io(_timeout),
this->_chanName.c_str());
}
template <typename T> int mEpicsCa<T>::putRaw(u_int16_t *value) {
int status = convertAndHandleEcaCode(
ca_put(DBR_ENUM, this->_pChanID, value), this->_chanName.c_str());
ca_put(channelType<V>::dbr, this->_pChanID, value),
this->_chanName.c_str());
if (status != CM_SUCCESS)
return status;