Source code for herosdevices.hardware.menlo.mixins

"""Reusable node-shape mixins shared across Menlo Systems functional-layer modules.

Menlo's device tree repeats several node shapes (current-controlled laser diodes, frequency-distribution
blocks, PID control loops, ...) under different node names in different modules. Each mixin here models one
such shape, taking the owning module and the shape's relative node path, so a module can compose as many
instances as its own tree needs - e.g. :py:class:`~herosdevices.hardware.menlo.dual_laser_lock.DualLaserLock` needs two
:py:class:`FrequencyDistribution` instances, one per wavelength.
"""

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .functional_layer import FunctionalLayerModule


class _NodeGroup:
    """Base for a mixin addressing one relative sub-path of a `FunctionalLayerModule`'s base path."""

    def __init__(self, module: "FunctionalLayerModule", relative_path: str) -> None:
        self._module = module
        self._relative_path = relative_path

    def _node(self, name: str) -> str:
        return f"{self._relative_path}.{name}"

    def _get(self, name: str) -> Any:
        return self._module.get(self._node(name))

    def _set(self, name: str, value: Any) -> None:
        self._module.set(self._node(name), value)


[docs] class CurrentControl(_NodeGroup): """Laser-diode current control (`setCurrentD1/D2/D3`), e.g. an `amp*` node on a CW channel.""" @property def set_current_d1(self) -> float: """Current setpoint of the first diode stage.""" return self._get("setCurrentD1") @set_current_d1.setter def set_current_d1(self, current: float) -> None: self._set("setCurrentD1", current) @property def set_current_d2(self) -> float: """Current setpoint of the second diode stage.""" return self._get("setCurrentD2") @set_current_d2.setter def set_current_d2(self, current: float) -> None: self._set("setCurrentD2", current) @property def set_current_d3(self) -> float: """Current setpoint of the third diode stage.""" return self._get("setCurrentD3") @set_current_d3.setter def set_current_d3(self, current: float) -> None: self._set("setCurrentD3", current)
[docs] class FrequencyDistribution(_NodeGroup): """Frequency-distribution block (`attenuation`/`counterInputFilter`/`ofdOutputFilter`). Shared shape across a CW channel's `frequencyDistribution`, CEO's `frequencyDistributionCeoBeat`, RR's `frequencyDistributionOpticalBeat`/`frequencyDistributionRfBeat`, and the per-wavelength `frequencyDistribution<wavelength>` nodes on combo CW channels. """ @property def attenuation(self) -> int: """Attenuation applied to the beat signal before it reaches the loop electronics.""" return self._get("attenuation") @attenuation.setter def attenuation(self, attenuation: int) -> None: self._set("attenuation", attenuation) @property def counter_input_filter_enabled(self) -> bool: """Whether the beat-frequency counter's input filter is enabled.""" return self._get("counterInputFilter") @counter_input_filter_enabled.setter def counter_input_filter_enabled(self, enabled: bool) -> None: self._set("counterInputFilter", enabled) @property def ofd_output_filter_enabled(self) -> bool: """Whether the optical frequency divider output filter is enabled.""" return self._get("ofdOutputFilter") @ofd_output_filter_enabled.setter def ofd_output_filter_enabled(self, enabled: bool) -> None: self._set("ofdOutputFilter", enabled)
[docs] class SelectMonitor(_NodeGroup): """Channel-select monitor. CW channels and `CEO` expose a single `hfsChannelSelected`; `RepetitionRate` exposes two (`hfs1ChannelSelected`/`hfs2ChannelSelected`) instead. All three properties are always defined here; which ones actually resolve on the device depends on which module composes this mixin. """ @property def hfs_channel_selected(self) -> int: """Selected HFS reference channel.""" return self._get("hfsChannelSelected") @property def hfs1_channel_selected(self) -> int: """Selected HFS reference channel for the first sub-channel.""" return self._get("hfs1ChannelSelected") @property def hfs2_channel_selected(self) -> int: """Selected HFS reference channel for the second sub-channel.""" return self._get("hfs2ChannelSelected")
[docs] class FrequencyControl(_NodeGroup): """Frequency-control loop shape (gain/setpoint/slope/corner-frequency/handover/slow-branch-enable knobs). Shared by a CW channel's `frequencyControl` and RR's `controlSettingsDualLock`/`controlSettingsSingleLock`. """ @property def p_gain(self) -> float: """Proportional gain of the loop.""" return self._get("pGain") @p_gain.setter def p_gain(self, gain: float) -> None: self._set("pGain", gain) @property def set_point(self) -> float: """Setpoint of the loop.""" return self._get("setPoint") @set_point.setter def set_point(self, setpoint: float) -> None: self._set("setPoint", setpoint) @property def signal_slope(self) -> int: """Sign/slope of the error signal used by the loop, i.e. the loop's polarity convention. Inferred name and semantics from context; verify against vendor documentation before relying on the exact meaning of the value. """ return self._get("signalSlope") @signal_slope.setter def signal_slope(self, slope: int) -> None: self._set("signalSlope", slope) @property def fs_corner_frequencies(self) -> Any: """Corner frequencies of the loop's feed-forward stage.""" return self._get("fsCornerFrequencies") @fs_corner_frequencies.setter def fs_corner_frequencies(self, frequencies: Any) -> None: self._set("fsCornerFrequencies", frequencies) @property def int3_handover_freq(self) -> float: """Handover frequency between the loop's integrator stages.""" return self._get("int3HandoverFreq") @int3_handover_freq.setter def int3_handover_freq(self, frequency: float) -> None: self._set("int3HandoverFreq", frequency) @property def slow_branch_enabled(self) -> bool: """Whether the loop's slow feedback branch is enabled.""" return self._get("slowBranchEnable") @slow_branch_enabled.setter def slow_branch_enabled(self, enabled: bool) -> None: self._set("slowBranchEnable", enabled)
[docs] class Tec(_NodeGroup): """Thermo-electric cooler status/control (`i_actual`/`t_actual_il`/`t_actual_ool`/`t_set`/`tecOn`).""" @property def i_actual(self) -> float: """Actual TEC current. Read-only.""" return self._get("i_actual") @property def t_actual_il(self) -> float: """Actual temperature, in-loop sensor. Read-only.""" return self._get("t_actual_il") @property def t_actual_ool(self) -> float: """Actual temperature, out-of-loop sensor. Read-only.""" return self._get("t_actual_ool") @property def t_set(self) -> float: """Target temperature.""" return self._get("t_set") @t_set.setter def t_set(self, temperature: float) -> None: self._set("t_set", temperature) @property def tec_on(self) -> bool: """Whether the TEC is enabled.""" return self._get("tecOn") @tec_on.setter def tec_on(self, enabled: bool) -> None: self._set("tecOn", enabled)