Source code for herosdevices.hardware.menlo.functional_layer
"""Base class for drivers scoped to one `functionalLayer` sub-tree of a Menlo Systems optical frequency comb."""
from typing import Any
from .ofc import OFC, poll_observables
[docs]
class FunctionalLayerModule:
"""Base for drivers that address one sub-tree of an OFC's `functionalLayer`, relative to a fixed base path.
Does not open its own connection: `ofc` is meant to be an already-running
:py:class:`~herosdevices.hardware.menlo.ofc.OFC` HERO, injected as a dependency (e.g. via boss's
`"ofc": "$device_menlo_ofc"` reference in the config), so multiple modules share the one physical
websocket connection to the OFC instead of each opening a duplicate connection to the same device.
Args:
ofc: The OFC HERO this module belongs to.
base_path: Dotted path to this module's settings object, e.g. `functionalLayer.cw1112_1Settings` or
`functionalLayer.rrSettings`.
observables: Additional observables to poll for `observable_data`, merged on top of
:py:attr:`DEFAULT_OBSERVABLES` (an entry here with the same name overrides the default). See
:py:meth:`~herosdevices.hardware.menlo.ofc.OFC.get_node` for the "path"/"unit" dict shape.
"""
DEFAULT_OBSERVABLES: dict[str, dict[str, str]] = {}
def __init__(self, ofc: OFC, base_path: str, observables: dict[str, dict[str, str]] | None = None) -> None:
self.ofc = ofc
self.base_path = base_path
self.observables = self.DEFAULT_OBSERVABLES | (observables or {})
def _path(self, relative_path: str) -> str:
return f"{self.base_path}.{relative_path}"
[docs]
def get(self, relative_path: str) -> Any:
"""Read the current value of a node relative to this module's settings object.
Use this for module properties not already exposed as a named attribute. See
:py:meth:`~herosdevices.hardware.menlo.ofc.OFC.explore` to find the available relative paths,
e.g. `ofc.explore(module.base_path, depth=2)`.
Args:
relative_path: Dotted path relative to `base_path`.
Returns:
The current value of the node.
"""
return self.ofc.get_node(self._path(relative_path))
[docs]
def set(self, relative_path: str, value: Any) -> None:
"""Set the value of a node relative to this module's settings object.
Args:
relative_path: Dotted path relative to `base_path`, see :py:meth:`get`.
value: Value to set.
"""
self.ofc.set_node(self._path(relative_path), value)
def _observable_data(self) -> dict[str, tuple[Any, str]]:
"""Read every path in `observables` (`DEFAULT_OBSERVABLES` merged with any instance overrides).
Tolerates an unreachable OFC. Defined here (ahead of `RFSource` in every subclass's MRO) so it
supersedes `RFSource`'s cached-value `_observable_data` for every functional-layer module, regardless
of a subclass's inheritance order.
Returns:
A dict mapping each observable's name to its `(value, unit)` pair. Empty if the OFC is not
currently reachable.
"""
return poll_observables(self.observables, self.ofc._ensure_connected, self.get, self.base_path)