更新数据

This commit is contained in:
GukSang.Jin
2026-05-21 16:44:02 +08:00
parent 44c86765fa
commit 6bc1475ae6
6 changed files with 311 additions and 73 deletions

View File

@@ -25,8 +25,6 @@ public sealed class ExperimentDataService : IExperimentDataService
Records = [];
CurrentSnapshot = _realtimeDataService.GetCurrentSnapshot(TimeSpan.Zero);
RecordSnapshot(CurrentSnapshot);
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
@@ -71,11 +69,15 @@ public sealed class ExperimentDataService : IExperimentDataService
var accumulatedSnapshot = ApplyAccumulatedTotals(massSnapshot);
CurrentSnapshot = accumulatedSnapshot;
Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot));
while (Records.Count > MaximumRows)
if (_isTestRunning)
{
Records.RemoveAt(0);
Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot));
while (Records.Count > MaximumRows)
{
Records.RemoveAt(0);
}
}
SnapshotUpdated?.Invoke(this, accumulatedSnapshot);

View File

@@ -1,16 +1,70 @@
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
namespace ConeCalorimeter.ViewModels;
public sealed class CValueCalibrationActionViewModel
public sealed class CValueCalibrationActionViewModel : ObservableObject
{
public CValueCalibrationActionViewModel(string label, ICommand command)
private bool _isActive;
private bool _isStatusKnown;
public CValueCalibrationActionViewModel(
string label,
ICommand command,
string inactiveDisplayText,
string activeDisplayText)
{
Label = label;
Command = command;
InactiveDisplayText = inactiveDisplayText;
ActiveDisplayText = activeDisplayText;
}
public string Label { get; }
public string InactiveDisplayText { get; }
public string ActiveDisplayText { get; }
public ICommand Command { get; }
public bool IsActive
{
get => _isActive;
private set
{
if (SetProperty(ref _isActive, value))
{
OnPropertyChanged(nameof(DisplayText));
OnPropertyChanged(nameof(StatusText));
}
}
}
public bool IsStatusKnown
{
get => _isStatusKnown;
private set
{
if (SetProperty(ref _isStatusKnown, value))
{
OnPropertyChanged(nameof(DisplayText));
OnPropertyChanged(nameof(StatusText));
}
}
}
public string DisplayText => IsStatusKnown
? IsActive ? ActiveDisplayText : InactiveDisplayText
: $"{Label}:未知";
public string StatusText => IsStatusKnown
? IsActive ? "开启" : "关闭"
: "未知";
public void UpdateStatus(bool isActive, bool isStatusKnown = true)
{
IsStatusKnown = isStatusKnown;
IsActive = isActive;
}
}

View File

@@ -14,14 +14,28 @@ public sealed class CValueCalibrationViewModel : PageViewModel
private const ushort PressureDifferenceRegister = 284;
private const ushort OxygenRegister = 286;
private const ushort CValueRegister = 308;
private const ushort CirculatingWaterCoil = 49;
private const ushort SamplingPumpCoil = 50;
private const ushort IgniterCoil = 53;
private const ushort FanCoil = 54;
private const ushort MethaneValveCoil = 55;
private const ushort BaselineCollectionCoil = 60;
private const ushort BaselineEndCoil = 62;
private const ushort CalibrationStartCoil = 70;
private const ushort CalibrationEndCoil = 72;
private const string CirculatingWaterAction = "循环水";
private const string SamplingPumpAction = "取样泵";
private const string IgniterAction = "点火器";
private const string FanAction = "风机";
private const string MethaneValveAction = "甲烷阀";
private const string BaselineCollectionAction = "基线采集";
private const string CalibrationStartAction = "标定开始";
private static readonly TimeSpan PulseDelay = TimeSpan.FromMilliseconds(300);
private readonly Action _closeAction;
private readonly Action _helpAction;
private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
private readonly DispatcherTimer _refreshTimer;
private readonly Dictionary<string, CValueCalibrationActionViewModel> _actionsByLabel = [];
private string _baselineOxygenText = "";
private string _temperatureText = "";
private string _pressureDifferenceText = "";
@@ -43,16 +57,17 @@ public sealed class CValueCalibrationViewModel : PageViewModel
TopActions =
[
new CValueCalibrationActionViewModel("甲烷阀", ActionCommand),
new CValueCalibrationActionViewModel("风机", ActionCommand),
new CValueCalibrationActionViewModel("点火器", ActionCommand),
new CValueCalibrationActionViewModel("取样泵", ActionCommand)
CreateAction(CirculatingWaterAction, $"{CirculatingWaterAction}:关闭", $"{CirculatingWaterAction}:开启"),
CreateAction(MethaneValveAction, $"{MethaneValveAction}:关闭", $"{MethaneValveAction}:开启"),
CreateAction(FanAction, $"{FanAction}:关闭", $"{FanAction}:开启"),
CreateAction(IgniterAction, $"{IgniterAction}:关闭", $"{IgniterAction}:开启"),
CreateAction(SamplingPumpAction, $"{SamplingPumpAction}:关闭", $"{SamplingPumpAction}:开启")
];
BottomActions =
[
new CValueCalibrationActionViewModel("标定开始", ActionCommand),
new CValueCalibrationActionViewModel("基线采集", ActionCommand)
CreateAction(CalibrationStartAction, CalibrationStartAction, "标定结束"),
CreateAction(BaselineCollectionAction, BaselineCollectionAction, "基线结束")
];
RefreshDeviceValues();
@@ -121,13 +136,13 @@ public sealed class CValueCalibrationViewModel : PageViewModel
LastAction = action;
if (IsPulseAction(action))
if (IsPairedAction(action))
{
_ = PulseActionCoilAsync(action);
TogglePairedAction(action);
return;
}
WriteActionCoil(action);
ToggleHoldAction(action);
}
private void RefreshDeviceValues()
@@ -139,6 +154,7 @@ public sealed class CValueCalibrationViewModel : PageViewModel
PressureDifferenceText = ReadFloatText(PressureDifferenceRegister);
CalibrationOxygenText = oxygenText;
CValueText = ReadFloatText(CValueRegister);
RefreshActionStates();
}
private string ReadFloatText(ushort registerAddress)
@@ -148,61 +164,124 @@ public sealed class CValueCalibrationViewModel : PageViewModel
: string.Empty;
}
private void WriteActionCoil(string action)
private CValueCalibrationActionViewModel CreateAction(
string label,
string inactiveDisplayText,
string activeDisplayText)
{
if (!TryGetActionCoil(action, out var coilAddress))
{
return;
}
if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
{
Debug.WriteLine($"C value calibration action '{action}' write failed.");
}
var action = new CValueCalibrationActionViewModel(label, ActionCommand, inactiveDisplayText, activeDisplayText);
_actionsByLabel[label] = action;
return action;
}
private async Task PulseActionCoilAsync(string action)
private void RefreshActionStates()
{
if (!TryGetActionCoil(action, out var coilAddress))
{
return;
}
if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
{
Debug.WriteLine($"C value calibration action '{action}' pulse start failed.");
return;
}
await Task.Delay(PulseDelay);
if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, false))
{
Debug.WriteLine($"C value calibration action '{action}' pulse reset failed.");
}
UpdateActionStatus(CirculatingWaterAction, CirculatingWaterCoil);
UpdateActionStatus(MethaneValveAction, MethaneValveCoil);
UpdateActionStatus(FanAction, FanCoil);
UpdateActionStatus(IgniterAction, IgniterCoil);
UpdateActionStatus(SamplingPumpAction, SamplingPumpCoil);
UpdateActionStatus(BaselineCollectionAction, BaselineCollectionCoil);
UpdateActionStatus(CalibrationStartAction, CalibrationStartCoil);
}
private static bool TryGetActionCoil(string action, out ushort coilAddress)
private bool UpdateActionStatus(string action, ushort coilAddress)
{
if (!_actionsByLabel.TryGetValue(action, out var actionViewModel))
{
return false;
}
if (_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive))
{
actionViewModel.UpdateStatus(isActive);
return true;
}
actionViewModel.UpdateStatus(actionViewModel.IsActive, false);
return false;
}
private void ToggleHoldAction(string action)
{
if (!TryGetHoldActionCoil(action, out var coilAddress))
{
return;
}
if (!_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive))
{
LastAction = $"{action}状态读取失败";
Debug.WriteLine($"C value calibration action '{action}' state read failed.");
return;
}
var nextValue = !isActive;
if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, nextValue))
{
LastAction = $"{action}{(nextValue ? "" : "")}";
if (!UpdateActionStatus(action, coilAddress) && _actionsByLabel.TryGetValue(action, out var actionViewModel))
{
actionViewModel.UpdateStatus(nextValue);
}
return;
}
LastAction = $"{action}{(nextValue ? "" : "")}失败";
Debug.WriteLine($"C value calibration action '{action}' write failed.");
}
private void TogglePairedAction(string action)
{
if (!TryGetPairedActionCoils(action, out var startCoilAddress, out var endCoilAddress, out var endActionText))
{
return;
}
if (!_tcpDeviceConnectionService.TryReadCoil(startCoilAddress, out var isActive))
{
LastAction = $"{action}状态读取失败";
Debug.WriteLine($"C value calibration action '{action}' state read failed.");
return;
}
var coilAddress = isActive ? endCoilAddress : startCoilAddress;
var actionText = isActive ? endActionText : action;
if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
{
LastAction = actionText;
if (_actionsByLabel.TryGetValue(action, out var actionViewModel))
{
actionViewModel.UpdateStatus(!isActive);
}
return;
}
LastAction = $"{actionText}失败";
Debug.WriteLine($"C value calibration action '{actionText}' write failed.");
}
private static bool TryGetHoldActionCoil(string action, out ushort coilAddress)
{
switch (action)
{
case "甲烷阀":
coilAddress = 55;
case CirculatingWaterAction:
coilAddress = CirculatingWaterCoil;
return true;
case "风机":
coilAddress = 54;
case MethaneValveAction:
coilAddress = MethaneValveCoil;
return true;
case "点火器":
coilAddress = 53;
case FanAction:
coilAddress = FanCoil;
return true;
case "取样泵":
coilAddress = 50;
case IgniterAction:
coilAddress = IgniterCoil;
return true;
case BaselineCollectionAction:
coilAddress = 60;
return true;
case CalibrationStartAction:
coilAddress = 70;
case SamplingPumpAction:
coilAddress = SamplingPumpCoil;
return true;
default:
coilAddress = 0;
@@ -210,7 +289,33 @@ public sealed class CValueCalibrationViewModel : PageViewModel
}
}
private static bool IsPulseAction(string action)
private static bool TryGetPairedActionCoils(
string action,
out ushort startCoilAddress,
out ushort endCoilAddress,
out string endActionText)
{
switch (action)
{
case BaselineCollectionAction:
startCoilAddress = BaselineCollectionCoil;
endCoilAddress = BaselineEndCoil;
endActionText = "基线结束";
return true;
case CalibrationStartAction:
startCoilAddress = CalibrationStartCoil;
endCoilAddress = CalibrationEndCoil;
endActionText = "标定结束";
return true;
default:
startCoilAddress = 0;
endCoilAddress = 0;
endActionText = string.Empty;
return false;
}
}
private static bool IsPairedAction(string action)
{
return action is BaselineCollectionAction or CalibrationStartAction;
}

View File

@@ -14,6 +14,8 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
private const ushort SlopeRegister = 420;
private const ushort InterceptRegister = 422;
private const ushort AlarmCoil = 91;
private const ushort CirculatingWaterCoil = 49;
private const string CirculatingWaterAction = "循环水";
private readonly Action _closeAction;
private readonly Action _helpAction;
@@ -27,6 +29,7 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
private string _interceptText = "";
private string _lastAction = "待机";
private bool _alarmActive;
private bool _circulatingWaterActive;
private bool _isEditingConeParameters;
public ConeRadiationSettingsViewModel(
@@ -107,6 +110,20 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
private set => SetProperty(ref _alarmActive, value);
}
public bool CirculatingWaterActive
{
get => _circulatingWaterActive;
private set
{
if (SetProperty(ref _circulatingWaterActive, value))
{
OnPropertyChanged(nameof(CirculatingWaterButtonText));
}
}
}
public string CirculatingWaterButtonText => CirculatingWaterActive ? "循环水:开启" : "循环水:关闭";
public void BeginConeParameterEdit()
{
_isEditingConeParameters = true;
@@ -126,6 +143,12 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
LastAction = action;
if (action == CirculatingWaterAction)
{
ToggleCirculatingWater();
return;
}
if (action == "开始升温")
{
WriteTargetTemperature();
@@ -147,6 +170,10 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
}
AlarmActive = _tcpDeviceConnectionService.TryReadCoil(AlarmCoil, out var alarmActive) && alarmActive;
if (_tcpDeviceConnectionService.TryReadCoil(CirculatingWaterCoil, out var circulatingWaterActive))
{
CirculatingWaterActive = circulatingWaterActive;
}
}
private string ReadFloatText(ushort registerAddress)
@@ -200,6 +227,27 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
Debug.WriteLine($"Cone radiation parameters write failed. Slope: {slopeWritten}, intercept: {interceptWritten}.");
}
private void ToggleCirculatingWater()
{
if (!_tcpDeviceConnectionService.TryReadCoil(CirculatingWaterCoil, out var isActive))
{
LastAction = "循环水状态读取失败";
Debug.WriteLine("Cone radiation circulating water state read failed.");
return;
}
var nextValue = !isActive;
if (_tcpDeviceConnectionService.TryWriteCoil(CirculatingWaterCoil, nextValue))
{
CirculatingWaterActive = nextValue;
LastAction = $"循环水{(nextValue ? "" : "")}";
return;
}
LastAction = $"循环水{(nextValue ? "" : "")}失败";
Debug.WriteLine("Cone radiation circulating water write failed.");
}
private void WriteActionCoil(string action)
{
if (!TryGetActionCoil(action, out var coilAddress))
@@ -218,9 +266,6 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
{
switch (action)
{
case "循环水":
coilAddress = 49;
return true;
case "开始升温":
coilAddress = 100;
return true;

View File

@@ -16,6 +16,26 @@
<Setter Property="Foreground" Value="#071614" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="StatusActionButtonStyle"
TargetType="Button"
BasedOn="{StaticResource InstrumentButtonStyle}">
<Setter Property="Background" Value="#EEF1EF" />
<Setter Property="BorderBrush" Value="#A9B1AD" />
<Setter Property="Foreground" Value="#172320" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Background" Value="#DDEFE6" />
<Setter Property="BorderBrush" Value="#3F8B63" />
<Setter Property="Foreground" Value="#0D3B24" />
</DataTrigger>
<DataTrigger Binding="{Binding IsStatusKnown}" Value="False">
<Setter Property="Background" Value="#E5E7E6" />
<Setter Property="BorderBrush" Value="#B8BFBB" />
<Setter Property="Foreground" Value="#68736E" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid Background="#F4F6F5">
@@ -209,13 +229,13 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Label}"
<Button Content="{Binding DisplayText}"
Command="{Binding Command}"
CommandParameter="{Binding Label}"
Width="118"
Width="150"
Height="42"
Margin="0,5,24,5"
Style="{StaticResource InstrumentButtonStyle}" />
Margin="0,5,14,5"
Style="{StaticResource StatusActionButtonStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
@@ -230,13 +250,13 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Label}"
<Button Content="{Binding DisplayText}"
Command="{Binding Command}"
CommandParameter="{Binding Label}"
Width="138"
Width="150"
Height="42"
Margin="0,5,24,5"
Style="{StaticResource InstrumentButtonStyle}" />
Style="{StaticResource StatusActionButtonStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

View File

@@ -317,12 +317,24 @@
Style="{StaticResource InstrumentButtonStyle}"
VerticalAlignment="Center" />
<Button Grid.Column="8"
Content="循环水"
Content="{Binding CirculatingWaterButtonText}"
Command="{Binding ActionCommand}"
CommandParameter="循环水"
Height="42"
Style="{StaticResource InstrumentPrimaryButtonStyle}"
VerticalAlignment="Center" />
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button"
BasedOn="{StaticResource InstrumentPrimaryButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CirculatingWaterActive}" Value="True">
<Setter Property="Background" Value="#DDEFE6" />
<Setter Property="BorderBrush" Value="#3F8B63" />
<Setter Property="Foreground" Value="#0D3B24" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Grid>
</UserControl>