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>
143 lines
5.8 KiB
C#
143 lines
5.8 KiB
C#
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using HC_APTBS.Models;
|
|
using HC_APTBS.Services;
|
|
|
|
namespace HC_APTBS.ViewModels.Pages
|
|
{
|
|
/// <summary>
|
|
/// Developer Tools — saved KWP custom commands library. Wraps the persistence
|
|
/// surface in <see cref="IConfigurationService"/>, exposes commands to send the
|
|
/// selected entry, save the parent's current hex input as a new entry, and
|
|
/// delete entries.
|
|
///
|
|
/// <para>Compiled into Debug builds only — see <c>HC_APTBS.csproj</c>.</para>
|
|
/// </summary>
|
|
public sealed partial class DeveloperToolsCommandsViewModel : ObservableObject
|
|
{
|
|
private readonly DeveloperPageViewModel _parent;
|
|
private readonly IKwpService _kwp;
|
|
private readonly IConfigurationService _config;
|
|
private readonly IAppLogger _log;
|
|
private const string LogId = nameof(DeveloperToolsCommandsViewModel);
|
|
|
|
public DeveloperToolsCommandsViewModel(
|
|
DeveloperPageViewModel parent,
|
|
IKwpService kwp,
|
|
IConfigurationService config,
|
|
IAppLogger log)
|
|
{
|
|
_parent = parent;
|
|
_kwp = kwp;
|
|
_config = config;
|
|
_log = log;
|
|
}
|
|
|
|
/// <summary>Persistent collection from <see cref="IConfigurationService.CustomCommands"/>.</summary>
|
|
public ObservableCollection<CustomCommand> Items => _config.CustomCommands;
|
|
|
|
/// <summary>Currently selected list entry. Drives Send / Delete enable state.</summary>
|
|
[ObservableProperty]
|
|
[NotifyCanExecuteChangedFor(nameof(SendSelectedCommand))]
|
|
[NotifyCanExecuteChangedFor(nameof(DeleteSelectedCommand))]
|
|
private CustomCommand? _selected;
|
|
|
|
/// <summary>Name typed into the "Save current as…" input.</summary>
|
|
[ObservableProperty]
|
|
[NotifyCanExecuteChangedFor(nameof(SaveCurrentCommand))]
|
|
private string _newName = string.Empty;
|
|
|
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Sends the selected library entry over the persistent K-Line session.</summary>
|
|
[RelayCommand(CanExecute = nameof(CanSendSelected))]
|
|
private async Task SendSelectedAsync()
|
|
{
|
|
if (Selected is null) return;
|
|
|
|
if (!DeveloperPageViewModel.TryParseHex(Selected.HexBytes, out var bytes, out var error))
|
|
{
|
|
_parent.SetStatus($"'{Selected.Name}' has invalid hex: {error}");
|
|
return;
|
|
}
|
|
|
|
var hex = DeveloperPageViewModel.FormatHex(bytes);
|
|
_parent.AppendLog(DeveloperLogDirection.Tx, $"[{Selected.Name}] {hex}");
|
|
_parent.SetStatus($"Sending '{Selected.Name}' ({bytes.Length} byte(s))…");
|
|
|
|
try
|
|
{
|
|
var responses = await _kwp.SendRawCustomAsync(bytes, CancellationToken.None);
|
|
if (responses.Count == 0)
|
|
{
|
|
_parent.AppendLog(DeveloperLogDirection.Info, "(no response)");
|
|
_parent.SetStatus("No response packets.");
|
|
return;
|
|
}
|
|
foreach (var pkt in responses)
|
|
_parent.AppendLog(DeveloperLogDirection.Rx, DeveloperPageViewModel.FormatHex(pkt));
|
|
_parent.SetStatus($"Received {responses.Count} packet(s).");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_parent.AppendLog(DeveloperLogDirection.Info, $"ERROR: {ex.Message}");
|
|
_parent.SetStatus($"Send failed: {ex.Message}");
|
|
_log.Warning(LogId, $"SendSelected failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private bool CanSendSelected() => Selected is not null;
|
|
|
|
/// <summary>
|
|
/// Saves the parent VM's current <c>HexInput</c> as a new entry under
|
|
/// <see cref="NewName"/>. Validates that hex parses before persisting.
|
|
/// </summary>
|
|
[RelayCommand(CanExecute = nameof(CanSaveCurrent))]
|
|
private void SaveCurrent()
|
|
{
|
|
var name = (NewName ?? string.Empty).Trim();
|
|
if (name.Length == 0)
|
|
{
|
|
_parent.SetStatus("Enter a name before saving the current command.");
|
|
return;
|
|
}
|
|
|
|
var hex = (_parent.HexInput ?? string.Empty).Trim();
|
|
if (!DeveloperPageViewModel.TryParseHex(hex, out var bytes, out var error))
|
|
{
|
|
_parent.SetStatus($"Cannot save '{name}': {error}");
|
|
return;
|
|
}
|
|
|
|
var normalized = DeveloperPageViewModel.FormatHex(bytes);
|
|
Items.Add(new CustomCommand { Name = name, HexBytes = normalized });
|
|
_config.SaveCustomCommands();
|
|
|
|
_parent.AppendLog(DeveloperLogDirection.Info, $"SAVED command '{name}' = {normalized}");
|
|
_parent.SetStatus($"Saved '{name}'.");
|
|
NewName = string.Empty;
|
|
}
|
|
|
|
private bool CanSaveCurrent() => !string.IsNullOrWhiteSpace(NewName);
|
|
|
|
/// <summary>Removes the selected entry from the library and persists.</summary>
|
|
[RelayCommand(CanExecute = nameof(CanDeleteSelected))]
|
|
private void DeleteSelected()
|
|
{
|
|
if (Selected is null) return;
|
|
var name = Selected.Name;
|
|
Items.Remove(Selected);
|
|
_config.SaveCustomCommands();
|
|
_parent.AppendLog(DeveloperLogDirection.Info, $"DELETED command '{name}'");
|
|
_parent.SetStatus($"Deleted '{name}'.");
|
|
Selected = null;
|
|
}
|
|
|
|
private bool CanDeleteSelected() => Selected is not null;
|
|
}
|
|
}
|