更新数据

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

View File

@@ -1,16 +1,70 @@
using System.Windows.Input; using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
namespace ConeCalorimeter.ViewModels; 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; Label = label;
Command = command; Command = command;
InactiveDisplayText = inactiveDisplayText;
ActiveDisplayText = activeDisplayText;
} }
public string Label { get; } public string Label { get; }
public string InactiveDisplayText { get; }
public string ActiveDisplayText { get; }
public ICommand Command { 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 PressureDifferenceRegister = 284;
private const ushort OxygenRegister = 286; private const ushort OxygenRegister = 286;
private const ushort CValueRegister = 308; 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 BaselineCollectionAction = "基线采集";
private const string CalibrationStartAction = "标定开始"; private const string CalibrationStartAction = "标定开始";
private static readonly TimeSpan PulseDelay = TimeSpan.FromMilliseconds(300);
private readonly Action _closeAction; private readonly Action _closeAction;
private readonly Action _helpAction; private readonly Action _helpAction;
private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService; private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
private readonly DispatcherTimer _refreshTimer; private readonly DispatcherTimer _refreshTimer;
private readonly Dictionary<string, CValueCalibrationActionViewModel> _actionsByLabel = [];
private string _baselineOxygenText = ""; private string _baselineOxygenText = "";
private string _temperatureText = ""; private string _temperatureText = "";
private string _pressureDifferenceText = ""; private string _pressureDifferenceText = "";
@@ -43,16 +57,17 @@ public sealed class CValueCalibrationViewModel : PageViewModel
TopActions = TopActions =
[ [
new CValueCalibrationActionViewModel("甲烷阀", ActionCommand), CreateAction(CirculatingWaterAction, $"{CirculatingWaterAction}:关闭", $"{CirculatingWaterAction}:开启"),
new CValueCalibrationActionViewModel("风机", ActionCommand), CreateAction(MethaneValveAction, $"{MethaneValveAction}:关闭", $"{MethaneValveAction}:开启"),
new CValueCalibrationActionViewModel("点火器", ActionCommand), CreateAction(FanAction, $"{FanAction}:关闭", $"{FanAction}:开启"),
new CValueCalibrationActionViewModel("取样泵", ActionCommand) CreateAction(IgniterAction, $"{IgniterAction}:关闭", $"{IgniterAction}:开启"),
CreateAction(SamplingPumpAction, $"{SamplingPumpAction}:关闭", $"{SamplingPumpAction}:开启")
]; ];
BottomActions = BottomActions =
[ [
new CValueCalibrationActionViewModel("标定开始", ActionCommand), CreateAction(CalibrationStartAction, CalibrationStartAction, "标定结束"),
new CValueCalibrationActionViewModel("基线采集", ActionCommand) CreateAction(BaselineCollectionAction, BaselineCollectionAction, "基线结束")
]; ];
RefreshDeviceValues(); RefreshDeviceValues();
@@ -121,13 +136,13 @@ public sealed class CValueCalibrationViewModel : PageViewModel
LastAction = action; LastAction = action;
if (IsPulseAction(action)) if (IsPairedAction(action))
{ {
_ = PulseActionCoilAsync(action); TogglePairedAction(action);
return; return;
} }
WriteActionCoil(action); ToggleHoldAction(action);
} }
private void RefreshDeviceValues() private void RefreshDeviceValues()
@@ -139,6 +154,7 @@ public sealed class CValueCalibrationViewModel : PageViewModel
PressureDifferenceText = ReadFloatText(PressureDifferenceRegister); PressureDifferenceText = ReadFloatText(PressureDifferenceRegister);
CalibrationOxygenText = oxygenText; CalibrationOxygenText = oxygenText;
CValueText = ReadFloatText(CValueRegister); CValueText = ReadFloatText(CValueRegister);
RefreshActionStates();
} }
private string ReadFloatText(ushort registerAddress) private string ReadFloatText(ushort registerAddress)
@@ -148,61 +164,124 @@ public sealed class CValueCalibrationViewModel : PageViewModel
: string.Empty; : string.Empty;
} }
private void WriteActionCoil(string action) private CValueCalibrationActionViewModel CreateAction(
string label,
string inactiveDisplayText,
string activeDisplayText)
{ {
if (!TryGetActionCoil(action, out var coilAddress)) var action = new CValueCalibrationActionViewModel(label, ActionCommand, inactiveDisplayText, activeDisplayText);
_actionsByLabel[label] = action;
return action;
}
private void RefreshActionStates()
{
UpdateActionStatus(CirculatingWaterAction, CirculatingWaterCoil);
UpdateActionStatus(MethaneValveAction, MethaneValveCoil);
UpdateActionStatus(FanAction, FanCoil);
UpdateActionStatus(IgniterAction, IgniterCoil);
UpdateActionStatus(SamplingPumpAction, SamplingPumpCoil);
UpdateActionStatus(BaselineCollectionAction, BaselineCollectionCoil);
UpdateActionStatus(CalibrationStartAction, CalibrationStartCoil);
}
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; return;
} }
if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true)) 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."); Debug.WriteLine($"C value calibration action '{action}' write failed.");
} }
}
private async Task PulseActionCoilAsync(string action) private void TogglePairedAction(string action)
{ {
if (!TryGetActionCoil(action, out var coilAddress)) if (!TryGetPairedActionCoils(action, out var startCoilAddress, out var endCoilAddress, out var endActionText))
{ {
return; return;
} }
if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true)) if (!_tcpDeviceConnectionService.TryReadCoil(startCoilAddress, out var isActive))
{ {
Debug.WriteLine($"C value calibration action '{action}' pulse start failed."); LastAction = $"{action}状态读取失败";
Debug.WriteLine($"C value calibration action '{action}' state read failed.");
return; return;
} }
await Task.Delay(PulseDelay); var coilAddress = isActive ? endCoilAddress : startCoilAddress;
var actionText = isActive ? endActionText : action;
if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, false)) if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
{ {
Debug.WriteLine($"C value calibration action '{action}' pulse reset failed."); LastAction = actionText;
} if (_actionsByLabel.TryGetValue(action, out var actionViewModel))
{
actionViewModel.UpdateStatus(!isActive);
} }
private static bool TryGetActionCoil(string action, out ushort coilAddress) return;
}
LastAction = $"{actionText}失败";
Debug.WriteLine($"C value calibration action '{actionText}' write failed.");
}
private static bool TryGetHoldActionCoil(string action, out ushort coilAddress)
{ {
switch (action) switch (action)
{ {
case "甲烷阀": case CirculatingWaterAction:
coilAddress = 55; coilAddress = CirculatingWaterCoil;
return true; return true;
case "风机": case MethaneValveAction:
coilAddress = 54; coilAddress = MethaneValveCoil;
return true; return true;
case "点火器": case FanAction:
coilAddress = 53; coilAddress = FanCoil;
return true; return true;
case "取样泵": case IgniterAction:
coilAddress = 50; coilAddress = IgniterCoil;
return true; return true;
case BaselineCollectionAction: case SamplingPumpAction:
coilAddress = 60; coilAddress = SamplingPumpCoil;
return true;
case CalibrationStartAction:
coilAddress = 70;
return true; return true;
default: default:
coilAddress = 0; 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; 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 SlopeRegister = 420;
private const ushort InterceptRegister = 422; private const ushort InterceptRegister = 422;
private const ushort AlarmCoil = 91; private const ushort AlarmCoil = 91;
private const ushort CirculatingWaterCoil = 49;
private const string CirculatingWaterAction = "循环水";
private readonly Action _closeAction; private readonly Action _closeAction;
private readonly Action _helpAction; private readonly Action _helpAction;
@@ -27,6 +29,7 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
private string _interceptText = ""; private string _interceptText = "";
private string _lastAction = "待机"; private string _lastAction = "待机";
private bool _alarmActive; private bool _alarmActive;
private bool _circulatingWaterActive;
private bool _isEditingConeParameters; private bool _isEditingConeParameters;
public ConeRadiationSettingsViewModel( public ConeRadiationSettingsViewModel(
@@ -107,6 +110,20 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
private set => SetProperty(ref _alarmActive, value); 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() public void BeginConeParameterEdit()
{ {
_isEditingConeParameters = true; _isEditingConeParameters = true;
@@ -126,6 +143,12 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
LastAction = action; LastAction = action;
if (action == CirculatingWaterAction)
{
ToggleCirculatingWater();
return;
}
if (action == "开始升温") if (action == "开始升温")
{ {
WriteTargetTemperature(); WriteTargetTemperature();
@@ -147,6 +170,10 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
} }
AlarmActive = _tcpDeviceConnectionService.TryReadCoil(AlarmCoil, out var alarmActive) && alarmActive; AlarmActive = _tcpDeviceConnectionService.TryReadCoil(AlarmCoil, out var alarmActive) && alarmActive;
if (_tcpDeviceConnectionService.TryReadCoil(CirculatingWaterCoil, out var circulatingWaterActive))
{
CirculatingWaterActive = circulatingWaterActive;
}
} }
private string ReadFloatText(ushort registerAddress) 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}."); 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) private void WriteActionCoil(string action)
{ {
if (!TryGetActionCoil(action, out var coilAddress)) if (!TryGetActionCoil(action, out var coilAddress))
@@ -218,9 +266,6 @@ public sealed class ConeRadiationSettingsViewModel : PageViewModel
{ {
switch (action) switch (action)
{ {
case "循环水":
coilAddress = 49;
return true;
case "开始升温": case "开始升温":
coilAddress = 100; coilAddress = 100;
return true; return true;

View File

@@ -16,6 +16,26 @@
<Setter Property="Foreground" Value="#071614" /> <Setter Property="Foreground" Value="#071614" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
</Style> </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> </UserControl.Resources>
<Grid Background="#F4F6F5"> <Grid Background="#F4F6F5">
@@ -209,13 +229,13 @@
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Button Content="{Binding Label}" <Button Content="{Binding DisplayText}"
Command="{Binding Command}" Command="{Binding Command}"
CommandParameter="{Binding Label}" CommandParameter="{Binding Label}"
Width="118" Width="150"
Height="42" Height="42"
Margin="0,5,24,5" Margin="0,5,14,5"
Style="{StaticResource InstrumentButtonStyle}" /> Style="{StaticResource StatusActionButtonStyle}" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
@@ -230,13 +250,13 @@
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Button Content="{Binding Label}" <Button Content="{Binding DisplayText}"
Command="{Binding Command}" Command="{Binding Command}"
CommandParameter="{Binding Label}" CommandParameter="{Binding Label}"
Width="138" Width="150"
Height="42" Height="42"
Margin="0,5,24,5" Margin="0,5,24,5"
Style="{StaticResource InstrumentButtonStyle}" /> Style="{StaticResource StatusActionButtonStyle}" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>

View File

@@ -317,12 +317,24 @@
Style="{StaticResource InstrumentButtonStyle}" Style="{StaticResource InstrumentButtonStyle}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
<Button Grid.Column="8" <Button Grid.Column="8"
Content="循环水" Content="{Binding CirculatingWaterButtonText}"
Command="{Binding ActionCommand}" Command="{Binding ActionCommand}"
CommandParameter="循环水" CommandParameter="循环水"
Height="42" 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>
</Grid> </Grid>
</UserControl> </UserControl>