Source code for herosdevices.hardware.menlo.ceo
"""HERO driver for the carrier-envelope-offset (CEO) stabilization loop of a Menlo Systems frequency comb."""
from herosdevices.helper import mark_driver
from .beat_loop import BeatLoop
from .dds import DDS
from .functional_layer import FunctionalLayerModule
from .mixins import FrequencyDistribution, SelectMonitor
from .ofc import OFC
[docs]
@mark_driver(
name="Menlo CEO",
info="Carrier-envelope-offset (CEO) stabilization loop of a Menlo Systems frequency comb, via QWebChannel",
state="alpha",
additional_docs=["/tutorials/menlo_ofc.rst"],
product_page="https://www.menlosystems.com/",
)
class CEO(DDS, BeatLoop):
"""Driver for the carrier-envelope-offset (CEO) stabilization loop of a Menlo Systems frequency comb.
Wraps the OFC's `functionalLayer.ceoSettings` sub-tree, a fixed, singleton module (there is only one CEO
loop per OFC, so unlike :py:class:`~herosdevices.hardware.menlo.DDS`/
:py:class:`~herosdevices.hardware.menlo.LaserLock` there is no `funclayer_identifier` to select).
Combines :py:class:`~herosdevices.hardware.menlo.DDS` with :py:class:`~herosdevices.hardware.menlo.BeatLoop`.
This means `CEO` implements atomiq's `RFSource` interface via the DDS:
`_set_frequency` tunes the DDS directly, not the loop's beat-frequency setpoint (see
:py:attr:`beat_frequency_setpoint` for that).
Also polls `xpsTec`, the temperature-controlled actuator's own TEC loop, and exposes
`frequency_distribution`/`select_monitor`.
Args:
ofc: The OFC HERO this loop belongs to.
observables: Additional observables to poll, merged on top of `DEFAULT_OBSERVABLES`, see
:py:class:`~herosdevices.hardware.menlo.functional_layer.FunctionalLayerModule`.
"""
DEFAULT_OBSERVABLES: dict[str, dict[str, str]] = BeatLoop.DEFAULT_OBSERVABLES | {
"beat_frequency": {"path": "carrierEnvelopeOffsetFrequency.ceoFrequencyActual", "unit": "Hz"},
"beat_frequency_setpoint": {"path": "carrierEnvelopeOffsetFrequency.ceoFrequencySetpoint", "unit": "Hz"},
"amp_enabled": {"path": "mainControls.ampEnabled", "unit": ""},
"xps_tec_temperature": {"path": "xpsTec.t_actual_il", "unit": "deg C"},
"xps_tec_current": {"path": "xpsTec.i_actual", "unit": "A"},
}
def __init__(self, ofc: OFC, observables: dict[str, dict[str, str]] | None = None) -> None:
FunctionalLayerModule.__init__(self, ofc, "functionalLayer.ceoSettings", observables=observables)
self.frequency_distribution = FrequencyDistribution(self, "frequencyDistributionCeoBeat")
self.select_monitor = SelectMonitor(self, "selectMonitor")
@property
def beat_frequency(self) -> float:
"""The currently measured CEO beat frequency. Read-only."""
return self.ofc.get_node(self._path("carrierEnvelopeOffsetFrequency.ceoFrequencyActual"))
@property
def beat_frequency_setpoint(self) -> float:
"""Target CEO beat frequency."""
return self.ofc.get_node(self._path("carrierEnvelopeOffsetFrequency.ceoFrequencySetpoint"))
@beat_frequency_setpoint.setter
def beat_frequency_setpoint(self, frequency: float) -> None:
self.ofc.set_node(self._path("carrierEnvelopeOffsetFrequency.ceoFrequencySetpoint"), frequency)
@property
def amp_enabled(self) -> bool:
"""Whether the CEO loop's actuator amplifier is enabled."""
return self.ofc.get_node(self._path("mainControls.ampEnabled"))
@amp_enabled.setter
def amp_enabled(self, enabled: bool) -> None:
self.ofc.set_node(self._path("mainControls.ampEnabled"), enabled)