当前位置: 首页 > news >正文

西门子 S7 PLC 通信 WPF 应用分析笔记

西门子 S7 PLC 通信 WPF 应用分析笔记

1. 项目概述

  • 技术栈
    • WPF(Windows Presentation Foundation)用于界面展示。
    • MVVM(Model-View-ViewModel)设计模式,通过GalaSoft.MvvmLight实现。
    • S7.Net库用于与西门子 S7 系列 PLC 进行以太网通信。
  • 功能
    • 配置 PLC 连接参数(IP、机架号、槽号)。
    • 连接 / 断开 PLC。
    • 从指定地址读取数据。
    • 向指定地址写入数据。
    • 记录操作日志。

2. 整体架构(MVVM 模式)

View(视图)
  • 文件MainWindow.xaml
  • 作用
    • 定义界面布局和控件。
    • 通过数据绑定将控件与 ViewModel 的属性关联。
    • 通过命令绑定将按钮点击事件绑定到 ViewModel 的命令。
ViewModel(视图模型)
  • 文件MainViewModel.cs
  • 作用
    • 持有界面所需的数据(连接参数、读写地址、结果、日志等)。
    • 实现业务逻辑(连接、断开、读写 PLC)。
    • 通过INotifyPropertyChanged通知 View 更新界面。
    • 通过RelayCommand处理用户操作。
Model(模型)
  • 外部库S7.Net
  • 作用
    • 提供Plc类实现与 S7 PLC 的底层通信。
    • 封装了 TCP/IP 连接、数据读写等功能。

3. 界面(View)分析

主要区域
  1. 标题
    • 显示"西门子S7 PLC通信"
  2. 连接设置
    • IP 地址(绑定IpAddress)。
    • 机架号(绑定Rack)。
    • 槽号(绑定Slot)。
    • 连接状态(绑定ConnectionStatus)。
    • 连接按钮(绑定ConnectCommand,仅在未连接时可用)。
    • 断开按钮(绑定DisconnectCommand,仅在已连接时可用)。
  3. 读取操作
    • 读取地址(绑定ReadAddress)。
    • 读取按钮(绑定ReadCommand,仅在已连接时可用)。
    • 读取结果(绑定ReadValue,只读)。
  4. 写入操作
    • 写入地址(绑定WriteAddress)。
    • 写入数值(绑定WriteValue)。
    • 写入按钮(绑定WriteCommand,仅在已连接时可用)。
  5. 操作日志
    • 显示程序运行日志(绑定Log,只读)。
绑定方式
  • 数据绑定

    xml

    <TextBox Text="{Binding IpAddress}" />
  • 命令绑定

    xml

    <Button Command="{Binding ConnectCommand}" />
  • 按钮可用性绑定

    xml

    <Button IsEnabled="{Binding !IsConnected}" />

4. 视图模型(ViewModel)分析

4.1 数据属性
  • 连接参数

    csharp

    运行

    public string IpAddress { get; set; } public short Rack { get; set; } public short Slot { get; set; }
  • 状态属性

    csharp

    运行

    public string ConnectionStatus { get; set; } public bool IsConnected { get; set; }
  • 读写相关

    csharp

    运行

    public string ReadAddress { get; set; } public string ReadValue { get; set; } public string WriteAddress { get; set; } public string WriteValue { get; set; }
  • 日志

    csharp

    运行

    public string Log { get; set; }
4.2 命令
  • ConnectCommand:连接 PLC。
  • DisconnectCommand:断开 PLC。
  • ReadCommand:读取数据。
  • WriteCommand:写入数据。
4.3 核心方法
AddLog

csharp

运行

private void AddLog(string message) { Log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}\n{Log}"; }
  • 在日志前添加时间戳。
  • 新日志显示在最上方。
Connect

csharp

运行

private async Task Connect() { try { AddLog($"正在连接PLC: {IpAddress}, 机架: {Rack}, 槽号: {Slot}"); _plc = new Plc(CpuType.S71200, IpAddress, Rack, Slot); await Task.Run(() => _plc.Open()); IsConnected = _plc.IsConnected; ConnectionStatus = IsConnected ? "已连接" : "连接失败"; AddLog(ConnectionStatus); } catch (Exception ex) { ConnectionStatus = "连接错误: " + ex.Message; IsConnected = false; AddLog(ConnectionStatus); MessageBox.Show(ex.Message, "连接错误", MessageBoxButton.OK, MessageBoxImage.Error); } }
  • 创建Plc对象并调用Open()连接。
  • 异步执行防止 UI 阻塞。
  • 更新连接状态并记录日志。
  • 异常处理:提示错误信息。
Disconnect

csharp

运行

private async Task Disconnect() { try { if (_plc != null && _plc.IsConnected) { AddLog("正在断开PLC连接"); await Task.Run(() => _plc.Close()); IsConnected = false; ConnectionStatus = "已断开"; AddLog(ConnectionStatus); } } catch (Exception ex) { ConnectionStatus = "断开错误: " + ex.Message; AddLog(ConnectionStatus); MessageBox.Show(ex.Message, "断开错误", MessageBoxButton.OK, MessageBoxImage.Error); } }
  • 调用Close()断开连接。
  • 更新状态并记录日志。
ReadFromPLC

csharp

运行

private async Task ReadFromPLC() { try { if (_plc != null && _plc.IsConnected) { AddLog($"正在读取PLC地址: {ReadAddress}"); var value = await Task.Run(() => _plc.Read(ReadAddress)); ReadValue = value?.ToString() ?? "读取失败"; AddLog($"读取成功: {ReadValue}"); } else { AddLog("读取失败: 请先连接PLC"); MessageBox.Show("请先连接PLC", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } catch (Exception ex) { ReadValue = "读取错误: " + ex.Message; AddLog(ReadValue); MessageBox.Show(ex.Message, "读取错误", MessageBoxButton.OK, MessageBoxImage.Error); } }
  • 检查连接状态。
  • 调用_plc.Read()读取数据。
  • 更新ReadValue并记录日志。
WriteToPLC

csharp

运行

private async Task WriteToPLC() { try { if (_plc != null && _plc.IsConnected) { if (double.TryParse(WriteValue, out double numericValue)) { AddLog($"正在写入PLC地址: {WriteAddress}, 值: {numericValue}"); await Task.Run(() => _plc.Write(WriteAddress, numericValue)); AddLog("写入成功"); MessageBox.Show("写入成功", "成功", MessageBoxButton.OK, MessageBoxImage.Information); } else { AddLog("写入失败: 请输入有效的数值"); MessageBox.Show("请输入有效的数值", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } else { AddLog("写入失败: 请先连接PLC"); MessageBox.Show("请先连接PLC", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } catch (Exception ex) { AddLog($"写入错误: {ex.Message}"); MessageBox.Show(ex.Message, "写入错误", MessageBoxButton.OK, MessageBoxImage.Error); } }
  • 检查连接状态。
  • 验证输入是否为有效的double
  • 调用_plc.Write()写入数据。
  • 记录日志并提示结果。

5. 关键技术点

  1. MVVM 模式
    • 分离界面与业务逻辑,提高可维护性。
  2. 数据绑定
    • View 与 ViewModel 自动同步数据。
  3. 命令模式
    • RelayCommand实现按钮点击事件与 ViewModel 方法绑定。
  4. 异步编程
    • async/await防止 UI 阻塞。
  5. S7.Net 库
    • 简化与西门子 S7 PLC 的通信。
  6. 日志记录
    • 实时显示操作过程和错误信息。

6. 运行流程示例

  1. 用户输入IP 地址机架号槽号
  2. 点击连接按钮:
    • ConnectCommand调用Connect()方法。
    • 程序尝试连接 PLC。
    • 更新连接状态并记录日志。
  3. 输入读取地址,点击读取
    • ReadCommand调用ReadFromPLC()
    • 从 PLC 读取数据并显示结果。
  4. 输入写入地址写入数值,点击写入
    • WriteCommand调用WriteToPLC()
    • 将数据写入 PLC 并提示结果。
  5. 点击断开按钮:
    • DisconnectCommand调用Disconnect()
    • 断开与 PLC 的连接。

7. 改进建议

  1. 日志持久化
    • 将日志保存到文件,方便后续分析。
  2. 数据类型支持
    • 支持更多数据类型(如int,bool)。
  3. 批量读写
    • 支持一次性读写多个地址。
  4. 错误处理优化
    • MessageBox替换为界面内的错误提示,提升用户体验。
  5. UI 美化
    • 使用MahApps.Metro等库美化界面。

总结:该项目是一个基于 WPF 和 MVVM 模式的西门子 S7 PLC 通信工具,通过S7.Net库实现了 PLC 的连接、数据读取和写入功能,并提供了友好的日志记录功能。代码结构清晰,易于扩展和维护。

完整代码:

using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using S7.Net; using System; using System.Threading.Tasks; using System.Windows; namespace WpfApp1.ViewModel { public class MainViewModel : ViewModelBase { // PLC 核心对象 private Plc _plc; // 连接配置属性 private string _ipAddress = "192.168.0.1"; private short _rack = 0; private short _slot = 1; // 状态属性 private string _connectionStatus = "未连接"; private bool _isConnected; // 读写相关属性 private string _readValue; private string _writeValue; private string _readAddress = "DB1.DBD0"; private string _writeAddress = "DB1.DBD0"; // 日志属性 private string _log; // 公共属性(INotifyPropertyChanged 支持) public string Log { get => _log; set => Set(ref _log, value); } public string IpAddress { get => _ipAddress; set => Set(ref _ipAddress, value); } public short Rack { get => _rack; set => Set(ref _rack, value); } public short Slot { get => _slot; set => Set(ref _slot, value); } public string ConnectionStatus { get => _connectionStatus; set => Set(ref _connectionStatus, value); } public bool IsConnected { get => _isConnected; set => Set(ref _isConnected, value); } public string ReadValue { get => _readValue; set => Set(ref _readValue, value); } public string WriteValue { get => _writeValue; set => Set(ref _writeValue, value); } public string ReadAddress { get => _readAddress; set => Set(ref _readAddress, value); } public string WriteAddress { get => _writeAddress; set => Set(ref _writeAddress, value); } // 命令属性 public RelayCommand ConnectCommand { get; private set; } public RelayCommand DisconnectCommand { get; private set; } public RelayCommand ReadCommand { get; private set; } public RelayCommand WriteCommand { get; private set; } // 构造函数 public MainViewModel() { // 初始化命令 ConnectCommand = new RelayCommand(async () => await Connect()); DisconnectCommand = new RelayCommand(async () => await Disconnect()); ReadCommand = new RelayCommand(async () => await ReadFromPLC()); WriteCommand = new RelayCommand(async () => await WriteToPLC()); // 启动日志 AddLog("应用程序启动"); } /// <summary> /// 添加日志记录(时间戳 + 消息) /// </summary> /// <param name="message">日志内容</param> private void AddLog(string message) { Log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}\n{Log}"; } /// <summary> /// 连接PLC /// </summary> private async Task Connect() { try { AddLog($"正在连接PLC: {IpAddress}, 机架: {Rack}, 槽号: {Slot}"); // 初始化PLC对象并连接(异步执行) _plc = new Plc(CpuType.S71200, IpAddress, Rack, Slot); await Task.Run(() => _plc.Open()); // 更新连接状态 IsConnected = _plc.IsConnected; ConnectionStatus = IsConnected ? "已连接" : "连接失败"; AddLog(ConnectionStatus); } catch (Exception ex) { // 异常处理 ConnectionStatus = "连接错误: " + ex.Message; IsConnected = false; AddLog(ConnectionStatus); MessageBox.Show(ex.Message, "连接错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// <summary> /// 断开PLC连接 /// </summary> private async Task Disconnect() { try { if (_plc != null && _plc.IsConnected) { AddLog("正在断开PLC连接"); // 异步断开连接 await Task.Run(() => _plc.Close()); // 更新状态 IsConnected = false; ConnectionStatus = "已断开"; AddLog(ConnectionStatus); } } catch (Exception ex) { // 异常处理 ConnectionStatus = "断开错误: " + ex.Message; AddLog(ConnectionStatus); MessageBox.Show(ex.Message, "断开错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// <summary> /// 从PLC读取数据 /// </summary> private async Task ReadFromPLC() { try { if (_plc != null && _plc.IsConnected) { AddLog($"正在读取PLC地址: {ReadAddress}"); // 异步读取数据 var value = await Task.Run(() => _plc.Read(ReadAddress)); // 更新读取结果 ReadValue = value?.ToString() ?? "读取失败"; AddLog($"读取成功: {ReadValue}"); } else { // 未连接状态提示 AddLog("读取失败: 请先连接PLC"); MessageBox.Show("请先连接PLC", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } catch (Exception ex) { // 异常处理 ReadValue = "读取错误: " + ex.Message; AddLog(ReadValue); MessageBox.Show(ex.Message, "读取错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// <summary> /// 向PLC写入数据 /// </summary> private async Task WriteToPLC() { try { if (_plc != null && _plc.IsConnected) { // 验证输入是否为有效数值 if (double.TryParse(WriteValue, out double numericValue)) { AddLog($"正在写入PLC地址: {WriteAddress}, 值: {numericValue}"); // 异步写入数据 await Task.Run(() => _plc.Write(WriteAddress, numericValue)); // 写入成功提示 AddLog("写入成功"); MessageBox.Show("写入成功", "成功", MessageBoxButton.OK, MessageBoxImage.Information); } else { // 输入格式错误提示 AddLog("写入失败: 请输入有效的数值"); MessageBox.Show("请输入有效的数值", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } else { // 未连接状态提示 AddLog("写入失败: 请先连接PLC"); MessageBox.Show("请先连接PLC", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } catch (Exception ex) { // 异常处理 AddLog($"写入错误: {ex.Message}"); MessageBox.Show(ex.Message, "写入错误", MessageBoxButton.OK, MessageBoxImage.Error); } } } }
<Window x:Class="WpfApp1.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:WpfApp1" xmlns:vm="clr-namespace:WpfApp1.ViewModel" mc:Ignorable="d" Title="西门子S7 PLC通信" Height="546.939" Width="816.764"> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <Grid Margin="20"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!-- 标题 --> <TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="西门子S7 PLC通信" FontSize="24" FontWeight="Bold" Margin="0,0,0,20" HorizontalAlignment="Center" /> <!-- 连接设置 --> <GroupBox Grid.Row="1" Grid.ColumnSpan="2" Header="连接设置" Margin="0,0,0,20"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <!-- IP地址 --> <Label Grid.Row="0" Grid.Column="0" Content="IP地址:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding IpAddress}" Margin="0,10,10,10" Height="30" /> <!-- 机架号 --> <Label Grid.Row="0" Grid.Column="2" Content="机架:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBox Grid.Row="0" Grid.Column="3" Text="{Binding Rack}" Margin="0,10,10,10" Width="60" Height="30" /> <!-- 槽号 --> <Label Grid.Row="0" Grid.Column="4" Content="槽号:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBox Grid.Row="0" Grid.Column="5" Text="{Binding Slot}" Margin="0,10,10,10" Width="60" Height="30" /> <!-- 连接状态 --> <Label Grid.Row="1" Grid.Column="0" Content="连接状态:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding ConnectionStatus}" Margin="0,10,10,10" Height="30" VerticalAlignment="Center" FontWeight="Bold" /> <!-- 连接按钮 --> <Button Grid.Row="1" Grid.Column="2" Content="连接" Command="{Binding ConnectCommand}" Margin="0,10,10,10" Width="80" Height="30" IsEnabled="{Binding !IsConnected}" /> <!-- 断开按钮 --> <Button Grid.Row="1" Grid.Column="3" Content="断开" Command="{Binding DisconnectCommand}" Margin="0,10,10,10" Width="80" Height="30" IsEnabled="{Binding IsConnected}" /> </Grid> </GroupBox> <!-- 读取操作 --> <GroupBox Grid.Row="2" Grid.Column="0" Header="读取操作" Margin="0,0,10,20" Width="380"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!-- 读取地址 --> <Label Grid.Row="0" Grid.Column="0" Content="读取地址:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ReadAddress}" Margin="0,10,10,10" Height="30" /> <!-- 读取按钮 --> <Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="读取" Command="{Binding ReadCommand}" Margin="10,10,10,10" Height="40" IsEnabled="{Binding IsConnected}" /> <!-- 读取结果 --> <Label Grid.Row="2" Grid.Column="0" Content="读取结果:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding ReadValue}" Margin="0,10,10,10" Height="30" IsReadOnly="True" Background="#F0F0F0" /> </Grid> </GroupBox> <!-- 写入操作 --> <GroupBox Grid.Row="2" Grid.Column="1" Header="写入操作" Margin="0,0,0,20" Width="380"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!-- 写入地址 --> <Label Grid.Row="0" Grid.Column="0" Content="写入地址:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding WriteAddress}" Margin="0,10,10,10" Height="30" /> <!-- 写入数值 --> <Label Grid.Row="1" Grid.Column="0" Content="写入数值:" Margin="10,10,10,10" VerticalAlignment="Center" /> <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding WriteValue}" Margin="0,10,10,10" Height="30" /> <!-- 写入按钮 --> <Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Content="写入" Command="{Binding WriteCommand}" Margin="10,10,10,10" Height="40" IsEnabled="{Binding IsConnected}" /> </Grid> </GroupBox> <!-- 状态日志 --> <GroupBox Grid.Row="3" Grid.ColumnSpan="2" Header="操作日志" Margin="0,0,0,0"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBox Margin="10,10,10,10" IsReadOnly="True" Background="#F0F0F0" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Text="{Binding Log}" /> </Grid> </GroupBox> </Grid> </Window>
http://www.jsqmd.com/news/192727/

相关文章:

  • 塞浦路斯语婚礼习俗讲解:长辈数字人传授传统仪式细节
  • 格鲁吉亚语高加索登山指南:向导数字人介绍徒步路线
  • 为什么顶级公司都在测试PHP 8.7?(内部基准数据首次曝光)
  • 服务注册突然失效?PHP微服务容灾机制紧急应对指南
  • PHP WebSocket 实时消息推送全解析(从入门到高并发架构设计)
  • AI口型同步技术新突破!HeyGem数字人系统WebUI版全面解析
  • 巴斯克语工业安全培训:工厂主管数字人强调操作规范
  • 西班牙语旅游口语:度假村接待员教你常用表达
  • HeyGem数字人系统常见问题解答:处理慢、分辨率支持、并发任务解析
  • ‌测试左移避坑:开发写单元测试 ≠ 测试介入
  • HeyGem数字人系统适合哪些场景?教育、客服、短视频应用全解析
  • C#组件化扩展实践全记录,手把手教你构建可插拔业务模块
  • 波兰语法律咨询服务:律师数字人解答常见民事问题
  • 拉祜语咖啡种植技术:农户数字人交流种植心得
  • 【Python小技巧】-PyCharm配置专业开发规范模板完全指南
  • 【PHP 8.7性能优化终极指南】:基于最新基准测试的调优策略
  • PHP微服务上线必看:服务注册配置的8个致命误区
  • C#企业级扩展技术深度解析(从反射到MEF再到依赖注入)
  • Consul vs ZooKeeper vs Eureka:PHP微服务注册该用谁?
  • 【性能调优关键时刻】如何在macOS上高效调试C# HTTP拦截器?
  • 前端新手必看:用事件委托轻松搞定动态元素交互(附实战技巧)
  • 威尔士语学校教育改革:校长数字人宣布新政措施
  • 读共生:4_0时代的人机关系07工作者
  • PHP 8.7性能飞跃真相:5大基准测试结果震惊开发者社区
  • 基诺语特懋克节:族长数字人宣布新年开始
  • 2026南阳短视频曝光优化TOP5权威推荐:深度解析如何使短视频曝光率提高 - 工业推荐榜
  • Linux调度分析(3)调度内核API介绍
  • 【PHP 8.7性能基准测试全揭秘】:实测数据告诉你升级是否值得
  • 《commander-cpp》单头文件的、链式调用的、自动生成帮助文档的C++命令行参数解析库
  • C#扩展点设计艺术:打造高内聚低耦合的企业系统架构(稀缺架构思维曝光)