From b72a926943f5d99200ad33e4b72f220a78e3d596 Mon Sep 17 00:00:00 2001 From: Pete R Jemian Date: Tue, 1 Jul 2025 12:42:14 -0500 Subject: [PATCH 1/4] MNT add docstrings --- ophyd/signal.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ophyd/signal.py b/ophyd/signal.py index 649329c79..5c87a0347 100644 --- a/ophyd/signal.py +++ b/ophyd/signal.py @@ -1,4 +1,5 @@ # vi: ts=4 sw=4 +"""Ophyd Signal Classes""" import os import threading import time @@ -669,6 +670,8 @@ def _run_metadata_callbacks(self): class SignalRO(Signal): + """A read-only Signal -- raises ReadOnlyError when put() or set() are called,""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._metadata.update( From 382465d76a9eaef1491cf91d1f5d15b57ebec175 Mon Sep 17 00:00:00 2001 From: Pete R Jemian Date: Tue, 1 Jul 2025 13:13:11 -0500 Subject: [PATCH 2/4] DOC rephrase --- ophyd/signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ophyd/signal.py b/ophyd/signal.py index 5c87a0347..43900aa82 100644 --- a/ophyd/signal.py +++ b/ophyd/signal.py @@ -903,7 +903,7 @@ class InternalSignal(InternalSignalMixin, Signal): class InternalSignalError(ReadOnlyError): """ - A read-only error sourced from trying to write to an internal signal. + A read-only error raised when trying to write to an internal signal. """ def __init__(self, message=None): From 2cde79a172a74bb3cb41df4b36f36a567fc00ea3 Mon Sep 17 00:00:00 2001 From: Pete R Jemian Date: Tue, 1 Jul 2025 13:13:22 -0500 Subject: [PATCH 3/4] DOC add anchor --- docs/user/explanations/area-detector.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/user/explanations/area-detector.rst b/docs/user/explanations/area-detector.rst index 446b4c53c..faf3986df 100644 --- a/docs/user/explanations/area-detector.rst +++ b/docs/user/explanations/area-detector.rst @@ -1,3 +1,4 @@ +.. _explain_areadetector: .. currentmodule:: ophyd.areadetector ================ From 4e7e20d9940e50ae63daf82915c1b01e5b0fa990 Mon Sep 17 00:00:00 2001 From: Pete R Jemian Date: Tue, 1 Jul 2025 13:54:45 -0500 Subject: [PATCH 4/4] DOC expose classes in signal module --- docs/user/reference/signals.rst | 165 ++++++++++++++++++++++++++++---- 1 file changed, 144 insertions(+), 21 deletions(-) diff --git a/docs/user/reference/signals.rst b/docs/user/reference/signals.rst index d8f07bfc0..c16f65d07 100644 --- a/docs/user/reference/signals.rst +++ b/docs/user/reference/signals.rst @@ -1,34 +1,51 @@ .. _signal_indx: +.. currentmodule:: ophyd.signal + Signals ******* -In EPICS, **Signal** maybe backed by a read-only PV, a single -read-write PV, or a pair of read and write PVs, grouped together. In +.. FIXME: Start with a non-EPICS description of Signal. + + Signal represents a single value of the hardware system. (Could be RO, RW, or WO.) + +.. TODO: Device is not documented? + +In `EPICS `_, :class:`~EpicsSignal` maybe backed by +a read-only process variable (`PV +`_), +a single read-write PV, or a pair of read and write PVs, grouped together. In any of those cases, a single value is exposed to `bluesky -`_. For more complex hardware, for -example a `motor record -`_, the relationships -between the individual process variables needs to be encoded in a -:class:`~device.Device` (a :class:`~epics_motor.EpicsMotor` class -ships with ophyd for this case). This includes both what **Signals** -are grouped together, but also how to manipulate them a coordinated -fashion to achieve the high-level action (moving a motor, changing a -temperature, opening a valve, or taking data). More complex devices, -like a diffractometer or a Area Detector, can be assembled out of -simpler component devices. - - -A ``Signal`` is much like a ``Device`` -- they share almost the same -interface -- but a ``Signal`` has no sub-components. In ophyd's hierarchical, -tree-like representation of a complex piece of hardware, the signals are -the leaves. Each one represents a single PV or a read--write pair of PVs. +`_. For more complex hardware, for example +an EPICS `motor record `_, the +relationships between the individual process variables need to be encoded in a +:class:`~device.Device` (see the :class:`~ophyd.epics_motor.EpicsMotor` class). +A :class:`~device.Device` describes which **Signals** are grouped together, and +how to manipulate them in a coordinated fashion to achieve the high-level action +(moving a motor, changing a temperature, opening a valve, or taking data). More +complex devices, like a `diffractometer `_ or +an :ref:`area detector `, can be assembled from simpler +component devices. + +.. TODO: Next paragraph should move before the EpicsSignal paragraph above. +.. FIXME: tree & leaf metaphor does not need EPICS reference + +A :class:`~Signal` is much like a :class:`~ophyd.device.Device` -- they share +almost the same interface -- but a :class:`~Signal` has no sub-components. In +ophyd's hierarchical, tree-like representation of a complex piece of hardware, +the signals are the leaves. Each one represents a single PV or a read--write +pair of PVs. + +Signal Attributes +----------------- + +All ophyd Signal classes have these attributes: .. index:: kind attribute .. _kind: :attr:`kind` -------------- +++++++++++++ The :attr:`kind` attribute is the means to identify a signal that is relevant for handling by a callback. @@ -70,7 +87,7 @@ the :attr:`hints` attribute of the :class:`~device.Device`. .. _labels: :attr:`labels` --------------- +++++++++++++++ :class:`~signal.Signal` and :class:`~device.Device` now accept a :attr:`labels` attribute. The value is a list of text strings @@ -125,6 +142,112 @@ Then in an ipython session: rig.r rig_r rig.t rig_t +.. _signal_classes: + +Signal Classes +-------------- + +.. autosummary:: + :toctree: ../generated + + Signal + SignalRO + ArrayAttributeSignal + AttributeSignal + DerivedSignal + +.. _signal_classes_epics: + +Signal Classes for EPICS +---------------------------- + +.. autosummary:: + :toctree: ../generated + + EpicsSignalBase + EpicsSignal + EpicsSignalRO + EpicsSignalNoValidation + +.. _signal_classes_internal: + +Signal Classes for Internal Use +-------------------------------- + +.. autosummary:: + :toctree: ../generated + + InternalSignal + InternalSignalMixin + +.. _signal_exceptions: + +Exceptions +---------- + +A :class:`~ConnectionTimeoutError` (or :class:`~ReadTimeoutError`) typically +occurs when an :class:`~EpicsSignal` fails to connect (or respond to a read +request) within a specified time limit. This can happen due to network issues, +the EPICS IOC (server) or PV is unresponsive or unavailable, or the PV name is +incorrect. Here's an example how these exceptions might be used: + +.. code-block:: python + :linenos: + + from ophyd import EpicsSignal + from ophyd.signal import ConnectionTimeoutError, ReadTimeoutError + + signal = EpicsSignal("IOC:pv", name='signal') + try: + # Set timeouts of 5 seconds. + signal.wait_for_connection(timeout=5) + value = signal.get(timeout=5) + print(f"{signal.name}={value}") + except ConnectionTimeoutError: + print("Connection timeout. Please check EPICS PV {signal.pvname!r}.") + except ReadTimeoutError: + print("Read timeout. Please check EPICS PV {signal.pvname!r}.") + +An :class:`~InternalSignalError` is raised when an :class:`~InternalSignal` is +written from outside of its own parent class (the :class:`~ophyd.device.Device` +that defined this :class:`~InternalSignal`). Since an :class:`~InternalSignal` +is designed to be updated only from within the parent class, any other attempts +to write (from outside the parent) will raise this exception. All writes must be +done with ``internal=True``. + +.. autoexception:: ConnectionTimeoutError +.. autoexception:: InternalSignalError +.. autoexception:: ReadTimeoutError .. automodule:: ophyd.signal :noindex: + +---- + +.. currentmodule:: ophyd.signal + +.. rubric:: Signal Classes +.. autosummary:: + :toctree: ../generated + + ArrayAttributeSignal + AttributeSignal + DerivedSignal + EpicsSignal + EpicsSignalBase + EpicsSignalNoValidation + EpicsSignalRO + InternalSignal + InternalSignalMixin + Signal + SignalRO + +------------ + +.. TODO + +.. currentmodule:: ophyd.areadetector.base +.. autosummary:: + :toctree: ../generated + + NDDerivedSignal