diff --git a/Infrastructure/Pcan/PcanAdapter.cs b/Infrastructure/Pcan/PcanAdapter.cs index a18247b..9370174 100644 --- a/Infrastructure/Pcan/PcanAdapter.cs +++ b/Infrastructure/Pcan/PcanAdapter.cs @@ -48,11 +48,27 @@ namespace HC_APTBS.Infrastructure.Pcan private AutoResetEvent? _receiveEvent; private volatile bool _stopRead = true; + // ── Liveness tracking ──────────────────────────────────────────────────── + + private const int LivenessTimeoutMs = 500; + private HashSet _benchMessageIds = new(); + private HashSet _pumpMessageIds = new(); + private DateTime _lastBenchFrameUtc = DateTime.MinValue; + private DateTime _lastPumpFrameUtc = DateTime.MinValue; + private bool _benchAlive; + private bool _pumpAlive; + // ── ICanService ────────────────────────────────────────────────────────── /// public event Action? StatusChanged; + /// + public event Action? BenchLivenessChanged; + + /// + public event Action? PumpLivenessChanged; + /// public TPCANStatus CurrentStatus { get; private set; } = TPCANStatus.PCAN_ERROR_OK; @@ -186,6 +202,18 @@ namespace HC_APTBS.Infrastructure.Pcan } } + /// + public void RegisterBenchMessageIds(IReadOnlyCollection ids) + { + _benchMessageIds = new HashSet(ids); + } + + /// + public void RegisterPumpMessageIds(IReadOnlyCollection ids) + { + _pumpMessageIds = new HashSet(ids); + } + // ── ICanService: transmit ───────────────────────────────────────────────── /// @@ -289,6 +317,9 @@ namespace HC_APTBS.Infrastructure.Pcan EmitStatusChanged(status); } + // Check liveness timeouts. + CheckLivenessTimeout(); + // Configurable polling interval to avoid pegging the CPU. // Typical value: 2–50 ms depending on operational phase. Thread.Sleep(2); @@ -336,6 +367,27 @@ namespace HC_APTBS.Infrastructure.Pcan if (!snapshot.TryGetValue(frame.ID, out var parameters)) return; + // Track liveness for bench and pump frame groups. + var now = DateTime.UtcNow; + if (_benchMessageIds.Contains(frame.ID)) + { + _lastBenchFrameUtc = now; + if (!_benchAlive) + { + _benchAlive = true; + BenchLivenessChanged?.Invoke(true); + } + } + if (_pumpMessageIds.Contains(frame.ID)) + { + _lastPumpFrameUtc = now; + if (!_pumpAlive) + { + _pumpAlive = true; + PumpLivenessChanged?.Invoke(true); + } + } + byte[] data = frame.DATA; foreach (var param in parameters) @@ -472,6 +524,27 @@ namespace HC_APTBS.Infrastructure.Pcan return raw; } + /// + /// Checks if bench or pump frame reception has timed out and fires + /// liveness events on transition from alive to dead. + /// + private void CheckLivenessTimeout() + { + var now = DateTime.UtcNow; + + if (_benchAlive && (now - _lastBenchFrameUtc).TotalMilliseconds > LivenessTimeoutMs) + { + _benchAlive = false; + BenchLivenessChanged?.Invoke(false); + } + + if (_pumpAlive && (now - _lastPumpFrameUtc).TotalMilliseconds > LivenessTimeoutMs) + { + _pumpAlive = false; + PumpLivenessChanged?.Invoke(false); + } + } + // ── IIR low-pass filter ─────────────────────────────────────────────────── /// diff --git a/MainWindow.xaml b/MainWindow.xaml index 0045d47..dceda2d 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -106,9 +106,15 @@ ══════════════════════════════════════════════════════════════ --> - + + + + + + + - + @@ -116,6 +122,7 @@ + @@ -161,11 +168,25 @@ + + + + + + + - - + +