Files
HC_APTBS/ViewModels/InterlockBannerViewModel.cs
LucianoDev 0280a2fad1 feat: page-based navigation shell + Tests page wizard
Replace the monolithic MainWindow with a SelectedPage-driven shell
(Dashboard / Pump / Bench / Tests / Results / Settings). The Tests
page gets the Plan -> Preconditions -> Running -> Done wizard from
ui-structure.md \u00a74, backed by a 7-item precondition gate and
shared sub-views (PhaseCardView / TestSectionView / GraphicIndicatorView)
extracted from the now-deleted monolithic TestPanelView.

New VMs / views:
- Tests wizard: TestPreconditions, PhaseCard, GraphicIndicator,
  TestSection, TestPlan, TestRunning, TestDone
- Dashboard panels: DashboardConnection, DashboardReadings,
  DashboardAlarms, InterlockBanner, ResultHistory
- Pump / bench panels: PumpIdentificationPanel, PumpLiveData,
  UnlockPanel, BenchDriveControl, BenchReadings, RelayBank,
  TemperatureControl, DtcList, AuthGate
- Dialogs: generic ConfirmDialog, UserManageDialog, UserPromptDialog

Supporting changes:
- IsOilPumpOn exposed on MainViewModel for precondition evaluation
- RequiresAuth added to TestDefinition (XML round-trip)
- BipStatusDefinition + CompletedTestRun models
- ~35 new Test.* localization keys (en + es)
- Settings moved from modal dialog to full page
- Pause / Retry / Skip stubs in TestRunningView; full spec in
  docs/gap-test-running-controls.md for follow-up implementation
- docs/ui-structure.md captures the wizard design

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-18 13:11:34 +02:00

96 lines
3.3 KiB
C#

using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HC_APTBS.Services;
namespace HC_APTBS.ViewModels
{
/// <summary>
/// ViewModel for the bench-page interlock banner.
/// Surfaces two soft safety warnings inline (dismissible), matching the
/// Bench page guideline in <c>docs/ui-structure.md</c> §2:
/// <list type="bullet">
/// <item>Oil pump off while RPM &gt; threshold.</item>
/// <item>RPM above configured <c>AppSettings.MaxRpm</c> safety limit.</item>
/// </list>
/// </summary>
public sealed partial class InterlockBannerViewModel : ObservableObject
{
private const double OilPumpRpmThreshold = 300.0;
private readonly IConfigurationService _config;
/// <summary>Text shown in the banner. Empty string hides the banner.</summary>
[ObservableProperty] private string _message = string.Empty;
/// <summary>True when the banner is currently shown.</summary>
[ObservableProperty] private bool _isVisible;
/// <summary>True when the banner is showing a critical warning (red background).</summary>
[ObservableProperty] private bool _isCritical;
private bool _dismissed;
private int _dismissedFor; // Bit flags: 1=oil, 2=rpm
/// <param name="configService">Configuration service (source of <c>MaxRpm</c>).</param>
public InterlockBannerViewModel(IConfigurationService configService)
{
_config = configService;
}
/// <summary>
/// Recomputes the banner message from live bench state.
/// Called from the page's refresh tick handler.
/// </summary>
/// <param name="benchRpm">Current bench motor RPM.</param>
/// <param name="isOilPumpOn">True when the oil pump relay is energised.</param>
public void Update(double benchRpm, bool isOilPumpOn)
{
int maxRpm = Math.Max(1, _config.Settings.MaxRpm);
bool oilWarn = !isOilPumpOn && benchRpm > OilPumpRpmThreshold;
bool rpmWarn = benchRpm > maxRpm;
int activeFlags = (oilWarn ? 1 : 0) | (rpmWarn ? 2 : 0);
// Reset dismissal when the condition actually clears.
if (activeFlags == 0)
{
_dismissed = false;
_dismissedFor = 0;
Message = string.Empty;
IsCritical = false;
IsVisible = false;
return;
}
// If the operator already dismissed this exact condition set, stay hidden.
if (_dismissed && _dismissedFor == activeFlags)
return;
_dismissed = false;
if (rpmWarn)
{
Message = $"RPM {benchRpm:F0} exceeds safety limit ({maxRpm}).";
IsCritical = true;
}
else
{
Message = $"Oil pump is OFF while bench is running at {benchRpm:F0} RPM.";
IsCritical = false;
}
IsVisible = true;
_dismissedFor = activeFlags;
}
/// <summary>Hides the banner until the underlying condition changes.</summary>
[RelayCommand]
private void Dismiss()
{
_dismissed = true;
IsVisible = false;
}
}
}