The ca library is a pure Java Channel Access client implementation. ca strives to provide the easiest way of accessing EPICS Channel Access channels from Java.
The notes in this README file are aimed to assist the users of the library. Developers and maintainers of the library are invited to consult the Developer notes.
The version history and latest changes to the library are described in the CHANGELOG.
The results of the functional tests that have been performed on the latest release are available in the INTEGRATION_TESTS document.
An extended usage Example is available.
We warmly invite and appreciate contributions, improvement suggestions and/or feedback on your experiences with this library. Please use the GitHub issue tracker, or feel free to contact us directly.
- Simplicity.
- Use of Java type system.
- Synchronous and asynchronous operations for get, put, connect.
- Efficient handling of parallel operations without the need to use threads.
- Chaining of actions/operations, e.g. set this, then set that, ...
- Easily get additional metadata to value: Timestamp, Alarms, Graphic, Control.
- Supports channel monitoring, including mask capability to select events-of-interest (eg value, log, alarm and/or property changes).
- Supports the following listeners: ConnectionListener, AccessRightsListener.
- Library can be used inside MATLAB to provide access to EPICS control system data. (See the README_MATLAB file for further details.)
Implements CA protocol specified in Version 4.13 of the Channel Access Protocol Specification.
The CA protocol specification is available on the new EPICS website here. Because links have a tendency to go out-of-date a PDF version is archived in the documents area of this project.
The ca library 1.x.y series of releases are compatible with Java 8 and higher.
It is anticipated that the current ca release range (1.3.x) will be the last release to support Java 8 and that future releases will require at least Java 11.
The ca library is available as an Apache Maven package for use in Java projects which use Maven, Gradle, Ivy etc as their build system.
The current distribution home of the library is JCenter. Here you can find the most recent library builds. Earlier versions (up to 1.2.2) were available at Maven Central and for a while longer (2020-06-27) this practice will be continued.
Add the following dependency:
<dependency>
<groupId>org.epics</groupId>
<artifactId>ca</artifactId>
<version>1.3.2</version>
</dependency>
and the following repository:
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
Add the following dependency:
compile 'org.epics:ca:1.3.2'
and the following repository:
repositories {
maven {
url "https://jcenter.bintray.com"
}
The ca library supports the following configuration variables which can be specified, once per JVM instance, by means of environmental variables set in the host OS, or through Java System properties passed on the command-line.
Property Name | Description | Default Value |
---|---|---|
CA_LIBRARY_LOG_LEVEL | The level at which CA library log messages will be sent to the standard output stream. | "INFO" |
CA_REPEATER_LOG_LEVEL | The level at which CA Repeater log messages will be sent to the standard output stream. | "INFO" |
CA_REPEATER_DISABLE | Whether the CA library will start/stop a local CA Repeater instance. | "false" |
CA_REPEATER_OUTPUT_CAPTURE | Whether to capture the output of the CA Repeater's log messages. | "false" |
CA_MONITOR_NOTIFIER_IMPL | The configuration of the CA library monitor notification engine. | see below |
The ca library supports the configuration of the operating parameters of the EPICS Channel Access Protocol by means of the "normal" configuration variables. These can be set once per context instance.
These can be specified by means of environmental variables in the host OS, through Java System properties, or by passing an appropriately configured properties object when creating a context. The following properties are supported:
Property Name | Description | Default Value |
---|---|---|
EPICS_CA_ADDR_LIST | The address list to be used when searching for channels. | empty |
EPICS_CA_AUTO_ADDR_LIST | Automatically build up search address list by introspecting local network interfaces. | "true" |
EPICS_CA_CONN_TMO | The UDP Beacon Message Timeout. | "30s" |
EPICS_CA_REPEATER_PORT | The port to be used when communicating with the local CA Repeater instance. | "5065" |
EPICS_CA_SERVER_PORT | The port to be used when broadcasting channel search requests to the CA servers. | "5064" |
EPICS_CA_MAX_ARRAY_BYTES | The maximum size in bytes of an array/waveform. | unlimited |
Note: In contrast to other Channel Access libraries EPICS_CA_MAX_ARRAY_BYTES is set to unlimited by default. Usually there is no reason to set this property since the memory is dynamically acquired as required.
By default the ca library will discover the available EPICS channels by broadcasting search requests on all locally-enabled network interfaces.
Internally the ca library uses a monitor notification engine to deliver the notifications received from the remote IOCs
to the local library users via the Java Consumer
interface.
The CA_MONITOR_NOTIFIER_IMPL
property can be used to configure the properties of this engine as follows:
Configuration String | Default Buffer Size | Default Number of Consumer Notification Threads | Additional Comment |
---|---|---|---|
"BlockingQueueMultipleWorkerMonitorNotificationServiceImpl {,threads}{,bufsiz}" | Integer.MAX_VALUE | 16 | Threads and buffer size are configurable. |
"BlockingQueueSingleWorkerMonitorNotificationServiceImpl {,threads}{,bufsiz}" | Integer.MAX_VALUE | 1 | Threads parameter is ignored and fixed to 1. |
"StripedExecutorServiceMonitorNotificationServiceImpl {,threads}" | Integer.MAX_VALUE | 10 | Uses Heinz Kabbutz StripedExecutorService. |
Note:
- The configuration of the monitor notification engine was always intended as an experimental feature. The LMAX-Disruptor based notification engines which were available previously have now been retired. The primary reason for this is because they did not scale well to multiple channels.
- The
BlockingQueueMultipleWorkerMonitorNotificationServiceImpl
andStripedExecutorServiceMonitorNotificationServiceImpl
notification engines provide optional configuration parameters allowing the size of the notification buffer and number of consumer notification threads to be configured. - In the future it is likely that the
StripedExecutorServiceMonitorNotificationServiceImpl
will also be retired and that the ca library will offer only a single notification engine based on the blocking queue implementation.
This engine will remain fully configurable to meet the needs of all client applications. - Further details on the requirements for the monitor notification engine and its performance are available in the following MONITOR_INFO.md file.
In order to create channels a Context must first be created. The context acts as a channel container. When the context is closed, all the channels created with that context will also be automatically closed.
This is how to create a context:
Context context = new Context()
Each context supports the EPICS Channel-Access configuration properties defined in the previous section. The default properties may be overridden by variables set in the host operating system environment, or by setting Java system properties in the CA library environment. If finer-grained control is required over individual contexts then an appropriately configured properties object can be supplied in the class constructor.
Properties properties = new Properties();
properties.setProperty( Context.ProtocolConfiguration.EPICS_CA_ADDR_LIST.toString(), "10.10.10.255");
new Context(properties);
The context's resources (= network sockets and allocated memory) are disposed of when the context is no longer
required. This can be achieved by calling the close
method.
context.close();
Alternatively, as the Context implements the Java AutoCloseable
interface, it can also be used inside a
try-with-resources statement to ensure that the context's resources are automatically disposed when the
context goes out of scope.
try ( Context context = new Context)
{
// Code using context
}
To create a channel use:
Channel<Double> channel = context.createChannel("MY_CHANNEL", Double.class);
At channel creation time the channel's type needs to be defined. If you want to have a generic type of channel (i.e. you want to use the type set on the server) use:
Channel<Object> channel = context.createChannel("ARIDI-PCT:CURRENT", Object.class);
When getting a value from the channel you will get the correct/corresponding Java type that maps to the type set on the server.
After creating the channel object the channel needs to be connected. There is a synchronous and asynchronous way to do so. The synchronous/blocking way is to call:
channel.connect()
The asynchronous way is to call:
`connectAsync()`
connectAsync()
will return a CompletableFuture. To check whether the connect was successful call .get()
on it. The synchronous way to connect will block until the channel can be connected. If you want to specify a timeout for a connect use the asynchronous connect as follows:
channel.connectAsync().get(1, java.util.concurrent.TimeUnit.SECONDS);
For connecting multiple channels in parallel use:
Channel<Integer> channel1 = context.createChannel("adc02", Integer.class);
Channel<String> channel2 = context.createChannel("adc03", String.class);
// Wait for all channels to be connected
CompletableFuture.allOf(channel1.connectAsync(), channel2.connectAsync()).get();
A timeout for the multiple connect is realized the same way as with the single connectAsync()
.
After creating a channel you are able to get and put values via the get()
and put(value)
methods.
To put a value in a fire and forget style use putNoWait(value)
. This method will put the value change request on the network but does not wait for any kind of acknowledgement.
// Get value
double value = channel.get();
// Set value
channel.put(10.0);
// Set value (best effort style)
channel.putNoWait(10.0);
Beside the synchronous (i.e. blocking until the operation is done) versions of get()
and put(value)
there are also asynchronous calls. They are named getAsync()
and putAsync(value)
. Both functions immediately return with a CompletableFuture for the operation. The Future can be used to wait at any location in the application and to wait for the completion of the operation and to retrieve the final value of the channel.
Example asynchronous get:
CompletableFuture<Double> future = channel.getAsync();
CompletableFuture<Double> future2 = channel2.getAsync();
// do something different ...
doSomething();
// ... or simply sleep ...
Thread.sleep(1000);
// ... or simply do nothing ...
double value = future.get();
double value2 = future2.get();
Example asynchronous put:
CompletableFuture<Status> future = channel.putAsync(1.0); // this could, for example start some move of a motor ...
CompletableFuture<Status> future2 = channel2.putAsync(5.0);
/ do something different ...
doSomething();
// ... or simply sleep ...
Thread.sleep(1000);
// ... or simply do nothing ...
future.get(); // this will return a status object that can be queried if put was successful
future2.get(); // this will return a status object that can be queried if put was successful
If you want to retrieve more metadata besides the value from the channel you can request this by specifying the type of metadata with the get call. For example if you also want to get the value modification/update time besides the value from the cannel use:
channel.get(Timestamped.class)
Ca supports all metadata types Channel Access provides, namely Timestamped
, Alarm
, Graphic
and Control
.
Metadata Type | Metadata |
---|---|
Timestamped | seconds, nanos |
Alarm | alarmStatus, alarmSeverity |
Graphic | alarmStatus, alarmSeverity, units, precision, upperDisplay, lowerDisplay, upperAlarm, lowerAlarm, upperWarning, lowerWarning |
Control | alarmStatus, alarmSeverity, units, precision, upperDisplay, lowerDisplay, upperAlarm, lowerAlarm, upperWarning, lowerWarning, upperControl, lowerControl |
If you want to monitor a channel you can attach a monitor to it like this:
Monitor<Double> monitor = channel.addValueMonitor(value -> System.out.println(value));
To close a monitor use:
monitor.close()
Again if you would like to obtain metadata from the monitor you can specify the type of metadata that you are interested in.
Monitor<Timestamped<Double>> monitor =
channel.addMonitor(
Timestamped.class,
value -> { if (value != null) System.out.println(new Date(value.getMillis()) + " / " + value.getValue()); }
);
A channel can have Access Right and Connection listeners. These two types of listeners are attached as follows.
Listener connectionListener = channel.addConnectionListener((channel, state) -> System.out.println(channel.getName() + " is connected? " + state));
Listener accessRightListener = channel.addAccessRightListener((channel, rights) -> System.out.println(channel.getName() + " is rights? " + rights));
To remove the listener(s), one can use try-catch-resources
(i.e. Listeners implement AutoCloseable
) or
listener.close()
Note: These listeners can be attached to the channel before connecting.
The channels connection state can be checked as follows:
channel.getConnectionState()
The utility class Channels
provides various convenience functions to perform bulk operations on groups of channels.
To create channels Channels
provides these functions:
// Create and connect channel
Channel<String> channel1 = Channels.create(context, "name", String.class);
// Create and connect channel
Channel<String> channel2 = Channels.create(context, new ChannelDescriptor<String>("name", String.class));
// Create and connect multiple channels at once
List<ChannelDescriptor<?>> descriptors = new ArrayList<>();
descriptors.add(new ChannelDescriptor<String>("name", String.class));
descriptors.add(new ChannelDescriptor<Double>("name_double", Double.class));
List<Channel<?>> channels = Channels.create(context, descriptors);
All of these function will create and connect the specified channels.
For waiting until a channel reaches a specified value Channels
provide following functions:
waitForValue(channel, "value")
// Use custom comparator for checking what is equal ...
Comparator<String> comparator = ...
waitForValue(channel, "value", comparator)
Both functions are also available in an async version. Instead of blocking they return a CompletableFuture.
CompletableFuture<String> future = waitForValue(channel, "value")
// ... do something\
future.get();
// Use custom comparator for checking what is equal ...
Comparator<String> comparator = ...
CompletableFuture<String> future1 = waitForValue(channel, "value", comparator)
// ... do something
future1.get()
Ca provides the annotation, @CaChannel, to annotate channel declarations within a class. While using the Channels
utility class these annotations can be used to easily and efficiently create these channels.
All that needs to done is, to annotate the channel declarations as follows:
class AnnotatedClass {
@CaChannel(name="adc01", type=Double.class)
private Channel<Double> doubleChannel;
@CaChannel(name="adc01", type=String.class)
private Channel<String> stringChannel;
@CaChannel(name={"adc01", "simple"}, type=String.class)
private List<Channel<String>> stringChannels;
public Channel<Double> getDoubleChannel() {
return doubleChannel;
}
public Channel<String> getStringChannel() {
return stringChannel;
}
public List<Channel<String>> getStringChannels() {
return stringChannels;
}
}
Afterwards the channels can be created via Channels
as follows:
AnnotatedClass object = new AnnotatedClass();
Channels.create(context, object);
To close all annotated channels use:
Channels.close(object);
As channel names should not be hardcoded within an annotation, the name of a channel may contain multiple macros (e.g. @CaChannel(name="adc${MACRO1}", type=String.class)
). While creating the channels a map of macros need to be passed to the Channels.create
function.
Map<String,String> macros = new HashMap<>();
macros.put("MACRO1","01");
AnnotatedClass object = new AnnotatedClass();
Channels.create(context, object, macros);
Macro names are case sensitive!
Create simple channel:
try (Context context = new Context())
{
try(Channel<Double> channel = Channels.create(context, "MY_CHANNEL", Double.class)){
System.out.println(channel.get());
}
}
An extended usage example can be found at src/main/java/org/epics/ca/examples/Example.java.
Since ca release 1.3.x the Javadoc for the library is published in the GitHub repository pages area.
Note: the javadoc is currently in a rather rudimentary state. Over time we plan to improve it.
Please see the separate DEVELOPER notes.
If you have questions please contact: '[email protected]' or '[email protected]'.