Server API¶
Running a PVA Server¶
A Server
instance starts/stops a PVAccess server.
In order to be useful, a Server must be associated with one or more Providers.
Two Provider containers are available: StaticProvider
or DynamicProvider
.
Both are used with one of the SharedPV classes:,
thread.SharedPV
, asyncio.SharedPV
, and/or cothread.SharedPV
.
Instances of these different concurrency models may be mixed into a single Provider.
Example¶
A server with a single “mailbox” PV.
import time
from p4p.nt import NTScalar
from p4p.server import Server
from p4p.server.thread import SharedPV
pv = SharedPV(nt=NTScalar('d'), # scalar double
initial=0.0) # setting initial value also open()'s
@pv.put
def handle(pv, op):
pv.post(op.value()) # just store and update subscribers
op.done()
Server.forever(providers=[{
'demo:pv:name':pv, # PV name only appears here
}]) # runs until KeyboardInterrupt
This server can be tested using the included command line tools. eg.
$ python -m p4p.client.cli get demo:pv:name
$ python -m p4p.client.cli put demo:pv:name
$ python -m p4p.client.cli get demo:pv:name
And in another shell.
$ python -m p4p.client.cli monitor demo:pv:name
Note that this example allows clients to change any field, not just ‘.value’.
This may be restricted by inspecting the Value
returned by ‘op.value()’
to see which fields are marked as changed with eg. Value.changed()
.
A client put operation can be failed with eg. ‘op.done(error=”oops”)’.
In the put handler function ‘pv’ is the SharedPV
and ‘op’ is a ServerOperation
.
Server API¶
- class p4p.server.Server(conf=None, useenv=True, providers=[''])[source]¶
- Parameters:
providers – A list of provider names or instances. See below.
conf (dict) – Configuration keys for the server. Uses same names as environment variables (aka. EPICS_PVAS_*)
useenv (bool) – Whether to use process environment in addition to provided config.
isolate (bool) – If True, override conf= and useenv= to select a configuration suitable for isolated testing. eg. listening only on localhost with a randomly chosen port number. Use
conf()
to determine which port is being used.
Run a PVAccess server serving Channels from the listed providers. The server is running after construction, until stop().
S = Server(providers=["example"]) # do something else S.stop()
As a convenience, a Server may be used as a context manager to automatically
stop()
.with Server(providers=["example"]) as S: # do something else
When configuring a Server, conf keys provided to the constructor have the same name as the environment variables. If both are given, then the provided conf dict is used.
Call Server.conf() to see a list of valid server (EPICS_PVAS_*) key names and the actual values.
The providers list may contain: name strings (cf. installProvider()),
StaticProvider
orDynamicProvider
instances, or a dict “{‘pv:name’:SharedPV
}” to implicitly creat aStaticProvider
. Each entry may also be a tuple “(provider, order)” where “provider” is any of the allowed types, and “order” is an integer used to resolve ambiguity if more than one provider may claim a PV name. (lower numbers are queried first, the default order is 0)- conf()[source]¶
Return a dict() with the effective configuration this server is using.
Suitable to pass to another Server to duplicate this configuration, or to a client Context to allow it to connect to this server.
with Server(providers=["..."], isolate=True) as S: with p4p.client.thread.Context('pva', conf=S.conf(), useenv=False) as C: print(C.get("pv:name"))
- class p4p.server.StaticProvider(name=None)[source]¶
A channel provider which servers from a clearly defined list of names. This list may change at any time.
- Parameters:
name (str) – Provider name. Must be unique within the local context in which it is used. None, the default, will choose an appropriate value.
- close()¶
- add()¶
- remove()¶
- keys()¶
For situations where PV names are not known ahead of time, or when PVs are “created” only as requested, DynamicProvider should be used.
- class p4p.server.DynamicProvider(name, handler)[source]¶
A channel provider which does not maintain a list of provided channel names.
The following example shows a simple case, in fact so simple that StaticProvider is a better fit.
class DynHandler(object): def __init__(self): self.pv = SharedPV() def testChannel(self, name): # return True, False, or DynamicProvider.NotYet return name=="blah" def makeChannel(self, name, peer): if name=="blah": return self.pv # return None falls through to next source provider = DynamicProvider("arbitrary", DynHandler()) server = Server(providers=[provider])
- NotYet = b'nocache'¶
DynamicProvider Handler Interface¶
A DynamicProvider
Handler class will define the following:
- class p4p.server.ProviderHandler¶
- testChannel(pvname)¶
Called with a PV name which some client is searching for.
- Returns:
True to claim this PV. False to “permenently” disclaim this PV. Or
DynamicProvider.NotYet
to temporarily disclaim.
Each DynamicProvider maintains a cache of negative search results (when
`testChannel()==False`
) to avoid extra work on a subnet with many clients searching for non-existant PVs. If it is desirable to defeat this behavour, for example as part of lazy pv creation, then testChannel() can returnDynamicProvider.NotYet
instead of False.
- makeChannel(pvname, src):
Called when a client attempts to create a Channel for some PV. The object which is returned will not be collected until the client closes the Channel or becomes disconnected.
- Returns:
A
SharedPV
instance.
ServerOperation¶
This class is passed to SharedPV handler Handler.put()
and Handler.rpc()
methods.
- class p4p.server.ServerOperation¶
An in-progress Put or RPC operation from a client.
- done(value=None, error=None)¶
Signal completion of the operation.
# successful completion without result (Put or RPC) done() # successful completion with result (RPC only) done(Value) # unsuccessful completion (Put or RPC) done(error="msg")
- pvRequest() Value ¶
Access the request Value provided by the client, which may ignored, or used to modify handling.
- name() str ¶
The PV name used by the client
- peer() str ¶
Client address
- account() str ¶
Client identity
- roles() {str} ¶
Client group memberships
- onCancel(callable|None)¶
Set callable which will be invoked if the remote operation is cancelled by the client, or if client connection is lost.