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>
282 lines
12 KiB
C#
282 lines
12 KiB
C#
using System;
|
|
using System.Windows;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using HC_APTBS.Models;
|
|
using HC_APTBS.Services;
|
|
using HC_APTBS.ViewModels.Dialogs;
|
|
using HC_APTBS.Views.Dialogs;
|
|
|
|
namespace HC_APTBS.ViewModels
|
|
{
|
|
/// <summary>
|
|
/// ViewModel for manual bench controls: direction toggle, RPM start/stop with
|
|
/// PID ramp, oil pump toggle, and turn downcounter.
|
|
/// Created by <see cref="MainViewModel"/> as a child ViewModel.
|
|
/// </summary>
|
|
public sealed partial class BenchControlViewModel : ObservableObject
|
|
{
|
|
private readonly IBenchService _bench;
|
|
private readonly IConfigurationService _config;
|
|
|
|
// ── Direction ─────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>True when the bench rotates clockwise (right). False for counter-clockwise (left).</summary>
|
|
[ObservableProperty]
|
|
[NotifyPropertyChangedFor(nameof(IsDirectionLeft))]
|
|
private bool _isDirectionRight = true;
|
|
|
|
/// <summary>True when the bench rotates counter-clockwise (left). Computed inverse of <see cref="IsDirectionRight"/>.</summary>
|
|
public bool IsDirectionLeft => !IsDirectionRight;
|
|
|
|
// ── Oil pump ──────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>True when the oil pump relay is energised.</summary>
|
|
[ObservableProperty] private bool _isOilPumpOn;
|
|
|
|
// ── RPM control ───────────────────────────────────────────────────────────
|
|
|
|
/// <summary>True when the bench motor is running (last RPM command > 0).</summary>
|
|
[ObservableProperty] private bool _isBenchRunning;
|
|
|
|
/// <summary>Controls the RPM quick-select popup visibility.</summary>
|
|
[ObservableProperty] private bool _isRpmPopupOpen;
|
|
|
|
/// <summary>Text in the RPM input field.</summary>
|
|
[ObservableProperty] private string _rpmInputText = string.Empty;
|
|
|
|
/// <summary>Last RPM setpoint commanded via PID or direct set.</summary>
|
|
[ObservableProperty] private double _targetRpm;
|
|
|
|
/// <summary>Last voltage sent to the motor CAN parameter.</summary>
|
|
[ObservableProperty] private double _commandVoltage;
|
|
|
|
// ── Counter ───────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Controls the counter popup visibility.</summary>
|
|
[ObservableProperty] private bool _isCounterPopupOpen;
|
|
|
|
/// <summary>Text in the counter input field.</summary>
|
|
[ObservableProperty] private string _counterInputText = string.Empty;
|
|
|
|
/// <summary>Live counter count-down value read from CAN.</summary>
|
|
[ObservableProperty] private double _benchCounterValue;
|
|
|
|
// ── Constructor ───────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Creates the bench control ViewModel and subscribes to service events.
|
|
/// </summary>
|
|
/// <param name="benchService">Bench service for RPM, relay, and parameter operations.</param>
|
|
/// <param name="configService">Configuration service for bench parameters.</param>
|
|
public BenchControlViewModel(IBenchService benchService, IConfigurationService configService)
|
|
{
|
|
_bench = benchService;
|
|
_config = configService;
|
|
|
|
_bench.RpmCommandSent += () =>
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
{
|
|
TargetRpm = _bench.LastTargetRpm;
|
|
CommandVoltage = _bench.LastCommandVoltage;
|
|
});
|
|
}
|
|
|
|
// ── Direction toggle ──────────────────────────────────────────────────────
|
|
|
|
partial void OnIsDirectionRightChanged(bool value)
|
|
{
|
|
_bench.SetRelay(RelayNames.DirectionRight, value);
|
|
_bench.SetRelay(RelayNames.DirectionLeft, !value);
|
|
}
|
|
|
|
/// <summary>Sets the bench rotation direction to clockwise (right).</summary>
|
|
[RelayCommand]
|
|
private void SetDirectionRight() => IsDirectionRight = true;
|
|
|
|
/// <summary>Sets the bench rotation direction to counter-clockwise (left).</summary>
|
|
[RelayCommand]
|
|
private void SetDirectionLeft() => IsDirectionRight = false;
|
|
|
|
// ── Oil pump toggle ───────────────────────────────────────────────────────
|
|
|
|
partial void OnIsOilPumpOnChanged(bool value)
|
|
{
|
|
// Show confirmation dialog when turning oil pump ON (WAcceptOilTurnOn equivalent).
|
|
if (value)
|
|
{
|
|
var vm = new OilPumpConfirmViewModel();
|
|
var dlg = new OilPumpConfirmDialog(vm) { Owner = Application.Current.MainWindow };
|
|
dlg.ShowDialog();
|
|
|
|
if (!vm.Accepted)
|
|
{
|
|
// Revert without re-triggering this handler.
|
|
_isOilPumpOn = false;
|
|
OnPropertyChanged(nameof(IsOilPumpOn));
|
|
return;
|
|
}
|
|
}
|
|
|
|
_bench.SetRelay(RelayNames.OilPump, value);
|
|
|
|
// The dialog's ShowDialog call runs a nested dispatcher message pump.
|
|
// While it was blocking, RefreshFromTick may have fired and written
|
|
// _isOilPumpOn back to the stale relay.State value (false, because
|
|
// SetRelay above hadn't run yet). Re-assert the backing field now
|
|
// that relay.State is committed so IsOilPumpOn reports the correct
|
|
// value to callers downstream (e.g. TestsPageViewModel.StartTestAsync
|
|
// which guards on it right after this setter returns).
|
|
// See docs/gotcha-oil-pump-dialog-race.md.
|
|
if (_isOilPumpOn != value)
|
|
{
|
|
_isOilPumpOn = value;
|
|
OnPropertyChanged(nameof(IsOilPumpOn));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Energises the oil-pump relay and flags <see cref="IsOilPumpOn"/> without
|
|
/// presenting the leak-check confirmation dialog. Used by the Dashboard
|
|
/// "Connect & Auto Test" flow when the operator has opted in via
|
|
/// <see cref="AppSettings.AutoTestSkipsOilPumpConfirm"/>. Writes the backing
|
|
/// field directly to avoid re-entering <see cref="OnIsOilPumpOnChanged"/>.
|
|
/// </summary>
|
|
public void TurnOilPumpOnSilent()
|
|
{
|
|
if (_isOilPumpOn) return;
|
|
_bench.SetRelay(RelayNames.OilPump, true);
|
|
_isOilPumpOn = true;
|
|
OnPropertyChanged(nameof(IsOilPumpOn));
|
|
}
|
|
|
|
// ── RPM commands ──────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Opens the RPM quick-select popup.</summary>
|
|
[RelayCommand]
|
|
private void OpenRpmPopup()
|
|
{
|
|
IsRpmPopupOpen = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the bench motor at the RPM specified in <see cref="RpmInputText"/>.
|
|
/// Shows a safety warning dialog if the oil pump is off.
|
|
/// </summary>
|
|
[RelayCommand]
|
|
private void StartBench()
|
|
{
|
|
if (!int.TryParse(RpmInputText, out int rpm) || rpm <= 0) return;
|
|
|
|
// Safety warning if oil pump is not running (WCareOnRpmOn equivalent).
|
|
if (!IsOilPumpOn)
|
|
{
|
|
var vm = new RpmSafetyWarningViewModel();
|
|
var dlg = new RpmSafetyWarningDialog(vm) { Owner = Application.Current.MainWindow };
|
|
dlg.ShowDialog();
|
|
|
|
switch (vm.Result)
|
|
{
|
|
case RpmSafetyResult.Cancel:
|
|
return;
|
|
case RpmSafetyResult.ProceedWithOil:
|
|
IsOilPumpOn = true;
|
|
break;
|
|
case RpmSafetyResult.ProceedWithoutOil:
|
|
// Operator accepted the risk.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ensure direction relays are set.
|
|
_bench.SetRelay(RelayNames.DirectionRight, IsDirectionRight);
|
|
_bench.SetRelay(RelayNames.DirectionLeft, !IsDirectionRight);
|
|
|
|
_bench.StartRpmPid(rpm);
|
|
IsBenchRunning = true;
|
|
IsRpmPopupOpen = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the bench motor via PID stop and clears direction relays.
|
|
/// </summary>
|
|
[RelayCommand]
|
|
private void StopBench()
|
|
{
|
|
_bench.StopRpmPid();
|
|
_bench.SetRelay(RelayNames.DirectionLeft, false);
|
|
_bench.SetRelay(RelayNames.DirectionRight, false);
|
|
IsBenchRunning = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies the RPM value from <see cref="RpmInputText"/> and starts the bench.
|
|
/// Bound to the inline Apply button in <c>BenchRpmCommandCard</c>.
|
|
/// </summary>
|
|
[RelayCommand]
|
|
private void ApplyRpm() => StartBench();
|
|
|
|
/// <summary>
|
|
/// Quick-select button handler: sets the RPM input and starts the bench.
|
|
/// </summary>
|
|
/// <param name="rpmString">RPM value as a string from the button content.</param>
|
|
[RelayCommand]
|
|
private void SetQuickRpm(string rpmString)
|
|
{
|
|
RpmInputText = rpmString;
|
|
StartBench();
|
|
}
|
|
|
|
// ── Counter commands ──────────────────────────────────────────────────────
|
|
|
|
/// <summary>Toggles the counter popup visibility.</summary>
|
|
[RelayCommand]
|
|
private void ToggleCounterPopup()
|
|
{
|
|
IsCounterPopupOpen = !IsCounterPopupOpen;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends the counter value over CAN and activates the counter relay.
|
|
/// </summary>
|
|
[RelayCommand]
|
|
private void SendCounter()
|
|
{
|
|
if (!int.TryParse(CounterInputText, out int count) || count <= 0) return;
|
|
|
|
// Set the counter parameter value and transmit.
|
|
_bench.SetParameter(BenchParameterNames.Counter, count);
|
|
if (_config.Bench.ParametersByName.TryGetValue(
|
|
BenchParameterNames.Counter, out var counterParam))
|
|
{
|
|
_bench.SendParameters(counterParam.MessageId);
|
|
}
|
|
|
|
// Activate the counter relay.
|
|
_bench.SetRelay(RelayNames.Counter, true);
|
|
}
|
|
|
|
// ── Refresh (called from MainViewModel timer tick) ────────────────────────
|
|
|
|
/// <summary>
|
|
/// Updates live counter readback from CAN, and mirrors the oil-pump relay
|
|
/// state so this VM's <see cref="IsOilPumpOn"/> stays in sync even when the
|
|
/// relay is toggled outside the manual Bench page (e.g. the Dashboard
|
|
/// auto-test orchestrator). Writes through the backing field to avoid
|
|
/// re-triggering the confirmation dialog in <see cref="OnIsOilPumpOnChanged"/>.
|
|
/// Called on the UI thread from <see cref="MainViewModel.OnRefreshTick"/>.
|
|
/// </summary>
|
|
public void RefreshFromTick()
|
|
{
|
|
BenchCounterValue = _bench.ReadBenchParameter(BenchParameterNames.BenchCounter);
|
|
|
|
bool relayOn = _config.Bench.Relays.TryGetValue(RelayNames.OilPump, out var oilRelay) && oilRelay.State;
|
|
if (_isOilPumpOn != relayOn)
|
|
{
|
|
_isOilPumpOn = relayOn;
|
|
OnPropertyChanged(nameof(IsOilPumpOn));
|
|
}
|
|
}
|
|
}
|
|
}
|