Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "PVFactory" that supports Tango's protocol #2426

Merged
merged 6 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions core/pv/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,16 @@ Examples ::

sys://timeOffset(12 hours)
sys://timeOffset(1hour, time, 1)


Tango
------
Tango is different from EPICS, the smallest unit is Device, which includes the commands, states, and attributes.
The command and the attribute has been implemented, add prefix to PV Name in editing interface to distinguish, and the command usually has a return value, so need to use *Text Entry* or a combination of *Action button* and *Text Update* components to achieve it.
Currently, all types of scalars are supported, but SPECTRUM and IMAGE are not yet supported.

Examples ::

tga://device/attribute
tgc://device/command

6 changes: 6 additions & 0 deletions core/pv/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,11 @@
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.tango-controls</groupId>
<artifactId>JTango</artifactId>
<version>9.7.0</version>
<type>pom</type>
</dependency>
</dependencies>
</project>
162 changes: 162 additions & 0 deletions core/pv/src/main/java/org/phoebus/pv/tga/TangoAttrContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package org.phoebus.pv.tga;

import fr.esrf.Tango.DevFailed;
import fr.esrf.Tango.EventProperties;
import fr.esrf.TangoApi.AttributeInfoEx;
import fr.esrf.TangoApi.AttributeProxy;
import fr.esrf.TangoApi.DeviceAttribute;
import org.epics.vtype.*;
import org.phoebus.pv.PV;
import org.tango.attribute.AttributeTangoType;
import org.tango.server.events.EventType;

import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;

;

public class TangoAttrContext {
private static TangoAttrContext instance;
private final ConcurrentHashMap<String, AttributeProxy> attributeProxys;
private final ConcurrentHashMap<String, Integer> events;
private final ConcurrentHashMap<String, AttributeTangoType> types;



private TangoAttrContext() {
events = new ConcurrentHashMap<>();
attributeProxys = new ConcurrentHashMap<>();
types = new ConcurrentHashMap<>();
}

public static synchronized TangoAttrContext getInstance() throws Exception {
if (instance == null)
instance = new TangoAttrContext();
return instance;
}

public void subscribeAttributeEvent(String deviceName, String attributeName, String baseName, TangoAttr_PV pv) throws DevFailed {
String name = deviceName +"/" + attributeName;
AttributeProxy attributeProxy;
if (attributeProxys.get(baseName) == null) {
attributeProxy = new AttributeProxy(name);
subscribeAttributeEvent(baseName, attributeProxy, pv);
attributeProxys.put(baseName,attributeProxy);
}else {
//nothing to do
}

}

private void subscribeAttributeEvent(String baseName, AttributeProxy attributeProxy, TangoAttr_PV pv) throws DevFailed {
AttributeInfoEx attribute_info_ex;
try {
attribute_info_ex = attributeProxy.get_info_ex();
} catch (DevFailed e) {
throw new RuntimeException(e);
}

//obtain the type of attribute's value.
AttributeTangoType type = AttributeTangoType.getTypeFromTango(attribute_info_ex.data_type);

types.putIfAbsent(baseName, type);


//obtain the tango event type.
EventType eventType = EventType.CHANGE_EVENT;
EventProperties tangoObj = attribute_info_ex.events.getTangoObj();
if (tangoObj.ch_event.abs_change.equals("Not specified") && tangoObj.ch_event.rel_change.equals("Not specified"))
eventType = EventType.PERIODIC_EVENT;

//subscribe the tango event.
int event_id;
try {
event_id = attributeProxy.subscribe_event(eventType.getValue(), pv.new TangoCallBack(type), new String[]{});
} catch (DevFailed e) {
throw new RuntimeException(e);
}
events.put(baseName, event_id);

}

public void unSubscribeAttributeEvent(String baseName) throws Exception {
if (!attributeProxys.containsKey(baseName)){
PV.logger.log(Level.WARNING, "Could not unsubscribe Tango attribute \"" + baseName
+ "\" due to no Attribute Proxy.");
throw new Exception("Tango attribute unsubscribe failed: no Attribute proxy connection.");
}

AttributeProxy attributeProxy = attributeProxys.get(baseName);
Integer event_id = events.get(baseName);
if (event_id == null){
PV.logger.log(Level.WARNING, "Could not unsubscribe Tango attribute \"" + baseName
+ "\" due to no internal record of attribute");
throw new Exception("Tango attribute unsubscribe failed: no attribute record.");
}
attributeProxy.getDeviceProxy().unsubscribe_event(event_id);
attributeProxys.remove(baseName);
events.remove(baseName);
types.remove(baseName);
}


public void writeAttribute(String baseName, String attributeName, Object new_value) throws Exception {
AttributeProxy attributeProxy = attributeProxys.get(baseName);
AttributeTangoType type = types.get(baseName);
if (type == null){
PV.logger.log(Level.WARNING, "Could not find type of attribute :" + baseName);
throw new Exception("Tango attribute write failed: attribute type not found.");
}
VType vType;
String value;
switch (type){
case DEVBOOLEAN:
vType = TangoTypeUtil.convert(new_value, VBoolean.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, Boolean.parseBoolean(value)));
break;
case DEVLONG64:
case DEVULONG64:
vType = TangoTypeUtil.convert(new_value, VLong.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, Long.parseLong(value)));
break;
case DEVSHORT:
case DEVUSHORT:
vType = TangoTypeUtil.convert(new_value, VShort.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, Short.parseShort(value)));
break;
case DEVLONG:
case DEVULONG:
vType = TangoTypeUtil.convert(new_value, VInt.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, Integer.parseInt(value)));
break;
case DEVFLOAT:
vType = TangoTypeUtil.convert(new_value, VFloat.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, Float.parseFloat(value)));
break;
case DEVDOUBLE:
vType = TangoTypeUtil.convert(new_value, VDouble.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, Double.parseDouble(value)));
break;
case DEVSTRING:
vType = TangoTypeUtil.convert(new_value, VString.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, value));
break;
case DEVUCHAR:
vType = TangoTypeUtil.convert(new_value, VByte.class);
value = TangoTypeUtil.ToString(vType);
attributeProxy.write(new DeviceAttribute(attributeName, Byte.parseByte(value)));
break;
default:
throw new IllegalArgumentException("Value " + new_value + " cannot be converted.");
}

}

}
128 changes: 128 additions & 0 deletions core/pv/src/main/java/org/phoebus/pv/tga/TangoAttr_PV.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package org.phoebus.pv.tga;

import fr.esrf.Tango.DevFailed;
import fr.esrf.TangoApi.CallBack;
import fr.esrf.TangoApi.DeviceAttribute;
import fr.esrf.TangoApi.events.EventData;
import org.epics.vtype.*;
import org.phoebus.pv.PV;
import org.tango.attribute.AttributeTangoType;

import java.time.Instant;
import java.util.logging.Level;

public class TangoAttr_PV extends PV {

private final String baseName;
private String device;
private String attribute;


public TangoAttr_PV(String name, String baseName) throws Exception {
super(name);
this.baseName = baseName;
parseRawName(baseName);
TangoAttrContext.getInstance().subscribeAttributeEvent(device, attribute, baseName,this);
}


private void parseRawName(final String name) throws Exception {
int pos = name.lastIndexOf('/');
if (pos <= 0)
throw new Exception("Invalid input:" + name);
//Locate device name
device = name.substring(0,pos);
//Locate tango attribute
attribute = name.substring(pos+1);
}


@Override
protected void close()
{
try
{
TangoAttrContext.getInstance().unSubscribeAttributeEvent(baseName);
}
catch (Exception ex)
{
logger.log(Level.WARNING, "Failed to unsubscribe Tango Attribute from base name " + baseName);
ex.printStackTrace();
}
}

@Override
public void write(final Object new_value) throws Exception{
if (new_value == null)
throw new Exception(getName() + " got null");
TangoAttrContext.getInstance().writeAttribute(baseName, attribute, new_value);
}


class TangoCallBack extends CallBack {
private final AttributeTangoType type;

public TangoCallBack(AttributeTangoType type) {
this.type = type;
}

@Override
public void push_event(EventData evt) {
try {
VType value;
DeviceAttribute attr_value = evt.attr_value;
Time time = Time.of(Instant.ofEpochMilli(attr_value.getTime()));
switch (type){
case DEVBOOLEAN:
value = VBoolean.of(attr_value.extractBoolean(), Alarm.none(), time);
notifyListenersOfValue(value);
break;
case DEVLONG64:
value = VLong.of(attr_value.extractLong64(), Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVULONG64:
value = VLong.of(attr_value.extractULong64(), Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVSHORT:
value = VShort.of(attr_value.extractShort(), Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVUSHORT:
value = VInt.of(attr_value.extractUShort(),Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVLONG:
value = VInt.of(attr_value.extractLong(), Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVULONG:
value = VLong.of(attr_value.extractULong(), Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVFLOAT:
value = VFloat.of(attr_value.extractFloat(), Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVDOUBLE:
value = VDouble.of(attr_value.extractDouble(), Alarm.none(), time, Display.none());
notifyListenersOfValue(value);
break;
case DEVSTRING:
value = VString.of(attr_value.extractString(), Alarm.none(), time);
notifyListenersOfValue(value);
break;
case DEVUCHAR:
value = VShort.of(attr_value.extractUChar(), Alarm.none(), time,Display.none());
notifyListenersOfValue(value);
break;
default:
throw new IllegalArgumentException("Value " + evt.attr_value + " cannot be converted.");
}
}catch (DevFailed e){
throw new RuntimeException(e);
}
}
}
}
23 changes: 23 additions & 0 deletions core/pv/src/main/java/org/phoebus/pv/tga/TangoAttr_PVFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.phoebus.pv.tga;

import org.phoebus.pv.PV;
import org.phoebus.pv.PVFactory;


public class TangoAttr_PVFactory implements PVFactory {

/** PV type implemented by this factory */
final public static String TYPE = "tga";

@Override
public String getType() {
return TYPE;
}

@Override
public PV createPV(String name, String base_name) throws Exception {
return new TangoAttr_PV(name, base_name);
}


}
Loading