org.epics.gpclient.datasource.ca.CAChannelHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gpclient-ca Show documentation
Show all versions of gpclient-ca Show documentation
Support for ChannelAccess data source for the EPICS Generic Purpose Client.
The newest version!
/**
* Copyright information and license terms for this software can be
* found in the file LICENSE.TXT included with the distribution.
*/
package org.epics.gpclient.datasource.ca;
import gov.aps.jca.CAException;
import gov.aps.jca.Channel;
import gov.aps.jca.Monitor;
import gov.aps.jca.dbr.DBR;
import gov.aps.jca.dbr.DBRType;
import gov.aps.jca.dbr.DBR_CTRL_Double;
import gov.aps.jca.dbr.DBR_LABELS_Enum;
import gov.aps.jca.dbr.DBR_String;
import gov.aps.jca.dbr.DBR_TIME_Byte;
import gov.aps.jca.dbr.DBR_TIME_Double;
import gov.aps.jca.dbr.DBR_TIME_Enum;
import gov.aps.jca.dbr.DBR_TIME_Float;
import gov.aps.jca.dbr.DBR_TIME_Int;
import gov.aps.jca.dbr.DBR_TIME_Short;
import gov.aps.jca.dbr.DBR_TIME_String;
import gov.aps.jca.event.AccessRightsEvent;
import gov.aps.jca.event.AccessRightsListener;
import gov.aps.jca.event.ConnectionEvent;
import gov.aps.jca.event.ConnectionListener;
import gov.aps.jca.event.MonitorEvent;
import gov.aps.jca.event.MonitorListener;
import org.epics.gpclient.ReadCollector;
import org.epics.gpclient.WriteCollector.WriteRequest;
import org.epics.gpclient.datasource.MultiplexedChannelHandler;
import org.epics.gpclient.datasource.ca.types.CATypeAdapter;
import org.epics.util.array.ListNumber;
import org.epics.util.array.UnsafeUnwrapper;
import org.epics.vtype.VByte;
import org.epics.vtype.VDouble;
import org.epics.vtype.VEnum;
import org.epics.vtype.VFloat;
import org.epics.vtype.VInt;
import org.epics.vtype.VLong;
import org.epics.vtype.VNumberArray;
import org.epics.vtype.VShort;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.regex.Pattern;
import static org.epics.gpclient.datasource.ca.CADataSource.log;
import static org.epics.util.array.UnsafeUnwrapper.wrappedArray;
import static org.epics.util.array.UnsafeUnwrapper.wrappedDoubleArray;
public class CAChannelHandler extends MultiplexedChannelHandler {
private static final int LARGE_ARRAY = 10000000;
private final CADataSource caDataSource;
private volatile Channel channel;
private volatile boolean largeArray = false;
private volatile boolean sentReadOnlyException = false;
private Monitor valueMonitor;
private Monitor metadataMonitor;
public CAChannelHandler(String channelName, CADataSource caDataSource) {
super(channelName);
this.caDataSource = caDataSource;
}
/**
* The datasource this channel refers to.
*
* @return a ca data source
*/
public CADataSource getCADataSource() {
return caDataSource;
}
@Override
protected void connect() {
try {
// Give the listener right away so that no event gets lost
// If it's a large array, connect using lower priority
if (largeArray) {
channel = caDataSource.getContext().createChannel(getChannelName(), connectionListener, Channel.PRIORITY_MIN);
} else {
channel = caDataSource.getContext().createChannel(getChannelName(), connectionListener, (short) (Channel.PRIORITY_MIN + 1));
}
} catch (CAException ex) {
reportExceptionToAllReadersAndWriters(ex);
log.log(Level.WARNING, "JCA Connection failed", ex);
}
}
@Override
protected void disconnect() {
try {
// Close the channel
// Need to guard because the channel may be closed if the
// context was already destroyed
if (channel.getConnectionState() != Channel.ConnectionState.CLOSED) {
channel.removeConnectionListener(connectionListener);
channel.destroy();
}
} catch (CAException ex) {
reportExceptionToAllReadersAndWriters(ex);
log.log(Level.WARNING, "JCA Disconnect fail", ex);
} finally {
channel = null;
processConnection(null);
}
}
@Override
protected boolean isConnected(CAConnectionPayload connPayload) {
return connPayload != null && connPayload.isChannelConnected();
}
@Override
protected boolean isWriteConnected(CAConnectionPayload connPayload) {
return connPayload != null && connPayload.isWriteConnected();
}
@Override
protected CATypeAdapter findTypeAdapter(ReadCollector, ?> cache, CAConnectionPayload connection) {
return caDataSource.getCaTypeSupport().find(cache, connection);
}
private final ConnectionListener connectionListener = new ConnectionListener() {
@Override
public void connectionChanged(ConnectionEvent ev) {
synchronized (CAChannelHandler.this) {
try {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "JCA connectionChanged for channel {0} event {1}",
new Object[] { getChannelName(), ev });
}
// Take the channel from the event so that there is no
// synchronization problem
Channel channel = (Channel) ev.getSource();
// Check whether the channel is large and was opened
// as large. Reconnect if does not match
if (ev.isConnected() && channel.getElementCount() >= LARGE_ARRAY && !largeArray) {
disconnect();
largeArray = true;
connect();
return;
}
processConnection(new CAConnectionPayload(CAChannelHandler.this, channel, getConnectionPayload()));
if (ev.isConnected()) {
// If connected, no write access and exception was not sent, notify writers
if (!channel.getWriteAccess() && !sentReadOnlyException) {
reportExceptionToAllWriters(createReadOnlyException());
sentReadOnlyException = true;
}
// Setup monitors on connection
setup(channel);
} else {
resetMessage();
// Next connection, resend the read only exception if that's the case
sentReadOnlyException = false;
}
channel.addAccessRightsListener(new AccessRightsListener() {
@Override
public void accessRightsChanged(AccessRightsEvent ev) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "JCA accessRightsChanged for channel {0} event {1}",
new Object[] { getChannelName(), ev });
}
processConnection(
new CAConnectionPayload(CAChannelHandler.this, channel, getConnectionPayload()));
if (!sentReadOnlyException && !channel.getWriteAccess()) {
reportExceptionToAllWriters(createReadOnlyException());
sentReadOnlyException = true;
}
}
});
} catch (Exception ex) {
reportExceptionToAllReadersAndWriters(ex);
}
}
}
};;
private void setup(Channel channel) throws CAException {
DBRType metaType = metadataFor(channel);
// If metadata is needed, get it
if (metaType != null) {
DBR dbr = channel.get(metaType, 1);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "JCA metadata getCompleted for channel {0} event {1}",
new Object[] { getChannelName(), dbr });
}
processMessage(new CAMessagePayload(dbr, null));
}
// At each (re)connect, we need to create a new monitor:
// since the type could be changed, we would have a type mismatch
// between the current type and the old type when the monitor was
// created
// XXX: Ideally, we would destroy the monitor on reconnect,
// but currently this does not work with CAJ (you get an
// IllegalStateException because the transport is not there
// anymore). So, for now, we destroy the monitor during the
// the connection callback.
// XXX: Ideally, we should just close (clear) the monitor, but
// this would cause one last event to reach the monitorListener.
// So, we remove the monitorListener right before the clear.
if (valueMonitor != null) {
valueMonitor.removeMonitorListener(monitorListener);
valueMonitor.clear();
valueMonitor = null;
}
valueMonitor = channel.addMonitor(valueTypeFor(channel), countFor(channel), caDataSource.getMonitorMask(),
monitorListener);
// Remove current metadata monitor
if (metadataMonitor != null) {
metadataMonitor.removeMonitorListener(metadataListener);
metadataMonitor.clear();
metadataMonitor = null;
}
// Setup metadata monitor if required
if (caDataSource.isDbePropertySupported() && metaType != null) {
metadataMonitor = channel.addMonitor(metaType, 1, Monitor.PROPERTY, metadataListener);
}
// Flush the entire context (it's the best we can do)
channel.getContext().flushIO();
}
private final MonitorListener monitorListener = new MonitorListener() {
@Override
public void monitorChanged(MonitorEvent event) {
synchronized (CAChannelHandler.this) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "JCA value monitorChanged for channel {0} value {1}, event {2}",
new Object[] { getChannelName(), toStringDBR(event.getDBR()), event });
}
DBR metadata = null;
if (getLastMessagePayload() != null) {
metadata = getLastMessagePayload().getMetadata();
}
processMessage(new CAMessagePayload(metadata, event));
}
}
};
private final MonitorListener metadataListener = new MonitorListener() {
@Override
public void monitorChanged(MonitorEvent ev) {
synchronized (CAChannelHandler.this) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "JCA metadata monitorChanged for channel {0} event {1}",
new Object[] { getChannelName(), ev });
}
// In case the metadata arrives after the monitor
MonitorEvent event = null;
if (getLastMessagePayload() != null) {
event = getLastMessagePayload().getEvent();
}
processMessage(new CAMessagePayload(ev.getDBR(), event));
}
}
};
@Override
protected void write(Object newValue) {
if(newValue instanceof VNumberArray){
newValue = ((VNumberArray) newValue).getData();
}
// If it's a ListNumber, extract the array
if (newValue instanceof ListNumber) {
ListNumber data = (ListNumber) newValue;
UnsafeUnwrapper.Array> wrappedArray = wrappedArray(data);
if (wrappedArray == null) {
wrappedArray = wrappedDoubleArray(data);
}
newValue = wrappedArray.array;
}
try {
if (newValue instanceof Double[]) {
log.warning("You are writing a Double[] to channel " + getChannelName()
+ ": use org.epics.util.array.ListDouble instead");
final Double dbl[] = (Double[]) newValue;
final double val[] = new double[dbl.length];
for (int i = 0; i < val.length; ++i) {
val[i] = dbl[i].doubleValue();
}
newValue = val;
channel.put((double[])newValue);
}
else if (newValue instanceof Integer[]) {
log.warning("You are writing a Integer[] to channel " + getChannelName()
+ ": use org.epics.util.array.ListInt instead");
final Integer ival[] = (Integer[]) newValue;
final int val[] = new int[ival.length];
for (int i = 0; i < val.length; ++i) {
val[i] = ival[i].intValue();
}
newValue = val;
channel.put((int[])newValue);
}
else if(newValue instanceof Double){
channel.put(((Double) newValue).doubleValue());
}
else if(newValue instanceof Integer){
channel.put(((Integer) newValue).intValue());
}
else if(newValue instanceof BigInteger){
channel.put(((BigInteger) newValue).intValue());
}
else if(newValue instanceof Short){
channel.put(((Short) newValue).shortValue());
}
else if(newValue instanceof Float){
channel.put(((Float) newValue).floatValue());
}
else if(newValue instanceof Byte){
channel.put(((Byte) newValue).byteValue());
}
else if (newValue instanceof String) {
if (isLongString()) {
channel.put(toBytes(newValue.toString()));
} else {
if (channel.getFieldType().isBYTE() && channel.getElementCount() > 1) {
log.warning("You are writing the String " + newValue + " to BYTE channel " + getChannelName()
+ ": use {\"longString\":true} for support");
channel.put(toBytes(newValue.toString()));
} else {
channel.put(newValue.toString());
}
}
} else if (newValue instanceof byte[]) {
channel.put((byte[]) newValue);
} else if (newValue instanceof short[]) {
channel.put((short[]) newValue);
} else if (newValue instanceof int[]) {
channel.put((int[]) newValue);
} else if (newValue instanceof float[]) {
channel.put((float[]) newValue);
} else if (newValue instanceof double[]) {
channel.put((double[]) newValue);
} else if (newValue instanceof long[]) {
long[] longs = (long[])newValue;
double[] value = new double[longs.length];
for(int i = 0; i < longs.length; i++){
value[i] = (double)longs[i];
}
channel.put(value);
} else if (newValue instanceof VByte) {
channel.put(((VByte) newValue).getValue());
} else if (newValue instanceof VShort) {
channel.put(((VShort) newValue).getValue());
} else if (newValue instanceof VInt) {
channel.put(((VInt)newValue).getValue());
} else if (newValue instanceof VLong) {
// XXX: Channel access does not support 64 bit integers
// If fits 32 bits, use int. Use double otherwise
long value64 = ((VLong) newValue).getValue();
int value32 = (int) value64;
if (value32 == value64) {
channel.put(value32);
} else {
channel.put((double) value64);
}
} else if (newValue instanceof VFloat) {
channel.put(((VFloat) newValue).getValue());
} else if (newValue instanceof VDouble) {
channel.put(((VDouble) newValue).getValue());
} else if(newValue instanceof VEnum){
channel.put(((VEnum) newValue).getValue());
}
else {
// callback.channelWritten(new Exception(new RuntimeException("Unsupported type
// for CA: " + newValue.getClass())));
return;
}
caDataSource.getContext().flushIO();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected void processWriteRequest(WriteRequest> request) {
try {
write(request.getValue());
request.writeSuccessful();
} catch (Exception ex) {
request.writeFailed(ex);
}
}
protected int countFor(Channel channel) {
if (channel.getElementCount() == 1)
return 1;
if (caDataSource.isVarArraySupported())
return 0;
else
return channel.getElementCount();
}
protected DBRType metadataFor(Channel channel) {
DBRType type = channel.getFieldType();
if (type.isBYTE() || type.isSHORT() || type.isINT() || type.isFLOAT() || type.isDOUBLE())
return DBR_CTRL_Double.TYPE;
if (type.isENUM())
return DBR_LABELS_Enum.TYPE;
return null;
}
static Pattern rtypeStringPattern = Pattern.compile(".+\\.RTYP.*");
protected DBRType valueTypeFor(Channel channel) {
DBRType type = channel.getFieldType();
if (type.isBYTE()) {
return DBR_TIME_Byte.TYPE;
} else if (type.isSHORT()) {
return DBR_TIME_Short.TYPE;
} else if (type.isINT()) {
return DBR_TIME_Int.TYPE;
} else if (type.isFLOAT()) {
return DBR_TIME_Float.TYPE;
} else if (type.isDOUBLE()) {
return DBR_TIME_Double.TYPE;
} else if (type.isENUM()) {
return DBR_TIME_Enum.TYPE;
} else if (type.isSTRING()) {
if (caDataSource.isRtypValueOnly() && rtypeStringPattern.matcher(channel.getName()).matches()) {
return DBR_String.TYPE;
}
return DBR_TIME_String.TYPE;
}
throw new IllegalArgumentException("Unsupported type " + type);
}
private Exception createReadOnlyException() {
return new RuntimeException("'" + getChannelName() + "' is read-only");
}
private String toStringDBR(DBR value) {
StringBuilder builder = new StringBuilder();
if (value == null) {
return "null";
}
if (value.getValue() instanceof double[]) {
builder.append(Arrays.toString((double[]) value.getValue()));
} else if (value.getValue() instanceof short[]) {
builder.append(Arrays.toString((short[]) value.getValue()));
} else if (value.getValue() instanceof String[]) {
builder.append(Arrays.toString((String[]) value.getValue()));
} else {
builder.append(value.getValue().toString());
}
return builder.toString();
}
/**
* Converts a String into byte array.
*
* @param text the string to be converted
* @return byte array, always including '\0' termination
*/
static byte[] toBytes(final String text) {
// TODO: it's unclear what encoding is used and how
// Write string as byte array WITH '\0' TERMINATION!
final byte[] bytes = new byte[text.length() + 1];
System.arraycopy(text.getBytes(), 0, bytes, 0, text.length());
bytes[text.length()] = '\0';
return bytes;
}
/**
* Converts a byte array into a String. It
*
* @param data the array to be converted
* @return the string
*/
static String toString(byte[] data) {
int index = 0;
while (index < data.length && data[index] != '\0') {
index++;
}
return new String(data, 0, index);
}
public boolean isLongString() {
// TODO Auto-generated method stub
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy