:orphan:
.. _device-Menlo Laser Lock:
Menlo Laser Lock
================
**From:** `Menlo Systems `_
**Class:** :py:class:`herosdevices.hardware.menlo.laser_lock.LaserLock`
**Driver Quality Index:** alpha
Additional Information :bdg-warning:`Check before use`
------------------------------------------------------
.. dropdown:: Menlo OFC Setup
This driver is split into one core device and several modules attached to it.
:py:class:`~herosdevices.hardware.menlo.OFC` opens the single QWebChannel websocket connection to the comb and
exposes its raw control/status tree via ``get_node``/``set_node``/``explore``.
Each functional-layer module (:py:class:`~herosdevices.hardware.menlo.DDS`,
:py:class:`~herosdevices.hardware.menlo.LaserLock`, :py:class:`~herosdevices.hardware.menlo.DualLaserLock`,
:py:class:`~herosdevices.hardware.menlo.FXE`, :py:class:`~herosdevices.hardware.menlo.RepetitionRate`,
:py:class:`~herosdevices.hardware.menlo.CEO`, :py:class:`~herosdevices.hardware.menlo.Oscillator`) is a
separate HERO. It takes the already-running ``OFC``
HERO as a constructor argument via BOSS's ``"ofc": "$device_menlo_ofc"`` reference, so all modules share the
one physical connection instead of each opening their own.
See ``examples/menlo/ofc.json`` for a complete BOSS config wiring one comb with all its modules.
Some CW channels lock two wavelengths through one shared lock loop, with two separate frequency-distribution
blocks instead of one. Deploying plain ``LaserLock`` against one of these fails, since it hardcodes a single
frequency-distribution node name that these channels don't have. Use
:py:class:`~herosdevices.hardware.menlo.DualLaserLock` instead, passing both wavelengths' node names
explicitly; see ``examples/menlo/ofc.json`` for an example.
:py:class:`~herosdevices.hardware.menlo.FXE` wraps the comb's 16-channel frequency counter. Unlike the other
modules, it has no default observables, since which channel carries which signal is deployment-specific.
Pass ``observables`` for the channels relevant to your comb, e.g. a beat on channel 3:
.. code-block:: json
"arguments": {
"ofc": "$device_ofc",
"observables": {
"cw_beat": {"path": "counterFrequencies.channel03", "unit": "Hz"}
}
}
Node paths (used in ``get_node``/``set_node``/``observables``) are firmware-dependent and not documented by
Menlo. Use :py:meth:`~herosdevices.hardware.menlo.OFC.format_tree` to find them interactively:
.. code-block:: pycon
>>> print(ofc.format_tree("functionalLayer.rrSettings", depth=2))
functionalLayer.rrSettings
|-- dds
| |-- ddsFrequency = 28286800
| |-- outputOn = True
| `-- outputPower = 0.64
|-- mainControls
| |-- fastOutput ...
| |-- lock = True
| `-- slowOutput ...
`-- repetitionRate
|-- rrCounterRepRate = 250105340.0
`-- rrTargetBeatRF = 250105340
Nodes shown as ``...`` are unexpanded branches; raise ``depth`` or call ``format_tree`` again rooted at that
path to descend further. :py:meth:`~herosdevices.hardware.menlo.OFC.explore` returns the same tree as a plain
dict instead of a rendered string, if you want to process it programmatically.
For anything specific to your comb (e.g. a customer-specific fiber-noise-cancellation module),
poll or control it directly instead of adding a new class: every module accepts an ``observables`` argument,
merged on top of its ``DEFAULT_OBSERVABLES``, and ``OFC.get_node``/``OFC.set_node`` give full read/write
access to any node regardless.
.. code-block:: json
"arguments": {
"host": "IP_OR_HOSTNAME",
"observables": {
"fnc578_locked": {"path": "functionalLayer.fnc578Settings.mainControls.lock", "unit": ""},
"fnc1157_locked": {"path": "functionalLayer.fnc1157Settings.mainControls.lock", "unit": ""}
}
}
.. important::
The ``OFC`` device's ``_ensure_connected`` method must be reachable from the other modules over the
network. HEROS excludes underscore-prefixed methods from a ``RemoteHERO`` proxy unless marked
``force_remote``, so the ``OFC`` row in your BOSS config needs:
.. code-block:: json
"extra_decorators": [["_ensure_connected", "heros.inspect.force_remote"]]
See ``examples/menlo/ofc.json`` for this in context.
.. warning::
The PyPI package named ``pywebchannel`` is an unrelated project; installing it will not work. Install it
from source instead:
.. code-block:: bash
pip install "pywebchannel @ git+https://github.com/MenloSystems/pywebchannel"
``pywebchannel`` is not declared as a project dependency, so this install step must be run manually wherever the Menlo driver
is used: locally, in CI, and in any Docker image.
In a Docker Container deployment via BOSS, use the ``BOSS_PIP_PKGS`` environment variable (see the
`BOSS documentation `_)
instead of extending the image:
.. code-block:: yaml
:caption: docker-compose.yml
services:
device_ofc:
image: registry.gitlab.com/atomiq-project/herosdevices:latest
restart: always
network_mode: host
volumes:
- ./ofc.json:/ofc.json:ro
environment:
- BOSS_PIP_PKGS=pywebchannel@git+https://github.com/MenloSystems/pywebchannel
command: python -m boss.starter -u file:///ofc.json --log info
....................
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.
.. tab-set::
.. tab-item:: Arguments
Bold arguments are mandatory. For more information on the listed arguments refer to the class documentation: :py:class:`herosdevices.hardware.menlo.laser_lock.LaserLock` If parameters appear in this list but not in the class definition, please recursively check the linked base classes for the definition of the parameter.
.. list-table::
:widths: 50 50 50 100
:header-rows: 1
* - Argument
- Type
- Default Value
- Description
* - **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
-
- frequencyDistribution
- 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
- str | None
- None
- 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
- dict[str, dict[str, str]] | None
- None
- Additional observables to poll, merged on top of `DEFAULT_OBSERVABLES`, see :py:class:`~herosdevices.hardware.menlo.functional_layer.FunctionalLayerModule`.
.. tab-item:: Example JSON for BOSS
The following JSON strings can be used to start a HERO device representation of :py:class:`LaserLock ` using `BOSS `_.
.. code-block:: json
{
"_id": "device_menlo_ofc_cw1112_1",
"classname": "herosdevices.hardware.menlo.LaserLock",
"arguments": {
"ofc": "$device_menlo_ofc",
"funclayer_identifier": "cw1112_1",
"amp_node": "amp1112"
},
"datasource": {
"async": false,
"interval": 15
}
}
:sup:`from examples/menlo/ofc.json`
.. code-block:: json
{
"_id": "my_LaserLock",
"classname": "herosdevices.hardware.menlo.laser_lock.LaserLock",
"arguments": {
"ofc": "",
"funclayer_identifier": "",
"frequency_distribution_node": "frequencyDistribution",
"amp_node": null,
"observables": null
}
}
:sup:`generated from signature`
.. tab-item:: Inheritance
.. inheritance-diagram:: herosdevices.hardware.menlo.laser_lock.LaserLock