WIP state for Hugo to potentially fix a segfault
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
||||
.vscode
|
||||
build
|
||||
.ioc
|
||||
.ioc*
|
||||
+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();
|
||||
}
|
||||
+19
-4
@@ -1,10 +1,25 @@
|
||||
record(longin, "TEST:INT") {
|
||||
field(DESC, "Example record")
|
||||
record(longin, "MEPICSCA:TEST:LONGIN") {
|
||||
field(VAL, 42)
|
||||
field(HOPR, 100)
|
||||
field(LOPR, 0)
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
record(ai, "TEST:DOUBLE") {
|
||||
field(DESC, "Example record")
|
||||
record(ai, "MEPICSCA:TEST:AI") {
|
||||
field(VAL, 84)
|
||||
}
|
||||
|
||||
record(stringin, "MEPICSCA:TEST:STRINGIN") {
|
||||
field(VAL, "MyTestString")
|
||||
}
|
||||
|
||||
record(waveform, "MEPICSCA:TEST:WAVEFORM") {
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "200")
|
||||
}
|
||||
|
||||
record(bo, "MEPICSCA:TEST:BO") {
|
||||
field(VAL, 1)
|
||||
field(ZNAM, "OFF")
|
||||
field(ONAM, "ON")
|
||||
}
|
||||
@@ -3,3 +3,6 @@
|
||||
dbLoadRecords("$(IOCDIR)/record.db")
|
||||
|
||||
iocInit()
|
||||
|
||||
# Initialize the waveform record
|
||||
dbpf("MEPICSCA:TEST:WAVEFORM", "ABC")
|
||||
|
||||
+2
-1
@@ -7,4 +7,5 @@ export EPICS_CA_ADDR_LIST=127.0.0.1
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
export IOCDIR=$(pwd)
|
||||
./st.cmd
|
||||
|
||||
exec ./st.cmd
|
||||
|
||||
+324
-51
@@ -2,7 +2,10 @@
|
||||
#include "m_epics_ca.h"
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
@@ -10,7 +13,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CHECK(cond) \
|
||||
#define ASSERT(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "Test failed: " #cond << std::endl; \
|
||||
@@ -21,9 +24,9 @@
|
||||
#define EQUAL(left, right) \
|
||||
do { \
|
||||
if (!(left == right)) { \
|
||||
std::cerr << "\nTest failed: " << (left) << " (" << #left \
|
||||
<< ") != " << (right) << " (" << #right << *")" \
|
||||
<< std::endl; \
|
||||
std::cerr << "\nTest failed at line " << __LINE__ << ": " \
|
||||
<< (left) << " (" << #left << ") != " << (right) << " (" \
|
||||
<< #right << *")" << std::endl; \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
@@ -31,12 +34,24 @@
|
||||
class IocProcess {
|
||||
public:
|
||||
explicit IocProcess(const std::string &path) {
|
||||
/*
|
||||
Important note: DO NOT USE setpgid HERE!
|
||||
Even though it looks "cleaner" than the pkill solution in the destructor
|
||||
below (because you could just terminate the group), this seems to cause
|
||||
trouble deep within EPICS, causing the server not to be reachable
|
||||
anymore with caget.
|
||||
*/
|
||||
_pid = fork();
|
||||
|
||||
if (_pid == 0) {
|
||||
|
||||
// Hide IOC output
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
// Start the IOC from the child process
|
||||
setpgid(0, 0);
|
||||
execl(path.c_str(), path.c_str(), (char *)nullptr);
|
||||
execl(path.c_str(), path.c_str(), (char *)NULL);
|
||||
perror("execl failed");
|
||||
_exit(1);
|
||||
}
|
||||
@@ -44,17 +59,13 @@ class IocProcess {
|
||||
if (_pid < 0) {
|
||||
throw std::runtime_error("fork failed");
|
||||
}
|
||||
// Set the ID of the entire process group so it can be torn down in the
|
||||
// destructor later. This ensures IOC teardown.
|
||||
setpgid(_pid, _pid);
|
||||
}
|
||||
|
||||
~IocProcess() {
|
||||
if (_pid <= 0)
|
||||
return;
|
||||
|
||||
// Kill the entire process group due to the -1
|
||||
kill(-_pid, SIGTERM);
|
||||
kill(_pid, SIGTERM);
|
||||
|
||||
// Give the IOC 50 * 100 * 1000 = 5 seconds time for graceful
|
||||
// termination.
|
||||
@@ -66,25 +77,78 @@ class IocProcess {
|
||||
usleep(100 * 1000);
|
||||
}
|
||||
|
||||
// Force kill group if needed
|
||||
kill(-_pid, SIGKILL);
|
||||
waitpid(_pid, nullptr, 0);
|
||||
kill(_pid, SIGKILL);
|
||||
}
|
||||
|
||||
private:
|
||||
pid_t _pid = -1;
|
||||
};
|
||||
|
||||
void handler(int sig) { printf("Received signal: %d\n", sig); }
|
||||
|
||||
int main() {
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
signal(SIGPIPE, handler);
|
||||
signal(SIGTERM, handler);
|
||||
signal(SIGINT, handler);
|
||||
signal(SIGHUP, handler);
|
||||
|
||||
// Client setting for local testing
|
||||
setenv("EPICS_CA_AUTO_ADDR_LIST", "NO", 1);
|
||||
setenv("EPICS_CA_ADDR_LIST", "127.0.0.255", 1);
|
||||
setenv("EPICS_CA_ADDR_LIST", "127.0.0.1", 1);
|
||||
|
||||
// Create a new instance of the mEpicsCa bus driver and use it to
|
||||
// interact with the spawned IOC.
|
||||
auto int_ca = mEpicsCa<int>("MEPICSCA:TEST:LONGIN", false);
|
||||
auto int_hopr_ca = mEpicsCa<int>("MEPICSCA:TEST:LONGIN.HOPR", false);
|
||||
auto double_ca = mEpicsCa<double>("MEPICSCA:TEST:AI", false);
|
||||
auto string_ca = mEpicsCa<std::string>("MEPICSCA:TEST:STRINGIN", false);
|
||||
auto waveform_ca = mEpicsCa<std::string>("MEPICSCA:TEST:WAVEFORM", false);
|
||||
auto bo_ca = mEpicsCa<uint16_t>("MEPICSCA:TEST:BO", false);
|
||||
|
||||
// Ending s indicates subscription mechanism is active
|
||||
auto int_cas = mEpicsCa<int>("MEPICSCA:TEST:LONGIN", true);
|
||||
|
||||
// Generic status variable which is reused for the error codes throughout
|
||||
// the tests.
|
||||
int status = CM_SUCCESS;
|
||||
|
||||
{
|
||||
// =====================================================================
|
||||
// Tests before IOC is running
|
||||
// =====================================================================
|
||||
|
||||
// Drivers are not connected
|
||||
ASSERT((!int_ca.connected()));
|
||||
|
||||
// Cached values are invalid
|
||||
ASSERT((!int_cas.cached().has_value()));
|
||||
|
||||
// Attempting to write or read a value results in an error. stdout is
|
||||
// suppressed for the duration of this test
|
||||
|
||||
int saved_stdout = dup(STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
// Restore stdout
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
}
|
||||
|
||||
// Scope everything so the IOC gets torn down by the IocProcess destructor
|
||||
// before reporting that the tests were successfull.
|
||||
{
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
// Start the test IOC
|
||||
std::string path = __FILE__;
|
||||
@@ -92,11 +156,6 @@ int main() {
|
||||
std::string ioc_exe = dir + "/ioc/startioc";
|
||||
IocProcess proc = IocProcess(ioc_exe);
|
||||
|
||||
// Create a new instance of the mEpicsCa bus driver and use it to
|
||||
// interact with the spawned IOC.
|
||||
mEpicsCa<int> int_ca = mEpicsCa<int>("TEST:INT");
|
||||
mEpicsCa<double> double_ca = mEpicsCa<double>("TEST:DOUBLE");
|
||||
|
||||
// Wait for the IOC to become available
|
||||
double timeout = 20.0;
|
||||
|
||||
@@ -111,46 +170,260 @@ int main() {
|
||||
auto now = clock::now();
|
||||
double elapsed = std::chrono::duration<double>(now - start).count();
|
||||
if (elapsed > timeout) {
|
||||
printf(
|
||||
"Could not connect to IOC in %.3lf seconds. The IOC likely "
|
||||
"failed to start.\n",
|
||||
timeout);
|
||||
printf("Could not connect to IOC in %.3lf seconds. The IOC "
|
||||
"likely "
|
||||
"failed to start.\n",
|
||||
timeout);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
int status = ECA_NORMAL;
|
||||
// =====================================================================
|
||||
// No subscription
|
||||
// =====================================================================
|
||||
|
||||
// Read an integer from a record
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(int_val, 42);
|
||||
{
|
||||
// -----------------------------------------------------------------
|
||||
// Test get
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// Read a double from a record
|
||||
double double_val = 0.0;
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(double_val, 84.0);
|
||||
// Read an integer from a record
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, 42);
|
||||
|
||||
// Write an integer to a record and read it to check
|
||||
int new_int_val = 21;
|
||||
status = int_ca.put(&new_int_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(int_val, new_int_val);
|
||||
// Read from a different field
|
||||
status = int_hopr_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, 100);
|
||||
|
||||
// Write a double to a record and read it to check
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(double_val, new_double_val);
|
||||
// Read a double from a record
|
||||
double double_val = 0.0;
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(double_val, 84.0);
|
||||
|
||||
// Read a string from a stringin record using a string
|
||||
std::string string_val = "";
|
||||
status = string_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "MyTestString");
|
||||
|
||||
// Read a string from a stringin record using a char array
|
||||
char char_arr_val[40] = {0};
|
||||
status = string_ca.get(char_arr_val, sizeof(char_arr_val));
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(std::string(char_arr_val), "MyTestString");
|
||||
|
||||
// Read a string from a waveform record
|
||||
status = waveform_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ABC");
|
||||
|
||||
// Read an enum as a value and as a string
|
||||
uint16_t short_uint_val = 0;
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 1);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ON");
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Test put
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// Write an integer to a record and read it to check
|
||||
int new_int_val = 21;
|
||||
status = int_ca.put(&new_int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, new_int_val);
|
||||
|
||||
// Write to the limit field of the longin record
|
||||
new_int_val = 40;
|
||||
status = int_hopr_ca.put(&new_int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
int_val = 0; // Overwrite with other value
|
||||
status = int_hopr_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, new_int_val);
|
||||
|
||||
// Write a double to a record and read it to check
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
double_val = 0; // Overwrite with other value
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(double_val, new_double_val);
|
||||
|
||||
// Write a string to the stringin record using a string
|
||||
std::string new_string_val = "NewStringValue";
|
||||
status = string_ca.put(&new_string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
string_val = ""; // Overwrite with other value
|
||||
status = string_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, new_string_val);
|
||||
|
||||
// Write a string to the stringin record using a char array
|
||||
char new_char_arr_val[40] = "NewCharArrValue";
|
||||
status = string_ca.put(new_char_arr_val,
|
||||
(u_long)strlen(new_char_arr_val));
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
char_arr_val[0] = 0; // Overwrite with other value
|
||||
status = string_ca.get(char_arr_val, (u_long)strlen(char_arr_val));
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(std::string(char_arr_val), std::string(new_char_arr_val));
|
||||
|
||||
// Write to an enum with an integer value
|
||||
short_uint_val = 0;
|
||||
status = bo_ca.put(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
short_uint_val = 2; // Overwrite with other value
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 0);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "OFF");
|
||||
|
||||
// Write to an enum with a string
|
||||
std::string new_enum_string = "ON";
|
||||
status = bo_ca.put(&new_enum_string);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 1);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ON");
|
||||
|
||||
// Attempt to write an invalid string. stdout is suppressed for the
|
||||
// duration of this test
|
||||
|
||||
int saved_stdout = dup(STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
new_enum_string = "INVALID";
|
||||
status = bo_ca.put(&new_enum_string);
|
||||
EQUAL(status, FE_ERR_DRIVER); // Attempted to write invalid variant!
|
||||
|
||||
// Assert that nothing has changed in the record
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 1);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ON");
|
||||
|
||||
// Restore stdout
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// With subscription
|
||||
// =====================================================================
|
||||
|
||||
{
|
||||
int int_val = 0;
|
||||
int new_int_val = 13;
|
||||
|
||||
// Initial status of cached
|
||||
ASSERT(int_cas.cached().has_value());
|
||||
status = int_cas.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, 21); // Value which was put in the record by int_ca
|
||||
|
||||
status = int_cas.put(&new_int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
|
||||
// Wait a bit to allow the subscription thread to update
|
||||
double timeout = 5.0;
|
||||
|
||||
auto start = clock::now();
|
||||
while (true) {
|
||||
// Assert that the cached value has changed
|
||||
status = int_cas.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
|
||||
if (int_val == new_int_val)
|
||||
break;
|
||||
|
||||
ca_pend_event(0.1);
|
||||
|
||||
auto now = clock::now();
|
||||
double elapsed =
|
||||
std::chrono::duration<double>(now - start).count();
|
||||
if (elapsed > timeout) {
|
||||
printf("Failed to update int_cas value in %lf seconds.\n",
|
||||
timeout);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nTest was successful\n");
|
||||
// Wait for the IOC to go offline
|
||||
double timeout = 20.0;
|
||||
|
||||
auto start = clock::now();
|
||||
while (true) {
|
||||
if (!int_ca.connected()) {
|
||||
break;
|
||||
}
|
||||
|
||||
ca_pend_event(0.1);
|
||||
|
||||
auto now = clock::now();
|
||||
double elapsed = std::chrono::duration<double>(now - start).count();
|
||||
if (elapsed > timeout) {
|
||||
printf("Could not connect to IOC in %.3lf seconds. The IOC "
|
||||
"likely "
|
||||
"failed to start.\n",
|
||||
timeout);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
// =====================================================================
|
||||
// Tests after IOC has stopped
|
||||
// =====================================================================
|
||||
|
||||
// Drivers are not connected
|
||||
ASSERT((!int_ca.connected()));
|
||||
|
||||
// Cached values are invalid
|
||||
ASSERT((!int_cas.cached().has_value()));
|
||||
|
||||
// Attempting to write or read a value results in an error. stdout
|
||||
// is suppressed for the duration of this test
|
||||
|
||||
int saved_stdout = dup(STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
// Restore stdout
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
}
|
||||
|
||||
printf("Test was successful.\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user