Files
HC_APTBS/ViewModels/DfiManageViewModel.cs
LucianoDev 37d099cdbd feat: add Ford VP44 unlock progress dialog, K-Line fast unlock, localization, safety dialogs, and settings
Unlock progress UI:
- UnlockProgressDialog with dark-themed progress ring, phase indicator, elapsed
  time, and cancel/close buttons (non-modal, draggable borderless window)
- UnlockProgressViewModel with event-driven progress tracking via IUnlockService
- Triggers on pump selection (manual or K-Line auto-detect), not test start

UnlockService rewrite:
- Persistent CAN senders that outlive the unlock sequence (StopSenders on pump change)
- Concurrent K-Line fast unlock: awaits session Connected, sends RAM timer shortcut
  ({02 88 02 03 A8 01 00}), verifies via CAN TestUnlock before skipping wait
- Fix Type 1 verification (Value == 0 means unlocked, was inverted)

K-Line fast unlock support:
- IKwpService.TryFastUnlockAsync / KwpService implementation

Additional features:
- ILocalizationService with ES/EN resource dictionaries and runtime switching
- Safety dialogs: VoltageWarning, OilPumpConfirm, RpmSafetyWarning
- SettingsDialog for app configuration
- BenchService enhancements, ConfigurationService improvements, PDF report updates
- All UI strings localized via DynamicResource

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 13:22:48 +02:00

185 lines
7.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HC_APTBS.Services;
namespace HC_APTBS.ViewModels
{
/// <summary>
/// ViewModel for the DFIManageDisplay user control.
///
/// <para>
/// Exposes the current DFI (injection timing offset) value, a slider for manual
/// adjustment, KWP version selection, and commands to read and write DFI via K-Line.
/// Auto-mode is respected by the bench service when executing a DFI test phase.
/// </para>
/// </summary>
public sealed partial class DfiManageViewModel : ObservableObject
{
// ── Services ──────────────────────────────────────────────────────────────
private readonly IKwpService _kwp;
private readonly IConfigurationService _config;
private readonly ILocalizationService _loc;
private const string LogId = "DfiManageViewModel";
// ── Constructor ───────────────────────────────────────────────────────────
/// <summary>Initialises the ViewModel with the required services.</summary>
public DfiManageViewModel(IKwpService kwpService, IConfigurationService configService, ILocalizationService loc)
{
_kwp = kwpService;
_config = configService;
_loc = loc;
// Update the slider and LCD display in real time when the DFI is
// read during a full K-Line read (PumpIdentificationViewModel flow).
_kwp.DfiRead += (dfi) => SetDfi(dfi);
}
// ── DFI display ───────────────────────────────────────────────────────────
/// <summary>Current DFI value read from the ECU (displayed in the label).</summary>
[ObservableProperty] private double _currentDfi;
/// <summary>
/// Slider position in integer hundredths of a DFI unit (range 300 to 300).
/// Divide by 100 to get the actual DFI value.
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(SliderDfiValue))]
private int _sliderRaw;
/// <summary>The DFI value represented by the current slider position.</summary>
public double SliderDfiValue => Math.Round(SliderRaw / 100.0, 2);
// ── Options ───────────────────────────────────────────────────────────────
/// <summary>
/// KWP protocol version index (0, 1, or 2) used when writing DFI.
/// Corresponds to the three known VP44 authentication variants.
/// </summary>
[ObservableProperty] private int _versionIndex = 1;
/// <summary>
/// When <see langword="true"/> the bench service may perform automatic DFI
/// correction during test phases; when <see langword="false"/> it skips them.
/// </summary>
[ObservableProperty] private bool _isAutoMode = true;
// ── Operation state ───────────────────────────────────────────────────────
/// <summary>True while a read or write K-Line operation is in progress.</summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ReadDfiCommand))]
[NotifyCanExecuteChangedFor(nameof(WriteDfiCommand))]
private bool _isBusy;
/// <summary>Progress percentage (0100) for the current K-Line operation.</summary>
[ObservableProperty] private int _progressPercent;
/// <summary>Verbose status message for the current K-Line operation.</summary>
[ObservableProperty] private string _progressMessage = string.Empty;
// ── Public API ────────────────────────────────────────────────────────────
/// <summary>
/// Sets the displayed DFI value and repositions the slider to match.
/// Safe to call from any thread.
/// </summary>
public void SetDfi(double dfi)
{
double rounded = Math.Round(dfi, 2);
Application.Current.Dispatcher.Invoke(() =>
{
CurrentDfi = rounded;
SliderRaw = (int)(rounded * 100);
});
}
// ── Commands ──────────────────────────────────────────────────────────────
/// <summary>Reads the current DFI value from the ECU over K-Line.</summary>
[RelayCommand(CanExecute = nameof(CanOperate))]
private async Task ReadDfiAsync()
{
string? port = _kwp.DetectKLinePort();
if (string.IsNullOrEmpty(port))
{
MessageBox.Show(_loc.GetString("Error.KLineNotFound"),
_loc.GetString("Error.KLineTitle"), MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
IsBusy = true;
_kwp.ProgressChanged += OnProgress;
try
{
string dfiStr = await _kwp.ReadDfiAsync(port);
if (double.TryParse(dfiStr,
System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture,
out double val))
SetDfi(val);
}
finally
{
_kwp.ProgressChanged -= OnProgress;
IsBusy = false;
ProgressPercent = 0;
ProgressMessage = string.Empty;
}
}
/// <summary>Writes the slider DFI value to the ECU and reads back the result.</summary>
[RelayCommand(CanExecute = nameof(CanOperate))]
private async Task WriteDfiAsync()
{
string? port = _kwp.DetectKLinePort();
if (string.IsNullOrEmpty(port))
{
MessageBox.Show(_loc.GetString("Error.KLineNotFound"),
_loc.GetString("Error.KLineTitle"), MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
float value = (float)SliderDfiValue;
int version = VersionIndex;
IsBusy = true;
_kwp.ProgressChanged += OnProgress;
try
{
string dfiStr = await _kwp.WriteDfiAsync(port, value, version);
if (double.TryParse(dfiStr,
System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture,
out double val))
SetDfi(val);
}
finally
{
_kwp.ProgressChanged -= OnProgress;
IsBusy = false;
ProgressPercent = 0;
ProgressMessage = string.Empty;
}
}
private bool CanOperate() => !IsBusy;
// ── Helpers ───────────────────────────────────────────────────────────────
private void OnProgress(int pct, string msg)
{
Application.Current.Dispatcher.Invoke(() =>
{
ProgressPercent = pct;
ProgressMessage = msg;
});
}
}
}