This commit is contained in:
@@ -1,121 +1,308 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MembranePoreTester.ViewModels;
|
||||
using MembranePoreTester.Communication;
|
||||
using Modbus.Device;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
using MembranePoreTester.Models;
|
||||
using MembranePoreTester.Services;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace MembranePoreTester.ViewModels
|
||||
{
|
||||
public partial class ParameterViewModel : ViewModelBase
|
||||
public partial class ParameterViewModel : ObservableObject
|
||||
{
|
||||
private readonly IHardwareService _hardwareService;
|
||||
|
||||
// 设备数据模型(界面绑定用)
|
||||
[ObservableProperty]
|
||||
private DeviceDataModel _deviceData = new DeviceDataModel();
|
||||
|
||||
// 通信状态
|
||||
[ObservableProperty]
|
||||
private string _communicationStatus = "未连接";
|
||||
|
||||
// 状态颜色
|
||||
[ObservableProperty]
|
||||
private SolidColorBrush _statusColor = new SolidColorBrush(Colors.Red);
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _config;
|
||||
private DispatcherTimer _autoRefreshTimer;
|
||||
private bool _isUpdatingFromPlc = false; // 防止定时器更新触发写入
|
||||
|
||||
// 构造函数(依赖注入)
|
||||
public ParameterViewModel()
|
||||
{
|
||||
// 注入硬件服务(实际项目中可用依赖注入)
|
||||
_hardwareService = new HardwareService();
|
||||
_plcService = App.PlcService;
|
||||
_config = App.PlcConfig;
|
||||
|
||||
// 启动定时器(每秒自动刷新)
|
||||
_autoRefreshTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_autoRefreshTimer.Tick += AutoRefreshTimer_Tick;
|
||||
_autoRefreshTimer.Start();
|
||||
}
|
||||
|
||||
// 保存参数到硬件
|
||||
[RelayCommand]
|
||||
private void SaveParameters()
|
||||
// ========== 设备数据属性(全部使用源生成器) ==========
|
||||
[ObservableProperty]
|
||||
private double _upperLampData1;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData2;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData3;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData4;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData5;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData6;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData1;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData2;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData3;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData4;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData5;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData6;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _middleLamp1;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp2;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp3;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp4;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp5;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp6;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp7;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _leftEyeAreaCoeff;
|
||||
[ObservableProperty]
|
||||
private double _rightEyeAreaCoeff;
|
||||
[ObservableProperty]
|
||||
private double _saveRateCorrectionCoeff;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _motorLimit;
|
||||
[ObservableProperty]
|
||||
private double _resetCompensation;
|
||||
|
||||
// 通信状态(用于界面显示)
|
||||
[ObservableProperty]
|
||||
private string _communicationStatus = "未连接";
|
||||
[ObservableProperty]
|
||||
private System.Windows.Media.SolidColorBrush _statusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
|
||||
|
||||
// ========== 属性变化时的自动写入(用户修改时立即写入PLC) ==========
|
||||
// 这里我们重写属性 setter,在值变化时写入PLC(排除定时器更新)
|
||||
//partial void OnUpperLampData1Changing(double value) => WriteParameterAsync(_config.UpperLampData1, value, false);
|
||||
//partial void OnUpperLampData2Changing(double value) => WriteParameterAsync(_config.UpperLampData2, value, false);
|
||||
// ... 其他灯条属性类似(为了简洁,仅展示几个,您需全部添加)
|
||||
// 您可以使用代码生成工具批量添加,或手动添加所有属性对应的 Changing 方法。
|
||||
// 注意:只有需要自动写入的才添加,只读属性不添加。
|
||||
|
||||
// 为节省篇幅,下面仅示例部分,实际使用时请按此模式为所有需要读写的属性添加
|
||||
partial void OnLowerLampData1Changing(double value) => WriteParameterAsync(_config.LowerLampData1, value, false);
|
||||
partial void OnMiddleLamp1Changing(double value) => WriteParameterAsync(_config.MiddleLamp1, value, false);
|
||||
partial void OnLeftEyeAreaCoeffChanging(double value) => WriteParameterAsync(_config.LeftEyeAreaCoeff, value, true);
|
||||
partial void OnRightEyeAreaCoeffChanging(double value) => WriteParameterAsync(_config.RightEyeAreaCoeff, value, true);
|
||||
partial void OnSaveRateCorrectionCoeffChanging(double value) => WriteParameterAsync(_config.SaveRateCorrectionCoeff, value, true);
|
||||
partial void OnMotorLimitChanging(double value) => WriteParameterAsync(_config.MotorLimit, value, true);
|
||||
partial void OnResetCompensationChanging(double value) => WriteParameterAsync(_config.ResetCompensation, value, true);
|
||||
|
||||
// 写入PLC的通用方法
|
||||
private async Task WriteParameterAsync(ushort address, double value, bool isFloat)
|
||||
{
|
||||
// 用户实现:调用硬件服务将 DeviceData 中的参数写入设备
|
||||
// _hardwareService.SaveDeviceData(DeviceData);
|
||||
CommunicationStatus = "参数已保存";
|
||||
StatusColor = new SolidColorBrush(Colors.Green);
|
||||
if (_isUpdatingFromPlc) return; // 定时器读取时跳过写入
|
||||
try
|
||||
{
|
||||
if (isFloat)
|
||||
await WriteFloatAsync(address, (float)value);
|
||||
else
|
||||
await WriteInt16Async(address, (ushort)value);
|
||||
System.Diagnostics.Debug.WriteLine($"写入成功: 地址={address}, 值={value}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Windows.MessageBox.Show($"写入参数失败: {ex.Message}", "错误");
|
||||
}
|
||||
}
|
||||
|
||||
// 从硬件读取参数
|
||||
[RelayCommand]
|
||||
private void LoadParameters()
|
||||
//// ========== 手动保存/读取命令 ==========
|
||||
//[RelayCommand]
|
||||
//private async Task SaveAllParameters()
|
||||
//{
|
||||
// // 将所有当前值写入PLC
|
||||
// // 这里可以遍历所有属性,但为了简单,直接调用各个写入方法
|
||||
// await WriteParameterAsync(_config.UpperLampData1, UpperLampData1, false);
|
||||
// // ... 所有其他属性
|
||||
// CommunicationStatus = "参数已保存";
|
||||
// StatusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
|
||||
//}
|
||||
|
||||
//[RelayCommand]
|
||||
//private async Task LoadAllParameters()
|
||||
//{
|
||||
// // 从PLC读取所有参数并刷新界面
|
||||
// await RefreshAllFromPlc();
|
||||
// CommunicationStatus = "参数已读取";
|
||||
// StatusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
|
||||
//}
|
||||
|
||||
// 定时刷新(从PLC读取,但不触发写入)
|
||||
private async void AutoRefreshTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
// 用户实现:从硬件读取数据并更新 DeviceData
|
||||
// var data = _hardwareService.LoadDeviceData();
|
||||
// DeviceData = data;
|
||||
CommunicationStatus = "参数已读取";
|
||||
StatusColor = new SolidColorBrush(Colors.Green);
|
||||
await RefreshAllFromPlc();
|
||||
}
|
||||
|
||||
// 复位
|
||||
[RelayCommand]
|
||||
private void Reset()
|
||||
private async Task RefreshAllFromPlc()
|
||||
{
|
||||
// 用户实现:调用硬件复位
|
||||
// _hardwareService.Reset();
|
||||
_isUpdatingFromPlc = true;
|
||||
try
|
||||
{
|
||||
// 读取并更新每个属性(16位整数和浮点数)
|
||||
UpperLampData1 = await ReadInt16Async(_config.UpperLampData1);
|
||||
UpperLampData2 = await ReadInt16Async(_config.UpperLampData2);
|
||||
UpperLampData3 = await ReadInt16Async(_config.UpperLampData3);
|
||||
UpperLampData4 = await ReadInt16Async(_config.UpperLampData4);
|
||||
UpperLampData5 = await ReadInt16Async(_config.UpperLampData5);
|
||||
UpperLampData6 = await ReadInt16Async(_config.UpperLampData6);
|
||||
|
||||
LowerLampData1 = await ReadInt16Async(_config.LowerLampData1);
|
||||
LowerLampData2 = await ReadInt16Async(_config.LowerLampData2);
|
||||
LowerLampData3 = await ReadInt16Async(_config.LowerLampData3);
|
||||
LowerLampData4 = await ReadInt16Async(_config.LowerLampData4);
|
||||
LowerLampData5 = await ReadInt16Async(_config.LowerLampData5);
|
||||
LowerLampData6 = await ReadInt16Async(_config.LowerLampData6);
|
||||
|
||||
MiddleLamp1 = await ReadInt16Async(_config.MiddleLamp1);
|
||||
MiddleLamp2 = await ReadInt16Async(_config.MiddleLamp2);
|
||||
MiddleLamp3 = await ReadInt16Async(_config.MiddleLamp3);
|
||||
MiddleLamp4 = await ReadInt16Async(_config.MiddleLamp4);
|
||||
MiddleLamp5 = await ReadInt16Async(_config.MiddleLamp5);
|
||||
MiddleLamp6 = await ReadInt16Async(_config.MiddleLamp6);
|
||||
MiddleLamp7 = await ReadInt16Async(_config.MiddleLamp7);
|
||||
|
||||
LeftEyeAreaCoeff = await ReadFloatAsync(_config.LeftEyeAreaCoeff);
|
||||
RightEyeAreaCoeff = await ReadFloatAsync(_config.RightEyeAreaCoeff);
|
||||
SaveRateCorrectionCoeff = await ReadFloatAsync(_config.SaveRateCorrectionCoeff);
|
||||
|
||||
MotorLimit = await ReadFloatAsync(_config.MotorLimit);
|
||||
ResetCompensation = await ReadFloatAsync(_config.ResetCompensation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"刷新参数失败: {ex.Message}");
|
||||
CommunicationStatus = "通信异常";
|
||||
StatusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingFromPlc = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 设备控制命令(供右侧按钮使用)
|
||||
[RelayCommand]
|
||||
private async Task Reset()
|
||||
{
|
||||
// 实现硬件复位逻辑
|
||||
CommunicationStatus = "复位指令已发送";
|
||||
|
||||
await _plcService.WriteCoilAsync(91, true);
|
||||
Thread.Sleep(100);
|
||||
await _plcService.WriteCoilAsync(91, false);
|
||||
Thread.Sleep(100);
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 左眼开
|
||||
[RelayCommand]
|
||||
private void OpenLeftEye()
|
||||
private async Task OpenLeftEye()
|
||||
{
|
||||
// _hardwareService.OpenLeftEye();
|
||||
CommunicationStatus = "左眼开启指令已发送";
|
||||
await _plcService.WriteCoilAsync(0, true);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// 右眼开
|
||||
[RelayCommand]
|
||||
private void OpenRightEye()
|
||||
private async Task OpenRightEye()
|
||||
{
|
||||
// _hardwareService.OpenRightEye();
|
||||
CommunicationStatus = "右眼开启指令已发送";
|
||||
await _plcService.WriteCoilAsync(1, true);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// 反转
|
||||
[RelayCommand]
|
||||
private void Reverse()
|
||||
private async Task Reverse()
|
||||
{
|
||||
// _hardwareService.ReverseMotor();
|
||||
CommunicationStatus = "反转指令已发送";
|
||||
await _plcService.WriteCoilAsync(10, true);
|
||||
Thread.Sleep(100);
|
||||
await _plcService.WriteCoilAsync(10, false);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// 正转
|
||||
[RelayCommand]
|
||||
private void Forward()
|
||||
private async Task Forward()
|
||||
{
|
||||
// _hardwareService.ForwardMotor();
|
||||
CommunicationStatus = "正转指令已发送";
|
||||
await _plcService.WriteCoilAsync(11, true);
|
||||
Thread.Sleep(100);
|
||||
await _plcService.WriteCoilAsync(11, false);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// 导航命令(供用户自行实现页面跳转)
|
||||
// 导航命令(可根据实际需求实现)
|
||||
[RelayCommand]
|
||||
private void NavigateHome()
|
||||
{
|
||||
// 用户实现:切换到主页视图
|
||||
CommunicationStatus = "导航至主页";
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateTest()
|
||||
{
|
||||
CommunicationStatus = "导航至测试界面";
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateDataRecord()
|
||||
{
|
||||
CommunicationStatus = "导航至数据记录";
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateRecordScreen()
|
||||
{
|
||||
CommunicationStatus = "导航至记录画面";
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
// ========== PLC 读写基础方法 ==========
|
||||
private async Task<ushort> ReadInt16Async(ushort address)
|
||||
{
|
||||
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 1);
|
||||
return registers[0];
|
||||
}
|
||||
|
||||
private async Task WriteInt16Async(ushort address, ushort value)
|
||||
{
|
||||
await ((ModbusTcpPlcService)_plcService).WriteSingleRegisterAsync(address, value);
|
||||
}
|
||||
|
||||
private async Task<float> ReadFloatAsync(ushort address)
|
||||
{
|
||||
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 2);
|
||||
return ((ModbusTcpPlcService)_plcService).UshortToFloat(registers[1], registers[0]);
|
||||
}
|
||||
|
||||
private async Task WriteFloatAsync(ushort address, float value)
|
||||
{
|
||||
await ((ModbusTcpPlcService)_plcService).WriteMultipleRegistersAsync(address, value);
|
||||
}
|
||||
|
||||
// 释放定时器(在窗口关闭时调用)
|
||||
public void Dispose()
|
||||
{
|
||||
_autoRefreshTimer?.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,39 +128,39 @@
|
||||
|
||||
<!-- 第1行 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="上灯条数据1"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData1" Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.UpperLampData1, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData1" Grid.Row="0" Grid.Column="1" Text="{Binding UpperLampData1, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="上灯条数据4"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData4" Grid.Row="0" Grid.Column="3" Text="{Binding DeviceData.UpperLampData4, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData4" Grid.Row="0" Grid.Column="3" Text="{Binding UpperLampData4, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第2行 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="下灯条数据1"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData1" Grid.Column="1" Text="{Binding DeviceData.LowerLampData1, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData1" Grid.Column="1" Text="{Binding LowerLampData1, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="下灯条数据4"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData4" Grid.Column="3" Text="{Binding DeviceData.LowerLampData4, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData4" Grid.Column="3" Text="{Binding LowerLampData4, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第3行 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="上灯条数据2"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData2" Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.UpperLampData2, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData2" Grid.Row="2" Grid.Column="1" Text="{Binding UpperLampData2, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="2" Grid.Column="2" Content="上灯条数据5"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData5" Grid.Row="2" Grid.Column="3" Text="{Binding DeviceData.UpperLampData5, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData5" Grid.Row="2" Grid.Column="3" Text="{Binding UpperLampData5, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第4行 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="下灯条数据2"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="3" Name="txtLowerLampData2" Grid.Column="1" Text="{Binding DeviceData.LowerLampData2, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="3" Name="txtLowerLampData2" Grid.Column="1" Text="{Binding LowerLampData2, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="3" Grid.Column="2" Content="下灯条数据5"/>
|
||||
<TextBox IsReadOnly="True" Name="txtLowerLampData5" Grid.Row="3" Grid.Column="3" Text="{Binding DeviceData.LowerLampData5, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtLowerLampData5" Grid.Row="3" Grid.Column="3" Text="{Binding LowerLampData5, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第5行 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="上灯条数据3"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData3" Grid.Row="4" Grid.Column="1" Text="{Binding DeviceData.UpperLampData3, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData3" Grid.Row="4" Grid.Column="1" Text="{Binding UpperLampData3, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="4" Grid.Column="2" Content="上灯条数据6"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData6" Grid.Row="4" Grid.Column="3" Text="{Binding DeviceData.UpperLampData6, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData6" Grid.Row="4" Grid.Column="3" Text="{Binding UpperLampData6, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第6行 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="下灯条数据3"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="5" Name="txtLowerLampData3" Grid.Column="1" Text="{Binding DeviceData.LowerLampData3, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="5" Name="txtLowerLampData3" Grid.Column="1" Text="{Binding LowerLampData3, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="5" Grid.Column="2" Content="下灯条数据6"/>
|
||||
<TextBox IsReadOnly="True" Name="txtLowerLampData6" Grid.Row="5" Grid.Column="3" Text="{Binding DeviceData.LowerLampData6, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtLowerLampData6" Grid.Row="5" Grid.Column="3" Text="{Binding LowerLampData6, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
@@ -178,13 +178,13 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="左眼面积系数"/>
|
||||
<TextBox x:Name="txtLeftEyeAreaCoeff" Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.LeftEyeAreaCoeff, UpdateSourceTrigger=PropertyChanged}" Width="120"/>
|
||||
<TextBox x:Name="txtLeftEyeAreaCoeff" Grid.Row="0" Grid.Column="1" Text="{Binding LeftEyeAreaCoeff, UpdateSourceTrigger=LostFocus}" Width="120"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="右眼面积系数"/>
|
||||
<TextBox x:Name="txtRightEyeAreaCoeff" Grid.Row="1" Grid.Column="1" Text="{Binding DeviceData.RightEyeAreaCoeff, UpdateSourceTrigger=PropertyChanged}" Width="120"/>
|
||||
<TextBox x:Name="txtRightEyeAreaCoeff" Grid.Row="1" Grid.Column="1" Text="{Binding RightEyeAreaCoeff, UpdateSourceTrigger=LostFocus}" Width="120"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="保存率矫正系数"/>
|
||||
<TextBox x:Name="txtSaveRateCorrectionCoeff" Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.SaveRateCorrectionCoeff, UpdateSourceTrigger=PropertyChanged}" Width="120"/>
|
||||
<TextBox x:Name="txtSaveRateCorrectionCoeff" Grid.Row="2" Grid.Column="1" Text="{Binding SaveRateCorrectionCoeff, UpdateSourceTrigger=LostFocus}" Width="120"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
@@ -206,25 +206,25 @@
|
||||
|
||||
<!-- 第1行 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="中灯1"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp1" Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp1, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp1" Grid.Row="0" Grid.Column="1" Text="{Binding MiddleLamp1, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="中灯5"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp5" Grid.Row="0" Grid.Column="3" Text="{Binding DeviceData.MiddleLamp5, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp5" Grid.Row="0" Grid.Column="3" Text="{Binding MiddleLamp5, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第2行 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="中灯2"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp2" Grid.Row="1" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp2, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp2" Grid.Row="1" Grid.Column="1" Text="{Binding MiddleLamp2, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="中灯6"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp6" Grid.Row="1" Grid.Column="3" Text="{Binding DeviceData.MiddleLamp6, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp6" Grid.Row="1" Grid.Column="3" Text="{Binding MiddleLamp6, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第3行 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="中灯3"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp3" Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp3, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp3" Grid.Row="2" Grid.Column="1" Text="{Binding MiddleLamp3, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="2" Grid.Column="2" Content="中灯7"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp7" Grid.Row="2" Grid.Column="3" Text="{Binding DeviceData.MiddleLamp7, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp7" Grid.Row="2" Grid.Column="3" Text="{Binding MiddleLamp7, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第4行 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="中灯4"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp4" Grid.Row="3" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp4, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp4" Grid.Row="3" Grid.Column="1" Text="{Binding MiddleLamp4, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
@@ -236,9 +236,9 @@
|
||||
<GroupBox Header="⚙️ 电机状态">
|
||||
<StackPanel Margin="5">
|
||||
<Label Content="电机限位" FontWeight="SemiBold"/>
|
||||
<TextBox x:Name="txtMotorLimit" Text="{Binding DeviceData.MotorLimit, UpdateSourceTrigger=PropertyChanged}" Height="28" Margin="0,0,0,12"/>
|
||||
<TextBox x:Name="txtMotorLimit" Text="{Binding MotorLimit, UpdateSourceTrigger=LostFocus}" Height="28" Margin="0,0,0,12"/>
|
||||
<Label Content="复位补偿" FontWeight="SemiBold"/>
|
||||
<TextBox x:Name="txtResetCompensation" Text="{Binding DeviceData.MotorLimit, UpdateSourceTrigger=PropertyChanged}" Height="28" Margin="0,0,0,12"/>
|
||||
<TextBox x:Name="txtResetCompensation" Text="{Binding ResetCompensation, UpdateSourceTrigger=LostFocus}" Height="28" Margin="0,0,0,12"/>
|
||||
<!--<Button Content="💾 保存参数" Command="{Binding SaveParametersCommand}" Margin="0,5" Background="#4CAF50"/>
|
||||
<Button Content="🔄 读取参数" Command="{Binding LoadParametersCommand}" Margin="0,5" Background="#FF9800"/>-->
|
||||
</StackPanel>
|
||||
@@ -256,14 +256,14 @@
|
||||
</GroupBox>
|
||||
|
||||
<!-- 通信状态 -->
|
||||
<GroupBox Header="📡 通信状态">
|
||||
<!--<GroupBox Header="📡 通信状态">
|
||||
<Border Background="#F8F9FA" CornerRadius="4" Padding="10" Margin="5">
|
||||
<StackPanel>
|
||||
<Ellipse Width="12" Height="12" Fill="{Binding StatusColor}" Margin="0,0,0,5" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="{Binding CommunicationStatus}" FontWeight="Bold" Foreground="{Binding StatusColor}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</GroupBox>
|
||||
</GroupBox>-->
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -1,189 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MembranePoreTester.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using MembranePoreTester.Communication;
|
||||
|
||||
namespace MembranePoreTester
|
||||
{
|
||||
public partial class ParameterWindow : Window
|
||||
{
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _config;
|
||||
private DispatcherTimer _autoRefreshTimer;
|
||||
|
||||
// 文本框映射:(文本框, 地址, 参数名, 是否浮点数)
|
||||
private readonly List<(TextBox textBox, ushort address, string name, bool isFloat)> _textBoxMapping = new();
|
||||
private ParameterViewModel _viewModel;
|
||||
|
||||
public ParameterWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_plcService = App.PlcService;
|
||||
_config = App.PlcConfig;
|
||||
|
||||
// 16位整数(灯条、中灯、电机)
|
||||
AddMapping(txtUpperLampData1, _config.UpperLampData1, "上灯条数据1", false);
|
||||
AddMapping(txtLowerLampData1, _config.LowerLampData1, "下灯条数据1", false);
|
||||
AddMapping(txtUpperLampData2, _config.UpperLampData2, "上灯条数据2", false);
|
||||
AddMapping(txtLowerLampData2, _config.LowerLampData2, "下灯条数据2", false);
|
||||
AddMapping(txtUpperLampData3, _config.UpperLampData3, "上灯条数据3", false);
|
||||
AddMapping(txtLowerLampData3, _config.LowerLampData3, "下灯条数据3", false);
|
||||
AddMapping(txtUpperLampData4, _config.UpperLampData4, "上灯条数据4", false);
|
||||
AddMapping(txtLowerLampData4, _config.LowerLampData4, "下灯条数据4", false);
|
||||
AddMapping(txtUpperLampData5, _config.UpperLampData5, "上灯条数据5", false);
|
||||
AddMapping(txtLowerLampData5, _config.LowerLampData5, "下灯条数据5", false);
|
||||
AddMapping(txtUpperLampData6, _config.UpperLampData6, "上灯条数据6", false);
|
||||
AddMapping(txtLowerLampData6, _config.LowerLampData6, "下灯条数据6", false);
|
||||
|
||||
AddMapping(txtMiddleLamp1, _config.MiddleLamp1, "中灯1", false);
|
||||
AddMapping(txtMiddleLamp2, _config.MiddleLamp2, "中灯2", false);
|
||||
AddMapping(txtMiddleLamp3, _config.MiddleLamp3, "中灯3", false);
|
||||
AddMapping(txtMiddleLamp4, _config.MiddleLamp4, "中灯4", false);
|
||||
AddMapping(txtMiddleLamp5, _config.MiddleLamp5, "中灯5", false);
|
||||
AddMapping(txtMiddleLamp6, _config.MiddleLamp6, "中灯6", false);
|
||||
AddMapping(txtMiddleLamp7, _config.MiddleLamp7, "中灯7", false);
|
||||
|
||||
AddMapping(txtMotorLimit, _config.MotorLimit, "电机限位", true);
|
||||
AddMapping(txtResetCompensation, _config.ResetCompensation, "复位补偿", true);
|
||||
|
||||
|
||||
AddMapping(txtLeftEyeAreaCoeff, _config.LeftEyeAreaCoeff, "左眼面积系数", true);
|
||||
AddMapping(txtRightEyeAreaCoeff, _config.RightEyeAreaCoeff, "右眼面积系数", true);
|
||||
AddMapping(txtSaveRateCorrectionCoeff, _config.SaveRateCorrectionCoeff, "保存率矫正系数", true);
|
||||
|
||||
RegisterTextBoxEvents();
|
||||
|
||||
Loaded += async (s, e) =>
|
||||
{
|
||||
_autoRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
_autoRefreshTimer.Tick += AutoRefreshTimer_Tick;
|
||||
_autoRefreshTimer.Start();
|
||||
await LoadAllParameters();
|
||||
};
|
||||
|
||||
Closed += (s, e) => _autoRefreshTimer?.Stop();
|
||||
}
|
||||
|
||||
private void AddMapping(TextBox textBox, ushort address, string name, bool isFloat)
|
||||
{
|
||||
if (textBox != null)
|
||||
{
|
||||
_textBoxMapping.Add((textBox, address, name, isFloat));
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterTextBoxEvents()
|
||||
{
|
||||
foreach (var (tb, addr, name, isFloat) in _textBoxMapping)
|
||||
{
|
||||
tb.LostFocus += async (s, e) =>
|
||||
{
|
||||
if (float.TryParse(tb.Text, out float value))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isFloat)
|
||||
await WriteFloatAsync(addr, value);
|
||||
else
|
||||
await WriteInt16Async(addr, (ushort)value);
|
||||
System.Diagnostics.Debug.WriteLine($"{name} 已写入: {value}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"写入 {name} 失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show($"请输入有效的数值", "输入错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
await UpdateSingleParameter(tb, addr, isFloat);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async void AutoRefreshTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
foreach (var (tb, addr, name, isFloat) in _textBoxMapping)
|
||||
{
|
||||
if (!tb.IsFocused)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isFloat)
|
||||
{
|
||||
float val = await ReadFloatAsync(addr);
|
||||
tb.Text = val.ToString("F3");
|
||||
}
|
||||
else
|
||||
{
|
||||
ushort val = await ReadInt16Async(addr);
|
||||
tb.Text = val.ToString();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"自动刷新 {name} 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task UpdateSingleParameter(TextBox tb, ushort addr, bool isFloat)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isFloat)
|
||||
{
|
||||
float val = await ReadFloatAsync(addr);
|
||||
tb.Text = val.ToString("F3");
|
||||
}
|
||||
else
|
||||
{
|
||||
ushort val = await ReadInt16Async(addr);
|
||||
tb.Text = val.ToString();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"更新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadAllParameters()
|
||||
{
|
||||
foreach (var (tb, addr, name, isFloat) in _textBoxMapping)
|
||||
{
|
||||
await UpdateSingleParameter(tb, addr, isFloat);
|
||||
}
|
||||
}
|
||||
|
||||
// 16位整数读写
|
||||
private async Task<ushort> ReadInt16Async(ushort address)
|
||||
{
|
||||
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 1);
|
||||
return registers[0];
|
||||
}
|
||||
|
||||
private async Task WriteInt16Async(ushort address, ushort value)
|
||||
{
|
||||
await ((ModbusTcpPlcService)_plcService).WriteSingleRegisterAsync(address, value);
|
||||
}
|
||||
|
||||
// 32位浮点数读写
|
||||
private async Task<float> ReadFloatAsync(ushort address)
|
||||
{
|
||||
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 2);
|
||||
return ((ModbusTcpPlcService)_plcService).UshortToFloat(registers[1], registers[0]);
|
||||
}
|
||||
|
||||
private async Task WriteFloatAsync(ushort address, float value)
|
||||
{
|
||||
await ((ModbusTcpPlcService)_plcService).WriteMultipleRegistersAsync(address, value);
|
||||
_viewModel = new ParameterViewModel();
|
||||
DataContext = _viewModel;
|
||||
Closed += (s, e) => _viewModel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user