Client API

pvxs::client::Context represents a PVA protocol client.

#include <pvxs/client.h>
namespace pvxs { namespace client { ... } }

Configuration

The recommended starting point is creating new context configured from EPICS_PVA_* Environment variables. Use pvxs::client::Context::fromEnv().

EPICS_PVA_ADDR_LIST

A list of destination addresses to which UDP search messages will be sent. May contain unicast and/or broadcast addresses.

EPICS_PVA_AUTO_ADDR_LIST

If “YES” then all local broadcast addresses will be implicitly appended to $EPICS_PVA_ADDR_LIST. “YES” if unset.

EPICS_PVA_NAME_SERVERS

A list of the addresses of listening TCP sockets to which search messages will be sent.

EPICS_PVA_BROADCAST_PORT

Default UDP port to which UDP searches will be sent. 5076 if unset.

EPICS_PVA_CONN_TMO

Inactivity timeout for TCP connections. For compatibility with pvAccessCPP a multiplier of 4/3 is applied. So a value of 30 results in a 40 second timeout. Prior to 0.2.0 this variable was ignored.

New in version 0.3.0: EPICS_PVA_ADDR_LIST may contain IPv4 multicast, and IPv6 uni/multicast addresses.

New in version 0.2.0: Added EPICS_PVA_NAME_SERVERS.

New in version 0.2.0: Prior to 0.2.0 EPICS_PVA_CONN_TMO was ignored.

using namespace pvxs;
// Context configured from process environment
client::Context ctxt = client::Context::fromEnv();

Programmatic configuration can be accomplished by explicitly filling in a pvxs::client::Config.

Making Requests

A pvxs::client::Context instance is the entry point for all client network operations. Begin by calling one of the info(), get(), put(), rpc(), or monitor() methods. Each of these methods returns a *Builder object which can be used to provide additional configuration in what are in effected named arguments.

class Context

An independent PVA protocol client instance

Typically created with Config::build()

Context ctxt(Config::from_env().build());

Public Functions

constexpr Context() = default

An empty/dummy Context.

explicit Context(const Config&)

Create/allocate a new client with the provided config. Config::build() is a convenient shorthand.

const Config &config() const

effective config of running client

void close()

Force close the client.

~Context() will close() automatically. So an explicit call is optional.

Aborts/interrupts all in progress network operations. Blocks until any in-progress callbacks have completed.

Since

1.1.0

inline GetBuilder get(const std::string &pvname)

Request the present value of a PV

Simple blocking

Context ctxt(...);
auto result = ctxt.get("pv:name")
                  .exec()
                  ->wait();

With completion callback

Context ctxt(...);
auto op = ctxt.get("pv:name")
              .result([](Result&& prototype){
                 std::cout<<prototype();
              })
              .exec();
// store op until completion
See GetBuilder and Get/Info for details.

inline GetBuilder info(const std::string &pvname)

Request type information from PV. Results in a Value with no marked fields.

Simple blocking

Context ctxt(...);
auto result = ctxt.info("pv:name")
                  .exec()
                  ->wait();

With completion callback

Context ctxt(...);
auto op = ctxt.info("pv:name")
              .result([](Result&& prototype){
                 std::cout<<prototype();
              })
              .exec();
// store op until completion

See GetBuilder and Get/Info for details.

inline PutBuilder put(const std::string &pvname)

Request change/update of PV.

Assign certain values to certain fields and block for completion.

Context ctxt(...);
auto result = ctxt.put("pv:name")
                  .set("value", 42)
                  .exec()
                  ->wait();

Alternately, and more generally, using a .build() callback and use .result() callback for completion notification.

Context ctxt(...);
auto op = ctxt.put("pv:name")
              .build([](Value&& prototype) -> Value {
                  auto putval = prototype.cloneEmpty();
                  putval["value"] = 42;
                  return putval;
              })
              .result([](Result&& prototype){
                 try {
                     // always returns empty Value on success
                     prototype();
                     std::cout<<"Success";
                 }catch(std::exception& e){
                     std::cout<<"Error: "<<e.what();
                 }
              })
              .exec();
// store op until completion

See PutBuilder and Put for details.

inline RPCBuilder rpc(const std::string &pvname, const Value &arg)

Execute “stateless” remote procedure call operation.

Simple blocking

Value arg = ...;
Context ctxt(...);
auto result = ctxt.rpc("pv:name", arg)
                  .arg("blah", 5)
                  .arg("other", "example")
                  .exec()
                  ->wait();

With completion callback

Value arg = ...;
Context ctxt(...);
auto op = ctxt.rpc("pv:name", arg)
              .result([](Result&& prototype){
                 std::cout<<prototype();
              })
              .exec();
// store op until completion

See RPCBuilder and RPC for details.

inline MonitorBuilder monitor(const std::string &pvname)

Create a new subscription for changes to a PV.

MPMCFIFO<std::shared_ptr<Subscription>> workqueue(42u);

auto sub = ctxt.monitor("pv:name")
               .event([&workqueue](Subscription& sub) {
                   // Subscription queue becomes not empty.
                   // Avoid I/O on PVXS worker thread,
                   // delegate to application thread
                   workqueue.push(sub.shared_from_this());
               })
               .exec();

while(auto sub = workqueue.pop()) { // could workqueue.push(nullptr) to break
    try {
        Value update = sub.pop();
        if(!update)
            continue; // Subscription queue empty, wait for another event callback
        std::cout<<update<<"\n";
    } catch(std::exception& e) {
        // may be Connected(), Disconnect(), Finished(), or RemoteError()
        std::cerr<<"Error "<<e.what()<<"\n";
    }
    // queue not empty, reschedule
    workqueue.push(sub);
}
// store op until completion

See MonitorBuilder and Monitor for details.

inline ConnectBuilder connect(const std::string &pvname)

Manually add, and maintain, an entry in the Channel cache.

This optional method may be used when it is known that a given PV will be needed in future. ConnectBuilder::onConnect() and ConnectBuilder::onDisconnect() may be used to get asynchronous notification, or the returned Connect object may be used to poll Channel (dis)connect state.

Since

0.2.0

inline DiscoverBuilder discover(std::function<void(const Discovered&)> &&fn)

Discover the presence or absence of Servers.

Combines information from periodic Server Beacon messages, and optionally Discover pings, to provide notice when PVA servers appear or disappear from attached networks.

Note that a discover() Operation will never complete with a Value, and so can only end with a timeout or cancellation.

Context ctxt(...);
auto op = ctxt.discover([](const Discovered& evt) {
                 std::cout<<evt<<std::endl;
             })
             .pingAll(false) // implied default
             .exec();
op->wait(10.0); // wait 10 seconds, will always timeout.

Since

0.3.0

void hurryUp()

Request prompt search of any disconnected channels.

This method is recommended for use when executing a batch of operations.

Context ctxt = ...;
std::vector<std::string> pvnames = ...;
std::vector<Operation> ops(pvnames.size());

// Initiate all operations
for(size_t i=0; i<pvname.size(); i++)
    ops[i] = ctxt.get(pvnames[i]).exec();

ctxt.hurryUp(); // indicate end of batch

for(size_t i=0; i<pvname.size(); i++)
    ... = ops[i].wait(); // wait for results

Optional. Equivalent to detection of a new server. This method has no effect if called more often than once per 30 seconds.

Public Static Functions

static Context fromEnv()

Create new client context based on configuration from $EPICS_PVA* environment variables.

Shorthand for

Config::fromEnv().build() 
.
Since

0.2.1

static inline RequestBuilder request()

Compose a pvRequest independently of a network operation.

This is not a network operation.

Use of request() is optional. pvRequests can be composed with individual network operation Builders.

Value pvReq = Context::request()
                     .pvRequest("field(value)field(blah)")
                     .record("pipeline", true)
                     .build();

Get/Info

pvxs::client::Context::info() and pvxs::client::Context::get() return a pvxs::client::GetBuilder to prepare either a get() or info() (GET_FIELD) operation. The practical difference being that info() yields a Value which will never have any fields marked.

class GetBuilder : public pvxs::client::detail::CommonBuilder<GetBuilder, detail::CommonBase>

Prepare a remote GET or GET_FIELD (info) operation. See Context::get()

Public Functions

inline GetBuilder &result(std::function<void(Result&&)> &&cb)

Callback through which result Value or an error will be delivered. The functor is stored in the Operation returned by exec().

inline std::shared_ptr<Operation> exec()

Execute the network operation. The caller must keep returned Operation pointer until completion or the operation will be implicitly canceled.

Put

pvxs::client::Context::put() returns a pvxs::client::PutBuilder to prepare a put() operation. In the generic form of put(), the field values to sent have to be passed to the builder callback. This is necessary as the server mandated PV type definition is not known when a Put operation is initiated.

Additionally, a put operation will by default first fetch the present value of the PV and provide it to the builder callback. This allows eg. to perform string to index lookup when writing to an NTEnum.

class PutBuilder : public pvxs::client::detail::CommonBuilder<PutBuilder, detail::PRBase>

Prepare a remote PUT operation See Context::put()

Public Functions

inline PutBuilder &fetchPresent(bool f)

If fetchPresent is true (the default). Then the Value passed to the build() callback will be initialized with a previous value for this PV.

This will be necessary for situation like NTEnum to fetch the choices list. But may be undesirable when writing to array fields to avoid the expense of fetching a copy of the array to be overwritten.

template<typename T>
inline PutBuilder &set(const std::string &name, const T &val, bool required = true)

Utilize default .build() to assign a value to the named field.

Parameters:
  • name – The field name to attempt to assign.

  • val – The value to assign. cf. Value::from()

  • required – Whether to fail if this value can not be assigned to this field.

inline PutBuilder &build(std::function<Value(Value&&)> &&cb)

Provide the builder callback.

Once the PV type information is received from the server, this function will be responsible for populating a Value which will actually be sent.

The functor is stored in the Operation returned by exec().

inline PutBuilder &result(std::function<void(Result&&)> &&cb)

Provide the operation result callback. This callback will be passed a Result which is either an empty Value (success) or an exception on error.

The functor is stored in the Operation returned by exec().

std::shared_ptr<Operation> exec()

Execute the network operation. The caller must keep returned Operation pointer until completion or the operation will be implicitly canceled.

RPC

pvxs::client::Context::rpc() returns a pvxs::client::RPCBuilder to prepare an rpc() operation. There are two ways to prepare the arguments of an RPC operation.

The recommended way is to use the one argument form of rpc() and zero or more calls to pvxs::client::RPCBuilder::arg() to set argument names and values. These will be combined into a single argument structure conforming to the pvxs::nt::NTURI convention.

Alternately, the two argument form of rpc() accepts are arbitrary Value which is passed to the server unaltered.

class RPCBuilder : public pvxs::client::detail::CommonBuilder<RPCBuilder, detail::PRBase>

Prepare a remote RPC operation. See Context::rpc()

Public Functions

inline RPCBuilder &result(std::function<void(Result&&)> &&cb)

Callback through which result Value or an error will be delivered. The functor is stored in the Operation returned by exec().

template<typename T>
inline RPCBuilder &arg(const std::string &name, const T &val)

Provide argument value.

Parameters:
  • name – Argument name

  • val – The value to assign. cf. Value::from()

std::shared_ptr<Operation> exec()

Execute the network operation. The caller must keep returned Operation pointer until completion or the operation will be implicitly canceled.

Operation and Result

The exec() method of the *Builder objects returns a shared_ptr to an pvxs::client::Operation handle, which represents the in-progress network operation. The caller must retain this handle until completion, or the operation will be implicitly cancelled.

When an Operation completes, a pvxs::client::Result is passed to the result() callback. This object holds either a pvxs::Value if the operation succeeded, or an exception.

struct Operation

Handle for in-progress operation.

Public Types

enum operation_t

Operation type.

Values:

enumerator Info
enumerator Get
enumerator Put
enumerator RPC
enumerator Monitor
enumerator Discover

Public Functions

virtual const std::string &name() = 0

PV name.

virtual bool cancel() = 0

Explicitly cancel a pending operation. Blocks until an in-progress callback has completed.

Returns:

true if the operation was canceled, or false if already complete.

virtual Value wait(double timeout) = 0

Block until Operation completion.

As an alternative to a .result() callback, wait for operation completion, timeout, or interruption (via. interrupt() ).

Parameters:

timeout – Time to wait prior to throwing TimeoutError. cf. epicsEvent::wait(double)

Throws:
  • Timeout – Timeout exceeded

  • Interruptedinterrupt() called

Returns:

result Value. Always empty/invalid for put()

inline Value wait()

wait(double) without a timeout

virtual void interrupt() = 0

Queue an interruption of a wait() or wait(double) call.

class Result

Holder for a Value or an exception.

Public Functions

inline Value &operator()()

Access to the Value, or rethrow the exception.

Monitor

pvxs::client::Context::monitor() returns a pvxs::client::MonitorBuilder to prepare a MONITOR operation. The result of this preparation is a pvxs::client::Subscription which represents the in-progress network operation. The caller must retain this handle or the operation will be implicitly cancelled.

Until cancelled, a Subscription will attempt to (re)connect to the requested PV.

A Subscription object allows access to a queue of data updates as Value and events/errors as exceptions. The pvxs::client::Subscription::pop() method will remove an entry from the queue, or return an empty/invalid Value. Data updates are returned as a valid Value. Events/errors are thrown as exceptions.

An pvxs::client::MonitorBuilder::event() callback is only invoked when the Subscription queue becomes not-empty. It will not be called again until pvxs::client::Subscription::pop() has returned an empty/invliad Value.

The special exceptions pvxs::client::Connected, pvxs::client::Disconnect, and pvxs::client::Finished have specific meaning when thrown by pvxs::client::Subscription::pop().

Connected

Depending on pvxs::client::MonitorBuilder::maskConnected() (default true). Queued when a Subscription becomes connected. The Connected object include the server host:port as well as a (client) time of connection.

Disconnect

Depending on pvxs::client::MonitorBuilder::maskDisconnected() (default false). Queued when a Subscription becomes disconnected.

Finished

Depending on pvxs::client::MonitorBuilder::maskDisconnected() (default false). Queued when the server indicates that Subscription will receive no more date updates as a normal completion. Finished is a sub-class of Disconnect.

There are several aspects of a Subscription which may be selected through the MonitorBuilder. The special pvxs::client::Connected and pvxs::client::Disconnect “errors” may appear in the event queue

class MonitorBuilder : public pvxs::client::detail::CommonBuilder<MonitorBuilder, detail::CommonBase>

Prepare a remote subscription See Context::monitor()

Public Functions

inline MonitorBuilder &event(std::function<void(Subscription&)> &&cb)

Install FIFO not-empty event callback.

This functor will be called each time the Subscription event queue becomes not empty. A Subscription becomes empty when Subscription::pop() returns an empty/invalid Value.

The functor is stored in the Subscription returned by exec().

inline MonitorBuilder &maskConnected(bool m = true)

Include Connected exceptions in queue (default false).

inline MonitorBuilder &maskDisconnected(bool m = true)

Include Disconnected exceptions in queue (default true).

std::shared_ptr<Subscription> exec()

Submit request to subscribe.

struct Subscription

Handle for monitor subscription.

Public Functions

inline const std::string &name()

PV name.

virtual bool cancel() = 0

Explicitly cancel a active subscription. Blocks until any in-progress callback has completed.

virtual void pause(bool p = true) = 0

Ask a server to stop (true) or re-start (false), sending updates to this Subscription.

inline void resume()

Shorthand for.

pause(false) 

virtual Value pop() = 0

De-queue update from subscription event queue.

If the queue is empty, return an empty/invalid Value (Value::valid()==false). A data update is returned as a Value. An error or special event is thrown.

std::shared_ptr<Subscription> sub(...);
try {
    while(auto update = sub.pop()) {
        // have data update
        ...
    }
    // queue empty
} catch(Connected& con) {   // if MonitorBuilder::maskConnected(false)
} catch(Finished& con) {    // if MonitorBuilder::maskDisconnected(false)
} catch(Disconnect& con) {  // if MonitorBuilder::maskDisconnected(false)
} catch(RemoteError& con) { // error message from server
} catch(std::exception& con) { // client side error
}

Throws:
Returns:

A valid Value until the queue is empty

virtual void stats(SubscriptionStat&, bool reset = false) = 0

Poll statistics

Since

1.1.0

virtual std::shared_ptr<Subscription> shared_from_this() const = 0

Return strong internal reference which will not prevent implicit cancellation when the last reference returned by exec() is released.

Since

0.2.0

Connect

Request that a Channel be created now which may be used by other Operations, allowing them to complete more quickly.

class ConnectBuilder

cf. Context::connect()

Since

0.2.0

Public Functions

inline ConnectBuilder &onConnect(std::function<void()> &&cb)

Handler to be invoked when channel becomes connected.

inline ConnectBuilder &onDisconnect(std::function<void()> &&cb)

Handler to be invoked when channel becomes disconnected.

inline ConnectBuilder &syncCancel(bool b)

Controls whether Connect::~Connect() synchronizes.

When true (the default) explicit or implicit cancel blocks until any in progress callback has completed. This makes safe some use of references in callbacks.

Since

0.2.0

std::shared_ptr<Connect> exec()

Submit request to connect.

struct Connect

Handle for entry in Channel cache.

Public Functions

virtual const std::string &name() const = 0

Name passed to Context::connect()

virtual bool connected() const = 0

Poll (momentary) connection status.

Threading

A client Context will invoke user callback functions from one or more internal worker threads. However, it is guaranteed that callbacks relating to a given Channel (PV name + priority) will never be executed concurrently. This implies that callbacks for a single operation will also never be executed concurrently.

User code must avoid doing unnecessary work from within a callback function as this will prevent other callbacks from be executed.

Ownership

User provided callbacks are in the form of std::function which may, directly or indirectly, store shared_ptr<> instances. The returned Operation and Subscription instances should be treated as storing the std::function instance(s) and thus any shared_ptr<> captured in them.

Therefore, in order to avoid a resource leak, it is advisable to consider whether a returned Operation or Subscription may participate in a reference loop.

For example, the following creates a reference loop between the Operation instance and the “mystruct” instance.

struct mystruct {
    std::shared_ptr<Operation> op; // <-- Danger!
};
auto myptr = std::make_shared<mystruct>();

Context ctxt(...);
myptr->op = ctxt.get("pv:name")
                .result([myptr](Result&& result) { // <-- Danger!
                })
                .exec();

While such loops can be explicitly broken (eg. by NULLing ‘myptr->op’) it is strongly recommended to avoid such situations as unexpected (exceptional) conditions can easily lead to resource leaks which are quite difficult to detect and isolate.

Where possible it is recommended to capture weak_ptr<> instances.

pvRequest

All operations except info() (GET_FIELD) take a Value which servers may use to modify or qualify the operation. Conventionally, the two ways this may be done is to provide a mask to limit the (sub)fields for which data is returned. Secondly, to provide certain well-known options to modify the operation.

The pvRequest conditions may be specified in three ways through the methods of pvxs::client::detail::CommonBuilder exposed through the individual *Builder types.

Programmatic

The field() and record() methods.

Textual

The pvRequest() method accepts a string which is parsed into calls to the field() and record() methods. These two approaches may be intermixed.

Fallback

The rawRequest() method accepts an externally assembled Value which is sent without modification.

template<typename SubBuilder, typename Base>
class CommonBuilder : public Base

Options common to all operations.

Public Functions

inline SubBuilder &field(const std::string &fld)

Add field to pvRequest blob. A more efficient alternative to

pvRequest("field(name)") 

template<typename T>
inline SubBuilder &record(const std::string &name, const T &val)

Add a key/value option to the request.

Well known options include:

  • queueSize : positive integer

  • block : bool

  • process : bool or string “true”, “false”, or “passive”

  • pipeline : bool

A more efficient alternative to

pvRequest("record[key=value]") 

inline SubBuilder &pvRequest(const std::string &expr)

Parse pvRequest string.

Supported syntax is a list of zero or more entities separated by zero or more spaces.

  • field(<fld.name>)

  • record[<key>=\<value>]

inline SubBuilder &rawRequest(const Value &r)

Store raw pvRequest blob.

inline SubBuilder &syncCancel(bool b)

Controls whether Operation::cancel() and Subscription::cancel() synchronize.

When true (the default) explicit or implicit cancel blocks until any in progress callback has completed. This makes safe some use of references in callbacks.

Since

0.2.0

Syntax

The parser behind pvxs::client::detail::CommonBuilder::pvRequest() understands the following grammar.

pvRequest   ::=  | entry | pvRequest entry
entry       ::=  field | record | field_name
field       ::=  "field" "(" field_list ")"
record      ::=  "record" "[" option_list "]"
field_list  ::=  | field_name | field_list "," field_name
option_list ::=  | option | option_list option
option      ::=  key "=" value

For examples:

  • “field()”

  • “field(value)”

  • “value”

  • “field(value,alarm)”

  • “field(value)field(alarm)”

  • “record[wait=true]”

  • “field()record[wait=true]”

  • “field(value)record[wait=true]”

Misc

struct Config

Public Functions

Config &applyEnv()

update using defined EPICS_PVA* environment variables

Config &applyDefs(const defs_t &defs)

update with definitions as with EPICS_PVA* environment variables Process environment is not changed.

void updateDefs(defs_t &defs) const

extract definitions with environment variable names as keys. Process environment is not changed.

void expand()

Apply rules to translate current requested configuration into one which can actually be loaded based on current host network configuration.

Explicit use of expand() is optional as the Context ctor expands any Config given. expand() is provided as a aid to help understand how Context::effective() is arrived at.

Post:

autoAddrList==false

inline Context build() const

Create a new client Context using the current configuration.

Public Members

std::vector<std::string> addressList

List of unicast, multicast, and broadcast addresses to which search requests will be sent.

Entries may take the forms:

  • <ipaddr>[:<port#>]

  • <ipmultiaddr>[:<port>][,<ttl>][@<ifaceaddr>]

std::vector<std::string> interfaces

List of local interface addresses on which beacons may be received. Also constrains autoAddrList to only consider broadcast addresses of listed interfaces. Empty implies wildcard 0.0.0.0

std::vector<std::string> nameServers

List of TCP name servers. Client context will maintain connections, and send search requests, to these servers.

Since

0.2.0

unsigned short udp_port = 5076

UDP port to bind. Default is 5076. May be zero, cf. Server::config() to find allocated port.

unsigned short tcp_port = 5075

Default TCP port for name servers

Since

0.2.0

bool autoAddrList = true

Whether to extend the addressList with local interface broadcast addresses. (recommended)

double tcpTimeout = 40.0

Inactivity timeout interval for TCP connections. (seconds)

Since

0.2.0

Public Static Functions

static inline Config fromEnv()

Default configuration using process environment.

struct Connected : public std::runtime_error

For monitor only. Subscription has (re)connected.

struct Disconnect : public std::runtime_error

Operation failed because of connection to server was lost.

Subclassed by pvxs::client::Finished

Public Members

const epicsTime time

When loss of connection was noticed (when timeout expires).

struct Finished : public pvxs::client::Disconnect

For monitor only. Subscription has completed normally and no more events will ever be received.

struct RemoteError : public std::runtime_error

Error condition signaled by server.