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