Files
eco/STARTUP_MODES.md
2026-08-21 15:10:25 +02:00

10 KiB

eco startup modes

eco has four front-ends, selected with --ui (see eco_cli.py). The default remains shell (the traditional terminal session) and nothing about it changes/sf/bernina/bin/eco, the script everyone actually runs today, is untouched and keeps working exactly as before. Everything below is additive and opt-in.

--ui What it is Status
shell Traditional interactive IPython session (default) live, unchanged
lab JupyterLab on the packaged notebook (eco/voila_app.ipynb) existing, not yet wired into /sf/bernina/bin/eco
voila That notebook served as a standalone Voila dashboard existing, not yet wired
desktop A Spyder/MATLAB-like Qt workbench window new (this round), not yet wired

None of lab/voila/desktop are reachable from /sf/bernina/bin/eco yet — eco_cli.py (the module that implements all four) isn't installed as the live eco command in bpy312. Try them directly for now:

source /sf/bernina/bin/bpy-env
cd /sf/bernina/code/gac-bernina/eco
python3 eco_cli.py --ui desktop            # the new Qt workbench
python3 eco_cli.py --ui desktop --theme dark --lazy
python3 eco_cli.py --ui lab                # existing JupyterLab notebook
python3 eco_cli.py --ui voila              # existing Voila dashboard

Wiring eco_cli.py up as the actual eco command (so eco --ui desktop just works) is a separate, deliberately-not-yet-taken step — it touches the live launcher script everyone uses, so it wants its own explicit go-ahead rather than happening as a side effect of this work.

desktop — the Qt workbench

eco/widgets/desktop_app.py. One window:

  • Center: a live IPython console (qtconsole) with namespace already available. Its kernel is in-process (same Python process as the window itself, so it can literally share the calling terminal's own namespace dict) whenever that's safe — but not when this process already has its own running IPython shell (the normal case for calling eco.start_desktop() from an ipython terminal session): a second in-process kernel can't coexist with one already running (IPython's InteractiveShell is a process-wide singleton), so the console falls back to a real subprocess kernel with its own, freshly-rebuilt namespace instead. See eco.widgets.console_kernel for the full explanation — the banner shown when the console opens says which case you're in. Pass with_console=False to skip this entirely: no console, no kernel, no MultipleInstanceError/PYTHONPATH subtleties to worry about — the calling terminal stays the one and only "master" session (see the "New independent console(s)" section below for the opposite: a genuinely separate second session).
  • Left dock, "Namespace": every registered name in the namespace, filterable. Names already initialized are opened immediately on click. Lazy (not-yet-built) names are shown grayed out; clicking one triggers initialization in the background (a spinner shows on that row while it runs) and opens it automatically once ready. Failed names are shown in red and are not retried automatically.
  • "Opening" a name calls <name>.widget() directly (not through the console — see EcoDesktopApp._open_widget's docstring for why calling it straight from the launcher's own Qt code already gets a real QWidget back, the same as typing it at the calling terminal's own prompt), so this works identically whether or not with_console is on. Known v1 limitation: each opened widget is still its own top-level window, not its own dock panel — true QDockWidget embedding is a reasonable next step, not attempted in this first pass.
  • A per-class hook lets a device supply a purpose-built view instead of the generic property grid: set _default_widget = "some_method_name" on the Assembly subclass (see eco.elements.assembly.Assembly.widget). AxisPTZ already does this — _default_widget = "viewer" opens its live-video viewer instead of a plain slider grid.
  • A modern theme is available: pass theme="dark"/"light" — uses the real qt-material package (now installed) if present, else a hand-built approximation of its default teal-on-charcoal look; see eco.widgets.qt_theme. Same switch used by eco.widgets.camserver_panel_qt/camserver_stream_qt.
  • Workspace menu: "Save Workspace Now" writes the dock layout plus which namespace entries are currently open to ~/.eco/desktop_workspace.json (also happens automatically when the window closes); "Reload Last Workspace" restores both — reopening each widget the same way clicking it would (lazy entries get re-initialized first). A fresh window always starts empty; reloading is only ever an explicit action, never automatic on startup. "New (Empty) Workspace" just forgets the tracked open-widget list for the current session.

Testing any of this from a running terminal session, without leaving it

Once eco has started (namespace exists in your session — the normal case), three functions are available directly as eco.X():

eco.start_desktop(theme="dark")     # opens the Qt workbench, non-blocking;
                                     # its console shares *this* session's own
                                     # namespace when that's possible (see above)
eco.start_desktop(with_console=False)  # no embedded console/kernel at all --
                                     # this terminal stays the only "master"
                                     # session; the Namespace panel still
                                     # works fully either way (see below)
eco.start_console()                 # a brand-new, INDEPENDENT console/kernel --
                                     # a second working session, not another view
                                     # onto this one. kind="qt" (default) or
                                     # kind="jupyterlab"
eco.start_jupyterlab()              # attaches to a running JupyterLab server if
                                     # there is one, else starts a background one
eco.start_webapp()                  # starts (or reuses) a background Voila server

None of these block your terminal — the whole point is to try any of the other front-ends side by side with the one you're already in. See eco/widgets/app_launchers.py for the full signatures.

Every kernel is tracked, with its full input/output history

Whether opened via start_desktop() or start_console(), every kernel's activity — everything typed or run, everything that came back (results, stdout/stderr, errors) — is logged as it happens, tagged with which kernel it belongs to, so a sequence of commands across several consoles/widgets can be reconstructed after the fact instead of only being visible in whichever window happened to be open at the time:

from eco.widgets import kernel_registry
kernel_registry.sessions_summary()   # what's running right now, and where its log is
kernel_registry.find_all_logs()      # every session's log on disk, including past ones
session.tail(20)                     # last 20 events for one KernelSession

Each session's log is a plain JSONL file under ~/.eco/kernel_logs/, one line per event (session_start, input, result, stream, error), timestamped. See eco.widgets.kernel_registry and eco.widgets.console_kernel.LoggingJupyterWidget (the console widget both start_desktop() and start_console() build on, which is what actually does the logging).

Try it from Python directly instead of the CLI:

from eco.widgets.desktop_app import EcoDesktopApp, build_namespace
namespace = build_namespace(scope="bernina", lazy=True)
app = EcoDesktopApp(namespace, theme="dark")   # blocks until the window closes

lab/voila — the browser/ipywidgets side

Already built (a prior round, not this one) in eco/widgets/widget_tray.py (WidgetTray, make_namespace_dashboard) and eco/voila_app.ipynb. This is the Voila-compatible answer to the same "browse the namespace, open and close widgets without leaking EPICS polling" problem — it works in Voila (no JupyterLab shell needed) as well as JupyterLab/Notebook, which sidecar (below) cannot.

New this round: eco/widgets/jupyter_sidecar.py's open_in_sidecar(obj, anchor=...) — a thin wrapper around the sidecar package (pip install sidecar) for JupyterLab specifically: it opens obj.widget() in a real Lumino dock panel that you can then freely drag/tab/tile via JupyterLab's own UI, the closest browser-side analogue to the Qt side's dockable panels. It does not work in Voila or classic Notebook (no Lumino shell to dock into there) — use widget_tray/voila_app.ipynb for those. Degrades to a clear RuntimeError (not a traceback) if sidecar isn't installed.

JupyterLab's own Workspaces feature (jupyter lab workspaces export/ import, also in the command palette) is the closest browser-side equivalent of the Qt side's window-layout persistence — not eco code, just worth knowing it exists if a sidecar-heavy layout is worth saving.

What's genuinely new vs. what already existed

New in this round: eco/widgets/desktop_app.py (the whole Qt workbench), eco/widgets/jupyter_sidecar.py, the _default_widget hook on Assembly.widget() (eco/elements/assembly.py) plus AxisPTZ using it, the desktop choice wired into eco_cli.py, and this file. Everything under lab/voila (widget_tray.py, voila_app.ipynb, eco_cli.py itself) already existed going into this round and is described here only for the full picture.

Newer still: eco/widgets/console_kernel.py (the in-process/subprocess kernel-building + input/output logging shared by every console widget), eco/widgets/kernel_registry.py (the cross-kernel activity log described above), eco/widgets/console_window_qt.py and eco.start_console() (a genuinely independent second console/kernel). These replaced desktop_app.py's original always-in-process kernel, which crashed (ipykernel's MultipleInstanceError) the moment eco.start_desktop() was called from an already-running IPython terminal session — the single most common real way to actually use it. See eco.widgets.console_kernel's module docstring for the full explanation.