Bundles several feature streams that have been iterating on the working tree: - Developer Tools page (Debug-only via DEVELOPER_TOOLS symbol): hosts the identification card, manual KWP write + transaction log, ROM/EEPROM dump card with progress banner and completion message, persisted custom-commands library, persisted EEPROM passwords library. New service primitives: IKwpService.SendRawCustomAsync / ReadEepromAsync / ReadRomEepromAsync. Persistence mirrors the Clients XML pattern in two new files (custom_commands.xml, eeprom_passwords.xml). - Auto-test orchestrator (IAutoTestOrchestrator + AutoTestState): linear K-Line read -> unlock -> bench-on -> test sequence with snackbar UI and progress dialog VM, gated on dashboard alarms. - BIP-STATUS display: BipDisplayViewModel + BipDisplayView, RAM read at 0x0106 via IKwpService.ReadBipStatusAsync; status definitions in BipStatusDefinition. - Tests page redesign: TestSectionCard + PhaseTileView replacing the old TestPlanView/TestRunningView/TestDoneView/TestPreconditionsView/ TestSectionView controls and their VMs. - Pump command sliders: Fluent thick-track style with overhang thumb, click-anywhere-and-drag, mouse-wheel adjustment. - Window startup: app.manifest declares PerMonitorV2 DPI awareness, MainWindow installs a WM_GETMINMAXINFO hook in OnSourceInitialized and maximizes there (after the hook is in place) so the app fits the work area exactly on any display configuration. - Misc: PercentToPixelsConverter, seed_aliases.py one-shot pump-alias importer, tools/Import-BipStatus.ps1, kline_eeprom_spec.md and dump-functions reference docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
310 lines
13 KiB
C#
310 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using HC_APTBS.Models;
|
|
using HC_APTBS.Services;
|
|
|
|
namespace HC_APTBS.ViewModels
|
|
{
|
|
/// <summary>
|
|
/// Root ViewModel for the test panel that displays all tests for the selected pump.
|
|
///
|
|
/// <para>
|
|
/// Replaces the former <c>TestDisplayViewModel</c>. Holds one
|
|
/// <see cref="TestSectionViewModel"/> per <see cref="TestDefinition"/> and provides
|
|
/// the public API that <see cref="MainViewModel"/> calls in response to
|
|
/// <see cref="Services.IBenchService"/> events.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed partial class TestPanelViewModel : ObservableObject
|
|
{
|
|
private readonly ILocalizationService _loc;
|
|
|
|
/// <summary>Initialises a new test panel with a localization service.</summary>
|
|
public TestPanelViewModel(ILocalizationService loc) => _loc = loc;
|
|
|
|
// ── Cached active phase for fast live-indicator lookup ─────────────────────
|
|
|
|
private PhaseCardViewModel? _activePhaseCard;
|
|
|
|
// ── Global toggles ────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Controls visibility of operation values (RPM, ME, FBKW) on all phase cards.
|
|
/// Cascades to every <see cref="PhaseCardViewModel.ShowOperationValues"/>.
|
|
/// </summary>
|
|
[ObservableProperty] private bool _showOperationValues;
|
|
|
|
// ── Status ────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Current verbose status message from the bench service.</summary>
|
|
[ObservableProperty] private string _statusText = string.Empty;
|
|
|
|
/// <summary>True while a test sequence is in progress.</summary>
|
|
[ObservableProperty] private bool _isRunning;
|
|
|
|
/// <summary>Estimated remaining time for the entire test sequence (seconds).</summary>
|
|
[ObservableProperty] private int _remainingSeconds;
|
|
|
|
// ── Active phase countdown (driven by IBenchService.PhaseTimerTick) ───────
|
|
|
|
/// <summary>Name of the currently running phase (empty when idle).</summary>
|
|
[ObservableProperty] private string _currentPhaseName = string.Empty;
|
|
|
|
/// <summary>Sub-section of the current phase: "Conditioning", "Measuring", or empty.</summary>
|
|
[ObservableProperty] private string _sectionLabel = string.Empty;
|
|
|
|
/// <summary>Seconds remaining in the current sub-section countdown.</summary>
|
|
[ObservableProperty] private int _phaseRemainingSeconds;
|
|
|
|
/// <summary>Total seconds for the current sub-section (denominator for progress).</summary>
|
|
[ObservableProperty] private int _phaseTotalSeconds;
|
|
|
|
/// <summary>Progress through the current sub-section (0.0 → 1.0).</summary>
|
|
[ObservableProperty] private double _phaseProgress;
|
|
|
|
// ── Test sections ─────────────────────────────────────────────────────────
|
|
|
|
/// <summary>All test sections for the currently loaded pump.</summary>
|
|
public ObservableCollection<TestSectionViewModel> Tests { get; } = new();
|
|
|
|
// ── Show values cascade ───────────────────────────────────────────────────
|
|
|
|
partial void OnShowOperationValuesChanged(bool value)
|
|
{
|
|
foreach (var section in Tests)
|
|
foreach (var phase in section.Phases)
|
|
phase.ShowOperationValues = value;
|
|
}
|
|
|
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Toggles enable/disable for every phase across all test sections.
|
|
/// If any phase is currently disabled, enables all; otherwise disables all.
|
|
/// </summary>
|
|
[RelayCommand]
|
|
private void ToggleCheckAll()
|
|
{
|
|
bool anyDisabled = Tests.Any(s => s.Phases.Any(p => !p.IsEnabled));
|
|
bool newState = anyDisabled;
|
|
|
|
foreach (var section in Tests)
|
|
{
|
|
// Bypass per-section cascade guard by setting AllPhasesChecked directly,
|
|
// which will cascade down to children.
|
|
section.AllPhasesChecked = newState;
|
|
}
|
|
}
|
|
|
|
// ── Public API: loading ───────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Populates the test panel with all tests from the given pump definition.
|
|
/// Call when the selected pump changes.
|
|
/// </summary>
|
|
/// <param name="pump">The pump whose tests to display.</param>
|
|
public void LoadAllTests(PumpDefinition pump)
|
|
{
|
|
Tests.Clear();
|
|
_activePhaseCard = null;
|
|
StatusText = string.Empty;
|
|
RemainingSeconds = 0;
|
|
|
|
foreach (var testDef in pump.Tests)
|
|
{
|
|
var section = TestSectionViewModel.FromDefinition(testDef, ShowOperationValues, _loc);
|
|
Tests.Add(section);
|
|
}
|
|
|
|
// Compute initial remaining seconds estimate.
|
|
RemainingSeconds = pump.Tests.Sum(t => t.EstimatedTotalSeconds());
|
|
}
|
|
|
|
// ── Public API: real-time updates from BenchService events ─────────────────
|
|
|
|
/// <summary>
|
|
/// Marks the named phase as actively executing and clears any previous active state.
|
|
/// Caches the active phase card for fast live-indicator updates.
|
|
/// </summary>
|
|
/// <param name="phaseName">Name of the phase that is now running.</param>
|
|
public void SetActivePhase(string phaseName)
|
|
{
|
|
StatusText = phaseName;
|
|
CurrentPhaseName = phaseName;
|
|
ClearPhaseTimer();
|
|
_activePhaseCard = null;
|
|
|
|
foreach (var section in Tests)
|
|
{
|
|
bool sectionActive = false;
|
|
foreach (var phase in section.Phases)
|
|
{
|
|
if (phase.Name == phaseName && !phase.IsPassed && !phase.IsFailed)
|
|
{
|
|
phase.IsActive = true;
|
|
_activePhaseCard = phase;
|
|
sectionActive = true;
|
|
}
|
|
else
|
|
{
|
|
phase.IsActive = false;
|
|
}
|
|
}
|
|
section.IsActiveTest = sectionActive;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a phase as completed with the given pass/fail result. Also locks the
|
|
/// active phase's indicators to the pass/fail colour before the active-phase
|
|
/// reference is cleared, so the bar does not flash back to accent from a final
|
|
/// in-range oscillation sample.
|
|
/// </summary>
|
|
/// <param name="phaseName">Name of the completed phase.</param>
|
|
/// <param name="passed">True if the phase passed all criteria.</param>
|
|
public void SetPhaseResult(string phaseName, bool passed)
|
|
{
|
|
foreach (var section in Tests)
|
|
{
|
|
foreach (var phase in section.Phases)
|
|
{
|
|
if (phase.Name != phaseName || (!phase.IsActive && !phase.IsPassed && !phase.IsFailed))
|
|
continue;
|
|
|
|
// Only update if this is the active phase (avoid overwriting already-completed phases
|
|
// with the same name in different tests).
|
|
if (!phase.IsActive) continue;
|
|
|
|
phase.IsActive = false;
|
|
phase.IsPassed = passed;
|
|
phase.IsFailed = !passed;
|
|
phase.ResultText = passed ? _loc.GetString("Common.Pass") : _loc.GetString("Common.Fail");
|
|
break;
|
|
}
|
|
|
|
// Recalculate section active state.
|
|
section.IsActiveTest = section.Phases.Any(p => p.IsActive);
|
|
}
|
|
|
|
if (_activePhaseCard?.Name == phaseName)
|
|
{
|
|
MarkActivePhaseCompleted(passed);
|
|
_activePhaseCard = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the live measurement value on the graphic indicator for the currently
|
|
/// active phase that has a matching receive parameter. Called every refresh tick
|
|
/// so the bar moves continuously through conditioning and measurement.
|
|
/// </summary>
|
|
/// <param name="paramName">CAN parameter name (e.g. "QDelivery").</param>
|
|
/// <param name="value">Current measured value.</param>
|
|
public void UpdateLiveIndicator(string paramName, double value)
|
|
{
|
|
if (_activePhaseCard == null) return;
|
|
|
|
foreach (var indicator in _activePhaseCard.ResultIndicators)
|
|
{
|
|
if (indicator.ParameterName == paramName)
|
|
{
|
|
indicator.CurrentValue = value;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies a runtime tolerance/expected-value update (e.g. after DFI auto-adjust)
|
|
/// to the matching indicator on the active phase. Does not touch
|
|
/// <see cref="GraphicIndicatorViewModel.CurrentValue"/> — live values flow
|
|
/// through <see cref="UpdateLiveIndicator"/>.
|
|
/// </summary>
|
|
public void ApplyToleranceUpdate(string paramName, double expected, double tolerance)
|
|
{
|
|
if (_activePhaseCard == null) return;
|
|
|
|
foreach (var indicator in _activePhaseCard.ResultIndicators)
|
|
{
|
|
if (indicator.ParameterName == paramName)
|
|
{
|
|
indicator.ApplyTolerance(expected, tolerance);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Live-indicator list for the currently executing phase. Empty when no phase
|
|
/// is active. Consumers should iterate this once per refresh tick to push
|
|
/// current readings (see <see cref="MainViewModel.OnRefreshTick"/>).
|
|
/// </summary>
|
|
public IReadOnlyList<GraphicIndicatorViewModel> ActivePhaseIndicators
|
|
=> _activePhaseCard?.ResultIndicators as IReadOnlyList<GraphicIndicatorViewModel>
|
|
?? Array.Empty<GraphicIndicatorViewModel>();
|
|
|
|
private void MarkActivePhaseCompleted(bool passed)
|
|
{
|
|
if (_activePhaseCard == null) return;
|
|
foreach (var indicator in _activePhaseCard.ResultIndicators)
|
|
{
|
|
indicator.PhasePassed = passed;
|
|
indicator.IsPhaseCompleted = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies a phase-timer tick from <see cref="IBenchService.PhaseTimerTick"/>.
|
|
/// Updates the sub-section label, remaining/total seconds and computed progress.
|
|
/// Must be called on the UI thread.
|
|
/// </summary>
|
|
public void ApplyPhaseTimerTick(string section, int remaining, int total)
|
|
{
|
|
SectionLabel = section;
|
|
PhaseRemainingSeconds = remaining;
|
|
PhaseTotalSeconds = total;
|
|
PhaseProgress = total > 0 ? 1.0 - (double)remaining / total : 0.0;
|
|
}
|
|
|
|
/// <summary>Clears the active-phase countdown (call on phase change and test end).</summary>
|
|
public void ClearPhaseTimer()
|
|
{
|
|
SectionLabel = string.Empty;
|
|
PhaseRemainingSeconds = 0;
|
|
PhaseTotalSeconds = 0;
|
|
PhaseProgress = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets all phase execution states and graphic indicators for a fresh test run.
|
|
/// </summary>
|
|
public void ResetResults()
|
|
{
|
|
_activePhaseCard = null;
|
|
StatusText = string.Empty;
|
|
CurrentPhaseName = string.Empty;
|
|
ClearPhaseTimer();
|
|
|
|
foreach (var section in Tests)
|
|
{
|
|
section.IsActiveTest = false;
|
|
foreach (var phase in section.Phases)
|
|
phase.Reset();
|
|
}
|
|
|
|
// Recalculate remaining seconds.
|
|
int total = 0;
|
|
foreach (var section in Tests)
|
|
{
|
|
if (section.Source != null)
|
|
total += section.Source.EstimatedTotalSeconds();
|
|
}
|
|
RemainingSeconds = total;
|
|
}
|
|
}
|
|
}
|