更新
This commit is contained in:
@@ -394,14 +394,14 @@
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="水平位移" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding Recipe.TravelMm, Mode=TwoWay, StringFormat=F0 mm}" />
|
||||
Text="{Binding Recipe.TravelMm, Mode=TwoWay, StringFormat={}{0:F0} mm}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="水平速度" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding Recipe.SpeedMmPerMin, Mode=TwoWay, StringFormat=F0 mm/min}" />
|
||||
Text="{Binding Recipe.SpeedMmPerMin, Mode=TwoWay, StringFormat={}{0:F0} mm/min}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UniformGrid>
|
||||
@@ -612,17 +612,6 @@
|
||||
<TextBlock VerticalAlignment="Center" FontWeight="SemiBold" Text="暂停" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,0,10,10">
|
||||
<DockPanel>
|
||||
<Ellipse Width="12"
|
||||
Height="12"
|
||||
Fill="{Binding InterlockIndicatorBrush, Mode=OneWay}"
|
||||
Stroke="#8DA0AE"
|
||||
StrokeThickness="1"
|
||||
Margin="0,2,8,0" />
|
||||
<TextBlock VerticalAlignment="Center" FontWeight="SemiBold" Text="联锁" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,0,0,10">
|
||||
<DockPanel>
|
||||
<Ellipse Width="12"
|
||||
@@ -672,10 +661,6 @@
|
||||
Style="{StaticResource PrimaryButtonStyle}"
|
||||
Command="{Binding PlcStartCommand}"
|
||||
Content="{Binding StartButtonText, Mode=OneWay}" />
|
||||
<Button Width="132"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Command="{Binding PauseCommand}"
|
||||
Content="暂停" />
|
||||
<Button Width="132"
|
||||
Style="{StaticResource DangerButtonStyle}"
|
||||
Command="{Binding PlcStopCommand}"
|
||||
@@ -696,19 +681,31 @@
|
||||
<WrapPanel>
|
||||
<Button Width="108"
|
||||
Style="{StaticResource CompactButtonStyle}"
|
||||
Command="{Binding RiseCommand}"
|
||||
Tag="Up"
|
||||
PreviewMouseLeftButtonDown="TableMotionButton_PreviewMouseLeftButtonDown"
|
||||
PreviewMouseLeftButtonUp="TableMotionButton_PreviewMouseLeftButtonUp"
|
||||
LostMouseCapture="TableMotionButton_LostMouseCapture"
|
||||
Content="上升" />
|
||||
<Button Width="108"
|
||||
Style="{StaticResource CompactButtonStyle}"
|
||||
Command="{Binding LowerCommand}"
|
||||
Tag="Down"
|
||||
PreviewMouseLeftButtonDown="TableMotionButton_PreviewMouseLeftButtonDown"
|
||||
PreviewMouseLeftButtonUp="TableMotionButton_PreviewMouseLeftButtonUp"
|
||||
LostMouseCapture="TableMotionButton_LostMouseCapture"
|
||||
Content="下降" />
|
||||
<Button Width="108"
|
||||
Style="{StaticResource CompactButtonStyle}"
|
||||
Command="{Binding BackCommand}"
|
||||
Tag="Left"
|
||||
PreviewMouseLeftButtonDown="TableMotionButton_PreviewMouseLeftButtonDown"
|
||||
PreviewMouseLeftButtonUp="TableMotionButton_PreviewMouseLeftButtonUp"
|
||||
LostMouseCapture="TableMotionButton_LostMouseCapture"
|
||||
Content="后退" />
|
||||
<Button Width="108"
|
||||
Style="{StaticResource CompactButtonStyle}"
|
||||
Command="{Binding ForwardCommand}"
|
||||
Tag="Right"
|
||||
PreviewMouseLeftButtonDown="TableMotionButton_PreviewMouseLeftButtonDown"
|
||||
PreviewMouseLeftButtonUp="TableMotionButton_PreviewMouseLeftButtonUp"
|
||||
LostMouseCapture="TableMotionButton_LostMouseCapture"
|
||||
Content="前进" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
@@ -837,28 +834,28 @@
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="实时静摩擦系数" />
|
||||
<TextBlock Style="{StaticResource MetricValueStyle}"
|
||||
Text="{Binding CurrentStaticCoefficient, Mode=OneWay, StringFormat=F3}" />
|
||||
Text="{Binding CurrentStaticCoefficient, Mode=OneWay, StringFormat={}{0:F3}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource MetricCardStyle}" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="实时动摩擦系数" />
|
||||
<TextBlock Style="{StaticResource MetricValueStyle}"
|
||||
Text="{Binding CurrentKineticCoefficient, Mode=OneWay, StringFormat=F3}" />
|
||||
Text="{Binding CurrentKineticCoefficient, Mode=OneWay, StringFormat={}{0:F3}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource MetricCardStyle}" Margin="0,0,10,0">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="峰值力" />
|
||||
<TextBlock Style="{StaticResource MetricValueStyle}"
|
||||
Text="{Binding CurrentPeakForceN, Mode=OneWay, StringFormat=F3 N}" />
|
||||
Text="{Binding CurrentPeakForceN, Mode=OneWay, StringFormat={}{0:F3} N}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource MetricCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="稳定段平均力" />
|
||||
<TextBlock Style="{StaticResource MetricValueStyle}"
|
||||
Text="{Binding CurrentAverageForceN, Mode=OneWay, StringFormat=F3 N}" />
|
||||
Text="{Binding CurrentAverageForceN, Mode=OneWay, StringFormat={}{0:F3} N}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UniformGrid>
|
||||
@@ -873,32 +870,46 @@
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="实时力值" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding CurrentForceN, Mode=OneWay, StringFormat=F3 N}" />
|
||||
Text="{Binding CurrentForceN, Mode=OneWay, StringFormat={}{0:F3} N}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="水平速度" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding CurrentSpeedMmPerMin, Mode=OneWay, StringFormat=F1 mm/min}" />
|
||||
Text="{Binding CurrentSpeedMmPerMin, Mode=OneWay, StringFormat={}{0:F1} mm/min}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,0,10,0">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="升降速度" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding CurrentLiftSpeed, Mode=OneWay, StringFormat=F1 mm/min}" />
|
||||
Text="{Binding CurrentLiftSpeed, Mode=OneWay, StringFormat={}{0:F1} mm/min}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="批次统计" />
|
||||
<TextBlock FontWeight="SemiBold"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding TraceabilitySummary, Mode=OneWay}" />
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="升降位置" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding CurrentLiftPosition, Mode=OneWay, StringFormat={}{0:F1} mm}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,0,10,0">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="水平位置" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding CurrentHorizontalPosition, Mode=OneWay, StringFormat={}{0:F1} mm}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UniformGrid>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,14,0,0">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="批次统计" />
|
||||
<TextBlock FontWeight="SemiBold"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding TraceabilitySummary, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,14,0,0">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="导出摘要" />
|
||||
@@ -912,28 +923,28 @@
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="静摩擦系数 1" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding StaticCoefficient1, Mode=OneWay, StringFormat=F3}" />
|
||||
Text="{Binding StaticCoefficient1, Mode=OneWay, StringFormat={}{0:F3}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="静摩擦系数 2" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding StaticCoefficient2, Mode=OneWay, StringFormat=F3}" />
|
||||
Text="{Binding StaticCoefficient2, Mode=OneWay, StringFormat={}{0:F3}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}" Margin="0,0,10,0">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="动摩擦系数 1" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding KineticCoefficient1, Mode=OneWay, StringFormat=F3}" />
|
||||
Text="{Binding KineticCoefficient1, Mode=OneWay, StringFormat={}{0:F3}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource InsetCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MetricLabelStyle}" Text="动摩擦系数 2" />
|
||||
<TextBlock Style="{StaticResource SmallMetricValueStyle}"
|
||||
Text="{Binding KineticCoefficient2, Mode=OneWay, StringFormat=F3}" />
|
||||
Text="{Binding KineticCoefficient2, Mode=OneWay, StringFormat={}{0:F3}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UniformGrid>
|
||||
@@ -1070,8 +1081,8 @@
|
||||
<DataGridTextColumn Header="轮次" Binding="{Binding RunIndex}" Width="0.8*" />
|
||||
<DataGridTextColumn Header="时间" Binding="{Binding CompletedAtLabel}" Width="1.2*" />
|
||||
<DataGridTextColumn Header="批次" Binding="{Binding BatchNumber}" Width="1.1*" />
|
||||
<DataGridTextColumn Header="μs" Binding="{Binding StaticCoefficient, StringFormat=F3}" Width="0.9*" />
|
||||
<DataGridTextColumn Header="μk" Binding="{Binding KineticCoefficient, StringFormat=F3}" Width="0.9*" />
|
||||
<DataGridTextColumn Header="μs" Binding="{Binding StaticCoefficient, StringFormat={}{0:F3}}" Width="0.9*" />
|
||||
<DataGridTextColumn Header="μk" Binding="{Binding KineticCoefficient, StringFormat={}{0:F3}}" Width="0.9*" />
|
||||
<DataGridTextColumn Header="模式" Binding="{Binding TestMode}" Width="1.1*" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using COFTester.ViewModels;
|
||||
|
||||
namespace COFTester;
|
||||
@@ -39,6 +41,44 @@ public partial class MainWindow : Window
|
||||
MainWorkspaceTabs.SelectedIndex = 1;
|
||||
}
|
||||
|
||||
private void TableMotionButton_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not Button button || button.Tag is not string direction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
button.CaptureMouse();
|
||||
_viewModel.BeginTableMotion(direction);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void TableMotionButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
EndTableMotionFromButton(sender);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void TableMotionButton_LostMouseCapture(object sender, MouseEventArgs e)
|
||||
{
|
||||
EndTableMotionFromButton(sender);
|
||||
}
|
||||
|
||||
private void EndTableMotionFromButton(object sender)
|
||||
{
|
||||
if (sender is not Button button || button.Tag is not string direction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_viewModel.EndTableMotion(direction);
|
||||
|
||||
if (Mouse.Captured == button)
|
||||
{
|
||||
button.ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSidebarCollapsed(bool collapsed)
|
||||
{
|
||||
_isSidebarCollapsed = collapsed;
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace COFTester.Models;
|
||||
|
||||
public sealed class TestRecipe : ObservableObject
|
||||
{
|
||||
private string _productCode = string.Empty;
|
||||
private string _batchNumber = string.Empty;
|
||||
public const string DefaultProductCode = "COF-DEFAULT";
|
||||
|
||||
private string _productCode = DefaultProductCode;
|
||||
private string _batchNumber = CreateDefaultBatchNumber();
|
||||
private string _testMode = "膜/膜";
|
||||
private string _counterfaceMaterial = "同材质";
|
||||
private string _direction = "MD";
|
||||
@@ -88,4 +90,22 @@ public sealed class TestRecipe : ObservableObject
|
||||
get => _specimenDescription;
|
||||
set => SetProperty(ref _specimenDescription, value);
|
||||
}
|
||||
|
||||
public void ApplyDefaultIdentifiers()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ProductCode))
|
||||
{
|
||||
ProductCode = DefaultProductCode;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BatchNumber))
|
||||
{
|
||||
BatchNumber = CreateDefaultBatchNumber();
|
||||
}
|
||||
}
|
||||
|
||||
public static string CreateDefaultBatchNumber()
|
||||
{
|
||||
return $"BATCH-{DateTime.Now:yyyyMMdd}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
private bool _isPlcCommandBusy;
|
||||
private bool _isSyncingRecipeFromPlc;
|
||||
private bool _activeRunStartedByPlc;
|
||||
private ushort? _activeTableMotionCoil;
|
||||
private ushort? _pendingTableMotionStopCoil;
|
||||
private MachineRuntimeState _machineRuntimeState = MachineRuntimeState.Idle;
|
||||
private string _plcCommandSummary = "等待 PLC 控制指令。";
|
||||
|
||||
@@ -319,6 +321,45 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
|
||||
public RelayCommand ClearHistoryCommand => _clearHistoryCommand;
|
||||
|
||||
public void BeginTableMotion(string direction)
|
||||
{
|
||||
if (!TryGetTableMotionCoil(direction, out var actionName, out var coilAddress))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_activeTableMotionCoil is not null || _isPlcCommandBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_activeTableMotionCoil = coilAddress;
|
||||
_pendingTableMotionStopCoil = null;
|
||||
_ = SetTableMotionCoilAsync(actionName, coilAddress, value: true);
|
||||
}
|
||||
|
||||
public void EndTableMotion(string direction)
|
||||
{
|
||||
if (!TryGetTableMotionCoil(direction, out var actionName, out var coilAddress))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_activeTableMotionCoil != coilAddress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isPlcCommandBusy)
|
||||
{
|
||||
_pendingTableMotionStopCoil = coilAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
_activeTableMotionCoil = null;
|
||||
_ = SetTableMotionCoilAsync(actionName, coilAddress, value: false);
|
||||
}
|
||||
|
||||
public string MachineState
|
||||
{
|
||||
get => _machineState;
|
||||
@@ -603,6 +644,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
OnPropertyChanged(nameof(DeviceLastConnectedAtLabel));
|
||||
AddInfoEvent($"设备通信已连接: {_deviceConnectionService.Endpoint}");
|
||||
await SyncRecipeFromPlcAsync();
|
||||
_deviceDataReader.Initialize(Recipe);
|
||||
EnsureRealtimeMonitorTimerRunning();
|
||||
RaiseCommandStates();
|
||||
|
||||
if (_machineRuntimeState == MachineRuntimeState.Idle)
|
||||
@@ -681,6 +724,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
|
||||
private bool TryValidateStartOrResume()
|
||||
{
|
||||
Recipe.ApplyDefaultIdentifiers();
|
||||
|
||||
var validationErrors = ValidateRecipe().ToArray();
|
||||
if (validationErrors.Length == 0)
|
||||
{
|
||||
@@ -768,7 +813,6 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
_timer.Stop();
|
||||
_machineRuntimeState = MachineRuntimeState.Paused;
|
||||
MachineState = "暂停";
|
||||
StateBrush = BrushFromHex("#C49645");
|
||||
@@ -779,7 +823,6 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
|
||||
private void Stop()
|
||||
{
|
||||
_timer.Stop();
|
||||
_isShowingHistoricalRun = false;
|
||||
_displayedRecipeSnapshot = _activeRecipeSnapshot ?? TestRecipeSnapshot.FromRecipe(Recipe);
|
||||
ResetActiveRunContext();
|
||||
@@ -840,7 +883,6 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
_timer.Stop();
|
||||
_isShowingHistoricalRun = false;
|
||||
_displayedRecipeSnapshot = _activeRecipeSnapshot ?? TestRecipeSnapshot.FromRecipe(Recipe);
|
||||
ResetActiveRunContext();
|
||||
@@ -877,20 +919,17 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
try
|
||||
{
|
||||
var frame = await ReadRealtimeFrameAsync();
|
||||
if (!_isShowingHistoricalRun)
|
||||
{
|
||||
UpdateLiveProcessSnapshot(frame);
|
||||
}
|
||||
|
||||
if (_machineRuntimeState != MachineRuntimeState.Running)
|
||||
{
|
||||
RaiseStatusProperties();
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentForceN = Math.Max(frame.ForceN, 0.001);
|
||||
CurrentDisplacementMm = frame.DisplacementMm;
|
||||
CurrentSpeedMmPerMin = frame.SpeedMmPerMin;
|
||||
CurrentLiftSpeed = frame.LiftSpeed;
|
||||
CurrentLiftDisplacement = frame.LiftDisplacement;
|
||||
CurrentLiftPosition = frame.LiftPosition;
|
||||
CurrentHorizontalPosition = frame.HorizontalPosition;
|
||||
|
||||
_forceSamples.Add(new ObservablePoint(frame.DisplacementMm, CurrentForceN));
|
||||
_currentRunSamples.Add(new RawSampleRecord
|
||||
{
|
||||
@@ -931,7 +970,14 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleRealtimeSamplingFailure(ex);
|
||||
if (_machineRuntimeState == MachineRuntimeState.Running || HasRecoverableActiveRunContext())
|
||||
{
|
||||
HandleRealtimeSamplingFailure(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleRealtimeMonitorFailure(ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -939,10 +985,19 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLiveProcessSnapshot(ProcessFrame frame)
|
||||
{
|
||||
CurrentForceN = Math.Max(frame.ForceN, 0.001);
|
||||
CurrentDisplacementMm = frame.DisplacementMm;
|
||||
CurrentSpeedMmPerMin = frame.SpeedMmPerMin;
|
||||
CurrentLiftSpeed = frame.LiftSpeed;
|
||||
CurrentLiftDisplacement = frame.LiftDisplacement;
|
||||
CurrentLiftPosition = frame.LiftPosition;
|
||||
CurrentHorizontalPosition = frame.HorizontalPosition;
|
||||
}
|
||||
|
||||
private void FinalizeRun()
|
||||
{
|
||||
_timer.Stop();
|
||||
|
||||
var record = new RunRecord
|
||||
{
|
||||
RunId = _activeRunId,
|
||||
@@ -1071,7 +1126,104 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
|
||||
private bool CanExecutePlcCommand()
|
||||
{
|
||||
return !_isPlcCommandBusy;
|
||||
return !_isPlcCommandBusy && _activeTableMotionCoil is null;
|
||||
}
|
||||
|
||||
private static bool TryGetTableMotionCoil(string direction, out string actionName, out ushort coilAddress)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case "Up":
|
||||
actionName = "上";
|
||||
coilAddress = CoilRise;
|
||||
return true;
|
||||
case "Down":
|
||||
actionName = "下";
|
||||
coilAddress = CoilLower;
|
||||
return true;
|
||||
case "Left":
|
||||
actionName = "左";
|
||||
coilAddress = CoilBack;
|
||||
return true;
|
||||
case "Right":
|
||||
actionName = "右";
|
||||
coilAddress = CoilForward;
|
||||
return true;
|
||||
default:
|
||||
actionName = string.Empty;
|
||||
coilAddress = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> SetTableMotionCoilAsync(string actionName, ushort coilAddress, bool value)
|
||||
{
|
||||
if (_isPlcCommandBusy)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
_pendingTableMotionStopCoil = coilAddress;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_isPlcCommandBusy = true;
|
||||
RaiseCommandStates();
|
||||
|
||||
try
|
||||
{
|
||||
if (!_deviceConnectionService.IsConnected)
|
||||
{
|
||||
await InitializeDeviceConnectionAsync();
|
||||
}
|
||||
|
||||
if (!_deviceConnectionService.IsConnected)
|
||||
{
|
||||
PlcCommandSummary = $"PLC 台面{actionName}{(value ? "启动" : "停止")}失败: 设备未连接。";
|
||||
|
||||
if (value && _activeTableMotionCoil == coilAddress)
|
||||
{
|
||||
_activeTableMotionCoil = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
await _deviceConnectionService.WriteSingleCoilAsync(ModbusSlaveAddress, coilAddress, value);
|
||||
PlcCommandSummary = $"PLC 台面{actionName}{(value ? "启动" : "停止")}已发送: M{coilAddress}={(value ? "ON" : "OFF")}";
|
||||
AddInfoEvent($"PLC 台面点动写入成功: {actionName} M{coilAddress}={(value ? "ON" : "OFF")}");
|
||||
InterlockMessage = $"已发送 PLC 台面{actionName}{(value ? "启动" : "停止")}指令: M{coilAddress}={(value ? "ON" : "OFF")}";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PlcCommandSummary = $"PLC 台面{actionName}{(value ? "启动" : "停止")}失败: {ex.Message}";
|
||||
AddWarningEvent($"PLC 台面点动写入失败 {actionName} M{coilAddress}={(value ? "ON" : "OFF")}: {ex.Message}");
|
||||
|
||||
if (value && _activeTableMotionCoil == coilAddress)
|
||||
{
|
||||
_activeTableMotionCoil = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPlcCommandBusy = false;
|
||||
RaiseCommandStates();
|
||||
|
||||
if (value && _pendingTableMotionStopCoil == coilAddress)
|
||||
{
|
||||
_pendingTableMotionStopCoil = null;
|
||||
|
||||
if (_activeTableMotionCoil == coilAddress)
|
||||
{
|
||||
_activeTableMotionCoil = null;
|
||||
await SetTableMotionCoilAsync(actionName, coilAddress, value: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ExecutePlcPulseCommandAsync(string actionName, ushort coilAddress, bool updateLocalState = true)
|
||||
@@ -1340,6 +1492,14 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
return await _deviceDataReader.ReadFrameAsync();
|
||||
}
|
||||
|
||||
private void EnsureRealtimeMonitorTimerRunning()
|
||||
{
|
||||
if (_deviceConnectionService.IsConnected && !_timer.IsEnabled)
|
||||
{
|
||||
_timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRealtimeSamplingFailure(Exception ex)
|
||||
{
|
||||
_timer.Stop();
|
||||
@@ -1369,6 +1529,26 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
RaiseStatusProperties();
|
||||
}
|
||||
|
||||
private void HandleRealtimeMonitorFailure(Exception ex)
|
||||
{
|
||||
_timer.Stop();
|
||||
_deviceConnectionService.Disconnect();
|
||||
_deviceReconnectTimer.Start();
|
||||
_deviceConnectionFailureLogged = true;
|
||||
DeviceConnectionStatus = "重连中";
|
||||
DeviceConnectionSummary = "实时监控读取中断,软件自动重连中。";
|
||||
OnPropertyChanged(nameof(DeviceConnectionIndicatorBrush));
|
||||
RaiseCommandStates();
|
||||
|
||||
if (_machineRuntimeState == MachineRuntimeState.Idle)
|
||||
{
|
||||
InterlockMessage = $"实时监控读取失败: {ex.Message}";
|
||||
}
|
||||
|
||||
AddWarningEvent($"实时监控读取失败: {ex.Message}");
|
||||
RaiseStatusProperties();
|
||||
}
|
||||
|
||||
private async void OnRecipePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (_isSyncingRecipeFromPlc || string.IsNullOrWhiteSpace(e.PropertyName))
|
||||
|
||||
Reference in New Issue
Block a user