Source code for herosdevices.hardware.menlo.laser_lock

"""HERO driver for a laser-lock module (DDS + closed lock loop) of a Menlo Systems frequency comb."""

from herosdevices.helper import mark_driver

from .beat_loop import BeatLoop
from .dds import DDS
from .mixins import CurrentControl, FrequencyControl, FrequencyDistribution, SelectMonitor
from .ofc import OFC


[docs] @mark_driver( name="Menlo Laser Lock", info="Laser-lock module (DDS + closed lock loop) of a Menlo Systems frequency comb, via QWebChannel", state="alpha", additional_docs=["/tutorials/menlo_ofc.rst"], product_page="https://www.menlosystems.com/", ) class LaserLock(DDS): """Driver for a laser-lock module of a Menlo Systems frequency comb. Extends :py:class:`~herosdevices.hardware.menlo.DDS` with the closed feedback loop (a beat-note PLL) that locks the DDS to a reference. Use :py:class:`~herosdevices.hardware.menlo.DDS` directly for a module that only exposes a bare DDS output with no lock loop. Args: ofc: The OFC HERO this laser-lock module belongs to. funclayer_identifier: Identifier of the module's functional-layer settings object, e.g. "cw1112_1" for the sub-tree at `functionalLayer.cw1112_1Settings`. Use `ofc.explore("functionalLayer")` to find the identifier for a given module. frequency_distribution_node: Relative node name of this module's frequency-distribution block. Defaults to `"frequencyDistribution"`, the plain CW-channel node name. Dual-wavelength channels (see :py:class:`~herosdevices.hardware.menlo.dual_laser_lock.DualLaserLock`) use a wavelength-suffixed name instead, since they have two such blocks. amp_node: Relative node name of this module's laser-diode current-control block (`amp*`), if it has one - not every CW channel does (e.g. `cw578vexlumSettings` has none). `None` (the default) leaves :py:attr:`current_control` unset. 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]] = DDS.DEFAULT_OBSERVABLES | { "locked": {"path": "mainControls.lock", "unit": ""}, "fast_output_monitor": {"path": "mainControls.fastOutput.monitor", "unit": "V"}, "slow_branch_monitor": {"path": "mainControls.slowBranch.monitor", "unit": "V"}, } # mainControls.lock/fastOutput/unlockHere use the same node names as BeatLoop's, so these are the same # property/method objects, not copies - a change to one applies to both. locked = BeatLoop.locked fast_output_setpoint = BeatLoop.fast_output_setpoint fast_output_locked = BeatLoop.fast_output_locked fast_output_monitor = BeatLoop.fast_output_monitor unlock_here = BeatLoop.unlock_here def __init__( self, ofc: OFC, funclayer_identifier: str, frequency_distribution_node: str = "frequencyDistribution", amp_node: str | None = None, observables: dict[str, dict[str, str]] | None = None, ) -> None: DDS.__init__(self, ofc, funclayer_identifier, observables=observables) self.frequency_control = FrequencyControl(self, "frequencyControl") self.frequency_distribution = FrequencyDistribution(self, frequency_distribution_node) self.select_monitor = SelectMonitor(self, "selectMonitor") self.current_control = CurrentControl(self, amp_node) if amp_node is not None else None @property def amplifier_enabled(self) -> bool: """Whether the loop's actuator amplifier (e.g. piezo/current driver) is enabled.""" return self.ofc.get_node(self._path("mainControls.amplifierSwitch")) @amplifier_enabled.setter def amplifier_enabled(self, enabled: bool) -> None: self.ofc.set_node(self._path("mainControls.amplifierSwitch"), enabled) @property def slow_branch_setpoint(self) -> float: """Setpoint of the loop's slow feedback branch.""" return self.ofc.get_node(self._path("mainControls.slowBranch.setpoint")) @slow_branch_setpoint.setter def slow_branch_setpoint(self, setpoint: float) -> None: self.ofc.set_node(self._path("mainControls.slowBranch.setpoint"), setpoint) @property def slow_branch_locked(self) -> bool: """Whether the loop's slow feedback branch is currently locked. Read-only status.""" return self.ofc.get_node(self._path("mainControls.slowBranch.locked")) @property def slow_branch_monitor(self) -> float: """Monitor voltage of the loop's slow feedback branch. Read-only.""" return self.ofc.get_node(self._path("mainControls.slowBranch.monitor"))