Real bug reported after live testing: `eco jupyterlab` rendered the Namespace launcher as a large inline block (full notebook width, not a narrow sidebar like eco desktop's dock), and opening a device apparently popped a Qt window instead of an ipywidgets view. Root cause: eco jupyterlab was pointed at the same voila_app.ipynb used by `eco webapp`, which renders everything via eco.widgets.widget_tray.make_namespace_dashboard -- built for Voila, which (per eco.widgets.jupyter_sidecar's own existing docstring) has no Lumino shell to dock into at all, so there was never a "narrow sidebar" option available there in the first place. JupyterLab DOES have a Lumino shell, and eco already had eco.widgets.jupyter_sidecar.open_in_sidecar for exactly this -- it just wasn't wired up to the CLI. - NamespaceLauncherWidget now takes a generic on_open(obj, label) callback instead of a hardcoded WidgetTray, so it can drive either a WidgetTray panel (Voila/webapp, unchanged) or a Sidecar panel (JupyterLab, new). - New eco.widgets.jupyter_sidecar.open_namespace_dashboard(): docks the Namespace launcher in its own Sidecar panel, and routes each opened device into its own Sidecar panel too -- the closest browser-side analogue to eco desktop's dockable Namespace panel + tiled device docks. - New eco/jupyterlab_app.ipynb (packaged; added to package-data, learned that lesson the hard way earlier this session) uses it, with its code cell collapsed by default (standard nbformat source_hidden metadata) -- JupyterLab has no server-side route to auto-open a real Console tab (it's a frontend-only command, confirmed by grepping jupyterlab's own source for any matching handler), so a notebook is still the minimal anchor tab needed for Sidecar/ipywidgets to have something to render into; this keeps its visible footprint to just the dashboard. - New _write_jupyterlab_notebook(): opening an *existing* .ipynb always uses the kernel named in its own file, not JupyterLab's server-wide default -- so eco_cli.py now regenerates a copy with its kernelspec pointed at the just-registered eco-<scope> kernel, same as _register_eco_kernel's other generated files. Voila (`eco webapp`) is unchanged -- make_namespace_dashboard/WidgetTray remains correct there, confirmed working earlier this session. Verified: the notebook's cell code executes without error against a real eco-bernina kernel (same mechanism used to verify the kernel registration earlier), a scripted Sidecar dashboard construction + simulated click against a fake namespace, and the full test suite (338/340 -- the 2 failures are pre-existing in test_config_lazy_init.py, unrelated). 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.