Files
Cardiopulmonarybypasssystems/Cardiopulmonarybypasssystems/ViewModels/MainViewModel.Rs485.cs
GukSang.Jin 6af85741a2 更新202659
2026-05-09 15:16:26 +08:00

1622 lines
57 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks;
using Cardiopulmonarybypasssystems.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Cardiopulmonarybypasssystems.ViewModels;
public partial class MainViewModel
{
private enum Rs485StartExecutionResult
{
Failed,
PendingConfirmation,
Confirmed
}
private enum Rs485ToggleExecutionResult
{
NotHandled,
Failed,
Succeeded
}
private static readonly string[] Rs485PumpKeys =
[
"PressureDropPump",
"RecirculationMainPump",
"RecirculationReturnPump",
"RecirculationDrainagePump",
"KinkResistancePump",
"HemolysisDrainageSinglePump",
"HemolysisReturnSinglePump",
"HemolysisDualLumenPump"
];
private static readonly HashSet<string> Rs485PumpKeySet = [.. Rs485PumpKeys];
private const double DefaultRs485RawPerLitrePerMinute = 100d;
private const double DefaultRs485RawOffset = 0d;
private const int MaxRs485MotorCommand = short.MaxValue;
private const double FlowStabilizationDeadbandLpm = 0.05d;
private const int FlowStabilizationMaxRawStep = 5;
private const double FlowStabilizationMaxRelativeTrim = 0.20d;
private const string PressureDropRs485PumpKey = "PressureDropPump";
private const string KinkResistanceRs485PumpKey = "KinkResistancePump";
private static readonly TimeSpan FlowStabilizationAdjustmentInterval = TimeSpan.FromSeconds(2);
private static readonly string[] HemolysisRs485PumpKeys =
[
"HemolysisDrainageSinglePump",
"HemolysisReturnSinglePump",
"HemolysisDualLumenPump"
];
private static readonly string[] RecirculationRs485PumpKeys =
[
"RecirculationMainPump",
"RecirculationReturnPump",
"RecirculationDrainagePump"
];
private static readonly TimeSpan Rs485RuntimeRefreshInterval = TimeSpan.FromSeconds(4);
private DateTime _lastRs485RuntimeRefreshUtc = DateTime.MinValue;
private string _lastRs485RuntimeRefreshFailureMessage = string.Empty;
[ObservableProperty]
private string rs485PortName = "COM9";
[ObservableProperty]
private int rs485BaudRate = 9600;
[ObservableProperty]
private string rs485Parity = "Even";
[ObservableProperty]
private int rs485DataBits = 8;
[ObservableProperty]
private int rs485StopBits = 1;
[ObservableProperty]
private int rs485ReadTimeoutMs = 500;
[ObservableProperty]
private int rs485WriteTimeoutMs = 500;
[ObservableProperty]
private bool rs485AutoSwitchPresetMode = true;
[ObservableProperty]
private bool rs485PersistPresetAfterWrite;
[ObservableProperty]
private bool rs485AutoStartPumpAfterWrite;
[ObservableProperty]
private bool rs485AdvancedSettingsVisible;
[ObservableProperty]
private bool realtimeActuatorControlsVisible = true;
[ObservableProperty]
private string rs485StatusText = "RS485 待确认";
public ObservableCollection<string> Rs485ParityOptions { get; } = new(["Even", "Odd", "None"]);
public ObservableCollection<PumpControlChannel> Rs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> ActiveRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> RealtimeRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> PressureDropRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> KinkResistanceRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> HemolysisRs485FlowPumpControls { get; } = [];
public ObservableCollection<PumpControlChannel> RecirculationRs485FlowPumpControls { get; } = [];
public bool HasRealtimeRs485FlowPumpControls => RealtimeRs485FlowPumpControls.Count > 0;
public bool HasPressureDropRs485FlowPumpControls => PressureDropRs485FlowPumpControls.Count > 0;
public bool HasKinkResistanceRs485FlowPumpControls => KinkResistanceRs485FlowPumpControls.Count > 0;
public bool HasHemolysisRs485FlowPumpControls => HemolysisRs485FlowPumpControls.Count > 0;
public bool HasRecirculationRs485FlowPumpControls => RecirculationRs485FlowPumpControls.Count > 0;
public int SelectedRs485PumpCount => ActiveRs485FlowPumpControls.Count(item => item.IsBatchSelected);
public string Rs485AdvancedSettingsToggleText => Rs485AdvancedSettingsVisible ? "收起高级维护" : "展开高级维护";
public string RealtimeActuatorControlsToggleText => RealtimeActuatorControlsVisible ? "隐藏执行机构控制" : "显示执行机构控制";
public string Rs485ConnectionSummary =>
$"{Rs485PortName} / {Rs485BaudRate}bps / {Rs485Parity} / {Rs485DataBits}-{Rs485StopBits}";
public int Rs485EnabledPumpCount => Rs485FlowPumpControls.Count(item => item.Rs485Enabled);
public int Rs485CalibrationConfirmedPumpCount =>
Rs485FlowPumpControls.Count(item => item.Rs485Enabled && item.HasConfirmedSetpointCalibration);
public string Rs485CalibrationSummary => Rs485EnabledPumpCount == 0
? "当前未启用 RS485 泵通道。"
: Rs485CalibrationConfirmedPumpCount == Rs485EnabledPumpCount
? $"已确认全部 {Rs485EnabledPumpCount} 台泵的流量换算。"
: $"已确认 {Rs485CalibrationConfirmedPumpCount}/{Rs485EnabledPumpCount} 台泵的流量换算。";
public string Rs485ManualHint =>
"主操作区按 8 泵批量预设设计可逐行录入目标流量后一次批量写入PLC 仅保留实时采集。";
partial void OnRs485PortNameChanged(string value) => UpdateAndPersistRs485Settings();
partial void OnRs485BaudRateChanged(int value) => UpdateAndPersistRs485Settings();
partial void OnRs485ParityChanged(string value)
{
var targetStopBits = string.Equals(value, "None", StringComparison.OrdinalIgnoreCase) ? 2 : 1;
if (Rs485StopBits != targetStopBits)
{
Rs485StopBits = targetStopBits;
}
UpdateAndPersistRs485Settings();
}
partial void OnRs485DataBitsChanged(int value) => UpdateAndPersistRs485Settings();
partial void OnRs485StopBitsChanged(int value) => UpdateAndPersistRs485Settings();
partial void OnRs485ReadTimeoutMsChanged(int value) => UpdateAndPersistRs485Settings();
partial void OnRs485WriteTimeoutMsChanged(int value) => UpdateAndPersistRs485Settings();
partial void OnRs485AutoSwitchPresetModeChanged(bool value) => UpdateAndPersistRs485Settings();
partial void OnRs485PersistPresetAfterWriteChanged(bool value) => UpdateAndPersistRs485Settings();
partial void OnRs485AutoStartPumpAfterWriteChanged(bool value) => UpdateAndPersistRs485Settings();
partial void OnRs485AdvancedSettingsVisibleChanged(bool value) => OnPropertyChanged(nameof(Rs485AdvancedSettingsToggleText));
partial void OnRealtimeActuatorControlsVisibleChanged(bool value) => OnPropertyChanged(nameof(RealtimeActuatorControlsToggleText));
[RelayCommand]
private void ToggleRealtimeActuatorControls()
{
RealtimeActuatorControlsVisible = !RealtimeActuatorControlsVisible;
}
private void InitializeRs485FlowControl()
{
Rs485FlowPumpControls.Clear();
foreach (var pump in PumpControls.Where(item => Rs485PumpKeySet.Contains(item.Key)))
{
Rs485FlowPumpControls.Add(pump);
}
var defaults = BuildDefaultRs485PumpBindings();
foreach (var pump in Rs485FlowPumpControls)
{
ApplyRs485Binding(pump, defaults.FirstOrDefault(item => item.PumpKey == pump.Key));
if (string.IsNullOrWhiteSpace(pump.PendingSetpointText))
{
pump.PendingSetpointText = pump.FlowAvailable
? pump.FlowValue.ToString("F2", CultureInfo.InvariantCulture)
: pump.Rs485MinFlowLpm.ToString("F2", CultureInfo.InvariantCulture);
}
pump.PropertyChanged -= OnRs485PumpConfigurationChanged;
pump.PropertyChanged += OnRs485PumpConfigurationChanged;
}
RefreshActiveRs485FlowPumpControls();
OnPropertyChanged(nameof(Rs485ConnectionSummary));
}
private Rs485SerialSettings BuildRs485SerialSettings() => new()
{
PortName = Rs485PortName,
BaudRate = Rs485BaudRate,
Parity = Rs485Parity,
DataBits = Rs485DataBits,
StopBits = Rs485StopBits,
ReadTimeoutMs = Rs485ReadTimeoutMs,
WriteTimeoutMs = Rs485WriteTimeoutMs,
AutoSwitchPresetMode = Rs485AutoSwitchPresetMode,
PersistPresetAfterWrite = Rs485PersistPresetAfterWrite,
AutoStartPumpAfterWrite = Rs485AutoStartPumpAfterWrite
};
private List<Rs485PumpBindingSettings> BuildDefaultRs485PumpBindings()
{
var bindings = new List<Rs485PumpBindingSettings>();
for (var index = 0; index < Rs485PumpKeys.Length; index++)
{
bindings.Add(new Rs485PumpBindingSettings
{
PumpKey = Rs485PumpKeys[index],
Enabled = true,
CalibrationConfirmed = true,
SlaveAddress = (byte)(index + 1),
ForwardSpeedRegister = 0x00A2,
ReverseSpeedRegister = 0x00A3,
RunStatusRegister = 0x00F3,
DeviceAddressRegister = 0x00FA,
PresetModeRegister = 0x00FB,
SavePresetRegister = 0x01A0,
MotorControlRegister = 0x0040,
RawPerLitrePerMinute = DefaultRs485RawPerLitrePerMinute,
RawOffset = DefaultRs485RawOffset,
MinFlowLpm = 0,
MaxFlowLpm = 20.0
});
}
return bindings;
}
private void ApplyRs485Binding(PumpControlChannel pump, Rs485PumpBindingSettings? binding)
{
binding ??= BuildDefaultRs485PumpBindings().FirstOrDefault(item => item.PumpKey == pump.Key)
?? new Rs485PumpBindingSettings { PumpKey = pump.Key };
pump.Rs485Enabled = binding.Enabled;
pump.Rs485CalibrationConfirmed = binding.CalibrationConfirmed;
pump.Rs485SlaveAddress = binding.SlaveAddress;
pump.Rs485ForwardSpeedRegister = binding.ForwardSpeedRegister;
pump.Rs485ReverseSpeedRegister = binding.ReverseSpeedRegister;
pump.Rs485RunStatusRegister = binding.RunStatusRegister;
pump.Rs485DeviceAddressRegister = binding.DeviceAddressRegister;
pump.Rs485PresetModeRegister = binding.PresetModeRegister;
pump.Rs485SavePresetRegister = binding.SavePresetRegister;
pump.Rs485MotorControlRegister = binding.MotorControlRegister;
pump.Rs485RawPerLitrePerMinute = binding.RawPerLitrePerMinute > 0
? binding.RawPerLitrePerMinute
: DefaultRs485RawPerLitrePerMinute;
pump.Rs485RawOffset = binding.RawPerLitrePerMinute > 0
? binding.RawOffset
: DefaultRs485RawOffset;
pump.Rs485MinFlowLpm = binding.MinFlowLpm;
pump.Rs485MaxFlowLpm = binding.MaxFlowLpm <= 0 ? Math.Max(RatedMaxFlow, 20.0) : binding.MaxFlowLpm;
if (string.IsNullOrWhiteSpace(pump.PendingSetpointText))
{
pump.PendingSetpointText = pump.FlowAvailable
? pump.FlowValue.ToString("F2", CultureInfo.InvariantCulture)
: pump.Rs485MinFlowLpm.ToString("F2", CultureInfo.InvariantCulture);
}
if (binding.RawPerLitrePerMinute <= 0)
{
pump.SetpointStatusText = $"已应用默认换算初值 {DefaultRs485RawPerLitrePerMinute:F0} 速度/Lmin";
}
else if (!pump.HasSetpointCalibration)
{
pump.SetpointStatusText = "未配置 L/min 与速度换算系数";
}
else
{
pump.SetpointStatusText = "换算系数已确认";
}
}
private void OnRs485PumpConfigurationChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is not PumpControlChannel pump)
{
return;
}
if (e.PropertyName is not nameof(PumpControlChannel.Rs485Enabled)
and not nameof(PumpControlChannel.IsBatchSelected)
and not nameof(PumpControlChannel.Rs485SlaveAddress)
and not nameof(PumpControlChannel.Rs485RawPerLitrePerMinute)
and not nameof(PumpControlChannel.Rs485RawOffset)
and not nameof(PumpControlChannel.Rs485CalibrationConfirmed)
and not nameof(PumpControlChannel.Rs485MinFlowLpm)
and not nameof(PumpControlChannel.Rs485MaxFlowLpm)
and not nameof(PumpControlChannel.Rs485ForwardSpeedRegister)
and not nameof(PumpControlChannel.Rs485ReverseSpeedRegister)
and not nameof(PumpControlChannel.Rs485RunStatusRegister)
and not nameof(PumpControlChannel.Rs485DeviceAddressRegister)
and not nameof(PumpControlChannel.Rs485PresetModeRegister)
and not nameof(PumpControlChannel.Rs485SavePresetRegister)
and not nameof(PumpControlChannel.Rs485MotorControlRegister))
{
return;
}
if (e.PropertyName == nameof(PumpControlChannel.IsBatchSelected))
{
OnPropertyChanged(nameof(SelectedRs485PumpCount));
return;
}
if (!pump.HasSetpointCalibration)
{
pump.SetpointStatusText = "未配置 L/min 与速度换算系数";
}
else
{
pump.SetpointStatusText = "换算系数已确认";
}
if (e.PropertyName == nameof(PumpControlChannel.Rs485Enabled))
{
RefreshActiveRs485FlowPumpControls();
}
RaiseRs485CalibrationSummaryChanges();
UpdateAndPersistRs485Settings();
}
private void RefreshActiveRs485FlowPumpControls()
{
ActiveRs485FlowPumpControls.Clear();
RealtimeRs485FlowPumpControls.Clear();
PressureDropRs485FlowPumpControls.Clear();
KinkResistanceRs485FlowPumpControls.Clear();
HemolysisRs485FlowPumpControls.Clear();
RecirculationRs485FlowPumpControls.Clear();
foreach (var pump in Rs485FlowPumpControls.Where(item => item.Rs485Enabled))
{
ActiveRs485FlowPumpControls.Add(pump);
if (string.Equals(pump.Key, PressureDropRs485PumpKey, StringComparison.Ordinal))
{
PressureDropRs485FlowPumpControls.Add(pump);
}
else if (string.Equals(pump.Key, KinkResistanceRs485PumpKey, StringComparison.Ordinal))
{
KinkResistanceRs485FlowPumpControls.Add(pump);
}
else if (HemolysisRs485PumpKeys.Contains(pump.Key, StringComparer.Ordinal))
{
HemolysisRs485FlowPumpControls.Add(pump);
}
else if (RecirculationRs485PumpKeys.Contains(pump.Key, StringComparer.Ordinal))
{
RecirculationRs485FlowPumpControls.Add(pump);
}
else
{
RealtimeRs485FlowPumpControls.Add(pump);
}
}
OnPropertyChanged(nameof(HasRealtimeRs485FlowPumpControls));
OnPropertyChanged(nameof(HasPressureDropRs485FlowPumpControls));
OnPropertyChanged(nameof(HasKinkResistanceRs485FlowPumpControls));
OnPropertyChanged(nameof(HasHemolysisRs485FlowPumpControls));
OnPropertyChanged(nameof(HasRecirculationRs485FlowPumpControls));
RaiseRs485CalibrationSummaryChanges();
}
[RelayCommand]
private async Task StartRecirculationRs485Pumps()
{
if (!EnsureSessionEditable("再循环三泵统一启动"))
{
return;
}
var pumps = RecirculationRs485FlowPumpControls.ToList();
if (pumps.Count == 0)
{
Rs485StatusText = "当前未配置再循环 RS485 泵。";
LatestAction = Rs485StatusText;
return;
}
var issuedCount = 0;
var confirmedCount = 0;
var skippedCount = 0;
var failedCount = 0;
foreach (var pump in pumps)
{
var effectiveRunning = pump.PendingRs485RunningState ?? pump.IsRunning;
if (effectiveRunning)
{
skippedCount++;
continue;
}
var startResult = await TryWriteAndStartPumpCore(pump, "再循环三泵统一启动");
if (startResult is Rs485StartExecutionResult.PendingConfirmation or Rs485StartExecutionResult.Confirmed)
{
issuedCount++;
if (startResult == Rs485StartExecutionResult.Confirmed)
{
confirmedCount++;
}
}
else
{
failedCount++;
}
}
var summary = $"再循环三泵启动完成:已下发 {issuedCount} 台,已确认运行 {confirmedCount} 台,跳过 {skippedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("再循环三泵启动", summary));
}
[RelayCommand]
private async Task StopRecirculationRs485Pumps()
{
if (!EnsureSessionEditable("再循环三泵统一停止"))
{
return;
}
var pumps = RecirculationRs485FlowPumpControls.ToList();
if (pumps.Count == 0)
{
Rs485StatusText = "当前未配置再循环 RS485 泵。";
LatestAction = Rs485StatusText;
return;
}
var stoppedCount = 0;
var failedCount = 0;
foreach (var pump in pumps)
{
if (await TryTogglePumpControlViaRs485(pump, nextState: false) == Rs485ToggleExecutionResult.Succeeded)
{
stoppedCount++;
}
else
{
failedCount++;
}
}
var summary = $"再循环三泵停止完成:已下发停止 {stoppedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("再循环三泵停止", summary));
}
private async Task RefreshRs485RuntimeStateSilentlyAsync()
{
if (DateTime.UtcNow - _lastRs485RuntimeRefreshUtc < Rs485RuntimeRefreshInterval)
{
return;
}
var pumps = ActiveRs485FlowPumpControls
.Where(item => item.SupportsRs485Preset && !item.IsRs485Busy)
.OrderBy(item => item.Rs485SlaveAddress)
.ToList();
if (pumps.Count == 0 || string.IsNullOrWhiteSpace(Rs485PortName))
{
return;
}
_lastRs485RuntimeRefreshUtc = DateTime.UtcNow;
var pumpMap = pumps.ToDictionary(item => item.Key, StringComparer.Ordinal);
var requests = pumps
.Select(BuildRs485Request)
.ToList();
var snapshots = await Task.Run(() => _rs485PumpFlowService.ReadPumpRuntimeStates(requests));
var allFailed = true;
foreach (var snapshot in snapshots)
{
if (!pumpMap.TryGetValue(snapshot.PumpKey, out var pump))
{
continue;
}
ApplySilentRs485RuntimeSnapshot(pump, snapshot.Result);
if (snapshot.Result.Success)
{
allFailed = false;
}
}
if (!allFailed)
{
_lastRs485RuntimeRefreshFailureMessage = string.Empty;
return;
}
var failureMessage = "RS485 状态周期刷新失败,请检查串口链路和从站连接。";
if (string.Equals(_lastRs485RuntimeRefreshFailureMessage, failureMessage, StringComparison.Ordinal))
{
return;
}
_lastRs485RuntimeRefreshFailureMessage = failureMessage;
Rs485StatusText = failureMessage;
LatestAction = failureMessage;
TraceEvents.Insert(0, NewTrace("RS485 状态刷新失败", failureMessage));
}
private static void ApplySilentRs485RuntimeSnapshot(PumpControlChannel pump, Rs485PumpFlowOperationResult result)
{
if (result.RawForwardSpeed.HasValue)
{
pump.RawSetpointValue = result.RawForwardSpeed.Value;
pump.SetpointAvailable = true;
if (pump.HasSetpointCalibration)
{
pump.SetpointFlowValue = (result.RawForwardSpeed.Value - pump.Rs485RawOffset) / pump.Rs485RawPerLitrePerMinute;
}
}
if (result.DeviceAddress.HasValue)
{
pump.Rs485DeviceAddress = result.DeviceAddress.Value;
}
if (result.RunStatus.HasValue)
{
pump.Rs485RunStatusCode = result.RunStatus.Value;
pump.PendingRs485RunningState = null;
if (result.RunStatus.Value == 1)
{
pump.IsRunning = true;
pump.StateAvailable = true;
}
else if (result.RunStatus.Value == 0)
{
pump.IsRunning = false;
pump.StateAvailable = true;
}
else
{
pump.StateAvailable = false;
}
return;
}
if (!pump.PendingRs485RunningState.HasValue)
{
pump.Rs485RunStatusCode = null;
pump.StateAvailable = false;
}
}
private async Task MaintainRs485FlowStabilizationAsync()
{
var adjustablePumps = ActiveRs485FlowPumpControls
.Where(ShouldMaintainFlowStabilization)
.OrderBy(item => item.Rs485SlaveAddress)
.ToList();
foreach (var pump in adjustablePumps)
{
await TryAdjustRs485FlowStabilizationAsync(pump);
}
}
private bool ShouldMaintainFlowStabilization(PumpControlChannel pump)
{
if (!pump.IsFlowStabilizationEnabled)
{
return false;
}
if (!pump.CanUseFlowStabilization)
{
pump.FlowStabilizationStatusText = "当前项目要求固定转速";
return false;
}
if (pump.IsRs485Busy)
{
pump.FlowStabilizationStatusText = "等待当前 RS485 操作完成";
return false;
}
if (!pump.IsRunning || pump.PendingRs485RunningState == false)
{
pump.FlowStabilizationStatusText = "等待泵运行";
return false;
}
if (!pump.FlowAvailable)
{
pump.FlowStabilizationStatusText = "等待流量反馈";
return false;
}
if (!pump.ConfirmedSetpointAvailable || pump.ConfirmedSetpointFlowValue <= 0)
{
pump.FlowStabilizationStatusText = "等待确认目标流量";
return false;
}
if (DateTime.UtcNow - pump.LastFlowStabilizationAdjustmentUtc < FlowStabilizationAdjustmentInterval)
{
return false;
}
return true;
}
private async Task TryAdjustRs485FlowStabilizationAsync(PumpControlChannel pump)
{
var targetFlow = pump.ConfirmedSetpointFlowValue;
var flowError = targetFlow - pump.FlowValue;
if (Math.Abs(flowError) <= FlowStabilizationDeadbandLpm)
{
pump.FlowStabilizationStatusText = $"稳流保持:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
return;
}
var targetRaw = pump.ConfirmedRawSetpointValue > 0
? pump.ConfirmedRawSetpointValue
: ConvertFlowToRawSpeed(pump, targetFlow);
if (targetRaw <= 0)
{
pump.FlowStabilizationStatusText = "目标流量换算值无效";
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
return;
}
var currentRaw = pump.FlowStabilizationRawSetpoint > 0
? pump.FlowStabilizationRawSetpoint
: pump.RawSetpointValue > 0
? pump.RawSetpointValue
: targetRaw;
var maxTrim = Math.Max(FlowStabilizationMaxRawStep, (int)Math.Round(targetRaw * FlowStabilizationMaxRelativeTrim, MidpointRounding.AwayFromZero));
var minRaw = Math.Max(1, targetRaw - maxTrim);
var maxRaw = Math.Min(MaxRs485MotorCommand, targetRaw + maxTrim);
var rawStep = Math.Sign(flowError) * FlowStabilizationMaxRawStep;
var nextRaw = Math.Clamp(currentRaw + rawStep, minRaw, maxRaw);
if (nextRaw == currentRaw)
{
pump.FlowStabilizationStatusText = $"稳流已到调节限幅:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
return;
}
if (!TryBeginRs485PumpOperation(pump, "稳流调节"))
{
return;
}
try
{
var request = BuildRs485Request(pump);
var result = await Task.Run(() => _rs485PumpFlowService.WritePumpMotorCommand(request, (short)nextRaw));
if (!result.Success)
{
pump.FlowStabilizationStatusText = result.Message;
Rs485StatusText = result.Message;
TraceEvents.Insert(0, NewTrace("RS485 稳流调节失败", $"{pump.Name} / {result.Message}"));
return;
}
pump.FlowStabilizationRawSetpoint = nextRaw;
CacheResolvedRs485Setpoint(pump, nextRaw);
ApplyPostCommandPumpState(pump, result, expectedRunning: true);
pump.FlowStabilizationStatusText = $"稳流调节:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min / 控制值 {nextRaw}";
}
finally
{
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
EndRs485PumpOperation(pump);
}
}
private void RaiseRs485CalibrationSummaryChanges()
{
OnPropertyChanged(nameof(Rs485EnabledPumpCount));
OnPropertyChanged(nameof(Rs485CalibrationConfirmedPumpCount));
OnPropertyChanged(nameof(Rs485CalibrationSummary));
OnPropertyChanged(nameof(SelectedRs485PumpCount));
}
[RelayCommand]
private void ToggleRs485AdvancedSettings()
{
Rs485AdvancedSettingsVisible = !Rs485AdvancedSettingsVisible;
}
private void UpdateAndPersistRs485Settings()
{
OnPropertyChanged(nameof(Rs485ConnectionSummary));
}
[RelayCommand]
private void SetAllRs485PumpSelection(string? parameter)
{
var isSelected = string.Equals(parameter, bool.TrueString, StringComparison.OrdinalIgnoreCase);
foreach (var pump in ActiveRs485FlowPumpControls)
{
pump.IsBatchSelected = isSelected;
}
var summary = isSelected
? $"已选中 {SelectedRs485PumpCount} 台泵。"
: "已清空泵选择。";
Rs485StatusText = summary;
LatestAction = summary;
}
[RelayCommand]
private void ApplySingleDeviceRs485Profile()
{
Rs485BaudRate = 9600;
Rs485Parity = "Even";
Rs485DataBits = 8;
Rs485StopBits = 1;
Rs485ReadTimeoutMs = 500;
Rs485WriteTimeoutMs = 500;
Rs485AutoSwitchPresetMode = false;
Rs485PersistPresetAfterWrite = false;
Rs485AutoStartPumpAfterWrite = false;
var primaryPump = Rs485FlowPumpControls.FirstOrDefault();
foreach (var pump in Rs485FlowPumpControls)
{
pump.Rs485Enabled = ReferenceEquals(pump, primaryPump);
pump.Rs485SlaveAddress = 1;
pump.Rs485ForwardSpeedRegister = 0x00A2;
pump.Rs485ReverseSpeedRegister = 0x00A3;
pump.Rs485RunStatusRegister = 0x00F3;
pump.Rs485DeviceAddressRegister = 0x00FA;
pump.Rs485PresetModeRegister = 0x00FB;
pump.Rs485SavePresetRegister = 0x01A0;
pump.Rs485MotorControlRegister = 0x0040;
pump.Rs485RawPerLitrePerMinute = DefaultRs485RawPerLitrePerMinute;
pump.Rs485RawOffset = DefaultRs485RawOffset;
pump.Rs485CalibrationConfirmed = true;
pump.Rs485MinFlowLpm = 0;
pump.Rs485MaxFlowLpm = 1.0;
pump.SetpointStatusText = pump.Rs485Enabled
? "已应用当前配置"
: "当前未启用";
}
Rs485StatusText = primaryPump is null
? "未找到可配置的 RS485 泵通道"
: $"已完成伺服器快速配置:启用 {primaryPump.Name} / 从站 1";
UpdateAndPersistRs485Settings();
}
[RelayCommand]
private async Task TestRs485Connection()
{
var baselinePump = Rs485FlowPumpControls
.Where(item => item.SupportsRs485Preset)
.OrderBy(item => item.Rs485SlaveAddress)
.FirstOrDefault();
if (baselinePump is null)
{
Rs485StatusText = "未找到已启用的 RS485 泵通道";
return;
}
if (!TryBeginRs485PumpOperation(baselinePump, "基准通讯"))
{
return;
}
try
{
var request = BuildRs485Request(baselinePump);
var result = await Task.Run(() => _rs485PumpFlowService.TestConnection(request));
ApplyRs485OperationResult(baselinePump, result, "RS485 基准通讯");
var summary = result.Success
? $"RS485 基准通讯正常:{baselinePump.Name} / 从站 {baselinePump.Rs485SlaveAddress}"
: $"RS485 基准通讯失败:{baselinePump.Name} / 从站 {baselinePump.Rs485SlaveAddress} / {result.Message}";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 基准通讯", summary));
}
finally
{
EndRs485PumpOperation(baselinePump);
}
}
[RelayCommand]
private async Task TestAllRs485Connections()
{
var pumps = Rs485FlowPumpControls
.Where(item => item.SupportsRs485Preset)
.OrderBy(item => item.Rs485SlaveAddress)
.ToList();
if (pumps.Count == 0)
{
Rs485StatusText = "未找到已启用的 RS485 泵通道";
return;
}
var successCount = 0;
foreach (var pump in pumps)
{
if (!TryBeginRs485PumpOperation(pump, "全泵巡检"))
{
continue;
}
try
{
var request = BuildRs485Request(pump);
var result = await Task.Run(() => _rs485PumpFlowService.TestConnection(request));
ApplyRs485OperationResult(pump, result, "RS485 全泵巡检");
if (result.Success)
{
successCount++;
}
}
finally
{
EndRs485PumpOperation(pump);
}
}
var failedCount = pumps.Count - successCount;
var summary = failedCount == 0
? $"RS485 全泵巡检完成:{successCount} 台全部正常。"
: $"RS485 全泵巡检完成:成功 {successCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 全泵巡检", summary));
}
[RelayCommand]
private async Task RefreshAllPumpSetpoints()
{
foreach (var pump in Rs485FlowPumpControls.Where(item => item.SupportsRs485Preset))
{
await ReadPumpSetpoint(pump);
}
}
[RelayCommand]
private async Task ReadPumpSetpoint(PumpControlChannel? pump)
{
if (!TryPreparePumpForRs485(pump, requireCalibration: false, out var activePump, out _))
{
return;
}
if (!TryBeginRs485PumpOperation(activePump, "读取"))
{
return;
}
try
{
var request = BuildRs485Request(activePump);
var result = await Task.Run(() => _rs485PumpFlowService.ReadPumpPreset(request));
ApplyRs485OperationResult(activePump, result, "读取泵预设");
}
finally
{
EndRs485PumpOperation(activePump);
}
}
[RelayCommand]
private async Task WritePumpSetpoint(PumpControlChannel? pump)
{
await TryWritePumpSetpointCore(pump, "RS485 预设流量写入", autoStartAfterWrite: false);
}
[RelayCommand]
private async Task WriteAllPumpSetpoints()
{
if (!EnsureSessionEditable("RS485 批量预设流量写入"))
{
return;
}
var candidates = Rs485FlowPumpControls
.Where(item => item.SupportsRs485Preset)
.ToList();
if (candidates.Count == 0)
{
Rs485StatusText = "未找到已启用的 RS485 泵通道";
return;
}
var successCount = 0;
var failedCount = 0;
foreach (var pump in candidates)
{
if (await TryWritePumpSetpointCore(pump, "RS485 批量预设流量写入", autoStartAfterWrite: false))
{
successCount++;
}
else
{
failedCount++;
}
}
var summary = $"批量写入完成:成功 {successCount} 台,失败 {failedCount} 台。批量模式仅写入目标值,不自动启动。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 批量写入", summary));
}
[RelayCommand]
private void ApplyRs485FlowPreset(string? presetKey)
{
double ratio = presetKey switch
{
"50" => 0.50d,
"75" => 0.75d,
"100" => 1.00d,
_ => 0d
};
if (ratio <= 0d)
{
Rs485StatusText = "未识别的批量流量预设";
return;
}
var referenceFlow = Math.Max(RatedMaxFlow * ratio, 0d);
foreach (var pump in ActiveRs485FlowPumpControls)
{
var targetFlow = Math.Clamp(referenceFlow, pump.Rs485MinFlowLpm, pump.Rs485MaxFlowLpm);
pump.PendingSetpointText = targetFlow.ToString("F2", CultureInfo.InvariantCulture);
pump.SetpointStatusText = $"已应用 {presetKey}% 快捷目标 {targetFlow:F2} L/min";
}
var summary = $"已将 8 泵目标流量批量设为 {presetKey}% 参考值。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 批量目标", summary));
}
[RelayCommand]
private async Task QuickToggleRs485Pump(PumpControlChannel? pump)
{
if (pump is null)
{
return;
}
var effectiveRunning = pump.PendingRs485RunningState ?? pump.IsRunning;
if (effectiveRunning)
{
await TogglePumpControl(pump);
return;
}
await TryWriteAndStartPumpCore(pump, "RS485 快捷启动");
}
[RelayCommand]
private async Task StartSingleRs485Pump(PumpControlChannel? pump)
{
if (pump is null)
{
return;
}
await TryWriteAndStartPumpCore(pump, "RS485 单泵启动");
}
[RelayCommand]
private async Task StopSingleRs485Pump(PumpControlChannel? pump)
{
if (pump is null)
{
return;
}
if (!EnsureSessionEditable("RS485 单泵停止"))
{
return;
}
await TryTogglePumpControlViaRs485(pump, nextState: false);
}
[RelayCommand]
private async Task StartAllRs485Pumps()
{
if (!EnsureSessionEditable("RS485 一键启动"))
{
return;
}
var issuedCount = 0;
var confirmedCount = 0;
var skippedCount = 0;
var failedCount = 0;
foreach (var pump in ActiveRs485FlowPumpControls)
{
var effectiveRunning = pump.PendingRs485RunningState ?? pump.IsRunning;
if (effectiveRunning)
{
skippedCount++;
continue;
}
var startResult = await TryWriteAndStartPumpCore(pump, "RS485 一键启动");
if (startResult is Rs485StartExecutionResult.PendingConfirmation or Rs485StartExecutionResult.Confirmed)
{
issuedCount++;
if (startResult == Rs485StartExecutionResult.Confirmed)
{
confirmedCount++;
}
}
else
{
failedCount++;
}
}
var summary = $"一键启动完成:已下发 {issuedCount} 台,已确认运行 {confirmedCount} 台,跳过 {skippedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 一键启动", summary));
}
[RelayCommand]
private async Task StartSelectedRs485Pumps()
{
if (!EnsureSessionEditable("RS485 选中启动"))
{
return;
}
var selectedPumps = ActiveRs485FlowPumpControls.Where(item => item.IsBatchSelected).ToList();
if (selectedPumps.Count == 0)
{
Rs485StatusText = "请先勾选需要启动的泵";
LatestAction = Rs485StatusText;
return;
}
var issuedCount = 0;
var confirmedCount = 0;
var skippedCount = 0;
var failedCount = 0;
foreach (var pump in selectedPumps)
{
var effectiveRunning = pump.PendingRs485RunningState ?? pump.IsRunning;
if (effectiveRunning)
{
skippedCount++;
continue;
}
var startResult = await TryWriteAndStartPumpCore(pump, "RS485 选中启动");
if (startResult is Rs485StartExecutionResult.PendingConfirmation or Rs485StartExecutionResult.Confirmed)
{
issuedCount++;
if (startResult == Rs485StartExecutionResult.Confirmed)
{
confirmedCount++;
}
}
else
{
failedCount++;
}
}
var summary = $"选中启动完成:已下发 {issuedCount} 台,已确认运行 {confirmedCount} 台,跳过 {skippedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 选中启动", summary));
}
[RelayCommand]
private async Task StopAllRs485Pumps()
{
if (!EnsureSessionEditable("RS485 一键停止"))
{
return;
}
var stoppedCount = 0;
var failedCount = 0;
foreach (var pump in ActiveRs485FlowPumpControls)
{
if (await TryTogglePumpControlViaRs485(pump, nextState: false) == Rs485ToggleExecutionResult.Succeeded)
{
stoppedCount++;
}
else
{
failedCount++;
}
}
var summary = $"一键停止完成:已下发停止 {stoppedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 一键停止", summary));
}
[RelayCommand]
private async Task StopSelectedRs485Pumps()
{
if (!EnsureSessionEditable("RS485 选中停止"))
{
return;
}
var selectedPumps = ActiveRs485FlowPumpControls.Where(item => item.IsBatchSelected).ToList();
if (selectedPumps.Count == 0)
{
Rs485StatusText = "请先勾选需要停止的泵";
LatestAction = Rs485StatusText;
return;
}
var stoppedCount = 0;
var failedCount = 0;
foreach (var pump in selectedPumps)
{
if (await TryTogglePumpControlViaRs485(pump, nextState: false) == Rs485ToggleExecutionResult.Succeeded)
{
stoppedCount++;
}
else
{
failedCount++;
}
}
var summary = $"选中停止完成:已下发停止 {stoppedCount} 台,失败 {failedCount} 台。";
Rs485StatusText = summary;
LatestAction = summary;
TraceEvents.Insert(0, NewTrace("RS485 选中停止", summary));
}
private async Task<bool> TryWritePumpSetpointCore(
PumpControlChannel? pump,
string operationName,
bool autoStartAfterWrite)
{
if (!EnsureSessionEditable(operationName))
{
return false;
}
if (!TryPreparePumpForRs485(pump, requireCalibration: true, out var activePump, out var targetFlow))
{
return false;
}
if (activePump.IsRunning)
{
activePump.SetpointStatusText = "泵运行中,禁止改写预设";
Rs485StatusText = $"{activePump.Name} 运行中,禁止改写预设";
return false;
}
var rawValue = ConvertFlowToRawSpeed(activePump, targetFlow!.Value);
if (rawValue is < 0 or > MaxRs485MotorCommand)
{
activePump.SetpointStatusText = $"换算后的速度值超出 0~{MaxRs485MotorCommand}";
Rs485StatusText = $"{activePump.Name} 写入失败:速度值超出范围";
return false;
}
if (!TryBeginRs485PumpOperation(activePump, "写入"))
{
return false;
}
try
{
var request = BuildRs485Request(activePump);
var result = await Task.Run(() => _rs485PumpFlowService.WritePumpPreset(request, (ushort)rawValue));
ApplyRs485OperationResult(activePump, result, "写入泵预设");
if (result.Success)
{
activePump.PendingSetpointText = targetFlow.Value.ToString("F2", CultureInfo.InvariantCulture);
CacheConfirmedRs485Setpoint(activePump, rawValue, targetFlow.Value);
if (autoStartAfterWrite)
{
var startResult = await TryAutoStartPumpAfterRs485Write(activePump, targetFlow.Value, forceAutoStart: true);
return startResult is Rs485StartExecutionResult.PendingConfirmation or Rs485StartExecutionResult.Confirmed;
}
}
return result.Success;
}
finally
{
EndRs485PumpOperation(activePump);
}
}
private async Task<Rs485StartExecutionResult> TryWriteAndStartPumpCore(PumpControlChannel? pump, string operationName)
{
if (!await TryWritePumpSetpointCore(pump, operationName, autoStartAfterWrite: false))
{
return Rs485StartExecutionResult.Failed;
}
if (pump is null)
{
return Rs485StartExecutionResult.Failed;
}
var targetFlow = pump.ConfirmedSetpointAvailable
? pump.ConfirmedSetpointFlowValue
: double.TryParse(
pump.PendingSetpointText,
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture,
out var parsedFlow)
? parsedFlow
: 0d;
return await TryAutoStartPumpAfterRs485Write(pump, targetFlow, forceAutoStart: true);
}
private async Task<Rs485StartExecutionResult> TryAutoStartPumpAfterRs485Write(PumpControlChannel pump, double targetFlow, bool forceAutoStart = false)
{
if (!forceAutoStart && !Rs485AutoStartPumpAfterWrite)
{
return Rs485StartExecutionResult.Failed;
}
if (pump.IsRunning)
{
var runningMessage = $"{pump.Name} 预设已更新为 {targetFlow:F2} L/min泵当前已在运行";
pump.SetpointStatusText = runningMessage;
Rs485StatusText = runningMessage;
LatestAction = runningMessage;
TraceEvents.Insert(0, NewTrace("RS485 自动启动", $"{pump.Name} 已在运行,无需重复启动"));
return Rs485StartExecutionResult.Confirmed;
}
if (ShouldUseRs485DirectPumpControl(pump))
{
if (!TryResolveRs485MotorCommand(pump, out var rawMotorSpeed, out var resolveMessage))
{
pump.SetpointStatusText = resolveMessage;
Rs485StatusText = resolveMessage;
LatestAction = resolveMessage;
TraceEvents.Insert(0, NewTrace("RS485 直接启泵", $"{pump.Name} / {resolveMessage}"));
return Rs485StartExecutionResult.Failed;
}
var request = BuildRs485Request(pump);
var directResult = await Task.Run(() => _pumpActuationService.StartPump(request, rawMotorSpeed));
ApplyRs485OperationResult(pump, directResult, "RS485 直接启泵");
if (!directResult.Success)
{
return Rs485StartExecutionResult.Failed;
}
CacheResolvedRs485Setpoint(pump, rawMotorSpeed);
ApplyPostCommandPumpState(pump, directResult, expectedRunning: true);
await RefreshTelemetryAsync();
return directResult.RunStatus == 1
? Rs485StartExecutionResult.Confirmed
: Rs485StartExecutionResult.PendingConfirmation;
}
var message = $"{pump.Name} 已写入目标流量,但当前通道未配置伺服器 RS485 启停寄存器";
pump.SetpointStatusText = message;
Rs485StatusText = message;
LatestAction = message;
TraceEvents.Insert(0, NewTrace("RS485 自动启动", $"{pump.Name} / 未配置伺服器启停寄存器"));
return Rs485StartExecutionResult.Failed;
}
private bool TryPreparePumpForRs485(
PumpControlChannel? pump,
bool requireCalibration,
out PumpControlChannel activePump,
out double? targetFlow)
{
activePump = pump!;
targetFlow = null;
if (pump is null)
{
Rs485StatusText = "未选择泵通道";
return false;
}
if (!pump.SupportsRs485Preset)
{
pump.SetpointStatusText = "该泵未启用 RS485 预设";
Rs485StatusText = $"{pump.Name} 未启用 RS485 预设";
return false;
}
if (string.IsNullOrWhiteSpace(Rs485PortName))
{
pump.SetpointStatusText = "请先配置串口号";
Rs485StatusText = "RS485 串口号为空";
return false;
}
if (requireCalibration && !pump.HasSetpointCalibration)
{
pump.SetpointStatusText = "请先配置 L/min 与速度换算系数";
Rs485StatusText = $"{pump.Name} 未配置换算系数";
return false;
}
if (!requireCalibration)
{
return true;
}
if (!double.TryParse(
pump.PendingSetpointText,
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture,
out var parsedFlow))
{
pump.SetpointStatusText = "请输入有效的 L/min";
Rs485StatusText = $"{pump.Name} 输入的流量格式无效";
return false;
}
if (parsedFlow < pump.Rs485MinFlowLpm || parsedFlow > pump.Rs485MaxFlowLpm)
{
pump.SetpointStatusText = $"允许范围 {pump.Rs485MinFlowLpm:F2}~{pump.Rs485MaxFlowLpm:F2} L/min";
Rs485StatusText = $"{pump.Name} 超出预设流量范围";
return false;
}
targetFlow = parsedFlow;
return true;
}
private Rs485PumpFlowRequest BuildRs485Request(PumpControlChannel pump) => new()
{
SerialSettings = BuildRs485SerialSettings(),
PumpSettings = new Rs485PumpBindingSettings
{
PumpKey = pump.Key,
Enabled = pump.Rs485Enabled,
SlaveAddress = pump.Rs485SlaveAddress,
ForwardSpeedRegister = pump.Rs485ForwardSpeedRegister,
ReverseSpeedRegister = pump.Rs485ReverseSpeedRegister,
RunStatusRegister = pump.Rs485RunStatusRegister,
DeviceAddressRegister = pump.Rs485DeviceAddressRegister,
PresetModeRegister = pump.Rs485PresetModeRegister,
SavePresetRegister = pump.Rs485SavePresetRegister,
MotorControlRegister = pump.Rs485MotorControlRegister,
RawPerLitrePerMinute = pump.Rs485RawPerLitrePerMinute,
RawOffset = pump.Rs485RawOffset,
MinFlowLpm = pump.Rs485MinFlowLpm,
MaxFlowLpm = pump.Rs485MaxFlowLpm
}
};
private int ConvertFlowToRawSpeed(PumpControlChannel pump, double flowLpm) =>
(int)Math.Round(flowLpm * pump.Rs485RawPerLitrePerMinute + pump.Rs485RawOffset, MidpointRounding.AwayFromZero);
private double ConvertRawSpeedToFlow(PumpControlChannel pump, int rawSpeed) =>
pump.HasSetpointCalibration
? Math.Max(0, (rawSpeed - pump.Rs485RawOffset) / pump.Rs485RawPerLitrePerMinute)
: 0;
private void ApplyRs485OperationResult(PumpControlChannel pump, Rs485PumpFlowOperationResult result, string traceCategory)
{
Rs485StatusText = result.Message;
pump.SetpointStatusText = result.Message;
if (result.RawForwardSpeed.HasValue)
{
pump.RawSetpointValue = result.RawForwardSpeed.Value;
pump.SetpointAvailable = true;
if (pump.HasSetpointCalibration)
{
pump.SetpointFlowValue = ConvertRawSpeedToFlow(pump, result.RawForwardSpeed.Value);
}
}
if (result.DeviceAddress.HasValue)
{
pump.Rs485DeviceAddress = result.DeviceAddress.Value;
}
if (result.RunStatus.HasValue)
{
pump.Rs485RunStatusCode = result.RunStatus.Value;
if (result.RunStatus.Value is 0 or 1)
{
pump.StateAvailable = true;
pump.IsRunning = result.RunStatus.Value == 1;
}
}
else if (IsRs485ManagedPump(pump))
{
pump.Rs485RunStatusCode = null;
if (!pump.PendingRs485RunningState.HasValue)
{
pump.StateAvailable = false;
}
}
LatestAction = $"{traceCategory}{pump.Name} / {result.Message}";
TraceEvents.Insert(0, NewTrace(traceCategory, $"{pump.Name} / {result.Message}"));
}
private bool ShouldUseRs485DirectPumpControl(PumpControlChannel pump) =>
_pumpActuationService.CanHandleDirectControl(pump);
private bool IsRs485ManagedPump(PumpControlChannel pump) =>
_pumpActuationService.IsManagedPump(pump);
private bool TryResolveRs485MotorCommand(PumpControlChannel pump, out short rawMotorSpeed, out string message)
{
rawMotorSpeed = 0;
message = string.Empty;
if (!pump.HasSetpointCalibration)
{
message = $"{pump.Name} 尚未配置有效的流量换算系数";
return false;
}
var candidateRaw = pump.ConfirmedSetpointAvailable ? pump.ConfirmedRawSetpointValue : 0;
if (candidateRaw <= 0
&& double.TryParse(
pump.PendingSetpointText,
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture,
out var pendingFlow)
&& pendingFlow >= pump.Rs485MinFlowLpm
&& pendingFlow <= pump.Rs485MaxFlowLpm)
{
candidateRaw = ConvertFlowToRawSpeed(pump, pendingFlow);
}
if (candidateRaw <= 0)
{
message = $"{pump.Name} 启动前请输入有效目标流量";
return false;
}
if (candidateRaw > MaxRs485MotorCommand)
{
message = $"{pump.Name} 的直接启停控制值超过 {MaxRs485MotorCommand}";
return false;
}
rawMotorSpeed = (short)candidateRaw;
return true;
}
private static void CacheConfirmedRs485Setpoint(PumpControlChannel pump, int rawMotorSpeed, double flowLpm)
{
if (rawMotorSpeed <= 0)
{
return;
}
pump.ConfirmedSetpointAvailable = true;
pump.ConfirmedRawSetpointValue = rawMotorSpeed;
pump.ConfirmedSetpointFlowValue = flowLpm;
pump.FlowStabilizationRawSetpoint = rawMotorSpeed;
}
private bool TryBeginRs485PumpOperation(PumpControlChannel pump, string operationName)
{
if (pump.IsRs485Busy)
{
var message = string.IsNullOrWhiteSpace(pump.Rs485BusyOperation)
? $"{pump.Name} 正在执行 RS485 操作,请稍候"
: $"{pump.Name} 正在{pump.Rs485BusyOperation},请稍候";
pump.SetpointStatusText = message;
Rs485StatusText = message;
LatestAction = message;
return false;
}
pump.IsRs485Busy = true;
pump.Rs485BusyOperation = operationName;
return true;
}
private static void EndRs485PumpOperation(PumpControlChannel pump)
{
pump.Rs485BusyOperation = string.Empty;
pump.IsRs485Busy = false;
}
private void CacheResolvedRs485Setpoint(PumpControlChannel pump, int rawMotorSpeed)
{
if (rawMotorSpeed <= 0)
{
return;
}
pump.RawSetpointValue = rawMotorSpeed;
pump.SetpointAvailable = true;
if (pump.HasSetpointCalibration)
{
pump.SetpointFlowValue = ConvertRawSpeedToFlow(pump, rawMotorSpeed);
}
}
private static void ApplyPostCommandPumpState(
PumpControlChannel pump,
Rs485PumpFlowOperationResult result,
bool expectedRunning)
{
if (result.RunStatus.HasValue)
{
pump.PendingRs485RunningState = null;
pump.IsRunning = result.RunStatus.Value == 1;
pump.StateAvailable = result.RunStatus.Value is 0 or 1;
pump.Rs485RunStatusCode = result.RunStatus.Value;
return;
}
if (!expectedRunning)
{
pump.PendingRs485RunningState = null;
pump.IsRunning = false;
pump.StateAvailable = true;
pump.Rs485RunStatusCode = 0;
return;
}
pump.PendingRs485RunningState = expectedRunning;
pump.IsRunning = expectedRunning;
pump.StateAvailable = false;
pump.Rs485RunStatusCode = null;
}
private async Task<Rs485ToggleExecutionResult> TryTogglePumpControlViaRs485(PumpControlChannel pump, bool nextState)
{
if (!ShouldUseRs485DirectPumpControl(pump))
{
if (IsRs485ManagedPump(pump))
{
var message = $"{pump.Name} 当前未配置伺服器 RS485 启停寄存器";
pump.SetpointStatusText = message;
Rs485StatusText = message;
LatestAction = message;
TraceEvents.Insert(0, NewTrace("RS485 泵控", $"{pump.Name} / {message}"));
return Rs485ToggleExecutionResult.Failed;
}
return Rs485ToggleExecutionResult.NotHandled;
}
var operationName = nextState ? "启动" : "停止";
if (!TryBeginRs485PumpOperation(pump, operationName))
{
return Rs485ToggleExecutionResult.Failed;
}
try
{
if (nextState)
{
if (!TryResolveRs485MotorCommand(pump, out var rawMotorSpeed, out var resolveMessage))
{
pump.SetpointStatusText = resolveMessage;
Rs485StatusText = resolveMessage;
LatestAction = resolveMessage;
TraceEvents.Insert(0, NewTrace("RS485 泵控", $"{pump.Name} / {resolveMessage}"));
return Rs485ToggleExecutionResult.Failed;
}
var request = BuildRs485Request(pump);
var startResult = await Task.Run(() => _pumpActuationService.StartPump(request, rawMotorSpeed));
ApplyRs485OperationResult(pump, startResult, "RS485 泵控");
if (!startResult.Success)
{
return Rs485ToggleExecutionResult.Failed;
}
CacheResolvedRs485Setpoint(pump, rawMotorSpeed);
ApplyPostCommandPumpState(pump, startResult, expectedRunning: true);
await RefreshTelemetryAsync();
return Rs485ToggleExecutionResult.Succeeded;
}
var stopRequest = BuildRs485Request(pump);
var stopResult = await Task.Run(() => _pumpActuationService.StopPump(stopRequest));
ApplyRs485OperationResult(pump, stopResult, "RS485 泵控");
if (!stopResult.Success)
{
return Rs485ToggleExecutionResult.Failed;
}
ApplyPostCommandPumpState(pump, stopResult, expectedRunning: false);
await RefreshTelemetryAsync();
return Rs485ToggleExecutionResult.Succeeded;
}
finally
{
EndRs485PumpOperation(pump);
}
}
}