C#连接plc之 Model、DAL、BLL、Common三层架构可扩展

C# 结合 HslCommunication 库连接 PLC,并按照 Model、DAL、BLL、Common 的分层架构来设计一个可扩展的程序,这个架构设计思路非常专业,能让代码结构清晰、易于维护和扩展。

整体设计思路

我会按照Common(公共辅助)→ Model(数据模型)→ DAL(数据访问层)→ BLL(业务逻辑层) 的顺序来构建代码,每层职责单一:

  • Common:封装 Hsl 连接配置、通用工具类、枚举等;
  • Model:定义 PLC 数据模型、连接参数模型等;
  • DAL:直接与 PLC 交互,封装读写数据的基础方法;
  • BLL:调用 DAL 层方法,处理业务逻辑(如数据校验、格式转换)。

前置条件

  1. 安装 NuGet 包:HslCommunication(核心库)、HslCommunication.Profinet(如果是西门子 / 三菱 / 欧姆龙等品牌 PLC,需对应安装,如 HslCommunication.Profinet.Siemens);
  2. 确保 PLC 与电脑网络互通,且 PLC 侧已配置好通讯参数(IP、端口、站号等)。

1. Common 层(公共辅助)

封装 PLC 连接配置、枚举、通用工具,降低耦合。

1.1 枚举:PLC 类型

cs 复制代码
namespace PLCCommunication.Common
{
    /// <summary>
    /// PLC 品牌枚举(可扩展)
    /// </summary>
    public enum PlcType
    {
        SiemensS7,    // 西门子S7系列
        Mitsubishi,   // 三菱
        Omron,        // 欧姆龙
        ModbusTcp     // ModbusTCP通用
    }
}

1.2 PLC 连接配置类

cs 复制代码
using HslCommunication;
using HslCommunication.Profinet.Siemens;
using HslCommunication.Profinet.Mitsubishi;
using HslCommunication.Profinet.Omron;
using HslCommunication.ModBus;

namespace PLCCommunication.Common
{
    /// <summary>
    /// PLC 连接配置及连接对象创建工具
    /// </summary>
    public static class PlcConnectionHelper
    {
        /// <summary>
        /// 创建 PLC 连接对象(可扩展其他品牌)
        /// </summary>
        /// <param name="plcType">PLC类型</param>
        /// <param name="ipAddress">IP地址</param>
        /// <param name="port">端口(默认:西门子102,Modbus502,三菱5000,欧姆龙9600)</param>
        /// <param name="station">站号(西门子默认1,欧姆龙默认0)</param>
        /// <returns>PLC 连接对象</returns>
        public static IReadWriteNet CreatePlcClient(PlcType plcType, string ipAddress, int port = 0, byte station = 1)
        {
            IReadWriteNet plcClient = null;
            switch (plcType)
            {
                case PlcType.SiemensS7:
                    plcClient = new SiemensS7Net(SiemensPLCS.S1200, ipAddress, station);
                    if (port > 0) (plcClient as SiemensS7Net).Port = port;
                    break;
                case PlcType.Mitsubishi:
                    plcClient = new MitsubishiMcNet(ipAddress, port > 0 ? port : 5000);
                    break;
                case PlcType.Omron:
                    plcClient = new OmronFinsTcp(ipAddress, port > 0 ? port : 9600, station);
                    break;
                case PlcType.ModbusTcp:
                    plcClient = new ModbusTcpNet(ipAddress, port > 0 ? port : 502);
                    break;
                default:
                    throw new ArgumentException("不支持的PLC类型");
            }

            // 设置超时时间(可配置)
            plcClient.ConnectTimeOut = 5000;
            plcClient.ReceiveTimeOut = 5000;
            return plcClient;
        }

        /// <summary>
        /// 测试 PLC 连接
        /// </summary>
        /// <param name="plcClient">PLC连接对象</param>
        /// <returns>是否连接成功</returns>
        public static OperateResult TestPlcConnection(IReadWriteNet plcClient)
        {
            if (plcClient == null) return OperateResult.CreateFailedResult("PLC连接对象为空");
            return plcClient.ConnectServer();
        }
    }
}

2. Model 层(数据模型)

定义 PLC 读写参数、返回结果等模型,统一数据格式。

2.1 PLC 读写参数模型

cs 复制代码
using PLCCommunication.Common;

namespace PLCCommunication.Model
{
    /// <summary>
    /// PLC 连接参数模型
    /// </summary>
    public class PlcConnectionParam
    {
        /// <summary>
        /// PLC类型
        /// </summary>
        public PlcType PlcType { get; set; }

        /// <summary>
        /// IP地址
        /// </summary>
        public string IpAddress { get; set; }

        /// <summary>
        /// 端口号
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// 站号
        /// </summary>
        public byte Station { get; set; } = 1;
    }

    /// <summary>
    /// PLC 读写数据参数模型
    /// </summary>
    public class PlcDataParam
    {
        /// <summary>
        /// PLC连接参数
        /// </summary>
        public PlcConnectionParam ConnectionParam { get; set; }

        /// <summary>
        /// 数据地址(如:DB1.DBW0、M0.0、D0)
        /// </summary>
        public string Address { get; set; }

        /// <summary>
        /// 读取长度(仅数组/批量读取时用)
        /// </summary>
        public int Length { get; set; } = 1;
    }

    /// <summary>
    /// PLC 操作结果模型(通用返回)
    /// </summary>
    /// <typeparam name="T">返回数据类型</typeparam>
    public class PlcOperateResult<T>
    {
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool IsSuccess { get; set; }

        /// <summary>
        /// 错误信息
        /// </summary>
        public string ErrorMsg { get; set; }

        /// <summary>
        /// 返回数据
        /// </summary>
        public T Data { get; set; }

        /// <summary>
        /// 成功结果
        /// </summary>
        public static PlcOperateResult<T> Success(T data)
        {
            return new PlcOperateResult<T> { IsSuccess = true, Data = data };
        }

        /// <summary>
        /// 失败结果
        /// </summary>
        public static PlcOperateResult<T> Fail(string errorMsg)
        {
            return new PlcOperateResult<T> { IsSuccess = false, ErrorMsg = errorMsg };
        }
    }
}

3. DAL 层(数据访问层)

直接封装 PLC 读写的底层方法,仅负责与 PLC 交互,不处理业务逻辑。

cs 复制代码
using HslCommunication;
using PLCCommunication.Common;
using PLCCommunication.Model;

namespace PLCCommunication.DAL
{
    /// <summary>
    /// PLC 数据访问层
    /// </summary>
    public class PlcDal
    {
        /// <summary>
        /// 读取 PLC 单个数值(int/float/bool等)
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="param">读写参数</param>
        /// <returns>读取结果</returns>
        public PlcOperateResult<T> ReadPlcData<T>(PlcDataParam param)
        {
            try
            {
                // 创建PLC连接对象
                var plcClient = PlcConnectionHelper.CreatePlcClient(
                    param.ConnectionParam.PlcType,
                    param.ConnectionParam.IpAddress,
                    param.ConnectionParam.Port,
                    param.ConnectionParam.Station);

                // 测试连接
                var connectResult = PlcConnectionHelper.TestPlcConnection(plcClient);
                if (!connectResult.IsSuccess)
                {
                    return PlcOperateResult<T>.Fail($"PLC连接失败:{connectResult.Message}");
                }

                // 读取数据(Hsl 通用读取方法)
                OperateResult<T> readResult = plcClient.Read<T>(param.Address);
                if (!readResult.IsSuccess)
                {
                    return PlcOperateResult<T>.Fail($"读取失败:{readResult.Message}");
                }

                // 关闭连接
                plcClient.ConnectClose();
                return PlcOperateResult<T>.Success(readResult.Content);
            }
            catch (Exception ex)
            {
                return PlcOperateResult<T>.Fail($"读取异常:{ex.Message}");
            }
        }

        /// <summary>
        /// 写入 PLC 单个数值
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="param">读写参数</param>
        /// <param name="value">要写入的值</param>
        /// <returns>写入结果</returns>
        public PlcOperateResult<bool> WritePlcData<T>(PlcDataParam param, T value)
        {
            try
            {
                var plcClient = PlcConnectionHelper.CreatePlcClient(
                    param.ConnectionParam.PlcType,
                    param.ConnectionParam.IpAddress,
                    param.ConnectionParam.Port,
                    param.ConnectionParam.Station);

                var connectResult = PlcConnectionHelper.TestPlcConnection(plcClient);
                if (!connectResult.IsSuccess)
                {
                    return PlcOperateResult<bool>.Fail($"PLC连接失败:{connectResult.Message}");
                }

                // 写入数据
                OperateResult writeResult = plcClient.Write(param.Address, value);
                plcClient.ConnectClose();

                if (!writeResult.IsSuccess)
                {
                    return PlcOperateResult<bool>.Fail($"写入失败:{writeResult.Message}");
                }

                return PlcOperateResult<bool>.Success(true);
            }
            catch (Exception ex)
            {
                return PlcOperateResult<bool>.Fail($"写入异常:{ex.Message}");
            }
        }

        /// <summary>
        /// 批量读取 PLC 数据(返回数组)
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="param">读写参数(Length指定读取长度)</param>
        /// <returns>读取结果</returns>
        public PlcOperateResult<T[]> BatchReadPlcData<T>(PlcDataParam param)
        {
            try
            {
                var plcClient = PlcConnectionHelper.CreatePlcClient(
                    param.ConnectionParam.PlcType,
                    param.ConnectionParam.IpAddress,
                    param.ConnectionParam.Port,
                    param.ConnectionParam.Station);

                var connectResult = PlcConnectionHelper.TestPlcConnection(plcClient);
                if (!connectResult.IsSuccess)
                {
                    return PlcOperateResult<T[]>.Fail($"PLC连接失败:{connectResult.Message}");
                }

                // 批量读取
                OperateResult<T[]> readResult = plcClient.Read<T>(param.Address, param.Length);
                plcClient.ConnectClose();

                if (!readResult.IsSuccess)
                {
                    return PlcOperateResult<T[]>.Fail($"批量读取失败:{readResult.Message}");
                }

                return PlcOperateResult<T[]>.Success(readResult.Content);
            }
            catch (Exception ex)
            {
                return PlcOperateResult<T[]>.Fail($"批量读取异常:{ex.Message}");
            }
        }
    }
}

4. BLL 层(业务逻辑层)

调用 DAL 层方法,处理业务逻辑(如数据校验、单位转换、业务规则)。

cs 复制代码
using PLCCommunication.DAL;
using PLCCommunication.Model;

namespace PLCCommunication.BLL
{
    /// <summary>
    /// PLC 业务逻辑层
    /// </summary>
    public class PlcBll
    {
        private readonly PlcDal _plcDal = new PlcDal();

        /// <summary>
        /// 读取PLC整数(业务层可加校验:如数值范围)
        /// </summary>
        /// <param name="param">读写参数</param>
        /// <param name="minValue">最小值(可选)</param>
        /// <param name="maxValue">最大值(可选)</param>
        /// <returns>读取结果</returns>
        public PlcOperateResult<int> ReadIntData(PlcDataParam param, int? minValue = null, int? maxValue = null)
        {
            // 1. 校验参数
            if (string.IsNullOrEmpty(param.Address))
            {
                return PlcOperateResult<int>.Fail("PLC地址不能为空");
            }

            // 2. 调用DAL读取数据
            var readResult = _plcDal.ReadPlcData<int>(param);
            if (!readResult.IsSuccess)
            {
                return PlcOperateResult<int>.Fail(readResult.ErrorMsg);
            }

            // 3. 业务校验:数值范围
            if (minValue.HasValue && readResult.Data < minValue.Value)
            {
                return PlcOperateResult<int>.Fail($"读取的值{readResult.Data}小于最小值{minValue.Value}");
            }
            if (maxValue.HasValue && readResult.Data > maxValue.Value)
            {
                return PlcOperateResult<int>.Fail($"读取的值{readResult.Data}大于最大值{maxValue.Value}");
            }

            return PlcOperateResult<int>.Success(readResult.Data);
        }

        /// <summary>
        /// 写入PLC布尔值(如控制继电器)
        /// </summary>
        /// <param name="param">读写参数</param>
        /// <param name="value">布尔值</param>
        /// <returns>写入结果</returns>
        public PlcOperateResult<bool> WriteBoolData(PlcDataParam param, bool value)
        {
            // 1. 校验参数
            if (string.IsNullOrEmpty(param.Address))
            {
                return PlcOperateResult<bool>.Fail("PLC地址不能为空");
            }

            // 2. 调用DAL写入数据
            var writeResult = _plcDal.WritePlcData<bool>(param, value);
            if (!writeResult.IsSuccess)
            {
                return PlcOperateResult<bool>.Fail(writeResult.ErrorMsg);
            }

            // 3. 业务日志(示例:可扩展日志记录)
            Console.WriteLine($"[{DateTime.Now}] 向PLC地址{param.Address}写入布尔值:{value}");

            return PlcOperateResult<bool>.Success(true);
        }

        /// <summary>
        /// 批量读取PLC浮点数组(如温度采集)
        /// </summary>
        /// <param name="param">读写参数(Length指定读取个数)</param>
        /// <returns>读取结果</returns>
        public PlcOperateResult<float[]> BatchReadFloatData(PlcDataParam param)
        {
            // 1. 校验长度
            if (param.Length <= 0)
            {
                return PlcOperateResult<float[]>.Fail("批量读取长度必须大于0");
            }

            // 2. 调用DAL批量读取
            var readResult = _plcDal.BatchReadPlcData<float>(param);
            if (!readResult.IsSuccess)
            {
                return PlcOperateResult<float[]>.Fail(readResult.ErrorMsg);
            }

            return PlcOperateResult<float[]>.Success(readResult.Data);
        }
    }
}

总结

  1. 分层架构核心:Common 封装通用工具,Model 定义数据结构,DAL 负责纯数据访问,BLL 处理业务逻辑,职责单一、低耦合;
  2. 可扩展性:通过枚举和工厂方法(CreatePlcClient)新增 PLC 品牌,无需修改核心业务逻辑;
  3. 健壮性:每层都做了参数校验、异常捕获,统一返回 PlcOperateResult 模型,便于上层处理结果。

这个架构既满足了基础的 PLC 读写需求,又为后续扩展(如多品牌、高并发、日志、配置)预留了空间,非常适合工业场景下的 PLC 通讯开发。

相关推荐
bugcome_com2 小时前
深入浅出 C# 扩展方法:为现有类型 “无痛” 扩展功能
c#
代码方舟2 小时前
不仅是评分:利用 Python 解析天远借贷行为验证API 的 T0-T11 借贷时间轴数据
大数据·开发语言·python
夜泉_ly2 小时前
期末速通 -Java程序设计基础 -理论
java·开发语言
m0_611349312 小时前
什么是副作用(Side Effects)
开发语言·前端·javascript
妮妮分享2 小时前
维智地图如何集成
开发语言·ios·swift
weixin_439706253 小时前
如何使用JAVA进行MCP服务创建以及通过大模型进行调用
java·开发语言
执笔论英雄3 小时前
[RL]协程asyncio.CancelledError
开发语言·python·microsoft
A24207349303 小时前
深入理解JS DOM:从基础操作到性能优化的全面指南
开发语言·javascript·ecmascript
a_zzzzzzzz3 小时前
Python 解释器 + Shell 脚本实现桌面打开软件
开发语言·python