diff --git a/docs/conf.py b/docs/conf.py index 691a0c223d..f1945d56cf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -88,6 +88,11 @@ napoleon_numpy_docstring = False +napoleon_custom_sections = ( + ("Emits", "params_style"), # For signals emitted by hardware objects + ("Hardware object properties", "params_style"), +) + # -- Options for sphinx.ext.viewcode # https://www.sphinx-doc.org/en/master/usage/extensions/viewcode.html diff --git a/docs/source/dev/docs.md b/docs/source/dev/docs.md index e4e8efda9a..7be62221b7 100644 --- a/docs/source/dev/docs.md +++ b/docs/source/dev/docs.md @@ -79,3 +79,25 @@ The extension is enabled to handle docstrings within the Python code and it is configured for [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings). + +Two custom docstring section titles are available to document *hardware objects*: + +* `Emits` for signals emitted by *hardware objects*; +* and `Hardware object properties` + for the configuration settings of hardware object properties. + +They can be used like in this example: + +```python +class Thing(HardwareObject): + """Some thing. + + Hardware object properties: + colour (str): + The colour of the thing. + + Emits: + isReady (bool): + Emitted when the readiness state changes. + """ +``` diff --git a/mxcubecore/HardwareObjects/abstract/AbstractActuator.py b/mxcubecore/HardwareObjects/abstract/AbstractActuator.py index 56cdbb7054..653ddf9a77 100644 --- a/mxcubecore/HardwareObjects/abstract/AbstractActuator.py +++ b/mxcubecore/HardwareObjects/abstract/AbstractActuator.py @@ -18,12 +18,7 @@ # You should have received a copy of the GNU General Lesser Public License # along with MXCuBE. If not, see . -"""Abstract Actuator class. -Defines the set/update value, get/set/update limits and validate_value -methods and the get_value and _set_value abstract methods. -Initialises the actuator_name, username, read_only and default_value properties. -Emits signals valueChanged and limitsChanged. -""" +"""Abstract Actuator.""" import abc import math @@ -31,19 +26,59 @@ from mxcubecore.BaseHardwareObjects import HardwareObject - __copyright__ = """ Copyright © 2010-2022 by the MXCuBE collaboration """ __license__ = "LGPLv3+" class AbstractActuator(HardwareObject): - """Abstract actuator""" + """Abstract actuator defines methods common to all moving devices. + + The `_set_value` method is the only abtract method that needs to be overloaded + in each implementation. + + Attributes: + _nominal_value (float): + Current actuator value. + default_value (float | None): + Value specified by XML property, otherwise `None`. + _nominal_limits (tuple | None): + Values specified by XML property, otherwise `None`. + actuator_name (str): + Actuator name specified by XML property, otherwise `None`. + read_only (bool): + Read-only flag specified by XML property, otherwise `False`. + + Hardware object properties: + actuator_name (str) + username (str) + read_only (bool) + default_value (bool | int) + default_limits (tuple[int, int]) + + Emits: + valueChanged (tuple[int]): + Tuple whose first and only item is the new value. + + Emitted during initialization of the hardware object + and when setting a new value. + + limitsChanged (tuple[tuple[int, int]]): + Tuple whose first and only item is a two-item tuple of the new limits + (low limit first and high limit second). + + Emitted by `update_limits` if limit values are changed. + + stateChanged (tuple): + Tuple whose first and only item is the new state. + + Emitted by `force_emit_signals` + """ __metaclass__ = abc.ABCMeta unit = None - def __init__(self, name): + def __init__(self, name: str): super().__init__(name) self._nominal_value = None self._nominal_limits = (None, None) @@ -53,9 +88,7 @@ def __init__(self, name): self.username = None def init(self): - """Initialise actuator_name, username, read_only and default_value - properties. - """ + """Init properties: actuator_name, username, read_only and default_value.""" self.actuator_name = self.get_property("actuator_name") self.read_only = self.get_property("read_only") or False self.default_value = self.get_property("default_value") @@ -72,24 +105,28 @@ def init(self): @abc.abstractmethod def get_value(self): """Read the actuator position. + Returns: - value: Actuator position. + Actuator position. """ return None def get_limits(self): """Return actuator low and high limits. + Returns: - (tuple): two elements (low limit, high limit) tuple. + (tuple): Two-item tuple (low limit, high limit). """ return self._nominal_limits def set_limits(self, limits): - """Set actuator low and high limits. Emits signal limitsChanged. + """Set actuator low and high limits and emit signal `limitsChanged`. + Args: - limits (tuple): two elements (low limit, high limit) tuple. + limits (tuple): Two-item tuple (low limit, high limit). + Raises: - ValueError: Attempt to set limits for read-only Actuator. + ValueError: Attempt to set limits for read-only actuator. """ if self.read_only: raise ValueError("Attempt to set limits for read-only Actuator") @@ -97,12 +134,14 @@ def set_limits(self, limits): self._nominal_limits = limits self.emit("limitsChanged", (self._nominal_limits,)) - def validate_value(self, value): + def validate_value(self, value) -> bool: """Check if the value is within limits. + Args: - value(numerical): value + value(numerical): Value. + Returns: - (bool): True if within the limits + `True` if within the limits, `False` otherwise. """ if value is None: return True @@ -115,21 +154,25 @@ def validate_value(self, value): @abc.abstractmethod def _set_value(self, value): """Implementation of specific set actuator logic. + Args: - value: target value + value: Target value. """ - def set_value(self, value, timeout=0): + def set_value(self, value, timeout: float = 0) -> None: """Set actuator to value. + + If `timeout == 0`: return at once and do not wait (default). + + If `timeout is None`: wait forever. + Args: - value: target value - timeout (float): optional - timeout [s], - If timeout == 0: return at once and do not wait - (default); - if timeout is None: wait forever. + value: Target value. + timeout (float): Optional timeout in seconds. Default is `0` (do not wait). + Raises: ValueError: Invalid value or attemp to set read only actuator. - RuntimeError: Timeout waiting for status ready # From wait_ready + RuntimeError: Timeout waiting for status ready (from wait_ready). """ if self.read_only: raise ValueError("Attempt to set value for read-only Actuator") @@ -142,10 +185,11 @@ def set_value(self, value, timeout=0): else: raise ValueError(f"Invalid value {value}") - def update_value(self, value=None): - """Check if the value has changed. Emits signal valueChanged. + def update_value(self, value=None) -> None: + """Check if the value has changed and emit signal `valueChanged`. + Args: - value: value + value: Value. """ if value is None: value = self.get_value() @@ -154,10 +198,11 @@ def update_value(self, value=None): self._nominal_value = value self.emit("valueChanged", (value,)) - def update_limits(self, limits=None): - """Check if the limits have changed. Emits signal limitsChanged. + def update_limits(self, limits=None) -> None: + """Check if the limits have changed and emit signal `limitsChanged`. + Args: - limits (tuple): two elements tuple (low limit, high limit). + limits (tuple): Two-item tuple (low limit, high limit). """ if not limits: limits = self.get_limits() @@ -168,17 +213,17 @@ def update_limits(self, limits=None): self._nominal_limits = limits self.emit("limitsChanged", (limits,)) - def re_emit_values(self): - """Update values for all internal attributes""" + def re_emit_values(self) -> None: + """Update values for all internal attributes.""" self.update_value(self.get_value()) self.update_limits(self.get_limits()) super(AbstractActuator, self).re_emit_values() - def force_emit_signals(self): - """Forces to emit all signals. + def force_emit_signals(self) -> None: + """Force emission of all signals. - Method is called from gui - Do not call it within HWR + Method is called from GUI. + Do not call it within HWR. """ self.emit("valueChanged", (self.get_value(),)) self.emit("limitsChanged", (self.get_limits(),))