C#与西门子S7-1200通讯实例

一、通讯方式概述

C#与西门子S7-1200 PLC通讯主要有两种主流方式:

方式 协议 特点 适用场景
S7NetPlus S7协议(Profinet) 直接、高效、实时性强 局域网内高速数据交换
OPC UA OPC UA协议 跨平台、安全、标准化 跨网络、多系统集成

二、S7NetPlus完整实例(推荐)

1. 环境准备

NuGet包安装:

bash 复制代码
Install-Package S7netplus

或通过Visual Studio的NuGet包管理器搜索安装S7netplus(最新版本0.20.0+)。

2. 核心连接类

csharp 复制代码
using S7.Net;
using System;
using System.Threading.Tasks;

public class S71200PlcClient : IDisposable
{
    private Plc _plc;
    private bool _isConnected = false;
    
    // PLC连接参数
    public string IpAddress { get; set; } = "192.168.0.1";
    public short Rack { get; set; } = 0;      // S7-1200固定为0
    public short Slot { get; set; } = 1;      // S7-1200固定为1
    public int Timeout { get; set; } = 5000;  // 5秒超时
    
    /// <summary>
    /// 连接PLC
    /// </summary>
    public async Task<bool> ConnectAsync()
    {
        try
        {
            // 创建PLC实例
            _plc = new Plc(CpuType.S71200, IpAddress, Rack, Slot);
            _plc.ReadTimeout = Timeout;
            _plc.WriteTimeout = Timeout;
            
            // 异步连接
            await Task.Run(() => _plc.Open());
            
            _isConnected = _plc.IsConnected;
            return _isConnected;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接失败: {ex.Message}");
            return false;
        }
    }
    
    /// <summary>
    /// 断开连接
    /// </summary>
    public void Disconnect()
    {
        try
        {
            if (_plc != null && _plc.IsConnected)
            {
                _plc.Close();
            }
            _isConnected = false;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"断开连接失败: {ex.Message}");
        }
    }
    
    /// <summary>
    /// 读取PLC状态
    /// </summary>
    public string GetPlcStatus()
    {
        if (!_isConnected) return "未连接";
        
        try
        {
            byte statusCode = _plc.ReadStatus();
            return statusCode switch
            {
                0x08 => "运行中(RUN)",
                0x04 => "停止(STOP)",
                0x05 => "启动中(STARTUP)",
                _ => $"未知状态: {statusCode:X2}"
            };
        }
        catch (Exception ex)
        {
            return $"读取状态失败: {ex.Message}";
        }
    }
    
    /// <summary>
    /// 读取单个位(如I0.0)
    /// </summary>
    public bool ReadBit(string address)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            return (bool)_plc.Read(address);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取位失败 {address}: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 读取整数(如DB1.DBW2)
    /// </summary>
    public short ReadInt(string address)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            return (short)_plc.Read(address);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取整数失败 {address}: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 读取浮点数(如DB1.DBD4)
    /// </summary>
    public float ReadReal(string address)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            return (float)_plc.Read(address);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取浮点数失败 {address}: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 写入单个位(如Q0.0)
    /// </summary>
    public void WriteBit(string address, bool value)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            _plc.Write(address, value);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"写入位失败 {address}: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 写入整数(如MW10)
    /// </summary>
    public void WriteInt(string address, short value)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            _plc.Write(address, value);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"写入整数失败 {address}: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 写入浮点数(如DB1.DBD0)
    /// </summary>
    public void WriteReal(string address, float value)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            _plc.Write(address, value);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"写入浮点数失败 {address}: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 批量读取多个变量
    /// </summary>
    public object[] ReadMultiple(params string[] addresses)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            var results = new object[addresses.Length];
            for (int i = 0; i < addresses.Length; i++)
            {
                results[i] = _plc.Read(addresses[i]);
            }
            return results;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"批量读取失败: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 读取字节数组(如DB1.DBB0开始的10个字节)
    /// </summary>
    public byte[] ReadBytes(DataType dataType, int db, int startByte, int count)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            return _plc.ReadBytes(dataType, db, startByte, count);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取字节数组失败: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 写入字节数组
    /// </summary>
    public void WriteBytes(DataType dataType, int db, int startByte, byte[] data)
    {
        if (!_isConnected) throw new InvalidOperationException("PLC未连接");
        
        try
        {
            _plc.WriteBytes(dataType, db, startByte, data);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"写入字节数组失败: {ex.Message}");
            throw;
        }
    }
    
    public void Dispose()
    {
        Disconnect();
        _plc?.Dispose();
    }
}

3. 使用示例

csharp 复制代码
class Program
{
    static async Task Main(string[] args)
    {
        using var plcClient = new S71200PlcClient
        {
            IpAddress = "192.168.0.100"  // 替换为实际PLC IP
        };
        
        try
        {
            // 连接PLC
            bool connected = await plcClient.ConnectAsync();
            if (!connected)
            {
                Console.WriteLine("连接PLC失败");
                return;
            }
            
            Console.WriteLine("PLC连接成功");
            Console.WriteLine($"PLC状态: {plcClient.GetPlcStatus()}");
            
            // 读取输入位 I0.0
            bool input0 = plcClient.ReadBit("I0.0");
            Console.WriteLine($"I0.0 = {input0}");
            
            // 读取输出位 Q0.0
            bool output0 = plcClient.ReadBit("Q0.0");
            Console.WriteLine($"Q0.0 = {output0}");
            
            // 读取中间位 M0.0
            bool memory0 = plcClient.ReadBit("M0.0");
            Console.WriteLine($"M0.0 = {memory0}");
            
            // 读取DB块数据
            float temperature = plcClient.ReadReal("DB1.DBD0");
            Console.WriteLine($"温度 = {temperature}°C");
            
            short speed = plcClient.ReadInt("DB1.DBW2");
            Console.WriteLine($"速度 = {speed} RPM");
            
            // 写入数据
            plcClient.WriteBit("Q0.1", true);  // 置位Q0.1
            plcClient.WriteReal("DB1.DBD4", 25.5f);  // 写入温度设定值
            
            // 批量读取
            var batchResults = plcClient.ReadMultiple(
                "I0.0", "I0.1", "M0.0", "DB1.DBD0", "DB1.DBW2");
            
            Console.WriteLine("批量读取结果:");
            for (int i = 0; i < batchResults.Length; i++)
            {
                Console.WriteLine($"  地址{i}: {batchResults[i]}");
            }
            
            // 读取字节数组(DB1从字节0开始的10个字节)
            byte[] rawData = plcClient.ReadBytes(DataType.DataBlock, 1, 0, 10);
            Console.WriteLine($"原始数据: {BitConverter.ToString(rawData)}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"操作失败: {ex.Message}");
        }
        finally
        {
            Console.WriteLine("程序结束");
        }
    }
}

4. WinForms界面示例

csharp 复制代码
using System;
using System.Windows.Forms;
using S7.Net;

namespace S71200Monitor
{
    public partial class MainForm : Form
    {
        private Plc _plc;
        private Timer _pollTimer;
        
        public MainForm()
        {
            InitializeComponent();
            InitializeControls();
        }
        
        private void InitializeControls()
        {
            // 连接参数
            txtIpAddress.Text = "192.168.0.100";
            txtRack.Text = "0";
            txtSlot.Text = "1";
            
            // 定时器设置(100ms轮询)
            _pollTimer = new Timer { Interval = 100 };
            _pollTimer.Tick += PollTimer_Tick;
        }
        
        private async void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                CpuType cpuType = CpuType.S71200;
                string ipAddress = txtIpAddress.Text;
                short rack = short.Parse(txtRack.Text);
                short slot = short.Parse(txtSlot.Text);
                
                _plc = new Plc(cpuType, ipAddress, rack, slot);
                await Task.Run(() => _plc.Open());
                
                if (_plc.IsConnected)
                {
                    btnConnect.Enabled = false;
                    btnDisconnect.Enabled = true;
                    _pollTimer.Start();
                    
                    AddLog($"成功连接到PLC: {ipAddress}");
                    AddLog($"PLC状态: {GetPlcStatusString()}");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"连接失败: {ex.Message}", "错误", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            try
            {
                if (_plc != null && _plc.IsConnected)
                {
                    _plc.Close();
                    _pollTimer.Stop();
                    
                    btnConnect.Enabled = true;
                    btnDisconnect.Enabled = false;
                    
                    AddLog("已断开PLC连接");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"断开失败: {ex.Message}", "错误", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        private void PollTimer_Tick(object sender, EventArgs e)
        {
            if (_plc == null || !_plc.IsConnected) return;
            
            try
            {
                // 实时读取数据并更新UI
                bool motorRunning = (bool)_plc.Read("M0.0");
                lblMotorStatus.Text = motorRunning ? "运行中" : "停止";
                lblMotorStatus.BackColor = motorRunning ? Color.Green : Color.Red;
                
                float temperature = (float)_plc.Read("DB1.DBD0");
                lblTemperature.Text = $"{temperature:F1}°C";
                
                short speed = (short)_plc.Read("DB1.DBW2");
                lblSpeed.Text = $"{speed} RPM";
                
                // 更新状态栏
                toolStripStatusLabel.Text = $"最后更新: {DateTime.Now:HH:mm:ss}";
            }
            catch (Exception ex)
            {
                AddLog($"轮询错误: {ex.Message}");
            }
        }
        
        private void btnWrite_Click(object sender, EventArgs e)
        {
            if (_plc == null || !_plc.IsConnected)
            {
                MessageBox.Show("请先连接PLC", "提示", 
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            try
            {
                // 写入设定值
                float setValue = float.Parse(txtSetValue.Text);
                _plc.Write("DB1.DBD4", setValue);
                
                AddLog($"已写入设定值: {setValue}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"写入失败: {ex.Message}", "错误", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        private string GetPlcStatusString()
        {
            try
            {
                byte statusCode = _plc.ReadStatus();
                return statusCode switch
                {
                    0x08 => "运行中(RUN)",
                    0x04 => "停止(STOP)",
                    0x05 => "启动中(STARTUP)",
                    _ => $"未知状态: {statusCode:X2}"
                };
            }
            catch
            {
                return "状态未知";
            }
        }
        
        private void AddLog(string message)
        {
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new Action<string>(AddLog), message);
                return;
            }
            
            txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n");
            txtLog.ScrollToCaret();
        }
        
        // 控件声明(设计器生成)
        private TextBox txtIpAddress, txtRack, txtSlot, txtSetValue, txtLog;
        private Button btnConnect, btnDisconnect, btnWrite;
        private Label lblMotorStatus, lblTemperature, lblSpeed;
        private StatusStrip statusStrip;
        private ToolStripStatusLabel toolStripStatusLabel;
    }
}

5. PLC地址格式说明

地址类型 格式 示例 说明
输入位 I[字节].[位] I0.0 输入字节0的位0
输出位 Q[字节].[位] Q0.1 输出字节0的位1
中间位 M[字节].[位] M10.5 中间字节10的位5
数据块位 DB[块号].DBX[字节].[位] DB1.DBX0.0 DB1字节0的位0
数据块字 DB[块号].DBW[字节] DB1.DBW2 DB1从字节2开始的字
数据块双字 DB[块号].DBD[字节] DB1.DBD4 DB1从字节4开始的双字
定时器 T[编号] T0 定时器0
计数器 C[编号] C1 计数器1

三、OPC UA通讯方式

1. PLC端配置(TIA Portal)

  1. 启用OPC UA服务器

    • 双击PLC设备 → 属性 → OPC UA
    • 勾选"启用OPC UA服务器"
    • 设置服务器名称和端点URL(如opc.tcp://192.168.0.1:4840
  2. 变量暴露

    lad 复制代码
    // 在OB1中定义变量并设置为OPC UA可见
    Network 1:
    LD  "Temp_Sensor"
    T   "Device1".Temp  // Real型温度变量
    
    // OPC UA配置
    CALL "OPC UA Variable Config"
    IN := "Device1".Temp
    VISIBLE := TRUE
    BROWSENAME := "Device1_Temp"

2. C# OPC UA客户端

csharp 复制代码
using Opc.Ua;
using Opc.Ua.Client;
using System;
using System.Threading.Tasks;

public class S71200OpcClient
{
    private Session _session;
    private string _endpointUrl;
    
    public S71200OpcClient(string endpointUrl)
    {
        _endpointUrl = endpointUrl;
    }
    
    public async Task ConnectAsync()
    {
        try
        {
            // 选择端点
            var endpoints = CoreClientUtils.SelectEndpoint(_endpointUrl, false);
            
            // 创建配置
            var config = new ApplicationConfiguration
            {
                ApplicationName = "S7-1200 OPC UA Client",
                ApplicationType = ApplicationType.Client,
                SecurityConfiguration = new SecurityConfiguration
                {
                    ApplicationCertificate = new CertificateIdentifier(),
                    AutoAcceptUntrustedCertificates = true
                }
            };
            
            // 建立会话
            _session = await Session.Create(
                config,
                new ConfiguredEndpoint(null, endpoints[0], EndpointConfiguration.Create()),
                false,
                false,
                config.ApplicationName,
                60000,
                null,
                null);
            
            Console.WriteLine($"已连接到OPC UA服务器: {_endpointUrl}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接失败: {ex.Message}");
            throw;
        }
    }
    
    public async Task<object> ReadNodeAsync(string nodeId)
    {
        if (_session == null || !_session.Connected)
            throw new InvalidOperationException("会话未连接");
        
        try
        {
            var readValue = new ReadValueId
            {
                NodeId = new NodeId(nodeId),
                AttributeId = Attributes.Value
            };
            
            var request = new ReadRequest
            {
                NodesToRead = new ReadValueIdCollection { readValue }
            };
            
            var response = await _session.ReadAsync(request);
            
            if (response.Results.Count > 0 && 
                StatusCode.IsGood(response.Results[0].StatusCode))
            {
                return response.Results[0].Value;
            }
            
            throw new Exception($"读取失败: {response.Results[0].StatusCode}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取节点失败 {nodeId}: {ex.Message}");
            throw;
        }
    }
    
    public async Task WriteNodeAsync(string nodeId, object value)
    {
        if (_session == null || !_session.Connected)
            throw new InvalidOperationException("会话未连接");
        
        try
        {
            var writeValue = new WriteValue
            {
                NodeId = new NodeId(nodeId),
                AttributeId = Attributes.Value,
                Value = new DataValue(new Variant(value))
            };
            
            var request = new WriteRequest
            {
                NodesToWrite = new WriteValueCollection { writeValue }
            };
            
            var response = await _session.WriteAsync(request);
            
            if (response.Results.Count > 0 && 
                !StatusCode.IsGood(response.Results[0]))
            {
                throw new Exception($"写入失败: {response.Results[0]}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"写入节点失败 {nodeId}: {ex.Message}");
            throw;
        }
    }
    
    public void Disconnect()
    {
        _session?.Close();
        _session?.Dispose();
    }
}

3. OPC UA使用示例

csharp 复制代码
class Program
{
    static async Task Main(string[] args)
    {
        var opcClient = new S71200OpcClient("opc.tcp://192.168.0.1:4840");
        
        try
        {
            // 连接
            await opcClient.ConnectAsync();
            
            // 读取温度变量(节点ID需根据实际配置)
            var temperature = await opcClient.ReadNodeAsync("ns=2;s=Device1_Temp");
            Console.WriteLine($"温度: {temperature}°C");
            
            // 写入设定值
            await opcClient.WriteNodeAsync("ns=2;s=Device1_Setpoint", 25.5);
            Console.WriteLine("设定值写入成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"操作失败: {ex.Message}");
        }
        finally
        {
            opcClient.Disconnect();
        }
    }
}

四、两种方式对比与选择建议

特性 S7NetPlus OPC UA
性能 ⭐⭐⭐⭐⭐(毫秒级) ⭐⭐⭐⭐(10-100ms)
实时性 极高
安全性 基础(IP过滤) ⭐⭐⭐⭐⭐(证书、加密)
跨平台 仅Windows/.NET ⭐⭐⭐⭐⭐(全平台)
配置复杂度 简单 中等
标准化 西门子专有 国际标准(IEC 62541)
网络要求 局域网 局域网/广域网

选择建议:

  • S7NetPlus:适合局域网内高速数据采集、实时控制、单系统集成
  • OPC UA:适合跨网络、多系统集成、高安全性要求、长期维护项目

参考代码 C#与西门子1200通讯实例 www.youwenfan.com/contentcst/38493.html

五、常见问题与解决方案

1. 连接失败问题

csharp 复制代码
// 常见错误代码及解决方案
public enum ConnectionError
{
    NoError = 0,
    IpAddressInvalid = 1,      // IP地址无效
    PortBlocked = 2,           // 端口被防火墙阻挡
    PlcNotReachable = 3,       // PLC不可达
    WrongCpuType = 4,          // CPU类型错误
    AccessDenied = 5,          // 访问被拒绝
    Timeout = 6                // 连接超时
}

public static string GetErrorSolution(ConnectionError error)
{
    return error switch
    {
        ConnectionError.IpAddressInvalid => 
            "检查IP地址格式,确保与PLC IP一致",
        ConnectionError.PortBlocked => 
            "检查防火墙设置,开放102端口(S7协议默认端口)",
        ConnectionError.PlcNotReachable => 
            "1. 使用ping测试网络连通性\n2. 检查网线连接\n3. 确认PLC已上电",
        ConnectionError.WrongCpuType => 
            "确认PLC型号,S7-1200使用CpuType.S71200",
        ConnectionError.AccessDenied => 
            "检查PLC访问权限设置,可能需要密码",
        ConnectionError.Timeout => 
            "1. 增加超时时间\n2. 检查网络延迟\n3. 减少同时连接数",
        _ => "未知错误,请检查日志"
    };
}

2. 数据读写异常处理

csharp 复制代码
public class PlcDataManager
{
    private Plc _plc;
    private int _retryCount = 3;
    private int _retryDelay = 100; // ms
    
    /// <summary>
    /// 带重试机制的读取
    /// </summary>
    public async Task<object> ReadWithRetryAsync(string address)
    {
        for (int i = 0; i < _retryCount; i++)
        {
            try
            {
                return _plc.Read(address);
            }
            catch (PlcException ex) when (i < _retryCount - 1)
            {
                Console.WriteLine($"第{i+1}次读取失败,{_retryDelay}ms后重试: {ex.Message}");
                await Task.Delay(_retryDelay);
            }
        }
        throw new Exception($"读取失败,已重试{_retryCount}次");
    }
    
    /// <summary>
    /// 心跳检测
    /// </summary>
    public async Task StartHeartbeatAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                // 读取PLC状态作为心跳
                byte status = _plc.ReadStatus();
                Console.WriteLine($"心跳正常,PLC状态: {status:X2}");
                
                await Task.Delay(5000, cancellationToken); // 5秒一次
            }
            catch (Exception ex)
            {
                Console.WriteLine($"心跳检测失败: {ex.Message}");
                // 触发重连逻辑
                await ReconnectAsync();
            }
        }
    }
    
    private async Task ReconnectAsync()
    {
        Console.WriteLine("尝试重新连接PLC...");
        
        for (int i = 0; i < 5; i++) // 最多重试5次
        {
            try
            {
                if (_plc.IsConnected)
                    _plc.Close();
                
                await Task.Run(() => _plc.Open());
                
                if (_plc.IsConnected)
                {
                    Console.WriteLine("PLC重连成功");
                    return;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"第{i+1}次重连失败: {ex.Message}");
                await Task.Delay(2000 * (i + 1)); // 指数退避
            }
        }
        
        throw new Exception("PLC重连失败,请检查网络连接");
    }
}

3. 性能优化建议

csharp 复制代码
public class HighPerformancePlcClient
{
    private Plc _plc;
    private ConcurrentQueue<PlcRequest> _requestQueue;
    private SemaphoreSlim _semaphore;
    
    /// <summary>
    /// 批量读取优化
    /// </summary>
    public async Task<Dictionary<string, object>> BatchReadAsync(
        IEnumerable<string> addresses, int batchSize = 20)
    {
        var results = new ConcurrentDictionary<string, object>();
        var addressList = addresses.ToList();
        
        // 分批处理
        var batches = addressList
            .Select((addr, index) => new { addr, index })
            .GroupBy(x => x.index / batchSize)
            .Select(g => g.Select(x => x.addr).ToList())
            .ToList();
        
        // 并行处理批次
        await Parallel.ForEachAsync(batches, async (batch, ct) =>
        {
            foreach (var address in batch)
            {
                try
                {
                    var value = _plc.Read(address);
                    results[address] = value;
                }
                catch (Exception ex)
                {
                    results[address] = new { Error = ex.Message };
                }
            }
        });
        
        return new Dictionary<string, object>(results);
    }
    
    /// <summary>
    /// 异步写入队列
    /// </summary>
    public async Task EnqueueWriteAsync(string address, object value)
    {
        var request = new PlcRequest
        {
            Address = address,
            Value = value,
            Timestamp = DateTime.Now
        };
        
        _requestQueue.Enqueue(request);
        
        // 触发处理
        _ = Task.Run(async () =>
        {
            await _semaphore.WaitAsync();
            try
            {
                await ProcessWriteQueueAsync();
            }
            finally
            {
                _semaphore.Release();
            }
        });
    }
}

六、实际项目部署建议

1. 配置文件示例(appsettings.json)

json 复制代码
{
  "PlcSettings": {
    "IpAddress": "192.168.0.100",
    "Rack": 0,
    "Slot": 1,
    "Timeout": 5000,
    "RetryCount": 3,
    "HeartbeatInterval": 5000,
    "DataPoints": [
      {
        "Name": "Temperature",
        "Address": "DB1.DBD0",
        "DataType": "Real",
        "PollingInterval": 1000
      },
      {
        "Name": "MotorSpeed",
        "Address": "DB1.DBW2",
        "DataType": "Int",
        "PollingInterval": 500
      }
    ]
  },
  "Logging": {
    "Level": "Information",
    "FilePath": "logs/plc_communication.log"
  }
}

2. 依赖注入配置

csharp 复制代码
// Startup.cs 或 Program.cs
public static IServiceCollection AddPlcServices(this IServiceCollection services, IConfiguration configuration)
{
    var plcSettings = configuration.GetSection("PlcSettings").Get<PlcSettings>();
    
    services.AddSingleton(plcSettings);
    
    services.AddSingleton<IPlcClient>(provider =>
    {
        var settings = provider.GetRequiredService<PlcSettings>();
        var logger = provider.GetRequiredService<ILogger<PlcClient>>();
        
        return new PlcClient(settings, logger);
    });
    
    services.AddHostedService<PlcBackgroundService>();
    
    return services;
}

3. 监控与诊断

csharp 复制代码
public class PlcDiagnosticService
{
    private readonly IPlcClient _plcClient;
    private readonly ILogger<PlcDiagnosticService> _logger;
    private readonly PerformanceCounter _readCounter;
    private readonly PerformanceCounter _writeCounter;
    
    public PlcDiagnosticService(IPlcClient plcClient, ILogger<PlcDiagnosticService> logger)
    {
        _plcClient = plcClient;
        _logger = logger;
        
        // 性能计数器
        _readCounter = new PerformanceCounter(
            "PlcCommunication", "ReadOperations", false);
        _writeCounter = new PerformanceCounter(
            "PlcCommunication", "WriteOperations", false);
    }
    
    public async Task<DiagnosticReport> GetDiagnosticReportAsync()
    {
        var report = new DiagnosticReport
        {
            Timestamp = DateTime.Now,
            ConnectionStatus = _plcClient.IsConnected,
            PlcStatus = await _plcClient.GetStatusAsync(),
            PerformanceMetrics = new PerformanceMetrics
            {
                ReadOperationsPerSecond = _readCounter.NextValue(),
                WriteOperationsPerSecond = _writeCounter.NextValue(),
                AverageResponseTime = CalculateAverageResponseTime(),
                ErrorRate = CalculateErrorRate()
            },
            RecentErrors = GetRecentErrors(50)
        };
        
        return report;
    }
    
    public void LogOperation(string operation, TimeSpan duration, bool success)
    {
        _logger.LogInformation(
            "PLC操作: {Operation}, 耗时: {Duration}ms, 状态: {Status}",
            operation, duration.TotalMilliseconds, 
            success ? "成功" : "失败");
        
        if (!success)
        {
            _logger.LogWarning("PLC操作失败: {Operation}", operation);
        }
    }
}

七、资源与参考

  1. 官方资源

  2. 学习资料

    • 《C#上位机对接西门子PLC完整实战指南》
    • 《深入解析S7NetPlus:工业环境下的.NET西门子PLC通信实战指南》
  3. 调试工具

    • Wireshark(网络抓包分析)
    • S7-PLCSIM Advanced(PLC仿真)
    • OPC UA Expert(OPC UA客户端测试工具)
相关推荐
~plus~2 小时前
C# 异步编程深度剖析:从 async/await 到 ValueTask
开发语言·c#
回忆2012初秋2 小时前
C# 大文件分片上传完整实现指南
开发语言·c#
jf加菲猫2 小时前
第12章 数据可视化
开发语言·c++·qt·ui
Lenyiin2 小时前
Python数据类型与运算符:深入理解Python世界的基石
java·开发语言·python
AI科技星2 小时前
张祥前统一场论中两个电荷定义的统一性解析
开发语言·线性代数·算法·数学建模·平面
代码地平线2 小时前
C语言实现堆与堆排序详解:从零手写到TopK算法及时间复杂度证明
c语言·开发语言·算法
西西学代码2 小时前
查找设备页面(amap_map)
开发语言·前端·javascript
迦南的迦 亚索的索2 小时前
PYTHON_DAY21_数据分析
开发语言·python·数据分析
枫叶丹42 小时前
【HarmonyOS 6.0】ArkWeb 手势获焦模式详解:告别点击获焦,迎接长按触发
开发语言·华为·harmonyos