C#上位机开发实战:封装可复用的欧姆龙NX PLC通讯库

在工业自动化领域,PLC与上位机的稳定通讯是系统可靠运行的基础。欧姆龙NX系列作为新一代控制器,其Ethernet/IP通讯方式为开发者提供了高效的数据交换能力。本文将带您从零开始构建一个 工程化、可复用 的C#通讯库,涵盖连接管理、数据读写、异常处理等核心功能,最终打包为可直接引用的DLL组件。

1. 工程架构设计

1.1 类库结构规划

一个健壮的PLC通讯库需要清晰的层次划分:

OmronNXCommunication
├── Core
│   ├── NXConnection.cs      // 连接管理
│   ├── NXDataAccess.cs      // 数据读写
│   └── NXExceptions.cs      // 自定义异常
├── Models
│   ├── NXDeviceInfo.cs      // 设备配置
│   └── NXVariable.cs        // 变量定义
└── Utilities
    ├── ByteConverter.cs     // 字节处理
    └── HeartbeatService.cs  // 心跳检测

1.2 关键设计原则

  • 依赖倒置 :通过接口抽象通讯细节
  • 单一职责 :每个类只处理特定功能
  • 防御性编程 :对所有输入参数进行验证
public interface INXCommunicator
{
    bool Connect(NXDeviceInfo device);
    void Disconnect();
    bool IsConnected { get; }
}

2. 核心功能实现

2.1 连接管理与心跳机制

使用 System.Timers.Timer 实现自动重连:

public class NXConnection : IDisposable
{
    private NXCompolet _compolet;
    private Timer _heartbeatTimer;
    private int _retryCount = 0;
    
    public void Initialize(NXDeviceInfo device)
    {
        _compolet = new NXCompolet {
            PeerAddress = device.IPAddress,
            LocalPort = device.Port,
            ConnectionType = ConnectionType.Explicit
        };
        
        _heartbeatTimer = new Timer(device.HeartbeatInterval);
        _heartbeatTimer.Elapsed += CheckConnection;
    }
    
    private void CheckConnection(object sender, ElapsedEventArgs e)
    {
        if (!_compolet.IsConnected && _retryCount < 3) 
        {
            try {
                _compolet.Active = true;
                _retryCount = 0;
            }
            catch {
                _retryCount++;
            }
        }
    }
}

2.2 数据读写封装

位操作实现
public class NXDataAccess
{
    public bool ReadBit(string address)
    {
        if (string.IsNullOrWhiteSpace(address))
            throw new ArgumentNullException(nameof(address));
            
        try {
            byte[] data = _compolet.ReadRawData(address) as byte[];
            return data[0] == 0x01;
        }
        catch (Exception ex) {
            throw new NXReadException($"读取位{address}失败", ex);
        }
    }
    
    public void WriteBit(string address, bool value)
    {
        byte[] data = value ? new byte[] {0x01} : new byte[] {0x00};
        _compolet.WriteRawData(address, data);
    }
}
字/字符串操作对比
操作类型 读取方法 写入方法 特殊处理
ReadVariable short WriteVariable 传数值 处理字节序
字符串 ReadVariable string 编码转换后 WriteRawData 处理长度前缀和终止符

3. 异常处理策略

3.1 自定义异常体系

public class NXCommunicationException : Exception
{
    public string Address { get; }
    public DateTime ErrorTime { get; } = DateTime.Now;
    
    public NXCommunicationException(string message, string address) 
        : base(message) 
    {
        Address = address;
    }
}

public class NXTimeoutException : NXCommunicationException
{
    public int TimeoutMs { get; }
    
    public NXTimeoutException(int timeout, string address)
        : base($"操作超时({timeout}ms)", address)
    {
        TimeoutMs = timeout;
    }
}

3.2 重试机制实现

public T ExecuteWithRetry<T>(Func<T> action, int maxRetries = 3)
{
    int retryCount = 0;
    while (true)
    {
        try {
            return action();
        }
        catch (NXTimeoutException) when (retryCount < maxRetries) {
            retryCount++;
            Thread.Sleep(100 * retryCount);
        }
    }
}

4. 性能优化技巧

4.1 批量读写优化

public Dictionary<string, object> ReadMultiple(IEnumerable<string> addresses)
{
    var results = new Dictionary<string, object>();
    var batch = new List<string>();
    
    foreach (var addr in addresses)
    {
        batch.Add(addr);
        if (batch.Count >= 50) // 每批最多50个地址
        {
            var batchResults = _compolet.ReadVariableMultiple(batch.ToArray());
            foreach (DictionaryEntry item in batchResults)
                results.Add(item.Key.ToString(), item.Value);
            batch.Clear();
        }
    }
    
    return results;
}

4.2 连接池管理

public class NXConnectionPool : IDisposable
{
    private ConcurrentBag<NXCompolet> _connections;
    private int _maxPoolSize = 5;
    
    public NXCompolet GetConnection()
    {
        if (_connections.TryTake(out var conn))
            return conn;
            
        if (_connections.Count < _maxPoolSize)
            return CreateNewConnection();
            
        throw new NXBusyException("连接池已满");
    }
    
    public void ReleaseConnection(NXCompolet conn)
    {
        if (conn.IsConnected)
            _connections.Add(conn);
    }
}

5. 打包与部署

5.1 生成NuGet包

  1. 编辑 .csproj 文件添加包信息:
<PropertyGroup>
    <PackageId>OmronNX.Communication</PackageId>
    <Version>1.0.0</Version>
    <Authors>YourName</Authors>
    <Description>欧姆龙NX系列PLC通讯库</Description>
</PropertyGroup>
  1. 使用CLI命令打包:
dotnet pack --configuration Release

5.2 版本控制策略

采用语义化版本控制:

  • 主版本号 :重大架构变更
  • 次版本号 :新增功能且向下兼容
  • 修订号 :问题修复和优化

在库中通过常量定义版本:

public static class LibraryInfo
{
    public const string Version = "1.2.0";
    public static readonly DateTime BuildDate = new DateTime(2023, 6, 15);
}

6. 实际应用示例

6.1 生产线监控场景

public class ProductionLineMonitor
{
    private readonly INXCommunicator _plc;
    
    public ProductionLineMonitor(INXCommunicator communicator)
    {
        _plc = communicator;
    }
    
    public ProductionStatus GetCurrentStatus()
    {
        return new ProductionStatus {
            IsRunning = _plc.ReadBit("Main_Running"),
            CurrentSpeed = _plc.ReadWord("Motor_Speed"),
            FaultCode = _plc.ReadWord("Error_Code")
        };
    }
}

6.2 与主流框架集成

在WPF应用中通过DI注入:

services.AddSingleton<INXCommunicator>(provider => 
    new NXCommunicationService(
        new NXDeviceInfo {
            IPAddress = Configuration["PLC:IP"],
            Port = int.Parse(Configuration["PLC:Port"])
        }
    ));

在ASP.NET Core中作为后台服务:

services.AddHostedService<PLCBackgroundService>();

更多推荐