.. _pyrogue_protocol_epicspvserver: .. _protocols_epics_epicspvserver: .. _protocols_epicsv4_epicspvserver: ======================== Epics PV Server Protocol ======================== ``EpicsPvServer`` is the EPICS V4 server-side bridge for exposing PyRogue tree variables as PVs. What It Does ============ ``EpicsPvServer`` publishes and serves EPICS-facing process variables using the PyRogue integration layer. Server Behavior =============== ``EpicsPvServer``: - Requires a running ``Root`` before startup. - Builds PV mappings either automatically (``base:path``) or from ``pvMap``. - Supports include and exclude group filtering (default excludes ``NoServe``). - Creates one ``EpicsPvHolder`` per served variable and exposes ``list()`` and ``dump()`` helpers for mapping inspection. Constructor and Mapping Overview ================================ ``EpicsPvServer(base=..., root=..., incGroups=..., excGroups=..., pvMap=...)`` uses two mapping modes: - Automatic mode (``pvMap=None``): serves variables that pass group filters using ``:`` naming. - Explicit mode (``pvMap`` dict): serves only mapped variable paths with user-defined PV names. Default exclusion is ``['NoServe']`` when ``excGroups`` is not provided. Setup Example ============= .. code-block:: python import pyrogue as pr import pyrogue.protocols.epicsV4 as pep class MyRoot(pr.Root): def __init__(self): super().__init__(name='MyRoot') # Add variables/devices here as usual. # Build EPICS server with automatic path-based naming. self.epics = pep.EpicsPvServer( base='MyIoc', root=self, incGroups=None, excGroups=['NoServe'], pvMap=None, ) # Register as protocol so Root lifecycle starts/stops it. self.addProtocol(self.epics) with MyRoot() as root: # Inspect active mapping. root.epics.dump() # Optionally write mapping to file for IOC/client integration. root.epics.dump('epics_map.txt') Typical Usage Pattern ===================== The common setup follows this pattern: 1. Create and start a ``Root`` with Local and Remote variables. 2. Construct ``EpicsPvServer(base=..., root=..., pvMap=...)``. 3. Register it with ``root.addProtocol(...)``. 4. Use a P4P client to put and get values and confirm round-trip behavior. 5. Use ``ctxt.rpc()`` to invoke PyRogue Commands through EPICS. RPC for Commands ================ PyRogue Commands (``LocalCommand`` and ``RemoteCommand``) are automatically exposed through EPICS RPC when served by ``EpicsPvServer``. The server-side ``EpicsPvHandler.rpc()`` method dispatches incoming RPC requests to the bound PyRogue Command. Commands appear in the PV namespace alongside variables and can be invoked from any P4P client using ``ctxt.rpc()``. Invoking a Command Without Arguments ------------------------------------- Use an ``NTURI`` with an empty query to call a no-argument command: .. code-block:: python from p4p.client.thread import Context from p4p.nt import NTURI ctxt = Context('pva') pv_name = 'MyIoc:MyRoot:MyDevice:ResetCounter' uri = NTURI([]) ctxt.rpc(pv_name, uri.wrap(pv_name)) On the server side, the handler sees no ``arg`` field in the query and calls the command with no argument. Invoking a Command With an Argument ------------------------------------ To pass a value into a command, declare a query field named ``arg`` in the ``NTURI``. The handler extracts ``val.arg`` from the query and forwards it to the PyRogue command: .. code-block:: python from p4p.client.thread import Context from p4p.nt import NTURI ctxt = Context('pva') pv_name = 'MyIoc:MyRoot:MyDevice:SetThreshold' uri = NTURI([('arg', 'i')]) # 'i' = int32; use 'd' for float64, 's' for string ctxt.rpc(pv_name, uri.wrap(pv_name, kws={'arg': 42})) The NTURI type code for the ``arg`` field should match the expected value type. Common type codes are ``'i'`` (int32), ``'l'`` (int64), ``'d'`` (float64), and ``'s'`` (string). P4P Context Behavior After RPC ------------------------------- .. note:: Calling ``ctxt.rpc()`` on a P4P ``Context`` may cause subsequent ``ctxt.get()`` calls to return raw ``p4p.wrapper.Value`` structs instead of auto-unwrapped scalars. If you need to continue using ``ctxt.get()`` after an RPC call, use a separate ``Context`` for the RPC operation: .. code-block:: python rpc_ctxt = Context('pva') rpc_ctxt.rpc(pv_name, uri.wrap(pv_name)) rpc_ctxt.close() # The original context continues to return unwrapped scalars. value = ctxt.get(other_pv) When To Use It ============== - You need external EPICS clients to consume or control values exposed by Rogue. - Your deployment requires EPICS compatibility alongside existing PyRogue tooling. - You want an explicit server boundary for EPICS protocol behavior. Integration Guidance ==================== - Keep naming and unit conventions aligned between tree variables and EPICS PVs. - Document which PVs are authoritative control points versus status mirrors. - Pair this page with tree-side validation and polling strategy docs when the deployment depends on them. Logging ======= ``EpicsPvServer`` uses Python logging. - Logger name: ``pyrogue.EpicsPvServer`` - Logging API: ``pyrogue.setLogLevel('pyrogue.EpicsPvServer', 'DEBUG')`` This logger is used for PV mapping errors and other server-side operational messages emitted by the Python implementation. What To Explore Next ==================== - Per-variable EPICS publication behavior: :doc:`epicspvholder` Related Topics ============== - EPICS V4 overview: :doc:`index` - Tree group filtering: :doc:`/pyrogue_tree/core/groups` - Logging behavior in PyRogue: :doc:`/logging/index` API Reference ============= See :doc:`/api/python/pyrogue/protocols/epicsv4/epicspvserver` for generated API details.