Complete Guide to C #/. NET Industrial Automation Development: Building Enterprise level Industrial ApplicationsIntroduction: Advantages of C #/. NET in Industrial Automation
The C #/. NET platform holds an important position in the field of enterprise level industrial automation due to its powerful type system, rich libraries, outstanding performance, and comprehensive support from Microsoft. From traditional Windows desktop applications to modern cloud native microservices, C #/. NET provides a complete solution stack, particularly suitable for industrial applications that require high reliability, high performance, and long-term maintenance.
1, C #/. NET Industrial Development Environment Configuration
1.1 Development Environment Requirements
<!-- .NET SDK版本要求 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
1.2 Project Structure and Dependency Configuration
<!-- 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 Project directory structure
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/ # 项目文档
2, Deep usage of NModbus library
2.1 Basic Modbus TCP Client
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 Serial Port Client
```csharp /// <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("
Leave a Reply