From 264cee4e1a811465f18b5bac15dcd52966e294a8 Mon Sep 17 00:00:00 2001 From: xyy <544939200@qq.com> Date: Wed, 1 Apr 2026 21:30:13 +0800 Subject: [PATCH] --- ViewModels/ParameterViewModel.cs | 303 +++++++++++++++++++++++++------ Views/ParameterWindow.xaml | 52 +++--- Views/ParameterWindow.xaml.cs | 181 +----------------- 3 files changed, 276 insertions(+), 260 deletions(-) diff --git a/ViewModels/ParameterViewModel.cs b/ViewModels/ParameterViewModel.cs index 04ec20f..ffed32e 100644 --- a/ViewModels/ParameterViewModel.cs +++ b/ViewModels/ParameterViewModel.cs @@ -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 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 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(); } } } \ No newline at end of file diff --git a/Views/ParameterWindow.xaml b/Views/ParameterWindow.xaml index 4d1517a..4b9ba58 100644 --- a/Views/ParameterWindow.xaml +++ b/Views/ParameterWindow.xaml @@ -128,39 +128,39 @@