在工业自动化领域,与PLC(可编程逻辑控制器)的通信是核心的技术需求。本文将探讨如何利用 S7netplus 库,在 C# 上位机应用中实现与西门子系列 PLC 的通信。S7netplus 是一个开源的 .NET 库,专门用于通过以太网与西门子的 S7 系列 PLC 进行通信,它提供了简单而有效的接口。
更多库的细节,请参考S7netplus的官方文档,这里不再重复
通过NuGet包管理器安装S7netplus库,打开发环境,命令行执行以下命令:
Install-Package S7.Net
除了命令行执行,也可以:工具 → NuGet 包管理器 → 管理解决方案的NuGet程序包 → 搜索S7netplus
下载
为了确保我们的应用在与西门子PLC通讯时维持一致性和高效性,本文采用了单例模式(Singleton Pattern)来实现S7Protocol类。单例模式是一种常用的设计模式,用于限制一个类的实例化次数,确保系统中只存在一个该类的实例。这种模式非常适合管理与PLC的连接,因为通常我们只需要一个稳定的连接通道
设计模式是一组在软件设计中普遍有效的规则和最佳实践,用于解决常见的设计问题和改善代码的可维护性、可扩展性或可复用性。
新建一个S7Protocol.cs
文件,按照C#命名规范,类名也为S7Protocol
在S7Protocol类中,我们将实现单例模式的典型结构,包括私有的构造方法和一个静态的私有字段来持有类的实例。此外,我们提供一个静态的公有方法来获取这个实例,确保全局访问点的一致性和安全性
///
/// 提供与西门子PLC进行通信的类
///
public class S7Protocol {
private static S7Protocol? _instance; // 用于存储 S7Protocol 类的唯一实例
private static readonly object _lock = new(); // 用作同步锁, 确保即使在多线程环境下,实例的创建也是线程安全的
private Plc _plc; // PLC 实例,用于与西门子 PLC 设备进行通信
///
/// 公开的静态方法提供唯一的全局访问点
///
public static S7Protocol Instance {
get {
if (_instance == null) {
lock (_lock) {
if (_instance == null) {
// 注意:这里避免使用硬编码遵循编码规范
_instance = new S7Protocol("S7-1500", "192.168.10.1", 0, 1);
}
}
}
return _instance;
}
}
///
/// 私有构造函数以防外部直接实例化
///
/// 型号
/// 地址
/// 机架
/// 插槽
private S7Protocol(string cpu, string ip, short rack, short slot) {
_plc = new Plc(ParseCpuType(cpu), ip, rack, slot);
}
///
/// 将字符串解析为 CpuType 枚举
///
/// CPU类型
/// CpuType枚举
/// 传入的CPU参数不支持时抛出
private static CpuType ParseCpuType(string cpuType) {
return cpuType switch {
"S7-200" => CpuType.S7200,
"S7-300" => CpuType.S7300,
"S7-400" => CpuType.S7400,
"S7-1200" => CpuType.S71200,
"S7-1500" => CpuType.S71500,
_ => throw new ArgumentException($"不支持的CPU类型: {cpuType}"),
};
}
}
在S7Protocol类的框架基本完成后,我们通过声明一个静态只读实例来使用它:
private static readonly S7Protocol _s7Protocol = S7Protocol.Instance
这种方式确保我们在应用程序中使用的是一个单例对象,从而维护与PLC的通讯在整个应用程序中的一致性和稳定性。接下来,我们需要实现一些基本的方法来进行通讯操作:
///
/// 异步连接到PLC设备
///
/// 连接成功返回true,否则false
public async Task<bool> ConnectToPlcAsync() {
try {
if (_plc != null) {
await _plc.OpenAsync();
if (_plc.IsConnected) {
return _plc.IsConnected;
} else {
return _plc.IsConnected;
}
}
return false;
} catch (Exception ex) {
communicationLogger.Error($"连接PLC时发生错误:{ex}");
return false;
}
}
///
/// 异步关闭PLC连接
///
/// 关闭成功返回true,否则false
public async Task<bool> DisconnectFromPlcAsync() {
try {
if (_plc != null && _plc.IsConnected) {
await Task.Run(_plc.Close);
return true;
}
return false;
} catch (Exception ex) {
communicationLogger.Error($"关闭PLC连接时发生错误:{ex}");
return false;
}
}
通过异步实现连接、关闭PLC的操作,异步编程模型提供了多个优势:
现在已经可以轻松地在应用程序的任何部分调用这些方法。在连接PLC之前,检查西门子PLC的设置:
在应用程序适当的位置调用ConnectToPlcAsync方法:
public async Task InitConnectionAsync() {
bool isConnected = await _s7Protocol.ConnectToPlcAsync();
if (isConnected){
// 连接成功执行的逻辑
} else {
// 连接失败执行的逻辑
}
}
当应用程序关闭或停止与PLC的交互时,应当主动断开与PLC的连接:
public async Task CloseConnectionAsync() {
bool isDisconnected = await _s7Protocol.DisconnectFromPlcAsync();
if (isDisconnected){
// 成功断开连接执行的逻辑
} else{
// 断开连接失败执行的逻辑
}
}
S7Protocol
时使用的连接参数应从配置文件或环境变量、数据库中获取,避免硬编码本文主要内容是编程规范以及如何通过C#和S7netplus
库与西门子PLC建立和断开连接,由于篇幅所限,并未涵盖数据的读取和写入操作。这些操作同样重要,但已提供的基础知识足以使读者能够自行实现这些功能。官方文档将是理解和实现这些高级功能的重要资源。
当然,为进一步支持开发者,以下提供了两个规范的接口,如何异步从PLC读取和写入结构体数据":
///
/// 异步读取PLC中的结构体数据
///
///
/// 这个方法适用于需要将PLC中的数据块映射到C#中的结构体
///
/// 结构体类型
/// 数据块编号
/// 起始字节地址,默认为0
/// 结构体数据
/// 如果PLC连接未初始化或已关闭,则抛出此异常
Task<T?> ReadStructAsync<T>(int db, int startByteAdr = 0) where T : struct;
///
/// 异步向PLC写入结构体数据
///
///
/// 这个方法适用于需要将C#中的结构体数据写入到PLC
///
/// 结构体类型
/// 要写入的结构体实例
/// 数据块编号
/// 起始字节地址,默认为0
/// 操作成功返回true,否则返回false
/// 如果PLC连接未初始化或已关闭,则抛出此异常
Task<bool> WriteStructAsync<T>(T structValue, int db, int startByteAdr = 0) where T : struct;
通过上述讨论和示例,开发者应能够更自信地管理和控制与PLC的通信