C#/.NET工业自动化开发完全指南:构建企业级工业应用
引言:C#/.NET在工业自动化中的优势
C#/.NET平台凭借其强大的类型系统、丰富的类库、卓越的性能和微软的全面支持,在企业级工业自动化领域占据重要地位。从传统的Windows桌面应用到现代的云原生微服务,C#/.NET提供了完整的解决方案栈,特别适合需要高可靠性、高性能和长期维护的工业应用。
本文将深入探讨C#/.NET在工业自动化中的应用,重点介绍NModbus库的使用、WPF/WinForms界面开发、企业级架构设计,并结合实际案例展示如何构建专业级的工业自动化系统。
一、C#/.NET工业开发环境配置
1.1 开发环境要求
<!-- .NET SDK版本要求 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
1.2 项目结构和依赖配置
<!-- IndustrialAutomation.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<!-- Modbus通信 -->
<PackageReference Include="NModbus" Version="3.0.73" />
<PackageReference Include="NModbus.Serial" Version="3.0.73" />
<!-- 串口通信 -->
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<!-- 日志记录 -->
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<!-- 依赖注入 -->
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<!-- 配置管理 -->
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<!-- 数据验证 -->
<PackageReference Include="FluentValidation" Version="11.8.0" />
<!-- 单元测试 -->
<PackageReference Include="xunit" Version="2.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5" />
<PackageReference Include="Moq" Version="4.20.69" />
</ItemGroup>
</Project>
1.3 项目目录结构
IndustrialAutomation/
├── src/
│ ├── IndustrialAutomation.Core/ # 核心业务逻辑
│ │ ├── Models/ # 数据模型
│ │ ├── Services/ # 业务服务
│ │ ├── Interfaces/ # 接口定义
│ │ └── Exceptions/ # 自定义异常
│ │
│ ├── IndustrialAutomation.Modbus/ # Modbus通信模块
│ │ ├── Clients/ # Modbus客户端
│ │ ├── Servers/ # Modbus服务器
│ │ └── Converters/ # 数据转换器
│ │
│ ├── IndustrialAutomation.UI/ # 用户界面
│ │ ├── Views/ # WPF视图
│ │ ├── ViewModels/ # MVVM视图模型
│ │ ├── Converters/ # 值转换器
│ │ └── Behaviors/ # 行为
│ │
│ └── IndustrialAutomation.Host/ # 应用程序宿主
│
├── tests/
│ ├── IndustrialAutomation.Core.Tests/
│ ├── IndustrialAutomation.Modbus.Tests/
│ └── IndustrialAutomation.Integration.Tests/
│
├── config/
│ ├── appsettings.json # 应用配置
│ ├── appsettings.Development.json # 开发环境配置
│ └── devices.json # 设备配置
│
└── docs/ # 项目文档
二、NModbus库深度使用
2.1 基础Modbus TCP客户端
using NModbus;
using NModbus.Device;
using System.Net.Sockets;
namespace IndustrialAutomation.Modbus.Clients
{
/// <summary>
/// Modbus TCP客户端
/// </summary>
public class ModbusTcpClient : IModbusClient, IDisposable
{
private readonly ILogger<ModbusTcpClient> _logger;
private readonly TcpClient _tcpClient;
private IModbusMaster? _modbusMaster;
private bool _disposed;
/// <summary>
/// 设备连接状态
/// </summary>
public bool IsConnected => _tcpClient?.Connected ?? false;
/// <summary>
/// 设备IP地址
/// </summary>
public string IpAddress { get; }
/// <summary>
/// 设备端口
/// </summary>
public int Port { get; }
/// <summary>
/// 初始化Modbus TCP客户端
/// </summary>
public ModbusTcpClient(
string ipAddress,
int port = 502,
ILogger<ModbusTcpClient>? logger = null)
{
IpAddress = ipAddress ?? throw new ArgumentNullException(nameof(ipAddress));
Port = port;
_logger = logger ?? NullLogger<ModbusTcpClient>.Instance;
_tcpClient = new TcpClient();
}
/// <summary>
/// 连接到设备
/// </summary>
public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("正在连接到设备 {IpAddress}:{Port}", IpAddress, Port);
await _tcpClient.ConnectAsync(IpAddress, Port, cancellationToken);
var factory = new ModbusFactory();
_modbusMaster = factory.CreateMaster(_tcpClient);
// 设置超时时间
_modbusMaster.Transport.ReadTimeout = 3000;
_modbusMaster.Transport.WriteTimeout = 3000;
_logger.LogInformation("成功连接到设备 {IpAddress}:{Port}", IpAddress, Port);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "连接设备 {IpAddress}:{Port} 失败", IpAddress, Port);
return false;
}
}
/// <summary>
/// 读取保持寄存器
/// </summary>
public async Task<ushort[]> ReadHoldingRegistersAsync(
byte slaveAddress,
ushort startAddress,
ushort numberOfPoints,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"读取保持寄存器 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数量: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
var result = await Task.Run(() =>
_modbusMaster!.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints),
cancellationToken);
_logger.LogDebug(
"读取保持寄存器成功 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数据: {Data}",
slaveAddress, startAddress, result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,
"读取保持寄存器失败 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数量: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
throw new ModbusCommunicationException(
$"读取保持寄存器失败: {ex.Message}", ex);
}
}
/// <summary>
/// 写入单个保持寄存器
/// </summary>
public async Task WriteSingleRegisterAsync(
byte slaveAddress,
ushort registerAddress,
ushort value,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"写入单个寄存器 - 从站: {SlaveAddress}, 地址: {RegisterAddress}, 值: {Value}",
slaveAddress, registerAddress, value);
await Task.Run(() =>
_modbusMaster!.WriteSingleRegister(slaveAddress, registerAddress, value),
cancellationToken);
_logger.LogDebug(
"写入单个寄存器成功 - 从站: {SlaveAddress}, 地址: {RegisterAddress}",
slaveAddress, registerAddress);
}
catch (Exception ex)
{
_logger.LogError(ex,
"写入单个寄存器失败 - 从站: {SlaveAddress}, 地址: {RegisterAddress}, 值: {Value}",
slaveAddress, registerAddress, value);
throw new ModbusCommunicationException(
$"写入单个寄存器失败: {ex.Message}", ex);
}
}
/// <summary>
/// 写入多个保持寄存器
/// </summary>
public async Task WriteMultipleRegistersAsync(
byte slaveAddress,
ushort startAddress,
ushort[] data,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"写入多个寄存器 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数据长度: {Length}",
slaveAddress, startAddress, data.Length);
await Task.Run(() =>
_modbusMaster!.WriteMultipleRegisters(slaveAddress, startAddress, data),
cancellationToken);
_logger.LogDebug(
"写入多个寄存器成功 - 从站: {SlaveAddress}, 地址: {StartAddress}",
slaveAddress, startAddress);
}
catch (Exception ex)
{
_logger.LogError(ex,
"写入多个寄存器失败 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数据长度: {Length}",
slaveAddress, startAddress, data.Length);
throw new ModbusCommunicationException(
$"写入多个寄存器失败: {ex.Message}", ex);
}
}
/// <summary>
/// 读取线圈状态
/// </summary>
public async Task<bool[]> ReadCoilsAsync(
byte slaveAddress,
ushort startAddress,
ushort numberOfPoints,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"读取线圈 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数量: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
var result = await Task.Run(() =>
_modbusMaster!.ReadCoils(slaveAddress, startAddress, numberOfPoints),
cancellationToken);
_logger.LogDebug(
"读取线圈成功 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数据: {Data}",
slaveAddress, startAddress, result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,
"读取线圈失败 - 从站: {SlaveAddress}, 地址: {StartAddress}, 数量: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
throw new ModbusCommunicationException(
$"读取线圈失败: {ex.Message}", ex);
}
}
/// <summary>
/// 批量读取优化
/// </summary>
public async Task<Dictionary<ushort, ushort[]>> BatchReadRegistersAsync(
byte slaveAddress,
IEnumerable<RegisterReadRequest> requests,
CancellationToken cancellationToken = default)
{
ValidateConnection();
var results = new Dictionary<ushort, ushort[]>();
foreach (var request in requests)
{
try
{
var data = await ReadHoldingRegistersAsync(
slaveAddress,
request.StartAddress,
request.NumberOfPoints,
cancellationToken);
results[request.StartAddress] = data;
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"批量读取失败 - 地址: {StartAddress}, 数量: {NumberOfPoints}",
request.StartAddress, request.NumberOfPoints);
results[request.StartAddress] = Array.Empty<ushort>();
}
}
return results;
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
try
{
_modbusMaster?.Dispose();
_modbusMaster = null;
if (_tcpClient.Connected)
{
_tcpClient.Close();
}
_logger.LogInformation("已断开与设备 {IpAddress}:{Port} 的连接", IpAddress, Port);
}
catch (Exception ex)
{
_logger.LogError(ex, "断开连接时发生错误");
}
}
/// <summary>
/// 验证连接状态
/// </summary>
private void ValidateConnection()
{
if (!IsConnected || _modbusMaster == null)
{
throw new InvalidOperationException("设备未连接");
}
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
Disconnect();
_tcpClient.Dispose();
}
_disposed = true;
}
}
}
/// <summary>
/// 寄存器读取请求
/// </summary>
public record RegisterReadRequest(
ushort StartAddress,
ushort NumberOfPoints);
}
2.2 Modbus RTU串口客户端
“`csharp
using NModbus;
using NModbus.Serial;
using System.IO.Ports;
namespace IndustrialAutomation.Modbus.Clients
{
///
///
public class ModbusRtuClient : IModbusClient, IDisposable
{
private readonly ILogger
private readonly SerialPort _serialPort;
private IModbusMaster? _modbusMaster;
private bool _disposed;
/// <summary>
/// 串口连接状态
/// </summary>
public bool IsConnected => _serialPort?.IsOpen ?? false;
/// <summary>
/// 串口名称
/// </summary>
public string PortName { get; }
/// <summary>
/// 波特率
/// </summary>
public int BaudRate { get; }
/// <summary>
/// 初始化Modbus RTU客户端
/// </summary>
public ModbusRtuClient(
string portName,
int baudRate = 9600,
ILogger<ModbusRtuClient>? logger = null)
{
PortName = portName ?? throw new ArgumentNullException(nameof(portName));
BaudRate = baudRate;
_logger = logger ?? NullLogger<ModbusRtuClient>.Instance;
_serialPort = new SerialPort(portName)
{
BaudRate = baudRate,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
ReadTimeout = 1000,
WriteTimeout = 1000
};
}
/// <summary>
/// 连接到串口设备
/// </summary>
public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("正在打开串口 {PortName} (波特率: {BaudRate})", PortName, BaudRate);
await Task.Run(() =>
{
if (!_serialPort.IsOpen)
{
_serialPort.Open();
}
}, cancellationToken);
var factory = new ModbusFactory();
var adapter = new SerialPortAdapter(_serialPort);
_modbusMaster = factory.CreateRtuMaster(adapter);
// 配置Modbus参数
_modbusMaster.Transport.ReadTimeout = 1000;
_modbusMaster.Transport.WriteTimeout = 1000;
_modbusMaster.Transport.Retries = 3;
_modbusMaster.Transport.WaitToRetryMilliseconds = 100;
_logger.LogInformation("成功打开串口 {PortName}", PortName);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "打开串口 {PortName} 失败", PortName);
return false;
}
}
/// <summary>
/// 扫描串口设备
/// </summary>
public async Task<List<byte>> ScanDevicesAsync(
byte startAddress = 1,
byte endAddress = 247,
CancellationToken cancellationToken = default)
{
var foundDevices = new List<byte>();
_logger.LogInformation("开始扫描设备 (地址范围: {StartAddress}-{EndAddress})", startAddress, endAddress);
for (byte slaveAddress = startAddress; slaveAddress <= endAddress; slaveAddress++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
try
{
// 尝试读取设备标识
var result = await Task.Run(() =>
_modbusMaster!.ReadDeviceIdentification(slaveAddress, 0x01),
cancellationToken);
if (result != null)
{
foundDevices.Add(slaveAddress);
_logger.LogInformation("
