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
{
///

/// Modbus RTU串口客户端
///

public class ModbusRtuClient : IModbusClient, IDisposable
{
private readonly ILogger _logger;
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("

相关新闻

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

cloud@modbus.cn

QQ
微信