C# 结合 HslCommunication 库连接 PLC,并按照 Model、DAL、BLL、Common 的分层架构来设计一个可扩展的程序,这个架构设计思路非常专业,能让代码结构清晰、易于维护和扩展。
整体设计思路
我会按照Common(公共辅助)→ Model(数据模型)→ DAL(数据访问层)→ BLL(业务逻辑层) 的顺序来构建代码,每层职责单一:
- Common:封装 Hsl 连接配置、通用工具类、枚举等;
- Model:定义 PLC 数据模型、连接参数模型等;
- DAL:直接与 PLC 交互,封装读写数据的基础方法;
- BLL:调用 DAL 层方法,处理业务逻辑(如数据校验、格式转换)。
前置条件
- 安装 NuGet 包:
HslCommunication(核心库)、HslCommunication.Profinet(如果是西门子 / 三菱 / 欧姆龙等品牌 PLC,需对应安装,如HslCommunication.Profinet.Siemens); - 确保 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);
}
}
}
总结
- 分层架构核心:Common 封装通用工具,Model 定义数据结构,DAL 负责纯数据访问,BLL 处理业务逻辑,职责单一、低耦合;
- 可扩展性:通过枚举和工厂方法(
CreatePlcClient)新增 PLC 品牌,无需修改核心业务逻辑; - 健壮性:每层都做了参数校验、异常捕获,统一返回
PlcOperateResult模型,便于上层处理结果。
这个架构既满足了基础的 PLC 读写需求,又为后续扩展(如多品牌、高并发、日志、配置)预留了空间,非常适合工业场景下的 PLC 通讯开发。