summaryrefslogtreecommitdiff
path: root/core/reference_registry.py
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-09-19 17:38:07 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-09-19 17:38:07 -0600
commitf3327d56d48d63b21ffc03a55fa5e7db753d1707 (patch)
treee19d4c7846dd4a434f8a18f82290597731b4a12c /core/reference_registry.py
parent00671a90bb51bfe485d98a928a456d55adbe1faf (diff)
Redesign Signal/Channel naming and fix pin-remap breaking references
Arduino analog Signal ids used to be the pin name itself, so fixing a wrong pin silently changed the id and orphaned any plot trace, control, or filter pipeline pointing at it. Analog Signal ids are now positional (ai0, ai1, ...) and decoupled from the underlying pin, matching how digital I/O already worked. More broadly, formalizes a Device -> Signal -> Channel -> Plot naming model (SignalConfig for physical device channels, Channel for derived/ computed ones) and adds core/reference_registry.py to propagate any Signal or Channel id rename through plot traces, controls, and filter pipelines instead of leaving them dangling. Signal ids are now editable in the Signals tab with the same propagation. .labui profile schema bumped to v2.0 with a transparent migration for old files, including remapping old pin-name Signal ids to the new positional ones. Removed six confirmed-dead files (two legacy device classes, four superseded top-level ui/ duplicates).
Diffstat (limited to 'core/reference_registry.py')
-rw-r--r--core/reference_registry.py74
1 files changed, 74 insertions, 0 deletions
diff --git a/core/reference_registry.py b/core/reference_registry.py
new file mode 100644
index 0000000..b6fcd0f
--- /dev/null
+++ b/core/reference_registry.py
@@ -0,0 +1,74 @@
+"""
+core/reference_registry.py
+
+Generic (device_id, channel_id) reference-rename propagation.
+
+A Signal (physical device channel) or a Channel (derived, device_id ==
+"derived") can be renamed by the user. Both kinds are referenced the same
+way everywhere downstream — plot traces, control targets, filter
+pipelines, other derived channels' sources — as a plain
+(device_id, channel_id) pair. Renaming one without rewriting every
+reference to it leaves those references dangling: they silently stop
+receiving data (this is exactly the bug that motivated this module — an
+Arduino analog pin remap changed a Signal's id and orphaned its plot
+trace).
+
+propagate_rename() is the single place that walks every subsystem able to
+hold such a reference and rewrites it. It expects a MainWindow instance
+(or anything exposing the same .engine/.processor/._chart/._ctrl/
+._win_config attributes) since that's the one place all of them are
+reachable from.
+"""
+
+from typing import Tuple
+
+Ref = Tuple[str, str]
+
+
+def propagate_rename(app, old_ref: Ref, new_ref: Ref) -> None:
+ if old_ref == new_ref:
+ return
+
+ # Physical signals live in the acquisition engine's buffers; derived
+ # Channels don't (their buffers live in SignalProcessor._derived_bufs).
+ if old_ref[0] != "derived":
+ app.engine.rename_channel(old_ref[0], old_ref[1], new_ref[1])
+
+ # Filter pipelines, other derived channels' sources, latest-value cache,
+ # and (if old_ref is itself a derived Channel) its ring buffer.
+ app.processor.rename_reference(old_ref, new_ref)
+
+ # Live plot — trace specs in the strip chart's current layout.
+ chart = getattr(app, "_chart", None)
+ changed = False
+ if chart is not None and chart._cfg is not None:
+ for pane in chart._cfg.panes:
+ for tr in pane.traces:
+ if (tr.device_id, tr.channel_id) == old_ref:
+ tr.device_id, tr.channel_id = new_ref
+ changed = True
+ if changed:
+ chart.refresh()
+
+ # Control panel — saved specs (profile persistence) + live bound widgets
+ # (actual I/O routing — ControlWidget reads its own .device_id/.channel_id
+ # directly in _write()/_check_stale(), not from the spec).
+ ctrl = getattr(app, "_ctrl", None)
+ if ctrl is not None:
+ for spec in ctrl.get_specs():
+ if (spec.device_id, spec.channel_id) == old_ref:
+ spec.device_id, spec.channel_id = new_ref
+ for widget in getattr(ctrl, "_widgets", []):
+ if (widget.device_id, widget.channel_id) == old_ref:
+ widget.device_id, widget.channel_id = new_ref
+
+ # Config window tabs, if currently open — refresh so pickers/tables
+ # don't show the stale id until next full refresh.
+ win = getattr(app, "_win_config", None)
+ if win is not None:
+ if chart is not None:
+ win.refresh_plot(chart._cfg)
+ if hasattr(win, "refresh_derived"):
+ win.refresh_derived()
+ if hasattr(win, "refresh_devices"):
+ win.refresh_devices()