更新modbus最新
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace COFTester.Models
|
||||
{
|
||||
@@ -187,6 +188,16 @@ namespace COFTester.Models
|
||||
event EventHandler TestFinished; // 測試完成事件
|
||||
event EventHandler<string> ErrorOccurred; // 錯誤發生事件
|
||||
|
||||
/// <summary>
|
||||
/// 異步連接到設備
|
||||
/// </summary>
|
||||
Task<bool> ConnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 斷開設備連接
|
||||
/// </summary>
|
||||
void Disconnect();
|
||||
|
||||
void StartAcquisition(TestParameters parameters);
|
||||
void StopAcquisition();
|
||||
void ResetSensors();
|
||||
|
||||
@@ -185,13 +185,32 @@ namespace COFTester.Services
|
||||
{
|
||||
private CancellationTokenSource _cts;
|
||||
private bool _isAcquiring;
|
||||
private bool _isConnected;
|
||||
private readonly Random _random = new Random();
|
||||
|
||||
public event EventHandler<TestDataPoint> DataReceived;
|
||||
public event EventHandler TestFinished;
|
||||
public event EventHandler<string> ErrorOccurred;
|
||||
|
||||
public bool IsConnected => true; // 模擬始終連接
|
||||
public bool IsConnected => _isConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 模擬連接(始終成功)
|
||||
/// </summary>
|
||||
public Task<bool> ConnectAsync()
|
||||
{
|
||||
_isConnected = true;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模擬斷開連接
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
StopAcquisition();
|
||||
_isConnected = false;
|
||||
}
|
||||
|
||||
public void StartAcquisition(TestParameters parameters)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
using System.Threading.Tasks;
|
||||
using COFTester.Models;
|
||||
using COFTester.Services;
|
||||
using COFTester.Resources;
|
||||
@@ -15,48 +16,56 @@ using ScottPlot.WPF;
|
||||
|
||||
namespace COFTester.ViewModels
|
||||
{
|
||||
public class MainViewModel : INotifyPropertyChanged
|
||||
public class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private readonly IDataAcquisitionService _daqService;
|
||||
private readonly DataProcessingService _processingService;
|
||||
private readonly DispatcherTimer _clockTimer;
|
||||
private readonly WpfPlot _wpfPlot;
|
||||
private readonly AppConfig _config;
|
||||
private ScottPlot.Plottables.Scatter _scatterPlot = null!;
|
||||
private readonly Dictionary<Guid, ScottPlot.Plottables.Scatter> _testCurves = new();
|
||||
|
||||
private double _currentForce;
|
||||
private double _currentDisp;
|
||||
private bool _isTesting;
|
||||
private bool _isConnecting;
|
||||
private TestResult? _latestResult;
|
||||
private string _statusMessage;
|
||||
private DateTime _currentDateTime = DateTime.Now;
|
||||
private List<TestDataPoint> _realTimePoints;
|
||||
private bool _showAllCurves = true;
|
||||
private int _testCounter = 0;
|
||||
private bool _disposed = false;
|
||||
|
||||
public MainViewModel(IDataAcquisitionService daqService, DataProcessingService processingService, WpfPlot wpfPlot)
|
||||
public MainViewModel(IDataAcquisitionService daqService, DataProcessingService processingService, WpfPlot wpfPlot, AppConfig config)
|
||||
{
|
||||
_daqService = daqService;
|
||||
_processingService = processingService;
|
||||
_wpfPlot = wpfPlot;
|
||||
_daqService = daqService ?? throw new ArgumentNullException(nameof(daqService));
|
||||
_processingService = processingService ?? throw new ArgumentNullException(nameof(processingService));
|
||||
_wpfPlot = wpfPlot ?? throw new ArgumentNullException(nameof(wpfPlot));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_realTimePoints = new List<TestDataPoint>();
|
||||
|
||||
// 初始化状态消息
|
||||
_statusMessage = LanguageResources.Instance.SystemReady;
|
||||
|
||||
// 訂閱數據採集服務事件
|
||||
_daqService.DataReceived += OnDataReceived;
|
||||
_daqService.TestFinished += OnTestFinished;
|
||||
_daqService.ErrorOccurred += (s, e) => StatusMessage = string.Format(Lang.Error, e);
|
||||
_daqService.ErrorOccurred += OnErrorOccurred;
|
||||
|
||||
StartCommand = new RelayCommand(StartTest, () => !_isTesting && _daqService.IsConnected);
|
||||
// 初始化命令
|
||||
ConnectCommand = new AsyncRelayCommand(ConnectAsync, () => !IsConnected && !_isConnecting);
|
||||
DisconnectCommand = new RelayCommand(Disconnect, () => IsConnected && !_isTesting);
|
||||
StartCommand = new RelayCommand(StartTest, () => !_isTesting && IsConnected);
|
||||
StopCommand = new RelayCommand(StopTest, () => _isTesting);
|
||||
ResetCommand = new RelayCommand(Reset, () => !_isTesting);
|
||||
ResetCommand = new RelayCommand(Reset, () => !_isTesting && IsConnected);
|
||||
SwitchLanguageCommand = new RelayCommand(SwitchLanguage);
|
||||
OpenConfigCommand = new RelayCommand(OpenConfig);
|
||||
ClearAllTestsCommand = new RelayCommand(ClearAllTests, () => !_isTesting && TestRecords.Count > 0);
|
||||
GenerateReportCommand = new RelayCommand(GenerateReport, () => TestRecords.Count > 0);
|
||||
|
||||
Parameters = new TestParameters();
|
||||
Parameters = _config.DefaultTestParameters ?? new TestParameters();
|
||||
TestRecords = new ObservableCollection<TestResult>();
|
||||
TestRecords.CollectionChanged += (s, e) =>
|
||||
{
|
||||
@@ -74,6 +83,16 @@ namespace COFTester.ViewModels
|
||||
};
|
||||
_clockTimer.Tick += (s, e) => CurrentDateTime = DateTime.Now;
|
||||
_clockTimer.Start();
|
||||
|
||||
// 自動連接(如果是模擬模式則直接連接)
|
||||
if (_config.CommunicationMode?.ToUpper() == "SIMULATED")
|
||||
{
|
||||
StatusMessage = "模擬模式 - 已就緒";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"通信模式: {_config.CommunicationMode} - 請點擊連接";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -186,6 +205,49 @@ namespace COFTester.ViewModels
|
||||
|
||||
public TestParameters Parameters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 設備連接狀態
|
||||
/// </summary>
|
||||
public bool IsConnected => _daqService.IsConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在連接
|
||||
/// </summary>
|
||||
public bool IsConnecting
|
||||
{
|
||||
get => _isConnecting;
|
||||
set
|
||||
{
|
||||
_isConnecting = value;
|
||||
OnPropertyChanged();
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 當前通信模式
|
||||
/// </summary>
|
||||
public string CommunicationMode => _config.CommunicationMode ?? "Unknown";
|
||||
|
||||
/// <summary>
|
||||
/// 連接信息顯示
|
||||
/// </summary>
|
||||
public string ConnectionInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return _config.CommunicationMode?.ToUpper() switch
|
||||
{
|
||||
"MODBUSTCP" => $"TCP: {_config.ModbusTcp.IpAddress}:{_config.ModbusTcp.Port}",
|
||||
"MODBUSRTU" => $"RTU: {_config.ModbusRtu.PortName} @ {_config.ModbusRtu.BaudRate}",
|
||||
"MODBUSASCII" => $"ASCII: {_config.ModbusRtu.PortName} @ {_config.ModbusRtu.BaudRate}",
|
||||
"SERIALPORT" => $"串口: {_config.SerialPort.PortName} @ {_config.SerialPort.BaudRate}",
|
||||
"SIMULATED" => "模擬模式",
|
||||
_ => "未知模式"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 所有测试记录集合
|
||||
/// </summary>
|
||||
@@ -284,6 +346,8 @@ namespace COFTester.ViewModels
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
public ICommand ConnectCommand { get; }
|
||||
public ICommand DisconnectCommand { get; }
|
||||
public ICommand StartCommand { get; }
|
||||
public ICommand StopCommand { get; }
|
||||
public ICommand ResetCommand { get; }
|
||||
@@ -344,12 +408,103 @@ namespace COFTester.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
private void OnErrorOccurred(object? sender, string error)
|
||||
{
|
||||
Application.Current?.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
StatusMessage = $"錯誤: {error}";
|
||||
System.Diagnostics.Debug.WriteLine($"[Modbus Error] {error}");
|
||||
|
||||
// 嚴重錯誤時停止測試
|
||||
if (error.Contains("連接") || error.Contains("超時") || error.Contains("Connection"))
|
||||
{
|
||||
if (_isTesting)
|
||||
{
|
||||
IsTesting = false;
|
||||
}
|
||||
OnPropertyChanged(nameof(IsConnected));
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Actions
|
||||
|
||||
/// <summary>
|
||||
/// 異步連接到設備
|
||||
/// </summary>
|
||||
private async Task ConnectAsync()
|
||||
{
|
||||
if (IsConnected || IsConnecting) return;
|
||||
|
||||
try
|
||||
{
|
||||
IsConnecting = true;
|
||||
StatusMessage = $"正在連接 {ConnectionInfo}...";
|
||||
|
||||
bool connected = await _daqService.ConnectAsync();
|
||||
|
||||
if (connected)
|
||||
{
|
||||
StatusMessage = $"已連接 - {ConnectionInfo}";
|
||||
OnPropertyChanged(nameof(IsConnected));
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = "連接失敗,請檢查設備和配置";
|
||||
MessageBox.Show(
|
||||
$"無法連接到設備\n\n通信模式: {CommunicationMode}\n連接信息: {ConnectionInfo}\n\n請檢查:\n1. 設備是否已開機\n2. 連接線是否正確\n3. 配置參數是否正確",
|
||||
"連接失敗",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"連接錯誤: {ex.Message}";
|
||||
MessageBox.Show($"連接時發生錯誤:\n{ex.Message}", "錯誤", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsConnecting = false;
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 斷開設備連接
|
||||
/// </summary>
|
||||
private void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isTesting)
|
||||
{
|
||||
StopTest();
|
||||
}
|
||||
|
||||
_daqService.Disconnect();
|
||||
StatusMessage = "已斷開連接";
|
||||
OnPropertyChanged(nameof(IsConnected));
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"斷開連接錯誤: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private void StartTest()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
StatusMessage = "請先連接設備";
|
||||
MessageBox.Show("請先連接設備後再開始測試", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
_realTimePoints.Clear();
|
||||
OnPropertyChanged(nameof(DataPointsCount));
|
||||
LatestResult = null;
|
||||
@@ -568,6 +723,55 @@ namespace COFTester.ViewModels
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// 停止時鐘
|
||||
_clockTimer?.Stop();
|
||||
|
||||
// 取消訂閱事件
|
||||
if (_daqService != null)
|
||||
{
|
||||
_daqService.DataReceived -= OnDataReceived;
|
||||
_daqService.TestFinished -= OnTestFinished;
|
||||
_daqService.ErrorOccurred -= OnErrorOccurred;
|
||||
|
||||
// 停止測試
|
||||
if (_isTesting)
|
||||
{
|
||||
_daqService.StopAcquisition();
|
||||
}
|
||||
|
||||
// 斷開連接
|
||||
_daqService.Disconnect();
|
||||
|
||||
// 釋放服務資源
|
||||
if (_daqService is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
~MainViewModel()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -602,4 +806,53 @@ namespace COFTester.ViewModels
|
||||
// 自動觸發 CanExecuteChanged
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 異步 RelayCommand 實現,用於異步操作(如連接設備)
|
||||
/// </summary>
|
||||
public class AsyncRelayCommand : ICommand
|
||||
{
|
||||
private readonly Func<Task> _execute;
|
||||
private readonly Func<bool>? _canExecute;
|
||||
private bool _isExecuting;
|
||||
|
||||
public AsyncRelayCommand(Func<Task> execute, Func<bool>? canExecute = null)
|
||||
{
|
||||
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return !_isExecuting && (_canExecute?.Invoke() ?? true);
|
||||
}
|
||||
|
||||
public async void Execute(object? parameter)
|
||||
{
|
||||
if (!CanExecute(parameter)) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isExecuting = true;
|
||||
RaiseCanExecuteChanged();
|
||||
await _execute();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isExecuting = false;
|
||||
RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
|
||||
private void RaiseCanExecuteChanged()
|
||||
{
|
||||
Application.Current?.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,40 @@
|
||||
<Border Style="{StaticResource CardStyle}" Margin="5,10,5,5">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Lang.TestControl}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
|
||||
|
||||
<!-- 連接控制 -->
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Content="連接" Command="{Binding ConnectCommand}" Height="36" Foreground="White" FontSize="12" Margin="0,0,3,0">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#27AE60"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
<Button Grid.Column="1" Content="斷開" Command="{Binding DisconnectCommand}" Height="36" Foreground="White" FontSize="12" Margin="3,0,0,0">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#95A5A6"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- 連接狀態顯示 -->
|
||||
<Border Background="#F8F9FA" CornerRadius="4" Padding="8" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding ConnectionInfo}" FontSize="11" Foreground="{StaticResource GrayBrush}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Button Content="{Binding Lang.StartTest}" Command="{Binding StartCommand}" Height="50" Foreground="White" FontWeight="Bold" Margin="0,5" FontSize="16">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
@@ -331,14 +365,42 @@
|
||||
<StatusBarItem>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Ellipse Width="8" Height="8" Margin="0,0,5,0">
|
||||
<Ellipse.Fill>
|
||||
<SolidColorBrush Color="#27AE60"/>
|
||||
</Ellipse.Fill>
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="Ellipse">
|
||||
<Setter Property="Fill" Value="#E74C3C"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Fill" Value="#27AE60"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
</Ellipse>
|
||||
<TextBlock Text="{Binding Lang.PLCConnected}" Foreground="{StaticResource SuccessBrush}"/>
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="未連接"/>
|
||||
<Setter Property="Foreground" Value="#E74C3C"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="已連接"/>
|
||||
<Setter Property="Foreground" Value="#27AE60"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsConnecting}" Value="True">
|
||||
<Setter Property="Text" Value="連接中..."/>
|
||||
<Setter Property="Foreground" Value="#F39C12"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
<Separator/>
|
||||
<StatusBarItem>
|
||||
<TextBlock Text="{Binding CommunicationMode}" Foreground="{StaticResource GrayBrush}" FontSize="11"/>
|
||||
</StatusBarItem>
|
||||
<Separator/>
|
||||
<StatusBarItem>
|
||||
<TextBlock Foreground="{StaticResource GrayBrush}">
|
||||
<Run Text="{Binding Lang.DataPointsCount, Mode=OneWay}"/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using COFTester.ViewModels;
|
||||
using COFTester.Services;
|
||||
using COFTester.Models;
|
||||
using System.Windows;
|
||||
|
||||
namespace COFTester.Views
|
||||
@@ -9,22 +10,29 @@ namespace COFTester.Views
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly MainViewModel _viewModel;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 實例化服務
|
||||
var daqService = new SimulatedDataAcquisitionService();
|
||||
// 加載配置
|
||||
var config = AppConfig.Load(AppConfig.GetDefaultConfigPath());
|
||||
|
||||
// 根據配置創建數據採集服務(支持 Modbus TCP/RTU/ASCII 和模擬模式)
|
||||
IDataAcquisitionService daqService = ModbusServiceFactory.CreateService(config);
|
||||
var processingService = new DataProcessingService();
|
||||
|
||||
// 設置 DataContext
|
||||
var viewModel = new MainViewModel(daqService, processingService, FrictionPlot);
|
||||
this.DataContext = viewModel;
|
||||
_viewModel = new MainViewModel(daqService, processingService, FrictionPlot, config);
|
||||
this.DataContext = _viewModel;
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
protected override void OnClosed(System.EventArgs e)
|
||||
{
|
||||
|
||||
// 清理資源
|
||||
_viewModel?.Dispose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user