C# 산업 자동화개발完全指南:构建企业级적용

무료무료 기술 자료

이 글은 바로 읽을 수 있으며 기초 학습과 검색 유입에 적합합니다.

C# 工业自动化开发完全指南:构建企业级应用

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 项目结构和依赖설정

<!-- Industrial자동화.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 项目目录结构

Industrial자동화/
├── src/
│   ├── Industrial자동화.Core/          # 核心业务逻辑
│   │   ├── Models/                         # 데이터模型
│   │   ├── Services/                       # 业务服务
│   │   ├── Interfaces/                     # 인터페이스定义
│   │   └── Exceptions/                     # 自定义异常
│   │
│   ├── Industrial자동화.Modbus/        # Modbus통신模块
│   │   ├── Clients/                        # Modbus클라이언트
│   │   ├── Servers/                        # Modbus服务器
│   │   └── 컨버터/                     # 데이터컨버터
│   │
│   ├── Industrial자동화.UI/            # 用户界面
│   │   ├── Views/                          # WPF视图
│   │   ├── ViewModels/                     # MVVM视图模型
│   │   ├── 컨버터/                     # 值컨버터
│   │   └── Behaviors/                      # 行为
│   │
│   └── Industrial자동화.Host/          # 적용程序宿主
│
├── tests/
│   ├── Industrial자동화.Core.Tests/
│   ├── Industrial자동화.Modbus.Tests/
│   └── Industrial자동화.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 Industrial자동화.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 Ip주소 { get; }

        /// <summary>
        /// 设备포트
        /// </summary>
        public int Port { get; }

        /// <summary>
        /// 初始化Modbus TCP클라이언트
        /// </summary>
        public ModbusTcpClient(
            string ip주소,
            int port = 502,
            ILogger<ModbusTcpClient>? logger = null)
        {
            Ip주소 = ip주소 ?? throw new ArgumentNullException(nameof(ip주소));
            Port = port;
            _logger = logger ?? NullLogger<ModbusTcpClient>.Instance;
            _tcpClient = new TcpClient();
        }

        /// <summary>
        /// 연결到设备
        /// </summary>
        public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
        {
            try
            {
                _logger.LogInformation("正在연결到设备 {Ip주소}:{Port}", Ip주소, Port);

                await _tcpClient.ConnectAsync(Ip주소, Port, cancellationToken);

                var factory = new ModbusFactory();
                _modbusMaster = factory.CreateMaster(_tcpClient);

                // 设置超时时间
                _modbusMaster.Transport.ReadTimeout = 3000;
                _modbusMaster.Transport.WriteTimeout = 3000;

                _logger.LogInformation("成功연결到设备 {Ip주소}:{Port}", Ip주소, Port);
                return true;
            }
            catch (Exception ex)
            {
                _logger.Log오답(ex, "연결设备 {Ip주소}:{Port} 失败", Ip주소, Port);
                return false;
            }
        }

        /// <summary>
        /// 유지 레지스터 읽기 (FC03)
        /// </summary>
        public async Task<ushort[]> ReadHolding회원가입sAsync(
            byte slave주소,
            ushort 시작주소,
            ushort numberOfPoints,
            CancellationToken cancellationToken = default)
        {
            ValidateConnection();

            try
            {
                _logger.LogDebug(
                    "유지 레지스터 읽기 (FC03) - 슬레이브: {Slave주소}, 주소: {Start주소}, 数量: {NumberOfPoints}",
                    slave주소, 시작주소, numberOfPoints);

                var result = await Task.Run(() =>
                    _modbusMaster!.ReadHolding회원가입s(slave주소, 시작주소, numberOfPoints),
                    cancellationToken);

                _logger.LogDebug(
                    "유지 레지스터 읽기 (FC03)成功 - 슬레이브: {Slave주소}, 주소: {Start주소}, 데이터: {데이터}",
                    slave주소, 시작주소, result);

                return result;
            }
            catch (Exception ex)
            {
                _logger.Log오답(ex,
                    "유지 레지스터 읽기 (FC03)失败 - 슬레이브: {Slave주소}, 주소: {Start주소}, 数量: {NumberOfPoints}",
                    slave주소, 시작주소, numberOfPoints);
                throw new ModbusCommunicationException(
                    $"유지 레지스터 읽기 (FC03)失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 쓰기单个유지 레지스터
        /// </summary>
        public async Task WriteSingle회원가입Async(
            byte slave주소,
            ushort register주소,
            ushort value,
            CancellationToken cancellationToken = default)
        {
            ValidateConnection();

            try
            {
                _logger.LogDebug(
                    "단일 레지스터 쓰기 (FC06) - 슬레이브: {Slave주소}, 주소: {회원가입주소}, 值: {Value}",
                    slave주소, register주소, value);

                await Task.Run(() =>
                    _modbusMaster!.WriteSingle회원가입(slave주소, register주소, value),
                    cancellationToken);

                _logger.LogDebug(
                    "단일 레지스터 쓰기 (FC06)成功 - 슬레이브: {Slave주소}, 주소: {회원가입주소}",
                    slave주소, register주소);
            }
            catch (Exception ex)
            {
                _logger.Log오답(ex,
                    "단일 레지스터 쓰기 (FC06)失败 - 슬레이브: {Slave주소}, 주소: {회원가입주소}, 值: {Value}",
                    slave주소, register주소, value);
                throw new ModbusCommunicationException(
                    $"단일 레지스터 쓰기 (FC06)失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 쓰기多个유지 레지스터
        /// </summary>
        public async Task WriteMultiple회원가입sAsync(
            byte slave주소,
            ushort 시작주소,
            ushort[] 데이터,
            CancellationToken cancellationToken = default)
        {
            ValidateConnection();

            try
            {
                _logger.LogDebug(
                    "다중 레지스터 쓰기 (FC16) - 슬레이브: {Slave주소}, 주소: {Start주소}, 데이터길이: {Length}",
                    slave주소, 시작주소, 데이터.Length);

                await Task.Run(() =>
                    _modbusMaster!.WriteMultiple회원가입s(slave주소, 시작주소, 데이터),
                    cancellationToken);

                _logger.LogDebug(
                    "다중 레지스터 쓰기 (FC16)成功 - 슬레이브: {Slave주소}, 주소: {Start주소}",
                    slave주소, 시작주소);
            }
            catch (Exception ex)
            {
                _logger.Log오답(ex,
                    "다중 레지스터 쓰기 (FC16)失败 - 슬레이브: {Slave주소}, 주소: {Start주소}, 데이터길이: {Length}",
                    slave주소, 시작주소, 데이터.Length);
                throw new ModbusCommunicationException(
                    $"다중 레지스터 쓰기 (FC16)失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 코일 읽기 (FC01)
        /// </summary>
        public async Task<bool[]> ReadCoilsAsync(
            byte slave주소,
            ushort 시작주소,
            ushort numberOfPoints,
            CancellationToken cancellationToken = default)
        {
            ValidateConnection();

            try
            {
                _logger.LogDebug(
                    "읽기코일 - 슬레이브: {Slave주소}, 주소: {Start주소}, 数量: {NumberOfPoints}",
                    slave주소, 시작주소, numberOfPoints);

                var result = await Task.Run(() =>
                    _modbusMaster!.ReadCoils(slave주소, 시작주소, numberOfPoints),
                    cancellationToken);

                _logger.LogDebug(
                    "읽기코일成功 - 슬레이브: {Slave주소}, 주소: {Start주소}, 데이터: {데이터}",
                    slave주소, 시작주소, result);

                return result;
            }
            catch (Exception ex)
            {
                _logger.Log오답(ex,
                    "읽기코일失败 - 슬레이브: {Slave주소}, 주소: {Start주소}, 数量: {NumberOfPoints}",
                    slave주소, 시작주소, numberOfPoints);
                throw new ModbusCommunicationException(
                    $"읽기코일失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 批量읽기优化
        /// </summary>
        public async Task<Dictionary<ushort, ushort[]>> BatchRead회원가입sAsync(
            byte slave주소,
            IEnumerable<회원가입ReadRequest> requests,
            CancellationToken cancellationToken = default)
        {
            ValidateConnection();

            var results = new Dictionary<ushort, ushort[]>();

            foreach (var request in requests)
            {
                try
                {
                    var 데이터 = await ReadHolding회원가입sAsync(
                        slave주소,
                        request.Start주소,
                        request.NumberOfPoints,
                        cancellationToken);

                    results[request.Start주소] = 데이터;
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex,
                        "批量읽기失败 - 주소: {Start주소}, 数量: {NumberOfPoints}",
                        request.Start주소, request.NumberOfPoints);
                    results[request.Start주소] = Array.Empty<ushort>();
                }
            }

            return results;
        }

        /// <summary>
        /// 断开연결
        /// </summary>
        public void Disconnect()
        {
            try
            {
                _modbusMaster?.Dispose();
                _modbusMaster = null;

                if (_tcpClient.Connected)
                {
                    _tcpClient.닫기();
                }

                _logger.LogInformation("已断开与设备 {Ip주소}:{Port} 的연결", Ip주소, Port);
            }
            catch (Exception ex)
            {
                _logger.Log오답(ex, "断开연결时发生错误");
            }
        }

        /// <summary>
        /// 验证연결状态
        /// </summary>
        private void ValidateConnection()
        {
            if (!IsConnected || _modbusMaster == null)
            {
                throw new Invalid작업Exception("设备연결되지 않음");
            }
        }

        /// <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 회원가입ReadRequest(
        ushort Start주소,
        ushort NumberOfPoints);
}

2.2 Modbus RTU시리얼 포트클라이언트

```csharp using NModbus; using NModbus.Serial; using System.IO.Ports; namespace Industrial자동화.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 Port이름 { get; }

    /// <summary>
    /// 통신 속도
    /// </summary>
    public int BaudRate { get; }

    /// <summary>
    /// 初始化Modbus RTU클라이언트
    /// </summary>
    public ModbusRtuClient(
        string port이름,
        int baudRate = 9600,
        ILogger<ModbusRtuClient>? logger = null)
    {
        Port이름 = port이름 ?? throw new ArgumentNullException(nameof(port이름));
        BaudRate = baudRate;
        _logger = logger ?? NullLogger<ModbusRtuClient>.Instance;

        _serialPort = new SerialPort(port이름)
        {
            BaudRate = baudRate,
            데이터Bits = 8,
            Parity = Parity.None,
            StopBits = StopBits.One,
            ReadTimeout = 1000,
            WriteTimeout = 1000
        };
    }

    /// <summary>
    /// 연결到시리얼 포트设备
    /// </summary>
    public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogInformation("正在시리얼 포트 열기 {Port이름} (통신 속도: {BaudRate})", Port이름, 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("成功시리얼 포트 열기 {Port이름}", Port이름);
            return true;
        }
        catch (Exception ex)
        {
            _logger.Log오답(ex, "시리얼 포트 열기 {Port이름} 失败", Port이름);
            return false;
        }
    }

    /// <summary>
    /// 扫描시리얼 포트设备
    /// </summary>
    public async Task<List<byte>> ScanDevicesAsync(
        byte 시작주소 = 1,
        byte end주소 = 247,
        CancellationToken cancellationToken = default)
    {
        var foundDevices = new List<byte>();

        _logger.LogInformation("开始扫描设备 (주소范围: {Start주소}-{End주소})", 시작주소, end주소);

        for (byte slave주소 = 시작주소; slave주소 <= end주소; slave주소++)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                break;
            }

            try
            {
                // 尝试읽기设备标识
                var result = await Task.Run(() =>
                    _modbusMaster!.ReadDeviceIdentification(slave주소, 0x01),
                    cancellationToken);

                if (result != null)
                {
                    foundDevices.Add(slave주소);
                    _logger.LogInformation("
실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널

답글 작성

이메일 주소는 공개되지 않습니다. 必填项已用 * 标注