更新2019
This commit is contained in:
Binary file not shown.
|
Can't render this file because it contains an unexpected character in line 1 and column 4.
|
199
DentistryHandpieces/HiddenSpeedSettingsViewModel.cs
Normal file
199
DentistryHandpieces/HiddenSpeedSettingsViewModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
143
DentistryHandpieces/HiddenSpeedSettingsWindow.xaml
Normal file
143
DentistryHandpieces/HiddenSpeedSettingsWindow.xaml
Normal 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>
|
||||
27
DentistryHandpieces/HiddenSpeedSettingsWindow.xaml.cs
Normal file
27
DentistryHandpieces/HiddenSpeedSettingsWindow.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user