Files
HC_APTBS/Services/IUnlockService.cs
LucianoDev 9a593e4ff2 fix: stop UI freeze when selecting a pump that needs immobilizer unlock
Phase 2 TestUnlock handshake was synchronous (Thread.Sleep x 8 = 4 s) and
the continuation after Phase 1 marshalled back to the WPF dispatcher via
the captured SynchronizationContext, so the eight 500 ms sleeps froze the
UI right before unlock completed.

- UnlockService.RunTestUnlockSequence -> async RunTestUnlockSequenceAsync
  with Task.Delay(500, ct) and ConfigureAwait(false)
- Add ConfigureAwait(false) on every internal await in UnlockService so
  continuations no longer hop to the UI thread (Task.WhenAll, Task.Delay,
  connectedTcs, TryFastUnlockAsync, fast-unlock settle delay)
- CancellationToken now propagates through Phase 2, so the snackbar Cancel
  button can interrupt the handshake within 500 ms instead of waiting out
  all eight Thread.Sleeps

Includes the companion observer in IUnlockService / UnlockService
(PumpUnlocked event, IsPumpUnlocked, StartObserver/StopObserver) that
the Phase 1 wait now races against — lets any source of unlock (fast
unlock, external manual, CAN flood finally working) short-circuit the
600 s timer as soon as the CAN TestUnlock parameter confirms it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 14:36:45 +02:00

69 lines
3.0 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using HC_APTBS.Models;
namespace HC_APTBS.Services
{
/// <summary>
/// Manages the immobilizer unlock sequence required by certain pump ECUs
/// (e.g. Ford VP44 models) before they respond to test commands.
/// </summary>
public interface IUnlockService
{
/// <summary>Raised with progress text during the unlock sequence.</summary>
event Action<string>? StatusChanged;
/// <summary>Raised when the unlock sequence completes. Argument is true if successful.</summary>
event Action<bool>? UnlockCompleted;
/// <summary>
/// Raised by the background observer on each lock→unlock transition. Unlike
/// <see cref="UnlockCompleted"/>, this fires as soon as the CAN TestUnlock
/// parameter confirms unlocked — regardless of whether the transition was
/// caused by this service, an external manual unlock, or a fast-unlock
/// completing early. Subscribers must marshal to the UI thread themselves.
/// </summary>
event Action? PumpUnlocked;
/// <summary>
/// Latched state from the background observer. True when the observer has
/// verified the pump is unlocked; false when the observer is not running
/// or the pump is currently locked. Use alongside <see cref="PumpUnlocked"/>
/// to race-guard "was the event already fired?".
/// </summary>
bool IsPumpUnlocked { get; }
/// <summary>
/// Starts a 1-second polling observer that watches the CAN TestUnlock
/// parameter and raises <see cref="PumpUnlocked"/> on each lock→unlock
/// transition. Idempotent: stops any prior observer before starting the
/// new one. No-op when <see cref="PumpDefinition.UnlockType"/> is 0.
/// </summary>
void StartObserver(PumpDefinition pump);
/// <summary>
/// Stops the polling observer. Safe to call when no observer is active.
/// Also resets <see cref="IsPumpUnlocked"/> to false.
/// </summary>
void StopObserver();
/// <summary>
/// Runs the immobilizer unlock sequence for the given pump.
/// Returns immediately if <see cref="PumpDefinition.UnlockType"/> is 0 (no unlock needed).
/// The persistent CAN senders remain active after this method returns;
/// call <see cref="StopSenders"/> when the pump is deselected.
/// </summary>
/// <param name="pump">Pump definition with unlock type and CAN parameters.</param>
/// <param name="ct">Cancellation token to abort the unlock sequence.</param>
Task UnlockAsync(PumpDefinition pump, CancellationToken ct);
/// <summary>
/// Stops the persistent CAN unlock senders. Call this when the pump is
/// deselected or the application is shutting down. Safe to call when no
/// senders are active.
/// </summary>
void StopSenders();
}
}