WIP state for Hugo to potentially fix a segfault
This commit is contained in:
+44
-8
@@ -1,6 +1,21 @@
|
||||
#ifndef mEpicsCa_H
|
||||
#define mEpicsCa_H
|
||||
|
||||
#include "cadef.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief Information about the channel type (integer, double, string, ...)
|
||||
*/
|
||||
struct ChInfo {
|
||||
// Channel type (like e.g. DBF_INT, DBF_DOUBLE, DBF_STRING etc.)
|
||||
short type;
|
||||
// Number of elements. Is 1 for most record types except for waveforms,
|
||||
// whose length is defined in the record.
|
||||
int count;
|
||||
};
|
||||
|
||||
template <typename T> class mEpicsCa {
|
||||
public:
|
||||
/**
|
||||
@@ -12,25 +27,25 @@ template <typename T> class mEpicsCa {
|
||||
* @param priority
|
||||
* @param pChanID
|
||||
*/
|
||||
mEpicsCa(const char *pChanName, caCh *pConnStateCallback,
|
||||
mEpicsCa(const char *pChanName, bool subscribe, caCh *pConnStateCallback,
|
||||
void *pUserPrivate, double timeout = 2.0);
|
||||
|
||||
mEpicsCa(const char *pChanName);
|
||||
mEpicsCa(const char *pChanName, bool subscribe);
|
||||
|
||||
~mEpicsCa();
|
||||
|
||||
template <typename V> int put(V *value, double timeout);
|
||||
template <typename V> int get(V *value);
|
||||
int get(char *buf, u_long len);
|
||||
|
||||
template <typename V> int put(V *value);
|
||||
|
||||
template <typename V> int get(V *value);
|
||||
|
||||
template <typename V> int get(V *value, double timeout);
|
||||
int put(const char *buf, u_long len);
|
||||
|
||||
bool connected();
|
||||
|
||||
double _timeout;
|
||||
|
||||
const std::optional<T> &cached() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief A callback for reacting to channel access channel state changes.
|
||||
@@ -44,8 +59,29 @@ template <typename T> class mEpicsCa {
|
||||
*/
|
||||
static void connStateCallback(struct connection_handler_args args);
|
||||
|
||||
static void eventCallback(struct event_handler_args args);
|
||||
|
||||
int getRaw(char *buf, size_t len);
|
||||
int getRaw(int *value);
|
||||
int getRaw(double *value);
|
||||
int getRaw(uint16_t *value);
|
||||
|
||||
int putRaw(const char *buf, size_t len);
|
||||
int putRaw(int *value);
|
||||
int putRaw(double *value);
|
||||
int putRaw(uint16_t *value);
|
||||
|
||||
std::optional<ChInfo> _channelInfo;
|
||||
chid _pChanID;
|
||||
std::string _chanName;
|
||||
|
||||
/*
|
||||
In case the subscription mechanism is used, this variable holds the last
|
||||
value returned by the callback.
|
||||
*/
|
||||
std::optional<T> _cached;
|
||||
};
|
||||
|
||||
#include "m_epics_ca.tpp"
|
||||
#include "m_epics_ca.tpp"
|
||||
|
||||
#endif
|
||||
+378
-98
@@ -1,6 +1,7 @@
|
||||
#include "cadef.h"
|
||||
#include "dbDefs.h"
|
||||
#include "midas.h"
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <iostream>
|
||||
@@ -9,38 +10,33 @@
|
||||
// Helper struct for dbfFromType
|
||||
template <typename> struct alwaysFalse : std::false_type {};
|
||||
|
||||
/**
|
||||
* @brief Check at compile time if T is a valid database field (DBF) type.
|
||||
*
|
||||
* This function will fail to compile if T is not a valid database field type.
|
||||
*
|
||||
* @tparam T
|
||||
* @return int
|
||||
*/
|
||||
template <typename T> constexpr int dbfFromType() {
|
||||
if constexpr (std::is_same_v<T, char *> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, std::string *>) {
|
||||
return DBF_STRING;
|
||||
} else if constexpr (std::is_same_v<T, int>) {
|
||||
return DBF_INT; // Same as DBF_SHORT
|
||||
} else if constexpr (std::is_same_v<T, float>) {
|
||||
return DBF_FLOAT;
|
||||
} else if constexpr (std::is_enum_v<T>) {
|
||||
return DBF_ENUM;
|
||||
} else if constexpr (std::is_same_v<T, char>) {
|
||||
return DBF_CHAR;
|
||||
} else if constexpr (std::is_same_v<T, long>) {
|
||||
return DBF_LONG;
|
||||
} else if constexpr (std::is_same_v<T, double>) {
|
||||
return DBF_DOUBLE;
|
||||
} else {
|
||||
static_assert(alwaysFalse<T>::value,
|
||||
"Unsupported EPICS database field type. See "
|
||||
"epics-base/src/ca/client/db_access.h for the list "
|
||||
"of allowed types");
|
||||
}
|
||||
}
|
||||
template <typename T> struct dbf_type;
|
||||
|
||||
template <> struct dbf_type<int> {
|
||||
static constexpr int value = DBF_INT;
|
||||
};
|
||||
|
||||
template <> struct dbf_type<double> {
|
||||
static constexpr int value = DBF_DOUBLE;
|
||||
};
|
||||
|
||||
template <> struct dbf_type<char *> {
|
||||
static constexpr int value = DBF_STRING;
|
||||
};
|
||||
|
||||
template <> struct dbf_type<const char *> {
|
||||
static constexpr int value = DBF_STRING;
|
||||
};
|
||||
|
||||
template <> struct dbf_type<std::string> {
|
||||
static constexpr int value = DBF_STRING;
|
||||
};
|
||||
|
||||
template <> struct dbf_type<uint16_t> {
|
||||
static constexpr int value = DBF_ENUM;
|
||||
};
|
||||
|
||||
template <typename T> constexpr int dbfFromType() { return dbf_type<T>::value; }
|
||||
|
||||
template <typename T, typename V> constexpr void assertEqual() {
|
||||
static_assert(dbfFromType<T>() == dbfFromType<V>());
|
||||
@@ -48,93 +44,111 @@ template <typename T, typename V> constexpr void assertEqual() {
|
||||
|
||||
/**
|
||||
* @brief Handle the ECA (error channel access) code returned by the ca_get and
|
||||
* ca_put macros.
|
||||
* ca_put macros and convert it to a MIDAS errror code.
|
||||
*
|
||||
* Handling means creating an appropriate MIDAS error message to inform the
|
||||
* user.
|
||||
*
|
||||
* @param status
|
||||
* @param status EPRICS status code
|
||||
* @param pChanName: Name of the EPICS channel
|
||||
* @return int Corresponding MIDAS status code
|
||||
*/
|
||||
void handleEcaCode(int status, const char *pChanName) {
|
||||
int convertAndHandleEcaCode(int status, const char *pChanName) {
|
||||
switch (status) {
|
||||
case ECA_NORMAL:
|
||||
break;
|
||||
return CM_SUCCESS;
|
||||
case ECA_BADCHID:
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Channel ID has been corrupted. Restart the IOC.",
|
||||
pChanName);
|
||||
break;
|
||||
return FE_ERR_DISABLED;
|
||||
case ECA_BADTYPE:
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Type mismatch. This is a driver bug, please call the "
|
||||
"support.",
|
||||
pChanName);
|
||||
break;
|
||||
return FE_ERR_DRIVER;
|
||||
case ECA_BADCOUNT:
|
||||
cm_msg(
|
||||
MERROR, __FILE__,
|
||||
"PV '%s': Invalid element count requested. This is a driver bug, "
|
||||
"please call the support.",
|
||||
pChanName);
|
||||
break;
|
||||
return FE_ERR_DRIVER;
|
||||
case ECA_STRTOBIG:
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': String length exceeds allowed maximum length. Try "
|
||||
"providing a shorter string or call the support.",
|
||||
pChanName);
|
||||
break;
|
||||
return FE_ERR_DRIVER;
|
||||
case ECA_NOWTACCESS:
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Write access denied. Please call the support.",
|
||||
pChanName);
|
||||
break;
|
||||
return FE_ERR_DRIVER;
|
||||
case ECA_ALLOCMEM:
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Unable to allocate memory. Please call the support.",
|
||||
pChanName);
|
||||
break;
|
||||
return FE_ERR_DRIVER;
|
||||
case ECA_DISCONN:
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Channel disconnected. Check if the PV name is correct "
|
||||
"and if the IOC is running.",
|
||||
pChanName);
|
||||
break;
|
||||
return FE_ERR_DRIVER;
|
||||
case ECA_TIMEOUT:
|
||||
cm_msg(
|
||||
MERROR, __FILE__,
|
||||
"PV '%s': Timed out while trying to read. Check if the PV name is "
|
||||
"correct and if the IOC is running.",
|
||||
pChanName);
|
||||
break;
|
||||
return CM_TIMEOUT;
|
||||
default:
|
||||
cm_msg(MERROR, __FILE__, "PV '%s': Received unknown error code %d.",
|
||||
pChanName, status);
|
||||
break;
|
||||
return FE_ERR_DRIVER;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
mEpicsCa<T>::mEpicsCa(const char *pChanName, caCh *pConnStateCallback,
|
||||
void *pUserPrivate, double timeout)
|
||||
: _timeout(timeout), _pChanID(nullptr), _chanName(pChanName) {
|
||||
mEpicsCa<T>::mEpicsCa(const char *pChanName, bool subscribe,
|
||||
caCh *pConnStateCallback, void *pUserPrivate,
|
||||
double timeout)
|
||||
: _timeout(timeout), _pChanID(nullptr), _chanName(pChanName),
|
||||
_cached(std::nullopt) {
|
||||
|
||||
// Compile time check of T
|
||||
dbfFromType<T>();
|
||||
int status = CA_M_SUCCESS;
|
||||
int status = CM_SUCCESS;
|
||||
|
||||
/*
|
||||
ca_disable_preemptive_callback: No callback thread is created and CA context
|
||||
has to be "polled" (e.g. via ca_pend_event or ca_pend_io). All callbacks
|
||||
happen in the main thread.
|
||||
ca_enable_preemptive_callback: EPICS creates a dedicated callback thread
|
||||
where the callback is run.
|
||||
|
||||
Obviously, ca_enable_preemptive_callback is the variant we want for
|
||||
subscription.
|
||||
*/
|
||||
auto choice = ca_preemptive_callback_select::ca_disable_preemptive_callback;
|
||||
if (subscribe) {
|
||||
choice = ca_preemptive_callback_select::ca_enable_preemptive_callback;
|
||||
}
|
||||
|
||||
/*
|
||||
Try to create the Channel access context explicitly:
|
||||
https://docs.epics-controls.org/projects/base/en/latest/cadef_h.html#_CPPv417ca_context_create29ca_preemptive_callback_select
|
||||
This function returns ECA_NOTTHREADED if a context has already been
|
||||
created for the current thread. This is generally not an issue and can
|
||||
safely be ignored. All other failures are handled in handleEcaCode.
|
||||
Preemptive callbacks are disabled by default for this driver.
|
||||
safely be ignored. All other failures are handled in
|
||||
convertAndHandleEcaCode. Preemptive callbacks are disabled by default for
|
||||
this driver.
|
||||
*/
|
||||
status = ca_context_create(
|
||||
ca_preemptive_callback_select::ca_disable_preemptive_callback);
|
||||
if (status != ECA_NORMAL && status != ECA_NOTTHREADED) {
|
||||
handleEcaCode(status, this->_chanName.c_str());
|
||||
status = ca_context_create(choice);
|
||||
if (status != ECA_NOTTHREADED) {
|
||||
status = convertAndHandleEcaCode(status, this->_chanName.c_str());
|
||||
}
|
||||
|
||||
// Use the user-provided callback, if it is not NULL, otherwise use the
|
||||
@@ -146,72 +160,332 @@ mEpicsCa<T>::mEpicsCa(const char *pChanName, caCh *pConnStateCallback,
|
||||
|
||||
// 0 is the lowest network priority, 99 is the highest.
|
||||
int priority = 0;
|
||||
status = ca_create_channel(pChanName, pConnStateCallback, pUserPrivate,
|
||||
priority, &this->_pChanID);
|
||||
handleEcaCode(status, this->_chanName.c_str());
|
||||
status = convertAndHandleEcaCode(
|
||||
ca_create_channel(pChanName, pConnStateCallback, pUserPrivate, priority,
|
||||
&this->_pChanID),
|
||||
this->_chanName.c_str());
|
||||
|
||||
// Store a pointer to "this" in the channel for retrieval in the
|
||||
// connStateCallback function.
|
||||
ca_set_puser(this->_pChanID, this);
|
||||
|
||||
if (subscribe) {
|
||||
ca_create_subscription(dbf_type<T>::value, 1, this->_pChanID, DBE_VALUE,
|
||||
&mEpicsCa<T>::eventCallback, this, nullptr);
|
||||
}
|
||||
|
||||
SEVCHK(status, "ca_create_channel");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
mEpicsCa<T>::mEpicsCa(const char *pChanName)
|
||||
: mEpicsCa(pChanName, nullptr, nullptr) {}
|
||||
void mEpicsCa<T>::eventCallback(struct event_handler_args args) {
|
||||
auto *self = static_cast<mEpicsCa<T> *>(ca_puser(args.chid));
|
||||
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
// Connection events are handled in connStateCallback, which also
|
||||
// invalidates the cache when disconnecting.
|
||||
if (args.status != ECA_NORMAL)
|
||||
return;
|
||||
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
// Special handling of string type: args.dbr will be a char array, so
|
||||
// we need to cast to a char array first which is then converted into
|
||||
// a string.
|
||||
self->_cached = static_cast<const char *>(args.dbr);
|
||||
} else {
|
||||
self->_cached = *static_cast<const T *>(args.dbr);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> const std::optional<T> &mEpicsCa<T>::cached() const {
|
||||
return _cached;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
mEpicsCa<T>::mEpicsCa(const char *pChanName, bool subscribe)
|
||||
: mEpicsCa(pChanName, subscribe, nullptr, nullptr) {}
|
||||
|
||||
template <typename T> mEpicsCa<T>::~mEpicsCa() {
|
||||
// Free up the channel resources
|
||||
ca_clear_channel(this->_pChanID);
|
||||
}
|
||||
|
||||
template <typename T> template <typename V> int mEpicsCa<T>::put(V *value) {
|
||||
return put(value, _timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename V>
|
||||
int mEpicsCa<T>::put(V *value, double timeout) {
|
||||
assertEqual<T, V>();
|
||||
int dbfType = dbfFromType<V>();
|
||||
|
||||
int status = ca_put(dbfType, this->_pChanID, value);
|
||||
handleEcaCode(status, this->_chanName.c_str());
|
||||
|
||||
if (status != ECA_NORMAL)
|
||||
return status;
|
||||
|
||||
// caget is asynchronous, so the pointer value is only updated when
|
||||
// ca_pend_io returns ECA_NORMAL
|
||||
status = ca_pend_io(timeout);
|
||||
|
||||
handleEcaCode(status, this->_chanName.c_str());
|
||||
return status;
|
||||
convertAndHandleEcaCode(ca_clear_channel(this->_pChanID),
|
||||
this->_chanName.c_str());
|
||||
}
|
||||
|
||||
template <typename T> template <typename V> int mEpicsCa<T>::get(V *value) {
|
||||
return get(value, _timeout);
|
||||
|
||||
// If "this" has the type mEpicsCa<uint16_t>, then V needs to be a string
|
||||
// or an uint16_t.
|
||||
if constexpr (dbfFromType<T>() == DBF_ENUM) {
|
||||
constexpr auto t = dbfFromType<T>();
|
||||
constexpr auto v = dbfFromType<V>();
|
||||
static_assert((t == v) || (t == DBF_ENUM && v == DBF_STRING) ||
|
||||
(t == DBF_STRING && v == DBF_ENUM),
|
||||
"Incompatible EPICS types");
|
||||
} else {
|
||||
assertEqual<T, V>();
|
||||
}
|
||||
|
||||
if (!connected()) {
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Channel disconnected. Check if the PV name is correct "
|
||||
"and if the IOC is running.",
|
||||
_chanName.c_str());
|
||||
return FE_ERR_DRIVER;
|
||||
}
|
||||
|
||||
// If a subscription mechanism is active, use the last cached value instead
|
||||
// of explictly invoking ca_get
|
||||
if (_cached.has_value()) {
|
||||
*value = _cached.value();
|
||||
return CM_SUCCESS;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<V, std::string>) {
|
||||
char buf[40] = {0};
|
||||
|
||||
int status = getRaw(buf, sizeof(buf));
|
||||
if (status == CM_SUCCESS)
|
||||
*value = buf;
|
||||
return status;
|
||||
} else {
|
||||
return getRaw(value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename V>
|
||||
int mEpicsCa<T>::get(V *value, double timeout) {
|
||||
assertEqual<T, V>();
|
||||
int dbfType = dbfFromType<V>();
|
||||
template <typename T> int mEpicsCa<T>::get(char *buf, u_long len) {
|
||||
|
||||
int status = ca_get(dbfType, this->_pChanID, value);
|
||||
handleEcaCode(status, this->_chanName.c_str());
|
||||
// If "this" has the type mEpicsCa<uint16_t>, then the type check is omitted
|
||||
if (dbfFromType<T>() != DBF_ENUM) {
|
||||
assertEqual<T, char *>();
|
||||
}
|
||||
|
||||
if (status != ECA_NORMAL)
|
||||
if (!connected()) {
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Channel disconnected. Check if the PV name is correct "
|
||||
"and if the IOC is running.",
|
||||
_chanName.c_str());
|
||||
return FE_ERR_DRIVER;
|
||||
}
|
||||
|
||||
// If a subscription mechanism is active, use the last cached value instead
|
||||
// of explictly invoking ca_get
|
||||
if (_cached.has_value()) {
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
strncpy(buf, _cached.value().c_str(), len);
|
||||
} else {
|
||||
strncpy(buf, _cached.value(), len);
|
||||
}
|
||||
return CM_SUCCESS;
|
||||
}
|
||||
|
||||
return getRaw(buf, len);
|
||||
}
|
||||
|
||||
template <typename T> int mEpicsCa<T>::getRaw(int *value) {
|
||||
int status = convertAndHandleEcaCode(
|
||||
ca_get(dbfFromType<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
|
||||
status = ca_pend_io(timeout);
|
||||
return convertAndHandleEcaCode(ca_pend_io(_timeout),
|
||||
this->_chanName.c_str());
|
||||
}
|
||||
|
||||
handleEcaCode(status, this->_chanName.c_str());
|
||||
return status;
|
||||
template <typename T> int mEpicsCa<T>::getRaw(double *value) {
|
||||
int status = convertAndHandleEcaCode(
|
||||
ca_get(dbfFromType<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>::getRaw(uint16_t *value) {
|
||||
int status = convertAndHandleEcaCode(
|
||||
ca_get(dbfFromType<uint16_t>(), 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>::getRaw(char *buf, u_long len) {
|
||||
int status = CM_SUCCESS;
|
||||
if (_channelInfo.value().count == 1) {
|
||||
// String record
|
||||
status = convertAndHandleEcaCode(
|
||||
ca_get(dbfFromType<char *>(), this->_pChanID, buf),
|
||||
this->_chanName.c_str());
|
||||
} else {
|
||||
// Waveform record
|
||||
status = convertAndHandleEcaCode(
|
||||
ca_array_get(DBR_CHAR, len, this->_pChanID, buf),
|
||||
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());
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// put
|
||||
// =============================================================================
|
||||
|
||||
template <typename T> template <typename V> int mEpicsCa<T>::put(V *value) {
|
||||
|
||||
// If "this" has the type mEpicsCa<uint16_t>, then V needs to be a string or
|
||||
// an integer
|
||||
if constexpr (dbfFromType<T>() == DBF_ENUM) {
|
||||
constexpr auto t = dbfFromType<T>();
|
||||
constexpr auto v = dbfFromType<V>();
|
||||
static_assert((t == v) || (t == DBF_ENUM && v == DBF_STRING) ||
|
||||
(t == DBF_STRING && v == DBF_ENUM),
|
||||
"Incompatible EPICS types");
|
||||
} else {
|
||||
assertEqual<T, V>();
|
||||
}
|
||||
|
||||
if (!connected()) {
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Channel disconnected. Check if the PV name is correct "
|
||||
"and if the IOC is running.",
|
||||
_chanName.c_str());
|
||||
return FE_ERR_DRIVER;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<V, std::string>) {
|
||||
// Use the c-style string buffer put algorithm
|
||||
return putRaw(value->c_str(), value->size());
|
||||
} else {
|
||||
return putRaw(value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> int mEpicsCa<T>::put(const char *buf, u_long len) {
|
||||
|
||||
// If "this" has the type mEpicsCa<uint16_t>, then the type check is omitted
|
||||
if constexpr (dbfFromType<T>() != DBF_ENUM) {
|
||||
assertEqual<T, char *>();
|
||||
}
|
||||
|
||||
if (!connected()) {
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Channel disconnected. Check if the PV name is correct "
|
||||
"and if the IOC is running.",
|
||||
_chanName.c_str());
|
||||
return FE_ERR_DRIVER;
|
||||
}
|
||||
return putRaw(buf, len);
|
||||
}
|
||||
|
||||
template <typename T> int mEpicsCa<T>::putRaw(double *value) {
|
||||
int status = convertAndHandleEcaCode(
|
||||
ca_put(dbfFromType<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(dbfFromType<uint16_t>(), 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(const char *buf, u_long len) {
|
||||
|
||||
if constexpr (dbfFromType<T>() == 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);
|
||||
|
||||
std::string variants;
|
||||
|
||||
for (int i = 0; i < data.no_str; ++i) {
|
||||
if (i > 0)
|
||||
variants += ", ";
|
||||
|
||||
variants += data.strs[i];
|
||||
|
||||
if (strcmp(data.strs[i], buf) == 0)
|
||||
return putRaw(&i);
|
||||
}
|
||||
|
||||
// Given string did not match any of the variants -> return an error
|
||||
cm_msg(MERROR, __FILE__,
|
||||
"PV '%s': Given string '%s' does not match one of the enum "
|
||||
"variants [%s]",
|
||||
this->_chanName.c_str(), buf, variants.c_str());
|
||||
return FE_ERR_DRIVER;
|
||||
} else {
|
||||
int status = CM_SUCCESS;
|
||||
if (_channelInfo.value().count == 1) {
|
||||
// String record
|
||||
status = convertAndHandleEcaCode(
|
||||
ca_put(dbfFromType<char *>(), this->_pChanID, buf),
|
||||
this->_chanName.c_str());
|
||||
} else {
|
||||
// Waveform record
|
||||
status = convertAndHandleEcaCode(
|
||||
ca_array_put(DBR_CHAR, len, this->_pChanID, buf),
|
||||
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(int *value) {
|
||||
int status = convertAndHandleEcaCode(
|
||||
ca_put(dbfFromType<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>
|
||||
@@ -219,12 +493,18 @@ void mEpicsCa<T>::connStateCallback(struct connection_handler_args args) {
|
||||
mEpicsCa *self = static_cast<mEpicsCa *>(ca_puser(args.chid));
|
||||
|
||||
if (args.op == CA_OP_CONN_UP) {
|
||||
struct ChInfo info;
|
||||
info.type = ca_field_type(self->_pChanID);
|
||||
info.count = ca_element_count(self->_pChanID);
|
||||
self->_channelInfo = std::optional(info);
|
||||
cm_msg(MDEBUG, __FILE__, "PV %s connected", self->_chanName.c_str());
|
||||
} else {
|
||||
self->_channelInfo.reset();
|
||||
self->_cached.reset();
|
||||
cm_msg(MDEBUG, __FILE__, "PV %s disconnected", self->_chanName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> bool mEpicsCa<T>::connected() {
|
||||
return _pChanID && ca_state(_pChanID) == cs_conn;
|
||||
return _channelInfo.has_value();
|
||||
}
|
||||
Reference in New Issue
Block a user