package org.epics.archiverappliance.engine.pv;
import java.lang.reflect.Constructor;
import java.util.Calendar;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.config.ArchDBRTypes;
import org.epics.archiverappliance.config.ConfigService;
import org.epics.archiverappliance.config.MetaInfo;
import org.epics.archiverappliance.data.DBRTimeEvent;
import org.epics.pvaccess.client.Channel;
import org.epics.pvaccess.client.Channel.ConnectionState;
import org.epics.pvaccess.client.ChannelGet;
import org.epics.pvaccess.client.ChannelGetRequester;
import org.epics.pvaccess.client.ChannelProvider;
import org.epics.pvaccess.client.ChannelRequester;
import org.epics.pvdata.copy.CreateRequest;
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.Field;
import org.epics.pvdata.pv.MessageType;
import org.epics.pvdata.pv.PVStructure;
import org.epics.pvdata.pv.Status;
import org.epics.pvdata.pv.Structure;
public class EPICS_V4_PV implements PV, ChannelGetRequester, ChannelRequester, MonitorRequester {
private static final Logger logger = Logger.getLogger(EPICS_V4_PV.class.getName());
/** Channel name. */
private String name = null;
private ChannelProvider channelProvider;
/**the meta info for this pv*/
private MetaInfo totalMetaInfo = new MetaInfo();
private PVConnectionState state = PVConnectionState.Idle;
private Channel channel;
/**configservice used by this pv*/
private ConfigService configservice = null;
/** PVListeners of this PV */
final private CopyOnWriteArrayList<PVListener> listeners = new CopyOnWriteArrayList<PVListener>();
/**
* isConnected? <code>true</code> if we are currently connected (based on
* the most recent connection callback).
* <p>
* EPICS_V3_PV also runs notifyAll() on <code>this</code> whenever the
* connected flag changes to <code>true</code>.
*/
private volatile boolean connected = false;
private boolean monitorIsDestroyed = false;
/**
* isRunning? <code>true</code> if we want to receive value updates.
*/
private volatile boolean running = false;
/**the DBRTimeEvent constructor for this pv*/
private Constructor<? extends DBRTimeEvent> con;
/**the current DBRTimeEvent*/
private DBRTimeEvent dbrtimeevent;
/**the ArchDBRTypes of this pv*/
private ArchDBRTypes archDBRType = null;
/**
* The JCA command thread that processes actions for this PV.
* This should be inherited from the ArchiveChannel.
*/
private int jcaCommandThreadId;
/**
* If this pv is a meta field, then the metafield parent PV is where the data for this metafield is stored.
**/
private PV parentPVForMetaField = null;
/**Does this pv have one meta field archived?*/
private boolean hasMetaField = false;
/**
* If this pv has many meta fields archived, allarchiveFieldsData includes the meta field names and their values.
* allarchiveFieldsData is updated when meta field changes
* if this pv doesn't have meta field archived, this is always null.
*/
private ConcurrentHashMap<String, String> allarchiveFieldsData = null;
/** Runtime fields that are not archived/stored are stored here */
private ConcurrentHashMap<String, String> runTimeFieldsData = new ConcurrentHashMap<String, String>();
/** if this pv has many meta fields archived,changedarchiveFieldsData includes the changed meta values and the field names*/
private ConcurrentHashMap<String, String> changedarchiveFieldsData = null;
/**we save all meta field once every day and lastTimeStampWhenSavingarchiveFields is when we save all last meta fields*/
private Calendar lastTimeStampWhenSavingarchiveFields = null;
/**this pv is meta field or not*/
private boolean isarchiveFieldsField = false;
/** Store the value for this only in the runtime and not into the stores...*/
private boolean isruntimeFieldField = false;
/**
* the ioc host name where this pv is
*/
private String hostName;
private Monitor subscription = null;
EPICS_V4_PV(final String name, ConfigService configservice, boolean isControlPV, ArchDBRTypes archDBRTypes, int jcaCommandThreadId) {
this(name, configservice, jcaCommandThreadId);
this.archDBRType = archDBRTypes;
if(archDBRTypes != null) {
this.con = configservice.getArchiverTypeSystem().getJCADBRConstructor(this.archDBRType);
}
}
EPICS_V4_PV(final String name, ConfigService configservice, int jcaCommandThreadId) {
this.name = name;
this.configservice = configservice;
this.channelProvider = configservice.getEngineContext().getChannelProvider();
this.jcaCommandThreadId = jcaCommandThreadId;
}
@Override
public String getName() {
return name;
}
@Override
public void addListener(PVListener listener) {
listeners.add(listener);
if (running && isConnected()) {
listener.pvValueUpdate(this);
}
}
@Override
public void removeListener(PVListener listener) {
listeners.remove(listener);
}
/** Notify all listeners. */
private void fireDisconnected() {
for (final PVListener listener : listeners) {
listener.pvDisconnected(this);
}
}
/** Notify all listeners. */
private void fireValueUpdate() {
for (final PVListener listener : listeners) {
listener.pvValueUpdate(this);
}
}
@Override
public void start() throws Exception {
if (running) {
return;
}
running = true;
this.connect();
}
@Override
public void stop() {
running = false;
this.scheduleCommand(new Runnable() {
@Override
public void run() {
unsubscribe();
disconnect();
}
});
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean isConnected() {
return connected;
}
@Override
public String getStateInfo() {
return state.toString();
}
@Override
public DBRTimeEvent getDBRTimeEvent() {
return this.dbrtimeevent;
}
@Override
public ArchDBRTypes getArchDBRTypes() {
return archDBRType;
}
@Override
public void markPVHasMetafields(boolean hasMetaField) {
if (hasMetaField) {
allarchiveFieldsData = new ConcurrentHashMap<String, String>();
changedarchiveFieldsData = new ConcurrentHashMap<String, String>();
}
this.hasMetaField = hasMetaField;
}
@Override
public void setMetaFieldParentPV(PV parentPV, boolean isRuntimeOnly) {
this.parentPVForMetaField = parentPV;
this.isarchiveFieldsField = true;
this.isruntimeFieldField = isRuntimeOnly;
}
@Override
public void updataMetaFieldValue(String pvName, String fieldValue) {
String[] strs = pvName.split("\\.");
String fieldName = strs[strs.length - 1];
if(isruntimeFieldField) {
logger.debug("Not storing value change for runtime field " + fieldName);
runTimeFieldsData.put(fieldName, fieldValue);
} else {
logger.debug("Storing value change for meta field " + fieldName);
allarchiveFieldsData.put(fieldName, fieldValue);
changedarchiveFieldsData.put(fieldName, fieldValue);
}
}
@Override
public HashMap<String, String> getLatestMetadata() {
HashMap<String, String> retVal = new HashMap<String, String>();
// The totalMetaInfo is updated once every 24hours...
MetaInfo metaInfo = this.totalMetaInfo;
if(metaInfo != null) {
metaInfo.addToDict(retVal);
}
// Add the latest value of the fields we are monitoring.
if(allarchiveFieldsData != null) {
retVal.putAll(allarchiveFieldsData);
}
if(runTimeFieldsData != null) {
retVal.putAll(runTimeFieldsData);
}
return retVal;
}
@Override
public void updateTotalMetaInfo() throws IllegalStateException {
// TODO cleanup this interface and implements this.
throw new UnsupportedOperationException();
}
@Override
public String getHostName() {
return hostName;
}
@Override
public String getLowLevelChannelInfo() {
return null;
}
@Override
public String getRequesterName() {
return this.getClass().getName() + "\tchannelName:" + this.name;
}
@Override
public void message(String arg0, MessageType arg1) {
logger.info(arg1);
}
@Override
public void monitorConnect(Status status, Monitor channelMonitor, Structure structure) {
if (monitorIsDestroyed)
return;
synchronized (this) {
if (status.isSuccess()) {
logger.debug("monitorConnect:" + "connect successfully");
String structureID = structure.getID();
logger.debug("Type from structure in monitorConnect is " + structureID);
Field valueField = structure.getField("value");
logger.debug("Value field in monitorConnect is of type " + valueField.getID());
archDBRType = this.determineDBRType(structureID, valueField.getID());
con = configservice.getArchiverTypeSystem().getV4Constructor(archDBRType);
logger.debug("Determined ArchDBRTypes for " + this.name + " as " + archDBRType);
channelMonitor.start();
this.notify();
} else {
logger.debug("monitorConnect:" + "connect failed");
}
}
}
@Override
public void monitorEvent(Monitor monitor) {
MonitorElement monitorElement = null;
try {
if (monitorIsDestroyed)
return;
if (!running) {
return;
}
if (subscription == null) {
return;
}
state = PVConnectionState.GotMonitor;
monitorElement = monitor.poll();
while (monitorElement != null) {
if(logger.isDebugEnabled()) {
logger.debug("Obtained monitor event for pv " + this.name);
}
if(archDBRType == null || con == null) {
logger.error("Have not determined the DBRTYpes yet for " + this.name);
return;
}
PVStructure totalPVStructure = monitorElement.getPVStructure();
try {
dbrtimeevent = con.newInstance(totalPVStructure);
totalMetaInfo.computeRate(dbrtimeevent);
if (isarchiveFieldsField) {
parentPVForMetaField.updataMetaFieldValue(this.name, "" + dbrtimeevent.getSampleValue().toString());
}
if (hasMetaField) {
// //////////handle the field value when it
// changes//////////////
if (changedarchiveFieldsData.size() > 0) {
logger.debug("Adding changed field for pv " + name + " with " + changedarchiveFieldsData.size());
HashMap<String, String> tempHashMap = new HashMap<String, String>();
tempHashMap.putAll(changedarchiveFieldsData);
// dbrtimeevent.s
dbrtimeevent.setFieldValues(tempHashMap, true);
changedarchiveFieldsData.clear();
}
// //////////////////////////
// ////////////save all the fields once every day//////////////
if (this.lastTimeStampWhenSavingarchiveFields == null) {
if (allarchiveFieldsData.size() != 0) {
saveMetaDataOnceEveryDay();
}
} else {
Calendar currentCalendar = Calendar.getInstance();
currentCalendar.add(Calendar.DAY_OF_MONTH, -1);
if (currentCalendar
.after(lastTimeStampWhenSavingarchiveFields)) {
// Calendar currentCalendar2=Calendar.getInstance();
saveMetaDataOnceEveryDay();
}
}
// //////////////////////////////
}
fireValueUpdate();
} catch (Exception e) {
logger.error("exception in monitor changed function when converting DBR to dbrtimeevent", e);
} finally {
monitor.release(monitorElement);
}
if (!connected)
connected = true;
monitorElement = monitor.poll();
}
} catch (final Exception ex) {
logger.error("exception in monitor changed ", ex);
}
}
@Override
public void unlisten(Monitor monitor) {
monitor.stop();
monitor.destroy();
}
@Override
public void channelCreated(Status status, Channel createdChannel) {
logger.info("Channel has been created" + createdChannel.getChannelName() + " Status: " + status.toString());
}
@Override
public void channelStateChange(final Channel channelChangingState, final org.epics.pvaccess.client.Channel.ConnectionState connectionStatus) {
this.scheduleCommand(new Runnable() {
@Override
public void run() {
if (connectionStatus == ConnectionState.CONNECTED) {
logger.info("channelStateChange:connected " + channelChangingState.getChannelName());
handleConnected(channelChangingState);
} else if (connectionStatus == ConnectionState.DISCONNECTED) {
logger.info("channelStateChange:disconnected " + channelChangingState.getChannelName());
state = PVConnectionState.Disconnected;
connected = false;
unsubscribe();
fireDisconnected();
}
}
});
}
@Override
public void channelGetConnect(final Status status, final ChannelGet channelGet, Structure arg2) {
this.scheduleCommand(new Runnable() {
@Override
public void run() {
if (status.isSuccess()) {
channelGet.get();
} else {
System.err.println(status.getMessage());
}
}
});
}
@Override
public void getDone(final Status status, ChannelGet arg1, final PVStructure pvStructure, BitSet arg3) {
this.scheduleCommand(new Runnable() {
@Override
public void run() {
if (status.isSuccess()) {
logger.info("Obtained meta info for PV " + EPICS_V4_PV.this.name);
totalMetaInfo.applyV4BasicInfo(EPICS_V4_PV.this.name, pvStructure, EPICS_V4_PV.this.configservice);
}
}
});
}
private void scheduleCommand(final Runnable command) {
configservice.getEngineContext().getJCACommandThread(jcaCommandThreadId).addCommand(command);
}
private void connect() {
logger.info("Connecting to PV " + this.name);
this.scheduleCommand(new Runnable() {
@Override
public void run() {
try {
state = PVConnectionState.Connecting;
synchronized (this) {
if (channel == null) {
channel = channelProvider.createChannel(name, EPICS_V4_PV.this, ChannelProvider.PRIORITY_DEFAULT);
}
if (channel == null)
return;
if (channel.getConnectionState() == ConnectionState.CONNECTED) {
handleConnected(channel);
}
}
} catch (Exception e) {
logger.error("exception when connecting pv", e);
}
}
});
}
/**
* PV is connected. Get meta info, or subscribe right away.
*/
private void handleConnected(final Channel channel) {
if (state == PVConnectionState.Connected)
return;
state = PVConnectionState.Connected;
for (final PVListener listener : listeners) {
listener.pvConnected(this);
}
if (!running) {
connected = true;
synchronized (this) {
this.notifyAll();
}
return;
}
PVStructure pvRequest = CreateRequest.create().createRequest("field()");
channel.createChannelGet(this, pvRequest);
subscribe();
}
private void disconnect() {
Channel channel_copy;
synchronized (this) {
if (channel == null)
return;
channel_copy = channel;
connected = false;
channel = null;
}
try {
channel_copy.destroy();
} catch (final Throwable e) {
logger.error("exception when disconnecting pv", e);
}
fireDisconnected();
}
/** Subscribe for value updates. */
private void subscribe() {
synchronized (this) {
// Prevent multiple subscriptions.
if (subscription != null) {
return;
}
// Late callback, channel already closed?
if (channel == null) {
return;
}
try {
state = PVConnectionState.Subscribing;
totalMetaInfo.setStartTime(System.currentTimeMillis());
PVStructure pvRequest = CreateRequest.create().createRequest("field()");
subscription = channel.createMonitor(this, pvRequest);
} catch (final Exception ex) {
logger.error("exception when subscribing pv", ex);
}
}
}
/** Unsubscribe from value updates. */
private void unsubscribe() {
Monitor sub_copy;
synchronized (this) {
sub_copy = subscription;
subscription = null;
archDBRType = null;
con = null;
}
if (sub_copy == null) {
return;
}
try {
sub_copy.stop();
sub_copy.destroy();
} catch (final Exception ex) {
logger.error("exception when unsubscribing pv", ex);
}
}
private void saveMetaDataOnceEveryDay() {
HashMap<String, String> tempHashMap = new HashMap<String, String>();
tempHashMap.putAll(allarchiveFieldsData);
if(runTimeFieldsData != null && !runTimeFieldsData.isEmpty()) {
// This should store fields like the description at least once every day.
tempHashMap.putAll(runTimeFieldsData);
}
if(this.totalMetaInfo != null) {
if(this.totalMetaInfo.getUnit() != null) {
tempHashMap.put("EGU", this.totalMetaInfo.getUnit());
}
if(this.totalMetaInfo.getPrecision() != 0) {
tempHashMap.put("PREC", Integer.toString(this.totalMetaInfo.getPrecision()));
}
}
// dbrtimeevent.s
dbrtimeevent.setFieldValues(tempHashMap, false);
lastTimeStampWhenSavingarchiveFields = Calendar.getInstance();
}
private ArchDBRTypes determineDBRType(String structureID, String valueTypeId) {
if(structureID == null || valueTypeId == null) {
return ArchDBRTypes.DBR_V4_GENERIC_BYTES;
}
if(structureID.contains("epics:nt/NTScalarArray") || structureID.contains("structure")) {
switch(valueTypeId) {
case "string[]":
return ArchDBRTypes.DBR_WAVEFORM_STRING;
case "double[]":
return ArchDBRTypes.DBR_WAVEFORM_DOUBLE;
case "int[]":
return ArchDBRTypes.DBR_WAVEFORM_INT;
case "byte[]":
return ArchDBRTypes.DBR_WAVEFORM_BYTE;
case "float[]":
return ArchDBRTypes.DBR_WAVEFORM_FLOAT;
case "short[]":
return ArchDBRTypes.DBR_WAVEFORM_SHORT;
case "enum_t":
return ArchDBRTypes.DBR_WAVEFORM_ENUM;
case "structure":
return ArchDBRTypes.DBR_V4_GENERIC_BYTES;
default:
logger.error("Cannot determine arch dbrtypes for " + structureID + " and " + valueTypeId + " for PV " + this.name);
return ArchDBRTypes.DBR_V4_GENERIC_BYTES;
}
} else {
switch(valueTypeId) {
case "string":
return ArchDBRTypes.DBR_SCALAR_STRING;
case "double":
return ArchDBRTypes.DBR_SCALAR_DOUBLE;
case "int":
return ArchDBRTypes.DBR_SCALAR_INT;
case "byte":
return ArchDBRTypes.DBR_SCALAR_BYTE;
case "float":
return ArchDBRTypes.DBR_SCALAR_FLOAT;
case "short":
return ArchDBRTypes.DBR_SCALAR_SHORT;
case "enum_t":
return ArchDBRTypes.DBR_SCALAR_ENUM;
case "structure":
return ArchDBRTypes.DBR_V4_GENERIC_BYTES;
default:
logger.error("Cannot determine arch dbrtypes for " + structureID + " and " + valueTypeId + " for PV " + this.name);
return ArchDBRTypes.DBR_V4_GENERIC_BYTES;
}
}
}
/***
*get the meta info for this pv
* @return MetaInfo
*/
@Override
public MetaInfo getTotalMetaInfo() {
return totalMetaInfo;
}
}