From 99445c6fd2fd4f15df828c79600b96b80fa1cfa6 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Wed, 22 Apr 2026 14:50:13 -0600 Subject: Added derived signal to be sources of other derived signals. Fixed derived signal saving profile. Renamed Signals>Pipeline to Signas>Channels and adding/removing channels feature. --- core/__pycache__/profile.cpython-312.pyc | Bin 14026 -> 14149 bytes core/__pycache__/signal_processor.cpython-312.pyc | Bin 23674 -> 23682 bytes core/profile.py | 21 ++++++++++----------- core/signal_processor.py | 13 ++++++------- 4 files changed, 16 insertions(+), 18 deletions(-) (limited to 'core') diff --git a/core/__pycache__/profile.cpython-312.pyc b/core/__pycache__/profile.cpython-312.pyc index 0fee9d9..208b48b 100644 Binary files a/core/__pycache__/profile.cpython-312.pyc and b/core/__pycache__/profile.cpython-312.pyc differ diff --git a/core/__pycache__/signal_processor.cpython-312.pyc b/core/__pycache__/signal_processor.cpython-312.pyc index adcf10d..e914490 100644 Binary files a/core/__pycache__/signal_processor.cpython-312.pyc and b/core/__pycache__/signal_processor.cpython-312.pyc differ diff --git a/core/profile.py b/core/profile.py index 2a70370..ef8c55b 100644 --- a/core/profile.py +++ b/core/profile.py @@ -231,6 +231,16 @@ class ProfileManager: except Exception: pass + # Clear all existing devices — profile defines the complete device set + for dev in list(registry.all_instances()): + try: + dev.disconnect() + except Exception: + pass + registry.remove_instance(dev.info.device_id) + if engine is not None: + engine.remove_device(dev.info.device_id) + for dev_cfg in profile.devices: dev_type = dev_cfg.get("device_type") dev_id = dev_cfg.get("device_id") @@ -241,17 +251,6 @@ class ProfileManager: print(f"[Profile] Unknown device type: {dev_type}") continue try: - # Replace existing device so saved config takes effect - existing = registry.get_instance(dev_id) - if existing is not None: - try: - existing.disconnect() - except Exception: - pass - registry.remove_instance(dev_id) - if engine is not None: - engine.remove_device(dev_id) - kwargs = {k: v for k, v in dev_cfg.items() if k != "device_type"} dev = factory(**kwargs) registry.add_instance(dev) diff --git a/core/signal_processor.py b/core/signal_processor.py index 4161d4d..0d4ed39 100644 --- a/core/signal_processor.py +++ b/core/signal_processor.py @@ -10,7 +10,7 @@ Provides two capabilities: 2. DERIVED CHANNELS — virtual channels computed from one or more physical channels. Each derived channel runs a user-defined function every time - new data arrives. Built-ins: velocity/acceleration from displacement, + new data arrives. Built-ins: derivative/second_derivative from displacement, power from voltage+current, RMS, etc. Custom: arbitrary Python snippet. Architecture @@ -195,8 +195,8 @@ class DerivedChannel: A virtual channel computed from one or more physical channels. kind options: - "velocity" — derivative of a displacement source - "acceleration" — second derivative of a displacement source + "derivative" — derivative of a source (dy/dt) + "second_derivative" — second derivative of a source (d²y/dt²) "power" — voltage_source * current_source "rms" — rolling RMS of a source (window samples) "expression" — arbitrary Python expression string @@ -418,7 +418,7 @@ class SignalProcessor(QObject): return None return dc._fn(inputs, t) - elif dc.kind == "velocity": + elif dc.kind == "derivative": # derivative of source[0] src = dc.sources[0] if dc.sources else None if src is None: return None @@ -428,12 +428,11 @@ class SignalProcessor(QObject): if prev_t is None or t == prev_t: return 0.0 return (inputs[0] - prev_v) / (t - prev_t) - elif dc.kind == "acceleration": - # second derivative — derivative of velocity + elif dc.kind == "second_derivative": + # second derivative — derivative of derivative src = dc.sources[0] if dc.sources else None if src is None: return None state = dc.params.setdefault("_state", {}) - # First get velocity prev_t = state.get("t"); prev_v = state.get("v") cur_vel = 0.0 if prev_t is not None and t != prev_t: -- cgit v1.2.3