Skip to content

Commit

Permalink
Add custom docstring sections for hardware objects
Browse files Browse the repository at this point in the history
  • Loading branch information
fabcor-maxiv committed Jun 27, 2024
1 parent 33afc56 commit d714841
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 40 deletions.
4 changes: 4 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@

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
Expand Down
22 changes: 22 additions & 0 deletions docs/source/dev/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
```
125 changes: 85 additions & 40 deletions mxcubecore/HardwareObjects/abstract/AbstractActuator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,67 @@
# You should have received a copy of the GNU General Lesser Public License
# along with MXCuBE. If not, see <http://www.gnu.org/licenses/>.

"""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
from ast import literal_eval

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 2-items 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)
Expand All @@ -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")
Expand All @@ -72,37 +105,43 @@ 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 elements (low limit, high limit) tuple.
"""
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-elements 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")

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
Expand All @@ -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")
Expand All @@ -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()
Expand All @@ -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-elements tuple (low limit, high limit).
"""
if not limits:
limits = self.get_limits()
Expand All @@ -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(),))
Expand Down

0 comments on commit d714841

Please sign in to comment.