Native menu bars are frontend-only in JupyterLab (same conclusion reached earlier this session for auto-opening a Console tab -- no server-side hook exists), so eco desktop's Tools -> Log Viewer menu action has no direct equivalent there. A button on the dashboard is the closest achievable analogue, wired through eco.logs's existing prefer= mechanism so all three front-ends share one code path instead of three parallel ones: - eco.logs._open_timeline gained a "sidecar" prefer option: renders the timeline's HTML into a new eco.widgets.jupyter_sidecar.open_html_in_ sidecar() panel -- a real, separate dockable Sidecar, matching desktop's menu action opening its own window. - eco.widgets.jupyter_sidecar.NamespaceDashboard (the JupyterLab counterpart to EcoDesktopApp) gained a "Log Viewer" button above the Namespace launcher, calling eco.logs.widget(prefer="sidecar"); reopening replaces (closes) the previous panel rather than piling up, and it's torn down in .close() alongside everything else. - eco.widgets.widget_tray.make_namespace_dashboard (Voila) gained the same button, calling eco.logs.widget(prefer="jupyter") into a dedicated ipywidgets.Output() -- Voila has no Sidecar shell to dock into, so this renders inline instead, same reasoning as its existing WidgetTray. Verified via scripted construction against fake namespaces (button present, opens, reopening replaces, torn down on close) and the full test suite (347/349 -- the 2 failures are pre-existing in test_config_lazy_init.py, unrelated). Along the way, found and worked around a real pre-existing test-isolation hazard: IPython's InteractiveShell.instance() is a process-wide singleton, and an earlier, unrelated test building a real ipykernel-backed shell leaves a stale one behind that later display()/Output.clear_output() calls in the same pytest session crash against -- not something this change caused, but the first tests to actually exercise that code path, so the new tests short-circuit the specific stale calls rather than depending on that global state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
eco — Experiment Control
___ _______
/ -_) __/ _ \
Experiment Control \__/\__/\___/
eco is a Python-based control environment for experiments, developed and used at SwissFEL, PSI. It is used both as:
- a library of experimental devices for higher-level Python applications or GUIs, and
- an interactive command-line interface, e.g. from an IPython/Jupyter shell or notebook.
eco follows an object-oriented approach: every device is represented as a Python object with a small, predictable interface, so devices can be freely combined in generic control/acquisition routines and analysed with the scientific Python ecosystem. For a general introduction to object-oriented Python, see e.g. this short introduction.
Documentation
The full documentation — installation, core concepts, and worked examples (listening monitors, archiver data and strip charts, pipeline offload, motor configuration) — lives in docs/ and is built with Sphinx, configured to build on Read the Docs via .readthedocs.yaml.
Build it locally:
pip install -r docs/requirements.txt
sphinx-build -b html docs docs/_build/html
Installation
conda install -c paulscherrerinstitute eco
or, for development, in editable mode from a checkout:
git clone https://github.com/paulscherrerinstitute/eco.git
cd eco
pip install -e .
See Installation for beamline-specific setup (the
eco launcher, .ecorc defaults) and the full dependency picture.
Creating a new device
New devices are implemented as a subclass of Assembly, which provides
naming, aliasing, and shell representation:
from eco.elements.assembly import Assembly
class MyDevice(Assembly):
def __init__(self, name=None):
super().__init__(name=name)
self._append(MySubObject, name="my_sub_object", is_setting=True, is_status=True)
is_setting=True marks the child as a setting of the assembly (shown by
.settings() and captured when settings are saved); is_status=True marks it
as contributing to the assembly's .status(). See
Representing real devices — the Assembly in the full docs
for the rest of the model (Adjustable, Detector, Namespace) and a
from-scratch, runnable example of each.