更新2019

This commit is contained in:
GukSang.Jin
2026-06-06 14:54:02 +08:00
parent 2973556320
commit c8298ab004
8 changed files with 769 additions and 46 deletions

Binary file not shown.
Can't render this file because it contains an unexpected character in line 1 and column 4.

View File

@@ -0,0 +1,199 @@
using System.Collections.ObjectModel;
using System.Globalization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace DentistryHandpieces;
public sealed class SpeedCoefficientSettingRow : ObservableObject
{
private string _valueText = string.Empty;
public required ushort Address { get; init; }
public required string RangeText { get; init; }
public string ValueText
{
get => _valueText;
set => SetProperty(ref _valueText, value);
}
}
public sealed class HiddenSpeedSettingsViewModel : ObservableObject
{
private const ushort LoadSpeedSettingRegister = 414;
private const ushort FirstSpeedCoefficientRegister = 1200;
private const int SpeedCoefficientCount = 30;
private readonly IPlcRegisterService _plcRegisterService;
private readonly PlcConnectionConfig _plcConfig;
private string _loadSpeedSettingInput = string.Empty;
private string _statusText = "打开后将从 PLC 读取参数。";
private bool _isBusy;
public HiddenSpeedSettingsViewModel(IPlcRegisterService plcRegisterService, PlcConnectionConfig plcConfig)
{
_plcRegisterService = plcRegisterService;
_plcConfig = plcConfig;
ReadCommand = new AsyncRelayCommand(ReadAsync);
SaveLoadSpeedSettingCommand = new AsyncRelayCommand(SaveLoadSpeedSettingAsync);
SaveSpeedCoefficientCommand = new AsyncRelayCommand<SpeedCoefficientSettingRow>(SaveSpeedCoefficientAsync);
for (int index = 0; index < SpeedCoefficientCount; index++)
{
int lower = index == 0 ? 0 : index * 10000 + 1;
int upper = (index + 1) * 10000;
SpeedCoefficientSettings.Add(new SpeedCoefficientSettingRow
{
Address = checked((ushort)(FirstSpeedCoefficientRegister + index * 2)),
RangeText = $"转速系数{lower}-{upper}"
});
}
}
public ObservableCollection<SpeedCoefficientSettingRow> SpeedCoefficientSettings { get; } = [];
public IAsyncRelayCommand ReadCommand { get; }
public IAsyncRelayCommand SaveLoadSpeedSettingCommand { get; }
public IAsyncRelayCommand<SpeedCoefficientSettingRow> SaveSpeedCoefficientCommand { get; }
public string LoadSpeedSettingInput
{
get => _loadSpeedSettingInput;
set => SetProperty(ref _loadSpeedSettingInput, value);
}
public string StatusText
{
get => _statusText;
private set => SetProperty(ref _statusText, value);
}
public async Task ReadAsync()
{
if (_isBusy)
{
return;
}
_isBusy = true;
StatusText = "正在读取 PLC 隐藏参数...";
try
{
await ReadValuesAsync();
StatusText = "隐藏参数读取成功。";
}
catch (Exception ex)
{
StatusText = $"读取失败:{ex.Message}";
}
finally
{
_isBusy = false;
}
}
private async Task SaveLoadSpeedSettingAsync()
{
if (_isBusy)
{
return;
}
if (!TryParseInt32(LoadSpeedSettingInput, out int loadSpeedSetting))
{
StatusText = "负载转速设置必须是有效的 32 位整数。";
return;
}
_isBusy = true;
StatusText = "正在保存负载转速设置...";
try
{
await _plcRegisterService.WriteInt32Async(_plcConfig, LoadSpeedSettingRegister, loadSpeedSetting);
int confirmedValue = await _plcRegisterService.ReadInt32Async(_plcConfig, LoadSpeedSettingRegister);
LoadSpeedSettingInput = confirmedValue.ToString(CultureInfo.InvariantCulture);
StatusText = "负载转速设置已保存并回读确认。";
}
catch (Exception ex)
{
StatusText = $"负载转速设置保存失败:{ex.Message}";
}
finally
{
_isBusy = false;
}
}
private async Task SaveSpeedCoefficientAsync(SpeedCoefficientSettingRow? row)
{
if (_isBusy || row is null)
{
return;
}
if (!TryParseFloat(row.ValueText, out float value))
{
StatusText = $"{row.RangeText} 必须是有效数字。";
return;
}
_isBusy = true;
StatusText = $"正在保存{row.RangeText}...";
try
{
await _plcRegisterService.WriteFloatAsync(_plcConfig, row.Address, value);
float confirmedValue = await _plcRegisterService.ReadFloatAsync(_plcConfig, row.Address);
if (float.IsNaN(confirmedValue) || float.IsInfinity(confirmedValue))
{
throw new InvalidOperationException("PLC 回读值无效。");
}
row.ValueText = confirmedValue.ToString("0.########", CultureInfo.InvariantCulture);
StatusText = $"{row.RangeText}已保存并回读确认。";
}
catch (Exception ex)
{
StatusText = $"{row.RangeText}保存失败:{ex.Message}";
}
finally
{
_isBusy = false;
}
}
private async Task ReadValuesAsync()
{
int loadSpeedSetting = await _plcRegisterService.ReadInt32Async(_plcConfig, LoadSpeedSettingRegister);
ushort[] addresses = SpeedCoefficientSettings.Select(static row => row.Address).ToArray();
IReadOnlyDictionary<ushort, float> values = await _plcRegisterService.ReadFloatValuesAsync(_plcConfig, addresses);
LoadSpeedSettingInput = loadSpeedSetting.ToString(CultureInfo.InvariantCulture);
foreach (SpeedCoefficientSettingRow row in SpeedCoefficientSettings)
{
if (!values.TryGetValue(row.Address, out float value)
|| float.IsNaN(value)
|| float.IsInfinity(value))
{
throw new InvalidOperationException($"{row.RangeText}读取无效。");
}
row.ValueText = value.ToString("0.########", CultureInfo.InvariantCulture);
}
}
private static bool TryParseInt32(string input, out int value)
{
return int.TryParse(input, NumberStyles.Integer, CultureInfo.CurrentCulture, out value)
|| int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
}
private static bool TryParseFloat(string input, out float value)
{
bool parsed = float.TryParse(input, NumberStyles.Float, CultureInfo.CurrentCulture, out value)
|| float.TryParse(input, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
return parsed && !float.IsNaN(value) && !float.IsInfinity(value);
}
}

View File

@@ -0,0 +1,143 @@
<Window x:Class="DentistryHandpieces.HiddenSpeedSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="转速隐藏参数设置"
Width="920"
Height="720"
MinWidth="760"
MinHeight="560"
WindowStartupLocation="CenterOwner"
FontFamily="Microsoft YaHei UI"
FontSize="16"
Background="#EDF2F5">
<Window.Resources>
<Style TargetType="TextBox">
<Setter Property="Height" Value="40" />
<Setter Property="Padding" Value="10,4" />
<Setter Property="FontSize" Value="17" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="#F8FAFC" />
<Setter Property="BorderBrush" Value="#B8C5D1" />
</Style>
<Style TargetType="Button">
<Setter Property="MinHeight" Value="44" />
<Setter Property="Padding" Value="18,8" />
<Setter Property="FontSize" Value="17" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Background" Value="#1F4F73" />
<Setter Property="Foreground" Value="White" />
<Setter Property="BorderBrush" Value="#15384F" />
</Style>
</Window.Resources>
<Grid Margin="18">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Background="White"
BorderBrush="#C9D4DD"
BorderThickness="1"
CornerRadius="6"
Padding="16">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
</Grid.ColumnDefinitions>
<TextBlock Text="负载转速设置"
FontSize="19"
FontWeight="Bold"
VerticalAlignment="Center"
Foreground="#22313F" />
<TextBox Grid.Column="1"
Text="{Binding LoadSpeedSettingInput, UpdateSourceTrigger=PropertyChanged}"
Margin="12,0" />
<Button Grid.Column="2"
Content="保存"
Command="{Binding SaveLoadSpeedSettingCommand}"
Margin="0" />
</Grid>
</Border>
<DataGrid Grid.Row="1"
ItemsSource="{Binding SpeedCoefficientSettings}"
Margin="0,14"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
HeadersVisibility="Column"
AlternatingRowBackground="#F3F7FA"
GridLinesVisibility="Horizontal"
RowHeight="42"
FontSize="16">
<DataGrid.Columns>
<DataGridTextColumn Header="参数名称"
Binding="{Binding RangeText}"
IsReadOnly="True"
Width="*" />
<DataGridTemplateColumn Header="设置值" Width="320">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid Margin="4,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="92" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,8,0" />
<Button Grid.Column="1"
Content="保存"
Command="{Binding DataContext.SaveSpeedCoefficientCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
MinHeight="36"
Padding="12,4" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Border Grid.Row="2"
Background="White"
BorderBrush="#C9D4DD"
BorderThickness="1"
CornerRadius="6"
Padding="14">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding StatusText}"
TextWrapping="Wrap"
Foreground="#334155"
FontWeight="SemiBold" />
<Grid Grid.Row="1" Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<TextBlock Text="仅保存当前修改项"
VerticalAlignment="Center"
Foreground="#52616F" />
<Button Grid.Column="1"
Content="重新读取"
Command="{Binding ReadCommand}"
Margin="8,0" />
<Button Grid.Column="2"
Content="关闭"
Click="CloseButton_Click"
Margin="8,0,0,0"
Background="#64748B"
BorderBrush="#475569" />
</Grid>
</Grid>
</Border>
</Grid>
</Window>

View File

@@ -0,0 +1,27 @@
using System.Windows;
namespace DentistryHandpieces;
public partial class HiddenSpeedSettingsWindow : Window
{
private readonly HiddenSpeedSettingsViewModel _viewModel;
public HiddenSpeedSettingsWindow(IPlcRegisterService plcRegisterService, PlcConnectionConfig plcConfig)
{
InitializeComponent();
_viewModel = new HiddenSpeedSettingsViewModel(plcRegisterService, plcConfig);
DataContext = _viewModel;
Loaded += Window_Loaded;
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= Window_Loaded;
await _viewModel.ReadAsync();
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
}

View File

@@ -303,8 +303,9 @@
</Style>
</Window.Resources>
<DockPanel LastChildFill="True">
<TabControl x:Name="MainTabs">
<Grid>
<DockPanel LastChildFill="True">
<TabControl x:Name="MainTabs">
<TabItem Header="轴向位移动量测试">
<Grid>
<Grid.RowDefinitions>
@@ -729,65 +730,52 @@
Style="{StaticResource MutedText}"
Margin="0,8,0,0" />
<Grid Grid.Row="2" Margin="0,16,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="58" />
</Grid.ColumnDefinitions>
<TextBlock Text="扭矩设置"
<Grid Grid.Row="2" Margin="0,16,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="58" />
</Grid.ColumnDefinitions>
<TextBlock Text="扭矩设置"
Style="{StaticResource FormLabel}"
VerticalAlignment="Center"
Margin="0,0,12,0" />
<TextBox Grid.Column="1"
<TextBox Grid.Column="1"
x:Name="HoldTorqueInput"
Text="{Binding HoldTorqueInput, UpdateSourceTrigger=PropertyChanged}" TextChanged="HoldTorqueInput_TextChanged" />
<TextBlock Grid.Column="2"
<TextBlock Grid.Column="2"
Text="Ncm"
Style="{StaticResource MutedText}"
VerticalAlignment="Center"
Margin="8,0,0,0" />
</Grid>
<Grid Grid.Row="1" Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="58" />
</Grid.ColumnDefinitions>
<TextBlock Text="扭矩时间"
</Grid>
<Grid Grid.Row="1" Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="58" />
</Grid.ColumnDefinitions>
<TextBlock Text="扭矩时间"
Style="{StaticResource FormLabel}"
VerticalAlignment="Center"
Margin="0,0,12,0" />
<TextBox Grid.Column="1"
<TextBox Grid.Column="1"
x:Name="TorqueHoldTimeInput"
Text="{Binding TorqueHoldTimeInput, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Column="2"
<TextBlock Grid.Column="2"
Text="s"
Style="{StaticResource MutedText}"
VerticalAlignment="Center"
Margin="8,0,0,0" />
</Grid>
</Grid>
</Grid>
</Grid>
<Border Grid.Row="3" Style="{StaticResource InfoStrip}" Margin="0,16,18,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="当前位置" Style="{StaticResource FormLabel}" />
<TextBlock Grid.Column="1"
x:Name="SpeedTorqueAxisPositionText"
Text="{Binding SpeedTorqueAxisPositionText}"
Style="{StaticResource ResultValue}" />
</Grid>
</Border>
</Grid>
</Border>
<Border Grid.Column="1" Style="{StaticResource PanelBorder}" Margin="10,0,0,0">
@@ -877,6 +865,125 @@
</Grid>
</TabItem>
<TabItem Header="空载转速测试">
<Grid Margin="0,14,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
PanningMode="VerticalOnly">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid MinHeight="260">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Style="{StaticResource PanelBorder}" Margin="0,0,10,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="空载转速" Style="{StaticResource MetricTitle}" />
<TextBlock Grid.Row="1"
Text="{Binding NoLoadSpeedRecordText}"
Style="{StaticResource MetricValue}"
Foreground="#0F766E"
VerticalAlignment="Center" />
</Grid>
</Border>
<Border Grid.Column="1" Style="{StaticResource PanelBorder}" Margin="10,0,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="转速误差率" Style="{StaticResource MetricTitle}" />
<TextBlock Grid.Row="1"
Text="{Binding NoLoadSpeedErrorRateText}"
Style="{StaticResource MetricValue}"
Foreground="#1D4ED8"
VerticalAlignment="Center" />
</Grid>
</Border>
</Grid>
<Border Grid.Row="1" Style="{StaticResource PanelBorder}" Margin="0,14,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="210" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="3"
Text="空载转速设置"
Style="{StaticResource MetricTitle}"
HorizontalAlignment="Left" />
<TextBlock Grid.Row="1"
Text="目标空载转速"
Style="{StaticResource FormLabel}"
Margin="0,16,12,0" />
<TextBox Grid.Row="1"
Grid.Column="1"
Text="{Binding NoLoadSpeedSettingInput, UpdateSourceTrigger=PropertyChanged}"
Margin="0,16,12,0" />
<TextBlock Grid.Row="1"
Grid.Column="2"
Text="r/min"
Style="{StaticResource MutedText}"
Margin="0,16,0,0"
VerticalAlignment="Center" />
<Border Grid.Row="2"
Grid.ColumnSpan="3"
Style="{StaticResource InfoStrip}"
Margin="0,16,0,0">
<TextBlock Text="{Binding NoLoadSpeedStatusText}"
FontSize="18"
FontWeight="SemiBold"
HorizontalAlignment="Center"
Foreground="#334155" />
</Border>
</Grid>
</Border>
</Grid>
</ScrollViewer>
<Grid Grid.Row="1" Margin="0,14,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="保存空载转速设置"
Command="{Binding SaveNoLoadSpeedSettingCommand}"
Margin="0,0,8,0" />
<Button Grid.Column="1"
Content="开始空载转速"
Command="{Binding RecordNoLoadSpeedCommand}"
Style="{StaticResource StartButtonStyle}"
Margin="8,0,0,0" />
</Grid>
</Grid>
</TabItem>
<TabItem Header="参数配置">
<Grid Margin="0,14,0,0">
<Grid.RowDefinitions>
@@ -1111,6 +1218,22 @@
</DataGrid>
</Grid>
</TabItem>
</TabControl>
</DockPanel>
</TabControl>
</DockPanel>
<Border x:Name="HiddenSettingsHotspot"
Width="56"
Height="56"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Background="Transparent"
Panel.ZIndex="100"
PreviewMouseLeftButtonDown="HiddenSettingsHotspot_PreviewMouseLeftButtonDown"
PreviewMouseLeftButtonUp="HiddenSettingsHotspot_PreviewMouseLeftButtonUp"
PreviewTouchDown="HiddenSettingsHotspot_PreviewTouchDown"
PreviewTouchUp="HiddenSettingsHotspot_PreviewTouchUp"
MouseLeave="HiddenSettingsHotspot_MouseLeave"
LostMouseCapture="HiddenSettingsHotspot_LostMouseCapture"
LostTouchCapture="HiddenSettingsHotspot_LostTouchCapture" />
</Grid>
</Window>

View File

@@ -1,21 +1,120 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
namespace DentistryHandpieces;
public partial class MainWindow : Window
{
private readonly HashSet<ManualMotionTarget> _activeManualMotionTargets = [];
private readonly ModbusTcpPlcCoilService _plcService;
private readonly DispatcherTimer _hiddenSettingsPressTimer;
private bool _isHiddenSettingsPressActive;
private bool _hasOpenedHiddenSettingsForCurrentPress;
public MainWindow()
{
InitializeComponent();
var plcService = new ModbusTcpPlcCoilService();
DataContext = new MainWindowViewModel(plcService, plcService, new WpfFileDialogService());
_plcService = new ModbusTcpPlcCoilService();
DataContext = new MainWindowViewModel(_plcService, _plcService, new WpfFileDialogService());
_hiddenSettingsPressTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(2)
};
_hiddenSettingsPressTimer.Tick += HiddenSettingsPressTimer_Tick;
Deactivated += (_, _) => ReleaseAllManualMotionTargetsAsync();
}
private void HiddenSettingsHotspot_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
HiddenSettingsHotspot.CaptureMouse();
BeginHiddenSettingsPress();
}
private void HiddenSettingsHotspot_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
HiddenSettingsHotspot.ReleaseMouseCapture();
EndHiddenSettingsPress();
}
private void HiddenSettingsHotspot_PreviewTouchDown(object sender, TouchEventArgs e)
{
e.Handled = true;
HiddenSettingsHotspot.CaptureTouch(e.TouchDevice);
BeginHiddenSettingsPress();
}
private void HiddenSettingsHotspot_PreviewTouchUp(object sender, TouchEventArgs e)
{
e.Handled = true;
HiddenSettingsHotspot.ReleaseTouchCapture(e.TouchDevice);
EndHiddenSettingsPress();
}
private void HiddenSettingsHotspot_MouseLeave(object sender, MouseEventArgs e)
{
if (HiddenSettingsHotspot.IsMouseCaptured)
{
HiddenSettingsHotspot.ReleaseMouseCapture();
EndHiddenSettingsPress();
}
}
private void HiddenSettingsHotspot_LostMouseCapture(object sender, MouseEventArgs e)
{
EndHiddenSettingsPress();
}
private void HiddenSettingsHotspot_LostTouchCapture(object sender, TouchEventArgs e)
{
EndHiddenSettingsPress();
}
private void BeginHiddenSettingsPress()
{
if (_isHiddenSettingsPressActive)
{
return;
}
_isHiddenSettingsPressActive = true;
_hasOpenedHiddenSettingsForCurrentPress = false;
_hiddenSettingsPressTimer.Stop();
_hiddenSettingsPressTimer.Start();
}
private void EndHiddenSettingsPress()
{
_isHiddenSettingsPressActive = false;
_hiddenSettingsPressTimer.Stop();
}
private void HiddenSettingsPressTimer_Tick(object? sender, EventArgs e)
{
_hiddenSettingsPressTimer.Stop();
if (!_isHiddenSettingsPressActive || _hasOpenedHiddenSettingsForCurrentPress)
{
return;
}
_isHiddenSettingsPressActive = false;
_hasOpenedHiddenSettingsForCurrentPress = true;
HiddenSettingsHotspot.ReleaseMouseCapture();
HiddenSettingsHotspot.ReleaseAllTouchCaptures();
if (DataContext is MainWindowViewModel viewModel)
{
var window = new HiddenSpeedSettingsWindow(_plcService, viewModel.GetPlcConnectionConfig())
{
Owner = this
};
window.ShowDialog();
}
}
private void HoldTorqueInput_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{

View File

@@ -38,6 +38,7 @@ public sealed class MainWindowViewModel : ObservableObject
private const ushort SpeedTorqueResetCoil = 90;
private const ushort SpeedTorqueResetEnabledCoil = 91;
private const ushort SpeedTorqueResetDoneCoil = 92;
private const ushort NoLoadSpeedRecordCoil = 100;
private const ushort SpeedTorqueWearPlateWarningCoil = 14;
private const ushort SpeedTorqueLimitWarningCoil = 200;
private const ushort AxialResetCoil = 95;
@@ -49,6 +50,8 @@ public sealed class MainWindowViewModel : ObservableObject
private const ushort AxialSampleEndRegister = 74;
private const ushort AxialSampleDifferenceRegister = 76;
private const ushort SpeedTorquePeakTorqueRegister = 82;
private const ushort NoLoadSpeedRecordRegister = 100;
private const ushort NoLoadSpeedErrorRateRegister = 102;
private const ushort DialIndicatorRegister = 130;
private const ushort SpeedTorqueDisplacementLimitRegister = 302;
private const ushort AxialDisplacementLimitRegister = 304;
@@ -63,6 +66,7 @@ public sealed class MainWindowViewModel : ObservableObject
private const ushort AxialForceHoldTimeRegister = 406;
private const ushort HoldTorqueRegister = 410;
private const ushort TorqueHoldTimeRegister = 412;
private const ushort NoLoadSpeedSettingRegister = 420;
private const ushort AxialForceDisplayRegister = 1130;
private const ushort AxialForceCoefficientRegister = 1128;
private const ushort AxialForceProtectionRegister = 1134;
@@ -86,6 +90,8 @@ public sealed class MainWindowViewModel : ObservableObject
AxialSampleEndRegister,
AxialSampleDifferenceRegister,
SpeedTorquePeakTorqueRegister,
NoLoadSpeedRecordRegister,
NoLoadSpeedErrorRateRegister,
DialIndicatorRegister,
AxialForceDisplayRegister,
TorqueDisplayRegister,
@@ -111,7 +117,8 @@ public sealed class MainWindowViewModel : ObservableObject
TorqueProtectionRegister,
SpeedCoefficientRegister,
SpeedStopThresholdRegister,
PressureCoefficientRegister
PressureCoefficientRegister,
NoLoadSpeedSettingRegister
];
private static readonly ushort[] RealtimeCoilAddresses =
@@ -145,6 +152,8 @@ public sealed class MainWindowViewModel : ObservableObject
private double _realtimeSpeed;
private double _realtimeTorque;
private double _realtimePressure;
private double _noLoadSpeedRecord;
private double _noLoadSpeedErrorRate;
private double _speedTorqueAxisPosition;
private double _speedTorqueZero;
private double _speedTorqueDisplacement;
@@ -181,6 +190,10 @@ public sealed class MainWindowViewModel : ObservableObject
private string _realtimeSpeedText = "0 r/min";
private string _realtimeTorqueText = "0.00 Ncm";
private string _realtimePressureText = "0.000 MPa";
private string _noLoadSpeedRecordText = "0 r/min";
private string _noLoadSpeedErrorRateText = "0.00 %";
private string _noLoadSpeedStatusText = "状态:待记录";
private string _noLoadSpeedSettingInput = "0";
private string _speedTorqueStatusText = "状态:待启动";
private string _finalSpeedTorqueText = "最终:-- / --";
private string _speedTorqueAxisPositionText = "0.000 mm";
@@ -222,6 +235,8 @@ public sealed class MainWindowViewModel : ObservableObject
StartSpeedTorqueCommand = new AsyncRelayCommand(StartSpeedTorqueAsync);
StopSpeedTorqueCommand = new AsyncRelayCommand(StopSpeedTorqueAsync);
ResetSpeedTorqueCommand = new AsyncRelayCommand(ResetSpeedTorqueAsync);
SaveNoLoadSpeedSettingCommand = new AsyncRelayCommand(SaveNoLoadSpeedSettingAsync);
RecordNoLoadSpeedCommand = new AsyncRelayCommand(RecordNoLoadSpeedAsync);
_parameterConfig = LoadParameterConfig();
ApplyParameterConfigToInputs();
@@ -283,6 +298,15 @@ public sealed class MainWindowViewModel : ObservableObject
public IAsyncRelayCommand ResetSpeedTorqueCommand { get; }
public IAsyncRelayCommand SaveNoLoadSpeedSettingCommand { get; }
public IAsyncRelayCommand RecordNoLoadSpeedCommand { get; }
internal PlcConnectionConfig GetPlcConnectionConfig()
{
return _parameterConfig.ToPlcConnectionConfig();
}
internal async Task<bool> BeginManualMotionAsync(ManualMotionTarget target)
{
return target switch
@@ -377,6 +401,12 @@ public sealed class MainWindowViewModel : ObservableObject
public string PressureCoefficientInput { get; set; } = "1";
public string NoLoadSpeedSettingInput
{
get => _noLoadSpeedSettingInput;
set => SetProperty(ref _noLoadSpeedSettingInput, value);
}
public string PlcIpAddressInput { get; set; } = "192.168.1.10";
public string PlcPortInput { get; set; } = "502";
@@ -459,6 +489,24 @@ public sealed class MainWindowViewModel : ObservableObject
private set => SetProperty(ref _realtimePressureText, value);
}
public string NoLoadSpeedRecordText
{
get => _noLoadSpeedRecordText;
private set => SetProperty(ref _noLoadSpeedRecordText, value);
}
public string NoLoadSpeedErrorRateText
{
get => _noLoadSpeedErrorRateText;
private set => SetProperty(ref _noLoadSpeedErrorRateText, value);
}
public string NoLoadSpeedStatusText
{
get => _noLoadSpeedStatusText;
private set => SetProperty(ref _noLoadSpeedStatusText, value);
}
public string SpeedTorqueStatusText
{
get => _speedTorqueStatusText;
@@ -573,6 +621,8 @@ public sealed class MainWindowViewModel : ObservableObject
_speedTorqueAxisPosition = ReadFloatValue(values, SpeedTorquePositionRegister, "1号当前位置");
_speedTorqueDisplacement = _speedTorqueAxisPosition - _speedTorqueZero;
_speedTorquePeakTorque = ReadFloatValue(values, SpeedTorquePeakTorqueRegister, "最大扭矩采集");
_noLoadSpeedRecord = ReadFloatValue(values, NoLoadSpeedRecordRegister, "空载转速记录");
_noLoadSpeedErrorRate = ReadFloatValue(values, NoLoadSpeedErrorRateRegister, "转速误差率");
_realtimePressure = ReadFloatValue(values, PressureDisplayRegister, "压力显示");
_relativeDisplacement = dialIndicator - _dialZero;
_axialForce = axialForce;
@@ -593,12 +643,14 @@ public sealed class MainWindowViewModel : ObservableObject
UpdateDisplacementDisplay(ResolveDisplacementStatus(DisplacementStatusText));
UpdateSpeedTorqueDisplay(ResolveSpeedTorqueStatus(SpeedTorqueStatusText));
UpdateNoLoadSpeedDisplay();
await AutoStopIfSetpointReachedAsync();
await AutoStopIfSpeedTorqueProtectionReachedAsync();
if (_lastRealtimeReadFailed)
{
StatusText = "实时数据已恢复";
NoLoadSpeedStatusText = "状态:实时数据已恢复";
_lastRealtimeReadFailed = false;
}
}
@@ -607,6 +659,7 @@ public sealed class MainWindowViewModel : ObservableObject
if (!_lastRealtimeReadFailed)
{
StatusText = $"实时数据读取失败:{ex.Message}";
NoLoadSpeedStatusText = "状态:实时数据读取失败";
_lastRealtimeReadFailed = true;
}
}
@@ -659,6 +712,7 @@ public sealed class MainWindowViewModel : ObservableObject
double speedCoefficient = ReadFloatValue(values, SpeedCoefficientRegister, "转速系数");
double speedStopThreshold = ReadFloatValue(values, SpeedStopThresholdRegister, "低速停止设置");
double pressureCoefficient = ReadFloatValue(values, PressureCoefficientRegister, "压力系数");
double noLoadSpeedSetting = ReadFloatValue(values, NoLoadSpeedSettingRegister, "空载转速设置");
_parameterConfig = new TestParameterConfig
{
@@ -693,6 +747,7 @@ public sealed class MainWindowViewModel : ObservableObject
SaveParameterConfig();
ApplyParameterConfigToInputs();
NoLoadSpeedSettingInput = FormatConfigNumber(noLoadSpeedSetting);
NotifyParameterInputsChanged();
UpdateDisplacementDisplay(DisplacementStatusText);
UpdateSpeedTorqueDisplay(SpeedTorqueStatusText);
@@ -1631,6 +1686,39 @@ public sealed class MainWindowViewModel : ObservableObject
await MoveSpeedTorqueDisplacementAsync("状态:后退");
}
private async Task SaveNoLoadSpeedSettingAsync()
{
if (!TryReadNonNegative(NoLoadSpeedSettingInput, "空载转速设置", out double setting, out string error))
{
NoLoadSpeedStatusText = $"状态:{error}";
return;
}
try
{
PlcConnectionConfig config = _parameterConfig.ToPlcConnectionConfig();
await _plcRegisterService.WriteFloatAsync(config, NoLoadSpeedSettingRegister, (float)setting);
float confirmedSetting = await _plcRegisterService.ReadFloatAsync(config, NoLoadSpeedSettingRegister);
NoLoadSpeedSettingInput = FormatConfigNumber(confirmedSetting);
NoLoadSpeedStatusText = $"状态:空载转速设置已写入 D{NoLoadSpeedSettingRegister}";
}
catch (Exception ex)
{
NoLoadSpeedStatusText = $"状态:空载转速设置写入失败:{ex.Message}";
}
}
private async Task RecordNoLoadSpeedAsync()
{
if (!await PulsePlcAsync(NoLoadSpeedRecordCoil, "记录空载转速"))
{
NoLoadSpeedStatusText = "状态:记录指令发送失败";
return;
}
NoLoadSpeedStatusText = $"状态:已触发 M{NoLoadSpeedRecordCoil},等待 PLC 更新记录";
}
private async Task StartSpeedTorqueAsync()
{
if (!await PulsePlcAsync(SpeedTorqueStartCoil, "转速/扭矩启动"))
@@ -2249,6 +2337,12 @@ public sealed class MainWindowViewModel : ObservableObject
UpdateTorqueCurveStatus();
}
private void UpdateNoLoadSpeedDisplay()
{
NoLoadSpeedRecordText = $"{FormatSpeed(_noLoadSpeedRecord)} r/min";
NoLoadSpeedErrorRateText = $"{FormatErrorRate(_noLoadSpeedErrorRate)} %";
}
private void AppendTorqueSample(double value, DateTime sampledAt)
{
if (!_isSpeedTorqueRunning
@@ -2429,6 +2523,11 @@ public sealed class MainWindowViewModel : ObservableObject
return value.ToString("0.000", CultureInfo.InvariantCulture);
}
private static string FormatErrorRate(double value)
{
return value.ToString("0.00", CultureInfo.InvariantCulture);
}
private static string FormatConfigNumber(double value)
{
return value.ToString("0.###", CultureInfo.InvariantCulture);

View File

@@ -19,10 +19,14 @@ public interface IPlcRegisterService
Task<IReadOnlyDictionary<ushort, float>> ReadFloatValuesAsync(PlcConnectionConfig config, IReadOnlyCollection<ushort> registerAddresses, CancellationToken cancellationToken = default);
Task<int> ReadInt32Async(PlcConnectionConfig config, ushort registerAddress, CancellationToken cancellationToken = default);
Task<ushort> ReadUInt16Async(PlcConnectionConfig config, ushort registerAddress, CancellationToken cancellationToken = default);
Task WriteFloatAsync(PlcConnectionConfig config, ushort registerAddress, float value, CancellationToken cancellationToken = default);
Task WriteInt32Async(PlcConnectionConfig config, ushort registerAddress, int value, CancellationToken cancellationToken = default);
Task WriteUInt16Async(PlcConnectionConfig config, ushort registerAddress, ushort value, CancellationToken cancellationToken = default);
}
@@ -97,6 +101,12 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
return registers[0];
}
public async Task<int> ReadInt32Async(PlcConnectionConfig config, ushort registerAddress, CancellationToken cancellationToken = default)
{
ushort[] registers = await ReadHoldingRegistersAsync(config, registerAddress, 2, cancellationToken);
return ToInt32(registers[0], registers[1], config.FloatWordOrder);
}
public async Task WriteFloatAsync(PlcConnectionConfig config, ushort registerAddress, float value, CancellationToken cancellationToken = default)
{
int bits = BitConverter.SingleToInt32Bits(value);
@@ -116,6 +126,19 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
await WriteMultipleRegistersAsync(config, registerAddress, [value], cancellationToken);
}
public async Task WriteInt32Async(PlcConnectionConfig config, ushort registerAddress, int value, CancellationToken cancellationToken = default)
{
Span<byte> bytes = stackalloc byte[4];
BinaryPrimitives.WriteInt32BigEndian(bytes, value);
ushort highWord = BinaryPrimitives.ReadUInt16BigEndian(bytes[..2]);
ushort lowWord = BinaryPrimitives.ReadUInt16BigEndian(bytes[2..]);
ushort[] registers = config.FloatWordOrder == PlcFloatWordOrder.HighWordFirst
? [highWord, lowWord]
: [lowWord, highWord];
await WriteMultipleRegistersAsync(config, registerAddress, registers, cancellationToken);
}
private async Task WriteSingleCoilAsync(PlcConnectionConfig config, ushort coilAddress, bool value, CancellationToken cancellationToken)
{
using var client = new TcpClient();
@@ -348,6 +371,16 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
return BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32BigEndian(bytes));
}
private static int ToInt32(ushort firstRegister, ushort secondRegister, PlcFloatWordOrder wordOrder)
{
ushort highWord = wordOrder == PlcFloatWordOrder.HighWordFirst ? firstRegister : secondRegister;
ushort lowWord = wordOrder == PlcFloatWordOrder.HighWordFirst ? secondRegister : firstRegister;
Span<byte> bytes = stackalloc byte[4];
BinaryPrimitives.WriteUInt16BigEndian(bytes[..2], highWord);
BinaryPrimitives.WriteUInt16BigEndian(bytes[2..], lowWord);
return BinaryPrimitives.ReadInt32BigEndian(bytes);
}
private static byte[] BuildWriteSingleCoilRequest(ushort transactionId, byte unitId, ushort coilAddress, bool value)
{
ushort coilValue = value ? (ushort)0xFF00 : (ushort)0x0000;