Difference between revisions of "V4 CA Client User Interface"

From EPICSWIKI
(Split DataListener off)
(TIPC)
 
(23 intermediate revisions by 3 users not shown)
Line 14: Line 14:
Is there anything we can learn from other communication libraries?
Is there anything we can learn from other communication libraries?
* A brief look at [[ZeroC ICE]]
* A brief look at [[ZeroC ICE]]
* TIPC, the Transparent Inter Process Communication protocol (TIPC) started out as a communications layer for building clusters, I think by Ericson, and is now becoming more open and also supported by  the latest WindRiver vxWorks releases.<br>The Buzz:
** allows socket-based applications to communicate easily and efficient
** based on publicly available source code
** A location-transparent addressing scheme
** Efficient, rapid, and reliable interprocess communication within a node and between nodes, using either connection-oriented or connectionless modes of operation
** Rapid notification of changes in topology and the ability to adjust quickly to these changes
== Constraints ==
* Initial implementation will be in C++
* Should very easily translate into Java, just like e.g. some XML APIs use the exact same classes and methods in C++ and Java. Ideally, Java and C++ version will be available from the start.
* Ideally, the C++ headers would be simple enough to be parsed by SWIG and hence perl/tcl/python/... glue code could be auto-generated via SWIG
* API is of course specific to EPICS CA, but ideally it'll also be usable as an interface to other protocols, so that EPICS Office can use an API that is only a slight extension of the CA client user API.


== Skeleton API ==
== Skeleton API ==
Line 19: Line 31:
that a CA client API should provide.
that a CA client API should provide.


=== Directory Server ===
=== Directory ===
* setServer(string URL-type-server-address)<BR>Optional; otherwise some site-specific default name server is used. That 'URL' might contain a user & password, which decides if this name server connection is read-only (for OPI clients) or if writes are allowed (for IOCs that add PVs to the name server).
The directory is used to discover available channels and map them to the CA Server address and port.
* Directory(string URL-type-server-address = "")<BR>Constructor; uses site-specific default server or specific one. The 'URL' might contain a user & password, which decides if this name server connection is read-only (for OPI clients) or if writes are allowed (for IOCs that add PVs to the name server).
* getChannelInfo(string PV_or_pattern)<BR>Returns list of
* getChannelInfo(string PV_or_pattern)<BR>Returns list of
** CA server - IP & port of server that has the PV
** CA server - IP & port of server that has the PV
Line 27: Line 40:
* deleteChannelInfo(string PV, CASInfo addr_and_port, CASType primary_or_backup_or...)<BR>Used by CA servers to remove their PV from directory
* deleteChannelInfo(string PV, CASInfo addr_and_port, CASType primary_or_backup_or...)<BR>Used by CA servers to remove their PV from directory


=== Data Access ===
There is a default directory, available via something like "EPICSDefaults::getDirectory()" and "...setDirectory()".
All the data is accessed via DataAccess, which handles the introspection
The implementation could use
of arbitrary data types. It should provide the following convenience
* LDAP
routines in addition to whatever more efficient methods it might have
* Broadcast (as before)
(which use hash IDs instead of strings to access properties,
* your custom implementation<br>as long as it uses the above interface and registers itself as the default.
use magic copy routines ins:
 
* list<string> getProperties()<BR>Get a list of all the properties
Note that Ben argues to hide the directory lookup from the CA client us
* type_info getType(string property)
er.
* bool hasWriteAccess(string property)
See separate  [[V4 Name Server]] wiki for the issue of "record" and "PV" information.
* string getAsString(string property)
 
=== Data ===
The available ''data'' constists of property names, types and values, for example:
    double    value; // The "value" property of our data
    string    units; // The "units" property....
    time_stamp time;
 
The data might contain arrays and structures.
The user might not want all the properties, and the user might also want to get properties in non-native types (strings for convenience, float instead of double to preserve space, ...).
The client has to provide storage, and might allocate this:
    float  value;
    string units;
 
The user interface must provide the following:
* getAllProperties()<BR>Get a list of all the property names and their data type so that the client can make an informed decision for allocating its storage of the data. There might be helper routines to convert between human-usable strings and more efficient numeric identifiers.
* bool hasWriteAccess(string property)<BR>Could also be part of the info return by previous method.
 
This functionality is available without getting any actual data.
To retrieve values, the user asks the client library to place the data into
the client-provided storage.
The ''traverse'' of the "dataAccess" proposal handles this by asking
the user to implement a property catalog that ''reveals'' each storage element.
The end effect is not too different from having the user invoke methods
like these:
* getAsString(string property, string *my_storage)
* getAsDouble(string property, double *my_storage)
* ...many more, one for each available data type.


=== Channel, General Methods ===
=== Channel ===
* Channel(string name)<br>Constructor. Channel has to have a name.
* Channel(string name, Directory dir = default)<br>Constructor. Channel has to have a name. Uses the 'best' channel from the default directory or from a given directory.
* Channel(string name, CASInfo)<br>... in case you want a specific server after querying the directory yourself or not using a directory at all.
* getName()<br>Returns the name of the channel.
* getName()<br>Returns the name of the channel.
* addListener(Listener l), removeListener(Listener l)<br>Register for notifications, see below.
* getProperties()<br>A read or subscription will return a dataAccess interface to the retrieved data. That might exclude properties that this client cannot access. It will include the data, which might be huge. This separate call will only get property information, no data, for all properties, including read/write access information. The result could be in the form of dataAccess, except all access to actual data yields empty results, only the property-info related part of dataAccess is functional.
Note that Ben suggests that the CA client library should select the CA server automatically by querying a server or via broadcasts; it should also pick amongst competing PVs of the same name without user interaction.


=== Channel, Connection Related Methods ===
Note also that in order to read or write, one needs to know what properties to access.
One idea is that - in contrast to the V3 API - the user shouldn't have to connect at all.
Does the channel magically provide a list of all properties?
The first 'get' or 'put' will try to connect.
Is there a "get all properties" request that reads them on-demand
A disconnect is a special type of data notification, not a separate
from the CA server?
connection callback.
Or does the directory hold the list of available properties and their types?
 
=== Channel: Listener ===
The Channel::Listener interface is invoked in response to the many asynchronous methods of the Channel.
* writeComplete(Channel ch)
** Channel that send this notification
* newData(Channel ch, DataAccess data, Event why)
** DataAccess interface to the data
** Reason for the update: Value deadband exceeded, minimum period expired, event 'blue beam', ...
* accessRightChange(Channel ch)
 
Alternatively, these could be separate interfaces for a writeListener, dataListener, stateListener.
 
=== Channel: State ===
In contrast to the V3 API, the user doesn't 'connect'.
The first data notification implies that we're connected.


* setDirectory(string directory_URL)<br>Use specific directory server, otherwise the default one is used.
* setServer(CA server info)<BR>Attempts to connect & maintain the connection with specific server. Otherwise, the directory is queried.
* bool isConnected()<BR> .. for those who want to poll
* bool isConnected()<BR> .. for those who want to poll
* xxx getServerInfo(), xxx getType()<BR>  .. only valid when isConnected().
* xxx getServerInfo(), xxx getType()<BR>  .. only valid when isConnected().
* setUser(), ...<BR>Sets/changes the 'user' that determines the access rights, allowing OPI tools to adjust this per-channel at runtime.
* setUser(), ...<BR>Sets/changes the 'user' that determines the access rights, allowing OPI tools to adjust this per-channel at runtime. To avoid security issues, setUser will probably send a public key and not a user name and password.
 
=== Channel: Writing ===
* write(new value)<BR>Sends the value to the server.
* createWriteRequest(new value, receipt {delivery, completion, response})<BR>Sends the value to the server, invokes Listener when CA server has received the value (delivery) or  all the processing triggered by the new value has completed (completion). In addition, some records might support command/response, in which case one can write a command and wait for a response callback which looks just like the result of a read request.
* scheduleWriteRequest(new value, event)<BR>Sends the value to the server, which will perform the write when the given event fires. Invokes Listener when CA server has performed the write.
 
=== Channel: Reading ===
* createSubscription(list<string> properties, Event e)<BR>Will invoke Listener once data arrives.
* cancelSubscription(....)<BR>Use properties & event or an ID returned from createSubscription?


=== Channel, 'Put' Related Methods ===
Event is a class that allows the user to select what triggers an update:
* put(new value)<BR>Sends data to the server.<BR>Returns error if currently disconnected or other local problem, no other feedback wether the value actually reached the server.
* useDefaultValueDeadband()<BR>Default if nothing else is called.
* putNotify(new value), putCompletionNotify(new value)<BR>Sends the value to the server, invokes putNotification when receiving record has received the value respectively all the processing triggered by the new value has completed.
* setValueDeadband(double change)
* addPutListener(pnl), removePutListener(pnl)<BR>Need to register listener to actually receive the put*Notify info.
* setPercentageDeadband(double percent)
* setLogarithmicDeadband(double exponent_of_ten)
* setMinimumRate(double seconds)<BR>There will be notifications at at least this rate, even if value didn't change.
* setMaximumRate(double vals_per_second)
* useAlarmConditionChange()
* useHardwareEvent(int event_id)<br>Somehow, event_id might be linked to 'Blue Beam'
* setMaxValueCount<br>Setting this to '1' turns the subscription into a single-value 'get'.


=== Channel, 'Get' Related Methods ===
Unclear how to present the Event to a user. For example, how would a 'camonitor' tool allow the user to select the events? Via 20 command-line switches, or should there be a syntax ala
* DataAccess = getData(property_list)<BR> Waits for the data.
"valueDeadband=1.0, maximumRate=20" which is supported by methods
* getDataNotify(property_list)<BR>  Will invoke DataListener once data arrives.
* string toString()
* addDataListener(dn), removeDataListener(dl)
* bood fromString(string event_specification)


=== Data Listener Interface===
=== ChannelGroup ===
The data listener is invoked in response to an async. 'get' as well as a subscription update and contains the following:
A channel group synchronizes access to multiple channels.
* Channel that send this notification
It listens to all members of the channel group and for example
* DataAccess interface to the data
allows waiting for ''all'' outstanding read or write requests
* Reason for the update
to return.
** event 'x'
** channel is disconnected
** access rights changed
** channel moved to different IOC


=== Channel, Subscription Related Methods ===
* add/remove channels<br>By name? Already existing Channel? Add channels that match a pattern, or go via the directory?
* addSubscription(event, filter)
* read/write<br>Has to handle reading/writing the same properties from all channels in group. Also channel-specific list of properties?
** event: alarm condition change, some hardware event (like blue beam).<br>Is this still simply a bit mask, and one needs detailed info from the IOC to know that 'blue beam' == bit 14?
** filters: min/max period, range, rate of change, absolute change
* cancelSubscription(event, filter)


The idea is that the end result of a subscription is just like a getDataNotify,
=== SyncChannel ===
invoking the dataListener, except that a subscription will
A wrapper class around the async. Channel.
typically return data more than once.
Uses a configurable timeout and provides synchronous 'read'.
One can add more then one subscription to the same channel.
 
* Inherits from Channel.
* double getTimeout(), void setTimeout(double seconds)
* DataAccess read(list<string> properties)<BR>Synchronous read of current values of properties.
* DataAccess read(string property)<BR>Synchronous read of current value of one property.


=== Container ===
=== Container ===
The Channel does not store any data from a 'put', 'get' or subscription.
The Channel does not store any data.
When attaching a container to a channel, the container will subscribe
When attaching a container to a channel, the container will subscribe
to the channel and keep a copy of all data, so one can always ask
to the channel and keep a copy of all data, so one can always ask
Line 93: Line 162:
* detach()
* detach()
* implements the DataAccess interface to allow access to the data.
* implements the DataAccess interface to allow access to the data.
Also see [[V4 Data Store]]


== caget 101 ==
== caget 101 ==
This is how a simple 'caget' could be written:
This is how a simple 'caget' could be written:


SyncChannel channel("fred");
DataAccess da = channel.read("value");
cout << "Value of " << channel.getName() << " : " << da.getAsString("value") << "\n";
We can imagine an even simpler example:
cout << "Value of fred:" << Epics::get("fred") << "\n";
This implies lots of work under the hood, with reasonable defaults.
== camonitor 101 ==
This is the goal for a 'camonitor':
class MyMonitor : public Channel::Listener
{
public:
  newData(Channel ch, DataAccess data, Event why)
  {
        cout << channel.getName() << " = " << da.getAsString("value") << "\n";
  }
};
...
  Channel channel("fred");  
  Channel channel("fred");  
  DataAccess da = channel.get("value");
  list<string> properties;
  cout << "Value of " << channel.getName() << " : " << da.getAsString("value") << "\n";
properties.push_back("value");
  channel.disconnect();
  Event e;
e. setMaximumRate(10.0);
  channel.createSubscription(properties, e);

Latest revision as of 19:13, 16 July 2005

Channel Access Client User Interface

Under V4, a ProcessVariable would no longer be limited to the current properties 'value', 'units', ... but allow the users to create CA servers and clients that understand new properties.

The low-level V4 CA client API is likely to be rather complex because it now needs to handle arbitrary property catalogs.

  • There still needs to be an easy to use high-level API, not much more complex than the existing one.
  • There needs to be access to CA from languages like Matlab in a way that's as easy as
pv = caopen('fred');
value = caget(pv);

Food for thought

Is there anything we can learn from other communication libraries?

  • A brief look at ZeroC ICE
  • TIPC, the Transparent Inter Process Communication protocol (TIPC) started out as a communications layer for building clusters, I think by Ericson, and is now becoming more open and also supported by the latest WindRiver vxWorks releases.
    The Buzz:
    • allows socket-based applications to communicate easily and efficient
    • based on publicly available source code
    • A location-transparent addressing scheme
    • Efficient, rapid, and reliable interprocess communication within a node and between nodes, using either connection-oriented or connectionless modes of operation
    • Rapid notification of changes in topology and the ability to adjust quickly to these changes

Constraints

  • Initial implementation will be in C++
  • Should very easily translate into Java, just like e.g. some XML APIs use the exact same classes and methods in C++ and Java. Ideally, Java and C++ version will be available from the start.
  • Ideally, the C++ headers would be simple enough to be parsed by SWIG and hence perl/tcl/python/... glue code could be auto-generated via SWIG
  • API is of course specific to EPICS CA, but ideally it'll also be usable as an interface to other protocols, so that EPICS Office can use an API that is only a slight extension of the CA client user API.

Skeleton API

What follows is in the form of pseudo classes and associated methods that a CA client API should provide.

Directory

The directory is used to discover available channels and map them to the CA Server address and port.

  • Directory(string URL-type-server-address = "")
    Constructor; uses site-specific default server or specific one. The 'URL' might contain a user & password, which decides if this name server connection is read-only (for OPI clients) or if writes are allowed (for IOCs that add PVs to the name server).
  • getChannelInfo(string PV_or_pattern)
    Returns list of
    • CA server - IP & port of server that has the PV
    • quality - is this the IOC, a gateway, backup/primary
  • addChannelInfo(string PV, CASInfo addr_and_port, CASType primary_or_backup_or...)
    Used by CA servers to register their PV in directory
  • deleteChannelInfo(string PV, CASInfo addr_and_port, CASType primary_or_backup_or...)
    Used by CA servers to remove their PV from directory

There is a default directory, available via something like "EPICSDefaults::getDirectory()" and "...setDirectory()". The implementation could use

  • LDAP
  • Broadcast (as before)
  • your custom implementation
    as long as it uses the above interface and registers itself as the default.

Note that Ben argues to hide the directory lookup from the CA client us er. See separate V4 Name Server wiki for the issue of "record" and "PV" information.

Data

The available data constists of property names, types and values, for example:

    double     value; // The "value" property of our data
    string     units; // The "units" property....
    time_stamp time;
 

The data might contain arrays and structures. The user might not want all the properties, and the user might also want to get properties in non-native types (strings for convenience, float instead of double to preserve space, ...). The client has to provide storage, and might allocate this:

    float  value;
    string units;

The user interface must provide the following:

  • getAllProperties()
    Get a list of all the property names and their data type so that the client can make an informed decision for allocating its storage of the data. There might be helper routines to convert between human-usable strings and more efficient numeric identifiers.
  • bool hasWriteAccess(string property)
    Could also be part of the info return by previous method.

This functionality is available without getting any actual data. To retrieve values, the user asks the client library to place the data into the client-provided storage. The traverse of the "dataAccess" proposal handles this by asking the user to implement a property catalog that reveals each storage element. The end effect is not too different from having the user invoke methods like these:

  • getAsString(string property, string *my_storage)
  • getAsDouble(string property, double *my_storage)
  • ...many more, one for each available data type.

Channel

  • Channel(string name, Directory dir = default)
    Constructor. Channel has to have a name. Uses the 'best' channel from the default directory or from a given directory.
  • Channel(string name, CASInfo)
    ... in case you want a specific server after querying the directory yourself or not using a directory at all.
  • getName()
    Returns the name of the channel.
  • addListener(Listener l), removeListener(Listener l)
    Register for notifications, see below.
  • getProperties()
    A read or subscription will return a dataAccess interface to the retrieved data. That might exclude properties that this client cannot access. It will include the data, which might be huge. This separate call will only get property information, no data, for all properties, including read/write access information. The result could be in the form of dataAccess, except all access to actual data yields empty results, only the property-info related part of dataAccess is functional.

Note that Ben suggests that the CA client library should select the CA server automatically by querying a server or via broadcasts; it should also pick amongst competing PVs of the same name without user interaction.

Note also that in order to read or write, one needs to know what properties to access. Does the channel magically provide a list of all properties? Is there a "get all properties" request that reads them on-demand from the CA server? Or does the directory hold the list of available properties and their types?

Channel: Listener

The Channel::Listener interface is invoked in response to the many asynchronous methods of the Channel.

  • writeComplete(Channel ch)
    • Channel that send this notification
  • newData(Channel ch, DataAccess data, Event why)
    • DataAccess interface to the data
    • Reason for the update: Value deadband exceeded, minimum period expired, event 'blue beam', ...
  • accessRightChange(Channel ch)

Alternatively, these could be separate interfaces for a writeListener, dataListener, stateListener.

Channel: State

In contrast to the V3 API, the user doesn't 'connect'. The first data notification implies that we're connected.

  • bool isConnected()
    .. for those who want to poll
  • xxx getServerInfo(), xxx getType()
    .. only valid when isConnected().
  • setUser(), ...
    Sets/changes the 'user' that determines the access rights, allowing OPI tools to adjust this per-channel at runtime. To avoid security issues, setUser will probably send a public key and not a user name and password.

Channel: Writing

  • write(new value)
    Sends the value to the server.
  • createWriteRequest(new value, receipt {delivery, completion, response})
    Sends the value to the server, invokes Listener when CA server has received the value (delivery) or all the processing triggered by the new value has completed (completion). In addition, some records might support command/response, in which case one can write a command and wait for a response callback which looks just like the result of a read request.
  • scheduleWriteRequest(new value, event)
    Sends the value to the server, which will perform the write when the given event fires. Invokes Listener when CA server has performed the write.

Channel: Reading

  • createSubscription(list<string> properties, Event e)
    Will invoke Listener once data arrives.
  • cancelSubscription(....)
    Use properties & event or an ID returned from createSubscription?

Event is a class that allows the user to select what triggers an update:

  • useDefaultValueDeadband()
    Default if nothing else is called.
  • setValueDeadband(double change)
  • setPercentageDeadband(double percent)
  • setLogarithmicDeadband(double exponent_of_ten)
  • setMinimumRate(double seconds)
    There will be notifications at at least this rate, even if value didn't change.
  • setMaximumRate(double vals_per_second)
  • useAlarmConditionChange()
  • useHardwareEvent(int event_id)
    Somehow, event_id might be linked to 'Blue Beam'
  • setMaxValueCount
    Setting this to '1' turns the subscription into a single-value 'get'.

Unclear how to present the Event to a user. For example, how would a 'camonitor' tool allow the user to select the events? Via 20 command-line switches, or should there be a syntax ala "valueDeadband=1.0, maximumRate=20" which is supported by methods

  • string toString()
  • bood fromString(string event_specification)

ChannelGroup

A channel group synchronizes access to multiple channels. It listens to all members of the channel group and for example allows waiting for all outstanding read or write requests to return.

  • add/remove channels
    By name? Already existing Channel? Add channels that match a pattern, or go via the directory?
  • read/write
    Has to handle reading/writing the same properties from all channels in group. Also channel-specific list of properties?

SyncChannel

A wrapper class around the async. Channel. Uses a configurable timeout and provides synchronous 'read'.

  • Inherits from Channel.
  • double getTimeout(), void setTimeout(double seconds)
  • DataAccess read(list<string> properties)
    Synchronous read of current values of properties.
  • DataAccess read(string property)
    Synchronous read of current value of one property.

Container

The Channel does not store any data. When attaching a container to a channel, the container will subscribe to the channel and keep a copy of all data, so one can always ask the container for the current values.

  • attach(channel)
  • detach()
  • implements the DataAccess interface to allow access to the data.

Also see V4 Data Store

caget 101

This is how a simple 'caget' could be written:

SyncChannel channel("fred"); 
DataAccess da = channel.read("value");
cout << "Value of " << channel.getName() << " : " << da.getAsString("value") << "\n";

We can imagine an even simpler example:

cout << "Value of fred:" << Epics::get("fred") << "\n";

This implies lots of work under the hood, with reasonable defaults.

camonitor 101

This is the goal for a 'camonitor':

class MyMonitor : public Channel::Listener
{
public:
  newData(Channel ch, DataAccess data, Event why)
  {
       cout << channel.getName() << " = " << da.getAsString("value") << "\n";
  }
};
...
Channel channel("fred"); 
list<string> properties;
properties.push_back("value");
Event e;
e. setMaximumRate(10.0);
channel.createSubscription(properties, e);