- 1. 引言:C#/.NET在Industrielle Automatisierung中的优势
- 2. 一、C#/.NET工业开发环境配置
- 3. 1.1 开发环境要求
- 4. 1.2 项目结构和依赖配置
- 5. 1.3 项目目录结构
- 6. 二、NModbus库深度使用
- 7. 2.1 基础Modbus TCP客户端
- 8. 2.2 Modbus RTUDie Serie客户端
C#/.NETIndustrielle Automatisierung开发完全指南:构建Unternehmen级工业应用
引言:C#/.NET在Industrielle Automatisierung中的优势
C#/.NET平台凭借其强大的TypusDas System、丰富的类库、卓越的性能和微软的全面支持,在Unternehmen级Industrielle Automatisierung领域占据重要地位。从传统的Windows桌面应用zu现代的云原生微服务,C#/.NET提供了vollständig的解决方案栈,特别适合需要高可靠性、高性能和长期维护的工业应用。
本文将深入探讨C#/.NET在Industrielle Automatisierung中的应用,重点介绍NModbus库的使用、WPF/WinForms界面开发、Unternehmen级架构设计,并结合实际案例展示如何构建专业级的Industrielle AutomatisierungDas System。
一、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>
<!-- ModbusKommunikation -->
<PackageReference Include="NModbus" Version="3.0.73" />
<PackageReference Include="NModbus.Serial" Version="3.0.73" />
<!-- Serielle Kommunikation -->
<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" />
<!-- Daten验证 -->
<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/ # Daten模型
│ │ ├── Services/ # 业务服务
│ │ ├── Interfaces/ # Schnittstelle定义
│ │ └── Exceptions/ # Custom definiert异常
│ │
│ ├── IndustrialAutomation.Modbus/ # ModbusKommunikation模块
│ │ ├── Clients/ # Modbus客户端
│ │ ├── Servers/ # ModbusDer Server
│ │ └── Converters/ # DatenKonverter.
│ │
│ ├── IndustrialAutomation.UI/ # 用户界面
│ │ ├── Views/ # WPF视图
│ │ ├── ViewModels/ # MVVM视图模型
│ │ ├── Converters/ # 值Konverter.
│ │ └── Behaviors/ # 行为
│ │
│ └── IndustrialAutomation.Host/ # 应用程序宿主
│
├── tests/
│ ├── IndustrialAutomation.Core.Tests/
│ ├── IndustrialAutomation.Modbus.Tests/
│ └── IndustrialAutomation.Integration.Tests/
│
├── config/
│ ├── appsettings.json # Anwendung Konfiguration
│ ├── appsettings.Development.json # 开发环境配置
│ └── devices.json # Ausrüstung配置
│
└── docs/ # 项目Dokumentation
二、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>
/// AusrüstungVerbindungStatus
/// </summary>
public bool IsConnected => _tcpClient?.Connected ?? false;
/// <summary>
/// AusrüstungIPAdresse
/// </summary>
public string IpAddress { get; }
/// <summary>
/// Ausrüstung端口
/// </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>
/// VerbindungzuAusrüstung
/// </summary>
public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("正在VerbindungzuAusrüstung {IpAddress}:{Port}", IpAddress, Port);
await _tcpClient.ConnectAsync(IpAddress, Port, cancellationToken);
var factory = new ModbusFactory();
_modbusMaster = factory.CreateMaster(_tcpClient);
// 设置超时Zeit
_modbusMaster.Transport.ReadTimeout = 3000;
_modbusMaster.Transport.WriteTimeout = 3000;
_logger.LogInformation("成功VerbindungzuAusrüstung {IpAddress}:{Port}", IpAddress, Port);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "VerbindungAusrüstung {IpAddress}:{Port} 失败", IpAddress, Port);
return false;
}
}
/// <summary>
/// Lesen und halten Register
/// </summary>
public async Task<ushort[]> ReadHoldingRegistersAsync(
byte slaveAddress,
ushort startAddress,
ushort numberOfPoints,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"Lesen und halten Register - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, Anzahl: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
var result = await Task.Run(() =>
_modbusMaster!.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints),
cancellationToken);
_logger.LogDebug(
"Lesen und halten Register成功 - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, Daten: {Data}",
slaveAddress, startAddress, result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,
"Lesen und halten Register失败 - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, Anzahl: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
throw new ModbusCommunicationException(
$"Lesen und halten Register失败: {ex.Message}", ex);
}
}
/// <summary>
/// 写入单个保持Register.
/// </summary>
public async Task WriteSingleRegisterAsync(
byte slaveAddress,
ushort registerAddress,
ushort value,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"Schreiben in ein einzelnes Register - Von der Station: {SlaveAddress}, Adresse: {RegisterAddress}, 值: {Value}",
slaveAddress, registerAddress, value);
await Task.Run(() =>
_modbusMaster!.WriteSingleRegister(slaveAddress, registerAddress, value),
cancellationToken);
_logger.LogDebug(
"Schreiben in ein einzelnes Register成功 - Von der Station: {SlaveAddress}, Adresse: {RegisterAddress}",
slaveAddress, registerAddress);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Schreiben in ein einzelnes Register失败 - Von der Station: {SlaveAddress}, Adresse: {RegisterAddress}, 值: {Value}",
slaveAddress, registerAddress, value);
throw new ModbusCommunicationException(
$"Schreiben in ein einzelnes Register失败: {ex.Message}", ex);
}
}
/// <summary>
/// 写入多个保持Register.
/// </summary>
public async Task WriteMultipleRegistersAsync(
byte slaveAddress,
ushort startAddress,
ushort[] data,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"Schreiben in mehrere Register - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, DatenLänge: {Length}",
slaveAddress, startAddress, data.Length);
await Task.Run(() =>
_modbusMaster!.WriteMultipleRegisters(slaveAddress, startAddress, data),
cancellationToken);
_logger.LogDebug(
"Schreiben in mehrere Register成功 - Von der Station: {SlaveAddress}, Adresse: {StartAddress}",
slaveAddress, startAddress);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Schreiben in mehrere Register失败 - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, DatenLänge: {Length}",
slaveAddress, startAddress, data.Length);
throw new ModbusCommunicationException(
$"Schreiben in mehrere Register失败: {ex.Message}", ex);
}
}
/// <summary>
/// Lesen Sie den Zustand der Spule
/// </summary>
public async Task<bool[]> ReadCoilsAsync(
byte slaveAddress,
ushort startAddress,
ushort numberOfPoints,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"Lesen线圈 - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, Anzahl: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
var result = await Task.Run(() =>
_modbusMaster!.ReadCoils(slaveAddress, startAddress, numberOfPoints),
cancellationToken);
_logger.LogDebug(
"Lesen线圈成功 - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, Daten: {Data}",
slaveAddress, startAddress, result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,
"Lesen线圈失败 - Von der Station: {SlaveAddress}, Adresse: {StartAddress}, Anzahl: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
throw new ModbusCommunicationException(
$"Lesen线圈失败: {ex.Message}", ex);
}
}
/// <summary>
/// 批QuantitätLesen优化
/// </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,
"批QuantitätLesen gescheitert - Adresse: {StartAddress}, Anzahl: {NumberOfPoints}",
request.StartAddress, request.NumberOfPoints);
results[request.StartAddress] = Array.Empty<ushort>();
}
}
return results;
}
/// <summary>
/// 断开Verbindung
/// </summary>
public void Disconnect()
{
try
{
_modbusMaster?.Dispose();
_modbusMaster = null;
if (_tcpClient.Connected)
{
_tcpClient.Close();
}
_logger.LogInformation("已断开与Ausrüstung {IpAddress}:{Port} 的Verbindung", IpAddress, Port);
}
catch (Exception ex)
{
_logger.LogError(ex, "断开Verbindung时发生Fehler.");
}
}
/// <summary>
/// 验证VerbindungStatus
/// </summary>
private void ValidateConnection()
{
if (!IsConnected || _modbusMaster == null)
{
throw new InvalidOperationException("Ausrüstungnicht verbunden");
}
}
/// <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>
/// Register.LesenBitte
/// </summary>
public record RegisterReadRequest(
ushort StartAddress,
ushort NumberOfPoints);
}
2.2 Modbus RTUDie Serie客户端
“`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>
/// Die SerieVerbindungStatus
/// </summary>
public bool IsConnected => _serialPort?.IsOpen ?? false;
/// <summary>
/// Die SerieName
/// </summary>
public string PortName { get; }
/// <summary>
/// Porter Rate
/// </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>
/// VerbindungzuSerielle Ausrüstung
/// </summary>
public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("正在Öffnen Sie die Serie. {PortName} (Porter Rate: {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);
// 配置ModbusParameter
_modbusMaster.Transport.ReadTimeout = 1000;
_modbusMaster.Transport.WriteTimeout = 1000;
_modbusMaster.Transport.Retries = 3;
_modbusMaster.Transport.WaitToRetryMilliseconds = 100;
_logger.LogInformation("成功Öffnen Sie die Serie. {PortName}", PortName);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Öffnen Sie die Serie. {PortName} 失败", PortName);
return false;
}
}
/// <summary>
/// 扫描Serielle Ausrüstung
/// </summary>
public async Task<List<byte>> ScanDevicesAsync(
byte startAddress = 1,
byte endAddress = 247,
CancellationToken cancellationToken = default)
{
var foundDevices = new List<byte>();
_logger.LogInformation("开始扫描Ausrüstung (Adresse范围: {StartAddress}-{EndAddress})", startAddress, endAddress);
for (byte slaveAddress = startAddress; slaveAddress <= endAddress; slaveAddress++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
try
{
// 尝试LesenAusrüstung标识
var result = await Task.Run(() =>
_modbusMaster!.ReadDeviceIdentification(slaveAddress, 0x01),
cancellationToken);
if (result != null)
{
foundDevices.Add(slaveAddress);
_logger.LogInformation("
Antwort veröffentlichen