diff options
Diffstat (limited to 'core/reference_registry.py')
| -rw-r--r-- | core/reference_registry.py | 74 |
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() |
