<Window x:Class="_9696999.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:_9696999"
mc:Ignorable="d"
Title="PLC监控系统" Height="900" Width="1400" Closed="Window_Closed">
<Window.Resources>
<local:StatusColorConverter x:Key="StatusColorConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<!-- PLC连接区域 -->
<RowDefinition Height="Auto"/>
<!-- 电机监控配置 -->
<RowDefinition Height="250"/>
<!-- 电机监控数据 -->
<RowDefinition Height="Auto"/>
<!-- 节拍时间图表配置 -->
<RowDefinition Height="220"/>
<!-- 节拍时间图表 -->
<RowDefinition Height="Auto"/>
<!-- 负载率监控配置 -->
<RowDefinition Height="220"/>
<!-- 负载率图表 -->
<RowDefinition Height="Auto"/>
<!-- 日志区域 -->
</Grid.RowDefinitions>
<!-- PLC连接控制区域 -->
<GroupBox Grid.Row="0" Header="PLC连接设置" Margin="5,5,5,3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="站号:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtStationNumber" Width="50" Margin="5" Text="1"/>
<Button x:Name="btnConnect" Content="连接PLC" Margin="5" Click="Connect_Click" Width="80"/>
<Button x:Name="btnDisconnect" Content="断开连接" Margin="5" Click="Disconnect_Click" Width="80" IsEnabled="False"/>
<TextBlock x:Name="tbConnectionStatus" Text="未连接" Margin="10" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Text="心跳状态:" Margin="5" VerticalAlignment="Center"/>
<TextBlock x:Name="tbHeartbeatStatus" Text="未连接" Margin="5" VerticalAlignment="Center" Foreground="Gray" FontWeight="Bold"/>
</StackPanel>
</GroupBox>
<!-- 电机监控配置 -->
<GroupBox Grid.Row="1" Header="电机监控点位配置" Margin="5,3,5,3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="点位名称:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtPointName" Width="120" Margin="5"/>
<TextBlock Text="点位地址:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtPointAddress" Width="80" Margin="5"/>
<TextBlock Text="标准值:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtStandardValue" Width="80" Margin="5"/>
<Button x:Name="btnAddPoint" Content="添加点位" Margin="5" Click="AddMonitorPoint_Click" Width="80"/>
<Button x:Name="btnSaveMotorConfig" Content="保存配置" Margin="5" Click="SaveMotorConfig_Click" Width="80"/>
<Button x:Name="btnLoadMotorConfig" Content="加载配置" Margin="5" Click="LoadMotorConfig_Click" Width="80"/>
</StackPanel>
</GroupBox>
<!-- 电机监控数据表格 -->
<GroupBox Grid.Row="2" Header="电机点位实时监控" Margin="5,3,5,3">
<DataGrid ItemsSource="{Binding MotorDataList}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTextColumn Header="点位名称" Binding="{Binding PointName}" Width="120"/>
<DataGridTextColumn Header="地址" Binding="{Binding Address}" Width="80"/>
<DataGridTextColumn Header="标准值" Binding="{Binding StandardValue}" Width="80"/>
<DataGridTextColumn Header="实际值" Binding="{Binding ActualValue}" Width="80"/>
<DataGridTextColumn Header="状态" Binding="{Binding Status}" Width="100">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="Foreground"
Value="{Binding Status, Converter={StaticResource StatusColorConverter}}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<!-- 节拍监控配置 -->
<GroupBox Grid.Row="3" Header="节拍时间监控配置" Margin="5,3,5,3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="部位名称:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtSectionName" Width="120" Margin="5"/>
<TextBlock Text="监控地址:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtSectionAddress" Width="80" Margin="5"/>
<Button x:Name="btnAddSection" Content="添加部位" Margin="5" Click="AddSection_Click" Width="80"/>
<Button x:Name="btnSaveRhythmConfig" Content="保存配置" Margin="5" Click="SaveRhythmConfig_Click" Width="80"/>
<Button x:Name="btnLoadRhythmConfig" Content="加载配置" Margin="5" Click="LoadRhythmConfig_Click" Width="80"/>
</StackPanel>
</GroupBox>
<!-- 节拍时间图表 -->
<GroupBox Grid.Row="4" Header="设备节拍时间监控" Margin="5,3,5,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Canvas x:Name="canvasRhythmChart" Background="White"/>
<StackPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Center" Margin="20,0,10,0">
<Rectangle Width="20" Height="20" Fill="Green" Margin="0,5"/>
<!-- 修正:使用 < 替代 < -->
<TextBlock Text="< 10s" Margin="0,5"/>
<Rectangle Width="20" Height="20" Fill="YellowGreen" Margin="0,5"/>
<TextBlock Text="10-20s" Margin="0,5"/>
<Rectangle Width="20" Height="20" Fill="Orange" Margin="0,5"/>
<TextBlock Text="20-30s" Margin="0,5"/>
<Rectangle Width="20" Height="20" Fill="Red" Margin="0,5"/>
<!-- 修正:使用 > 替代 > -->
<TextBlock Text="> 30s" Margin="0,5"/>
</StackPanel>
</Grid>
</GroupBox>
<!-- 负载监控配置 -->
<GroupBox Grid.Row="5" Header="伺服电机负载率监控配置" Margin="5,3,5,3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="电机名称:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtMotorName" Width="120" Margin="5"/>
<TextBlock Text="监控地址:" VerticalAlignment="Center" Margin="5"/>
<TextBox x:Name="txtMotorAddress" Width="80" Margin="5"/>
<Button x:Name="btnAddMotor" Content="添加电机" Margin="5" Click="AddMotor_Click" Width="80"/>
<Button x:Name="btnSaveLoadConfig" Content="保存配置" Margin="5" Click="SaveLoadConfig_Click" Width="80"/>
<Button x:Name="btnLoadLoadConfig" Content="加载配置" Margin="5" Click="LoadLoadConfig_Click" Width="80"/>
</StackPanel>
</GroupBox>
<!-- 负载率图表 -->
<GroupBox Grid.Row="6" Header="伺服电机负载率监控" Margin="5,3,5,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Canvas x:Name="canvasLoadChart" Background="White"/>
<StackPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Center" Margin="20,0,10,0">
<Rectangle Width="20" Height="20" Fill="Green" Margin="0,5"/>
<!-- 修正:使用 < 替代 < -->
<TextBlock Text="< 80%" Margin="0,5"/>
<Rectangle Width="20" Height="20" Fill="Red" Margin="0,5"/>
<!-- 修正:使用 ≥ 替代 >= -->
<TextBlock Text="≥ 80%" Margin="0,5"/>
</StackPanel>
</Grid>
</GroupBox>
<!-- 日志区域 -->
<GroupBox Grid.Row="7" Header="通讯日志" Margin="5,3,5,5">
<TextBox x:Name="txtCommunicationLog"
IsReadOnly="True"
VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
FontFamily="Consolas"
Height="120"/>
</GroupBox>
</Grid>
</Window>using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using ActUtlType64Lib;
using Microsoft.Win32;
using System.Text.Json.Serialization;
namespace _9696999
{
public partial class MainWindow : Window
{
private PlcMonitorViewModel _viewModel;
private ActUtlType64 _plc;
private DispatcherTimer _readTimer;
private DispatcherTimer _logClearTimer;
private DispatcherTimer _heartbeatTimer; // 心跳定时器
private bool _isConnected = false;
private int _stationNumber = 1; // 保存站号用于重连
private int _connectionRetryCount = 0; // 重试次数计数
private const int MAX_RETRY_COUNT = 5; // 最大重试次数
// 存储上一次的值用于比较变化
private ObservableCollection<float> _lastMotorValues = new ObservableCollection<float>();
private ObservableCollection<float> _lastRhythmValues = new ObservableCollection<float>();
private ObservableCollection<float> _lastLoadValues = new ObservableCollection<float>();
public MainWindow()
{
InitializeComponent();
_viewModel = new PlcMonitorViewModel();
DataContext = _viewModel;
_plc = new ActUtlType64();
_readTimer = new DispatcherTimer();
_logClearTimer = new DispatcherTimer();
_heartbeatTimer = new DispatcherTimer(); // 初始化心跳定时器
InitializePlc();
InitializeLogClearTimer();
InitializeHeartbeatTimer(); // 初始化心跳定时器
}
private void InitializeHeartbeatTimer()
{
_heartbeatTimer.Interval = TimeSpan.FromSeconds(5); // 5秒检测一次
_heartbeatTimer.Tick += HeartbeatTimer_Tick;
_heartbeatTimer.Start();
AddLog("心跳检测功能已启动");
}
private void HeartbeatTimer_Tick(object? sender, EventArgs e)
{
// 只有在已连接状态下才检测心跳
if (!_isConnected) return;
try
{
// 尝试读取一个测试点来检测连接状态
short testValue;
int result = _plc.ReadDeviceBlock2("D0", 1, out testValue);
if (result != 0)
{
// 读取失败,连接可能已断开
_isConnected = false;
_readTimer.Stop();
Application.Current.Dispatcher.Invoke(() =>
{
tbConnectionStatus.Text = "连接断开 (尝试重连中...)";
tbHeartbeatStatus.Text = "连接异常";
tbHeartbeatStatus.Foreground = Brushes.Red;
});
AddLog($"心跳检测失败,错误代码: {result}");
TryReconnect();
}
else
{
// 连接正常
Application.Current.Dispatcher.Invoke(() =>
{
tbHeartbeatStatus.Text = "连接正常";
tbHeartbeatStatus.Foreground = Brushes.Green;
});
}
}
catch (Exception ex)
{
AddLog($"心跳检测异常: {ex.Message}");
_isConnected = false;
_readTimer.Stop();
TryReconnect();
}
}
private void TryReconnect()
{
if (_connectionRetryCount >= MAX_RETRY_COUNT)
{
AddLog($"已达到最大重试次数({MAX_RETRY_COUNT}),停止自动重连");
_heartbeatTimer.Stop();
return;
}
_connectionRetryCount++;
AddLog($"尝试第 {_connectionRetryCount} 次重新连接...");
try
{
_plc.Close(); // 确保先关闭连接
int result = _plc.Open();
if (result == 0)
{
_isConnected = true;
_connectionRetryCount = 0; // 重置重试计数
Application.Current.Dispatcher.Invoke(() =>
{
tbConnectionStatus.Text = $"已重新连接 (站号: {_stationNumber})";
tbHeartbeatStatus.Text = "连接正常";
tbHeartbeatStatus.Foreground = Brushes.Green;
btnConnect.IsEnabled = false;
btnDisconnect.IsEnabled = true;
});
_readTimer.Start();
AddLog($"PLC重新连接成功 (站号: {_stationNumber})");
}
else
{
AddLog($"重新连接失败,错误代码: {result}");
}
}
catch (Exception ex)
{
AddLog($"重新连接异常: {ex.Message}");
}
}
private void InitializeLogClearTimer()
{
_logClearTimer = new DispatcherTimer();
// 设置间隔为10分钟(600,000毫秒)
_logClearTimer.Interval = TimeSpan.FromMinutes(1);
_logClearTimer.Tick += LogClearTimer_Tick;
_logClearTimer.Start();
AddLog("日志自动清除功能已启动(每隔10分钟清除一次)");
}
private void LogClearTimer_Tick(object? sender, EventArgs e)
{
ClearCommunicationLog();
}
private void ClearCommunicationLog()
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(ClearCommunicationLog);
return;
}
// 记录清除操作
string clearMessage = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 已自动清除通讯日志";
// 清除日志
txtCommunicationLog.Text = clearMessage + Environment.NewLine;
// 可选:添加日志记录表明已执行清除操作
AddLog("通讯日志已自动清除");
}
private void InitializePlc()
{
try
{
_plc = new ActUtlType64();
AddLog("PLC通信库初始化成功");
}
catch (Exception ex)
{
MessageBox.Show($"初始化PLC通信失败: {ex.Message}\n请确保已安装MX Component运行时库。");
AddLog($"PLC通信库初始化失败: {ex.Message}");
}
}
// 连接PLC
private void Connect_Click(object sender, RoutedEventArgs e)
{
// 验证站号输入
if (!int.TryParse(txtStationNumber.Text, out int stationNumber) || stationNumber < 1 || stationNumber > 255)
{
MessageBox.Show("请输入有效的站号 (1-255)");
return;
}
_stationNumber = stationNumber; // 保存站号用于重连
_connectionRetryCount = 0; // 重置重试计数
try
{
// 设置站号
_plc.ActLogicalStationNumber = stationNumber;
int result = _plc.Open();
if (result == 0) // 返回0表示成功
{
_isConnected = true;
tbConnectionStatus.Text = $"已连接 (站号: {stationNumber})";
tbHeartbeatStatus.Text = "连接正常";
tbHeartbeatStatus.Foreground = Brushes.Green;
btnConnect.IsEnabled = false;
btnDisconnect.IsEnabled = true;
txtStationNumber.IsEnabled = false;
StartMonitoring();
AddLog($"PLC连接成功 (站号: {stationNumber})");
}
else
{
MessageBox.Show($"连接失败,错误代码: {result}");
AddLog($"PLC连接失败,错误代码: {result}");
}
}
catch (Exception ex)
{
MessageBox.Show($"连接失败: {ex.Message}");
AddLog($"PLC连接异常: {ex.Message}");
}
}
// 启动监控定时器
private void StartMonitoring()
{
_readTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(200) // 200ms采集周期
};
_readTimer.Tick += async (s, e) => await ReadPlcDataAsync();
_readTimer.Start();
AddLog("开始监控PLC数据");
}
// 异步读取PLC数据
private async System.Threading.Tasks.Task ReadPlcDataAsync()
{
if (!_isConnected) return;
try
{
await System.Threading.Tasks.Task.Run(() =>
{
// 读取电机点位数据
ReadMotorData();
// 读取节拍时间数据
ReadRhythmData();
// 读取负载率数据
ReadLoadData();
});
}
catch (Exception ex)
{
AddLog($"读取数据异常: {ex.Message}");
}
}
// 读取电机点位数据
private void ReadMotorData()
{
for (int i = 0; i < _viewModel.MotorDataList.Count; i++)
{
short value = 0;
int result = _plc.ReadDeviceBlock2(_viewModel.MotorDataList[i].Address, 1, out value);
if (result == 0)
{
float actualValue = value;
Application.Current.Dispatcher.Invoke(() =>
{
var data = _viewModel.MotorDataList[i];
data.ActualValue = actualValue;
// 检查值是否发生变化
if (_lastMotorValues.Count <= i)
{
_lastMotorValues.Add(actualValue);
data.Status = (actualValue == data.StandardValue) ? "正常" : "异常";
if (data.Status == "异常")
{
AddLog($"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}");
}
}
else if (_lastMotorValues[i] != actualValue)
{
data.Status = (actualValue == data.StandardValue) ? "正常" : "异常";
if (data.Status == "异常")
{
AddLog($"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}");
}
_lastMotorValues[i] = actualValue;
}
});
}
else
{
AddLog($"读取点位 {_viewModel.MotorDataList[i].PointName} 失败,错误代码: {result}");
}
}
}
// 读取节拍时间数据并绘制柱状图
private void ReadRhythmData()
{
for (int i = 0; i < _viewModel.RhythmDataList.Count; i++)
{
short value = 0;
int result = _plc.ReadDeviceBlock2(_viewModel.RhythmDataList[i].Address, 1, out value);
if (result == 0)
{
float rhythmValue = value / 10.0f;
Application.Current.Dispatcher.Invoke(() =>
{
_viewModel.RhythmDataList[i].Value = rhythmValue;
DrawBarChart(canvasRhythmChart, _viewModel.RhythmDataList, true, "rhythm");
});
}
else
{
AddLog($"读取节拍数据 {_viewModel.RhythmDataList[i].Name} 失败,错误代码: {result}");
}
}
}
// 读取负载率数据并绘制柱状图
private void ReadLoadData()
{
for (int i = 0; i < _viewModel.LoadDataList.Count; i++)
{
short value = 0;
int result = _plc.ReadDeviceBlock2(_viewModel.LoadDataList[i].Address, 1, out value);
if (result == 0)
{
float loadValue = value;
Application.Current.Dispatcher.Invoke(() =>
{
_viewModel.LoadDataList[i].Value = value;
DrawBarChart(canvasLoadChart, _viewModel.LoadDataList, true, "load");
});
}
else
{
AddLog($"读取负载数据 {_viewModel.LoadDataList[i].Name} 失败,错误代码: {result}");
}
}
}
// 绘制柱状图
private void DrawBarChart(Canvas canvas, ObservableCollection<ChartData> dataList, bool showValue, string chartType)
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(() => DrawBarChart(canvas, dataList, showValue, chartType));
return;
}
canvas.Children.Clear();
if (dataList.Count == 0) return;
double canvasWidth = canvas.ActualWidth;
double canvasHeight = canvas.ActualHeight;
double barWidth = (canvasWidth - 40) / dataList.Count - 10;
double maxValue = GetMaxValue(dataList);
// 绘制Y轴
Line yAxis = new Line
{
X1 = 30,
Y1 = 20,
X2 = 30,
Y2 = canvasHeight - 30,
Stroke = Brushes.Black,
StrokeThickness = 2
};
canvas.Children.Add(yAxis);
// 绘制X轴
Line xAxis = new Line
{
X1 = 30,
Y1 = canvasHeight - 30,
X2 = canvasWidth - 10,
Y2 = canvasHeight - 30,
Stroke = Brushes.Black,
StrokeThickness = 2
};
canvas.Children.Add(xAxis);
// 绘制Y轴刻度
for (int i = 0; i <= 5; i++)
{
double yPos = canvasHeight - 30 - (i * (canvasHeight - 50) / 5);
double value = i * maxValue / 5;
Line tick = new Line
{
X1 = 25,
Y1 = yPos,
X2 = 30,
Y2 = yPos,
Stroke = Brushes.Black,
StrokeThickness = 1
};
canvas.Children.Add(tick);
TextBlock tickValue = new TextBlock
{
Text = value.ToString("F1"),
FontSize = 10,
Foreground = Brushes.Black
};
Canvas.SetLeft(tickValue, 5);
Canvas.SetTop(tickValue, yPos - 8);
canvas.Children.Add(tickValue);
}
for (int i = 0; i < dataList.Count; i++)
{
double barHeight = (dataList[i].Value / maxValue) * (canvasHeight - 60);
double x = 40 + i * (barWidth + 10);
double y = canvasHeight - 30 - barHeight;
// 根据图表类型和数值确定颜色
Brush barColor = GetBarColor(chartType, dataList[i].Value);
// 绘制柱形
System.Windows.Shapes.Rectangle rect = new System.Windows.Shapes.Rectangle
{
Width = barWidth,
Height = barHeight,
Fill = barColor,
Stroke = Brushes.Black,
StrokeThickness = 1
};
Canvas.SetLeft(rect, x);
Canvas.SetTop(rect, y);
canvas.Children.Add(rect);
// 绘制数值
if (showValue)
{
TextBlock text = new TextBlock
{
Text = dataList[i].Value.ToString("F1"),
FontSize = 10,
Foreground = Brushes.Black
};
Canvas.SetLeft(text, x + barWidth / 2 - 10);
Canvas.SetTop(text, y - 20);
canvas.Children.Add(text);
}
// 绘制名称
TextBlock nameText = new TextBlock
{
Text = $"{dataList[i].Name}\n{dataList[i].Address}",
FontSize = 10,
Foreground = Brushes.Black,
TextAlignment = TextAlignment.Center,
Width = barWidth + 10
};
Canvas.SetLeft(nameText, x - 5);
Canvas.SetTop(nameText, canvasHeight - 25);
canvas.Children.Add(nameText);
}
}
// 根据图表类型和数值获取对应的颜色
private Brush GetBarColor(string chartType, double value)
{
switch (chartType)
{
case "rhythm": // 设备节拍时间
if (value < 10) return Brushes.Green;
else if (value >= 10 && value < 20) return Brushes.YellowGreen;
else if (value >= 20 && value < 30) return Brushes.Orange;
else return Brushes.Red;
case "load": // 伺服电机负载率
return value < 80 ? Brushes.Green : Brushes.Red;
default: // 默认颜色
return Brushes.SteelBlue;
}
}
// 获取最大值用于比例计算
private float GetMaxValue(ObservableCollection<ChartData> dataList)
{
float max = 0;
foreach (var item in dataList)
{
if (item.Value > max) max = item.Value;
}
return max > 0 ? max : 1;
}
// 添加日志
private void AddLog(string message)
{
Application.Current.Dispatcher.Invoke(() =>
{
txtCommunicationLog.AppendText($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}\n");
txtCommunicationLog.ScrollToEnd();
});
}
// 电机点位配置保存
private void SaveMotorConfig_Click(object sender, RoutedEventArgs e)
{
try
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*";
saveFileDialog.DefaultExt = "json";
saveFileDialog.InitialDirectory = @"D:\"; // 默认指向D盘[1](@ref)
saveFileDialog.FileName = "motor_config.json";
if (saveFileDialog.ShowDialog() == true)
{
string jsonString = JsonSerializer.Serialize(_viewModel.MotorDataList);
File.WriteAllText(saveFileDialog.FileName, jsonString);
AddLog($"电机配置已保存到: {saveFileDialog.FileName}");
}
}
catch (Exception ex)
{
MessageBox.Show($"保存配置失败: {ex.Message}");
AddLog($"保存电机配置异常: {ex.Message}");
}
}
// 电机点位配置读取
private void LoadMotorConfig_Click(object sender, RoutedEventArgs e)
{
try
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*";
openFileDialog.DefaultExt = "json";
openFileDialog.InitialDirectory = @"D:\"; // 默认指向D盘
if (openFileDialog.ShowDialog() == true)
{
string jsonString = File.ReadAllText(openFileDialog.FileName);
var data = JsonSerializer.Deserialize<ObservableCollection<MotorPointData>>(jsonString);
if (data != null)
{
_viewModel.MotorDataList.Clear();
foreach (var item in data)
{
_viewModel.MotorDataList.Add(item);
}
AddLog($"电机配置已从 {openFileDialog.FileName} 读取");
}
}
}
catch (Exception ex)
{
MessageBox.Show($"读取配置失败: {ex.Message}");
AddLog($"读取电机配置异常: {ex.Message}");
}
}
// 节拍配置保存
private void SaveRhythmConfig_Click(object sender, RoutedEventArgs e)
{
try
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*";
saveFileDialog.DefaultExt = "json";
saveFileDialog.InitialDirectory = @"D:\";
saveFileDialog.FileName = "rhythm_config.json";
if (saveFileDialog.ShowDialog() == true)
{
string jsonString = JsonSerializer.Serialize(_viewModel.RhythmDataList);
File.WriteAllText(saveFileDialog.FileName, jsonString);
AddLog($"节拍配置已保存到: {saveFileDialog.FileName}");
}
}
catch (Exception ex)
{
MessageBox.Show($"保存配置失败: {ex.Message}");
AddLog($"保存节拍配置异常: {ex.Message}");
}
}
// 节拍配置读取
private void LoadRhythmConfig_Click(object sender, RoutedEventArgs e)
{
try
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*";
openFileDialog.DefaultExt = "json";
openFileDialog.InitialDirectory = @"D:\";
if (openFileDialog.ShowDialog() == true)
{
string jsonString = File.ReadAllText(openFileDialog.FileName);
var data = JsonSerializer.Deserialize<ObservableCollection<ChartData>>(jsonString);
if (data != null)
{
_viewModel.RhythmDataList.Clear();
foreach (var item in data)
{
_viewModel.RhythmDataList.Add(item);
}
AddLog($"节拍配置已从 {openFileDialog.FileName} 读取");
}
}
}
catch (Exception ex)
{
MessageBox.Show($"读取配置失败: {ex.Message}");
AddLog($"读取节拍配置异常: {ex.Message}");
}
}
// 负载配置保存
private void SaveLoadConfig_Click(object sender, RoutedEventArgs e)
{
try
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*";
saveFileDialog.DefaultExt = "json";
saveFileDialog.InitialDirectory = @"D:\";
saveFileDialog.FileName = "load_config.json";
if (saveFileDialog.ShowDialog() == true)
{
string jsonString = JsonSerializer.Serialize(_viewModel.LoadDataList);
File.WriteAllText(saveFileDialog.FileName, jsonString);
AddLog($"负载配置已保存到: {saveFileDialog.FileName}");
}
}
catch (Exception ex)
{
MessageBox.Show($"保存配置失败: {ex.Message}");
AddLog($"保存负载配置异常: {ex.Message}");
}
}
// 负载配置读取
private void LoadLoadConfig_Click(object sender, RoutedEventArgs e)
{
try
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*";
openFileDialog.DefaultExt = "json";
openFileDialog.InitialDirectory = @"D:\";
if (openFileDialog.ShowDialog() == true)
{
string jsonString = File.ReadAllText(openFileDialog.FileName);
var data = JsonSerializer.Deserialize<ObservableCollection<ChartData>>(jsonString);
if (data != null)
{
_viewModel.LoadDataList.Clear();
foreach (var item in data)
{
_viewModel.LoadDataList.Add(item);
}
AddLog($"负载配置已从 {openFileDialog.FileName} 读取");
}
}
}
catch (Exception ex)
{
MessageBox.Show($"读取配置失败: {ex.Message}");
AddLog($"读取负载配置异常: {ex.Message}");
}
}
// 断开连接
private void Disconnect_Click(object sender, RoutedEventArgs e)
{
try
{
_plc.Close();
_isConnected = false;
_readTimer.Stop();
tbConnectionStatus.Text = "未连接";
tbHeartbeatStatus.Text = "未连接";
tbHeartbeatStatus.Foreground = Brushes.Gray;
btnConnect.IsEnabled = true;
btnDisconnect.IsEnabled = false;
txtStationNumber.IsEnabled = true;
_connectionRetryCount = 0; // 重置重试计数
AddLog("PLC连接已断开");
}
catch (Exception ex)
{
MessageBox.Show($"断开连接失败: {ex.Message}");
AddLog($"断开连接异常: {ex.Message}");
}
}
// 添加监控点
private void AddMonitorPoint_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtPointName.Text) ||
string.IsNullOrWhiteSpace(txtPointAddress.Text) ||
string.IsNullOrWhiteSpace(txtStandardValue.Text))
{
MessageBox.Show("请填写完整的监控点信息");
return;
}
if (!float.TryParse(txtStandardValue.Text, out float standardValue))
{
MessageBox.Show("标准值必须是有效的数字");
return;
}
_viewModel.MotorDataList.Add(new MotorPointData
{
PointName = txtPointName.Text,
Address = txtPointAddress.Text,
StandardValue = standardValue,
ActualValue = 0,
Status = "未知"
});
txtPointName.Text = "";
txtPointAddress.Text = "";
txtStandardValue.Text = "";
}
// 添加节拍监控部位
private void AddSection_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtSectionName.Text) ||
string.IsNullOrWhiteSpace(txtSectionAddress.Text))
{
MessageBox.Show("请填写完整的部位信息");
return;
}
_viewModel.RhythmDataList.Add(new ChartData
{
Name = txtSectionName.Text,
Address = txtSectionAddress.Text,
Value = 0
});
txtSectionName.Text = "";
txtSectionAddress.Text = "";
}
// 添加负载监控电机
private void AddMotor_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtMotorName.Text) ||
string.IsNullOrWhiteSpace(txtMotorAddress.Text))
{
MessageBox.Show("请填写完整的电机信息");
return;
}
_viewModel.LoadDataList.Add(new ChartData
{
Name = txtMotorName.Text,
Address = txtMotorAddress.Text,
Value = 0
});
txtMotorName.Text = "";
txtMotorAddress.Text = "";
}
// 窗口关闭时清理资源
private void Window_Closed(object sender, EventArgs e)
{
if (_isConnected)
{
_plc.Close();
_readTimer.Stop();
}
// 停止所有定时器
_logClearTimer?.Stop();
_heartbeatTimer?.Stop();
}
}
}
把上面代码中的PLC连接设置部分隐藏在左侧区域,电机监控和通讯日志放在左侧,设备节拍时间和设备伺服电机负载率放在右边
最新发布