更新20260605

This commit is contained in:
GukSang.Jin
2026-06-05 11:36:45 +08:00
parent cf11aaae16
commit e1aa304bec
4 changed files with 127 additions and 0 deletions

View File

@@ -1342,6 +1342,24 @@
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
</ItemsControl> </ItemsControl>
<Button MinWidth="132"
Height="34"
Margin="0,0,10,10"
Padding="12,4"
Command="{Binding CalibrateProximalPressureCommand}"
Content="近端压力校准"
Background="#FFB7791F"
IsEnabled="{Binding CanModifySession}"
ToolTip="M1300" />
<Button MinWidth="132"
Height="34"
Margin="0,0,10,10"
Padding="12,4"
Command="{Binding CalibrateDistalPressureCommand}"
Content="远端压力校准"
Background="#FFB7791F"
IsEnabled="{Binding CanModifySession}"
ToolTip="M1301" />
</WrapPanel> </WrapPanel>
</Grid> </Grid>
</Border> </Border>

View File

@@ -16,6 +16,7 @@ public interface IModbusTelemetryService
float? ReadHoldingFloatRegister(ushort address); float? ReadHoldingFloatRegister(ushort address);
bool WriteHoldingRegister(ushort address, ushort value); bool WriteHoldingRegister(ushort address, ushort value);
bool WriteHoldingFloatRegister(ushort address, float value); bool WriteHoldingFloatRegister(ushort address, float value);
bool WriteCoil(ushort address, bool value, string operationName);
// Legacy PLC coil path retained only for non-RS485 pump compatibility. // Legacy PLC coil path retained only for non-RS485 pump compatibility.
void SetPumpRunning(string pumpKey, bool isRunning); void SetPumpRunning(string pumpKey, bool isRunning);
void SetValveOpen(string valveKey, bool isOpen); void SetValveOpen(string valveKey, bool isOpen);

View File

@@ -386,6 +386,45 @@ public sealed class ModbusTelemetryService : IModbusTelemetryService, IDisposabl
} }
} }
public bool WriteCoil(ushort address, bool value, string operationName)
{
lock (_syncRoot)
{
if (_master is null)
{
_lastErrorMessage = $"PLC 离线,未执行{operationName}线圈写入M{address}";
Logger.Warning(
"PLC 离线跳过线圈写入Operation={Operation}Coil=M{CoilAddress}Value={Value}",
operationName,
address,
value);
return false;
}
try
{
_master.WriteSingleCoil(_slaveId, address, value);
Logger.Information(
"PLC 线圈写入成功Operation={Operation}Coil=M{CoilAddress}Value={Value}",
operationName,
address,
value);
return true;
}
catch (Exception ex)
{
Logger.Error(
ex,
"PLC 线圈写入失败Operation={Operation}Coil=M{CoilAddress}Value={Value}",
operationName,
address,
value);
HandleConnectionFailure($"{operationName}线圈 M{address} 写入失败:{ex.Message}");
return false;
}
}
}
public void SetValveOpen(string valveKey, bool isOpen) public void SetValveOpen(string valveKey, bool isOpen)
{ {
lock (_syncRoot) lock (_syncRoot)

View File

@@ -31,6 +31,9 @@ public partial class MainViewModel : ObservableObject, IDisposable
private const string SettingsDirectoryName = "Cardiopulmonarybypasssystems"; private const string SettingsDirectoryName = "Cardiopulmonarybypasssystems";
private const string LimitSettingsFileName = "manufacturer-limits.json"; private const string LimitSettingsFileName = "manufacturer-limits.json";
private const float EngineeringFloatVerificationTolerance = 0.0001f; private const float EngineeringFloatVerificationTolerance = 0.0001f;
private const ushort ProximalPressureCalibrationCoil = 1300;
private const ushort DistalPressureCalibrationCoil = 1301;
private static readonly TimeSpan PressureCalibrationPulseDuration = TimeSpan.FromMilliseconds(300);
private static readonly (string Name, ushort Address)[] FlowCoefficientRegisters = private static readonly (string Name, ushort Address)[] FlowCoefficientRegisters =
[ [
("流量系数1", 1006), ("流量系数1", 1006),
@@ -62,6 +65,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
private string _lastTelemetryRefreshFailureMessage = string.Empty; private string _lastTelemetryRefreshFailureMessage = string.Empty;
private double? _proximalPressureRawKpa; private double? _proximalPressureRawKpa;
private double? _distalPressureRawKpa; private double? _distalPressureRawKpa;
private bool _pressureCalibrationPulseInProgress;
private static string ResolveLimitSettingsPath() private static string ResolveLimitSettingsPath()
{ {
return Path.Combine( return Path.Combine(
@@ -875,6 +879,71 @@ public partial class MainViewModel : ObservableObject, IDisposable
_ = RefreshTelemetryAsync(); _ = RefreshTelemetryAsync();
} }
[RelayCommand]
private async Task CalibrateProximalPressure()
{
await PulsePressureCalibrationAsync("近端压力校准", ProximalPressureCalibrationCoil);
}
[RelayCommand]
private async Task CalibrateDistalPressure()
{
await PulsePressureCalibrationAsync("远端压力校准", DistalPressureCalibrationCoil);
}
private async Task PulsePressureCalibrationAsync(string calibrationName, ushort coilAddress)
{
if (!EnsureSessionEditable(calibrationName))
{
return;
}
if (_pressureCalibrationPulseInProgress)
{
LatestAction = "压力校准指令正在执行,请稍后再操作。";
return;
}
_pressureCalibrationPulseInProgress = true;
try
{
Logger.Information(
"执行压力校准脉冲CalibrationName={CalibrationName}Coil=M{CoilAddress}TelemetryOnline={TelemetryOnline}",
calibrationName,
coilAddress,
IsTelemetryOnline);
if (!_telemetryService.WriteCoil(coilAddress, true, calibrationName))
{
LatestAction = IsTelemetryOnline
? $"{calibrationName} 指令下发失败:{_telemetryService.LastErrorMessage}"
: $"{calibrationName} 指令未下发PLC 当前离线。";
TraceEvents.Insert(0, NewTrace("压力校准", $"{calibrationName} / M{coilAddress}=1 下发失败"));
return;
}
LatestAction = $"{calibrationName} 已触发M{coilAddress}=1。";
TraceEvents.Insert(0, NewTrace("压力校准", $"{calibrationName} / M{coilAddress}=1"));
await Task.Delay(PressureCalibrationPulseDuration);
if (!_telemetryService.WriteCoil(coilAddress, false, calibrationName))
{
LatestAction = $"{calibrationName} 释放失败,请检查 PLC 线圈 M{coilAddress}。";
TraceEvents.Insert(0, NewTrace("压力校准", $"{calibrationName} / M{coilAddress}=0 释放失败"));
return;
}
LatestAction = $"{calibrationName} 已完成校准脉冲。";
TraceEvents.Insert(0, NewTrace("压力校准", $"{calibrationName} / M{coilAddress}=0"));
}
finally
{
_pressureCalibrationPulseInProgress = false;
_ = RefreshTelemetryAsync();
}
}
[RelayCommand] [RelayCommand]
private void ClearTrendData() private void ClearTrendData()
{ {