diff --git a/README.md b/README.md index 67fbd06..cb1f8e9 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,26 @@ returned as well. They are then stored in the class and cann be accessed with unchanged and `FE_ERR_DRIVER` is returned. Additionally, an error message is logged with `cm_msg`. +### Synchronous and asynchronous mode + +This driver supports asynchronous write operations via `mEpicsCa::put`. +When writing asynchronously, multiple write commands can be send in batches: + +```cpp +auto val_ca = mEpicsCa("EXAMPLE:AO"); +auto drvh_ca = mEpicsCa("EXAMPLE:AO.DRVH"); +auto drvl_ca = mEpicsCa("EXAMPLE:AO.DRVL"); + +drvh_ca.put(&10.0); +drvl_ca.put(&0.0); +val_ca.put(&5.0); + +val_ca.flushIo(); +``` + +All three write commands get send at once with `mEpicsCa::flushIo>`. This helps +to minimize network traffic when writing multiple values at once. + ## Testing This driver comes with its own test suite which can be run via CMake: diff --git a/bus/m_epics_ca.h b/bus/m_epics_ca.h index 8b3e293..f2b1aba 100644 --- a/bus/m_epics_ca.h +++ b/bus/m_epics_ca.h @@ -80,9 +80,10 @@ template class mEpicsCa { * left unchanged. * * When reading a value, the `STAT` and `SEVR` fields of the record of the - * channel are also read. If `SEVR` is not `menuAlarmSevrNO_ALARM`, it is - * assumed that the read value is invalid. The error is then logged and an - * error code is returned. + * channel are also read and used to update `mEpicsCa::status` and + * `mEpicsCa::severity`. If the severity is larger than + * `menuAlarmSevrNO_ALARM`, the error is logged and an error code is + * returned. * * If the subscription mechanism is used, this method just reads * `mEpicsCa::cached`. @@ -116,6 +117,8 @@ template class mEpicsCa { * @brief Like `mEpicsCa::get(V *value)`, but for a C-style string (char * array). * + * @tparam S Whether the method operates in synchronous or asynchronous mode + * (see above). * @param buf Pointer to the first char off the array * @param len Length of the array * @return int MIDAS status error code as defined in midas.h. @@ -126,6 +129,21 @@ template class mEpicsCa { * @brief Try to write `value` to the channel. If successful, * `CM_SUCCESS` is returned and the channel is updated. * + * By default, this method operates in "synchronous" mode: It returns after + * the channel / record field has been updated. However, by setting the + * template parameter S to `false`, it can be switched to asynchronous mode: + * The write request is queued within EPICS and this method returns + * immediately. The channel / field will be updated once `mEpicsCa::flushIo` + * has been called. This is useful if multiple values should be written at + * once, because it minimizes network traffic (i.e. sending one request + * updating 10 values instead of 10 request updating one value). Until + * `mEpicsCa::flushIo` has been called, the channel / field does not contain + * the new value! If multiple instances of `mEpicsCa` are involved, it is + * sufficient to call `mEpicsCa::flushIo` on one of them to update all + * values at once. + * + * @tparam S Whether the method operates in synchronous or asynchronous mode + * (see above). * @tparam V Type of the `value` pointer, which needs to be a valid "alias" * for `T`, see `mEpicsCa::get`. This is checked during instantiation. * @@ -141,17 +159,35 @@ template class mEpicsCa { * @param value Pointer to the new value which gets written to the channel. * @return int MIDAS status error code as defined in midas.h. */ - template int put(V *value); + template int put(V *value); /** * @brief Like `mEpicsCa::put(V *value)`, but for a C-style string (char * array). * + * @tparam S Whether the method operates in synchronous or asynchronous mode + * (see above). * @param buf Pointer to the first char off the array * @param len Length of the array * @return int MIDAS status error code as defined in midas.h. */ - int put(const char *buf, u_long len); + template int put(const char *buf, u_long len); + + /** + * @brief Finishes all asynchronous `put` calls (or times out) + * + * `mEpicsCa::put` can operate in asynchronous mode, meaning that the write + * requests are queued so they can be sent in batch. This method calls + * `ca_flush_io` using the provided `timeout` so the queued requests are + * processed. Using the asynchronous mode can be useful if a lot of + * operations are performed at once, because it minimizes network traffic + * (sending 10 requests at once instead of sending 10 individual requests, + * each containing handshakes etc.). This method should therefore be called + * after first performing asynchronous `mEpicsCa::put` calls. + * + * @return int Error code received from `ca_flush_io` (converted to MIDAS) + */ + int flushIo(); /** * @brief Returns whether the driver is currently connected to an EPICS @@ -226,14 +262,12 @@ template class mEpicsCa { static void eventCallback(struct event_handler_args args); // The following methods hold the actual implementations of `mEpicsCa::get` - // for different types of `T`. int getRaw(char *buf, size_t len); template 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); - template int putRaw(V *value); + template int putRaw(const char *buf, size_t len); + template int putRaw(V *value); /** * @brief Adjusts the status argument according to the values of the diff --git a/bus/m_epics_ca.tpp b/bus/m_epics_ca.tpp index aa6bf9c..1eb62da 100644 --- a/bus/m_epics_ca.tpp +++ b/bus/m_epics_ca.tpp @@ -508,7 +508,9 @@ template int mEpicsCa::getRaw(char *buf, u_long len) { // put // ============================================================================= -template template int mEpicsCa::put(V *value) { +template +template +int mEpicsCa::put(V *value) { // If "this" has the type mEpicsCa (enum channel), then V needs to // be a string or an integer @@ -532,13 +534,15 @@ template template int mEpicsCa::put(V *value) { if constexpr (std::is_same_v) { // Use the c-style string buffer put algorithm - return putRaw(value->c_str(), value->size()); + return putRaw(value->c_str(), value->size()); } else { - return putRaw(value); + return putRaw(value); } } -template int mEpicsCa::put(const char *buf, u_long len) { +template +template +int mEpicsCa::put(const char *buf, u_long len) { // If "this" has the type mEpicsCa (enum channel), then the type // check is omitted @@ -553,14 +557,16 @@ template int mEpicsCa::put(const char *buf, u_long len) { _chanName.c_str()); return FE_ERR_DRIVER; } - return putRaw(buf, len); + return putRaw(buf, len); } -template template int mEpicsCa::putRaw(V *value) { +template +template +int mEpicsCa::putRaw(V *value) { int status = convertAndHandleEcaCode( ca_put(channelType::dbr, this->_pChanID, value), this->_chanName.c_str()); - if (status != CM_SUCCESS) + if (status != CM_SUCCESS || !S) return status; // caget is asynchronous, so the pointer value is only updated when @@ -569,7 +575,9 @@ template template int mEpicsCa::putRaw(V *value) { this->_chanName.c_str()); } -template int mEpicsCa::putRaw(const char *buf, u_long len) { +template +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 @@ -588,7 +596,7 @@ template int mEpicsCa::putRaw(const char *buf, u_long len) { variants += data.strs[i]; if (strcmp(data.strs[i], buf) == 0) - return putRaw(&i); + return putRaw(&i); } // Given string did not match any of the variants -> return an @@ -613,7 +621,7 @@ template int mEpicsCa::putRaw(const char *buf, u_long len) { this->_chanName.c_str()); } - if (status != CM_SUCCESS) + if (status != CM_SUCCESS || !S) return status; // caget is asynchronous, so the pointer value is only updated when @@ -623,6 +631,12 @@ template int mEpicsCa::putRaw(const char *buf, u_long len) { } } +// ============================================================================= + +template int mEpicsCa::flushIo() { + return convertAndHandleEcaCode(ca_flush_io(), this->_chanName.c_str()); +} + template void mEpicsCa::connStateCallback(struct connection_handler_args args) { mEpicsCa *self = static_cast *>(ca_puser(args.chid));