org.epics.gpclient.datasource.pva.PVAChannelHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gpclient-pva Show documentation
Show all versions of gpclient-pva Show documentation
Support for PVAccess data source for the EPICS Generic Purpose Client.
/**
* 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.pva;
import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.epics.gpclient.ReadCollector;
import org.epics.gpclient.WriteCollector;
import org.epics.gpclient.WriteCollector.WriteRequest;
import org.epics.gpclient.datasource.DataSourceTypeAdapter;
import org.epics.pvaccess.client.Channel;
import org.epics.pvaccess.client.Channel.ConnectionState;
import org.epics.pvaccess.client.ChannelProvider;
import org.epics.pvaccess.client.ChannelPut;
import org.epics.pvaccess.client.ChannelPutRequester;
import org.epics.pvaccess.client.ChannelRequester;
import org.epics.pvaccess.client.GetFieldRequester;
import org.epics.pvdata.copy.CreateRequest;
import org.epics.pvdata.factory.ConvertFactory;
import org.epics.pvdata.factory.PVDataFactory;
import org.epics.pvdata.misc.BitSet;
import org.epics.pvdata.monitor.Monitor;
import org.epics.pvdata.monitor.MonitorElement;
import org.epics.pvdata.monitor.MonitorRequester;
import org.epics.pvdata.pv.Convert;
import org.epics.pvdata.pv.Field;
import org.epics.pvdata.pv.MessageType;
import org.epics.pvdata.pv.PVField;
import org.epics.pvdata.pv.PVInt;
import org.epics.pvdata.pv.PVScalar;
import org.epics.pvdata.pv.PVScalarArray;
import org.epics.pvdata.pv.PVStringArray;
import org.epics.pvdata.pv.PVStructure;
import org.epics.pvdata.pv.Status;
import org.epics.pvdata.pv.StringArrayData;
import org.epics.pvdata.pv.Structure;
import org.epics.gpclient.datasource.MultiplexedChannelHandler;
import org.epics.util.array.CollectionNumbers;
import org.epics.util.array.ListNumber;
import org.epics.util.array.UnsafeUnwrapper;
/**
*
* @author msekoranja
*/
class PVAChannelHandler extends
MultiplexedChannelHandler implements
ChannelRequester, GetFieldRequester, ChannelPutRequester, MonitorRequester {
private final ChannelProvider pvaChannelProvider;
private final short priority;
private final PVATypeSupport pvaTypeSupport;
private volatile Channel channel = null;
private final AtomicBoolean monitorCreated = new AtomicBoolean(false);
private final AtomicLong monitorLossCounter = new AtomicLong(0);
//private volatile Monitor monitor = null;
private volatile Field channelType = null;
private volatile boolean isChannelEnumType = false;
private final AtomicBoolean channelPutCreated = new AtomicBoolean(false);
private volatile ChannelPut channelPut = null;
private volatile PVStructure channelPutStructure = null;
private volatile BitSet bitSet = null;
private volatile PVField channelPutValueField = null;
private static final Logger logger = Logger.getLogger(PVAChannelHandler.class.getName());
private static CreateRequest createRequest = CreateRequest.create();
private static PVStructure allPVRequest = createRequest.createRequest("field()");
private static PVStructure standardPutPVRequest = createRequest.createRequest("field(value)");
private static PVStructure enumPutPVRequest = createRequest.createRequest("field(value.index)");
private static final String PVREQUEST_PREFIX = "?request=";
private final PVStructure pvRequest;
private final String extractPVField;
public static PVAChannelHandler create(String channelName,
ChannelProvider channelProvider, short priority,
PVATypeSupport typeSupport) {
int pos = channelName.indexOf(PVREQUEST_PREFIX);
if (pos == -1)
{
return new PVAChannelHandler(channelName, null, channelProvider, priority, typeSupport);
}
else
{
String pvRequestString = channelName.substring(pos+PVREQUEST_PREFIX.length());
channelName = channelName.substring(0, pos);
return new PVAChannelHandler(channelName, pvRequestString, channelProvider, priority, typeSupport);
}
}
public PVAChannelHandler(String channelName, String pvRequestString,
ChannelProvider channelProvider, short priority,
PVATypeSupport typeSupport) {
super(channelName);
this.pvRequest = (pvRequestString != null) ? createRequest.createRequest(pvRequestString) : null;
this.pvaChannelProvider = channelProvider;
this.priority = priority;
this.pvaTypeSupport = typeSupport;
if (pvRequest != null)
{
PVStructure field = pvRequest.getStructureField("field");
extractPVField = getOnlyChildFieldName(field);
}
else
extractPVField = null;
// NOTE: mind "return" above
}
private static final String _OPTIONS = "_options";
private static final String TAKE_PARENT = _OPTIONS;
private static final String getOnlyChildFieldName(PVStructure field)
{
if (field != null)
{
String[] fieldNames = field.getStructure().getFieldNames();
if (fieldNames.length > 0)
{
String name = null;
for (int i = 0; i < fieldNames.length; i++)
{
// ignore options
if (!fieldNames[0].equals(_OPTIONS))
{
if (name == null)
name = fieldNames[0];
else
return null;
}
}
if (name == null)
{
// only "_options" field, that's OK
return TAKE_PARENT;
}
else
{
String childName = getOnlyChildFieldName(field.getStructureField(name));
if (childName == null)
return null;
else if (childName.equals(_OPTIONS))
return name;
else
return name + "." + childName;
}
}
else
{
// no options, no subfield(s)
return TAKE_PARENT;
}
}
else
return null;
}
/**
* @return the channel
*/
public Channel getChannel() {
return channel;
}
/**
* @return the channelType
*/
public Field getChannelType() {
return channelType;
}
public String getExtractFieldName() {
return extractPVField;
}
@Override
public String getRequesterName() {
return this.getClass().getName();
}
@Override
public void message(String message, MessageType messageType) {
logger.log(toLoggerLevel(messageType), message);
}
/**
* Converts MessageType to Java Logging API Level.
* @param messageType pvData message type.
* @return Corresponded Java Logging API Level.
*/
public static Level toLoggerLevel(MessageType messageType) {
switch (messageType) {
case info:
return Level.INFO;
case warning:
return Level.WARNING;
case error:
case fatalError:
return Level.SEVERE;
default:
return Level.INFO;
}
}
private void reportStatus(String message, Status status)
{
if (!status.isSuccess()) {
logger.log(Level.WARNING, message + ": " + status.getMessage());
// for developers
String dump = status.getStackDump();
if (dump != null && !dump.isEmpty())
logger.log(Level.FINER, message + ": " + status.getMessage() + ", cause:\n" + dump);
}
}
@Override
public void connect() {
pvaChannelProvider.createChannel(getChannelName(), this, priority);
}
@Override
public void channelCreated(Status status, Channel channel) {
reportStatus("Failed to create channel instance '" + channel.getChannelName(), status);
this.channel = channel;
}
@Override
public void channelStateChange(Channel channel, ConnectionState connectionState) {
try {
// introspect
if (connectionState == ConnectionState.CONNECTED) {
if (extractPVField == null) {
channel.getField(this, null);
} else {
channel.getField(this, extractPVField);
}
} else {
processConnection(newConnectionPayload());
}
} catch (Exception ex) {
reportExceptionToAllReadersAndWriters(ex);
}
}
/* (non-Javadoc)
* @see org.epics.pvaccess.client.GetFieldRequester#getDone(org.epics.pvdata.pv.Status, org.epics.pvdata.pv.Field)
*/
@Override
public void getDone(Status status, Field field) {
reportStatus("Failed to instrospect channel '" + channel.getChannelName() + "'", status);
if (status.isSuccess())
{
channelType = field;
Field valueField = (channelType instanceof Structure) ? ((Structure)channelType).getField("value") : null;
if (valueField != null && valueField.getID().equals("enum_t"))
{
isChannelEnumType = true;
// TODO could create a monitor just to get value.choices
}
else
isChannelEnumType = false;
}
processConnection(newConnectionPayload());
}
private PVAConnectionPayload newConnectionPayload() {
return new PVAConnectionPayload(channelType, channel != null && channel.isConnected(), extractPVField);
}
@Override
public boolean isConnected(PVAConnectionPayload connectionPayload) {
return connectionPayload.connected;
}
@Override
protected boolean isWriteConnected(PVAConnectionPayload connectionPayload) {
// NOTE: access-rights not yet supported
return connectionPayload.connected;
}
@Override
public synchronized Map getProperties() {
Map properties = new HashMap();
if (channel != null) {
properties.put("Channel name", channel.getChannelName());
if (pvRequest != null)
properties.put("User pvRequest", pvRequest.toString());
properties.put("Connection state", channel.getConnectionState().name());
properties.put("Provider name", channel.getProvider().getProviderName());
if (channel.getConnectionState() == Channel.ConnectionState.CONNECTED) {
properties.put("Remote address", channel.getRemoteAddress());
properties.put("Channel type", channelType.getID());
//properties.put("Read access", channel.getReadAccess());
//properties.put("Write access", channel.getWriteAccess());
}
properties.put("Monitor loss count", monitorLossCounter.get());
}
return properties;
}
@Override
public void disconnect() {
// Close the channel
try {
channel.destroy();
} finally {
channel = null;
//monitor = null;
monitorCreated.set(false);
channelType = null;
channelPut = null;
channelPutValueField = null;
channelPutCreated.set(false);
}
}
private final LinkedList> writeRequests = new LinkedList>();
@Override
protected void processWriteRequest(WriteRequest> request) {
boolean wasEmpty;
synchronized (writeRequests) {
wasEmpty = writeRequests.isEmpty();
writeRequests.add(request);
}
if (!channelPutCreated.getAndSet(true)) {
channel.createChannelPut(this, isChannelEnumType ? enumPutPVRequest : standardPutPVRequest);
} else if (wasEmpty) {
doNextWrite();
}
}
private void doNextWrite() {
WriteRequest writeRequest;
synchronized (writeRequests) {
writeRequest = writeRequests.peek();
}
if (writeRequest != null) {
try {
if (channelPutValueField == null) {
throw new RuntimeException("No 'value' field");
}
fromObject(channelPutValueField, writeRequest.getValue());
channelPut.put(channelPutStructure, bitSet);
} catch (Exception ex) {
writeRequests.poll();
writeRequest.writeFailed(ex);
}
}
}
@Override
public void channelPutConnect(Status status, ChannelPut channelPut, Structure putStructure) {
reportStatus("Failed to create ChannelPut instance", status);
if (status.isSuccess())
{
this.channelPut = channelPut;
if (channelPutStructure == null ||
!channelPutStructure.getStructure().equals(putStructure))
{
channelPutStructure = PVDataFactory.getPVDataCreate().createPVStructure(putStructure);
bitSet = new BitSet(channelPutStructure.getNumberFields());
}
if (isChannelEnumType)
{
// handle inconsistent behavior
this.channelPutValueField = channelPutStructure.getSubField("value");
if (this.channelPutValueField instanceof PVStructure)
this.channelPutValueField = ((PVStructure)channelPutValueField).getSubField("index");
}
else
{
this.channelPutValueField = channelPutStructure.getSubField("value");
}
// set BitSet
bitSet.clear(); // re-connect case
if (this.channelPutValueField != null)
bitSet.set(channelPutValueField.getFieldOffset());
}
doNextWrite();
}
@Override
public void putDone(Status status, ChannelPut channePut) {
reportStatus("Failed to put value", status);
WriteRequest writeRequest;
synchronized (writeRequests)
{
writeRequest = writeRequests.poll();
}
if (writeRequest != null)
{
if (status.isSuccess())
{
writeRequest.writeSuccessful();
}
else
{
writeRequest.writeFailed(new Exception(status.getMessage()));
}
doNextWrite();
}
}
@Override
public void getDone(Status status, ChannelPut channelPut, PVStructure pvStructure, BitSet bitSet) {
// never used, i.e. ChannelPut.get() never called
}
private final static Convert convert = ConvertFactory.getConvert();
// TODO check if non-V types can ever be given as newValue
private final void fromObject(PVField field, Object newValue)
{
// enum support
if (isChannelEnumType)
{
// value.index int field expected
PVInt indexPutField = (PVInt)channelPutValueField;
int index = -1;
if (newValue instanceof Number)
{
index = ((Number)newValue).intValue();
}
else if (newValue instanceof String)
{
String nv = (String)newValue;
PVStructure lastValue = getLastMessagePayload();
if (lastValue == null)
throw new IllegalArgumentException("no monitor on '" + getChannelName() +"' created to get list of valid enum choices");
PVStringArray pvChoices = (PVStringArray)lastValue.getSubField("value.choices");
StringArrayData data = new StringArrayData();
pvChoices.get(0, pvChoices.getLength(), data);
final String[] choices = data.data;
for (int i = 0; i < choices.length; i++)
{
if (nv.equals(choices[i]))
{
index = i;
break;
}
}
// fallback: try to convert string to an number (index)
if (index == -1)
{
try {
int ix = Integer.parseInt(nv);
if (ix >= 0 && ix < choices.length)
index = ix;
} catch (Throwable th) {
// failed to convert, noop
}
}
if (index == -1)
throw new IllegalArgumentException("enumeration '" + nv +"' is not a valid choice");
}
indexPutField.put(index);
return;
}
if (channelPutValueField instanceof PVScalar)
{
if (newValue instanceof Double)
convert.fromDouble((PVScalar)field, ((Double)newValue).doubleValue());
else if (newValue instanceof Integer)
convert.fromInt((PVScalar)field, ((Integer)newValue).intValue());
else if (newValue instanceof String)
convert.fromString((PVScalar)field, (String)newValue);
else if (newValue instanceof Byte)
convert.fromByte((PVScalar)field, ((Byte)newValue).byteValue());
else if (newValue instanceof Short)
convert.fromShort((PVScalar)field, ((Short)newValue).shortValue());
else if (newValue instanceof Long)
convert.fromLong((PVScalar)field, ((Long)newValue).longValue());
else if (newValue instanceof Float)
convert.fromFloat((PVScalar)field, ((Float)newValue).floatValue());
else if (newValue instanceof Boolean)
// TODO no convert.fromBoolean
//convert.fromBoolean((PVScalar)field, ((Boolean)newValue).booleanValue());
convert.fromByte((PVScalar)field, ((Boolean)newValue).booleanValue() ? (byte)1 : (byte)0);
else
throw new RuntimeException("Unsupported write, cannot put '" + newValue.getClass() + "' into scalar '" + channelPutValueField.getField() + "'");
}
else if (channelPutValueField instanceof PVScalarArray)
{
// if it's a ListNumber, extract the array
if (newValue instanceof ListNumber) {
ListNumber data = (ListNumber) newValue;
// FIXME: Optimize!!! You should get the array type of whatever it is and write the exact boundaries
Object wrappedArray = UnsafeUnwrapper.readSafeDoubleArray(data).array;
}
else if (!newValue.getClass().isArray())
{
// create an array
Object newValueArray = Array.newInstance(newValue.getClass(), 1);
Array.set(newValueArray, 0, newValue);
newValue = newValueArray;
}
if (newValue instanceof double[])
convert.fromDoubleArray((PVScalarArray)field, 0, ((double[])newValue).length, (double[])newValue, 0);
else if (newValue instanceof int[])
convert.fromIntArray((PVScalarArray)field, 0, ((int[])newValue).length, (int[])newValue, 0);
else if (newValue instanceof String[])
convert.fromStringArray((PVScalarArray)field, 0, ((String[])newValue).length, (String[])newValue, 0);
// special case from string to array
else if (newValue instanceof String)
{
String str = ((String)newValue).trim();
// remove []
if (str.charAt(0) == '[' && str.charAt(str.length()-1) == ']')
str = str.substring(1, str.length()-1);
// split on commas and whitespaces
String[] splitValues = str.split("[,\\s]+");
convert.fromStringArray((PVScalarArray)field, 0, splitValues.length, splitValues, 0);
}
else if (newValue instanceof byte[])
convert.fromByteArray((PVScalarArray)field, 0, ((byte[])newValue).length, (byte[])newValue, 0);
else if (newValue instanceof short[])
convert.fromShortArray((PVScalarArray)field, 0, ((short[])newValue).length, (short[])newValue, 0);
else if (newValue instanceof long[])
convert.fromLongArray((PVScalarArray)field, 0, ((long[])newValue).length, (long[])newValue, 0);
else if (newValue instanceof float[])
convert.fromFloatArray((PVScalarArray)field, 0, ((float[])newValue).length, (float[])newValue, 0);
else if (newValue instanceof boolean[])
{
boolean[] bArray = (boolean[])newValue;
byte[] byteArray = new byte[bArray.length];
for (int i = 0; i < bArray.length; i++)
byteArray[i] = bArray[i] ? (byte)1 : (byte)0;
convert.fromByteArray((PVScalarArray)field, 0, byteArray.length, byteArray, 0);
}
else
throw new RuntimeException("Unsupported write, cannot put '" + newValue.getClass() + "' into array'" + channelPutValueField.getField() + "'");
}
else
throw new RuntimeException("Unsupported write, cannot put '" + newValue.getClass() + "' into '" + channelPutValueField.getField() + "'");
}
@Override
protected PVATypeAdapter findTypeAdapter(ReadCollector, ?> cache, PVAConnectionPayload connection) {
return pvaTypeSupport.find(cache, connection);
}
@Override
public void addReader(ReadCollector subscription) {
super.addReader(subscription);
if (!monitorCreated.getAndSet(true))
{
// TODO remove this....
for (int i = 0; i < 100 && channel.getConnectionState() == ConnectionState.NEVER_CONNECTED; i++)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) { }
}
// TODO optimize fields
channel.createMonitor(this, pvRequest != null ? pvRequest : allPVRequest);
}
}
/* (non-Javadoc)
* @see org.epics.pvdata.monitor.MonitorRequester#monitorConnect(org.epics.pvdata.pv.Status, org.epics.pvdata.monitor.Monitor, org.epics.pvdata.pv.Structure)
*/
@Override
public void monitorConnect(Status status, Monitor monitor, Structure structure) {
reportStatus("Failed to create monitor", status);
if (status.isSuccess())
{
//this.monitor = monitor;
monitor.start();
}
}
/* (non-Javadoc)
* @see org.epics.pvdata.monitor.MonitorRequester#monitorEvent(org.epics.pvdata.monitor.Monitor)
*/
@Override
public void monitorEvent(Monitor monitor) {
MonitorElement monitorElement;
while ((monitorElement = monitor.poll()) != null)
{
if (monitorElement.getOverrunBitSet().cardinality() > 0)
monitorLossCounter.incrementAndGet();
// TODO combine bitSet, etc.... do we need to copy structure?
processMessage(monitorElement.getPVStructure());
monitor.release(monitorElement);
}
}
/* (non-Javadoc)
* @see org.epics.pvdata.monitor.MonitorRequester#unlisten(org.epics.pvdata.monitor.Monitor)
*/
@Override
public void unlisten(Monitor monitor) {
// TODO Auto-generated method stub
}
@Override
public String toString() {
return "PVAChannelHandler [getChannelName()=" + getChannelName() + "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy