feat: redesign dashboard with Fluent KPI tiles, connection strip, and devices column
- Replace LCD-style readings with a 3×2 KPI tile grid (Fluent card surfaces, 52pt values) - Add persistent top connection strip with horizontal chips + pump name badge - Add elapsed test timer (DispatcherTimer, mm:ss) to Test Summary card - Restyle Test Summary and Active Alarms with Fluent brushes/iconography - Add Devices column (CAN / K-Line / Bench tiles) between KPI grid and test/alarms - Enumerates attached PCAN USB channels via PCAN_ATTACHED_CHANNELS API - Enumerates FTDI K-Line adapters via existing FtdiInterface helpers - Click to connect/disconnect; confirmation dialog when session active or test running - Hover tint: blue = will connect, red = will disconnect; Bench row is read-only stub - Extend ICanService with SelectedChannel + EnumerateAttachedChannels() - Expose IKwpService.ConnectedPort for active session device tracking - Add DeviceRow button style with MultiDataTrigger hover colour logic - Add 30+ new localization keys (ES + EN) for KPI labels, devices, confirmations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,9 @@ namespace HC_APTBS.Services.Impl
|
||||
/// <inheritdoc/>
|
||||
public KLineConnectionState KLineState => _kLineState;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? ConnectedPort => _connectedPort;
|
||||
|
||||
// ── Constructor ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <param name="logger">Application logger.</param>
|
||||
@@ -289,7 +292,7 @@ namespace HC_APTBS.Services.Impl
|
||||
Report(85, "Reading fault codes...");
|
||||
kwp.KeepAlive();
|
||||
var faultCodes = kwp.ReadFaultCodes();
|
||||
result[KlineKeys.Errors] = faultCodes.Count > 0
|
||||
result[KlineKeys.Errors] = faultCodes?.Count > 0
|
||||
? string.Join(Environment.NewLine, faultCodes)
|
||||
: KlineKeys.NoErrors;
|
||||
|
||||
@@ -418,7 +421,7 @@ namespace HC_APTBS.Services.Impl
|
||||
Report(85, "Reading fault codes...");
|
||||
kwp.KeepAlive();
|
||||
var faultCodes = kwp.ReadFaultCodes();
|
||||
result[KlineKeys.Errors] = faultCodes.Count > 0
|
||||
result[KlineKeys.Errors] = faultCodes?.Count > 0
|
||||
? string.Join(Environment.NewLine, faultCodes)
|
||||
: KlineKeys.NoErrors;
|
||||
|
||||
@@ -621,14 +624,15 @@ namespace HC_APTBS.Services.Impl
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
_busLock.Wait();
|
||||
try
|
||||
{
|
||||
_log.Info(LogId, "TryFastUnlock: sending unlock command over K-Line");
|
||||
var packets = _sessionKwp.SendCustom(
|
||||
var packets = _sessionKwp!.SendCustom(
|
||||
new List<byte> { 0x02, 0x88, 0x02, 0x03, 0xA8, 0x01, 0x00 });
|
||||
|
||||
bool nak = packets.Count == 1
|
||||
&& packets[0] is HC_APTBS.Infrastructure.Kwp.Packets.NakPacket;
|
||||
&& packets[0] is NakPacket;
|
||||
|
||||
_log.Info(LogId, $"TryFastUnlock: {(nak ? "NAK — pump rejected" : "ACK — pump unlocked")}");
|
||||
return !nak;
|
||||
@@ -638,6 +642,10 @@ namespace HC_APTBS.Services.Impl
|
||||
_log.Warning(LogId, $"TryFastUnlock failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_busLock.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -686,33 +694,33 @@ namespace HC_APTBS.Services.Impl
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
// Non-blocking try-acquire: if an operation holds the lock
|
||||
// we skip this cycle — the operation itself keeps the bus alive.
|
||||
if (await _busLock.WaitAsync(0, ct))
|
||||
{
|
||||
try
|
||||
{
|
||||
_sessionKwp!.KeepAlive();
|
||||
}
|
||||
catch (OperationCanceledException) { return; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(LogId, $"Keep-alive failed: {ex.Message}");
|
||||
CleanupSession();
|
||||
SetState(KLineConnectionState.Failed);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_busLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(KeepAliveIntervalMs, ct);
|
||||
}
|
||||
catch (OperationCanceledException) { return; }
|
||||
|
||||
// Non-blocking try-acquire: if an operation holds the lock
|
||||
// we skip this cycle — the operation itself keeps the bus alive.
|
||||
if (!await _busLock.WaitAsync(0, ct))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
_sessionKwp!.KeepAlive();
|
||||
}
|
||||
catch (OperationCanceledException) { return; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(LogId, $"Keep-alive failed: {ex.Message}");
|
||||
CleanupSession();
|
||||
SetState(KLineConnectionState.Failed);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_busLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,7 +754,7 @@ namespace HC_APTBS.Services.Impl
|
||||
var codes = _sessionKwp.ReadFaultCodes();
|
||||
_sessionKwp.KeepAlive();
|
||||
Report(100, "Done.");
|
||||
return codes.Count > 0
|
||||
return codes?.Count > 0
|
||||
? string.Join(Environment.NewLine, codes)
|
||||
: KlineKeys.NoErrors;
|
||||
}
|
||||
@@ -773,7 +781,7 @@ namespace HC_APTBS.Services.Impl
|
||||
var codes = _sessionKwp.ReadFaultCodes();
|
||||
_sessionKwp.KeepAlive();
|
||||
Report(100, "Done.");
|
||||
return codes.Count > 0
|
||||
return codes?.Count > 0
|
||||
? string.Join(Environment.NewLine, codes)
|
||||
: KlineKeys.NoErrors;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user