规范化C#编程:上位机与西门子S7系列PLC建立连接

原创 2024-07-12 09:52 厦门
文章的分类 编程和开发

如何通过C#上位机与西门子PLC通讯

概述

在工业自动化领域,与PLC(可编程逻辑控制器)的通信是核心的技术需求。本文将探讨如何利用 S7netplus 库,在 C# 上位机应用中实现与西门子系列 PLC 的通信。S7netplus 是一个开源的 .NET 库,专门用于通过以太网与西门子的 S7 系列 PLC 进行通信,它提供了简单而有效的接口。

S7netplus 库的功能介绍:详细了解这个库如何支持 PLC 通信

更多库的细节,请参考S7netplus的官方文档,这里不再重复

安装S7netplus库

通过NuGet包管理器安装S7netplus库,打开发环境,命令行执行以下命令:

Install-Package S7.Net

除了命令行执行,也可以:工具 → NuGet 包管理器 → 管理解决方案的NuGet程序包 → 搜索S7netplus下载

代码架构

为了确保我们的应用在与西门子PLC通讯时维持一致性和高效性,本文采用了单例模式(Singleton Pattern)来实现S7Protocol类。单例模式是一种常用的设计模式,用于限制一个类的实例化次数,确保系统中只存在一个该类的实例。这种模式非常适合管理与PLC的连接,因为通常我们只需要一个稳定的连接通道

设计模式的选择

设计模式是一组在软件设计中普遍有效的规则和最佳实践,用于解决常见的设计问题和改善代码的可维护性、可扩展性或可复用性。

  • 单例模式:通过确保全局只有一个对象实例,可以避免在多处创建连接时产生潜在的冲突或资源浪费
  • 工厂模式:如果需要与多种类型的PLC进行通讯或需要在运行时根据不同条件创建不同类型的连接,则工厂模式更合适
  • 策略模式:如果通讯策略需要根据不同的PLC或不同的运行时条件变化,策略模式可以定义一系列的算法,并使它们可以互换

    类的实现

    新建一个S7Protocol.cs文件,按照C#命名规范,类名也为S7Protocol

在S7Protocol类中,我们将实现单例模式的典型结构,包括私有的构造方法和一个静态的私有字段来持有类的实例。此外,我们提供一个静态的公有方法来获取这个实例,确保全局访问点的一致性和安全性

/// <summary>
/// 提供与西门子PLC进行通信的类
/// </summary>
public class S7Protocol {
    private static S7Protocol? _instance;            // 用于存储 S7Protocol 类的唯一实例
    private static readonly object _lock = new();   // 用作同步锁, 确保即使在多线程环境下,实例的创建也是线程安全的
    private Plc _plc;                                // PLC 实例,用于与西门子 PLC 设备进行通信

    /// <summary>
    /// 公开的静态方法提供唯一的全局访问点
    /// </summary>
    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;
        }
    }
    
    /// <summary>
    /// 私有构造函数以防外部直接实例化
    /// </summary>
    /// <param name="cpu">型号</param>
    /// <param name="ip">地址</param>
    /// <param name="rack">机架</param>
    /// <param name="slot">插槽</param>
    private S7Protocol(string cpu, string ip, short rack, short slot) {
        _plc = new Plc(ParseCpuType(cpu), ip, rack, slot);
    }

    /// <summary>
    /// 将字符串解析为 CpuType 枚举
    /// </summary>
    /// <param name="cpuType">CPU类型</param>
    /// <returns>CpuType枚举</returns>
    /// <exception cref="ArgumentException">传入的CPU参数不支持时抛出</exception>
    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的通讯在整个应用程序中的一致性和稳定性。接下来,我们需要实现一些基本的方法来进行通讯操作:

/// <summary>
/// 异步连接到PLC设备
/// </summary>
/// <returns>连接成功返回true,否则false</returns>
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;
    }
}

/// <summary>
/// 异步关闭PLC连接
/// </summary>
/// <returns>关闭成功返回true,否则false</returns>
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的操作,异步编程模型提供了多个优势:

  • 非阻塞操作:异步方法允许应用继续执行其他任务而不会因为等待操作完成而被阻塞。这对于用户界面(UI)密集型应用尤为重要,因为它可以保持界面的响应性
  • 资源利用率提高:通过异步编程,可以更有效地利用系统资源,尤其是在多核心处理器的环境下,异步任务可以被分配到不同的核心上执行
  • 改善应用性能和响应时间:异步操作可以帮助减少等待时间,如网络请求或繁重计算,从而提高应用的整体性能和响应速度

现在已经可以轻松地在应用程序的任何部分调用这些方法。在连接PLC之前,检查西门子PLC的设置:

  • 将PLC设置成允许来自远程对象的PUT/GET通信访问

连接到PLC

在应用程序适当的位置调用ConnectToPlcAsync方法:

public async Task InitConnectionAsync() {
    bool isConnected = await _s7Protocol.ConnectToPlcAsync();
    if (isConnected){
        // 连接成功执行的逻辑
    } else {
        // 连接失败执行的逻辑
    }
}

断开与PLC的连接

当应用程序关闭或停止与PLC的交互时,应当主动断开与PLC的连接:

public async Task CloseConnectionAsync() {
    bool isDisconnected = await _s7Protocol.DisconnectFromPlcAsync();
    if (isDisconnected){
        // 成功断开连接执行的逻辑
    } else{
        // 断开连接失败执行的逻辑
    }
}

注意事项

  • 配置管理:在实例化S7Protocol时使用的连接参数应从配置文件或环境变量、数据库中获取,避免硬编码
  • 异步操作注意:在使用异步方法时应注意调用环境和可能的线程安全问题,尤其是在多线程环境中共享资源如_plc实例时
  • 资源管理:确保在不再需要时,及时断开与PLC的连接,避免潜在的资源泄漏
  • 代码规范:遵循一致的代码规范对于保持项目的可读性和可维护性至关重要
  • 日志记录:在与PLC通信的过程中,适当的日志记录对于诊断问题、监控系统状态以及后续的分析都极为重要
  • 异常处理:正确的异常处理可以防止应用程序在遇到错误时崩溃,并提供错误恢复的机制

    结语

    本文主要内容是编程规范以及如何通过C#和S7netplus库与西门子PLC建立和断开连接,由于篇幅所限,并未涵盖数据的读取和写入操作。这些操作同样重要,但已提供的基础知识足以使读者能够自行实现这些功能。官方文档将是理解和实现这些高级功能的重要资源。

当然,为进一步支持开发者,以下提供了两个规范的接口,如何异步从PLC读取和写入结构体数据":

/// <summary>
/// 异步读取PLC中的结构体数据
/// </summary>
/// <remarks>
/// 这个方法适用于需要将PLC中的数据块映射到C#中的结构体
/// </remarks>
/// <typeparam name="T">结构体类型</typeparam>
/// <param name="db">数据块编号</param>
/// <param name="startByteAdr">起始字节地址,默认为0</param>
/// <returns>结构体数据</returns>
/// <exception cref="InvalidOperationException">如果PLC连接未初始化或已关闭,则抛出此异常</exception>
Task<T?> ReadStructAsync<T>(int db, int startByteAdr = 0) where T : struct;

/// <summary>
/// 异步向PLC写入结构体数据
/// </summary>
/// <remarks>
/// 这个方法适用于需要将C#中的结构体数据写入到PLC
/// </remarks>
/// <typeparam name="T">结构体类型</typeparam>
/// <param name="structValue">要写入的结构体实例</param>
/// <param name="db">数据块编号</param>
/// <param name="startByteAdr">起始字节地址,默认为0</param>
/// <returns>操作成功返回true,否则返回false</returns>
/// <exception cref="InvalidOperationException">如果PLC连接未初始化或已关闭,则抛出此异常</exception>
Task<bool> WriteStructAsync<T>(T structValue, int db, int startByteAdr = 0) where T : struct;

通过上述讨论和示例,开发者应能够更自信地管理和控制与PLC的通信



THE END


分享
赞赏
精选留言 写留言