更新202606045

This commit is contained in:
GukSang.Jin
2026-06-04 18:41:14 +08:00
parent 924b98d473
commit 48e0795145
6 changed files with 398 additions and 60 deletions

View File

@@ -61,12 +61,13 @@
<DataGridTextColumn Header="速度偏置" Binding="{Binding Rs485RawOffset, UpdateSourceTrigger=PropertyChanged}" Width="90" />
<DataGridTextColumn Header="最小 L/min" Binding="{Binding Rs485MinFlowLpm, UpdateSourceTrigger=PropertyChanged}" Width="90" />
<DataGridTextColumn Header="最大 L/min" Binding="{Binding Rs485MaxFlowLpm, UpdateSourceTrigger=PropertyChanged}" Width="90" />
<DataGridCheckBoxColumn Header="已确认" Binding="{Binding Rs485CalibrationConfirmed, UpdateSourceTrigger=PropertyChanged}" Width="76" />
<DataGridTextColumn Header="备注" Binding="{Binding SetpointStatusText}" IsReadOnly="True" Width="*" />
</DataGrid.Columns>
</DataGrid>
<TextBlock Margin="0,10,0,0"
Style="{StaticResource CaptionStyle}"
Text="上方 8 泵默认配置只保留运行所需参数,寄存器地址不在界面显示;下方 8 路流量系数表会先读取 PLC 当前值回显,再支持人工修改和写入校正。"
Text="上方 8 泵默认配置只保留运行所需参数,寄存器地址不在界面显示;速度换算核对后勾选已确认才允许启泵。下方 8 路流量系数表会先读取 PLC 当前值回显,再支持人工修改和写入校正。"
TextWrapping="Wrap" />
</StackPanel>
</Border>

View File

@@ -12,4 +12,6 @@ public sealed class ManufacturerLimitSettings
public double PressureDropLimit100 { get; set; } = 24;
public double AntiCollapseAllowedIncreaseRate { get; set; } = 50;
public double RecirculationAllowedLimit { get; set; } = 8;
public Rs485SerialSettings Rs485SerialSettings { get; set; } = new();
public List<Rs485PumpBindingSettings> Rs485PumpBindings { get; set; } = [];
}

View File

@@ -121,6 +121,15 @@ public partial class PumpControlChannel : ObservableObject
[ObservableProperty]
private string flowStabilizationStatusText = "稳流未启用";
[ObservableProperty]
private int consecutiveFlowStabilizationFailureCount;
[ObservableProperty]
private int consecutiveFlowStabilizationLimitCount;
[ObservableProperty]
private int consecutiveFlowStabilizationUnavailableCount;
public string StartAddressDisplay => $"M{StartAddress}";
public string FlowAddressDisplay => FlowAddress.HasValue ? $"D{FlowAddress.Value}" : "-";
public bool HasFlowTelemetry => FlowAddress.HasValue;
@@ -129,20 +138,22 @@ public partial class PumpControlChannel : ObservableObject
public bool UsesLegacyPlcDirectControl => Key == "NegativeAssistPump";
public bool HideRealtimeCardStateDescription => Key == "NegativeAssistPump";
public bool HasSetpointCalibration => Rs485RawPerLitrePerMinute > 0;
public bool HasConfirmedSetpointCalibration => HasSetpointCalibration;
public bool HasConfirmedSetpointCalibration => HasSetpointCalibration && Rs485CalibrationConfirmed;
public bool IsFlowEstablished => !HasFlowTelemetry || (FlowAvailable && FlowValue >= FlowEstablishedThreshold);
public string Rs485SlaveAddressDisplay => SupportsRs485Preset ? Rs485SlaveAddress.ToString() : "-";
public string CalibrationStatusText => !Rs485Enabled
? "未启用"
: HasSetpointCalibration
: HasConfirmedSetpointCalibration
? "已确认"
: "未配置";
: HasSetpointCalibration
? "待确认"
: "未配置";
public string SetpointReadbackDisplay => !SupportsRs485Preset
? "-"
: SetpointAvailable
? HasSetpointCalibration
? HasConfirmedSetpointCalibration
? $"{SetpointFlowValue:F2} L/min"
: "未配置换算"
: "未确认换算"
: "--";
public string RawSetpointDisplay => !SupportsRs485Preset
? "-"
@@ -226,14 +237,14 @@ public partial class PumpControlChannel : ObservableObject
? "泵已在运行"
: HasConfirmedSetpointCalibration
? string.Empty
: "未配置流量换算系数";
: "未确认流量换算系数";
public string StopActionHint => IsRs485Busy ? "RS485 操作中" : string.Empty;
public bool CanToggleRs485Action => PendingRs485RunningState == true || IsRunning || HasConfirmedSetpointCalibration;
public string ToggleActionHint => PendingRs485RunningState == true
? "启动确认中,可执行停止"
: CanToggleRs485Action
? string.Empty
: "未配置流量换算系数";
: "未确认流量换算系数";
public string Rs485ReadActionText => IsRs485Busy ? "处理中" : "读取";
public string Rs485WriteActionText => IsRs485Busy ? "处理中" : "写入";
public bool CanUseFlowStabilization => SupportsRs485DirectControl && Key != "KinkResistancePump";
@@ -244,7 +255,9 @@ public partial class PumpControlChannel : ObservableObject
? "固定转速"
: IsFlowStabilizationEnabled
? FlowStabilizationStatusText
: "稳流未启用";
: string.IsNullOrWhiteSpace(FlowStabilizationStatusText)
? "稳流未启用"
: FlowStabilizationStatusText;
public string SetpointStatusForeground => ResolveSetpointStatusForeground();
public string SetpointStatusBackground => ResolveSetpointStatusBackground();
@@ -325,6 +338,9 @@ public partial class PumpControlChannel : ObservableObject
if (!value)
{
FlowStabilizationStatusText = "稳流未启用";
ConsecutiveFlowStabilizationFailureCount = 0;
ConsecutiveFlowStabilizationLimitCount = 0;
ConsecutiveFlowStabilizationUnavailableCount = 0;
}
OnPropertyChanged(nameof(FlowStabilizationStateText));
@@ -363,6 +379,7 @@ public partial class PumpControlChannel : ObservableObject
OnPropertyChanged(nameof(StartActionHint));
OnPropertyChanged(nameof(CanToggleRs485Action));
OnPropertyChanged(nameof(ToggleActionHint));
OnPropertyChanged(nameof(SetpointReadbackDisplay));
}
partial void OnSetpointFlowValueChanged(double value) => OnPropertyChanged(nameof(SetpointReadbackDisplay));

View File

@@ -22,14 +22,14 @@ public sealed class ModbusTelemetryService : IModbusTelemetryService, IDisposabl
private static readonly IReadOnlyDictionary<string, ushort> FlowRegisters = new Dictionary<string, ushort>(StringComparer.Ordinal)
{
["PressureDropPump"] = 1000,
["RecirculationMainPump"] = 1010,
["RecirculationReturnPump"] = 1020,
["RecirculationDrainagePump"] = 1030,
["KinkResistancePump"] = 1040,
["HemolysisDrainageSinglePump"] = 1050,
["HemolysisReturnSinglePump"] = 1060,
["HemolysisDualLumenPump"] = 1070
["PressureDropPump"] = 1008,
["RecirculationMainPump"] = 1018,
["RecirculationReturnPump"] = 1028,
["RecirculationDrainagePump"] = 1038,
["KinkResistancePump"] = 1048,
["HemolysisDrainageSinglePump"] = 1058,
["HemolysisReturnSinglePump"] = 1068,
["HemolysisDualLumenPump"] = 1078
};
private static readonly IReadOnlyDictionary<string, string> FlowChannelNames = new Dictionary<string, string>(StringComparer.Ordinal)
@@ -698,9 +698,7 @@ public sealed class ModbusTelemetryService : IModbusTelemetryService, IDisposabl
private void SetChannelValue(string channelName, double nextValue, bool isAvailable)
{
var channel = Channel(channelName);
channel.Value = ShouldClampChannelValue(channelName)
? Math.Clamp(nextValue, channel.Min, channel.Max)
: nextValue;
channel.Value = nextValue;
channel.IsAvailable = isAvailable;
}
@@ -778,8 +776,5 @@ public sealed class ModbusTelemetryService : IModbusTelemetryService, IDisposabl
private static bool IsPlausiblePressureKpa(float value) =>
!float.IsNaN(value) && !float.IsInfinity(value) && value is > -1000f and < 1000f;
private static bool ShouldClampChannelValue(string channelName) =>
channelName is not "近端压力" and not "远端压力";
private DeviceChannel Channel(string name) => _channels.First(channel => channel.Name == name);
}

View File

@@ -44,6 +44,9 @@ public partial class MainViewModel
private const double FlowStabilizationDeadbandLpm = 0.05d;
private const int FlowStabilizationMaxRawStep = 5;
private const double FlowStabilizationMaxRelativeTrim = 0.20d;
private const int FlowStabilizationMaxConsecutiveFailures = 3;
private const int FlowStabilizationMaxConsecutiveLimitHits = 3;
private const int FlowStabilizationMaxUnavailableCycles = 3;
private const string PressureDropRs485PumpKey = "PressureDropPump";
private const string KinkResistanceRs485PumpKey = "KinkResistancePump";
private static readonly TimeSpan FlowStabilizationAdjustmentInterval = TimeSpan.FromSeconds(2);
@@ -62,6 +65,7 @@ public partial class MainViewModel
private static readonly TimeSpan Rs485RuntimeRefreshInterval = TimeSpan.FromSeconds(4);
private DateTime _lastRs485RuntimeRefreshUtc = DateTime.MinValue;
private string _lastRs485RuntimeRefreshFailureMessage = string.Empty;
private bool _suppressRs485SettingsSave;
[ObservableProperty]
private string rs485PortName = "COM9";
@@ -203,6 +207,81 @@ public partial class MainViewModel
AutoStartPumpAfterWrite = Rs485AutoStartPumpAfterWrite
};
private void ApplyRs485SerialSettings(Rs485SerialSettings? settings)
{
if (settings is null)
{
return;
}
if (!string.IsNullOrWhiteSpace(settings.PortName))
{
Rs485PortName = settings.PortName;
}
if (settings.BaudRate > 0)
{
Rs485BaudRate = settings.BaudRate;
}
if (!string.IsNullOrWhiteSpace(settings.Parity))
{
Rs485Parity = settings.Parity;
}
if (settings.DataBits > 0)
{
Rs485DataBits = settings.DataBits;
}
Rs485StopBits = settings.StopBits <= 0 ? 1 : settings.StopBits;
Rs485ReadTimeoutMs = settings.ReadTimeoutMs > 0 ? settings.ReadTimeoutMs : Rs485ReadTimeoutMs;
Rs485WriteTimeoutMs = settings.WriteTimeoutMs > 0 ? settings.WriteTimeoutMs : Rs485WriteTimeoutMs;
Rs485AutoSwitchPresetMode = settings.AutoSwitchPresetMode;
Rs485PersistPresetAfterWrite = settings.PersistPresetAfterWrite;
Rs485AutoStartPumpAfterWrite = settings.AutoStartPumpAfterWrite;
}
private void ApplyRs485Bindings(IReadOnlyList<Rs485PumpBindingSettings>? bindings)
{
var defaults = BuildDefaultRs485PumpBindings();
var bindingMap = (bindings ?? Array.Empty<Rs485PumpBindingSettings>())
.Where(item => !string.IsNullOrWhiteSpace(item.PumpKey))
.GroupBy(item => item.PumpKey, StringComparer.Ordinal)
.ToDictionary(group => group.Key, group => group.Last(), StringComparer.Ordinal);
foreach (var pump in Rs485FlowPumpControls)
{
bindingMap.TryGetValue(pump.Key, out var binding);
ApplyRs485Binding(pump, binding ?? defaults.FirstOrDefault(item => item.PumpKey == pump.Key));
}
RefreshActiveRs485FlowPumpControls();
OnPropertyChanged(nameof(Rs485ConnectionSummary));
}
private List<Rs485PumpBindingSettings> BuildCurrentRs485PumpBindings() =>
Rs485FlowPumpControls
.Select(pump => new Rs485PumpBindingSettings
{
PumpKey = pump.Key,
Enabled = pump.Rs485Enabled,
CalibrationConfirmed = pump.Rs485CalibrationConfirmed,
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
})
.ToList();
private List<Rs485PumpBindingSettings> BuildDefaultRs485PumpBindings()
{
var bindings = new List<Rs485PumpBindingSettings>();
@@ -212,7 +291,7 @@ public partial class MainViewModel
{
PumpKey = Rs485PumpKeys[index],
Enabled = true,
CalibrationConfirmed = true,
CalibrationConfirmed = false,
SlaveAddress = (byte)(index + 1),
ForwardSpeedRegister = 0x00A2,
ReverseSpeedRegister = 0x00A3,
@@ -270,6 +349,10 @@ public partial class MainViewModel
{
pump.SetpointStatusText = "未配置 L/min 与速度换算系数";
}
else if (!pump.Rs485CalibrationConfirmed)
{
pump.SetpointStatusText = "换算系数待确认";
}
else
{
pump.SetpointStatusText = "换算系数已确认";
@@ -312,6 +395,10 @@ public partial class MainViewModel
{
pump.SetpointStatusText = "未配置 L/min 与速度换算系数";
}
else if (!pump.Rs485CalibrationConfirmed)
{
pump.SetpointStatusText = "换算系数待确认";
}
else
{
pump.SetpointStatusText = "换算系数已确认";
@@ -579,6 +666,7 @@ public partial class MainViewModel
if (!pump.CanUseFlowStabilization)
{
pump.FlowStabilizationStatusText = "当前项目要求固定转速";
ResetFlowStabilizationProtectionCounters(pump);
return false;
}
@@ -588,21 +676,33 @@ public partial class MainViewModel
return false;
}
if (pump.PendingRs485RunningState == true)
{
pump.FlowStabilizationStatusText = "等待启动确认";
return false;
}
if (!pump.IsRunning || pump.PendingRs485RunningState == false)
{
pump.FlowStabilizationStatusText = "等待泵运行";
ResetFlowStabilizationProtectionCounters(pump);
return false;
}
if (!pump.FlowAvailable)
{
pump.FlowStabilizationStatusText = "等待流量反馈";
if (RegisterFlowStabilizationUnavailable(pump, "等待流量反馈"))
{
return false;
}
return false;
}
if (!pump.ConfirmedSetpointAvailable || pump.ConfirmedSetpointFlowValue <= 0)
{
pump.FlowStabilizationStatusText = "等待确认目标流量";
ResetFlowStabilizationProtectionCounters(pump);
return false;
}
@@ -620,6 +720,7 @@ public partial class MainViewModel
var flowError = targetFlow - pump.FlowValue;
if (Math.Abs(flowError) <= FlowStabilizationDeadbandLpm)
{
ResetFlowStabilizationProtectionCounters(pump);
pump.FlowStabilizationStatusText = $"稳流保持:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
return;
@@ -630,7 +731,7 @@ public partial class MainViewModel
: ConvertFlowToRawSpeed(pump, targetFlow);
if (targetRaw <= 0)
{
pump.FlowStabilizationStatusText = "目标流量换算值无效";
DisableFlowStabilizationForProtection(pump, "稳流保护:目标流量换算值无效");
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
return;
}
@@ -648,7 +749,18 @@ public partial class MainViewModel
if (nextRaw == currentRaw)
{
pump.FlowStabilizationStatusText = $"稳流已到调节限幅:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
pump.ConsecutiveFlowStabilizationLimitCount++;
pump.ConsecutiveFlowStabilizationFailureCount = 0;
pump.ConsecutiveFlowStabilizationUnavailableCount = 0;
var message = $"稳流已到调节限幅:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
if (pump.ConsecutiveFlowStabilizationLimitCount >= FlowStabilizationMaxConsecutiveLimitHits)
{
DisableFlowStabilizationForProtection(pump, $"稳流保护:{message},已连续 {pump.ConsecutiveFlowStabilizationLimitCount} 次无法继续调节");
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
return;
}
pump.FlowStabilizationStatusText = $"{message}{pump.ConsecutiveFlowStabilizationLimitCount}/{FlowStabilizationMaxConsecutiveLimitHits}";
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
return;
}
@@ -664,12 +776,26 @@ public partial class MainViewModel
var result = await Task.Run(() => _rs485PumpFlowService.WritePumpMotorCommand(request, (short)nextRaw));
if (!result.Success)
{
pump.FlowStabilizationStatusText = result.Message;
if (RegisterFlowStabilizationCommandFailure(pump, result.Message))
{
return;
}
Rs485StatusText = result.Message;
TraceEvents.Insert(0, NewTrace("RS485 稳流调节失败", $"{pump.Name} / {result.Message}"));
return;
}
if (result.RunStatus.HasValue && result.RunStatus.Value != 1)
{
DisableFlowStabilizationForProtection(pump, $"稳流保护:{pump.Name} 运行状态返回 {result.RunStatus.Value},停止自动调节");
ApplyPostCommandPumpState(pump, result, expectedRunning: true);
return;
}
pump.ConsecutiveFlowStabilizationFailureCount = 0;
pump.ConsecutiveFlowStabilizationLimitCount = 0;
pump.ConsecutiveFlowStabilizationUnavailableCount = 0;
pump.FlowStabilizationRawSetpoint = nextRaw;
CacheResolvedRs485Setpoint(pump, nextRaw);
ApplyPostCommandPumpState(pump, result, expectedRunning: true);
@@ -682,6 +808,59 @@ public partial class MainViewModel
}
}
private bool RegisterFlowStabilizationUnavailable(PumpControlChannel pump, string message)
{
pump.ConsecutiveFlowStabilizationUnavailableCount++;
pump.ConsecutiveFlowStabilizationFailureCount = 0;
pump.ConsecutiveFlowStabilizationLimitCount = 0;
if (pump.ConsecutiveFlowStabilizationUnavailableCount >= FlowStabilizationMaxUnavailableCycles)
{
DisableFlowStabilizationForProtection(
pump,
$"稳流保护:{message},已连续 {pump.ConsecutiveFlowStabilizationUnavailableCount} 次无有效反馈");
return true;
}
pump.FlowStabilizationStatusText = $"{message}{pump.ConsecutiveFlowStabilizationUnavailableCount}/{FlowStabilizationMaxUnavailableCycles}";
return false;
}
private bool RegisterFlowStabilizationCommandFailure(PumpControlChannel pump, string message)
{
pump.ConsecutiveFlowStabilizationFailureCount++;
pump.ConsecutiveFlowStabilizationLimitCount = 0;
pump.ConsecutiveFlowStabilizationUnavailableCount = 0;
if (pump.ConsecutiveFlowStabilizationFailureCount >= FlowStabilizationMaxConsecutiveFailures)
{
DisableFlowStabilizationForProtection(
pump,
$"稳流保护:{message},已连续 {pump.ConsecutiveFlowStabilizationFailureCount} 次调节失败");
return true;
}
pump.FlowStabilizationStatusText = $"{message}{pump.ConsecutiveFlowStabilizationFailureCount}/{FlowStabilizationMaxConsecutiveFailures}";
return false;
}
private void DisableFlowStabilizationForProtection(PumpControlChannel pump, string message)
{
pump.IsFlowStabilizationEnabled = false;
pump.FlowStabilizationStatusText = message;
ResetFlowStabilizationProtectionCounters(pump);
Rs485StatusText = message;
LatestAction = message;
TraceEvents.Insert(0, NewTrace("RS485 稳流保护", $"{pump.Name} / {message}"));
}
private static void ResetFlowStabilizationProtectionCounters(PumpControlChannel pump)
{
pump.ConsecutiveFlowStabilizationFailureCount = 0;
pump.ConsecutiveFlowStabilizationLimitCount = 0;
pump.ConsecutiveFlowStabilizationUnavailableCount = 0;
}
private void RaiseRs485CalibrationSummaryChanges()
{
OnPropertyChanged(nameof(Rs485EnabledPumpCount));
@@ -699,6 +878,13 @@ public partial class MainViewModel
private void UpdateAndPersistRs485Settings()
{
OnPropertyChanged(nameof(Rs485ConnectionSummary));
RaiseRs485CalibrationSummaryChanges();
if (_suppressRs485SettingsSave)
{
return;
}
SaveManufacturerLimitSettings();
}
[RelayCommand]
@@ -721,40 +907,50 @@ public partial class MainViewModel
[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)
var previousSuppress = _suppressRs485SettingsSave;
_suppressRs485SettingsSave = true;
try
{
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
? "已应用当前配置"
: "当前未启用";
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 = false;
pump.Rs485MinFlowLpm = 0;
pump.Rs485MaxFlowLpm = 1.0;
pump.SetpointStatusText = pump.Rs485Enabled
? "已应用当前配置,待确认换算系数"
: "当前未启用";
}
Rs485StatusText = primaryPump is null
? "未找到可配置的 RS485 泵通道"
: $"已完成伺服器快速配置:启用 {primaryPump.Name} / 从站 1";
}
finally
{
_suppressRs485SettingsSave = previousSuppress;
}
Rs485StatusText = primaryPump is null
? "未找到可配置的 RS485 泵通道"
: $"已完成伺服器快速配置:启用 {primaryPump.Name} / 从站 1";
UpdateAndPersistRs485Settings();
}
@@ -1324,6 +1520,13 @@ public partial class MainViewModel
return false;
}
if (requireCalibration && !pump.Rs485CalibrationConfirmed)
{
pump.SetpointStatusText = "请先确认 L/min 与速度换算系数";
Rs485StatusText = $"{pump.Name} 换算系数未确认";
return false;
}
if (!requireCalibration)
{
return true;
@@ -1440,6 +1643,12 @@ public partial class MainViewModel
return false;
}
if (!pump.Rs485CalibrationConfirmed)
{
message = $"{pump.Name} 尚未确认流量换算系数";
return false;
}
var candidateRaw = pump.ConfirmedSetpointAvailable ? pump.ConfirmedRawSetpointValue : 0;
if (candidateRaw <= 0
&& double.TryParse(
@@ -1519,6 +1728,8 @@ public partial class MainViewModel
{
pump.SetpointFlowValue = ConvertRawSpeedToFlow(pump, rawMotorSpeed);
}
pump.FlowStabilizationRawSetpoint = rawMotorSpeed;
}
private static void ApplyPostCommandPumpState(
@@ -1541,6 +1752,8 @@ public partial class MainViewModel
pump.IsRunning = false;
pump.StateAvailable = true;
pump.Rs485RunStatusCode = 0;
pump.FlowStabilizationRawSetpoint = 0;
ResetFlowStabilizationProtectionCounters(pump);
return;
}

View File

@@ -1007,6 +1007,11 @@ public partial class MainViewModel : ObservableObject, IDisposable
return;
}
if (!EnsureFlowStabilizationReadyForSample("抗塌陷基线采集", PressureDropRs485PumpKey))
{
return;
}
_antiCollapseBaselinePressureDrop = DeltaPressure;
_antiCollapseBaselineFlow = PumpFlow;
_antiCollapseBaselineCapturedAt = DateTime.Now;
@@ -1051,6 +1056,11 @@ public partial class MainViewModel : ObservableObject, IDisposable
return;
}
if (!EnsureFlowStabilizationReadyForSample("抗塌陷比较采集", PressureDropRs485PumpKey))
{
return;
}
var resultText = BuildAntiCollapseMeasuredText();
var noteText = BuildAntiCollapseRecordNote();
var comparison = GetAntiCollapseComparison();
@@ -2086,6 +2096,89 @@ public partial class MainViewModel : ObservableObject, IDisposable
private double ChannelValueOrDefault(string name) => TryGetChannel(name, out var channel) && channel.IsAvailable ? channel.Value : 0d;
private bool HasChannelTelemetry(params string[] names) => names.All(name => TryGetChannel(name, out var channel) && channel.IsAvailable);
private bool EnsureFlowStabilizationReadyForSample(string actionName, params string[] pumpKeys)
{
foreach (var pumpKey in pumpKeys.Distinct(StringComparer.Ordinal))
{
var pump = PumpControls.FirstOrDefault(item => string.Equals(item.Key, pumpKey, StringComparison.Ordinal));
if (pump is null)
{
continue;
}
var hasProtectionFault = pump.FlowStabilizationStatusText.StartsWith("稳流保护", StringComparison.Ordinal);
if (!pump.IsFlowStabilizationEnabled && !hasProtectionFault)
{
continue;
}
if (IsFlowStabilizationReadyForSample(pump, hasProtectionFault, out var reason))
{
continue;
}
var message = $"{actionName}已阻止:{pump.Name} {reason}";
LatestAction = message;
TraceEvents.Insert(0, NewTrace("稳流采样保护", message));
return false;
}
return true;
}
private static bool IsFlowStabilizationReadyForSample(
PumpControlChannel pump,
bool hasProtectionFault,
out string reason)
{
reason = string.Empty;
if (hasProtectionFault && !pump.IsFlowStabilizationEnabled)
{
reason = pump.FlowStabilizationStatusText;
return false;
}
if (pump.IsRs485Busy)
{
reason = "正在执行 RS485 操作,等待调节完成后再采样。";
return false;
}
if (pump.PendingRs485RunningState == true)
{
reason = "启动仍在确认中,等待运行/流量反馈稳定后再采样。";
return false;
}
if (!pump.IsRunning || pump.PendingRs485RunningState == false)
{
reason = "未确认运行,不能作为稳定流量采样。";
return false;
}
if (!pump.FlowAvailable)
{
reason = "无实时流量反馈,不能作为稳定流量采样。";
return false;
}
if (!pump.ConfirmedSetpointAvailable || pump.ConfirmedSetpointFlowValue <= 0)
{
reason = "无已确认目标流量,不能作为稳定流量采样。";
return false;
}
var error = Math.Abs(pump.FlowValue - pump.ConfirmedSetpointFlowValue);
if (error > FlowStabilizationDeadbandLpm)
{
reason = $"尚未进入稳流允差:目标 {pump.ConfirmedSetpointFlowValue:F2} / 当前 {pump.FlowValue:F2} L/min偏差 {error:F2} L/min。";
return false;
}
return true;
}
private bool TryGetChannel(string name, out DeviceChannel channel)
{
channel = Channels.FirstOrDefault(item => item.Name == name)!;
@@ -2467,6 +2560,11 @@ public partial class MainViewModel : ObservableObject, IDisposable
return;
}
if (!EnsureFlowStabilizationReadyForSample("压力降采样", PressureDropRs485PumpKey))
{
return;
}
var entry = PressureDropEntries.First(item => item.Label == label);
entry.ActualPumpFlow = PumpFlow;
entry.ProximalPressure = ChannelValue("近端压力");
@@ -2639,6 +2737,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
private void LoadManufacturerLimitSettings()
{
var previousRs485Suppress = _suppressRs485SettingsSave;
try
{
MigrateLegacyLimitSettingsIfNeeded();
@@ -2656,6 +2755,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
}
_suppressLimitSettingsSave = true;
_suppressRs485SettingsSave = true;
ProductModel = settings.ProductModel;
ApplicablePopulation = settings.ApplicablePopulation;
RatedMaxFlow = settings.RatedMaxFlow;
@@ -2666,6 +2766,8 @@ public partial class MainViewModel : ObservableObject, IDisposable
PressureDropLimit100 = settings.PressureDropLimit100;
AntiCollapseAllowedIncreaseRate = settings.AntiCollapseAllowedIncreaseRate;
RecirculationAllowedLimit = settings.RecirculationAllowedLimit;
ApplyRs485SerialSettings(settings.Rs485SerialSettings);
ApplyRs485Bindings(settings.Rs485PumpBindings);
}
catch
{
@@ -2675,6 +2777,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
finally
{
_suppressLimitSettingsSave = false;
_suppressRs485SettingsSave = previousRs485Suppress;
RefreshSpecializedJudgements();
}
}
@@ -2700,7 +2803,9 @@ public partial class MainViewModel : ObservableObject, IDisposable
PressureDropLimit75 = PressureDropLimit75,
PressureDropLimit100 = PressureDropLimit100,
AntiCollapseAllowedIncreaseRate = AntiCollapseAllowedIncreaseRate,
RecirculationAllowedLimit = RecirculationAllowedLimit
RecirculationAllowedLimit = RecirculationAllowedLimit,
Rs485SerialSettings = BuildRs485SerialSettings(),
Rs485PumpBindings = BuildCurrentRs485PumpBindings()
};
var json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
@@ -2811,6 +2916,11 @@ public partial class MainViewModel : ObservableObject, IDisposable
return;
}
if (!EnsureFlowStabilizationReadyForSample("再循环采样", RecirculationRs485PumpKeys))
{
return;
}
var entry = RecirculationEntries.First(item => item.Label == label);
entry.ActualPumpFlow = RecirculationPumpFlow;
entry.DrainageFlow = DrainageFlow;