"""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)