更新
This commit is contained in:
@@ -118,6 +118,9 @@ public partial class PumpControlChannel : ObservableObject
|
||||
[ObservableProperty]
|
||||
private int flowStabilizationRawSetpoint;
|
||||
|
||||
[ObservableProperty]
|
||||
private double flowStabilizationIntegralError;
|
||||
|
||||
[ObservableProperty]
|
||||
private string flowStabilizationStatusText = "稳流未启用";
|
||||
|
||||
@@ -338,10 +341,26 @@ public partial class PumpControlChannel : ObservableObject
|
||||
if (!value)
|
||||
{
|
||||
FlowStabilizationStatusText = "稳流未启用";
|
||||
FlowStabilizationRawSetpoint = 0;
|
||||
FlowStabilizationIntegralError = 0;
|
||||
ConsecutiveFlowStabilizationFailureCount = 0;
|
||||
ConsecutiveFlowStabilizationLimitCount = 0;
|
||||
ConsecutiveFlowStabilizationUnavailableCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlowStabilizationIntegralError = 0;
|
||||
ConsecutiveFlowStabilizationFailureCount = 0;
|
||||
ConsecutiveFlowStabilizationLimitCount = 0;
|
||||
ConsecutiveFlowStabilizationUnavailableCount = 0;
|
||||
FlowStabilizationStatusText = !IsRunning || PendingRs485RunningState == false
|
||||
? "等待泵运行"
|
||||
: !FlowAvailable
|
||||
? "等待流量反馈"
|
||||
: !ConfirmedSetpointAvailable || ConfirmedSetpointFlowValue <= 0
|
||||
? "等待确认目标流量"
|
||||
: "等待稳流调节";
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(FlowStabilizationStateText));
|
||||
}
|
||||
@@ -392,6 +411,7 @@ public partial class PumpControlChannel : ObservableObject
|
||||
ConfirmedRawSetpointValue = 0;
|
||||
ConfirmedSetpointFlowValue = 0;
|
||||
FlowStabilizationRawSetpoint = 0;
|
||||
FlowStabilizationIntegralError = 0;
|
||||
}
|
||||
|
||||
partial void OnSetpointAvailableChanged(bool value)
|
||||
|
||||
@@ -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 double FlowStabilizationProportionalGainRatio = 0.25d;
|
||||
private const double FlowStabilizationIntegralGainRatioPerSecond = 0.04d;
|
||||
private const double FlowStabilizationMaxIntegralRawTrimRatio = 0.10d;
|
||||
private const int FlowStabilizationMaxConsecutiveFailures = 3;
|
||||
private const int FlowStabilizationMaxConsecutiveLimitHits = 3;
|
||||
private const int FlowStabilizationMaxUnavailableCycles = 3;
|
||||
@@ -686,6 +689,7 @@ public partial class MainViewModel
|
||||
{
|
||||
pump.FlowStabilizationStatusText = "等待泵运行";
|
||||
ResetFlowStabilizationProtectionCounters(pump);
|
||||
ResetFlowStabilizationControllerState(pump);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -703,6 +707,7 @@ public partial class MainViewModel
|
||||
{
|
||||
pump.FlowStabilizationStatusText = "等待确认目标流量";
|
||||
ResetFlowStabilizationProtectionCounters(pump);
|
||||
ResetFlowStabilizationControllerState(pump);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -716,13 +721,15 @@ public partial class MainViewModel
|
||||
|
||||
private async Task TryAdjustRs485FlowStabilizationAsync(PumpControlChannel pump)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var targetFlow = pump.ConfirmedSetpointFlowValue;
|
||||
var flowError = targetFlow - pump.FlowValue;
|
||||
if (Math.Abs(flowError) <= FlowStabilizationDeadbandLpm)
|
||||
{
|
||||
ResetFlowStabilizationProtectionCounters(pump);
|
||||
pump.FlowStabilizationIntegralError = 0;
|
||||
pump.FlowStabilizationStatusText = $"稳流保持:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min";
|
||||
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
|
||||
pump.LastFlowStabilizationAdjustmentUtc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -732,7 +739,7 @@ public partial class MainViewModel
|
||||
if (targetRaw <= 0)
|
||||
{
|
||||
DisableFlowStabilizationForProtection(pump, "稳流保护:目标流量换算值无效");
|
||||
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
|
||||
pump.LastFlowStabilizationAdjustmentUtc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -744,17 +751,42 @@ public partial class MainViewModel
|
||||
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 elapsedSeconds = CalculateFlowStabilizationElapsedSeconds(pump, now);
|
||||
var proportionalRaw = flowError * pump.Rs485RawPerLitrePerMinute * FlowStabilizationProportionalGainRatio;
|
||||
var integralGain = Math.Max(0.0001d, pump.Rs485RawPerLitrePerMinute * FlowStabilizationIntegralGainRatioPerSecond);
|
||||
var maxIntegralRaw = Math.Max(FlowStabilizationMaxRawStep, targetRaw * FlowStabilizationMaxIntegralRawTrimRatio);
|
||||
var integralLimit = maxIntegralRaw / integralGain;
|
||||
var nextIntegralError = Math.Clamp(
|
||||
pump.FlowStabilizationIntegralError + flowError * elapsedSeconds,
|
||||
-integralLimit,
|
||||
integralLimit);
|
||||
var integralRaw = nextIntegralError * integralGain;
|
||||
var controllerOutputRaw = proportionalRaw + integralRaw;
|
||||
var rawStep = (int)Math.Round(controllerOutputRaw, MidpointRounding.AwayFromZero);
|
||||
if (rawStep == 0)
|
||||
{
|
||||
rawStep = Math.Sign(flowError);
|
||||
}
|
||||
|
||||
rawStep = Math.Clamp(rawStep, -FlowStabilizationMaxRawStep, FlowStabilizationMaxRawStep);
|
||||
var nextRaw = Math.Clamp(currentRaw + rawStep, minRaw, maxRaw);
|
||||
var appliedRawStep = nextRaw - currentRaw;
|
||||
Logger.Information(
|
||||
"RS485 稳流调节计算,PumpKey={PumpKey},PumpName={PumpName},TargetFlowLpm={TargetFlowLpm},CurrentFlowLpm={CurrentFlowLpm},FlowErrorLpm={FlowErrorLpm},TargetRaw={TargetRaw},CurrentRaw={CurrentRaw},NextRaw={NextRaw},MinRaw={MinRaw},MaxRaw={MaxRaw}",
|
||||
"RS485 PI稳流调节计算,PumpKey={PumpKey},PumpName={PumpName},TargetFlowLpm={TargetFlowLpm},CurrentFlowLpm={CurrentFlowLpm},FlowErrorLpm={FlowErrorLpm},ElapsedSeconds={ElapsedSeconds},P={ProportionalRaw},I={IntegralRaw},IntegralError={IntegralError},ControllerOutputRaw={ControllerOutputRaw},TargetRaw={TargetRaw},CurrentRaw={CurrentRaw},RawStep={RawStep},AppliedRawStep={AppliedRawStep},NextRaw={NextRaw},MinRaw={MinRaw},MaxRaw={MaxRaw}",
|
||||
pump.Key,
|
||||
pump.Name,
|
||||
targetFlow,
|
||||
pump.FlowValue,
|
||||
flowError,
|
||||
elapsedSeconds,
|
||||
proportionalRaw,
|
||||
integralRaw,
|
||||
nextIntegralError,
|
||||
controllerOutputRaw,
|
||||
targetRaw,
|
||||
currentRaw,
|
||||
rawStep,
|
||||
appliedRawStep,
|
||||
nextRaw,
|
||||
minRaw,
|
||||
maxRaw);
|
||||
@@ -778,12 +810,12 @@ public partial class MainViewModel
|
||||
maxRaw,
|
||||
pump.ConsecutiveFlowStabilizationLimitCount);
|
||||
DisableFlowStabilizationForProtection(pump, $"稳流保护:{message},已连续 {pump.ConsecutiveFlowStabilizationLimitCount} 次无法继续调节");
|
||||
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
|
||||
pump.LastFlowStabilizationAdjustmentUtc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
pump.FlowStabilizationStatusText = $"{message}({pump.ConsecutiveFlowStabilizationLimitCount}/{FlowStabilizationMaxConsecutiveLimitHits})";
|
||||
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
|
||||
pump.LastFlowStabilizationAdjustmentUtc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -818,14 +850,15 @@ public partial class MainViewModel
|
||||
pump.ConsecutiveFlowStabilizationFailureCount = 0;
|
||||
pump.ConsecutiveFlowStabilizationLimitCount = 0;
|
||||
pump.ConsecutiveFlowStabilizationUnavailableCount = 0;
|
||||
pump.FlowStabilizationIntegralError = nextIntegralError;
|
||||
pump.FlowStabilizationRawSetpoint = nextRaw;
|
||||
CacheResolvedRs485Setpoint(pump, nextRaw);
|
||||
ApplyPostCommandPumpState(pump, result, expectedRunning: true);
|
||||
pump.FlowStabilizationStatusText = $"稳流调节:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min / 控制值 {nextRaw}";
|
||||
pump.FlowStabilizationStatusText = $"PI稳流调节:目标 {targetFlow:F2} / 当前 {pump.FlowValue:F2} L/min / 控制值 {nextRaw}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
pump.LastFlowStabilizationAdjustmentUtc = DateTime.UtcNow;
|
||||
pump.LastFlowStabilizationAdjustmentUtc = now;
|
||||
EndRs485PumpOperation(pump);
|
||||
}
|
||||
}
|
||||
@@ -855,6 +888,20 @@ public partial class MainViewModel
|
||||
return false;
|
||||
}
|
||||
|
||||
private static double CalculateFlowStabilizationElapsedSeconds(PumpControlChannel pump, DateTime now)
|
||||
{
|
||||
if (pump.LastFlowStabilizationAdjustmentUtc == DateTime.MinValue)
|
||||
{
|
||||
return FlowStabilizationAdjustmentInterval.TotalSeconds;
|
||||
}
|
||||
|
||||
var elapsedSeconds = (now - pump.LastFlowStabilizationAdjustmentUtc).TotalSeconds;
|
||||
return Math.Clamp(
|
||||
elapsedSeconds,
|
||||
FlowStabilizationAdjustmentInterval.TotalSeconds,
|
||||
FlowStabilizationAdjustmentInterval.TotalSeconds * 3d);
|
||||
}
|
||||
|
||||
private bool RegisterFlowStabilizationCommandFailure(PumpControlChannel pump, string message)
|
||||
{
|
||||
pump.ConsecutiveFlowStabilizationFailureCount++;
|
||||
@@ -901,6 +948,12 @@ public partial class MainViewModel
|
||||
pump.ConsecutiveFlowStabilizationUnavailableCount = 0;
|
||||
}
|
||||
|
||||
private static void ResetFlowStabilizationControllerState(PumpControlChannel pump)
|
||||
{
|
||||
pump.FlowStabilizationIntegralError = 0;
|
||||
pump.FlowStabilizationRawSetpoint = 0;
|
||||
}
|
||||
|
||||
private void RaiseRs485CalibrationSummaryChanges()
|
||||
{
|
||||
OnPropertyChanged(nameof(Rs485EnabledPumpCount));
|
||||
@@ -1508,7 +1561,7 @@ public partial class MainViewModel
|
||||
|
||||
if (ShouldUseRs485DirectPumpControl(pump))
|
||||
{
|
||||
if (!TryResolveRs485MotorCommand(pump, out var rawMotorSpeed, out var resolveMessage))
|
||||
if (!TryResolveRs485MotorCommand(pump, out var rawMotorSpeed, out var resolvedTargetFlow, out var resolveMessage))
|
||||
{
|
||||
pump.SetpointStatusText = resolveMessage;
|
||||
Rs485StatusText = resolveMessage;
|
||||
@@ -1531,6 +1584,7 @@ public partial class MainViewModel
|
||||
return Rs485StartExecutionResult.Failed;
|
||||
}
|
||||
|
||||
CacheConfirmedRs485Setpoint(pump, rawMotorSpeed, resolvedTargetFlow);
|
||||
CacheResolvedRs485Setpoint(pump, rawMotorSpeed);
|
||||
ApplyPostCommandPumpState(pump, directResult, expectedRunning: true);
|
||||
await RefreshTelemetryAsync();
|
||||
@@ -1736,9 +1790,14 @@ public partial class MainViewModel
|
||||
private bool IsRs485ManagedPump(PumpControlChannel pump) =>
|
||||
_pumpActuationService.IsManagedPump(pump);
|
||||
|
||||
private bool TryResolveRs485MotorCommand(PumpControlChannel pump, out short rawMotorSpeed, out string message)
|
||||
private bool TryResolveRs485MotorCommand(
|
||||
PumpControlChannel pump,
|
||||
out short rawMotorSpeed,
|
||||
out double targetFlowLpm,
|
||||
out string message)
|
||||
{
|
||||
rawMotorSpeed = 0;
|
||||
targetFlowLpm = 0;
|
||||
message = string.Empty;
|
||||
|
||||
if (!pump.HasSetpointCalibration)
|
||||
@@ -1753,7 +1812,13 @@ public partial class MainViewModel
|
||||
return false;
|
||||
}
|
||||
|
||||
var candidateRaw = pump.ConfirmedSetpointAvailable ? pump.ConfirmedRawSetpointValue : 0;
|
||||
var candidateRaw = 0;
|
||||
if (pump.ConfirmedSetpointAvailable && pump.ConfirmedRawSetpointValue > 0)
|
||||
{
|
||||
candidateRaw = pump.ConfirmedRawSetpointValue;
|
||||
targetFlowLpm = pump.ConfirmedSetpointFlowValue;
|
||||
}
|
||||
|
||||
if (candidateRaw <= 0
|
||||
&& double.TryParse(
|
||||
pump.PendingSetpointText,
|
||||
@@ -1764,6 +1829,7 @@ public partial class MainViewModel
|
||||
&& pendingFlow <= pump.Rs485MaxFlowLpm)
|
||||
{
|
||||
candidateRaw = ConvertFlowToRawSpeed(pump, pendingFlow);
|
||||
targetFlowLpm = pendingFlow;
|
||||
}
|
||||
|
||||
if (candidateRaw <= 0)
|
||||
@@ -1772,6 +1838,17 @@ public partial class MainViewModel
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetFlowLpm <= 0 && pump.HasSetpointCalibration)
|
||||
{
|
||||
targetFlowLpm = ConvertRawSpeedToFlow(pump, candidateRaw);
|
||||
}
|
||||
|
||||
if (targetFlowLpm <= 0)
|
||||
{
|
||||
message = $"{pump.Name} 启动前请输入大于 0 的目标流量";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (candidateRaw > MaxRs485MotorCommand)
|
||||
{
|
||||
message = $"{pump.Name} 的直接启停控制值超过 {MaxRs485MotorCommand}";
|
||||
@@ -1793,6 +1870,7 @@ public partial class MainViewModel
|
||||
pump.ConfirmedRawSetpointValue = rawMotorSpeed;
|
||||
pump.ConfirmedSetpointFlowValue = flowLpm;
|
||||
pump.FlowStabilizationRawSetpoint = rawMotorSpeed;
|
||||
pump.FlowStabilizationIntegralError = 0;
|
||||
}
|
||||
|
||||
private bool TryBeginRs485PumpOperation(PumpControlChannel pump, string operationName)
|
||||
@@ -1847,6 +1925,11 @@ public partial class MainViewModel
|
||||
pump.IsRunning = result.RunStatus.Value == 1;
|
||||
pump.StateAvailable = result.RunStatus.Value is 0 or 1;
|
||||
pump.Rs485RunStatusCode = result.RunStatus.Value;
|
||||
if (!expectedRunning || result.RunStatus.Value != 1)
|
||||
{
|
||||
ResetFlowStabilizationAfterPumpStopped(pump);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1856,8 +1939,7 @@ public partial class MainViewModel
|
||||
pump.IsRunning = false;
|
||||
pump.StateAvailable = true;
|
||||
pump.Rs485RunStatusCode = 0;
|
||||
pump.FlowStabilizationRawSetpoint = 0;
|
||||
ResetFlowStabilizationProtectionCounters(pump);
|
||||
ResetFlowStabilizationAfterPumpStopped(pump);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1867,6 +1949,16 @@ public partial class MainViewModel
|
||||
pump.Rs485RunStatusCode = null;
|
||||
}
|
||||
|
||||
private static void ResetFlowStabilizationAfterPumpStopped(PumpControlChannel pump)
|
||||
{
|
||||
ResetFlowStabilizationControllerState(pump);
|
||||
ResetFlowStabilizationProtectionCounters(pump);
|
||||
if (pump.IsFlowStabilizationEnabled)
|
||||
{
|
||||
pump.FlowStabilizationStatusText = "等待泵运行";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Rs485ToggleExecutionResult> TryTogglePumpControlViaRs485(PumpControlChannel pump, bool nextState)
|
||||
{
|
||||
if (!ShouldUseRs485DirectPumpControl(pump))
|
||||
@@ -1894,7 +1986,7 @@ public partial class MainViewModel
|
||||
{
|
||||
if (nextState)
|
||||
{
|
||||
if (!TryResolveRs485MotorCommand(pump, out var rawMotorSpeed, out var resolveMessage))
|
||||
if (!TryResolveRs485MotorCommand(pump, out var rawMotorSpeed, out var resolvedTargetFlow, out var resolveMessage))
|
||||
{
|
||||
pump.SetpointStatusText = resolveMessage;
|
||||
Rs485StatusText = resolveMessage;
|
||||
@@ -1911,6 +2003,7 @@ public partial class MainViewModel
|
||||
return Rs485ToggleExecutionResult.Failed;
|
||||
}
|
||||
|
||||
CacheConfirmedRs485Setpoint(pump, rawMotorSpeed, resolvedTargetFlow);
|
||||
CacheResolvedRs485Setpoint(pump, rawMotorSpeed);
|
||||
ApplyPostCommandPumpState(pump, startResult, expectedRunning: true);
|
||||
await RefreshTelemetryAsync();
|
||||
|
||||
Reference in New Issue
Block a user