diff --git a/App.xaml b/App.xaml
index 8ea1286..3a364b7 100644
--- a/App.xaml
+++ b/App.xaml
@@ -3,5 +3,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HC_APTBS">
+
+
+
+
+
diff --git a/App.xaml.cs b/App.xaml.cs
index aff240e..d2f826c 100644
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -69,6 +69,7 @@ public partial class App : Application
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
// ── ViewModels ────────────────────────────────────────────────────────
services.AddSingleton();
diff --git a/CLAUDE.md b/CLAUDE.md
index 691efc8..16d723c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -83,11 +83,14 @@ ViewModels/ — [ObservableProperty] / [RelayCommand], no UI lo
FlowmeterChartViewModel.cs — Real-time flowmeter charts
AngleDisplayViewModel.cs — Encoder angle monitoring (PSG, INJ, Manual, Lock Angle)
StatusDisplayViewModel.cs — Pump status word display
- Dialogs/ — KlineErrors, Progress, Report, UserCheck ViewModels
+ Dialogs/ — KlineErrors, OilPumpConfirm, Progress, Report, RpmSafetyWarning,
+ Settings, UnlockProgress, UserCheck, VoltageWarning ViewModels
Views/
MainWindow.xaml — Root UI (multi-panel layout)
- Dialogs/ — KlineErrorsDialog, ProgressDialog, ReportDialog, UserCheckDialog
+ Dialogs/ — KlineErrorsDialog, OilPumpConfirmDialog, ProgressDialog,
+ ReportDialog, RpmSafetyWarningDialog, SettingsDialog,
+ UnlockProgressDialog, UserCheckDialog, VoltageWarningDialog
UserControls/ — AngleDisplay, BenchParamConfig, DfiManage, FlowmeterChart,
PumpControl, PumpIdentification, ResultDisplay, StatusDisplay,
TestDisplay, TestPanel
@@ -147,14 +150,11 @@ See `docs/` guidelines for full specs. Priority: CRITICAL > HIGH > MEDIUM > LOW.
**HIGH — Missing safety features:**
- No QOver zero-flow safety check (old: emergency stop if QOver==0 while RPM>300 + oil pump on)
-- No safety dialogs: 27V warning, oil pump confirmation, RPM warning
- Alarm bit collection during tests not wired up (`PhaseDefinition.RecordErrorBit` never called)
- No per-sample real-time UI callback during measurement (old fired per-sample events for live charts)
- Pump parameters (ME/FBKW/PreIn) not zeroed between test phases
**HIGH — Missing features:**
-- Ford unlock progress UI (service exists, no View — old had WUnlocker.xaml with visual ring + progress bar)
-- No localization system (old had Spanish/English resource dictionaries with runtime switching)
- No encryption (old encrypted user passwords with AES-256 + pump data with Rijndael; new stores plaintext)
- No KlineIDs auto-mapping (old remembered K-Line ID → pump ID associations)
diff --git a/MainWindow.xaml b/MainWindow.xaml
index 32cf193..d51d82f 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -7,7 +7,7 @@
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:models="clr-namespace:HC_APTBS.Models"
mc:Ignorable="d"
- Title="HC_APTBS — Herlic Test Bench"
+ Title="{DynamicResource App.Title}"
Height="1080" Width="1920"
WindowState="Maximized"
WindowStartupLocation="CenterScreen"
@@ -68,7 +68,8 @@
@@ -105,7 +106,7 @@
-
+
@@ -126,7 +127,7 @@
-
@@ -139,7 +140,7 @@
-
@@ -153,7 +154,7 @@
-
@@ -167,7 +168,7 @@
-
@@ -184,7 +185,7 @@
-
@@ -192,9 +193,9 @@
-
-
@@ -222,14 +223,14 @@
HorizontalAlignment="Right" VerticalAlignment="Center"
FontFamily="Consolas"/>
-
+
-
+
-
+
@@ -253,12 +254,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -288,12 +289,12 @@
-
-
+
+
-
-
+
+
@@ -310,10 +311,10 @@
-
-
-
-
+
+
+
+
@@ -323,7 +324,7 @@
-
+
@@ -334,15 +335,15 @@
-
+
@@ -350,18 +351,18 @@
-
-
+
-
+
-
@@ -384,21 +385,21 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Views/Dialogs/UnlockProgressDialog.xaml.cs b/Views/Dialogs/UnlockProgressDialog.xaml.cs
new file mode 100644
index 0000000..eb7c181
--- /dev/null
+++ b/Views/Dialogs/UnlockProgressDialog.xaml.cs
@@ -0,0 +1,49 @@
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Input;
+using HC_APTBS.ViewModels.Dialogs;
+
+namespace HC_APTBS.Views.Dialogs
+{
+ ///
+ /// Non-modal window showing immobilizer unlock progress.
+ /// Prevents user-initiated closing until the unlock sequence completes;
+ /// programmatic close via always succeeds.
+ /// Equivalent to the old WUnlocker window.
+ ///
+ public partial class UnlockProgressDialog : Window
+ {
+ private bool _forceClose;
+
+ /// Creates the dialog and wires the ViewModel.
+ public UnlockProgressDialog(UnlockProgressViewModel vm)
+ {
+ InitializeComponent();
+ DataContext = vm;
+ vm.RequestClose += ForceClose;
+ }
+
+ /// Closes the window unconditionally (bypasses the completion guard).
+ public void ForceClose()
+ {
+ _forceClose = true;
+ Close();
+ }
+
+ /// Allows dragging the borderless window by clicking anywhere.
+ private void OnMouseDrag(object sender, MouseButtonEventArgs e)
+ {
+ if (e.ChangedButton == MouseButton.Left)
+ DragMove();
+ }
+
+ /// Prevents user-initiated closing while the unlock sequence is still running.
+ private void OnWindowClosing(object? sender, CancelEventArgs e)
+ {
+ if (_forceClose) return;
+
+ if (DataContext is UnlockProgressViewModel vm && !vm.IsComplete)
+ e.Cancel = true;
+ }
+ }
+}
diff --git a/Views/Dialogs/UserCheckDialog.xaml b/Views/Dialogs/UserCheckDialog.xaml
index 7de5b68..aa1a6ea 100644
--- a/Views/Dialogs/UserCheckDialog.xaml
+++ b/Views/Dialogs/UserCheckDialog.xaml
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
- Title="User Authentication"
+ Title="{DynamicResource Dialog.UserCheck.Title}"
Height="170" Width="420"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
@@ -21,12 +21,12 @@
-
+
-
+
@@ -34,9 +34,9 @@
-
-
diff --git a/Views/Dialogs/VoltageWarningDialog.xaml b/Views/Dialogs/VoltageWarningDialog.xaml
new file mode 100644
index 0000000..fc24b3a
--- /dev/null
+++ b/Views/Dialogs/VoltageWarningDialog.xaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Views/Dialogs/VoltageWarningDialog.xaml.cs b/Views/Dialogs/VoltageWarningDialog.xaml.cs
new file mode 100644
index 0000000..95908fd
--- /dev/null
+++ b/Views/Dialogs/VoltageWarningDialog.xaml.cs
@@ -0,0 +1,20 @@
+using System.Windows;
+using HC_APTBS.ViewModels.Dialogs;
+
+namespace HC_APTBS.Views.Dialogs
+{
+ ///
+ /// Informational dialog warning the operator to switch the power supply
+ /// voltage before testing a pump. Equivalent to the old WAlert27v.
+ ///
+ public partial class VoltageWarningDialog : Window
+ {
+ /// Creates the dialog and wires the ViewModel.
+ public VoltageWarningDialog(VoltageWarningViewModel vm)
+ {
+ InitializeComponent();
+ DataContext = vm;
+ vm.RequestClose += Close;
+ }
+ }
+}
diff --git a/Views/UserControls/AngleDisplayView.xaml b/Views/UserControls/AngleDisplayView.xaml
index 4aa8bf1..a9bd0b4 100644
--- a/Views/UserControls/AngleDisplayView.xaml
+++ b/Views/UserControls/AngleDisplayView.xaml
@@ -19,7 +19,7 @@
-
@@ -32,10 +32,10 @@
+ ToolTip="{DynamicResource Angle.SetPsgZero}"/>
-
@@ -56,10 +56,10 @@
+ ToolTip="{DynamicResource Angle.SetInjZero}"/>
-
@@ -105,7 +105,7 @@
-
-
-
+
-
+
-
+
-
+
-
diff --git a/Views/UserControls/DfiManageView.xaml b/Views/UserControls/DfiManageView.xaml
index 9886bbc..8b57a6a 100644
--- a/Views/UserControls/DfiManageView.xaml
+++ b/Views/UserControls/DfiManageView.xaml
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
- d:DesignHeight="150" d:DesignWidth="800" MaxHeight="150">
+ d:DesignHeight="150" d:DesignWidth="460" MaxHeight="150">
-
+
-
-
+
+
-
@@ -48,40 +48,51 @@
-
+
+
+
-
-
-
+
-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Views/UserControls/PumpControlView.xaml b/Views/UserControls/PumpControlView.xaml
index 6031842..1649bb8 100644
--- a/Views/UserControls/PumpControlView.xaml
+++ b/Views/UserControls/PumpControlView.xaml
@@ -36,7 +36,7 @@
-
@@ -45,7 +45,7 @@
Width="28" Height="28" Margin="0,0,4,0"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="14"
- ToolTip="Min / Step / Max"/>
+ ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
-
+
-
+
-
+
@@ -92,7 +92,7 @@
-
@@ -100,7 +100,7 @@
Width="28" Height="28" Margin="0,0,4,0"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="14"
- ToolTip="Min / Step / Max"/>
+ ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
-
+
-
+
-
+
@@ -145,7 +145,7 @@
-
@@ -153,7 +153,7 @@
Width="28" Height="28" Margin="0,0,4,0"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="14"
- ToolTip="Min / Step / Max"/>
+ ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
-
+
-
+
-
+
diff --git a/Views/UserControls/PumpIdentificationView.xaml b/Views/UserControls/PumpIdentificationView.xaml
index 6a4ac68..f4eee64 100644
--- a/Views/UserControls/PumpIdentificationView.xaml
+++ b/Views/UserControls/PumpIdentificationView.xaml
@@ -15,7 +15,7 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -86,15 +86,15 @@
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/Views/UserControls/TestPanelView.xaml b/Views/UserControls/TestPanelView.xaml
index 55bd541..59f0d0d 100644
--- a/Views/UserControls/TestPanelView.xaml
+++ b/Views/UserControls/TestPanelView.xaml
@@ -168,7 +168,7 @@
Margin="16,0,0,0"/>
-
@@ -178,13 +178,13 @@
Margin="0,3,0,0">
-
-
@@ -265,17 +265,17 @@
-
+
-
+
-
+
@@ -323,17 +323,17 @@
-
-
-
@@ -353,14 +353,14 @@
-
+
@@ -368,7 +368,7 @@
Foreground="DimGray" FontSize="12" Margin="0,0,4,0">
-
+
diff --git a/docs/gap-ford-unlock-ui.md b/docs/gap-ford-unlock-ui.md
index 8be177d..78a2d64 100644
--- a/docs/gap-ford-unlock-ui.md
+++ b/docs/gap-ford-unlock-ui.md
@@ -1,50 +1,37 @@
-# Gap: Ford Unlock Progress UI
+# Gap: Ford Unlock Progress UI — RESOLVED
-## Problem
-The `UnlockService` backend is fully functional (Phase 1 + Phase 2 + verification), but there is no dedicated UI for displaying unlock progress. The old app had `WUnlocker.xaml` — a modal dialog with a visual progress ring and status text.
+## Status: Implemented
-## Current State
-- `UnlockService.StatusChanged` fires every 1000ms with `"Unlocking... {pct}% ({MM:SS})"`
-- `UnlockService.UnlockCompleted` fires once with `true`/`false`
-- `MainViewModel` subscribes and pipes status into `VerboseStatus` (displayed as plain text somewhere in MainWindow)
-- No progress bar, no percentage display, no cancel button, no dedicated dialog
+The unlock progress dialog, service refactoring, and K-Line fast unlock are fully implemented.
-## Old UI Reference (`WUnlocker.xaml`)
-- Standalone modal `Window` (300x400px), dark background (#FF2B2929), Topmost, centered on owner
-- Decorative `Ellipse` ring (200x200, #4D4D4D stroke, 10px thick) as the focal point
-- Inside the ring: large percentage (Courier New 60pt), "P R O G R E S S" label, elapsed time (MM:SS)
-- `LBLState` at top: live lock/immo status from CAN feedback ("Bloqueada/Desbloqueada")
-- `LBLVerbose` at bottom: phase description ("Unlocking...", "Testing...", "Sending")
-- "Cerrar" (Close) button disabled until progress reaches 100%
-- Window close prevented via `OnWindowClosing` until completion
+## What was done
-## Spec for New Implementation
+### 1. UnlockProgressDialog (View + ViewModel)
+- **`Views/Dialogs/UnlockProgressDialog.xaml`** — Dark-themed non-modal window (#2B2929), borderless with drag support, 200x200 ellipse progress ring, Courier New 60pt percentage, MM:SS elapsed time, phase indicator, linear progress bar, result text (green/red), Cancel + Close buttons
+- **`Views/Dialogs/UnlockProgressDialog.xaml.cs`** — `ForceClose()` for programmatic close, `OnWindowClosing` prevents user close until `IsComplete`, `OnMouseDrag` for window dragging
+- **`ViewModels/Dialogs/UnlockProgressViewModel.cs`** — `ObservableObject` + `IDisposable`, parses `StatusChanged` events via regex, marshals to UI thread, `CancelCommand` (Phase 1 only) / `CloseCommand` (after completion), `[NotifyCanExecuteChangedFor]` wiring
-### UnlockDialog.xaml (View)
-- Modal dialog (MVVM, no code-behind logic)
-- Progress bar (0-100%) + percentage text
-- Elapsed time display (MM:SS)
-- Phase indicator: "Phase 1: Sending unlock signals" / "Phase 2: Testing" / "Verifying..."
-- Current unlock type indicator (Type 1 / Type 2)
-- Cancel button (disabled during Phase 2 — it cannot be cancelled once started)
-- Close button (enabled only after completion)
-- Result indicator: green checkmark (success) / red X (failed)
+### 2. UnlockService rewrite (`Services/Impl/UnlockService.cs`)
+- **Persistent CAN senders** — Start before unlock, run indefinitely until `StopSenders()` is called on pump deselection. Prevents pump from re-locking after unlock.
+- **Concurrent fast unlock** — While the 600s CAN wait runs with progress reporting, a parallel task awaits K-Line session Connected state, then:
+ 1. Checks if pump is already unlocked (via `VerifyUnlock`)
+ 2. Sends K-Line fast unlock command (`{0x02, 0x88, 0x02, 0x03, 0xA8, 0x01, 0x00}`) which writes to pump RAM to fast-forward the internal 10 min timer
+ 3. Waits 2s, then re-checks `VerifyUnlock`
+ 4. If verified → cancels remaining wait, proceeds to Phase 2 immediately
+- **`IUnlockService.StopSenders()`** — New interface method, called from `MainViewModel.CloseUnlockDialog()` on pump change
-### UnlockViewModel.cs (ViewModel)
-- `[ObservableProperty] double Progress`
-- `[ObservableProperty] string ElapsedTime`
-- `[ObservableProperty] string Phase`
-- `[ObservableProperty] string Result`
-- `[ObservableProperty] bool IsComplete`
-- `[ObservableProperty] bool CanCancel`
-- `[RelayCommand] Cancel()` — calls `CancellationTokenSource.Cancel()`
-- Subscribe to `IUnlockService.StatusChanged` — parse percentage from status string
-- Subscribe to `IUnlockService.UnlockCompleted` — set result and enable close
+### 3. K-Line fast unlock support
+- **`IKwpService.TryFastUnlockAsync()`** — New interface method
+- **`KwpService.TryFastUnlockAsync()`** — Sends custom command over active session, returns true if no NAK (command accepted, not unlock confirmation)
-### Integration
-- Trigger: button in MainViewModel (currently exists but needs to open the dialog)
-- The dialog should be shown via a dialog service or `Window.ShowDialog()` from MainViewModel
-- Marshal all event handlers to UI thread
+### 4. MainViewModel integration
+- **Trigger on pump selection** — `OnPumpChanged()` calls `StartUnlockIfRequired()` for pumps with `UnlockType != 0` (both manual and K-Line auto-detect)
+- **Non-modal window** — `.Show()` instead of `.ShowDialog()`, user can interact with main UI during 10 min unlock
+- **Test start guards** — `StartTestAsync` blocks if unlock is in progress or failed
+- **Cleanup on pump change** — `CloseUnlockDialog()` stops senders, cancels CTS, disposes ViewModel, force-closes window
+
+### 5. Bug fix: Type 1 verification
+**Fixed.** Old code: `Lock = valor != 0` (non-zero = LOCKED). New code had `return Value != 0` (non-zero = SUCCESS). Changed to `return Value == 0`. Type 2 (`== 0xE4`) was already correct.
## Protocol Reference
@@ -54,7 +41,7 @@ The `UnlockService` backend is fully functional (Phase 1 + Phase 2 + verificatio
| Msg1 | 0x700 | `B2 00 00 00 00 00 00 00` | 500 ms |
| Msg2 | 0x300 | `01 48 50 C3 00 00 00 00` | 50 ms |
| TestUnlock states | 0x700 | `B2`, `B6`, `23`, `24` (byte[0]) x2 | 500 ms each |
-| Verify | TestUnlock param | Success when value != 0 | One-shot |
+| Verify | TestUnlock param | Success when value == 0 | One-shot |
### Type 2 (CAN IDs 0x700 + 0x500)
| Phase | ID | Data | Interval |
@@ -64,11 +51,21 @@ The `UnlockService` backend is fully functional (Phase 1 + Phase 2 + verificatio
| TestUnlock states | 0x700 | `B2`, `24`, `24`, `24` (byte[3]) x2 | 500 ms each |
| Verify | TestUnlock param | Success when value == 0xE4 | One-shot |
+### K-Line fast unlock (timer shortcut)
+| Command | `02 88 02 03 A8 01 00` |
+|---------|------------------------|
+| Effect | Writes pump RAM to fast-forward the internal 10 min timer |
+| Prerequisite | Active K-Line session + CAN flood senders already running |
+| ACK meaning | Command accepted (NOT unlock confirmed — must still verify via CAN TestUnlock) |
+
### Duration
-Phase 1: 600,500 ms (10 min 0.5 sec). Phase 2: ~4 sec (8 messages x 500ms). Total: ~604.5 sec.
+- Phase 1 (normal): 600,500 ms (10 min 0.5 sec)
+- Phase 1 (fast unlock): ~2 sec after K-Line ACK (+ K-Line read time)
+- Phase 2: ~4 sec (8 messages x 500 ms)
+- CAN senders: persist until pump deselection
-## Known Issue in Unlock Verification
-The **Type 1 verification logic may be inverted** compared to the old code. Old: `Lock = (valor != 0)` meant non-zero = LOCKED. New: `Value != 0` returned as SUCCESS (unlocked). Needs hardware testing to confirm which is correct.
+### Critical: CAN sender lifecycle
+CAN flood messages must be active **before** the fast unlock attempt (otherwise the timer resets instantly) and must **continue running after** unlock completes. Stopping them causes the pump to re-lock. `StopSenders()` is only called when the pump is deselected.
-## Missing Feature: TestImmo Check
-Old code tracked both `TestUnlock` and `TestImmo` CAN parameters and displayed combined status. New code only checks `TestUnlock`, ignoring `TestImmo` entirely. Consider adding the immobilizer state check for completeness.
+## Remaining gap: TestImmo check
+Old code tracked both `TestUnlock` and `TestImmo` CAN parameters and displayed combined status ("Inmovilizada/Liberada | Bloqueada/Desbloqueada"). New code only checks `TestUnlock`. Consider adding immobilizer state display for completeness.