2026-03-25 21:34:41 +08:00
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
2026-04-01 21:30:13 +08:00
|
|
|
|
using MembranePoreTester.Communication;
|
|
|
|
|
|
using Modbus.Device;
|
2026-03-25 21:34:41 +08:00
|
|
|
|
using System;
|
2026-04-01 21:30:13 +08:00
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows.Threading;
|
2026-03-25 21:34:41 +08:00
|
|
|
|
|
|
|
|
|
|
namespace MembranePoreTester.ViewModels
|
|
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
public partial class ParameterViewModel : ObservableObject
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private readonly IPlcService _plcService;
|
|
|
|
|
|
private readonly PlcConfiguration _config;
|
|
|
|
|
|
private DispatcherTimer _autoRefreshTimer;
|
|
|
|
|
|
private bool _isUpdatingFromPlc = false; // 防止定时器更新触发写入
|
2026-03-25 21:34:41 +08:00
|
|
|
|
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 构造函数(依赖注入)
|
|
|
|
|
|
public ParameterViewModel()
|
|
|
|
|
|
{
|
|
|
|
|
|
_plcService = App.PlcService;
|
|
|
|
|
|
_config = App.PlcConfig;
|
|
|
|
|
|
|
|
|
|
|
|
// 启动定时器(每秒自动刷新)
|
|
|
|
|
|
_autoRefreshTimer = new DispatcherTimer
|
|
|
|
|
|
{
|
|
|
|
|
|
Interval = TimeSpan.FromSeconds(1)
|
|
|
|
|
|
};
|
|
|
|
|
|
_autoRefreshTimer.Tick += AutoRefreshTimer_Tick;
|
|
|
|
|
|
_autoRefreshTimer.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 设备数据属性(全部使用源生成器) ==========
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _upperLampData1;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _upperLampData2;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _upperLampData3;
|
2026-03-25 21:34:41 +08:00
|
|
|
|
[ObservableProperty]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private double _upperLampData4;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _upperLampData5;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _upperLampData6;
|
2026-03-25 21:34:41 +08:00
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private double _lowerLampData1;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _lowerLampData2;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _lowerLampData3;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _lowerLampData4;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _lowerLampData5;
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private double _lowerLampData6;
|
2026-03-25 21:34:41 +08:00
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
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;
|
2026-03-25 21:34:41 +08:00
|
|
|
|
|
2026-04-01 21:30:13 +08:00
|
|
|
|
[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)
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
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}", "错误");
|
|
|
|
|
|
}
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-01 21:30:13 +08:00
|
|
|
|
//// ========== 手动保存/读取命令 ==========
|
|
|
|
|
|
//[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)
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
await RefreshAllFromPlc();
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private async Task RefreshAllFromPlc()
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
_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;
|
|
|
|
|
|
}
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 设备控制命令(供右侧按钮使用)
|
2026-03-25 21:34:41 +08:00
|
|
|
|
[RelayCommand]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private async Task Reset()
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 实现硬件复位逻辑
|
2026-03-25 21:34:41 +08:00
|
|
|
|
CommunicationStatus = "复位指令已发送";
|
2026-04-01 21:30:13 +08:00
|
|
|
|
|
|
|
|
|
|
await _plcService.WriteCoilAsync(91, true);
|
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
|
await _plcService.WriteCoilAsync(91, false);
|
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private async Task OpenLeftEye()
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
CommunicationStatus = "左眼开启指令已发送";
|
2026-04-01 21:30:13 +08:00
|
|
|
|
await _plcService.WriteCoilAsync(0, true);
|
|
|
|
|
|
Thread.Sleep(100);
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private async Task OpenRightEye()
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
CommunicationStatus = "右眼开启指令已发送";
|
2026-04-01 21:30:13 +08:00
|
|
|
|
await _plcService.WriteCoilAsync(1, true);
|
|
|
|
|
|
Thread.Sleep(100);
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private async Task Reverse()
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
CommunicationStatus = "反转指令已发送";
|
2026-04-01 21:30:13 +08:00
|
|
|
|
await _plcService.WriteCoilAsync(10, true);
|
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
|
await _plcService.WriteCoilAsync(10, false);
|
|
|
|
|
|
Thread.Sleep(100);
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
2026-04-01 21:30:13 +08:00
|
|
|
|
private async Task Forward()
|
2026-03-25 21:34:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
CommunicationStatus = "正转指令已发送";
|
2026-04-01 21:30:13 +08:00
|
|
|
|
await _plcService.WriteCoilAsync(11, true);
|
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
|
await _plcService.WriteCoilAsync(11, false);
|
|
|
|
|
|
Thread.Sleep(100);
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 导航命令(可根据实际需求实现)
|
2026-03-25 21:34:41 +08:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void NavigateHome()
|
|
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 切换页面逻辑
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void NavigateTest()
|
|
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 切换页面逻辑
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void NavigateDataRecord()
|
|
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 切换页面逻辑
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void NavigateRecordScreen()
|
|
|
|
|
|
{
|
2026-04-01 21:30:13 +08:00
|
|
|
|
// 切换页面逻辑
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 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();
|
2026-03-25 21:34:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|