feat: restore bench section UI with controls, PID RPM ramp, flowmeter charts, and fix CAN IDs
Restore the full bench control panel from the old source with MVVM architecture: - Two-column left panel layout: bench info displays (RPM with target/voltage, temps, pressures, Q-flow, pump live values) and user commands (direction toggle, start/stop with RPM popup and quick-select buttons, oil pump toggle, turn downcounter with CAN send) - PID RPM ramp controller (BenchPidController) with bumpless startup, anti-windup, and derivative-on-measurement for smooth motor speed transitions - Real-time flowmeter charts (LiveChartsCore) for Q-Delivery and Q-Over with tolerance band overlays - Bench/pump CAN liveness detection in PcanAdapter (receive-only IDs) - K-Line connection status indicator (placeholder) - Periodic relay bitmask sender (~21ms) and ElectronicMsg keepalive start on CAN connect, pump sender starts immediately on pump load Fix critical CAN message ID bug: default bench XML values were incorrectly converted from old source (decimal-notation hex parsed as actual hex digits, e.g. "10" -> "A" instead of keeping "10" which parses as 0x10). Corrected all IDs to match hardware: 0x10, 0x11, 0x13, 0x14, 0x15, 0x50, 0x51, 0x55. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
193
ViewModels/BenchControlViewModel.cs
Normal file
193
ViewModels/BenchControlViewModel.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using HC_APTBS.Models;
|
||||
using HC_APTBS.Services;
|
||||
|
||||
namespace HC_APTBS.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel for manual bench controls: direction toggle, RPM start/stop with
|
||||
/// PID ramp, oil pump toggle, and turn downcounter.
|
||||
/// Created by <see cref="MainViewModel"/> as a child ViewModel.
|
||||
/// </summary>
|
||||
public sealed partial class BenchControlViewModel : ObservableObject
|
||||
{
|
||||
private readonly IBenchService _bench;
|
||||
private readonly IConfigurationService _config;
|
||||
|
||||
// ── Direction ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True when the bench rotates clockwise (right). False for counter-clockwise (left).</summary>
|
||||
[ObservableProperty] private bool _isDirectionRight = true;
|
||||
|
||||
// ── Oil pump ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True when the oil pump relay is energised.</summary>
|
||||
[ObservableProperty] private bool _isOilPumpOn;
|
||||
|
||||
// ── RPM control ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True when the bench motor is running (last RPM command > 0).</summary>
|
||||
[ObservableProperty] private bool _isBenchRunning;
|
||||
|
||||
/// <summary>Controls the RPM quick-select popup visibility.</summary>
|
||||
[ObservableProperty] private bool _isRpmPopupOpen;
|
||||
|
||||
/// <summary>Text in the RPM input field.</summary>
|
||||
[ObservableProperty] private string _rpmInputText = string.Empty;
|
||||
|
||||
/// <summary>Last RPM setpoint commanded via PID or direct set.</summary>
|
||||
[ObservableProperty] private double _targetRpm;
|
||||
|
||||
/// <summary>Last voltage sent to the motor CAN parameter.</summary>
|
||||
[ObservableProperty] private double _commandVoltage;
|
||||
|
||||
// ── Counter ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Controls the counter popup visibility.</summary>
|
||||
[ObservableProperty] private bool _isCounterPopupOpen;
|
||||
|
||||
/// <summary>Text in the counter input field.</summary>
|
||||
[ObservableProperty] private string _counterInputText = string.Empty;
|
||||
|
||||
/// <summary>Live counter count-down value read from CAN.</summary>
|
||||
[ObservableProperty] private double _benchCounterValue;
|
||||
|
||||
// ── Constructor ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Creates the bench control ViewModel and subscribes to service events.
|
||||
/// </summary>
|
||||
/// <param name="benchService">Bench service for RPM, relay, and parameter operations.</param>
|
||||
/// <param name="configService">Configuration service for bench parameters.</param>
|
||||
public BenchControlViewModel(IBenchService benchService, IConfigurationService configService)
|
||||
{
|
||||
_bench = benchService;
|
||||
_config = configService;
|
||||
|
||||
_bench.RpmCommandSent += () =>
|
||||
{
|
||||
TargetRpm = _bench.LastTargetRpm;
|
||||
CommandVoltage = _bench.LastCommandVoltage;
|
||||
};
|
||||
}
|
||||
|
||||
// ── Direction toggle ──────────────────────────────────────────────────────
|
||||
|
||||
partial void OnIsDirectionRightChanged(bool value)
|
||||
{
|
||||
_bench.SetRelay(RelayNames.DirectionRight, value);
|
||||
_bench.SetRelay(RelayNames.DirectionLeft, !value);
|
||||
}
|
||||
|
||||
// ── Oil pump toggle ───────────────────────────────────────────────────────
|
||||
|
||||
partial void OnIsOilPumpOnChanged(bool value)
|
||||
{
|
||||
_bench.SetRelay(RelayNames.OilPump, value);
|
||||
}
|
||||
|
||||
// ── RPM commands ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Opens the RPM quick-select popup.</summary>
|
||||
[RelayCommand]
|
||||
private void OpenRpmPopup()
|
||||
{
|
||||
IsRpmPopupOpen = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the bench motor at the RPM specified in <see cref="RpmInputText"/>.
|
||||
/// Warns the operator if the oil pump is off.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void StartBench()
|
||||
{
|
||||
if (!int.TryParse(RpmInputText, out int rpm) || rpm <= 0) return;
|
||||
|
||||
// Safety warning if oil pump is not running.
|
||||
if (!IsOilPumpOn)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"Oil pump is OFF. Start bench without oil circulation?",
|
||||
"Oil Pump Warning",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning);
|
||||
if (result != MessageBoxResult.Yes) return;
|
||||
}
|
||||
|
||||
// Ensure direction relays are set.
|
||||
_bench.SetRelay(RelayNames.DirectionRight, IsDirectionRight);
|
||||
_bench.SetRelay(RelayNames.DirectionLeft, !IsDirectionRight);
|
||||
|
||||
_bench.StartRpmPid(rpm);
|
||||
IsBenchRunning = true;
|
||||
IsRpmPopupOpen = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the bench motor via PID stop and clears direction relays.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void StopBench()
|
||||
{
|
||||
_bench.StopRpmPid();
|
||||
_bench.SetRelay(RelayNames.DirectionLeft, false);
|
||||
_bench.SetRelay(RelayNames.DirectionRight, false);
|
||||
IsBenchRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quick-select button handler: sets the RPM input and starts the bench.
|
||||
/// </summary>
|
||||
/// <param name="rpmString">RPM value as a string from the button content.</param>
|
||||
[RelayCommand]
|
||||
private void SetQuickRpm(string rpmString)
|
||||
{
|
||||
RpmInputText = rpmString;
|
||||
StartBench();
|
||||
}
|
||||
|
||||
// ── Counter commands ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Toggles the counter popup visibility.</summary>
|
||||
[RelayCommand]
|
||||
private void ToggleCounterPopup()
|
||||
{
|
||||
IsCounterPopupOpen = !IsCounterPopupOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the counter value over CAN and activates the counter relay.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void SendCounter()
|
||||
{
|
||||
if (!int.TryParse(CounterInputText, out int count) || count <= 0) return;
|
||||
|
||||
// Set the counter parameter value and transmit.
|
||||
_bench.SetParameter(BenchParameterNames.Counter, count);
|
||||
if (_config.Bench.ParametersByName.TryGetValue(
|
||||
BenchParameterNames.Counter, out var counterParam))
|
||||
{
|
||||
_bench.SendParameters(counterParam.MessageId);
|
||||
}
|
||||
|
||||
// Activate the counter relay.
|
||||
_bench.SetRelay(RelayNames.Counter, true);
|
||||
}
|
||||
|
||||
// ── Refresh (called from MainViewModel timer tick) ────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Updates live counter readback from CAN.
|
||||
/// Called on the UI thread from <see cref="MainViewModel.OnRefreshTick"/>.
|
||||
/// </summary>
|
||||
public void RefreshFromTick()
|
||||
{
|
||||
BenchCounterValue = _bench.ReadBenchParameter(BenchParameterNames.BenchCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user