mathis_s 00fa3aa595
CI / build-and-test (push) Successful in 5s
Added asynchronous put
2026-04-29 14:59:50 +02:00
2026-04-29 14:59:50 +02:00
2026-04-29 11:16:41 +02:00
2025-11-25 16:55:18 +01:00
2026-04-23 08:46:42 +02:00
2026-04-28 08:40:55 +02:00
2026-04-29 14:59:50 +02:00
2026-04-23 08:46:42 +02:00

MIDAS - EPICS - Channel Access

A MIDAS bus driver for EPICS channel access: m(idas)EpicsC(hannel)A(ccess).

Written in C++.

Requirements

  • Environment variable MIDASSYS which is a path to the MIDAS shared libraries and headers. The libraries must be in $MIDASSYS/lib, the headers in $MIDASSYS/include.
  • Environment variable EPICSSYS which is a path to the EPICS shared libraries and headers. The libraries must be in $EPICSSYS/lib, the headers in $EPICSSYS/include.

Build

This is a header-only library which exposes the CMake target m_epics_ca. To use the header in your driver, simply include it within your own CMakeLists.txt via target_include_directories. The header can then be included in your own code: #include "m_epics_ca.h".

Example

This example shows how to interact with an EPICS record. It assumes that an IOC is running and reachable and that the following record are part of the IOC database:

record(longin, "EXAMPLE:LONGIN") {
    field(VAL, 42)
}

record(ao, "EXAMPLE:AO") {
    field(VAL, 1.0)
    field(DRVH, 13)
    field(DRVL, 0)
}

This code connects to the VAL field of "EXAMPLE:LONGIN" and to DRVH of EXAMPLE:AO, reads them and changes them. It also shows a type mismatch between record and class value type

#include "cadef.h"
#include "m_epics_ca.h

#define EQUAL(left, right)                                                     \
    do {                                                                       \
        if (!(left == right)) {                                                \
            std::cerr << "\nTest failed at line " << __LINE__ << ": '"         \
                      << (left) << "' (" << #left << ") != '" << (right)       \
                      << "' (" << #right << *")" << std::endl;                 \
            return 1;                                                          \
        }                                                                      \
    } while (0)

int main() {
    /*
    Type safety: Type of mEpicsCa must match that of the record. This is checked
    when connecting to the EPICS channel. If there is a mismatch, an error will
    be logged to MIDAS and any attempt to interact with the record will return
    an error code.
    */

   // int matches longin record
    auto val_ca = mEpicsCa<int>("EXAMPLE:LONGIN");

    // double matches ao record
    auto drvh_ca = mEpicsCa<double>("EXAMPLE:AO.DRVH");

    // Type mismatch
    auto mismatch_ca = mEpicsCa<int>("EXAMPLE:AO.DRVL");

    // Generic status variable which is reused for the error codes throughout
    // the tests.
    int status = CM_SUCCESS;

    // Reading a value from a channel and checking the (MIDAS-native) error code
    int val = 0;
    status = val_ca.get(&val);
    EQUAL(status, CM_SUCCESS);
    EQUAL(val, 42);

    double drvh = 0;
    status = drvh_ca.get(&drvh);
    EQUAL(status, CM_SUCCESS);
    EQUAL(drvh, 13);

    status = mismatch_ca.get(&val);
    EQUAL(status, FE_ERR_DRIVER); // Type mismatch -> No interaction with record possible
    EQUAL(val, 42); // Variable is left unchanged

    // Writing to a channel
    val = 21;
    status = int_ca.put(&val);
    EQUAL(status, CM_SUCCESS);

    // Checking last reported status and severity of the record 
    EQUAL(int_ca.status(), menuAlarmStatNO_ALARM);
    EQUAL(int_ca.severity(), menuAlarmSevrNO_ALARM);

    return 0;
}

For more examples, see test/m_epics_ca_test.cxx.

Features

MIDAS integration

While mEpicsCa is mostly a generic EPICS driver, it provides the following features for seamless usage in MIDAS drivers and frontends:

  • Error messages are logged using cm_msg.
  • Return codes are MIDAS-native (EPICS return codes are mapped to MIDAS)

Type safety

As shown in the example, it is necessary to specify the value type when instantiating mEpicsCa. At compile time, it is checked if the given type is a valid record type. For example, there is no record equivalent for std::deque, so trying to instantiate mEpicsCa<std::deque> will result in a compile error.

When connecting to a channel, the channel type is read out and compared to the value type. If those do not match, then the class will switch into an error state and just return FE_ERR_DRIVER when trying to get or put values. For some record types, there are multiple valid value types. One example is the binary out (bo) record, which is an enum record and therefore can accept either strings or integers. Hence, trying to read a string from the VAL field of a bo record using mEpicsCa<uint16_t> (uint16_t is the bo channel type) will work.

Subscription vs manual polling

For some fields of some records, EPICS offers a "subscription" mechanism where a callback is triggered every time the channel value changes. This can be used to "cache" the current value inside mEpicsCa so that get does not actually communicate with the record but instead returns the value received from the last callback (provided that the channel is not disconnected or in an error state).

Since the subscription mechanism does not work for all channels, it is opt-in with a second boolean argument: mEpicsCa<int>("EXAMPLE:LONGIN", true).

Unfortunately, there is no way to detect whether subscription works or not using the EPICS C-API, so verify that it does!

Handling of record STAT and SEVR fields

When accessing the record with get (or receiving a callback event if subscription is used), the values of the record's STAT and SEVR fields are returned as well. They are then stored in the class and cann be accessed with mEpicsCa::status and mEpicsCa::severity. If the severity is higher than 0 (== menuAlarmSevrNO_ALARM), then the pointer provided to get is left 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<false>. When writing asynchronously, multiple write commands can be send in batches:

auto val_ca = mEpicsCa<double>("EXAMPLE:AO");
auto drvh_ca = mEpicsCa<double>("EXAMPLE:AO.DRVH");
auto drvl_ca = mEpicsCa<double>("EXAMPLE:AO.DRVL");

drvh_ca.put<false>(&10.0);
drvl_ca.put<false>(&0.0);
val_ca.put<false>(&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:

mkdir build
cmake -B build
cmake --build build
ctest --test-dir build

The source code for the test binary is in test/m_epics_ca_test.cxx. This file can also serve as a collection of examples for different record types, like longin, ao, bo, stringin, waveform, ... .

CI

In CI, this test is run in the Docker container lin/midas_base_gitea. To manually run tests in the CI environment, log into the runner machine and use

sudo docker run --rm -it -v "$PWD":/workspace -w /workspace lin/midas_base_gitea bash

Please note that EPICS_HOST_ARCH is linux-x86_64 within that container! Therefore, that environment variable must not be set explicitly within the test.

S
Description
A bus driver for providing a channel access connection to EPICS from MIDAS.
Readme 306 KiB
Languages
C++ 93.8%
CMake 4.5%
Shell 1.5%
Batchfile 0.2%