.. _pyrogue_protocol_epicsv7_epicspvserver:
.. _protocols_epicsv7_epicspvserver:
================================
Epics PV Server (V7 Protocol)
================================
``EpicsPvServer`` is the EPICS V7 server-side bridge for exposing PyRogue tree
variables as EPICS records using softIocPVA (pythonSoftIOC).
What It Does
============
``EpicsPvServer`` builds and runs a softIocPVA instance in a background thread,
creating real EPICS records for each served PyRogue Variable or Command.
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.
- Starts softIocPVA once via ``iocInit()`` — the IOC is a process-wide
singleton; multiple ``EpicsPvServer`` instances share the same running IOC.
- Serves both CA and PVA protocols; any standard EPICS client can access
the published records.
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.
.. note::
Passing ``root=self`` to the constructor automatically registers the server
with the Root lifecycle via ``addProtocol``. Do **not** call
``self.addProtocol(self.epics)`` afterwards — doing so registers the server
twice and raises ``Exception: epicsV7: Duplicate _start() call`` at startup.
This differs from the EPICS V4 integration, where ``addProtocol`` must be
called explicitly. If you are migrating from V4, remove the
``self.addProtocol(...)`` call.
Setup Example
=============
.. code-block:: python
import pyrogue as pr
import pyrogue.protocols.epicsV7 as pep7
class MyRoot(pr.Root):
def __init__(self):
super().__init__(name='MyRoot')
# Add variables/devices here as usual.
# Passing root=self auto-registers with the Root lifecycle.
# Do NOT also call self.addProtocol(self.epics).
self.epics = pep7.EpicsPvServer(
base='MyIoc',
root=self,
incGroups=None,
excGroups=['NoServe'],
pvMap=None,
)
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=...)`` — this
automatically registers the server with the Root lifecycle.
3. Do **not** call ``root.addProtocol(...)`` — it is handled internally.
4. Use any EPICS client (CA or PVA) to put and get values.
5. Invoke PyRogue Commands using a plain put to the command's PV.
PV Name Length Handling
=======================
EPICS CA enforces a 60-character limit on PV names (``PVNAME_STRINGSZ = 61``
in EPICS Base). When the full name ``base:path`` would exceed 60 characters,
``EpicsPvServer`` automatically shortens the softioc record name to a
deterministic hash of the form ``tail_XXXXXXXXXX`` (10 lowercase hex digits
derived from SHA-1 of the full name). CA clients use this hashed short name;
PVA clients can use the full human-readable name via a PVA alias — they never
need to know about the hash.
- Names at or below 60 characters are published unchanged. Existing deployments
are completely unaffected.
- Two distinct variable paths that hash to the same short name cause
``_start()`` to raise ``RuntimeError`` immediately before any record is
created.
- ``list()`` always returns full long names regardless of whether a PV was
hashed.
- ``dump()`` annotates each hashed PV with its CA short name in the form
``(CA: base:tail_XXXXXXXXXX)``.
Example output from ``dump()`` for a hashed PV::
MyIoc:LocalRoot:MyDevice:ThisIsAVeryLongVariableNameThatExceedsSixtyCharacterLimit (CA: MyIoc:tail_3f9a1b2c4d)
PVA Transparency for Long Names
---------------------------------
For every PV whose CA record name was hashed, ``EpicsPvServer`` additionally
registers the full long name as a PVA-only channel backed by a
``p4p.server.SharedPV``. This means:
- PVA clients connect using the full, human-readable name.
- Reads on the long PVA name return the current PyRogue variable value.
- Writes on the long PVA name update the same PyRogue variable as a CA write
via the short name.
- A CA write via the short name is immediately visible to PVA clients on the
long name, and vice versa — with no feedback loops.
.. code-block:: python
from p4p.client.thread import Context
ctxt = Context('pva')
# PVA clients always use the full name — no knowledge of the hash required.
full_name = 'MyIoc:LocalRoot:MyDevice:ThisIsAVeryLongVariableNameThatExceedsSixtyCharacterLimit'
value = ctxt.get(full_name)
ctxt.put(full_name, 42)
Live Hardware Values
====================
softioc does not expose a Python callback that fires when a CA or PVA client
issues a ``caget`` / ``ctxt.get()``. Both EPICS V4 and V7 integrations serve
the most recently pushed value from the EPICS record buffer. To ensure clients
receive up-to-date hardware values, the Rogue server process must execute
hardware reads itself — either via PyRogue auto-polling (``pollInterval`` on
variables or ``pollEn=True`` on the Root) or by calling device-level read
commands explicitly.
Commands via caput
==================
PyRogue Commands (``LocalCommand`` and ``RemoteCommand``) are exposed as
longOut EPICS records. Clients invoke them with a plain put:
Invoking a No-Arg Command
--------------------------
.. code-block:: python
from p4p.client.thread import Context
ctxt = Context('pva')
pv_name = 'MyIoc:MyRoot:MyDevice:ResetCounter'
ctxt.put(pv_name, 0) # value=0 triggers no-arg command call
Invoking a Command With an Argument
-------------------------------------
.. code-block:: python
from p4p.client.thread import Context
ctxt = Context('pva')
pv_name = 'MyIoc:MyRoot:MyDevice:SetThreshold'
ctxt.put(pv_name, 42) # value is forwarded to command as arg
The put value is forwarded directly to the PyRogue command as its argument.
This is different from the V4 integration, which uses ``ctxt.rpc()`` with
an NTURI wrapper.
Migration from EPICS V4
=======================
The ``EpicsPvServer`` constructor signature is identical in V4 and V7.
The key differences when migrating:
1. Replace ``import pyrogue.protocols.epicsV4 as pep`` with
``import pyrogue.protocols.epicsV7 as pep7`` and update the class reference.
2. Replace ``ctxt.rpc()`` command invocations with ``ctxt.put(pv_name, value)``.
3. Install ``softioc`` (``pip install softioc``) instead of or alongside ``p4p``.
If you use ``p4p`` as a PVA client (for ``ctxt.get()`` / ``ctxt.put()``),
keep it — only the server-side dependency changes.
4. **Remove any** ``self.addProtocol(self.epics)`` **call.** In V4 this was
required; in V7 the constructor calls ``addProtocol`` automatically when
``root=self`` is passed. Keeping the explicit call registers the server
twice and raises ``Exception: epicsV7: Duplicate _start() call`` at startup.
When To Use It
==============
- Your deployment requires CA compatibility alongside PVA.
- You want to avoid the p4p SharedPV server in favor of standard softIocPVA.
Logging
=======
``EpicsPvServer`` uses Python logging.
- Logger name: ``pyrogue.EpicsPvServer``
- Configuration API:
``logging.getLogger('pyrogue.EpicsPvServer').setLevel(logging.DEBUG)``
What To Explore Next
====================
- Per-variable EPICS record behavior: :doc:`epicspvholder`
Related Topics
==============
- EPICS V7 overview: :doc:`index`
- EPICS V4 server (p4p-based): :doc:`/built_in_modules/protocols/epicsV4/epicspvserver`
- Tree group filtering: :doc:`/pyrogue_tree/core/groups`
- Logging behavior in PyRogue: :doc:`/logging/index`
API Reference
=============
- Generated API page: :doc:`/api/python/pyrogue/protocols/epicsv7/epicspvserver`