一、项目结构

SiemensS7Comm/
├── SiemensS7Comm.csproj
├── S7Client.cs              主入口(给用户用的)
├── S7Protocol.cs            底层协议编解码
├── S7Types.cs                PLC 数据类型 / 枚举 / 地址
├── IsoTcpTransport.cs       ISO-on-TCP 传输层(TPKT + COTP)
├── S7Area.cs                数据区定义
└── S7Exception.cs          自定义异常

二、核心代码

1. 枚举 & 数据类型 (S7Area.cs / S7Types.cs)

using System;

namespace SiemensS7Comm
{
    /// <summary>
    /// PLC 数据区
    /// </summary>
    public enum S7Area
    {
        PE  = 0x81,   // I  过程输入(I区)
        PA  = 0x82,   // Q  过程输出(Q区)
        MK  = 0x83,   // M  标志位(M区)
        DB  = 0x84,   // DB 数据块
        CT  = 0x1C,   // Counter(计数器 C区)
        TM  = 0x1D,   // Timer(定时器 T区)
    }

    /// <summary>
    /// PLC CPU类型(影响机架/插槽号)
    /// </summary>
    public enum S7CpuType
    {
        S7200     = 0,
        S7300     = 1,
        S7400     = 3,
        S71200    = 10,
        S71500    = 11,
        S7200_SMART = 20,
    }

    /// <summary>
    /// S7 读/写项描述
    /// </summary>
    public class S7Item
    {
        public S7Area Area { get; set; }
        public int DbNumber { get; set; }   // DB号(非DB区填0)
        public int ByteOffset { get; set; } // 起始字节
        public int BitOffset { get; set; }  // 位偏移 0~7(仅当按位读写时用)
        public int Length { get; set; }     // 字节数

        public S7Item() { }
        public S7Item(S7Area area, int dbNumber, int byteOffset, int length)
        {
            Area = area;
            DbNumber = dbNumber;
            ByteOffset = byteOffset;
            Length = length;
        }
    }

    /// <summary>
    /// PLC 连接参数
    /// </summary>
    public class S7Config
    {
        public string IpAddress { get; set; } = "192.168.0.1";
        public int Port { get; set; } = 102;
        public S7CpuType CpuType { get; set; } = S7CpuType.S7200_SMART;  // 大多数情况 S7-200SMART 用这个
        public int Rack { get; set; } = 0;
        public int Slot { get; set; } = 1;
        public int LocalTSAP  { get; set; } = 0x0100;   // 本地 TSAP(自动计算)
        public int RemoteTSAP { get; set; } = 0x0201;  // 远端 TSAP(自动计算)
        public int TimeoutMs { get; set; } = 3000;

        /// <summary>
        /// 根据 CPU 类型自动修正 Rack/Slot/RemoteTSAP
        /// </summary>
        public void AutoSetByCpu(S7CpuType cpu)
        {
            CpuType = cpu;
            switch (cpu)
            {
                case S7CpuType.S7300:
                    Rack = 0; Slot = 2;
                    RemoteTSAP = (Rack << 8) | Slot;
                    break;
                case S7CpuType.S7400:
                    Rack = 0; Slot = 3;
                    RemoteTSAP = (Rack << 8) | Slot;
                    break;
                case S7CpuType.S71200:
                case S7CpuType.S71500:
                    Rack = 0; Slot = 1;
                    RemoteTSAP = (Rack << 8) | Slot;
                    break;
                case S7CpuType.S7200_SMART:
                    Rack = 0; Slot = 1;
                    RemoteTSAP = 0x0201;  // S7-200 Smart 固定
                    break;
            }
        }
    }
}

2. ISO-on-TCP 传输层 (IsoTcpTransport.cs)

using System;
using System.Net.Sockets;
using System.Threading;

namespace SiemensS7Comm
{
    /// <summary>
    /// ISO-on-TCP (RFC 1006) 传输层
    /// 封装 TPKT + COTP
    /// </summary>
    internal class IsoTcpTransport : IDisposable
    {
        private Socket _socket;
        private byte[] _recvBuf = new byte[65536];
        private int _recvLen = 0;

        public bool IsConnected => _socket != null && _socket.Connected;

        /* ---------- connect ---------- */
        public void Connect(string ip, int port, int remoteTsap, int localTsap, int timeoutMs)
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.SendTimeout = timeoutMs;
            _socket.ReceiveTimeout = timeoutMs;

            _socket.Connect(ip, port);

            // ① COTP Connect Request
            SendCOTPConnectRequest(localTsap, remoteTsap);

            // ② 等待 COTP Connect Confirm
            byte[] resp = RecvPDU();
            if (resp.Length < 2 || resp[5] != 0xD0)  // 0xD0 = CC
                throw new S7Exception("COTP Connect 失败");

            // ③ S7 Setup Communication
            SendS7SetupComm();
            resp = RecvPDU();
            CheckS7Header(resp, "S7 Setup");
        }

        /* ---------- 读/写原始PDU ---------- */
        public byte[] Transact(byte[] pdu)
        {
            SendPDU(pdu);
            return RecvPDU();
        }

        /* ============================================================
           TPKT
           ============================================================ */

        // TPKT头固定 4字节:Ver=3, Reserved=0, Len(2字节)
        private void SendPDU(byte[] data)
        {
            byte[] tpkt = new byte[data.Length + 4];
            tpkt[0] = 3;               // Version
            tpkt[1] = 0;               // Reserved
            tpkt[2] = (byte)((data.Length + 4) >> 8);
            tpkt[3] = (byte)((data.Length + 4) & 0xFF);
            Buffer.BlockCopy(data, 0, tpkt, 4, data.Length);
            _socket.Send(tpkt);
        }

        private byte[] RecvPDU()
        {
            // 收 TPKT 头
            int got = 0;
            while (got < 4)
                got += _socket.Receive(_recvBuf, got, 4 - got, SocketFlags.None);

            if (_recvBuf[0] != 3)
                throw new S7Exception($"非法TPKT版本: {_recvBuf[0]}");

            int tpktLen = (_recvBuf[2] << 8) | _recvBuf[3];
            int payloadLen = tpktLen - 4;

            got = 0;
            while (got < payloadLen)
                got += _socket.Receive(_recvBuf, 4 + got, payloadLen - got, SocketFlags.None);

            byte[] result = new byte[tpktLen];
            Buffer.BlockCopy(_recvBuf, 0, result, 0, tpktLen);
            return result;
        }

        /* ============================================================
           COTP 层
           ============================================================ */

        /// <summary>
        /// COTP Connect Request (DT 类 / CR)
        /// </summary>
        private void SendCOTPConnectRequest(int localTsap, int remoteTsap)
        {
            // CR = 0xE0 长度可变,这里给一个最常用的最小CR包
            byte[] cr = new byte[22];

            // --- COTP CR ---
            cr[0] = 0xE0;                // CR
            cr[1] = 0x00;                 // CDT
            cr[2] = 0x00; cr[3] = 0x18;  // Destination-Reference (2B)
            cr[4] = (byte)((localTsap >> 8) & 0xFF);
            cr[5] = (byte)(localTsap & 0xFF);
            cr[6] = 0xC1; cr[7] = 0x01; cr[8] = (byte)((remoteTsap >> 8) & 0xFF);
            cr[9] = (byte)(remoteTsap & 0xFF);
            cr[10] = 0xC2; cr[11] = 0x01; cr[12] = (byte)((remoteTsap >> 8) & 0xFF);
            cr[13] = (byte)(remoteTsap & 0xFF);
            cr[14] = 0xC0; cr[15] = 0x01; cr[16] = 0x09; // PClass
            cr[17] = 0xC0; cr[18] = 0x02; cr[19] = 0x01; // Parameter
            cr[20] = 0xC0; cr[21] = 0x03; cr[22] = 0x00;

            // 包装成TPKT
            SendPDU(cr);
        }

        /* ============================================================
           S7 Setup Communication
           ============================================================ */

        private void SendS7SetupComm()
        {
            /*
              S7 Header (10B) + Param (8B)
              固定: PDU-Type=0xF0, Job=0x32, ...
            */
            byte[] s7 = new byte[25];

            // S7 Param part
            // Header
            s7[0] = 0x32; // Protocol ID
            s7[1] = 0x01; // Job = Setup
            s7[2] = 0x00; s7[3] = 0x00; // Redundancy
            s7[4] = 0x00; s7[5] = 0x00; // PDU-Ref
            s7[6] = 0x00; s7[7] = 0x08; // Param-Length
            s7[8] = 0x00; s7[9] = 0x00; // Data-Length

            // Setup param
            s7[10] = 0xF0; // Fun: Setup
            s7[11] = 0x00; // Unknown
            s7[12] = 0x00; s7[13] = 0x01; // Max AmQ-Calling
            s7[14] = 0x00; s7[15] = 0x01; // Max AmQ-Called
            s7[16] = 0x03; s7[17] = 0xC0; // PDU-Length = 960
            s7[18] = 0x00; s7[19] = 0x00; // unknown

            // Length field for COTP DT (header=2: LI=2 + TPDU type 0xF0)
            byte[] dt = new byte[s7.Length + 1];
            dt[0] = (byte)(s7.Length); // LI = rest length
            dt[1] = 0xF0;              // DT - Data TPDU
            Buffer.BlockCopy(s7, 0, dt, 2, s7.Length);

            SendPDU(dt);
        }

        private void CheckS7Header(byte[] resp, string tag)
        {
            // resp 是完整 TPKT帧,跳过 TPKT(4B) + COTP-DT(2B),看 S7 头
            if (resp.Length < 12) throw new S7Exception($"{tag}: 响应过短");
            // 跳过到 S7: resp[4] = LI, resp[5]=DT
            int off = 4 + 2; // S7 起始
            if (resp[off] != 0x32)
                throw new S7Exception($"{tag}: 非S7响应 0x{resp[off]:X2}");
        }

        public void Disconnect()
        {
            try { _socket?.Shutdown(SocketShutdown.Both); } catch { }
            try { _socket?.Close(); } catch { }
            _socket = null;
        }

        public void Dispose() => Disconnect();
    }
}

3. S7 协议编解码 (S7Protocol.cs)

using System;
using System.Text;

namespace SiemensS7Comm
{
    /// <summary>
    /// S7 PDU 构造 / 解析
    /// </summary>
    internal static class S7Protocol
    {
        // S7 Function Codes
        public const byte FUNC_READ  = 0x04;
        public const byte FUNC_WRITE = 0x05;

        // ==================== 构造 Read Request ====================
        public static byte[] BuildReadRequest(S7Item[] items)
        {
            int itemCount = items.Length;
            int paramLen = 12 * itemCount;
            int headerLen = 10 + paramLen;

            byte[] pdu = new byte[4 + 1 + headerLen]; // TPKT空壳先留着

            int pos = 0;

            // --- COTP-DT ---
            pdu[pos++] = (byte)(1 + headerLen); // LI
            pdu[pos++] = 0xF0;                    // DT

            // --- S7 Header (10B) ---
            pdu[pos++] = 0x32;         // Protocol
            pdu[pos++] = FUNC_READ;     // Job = Read
            pdu[pos++] = 0x00; pdu[pos++] = 0x00; // Redundancy
            pdu[pos++] = 0x00; pdu[pos++] = 0x00; // PDU-Ref
            pdu[pos++] = (byte)(paramLen >> 8); pdu[pos++] = (byte)(paramLen & 0xFF); // ParamLen
            pdu[pos++] = 0x00; pdu[pos++] = 0x00; // DataLen

            // --- Param: Item Count + Items ---
            pdu[pos++] = 0x01; // ??? reserved
            pdu[pos++] = (byte)itemCount; // Number of items

            foreach (var item in items)
            {
                // 8Byte per item
                pdu[pos++] = 0x12; // VariableSpec
                pdu[pos++] = 0x0A; // Length of following
                pdu[pos++] = 0x10; // Syntax = S7ANY
                pdu[pos++] = (byte)item.Area;
                // Area number (DB number big-endian)
                pdu[pos++] = (byte)((item.DbNumber >> 8) & 0xFF);
                pdu[pos++] = (byte)(item.DbNumber & 0xFF);
                // Byte address (3 bytes, bit24..bit0)
                int addr3 = (item.ByteOffset << 3) | item.BitOffset;
                pdu[pos++] = (byte)((addr3 >> 16) & 0xFF);
                pdu[pos++] = (byte)((addr3 >> 8) & 0xFF);
                pdu[pos++] = (byte)(addr3 & 0xFF);
                // Length in bits(!) / 8
                int bitLen = item.Length * 8;
                pdu[pos++] = (byte)((bitLen >> 8) & 0xFF);
                pdu[pos++] = (byte)(bitLen & 0xFF);
            }

            // 重新包装 TPKT
            return WrapTPKT(pdu, 4 + 1 + headerLen);
        }

        // ==================== 构造 Write Request ====================
        public static byte[] BuildWriteRequest(S7Item item, byte[] values)
        {
            int paramLen = 12;
            int dataHeaderLen = 4 + 1 + item.Length; // Return code(1) + Datalen(2) + Data...
            int totalLen = 10 + paramLen + dataHeaderLen;

            byte[] pdu = new byte[4 + 1 + totalLen];
            int pos = 0;

            // COTP-DT
            pdu[pos++] = (byte)(1 + totalLen);
            pdu[pos++] = 0xF0;

            // S7 Header
            pdu[pos++] = 0x32;
            pdu[pos++] = FUNC_WRITE;
            pdu[pos++] = 0x00; pdu[pos++] = 0x00;
            pdu[pos++] = 0x00; pdu[pos++] = 0x00;
            pdu[pos++] = (byte)(paramLen >> 8); pdu[pos++] = (byte)(paramLen & 0xFF);
            pdu[pos++] = (byte)((dataHeaderLen) >> 8); pdu[pos++] = (byte)((dataHeaderLen) & 0xFF);

            // Param Item
            pdu[pos++] = 0x01;
            pdu[pos++] = 0x01; // 1 item

            pdu[pos++] = 0x12;
            pdu[pos++] = 0x0A;
            pdu[pos++] = 0x10;
            pdu[pos++] = (byte)item.Area;
            pdu[pos++] = (byte)((item.DbNumber >> 8) & 0xFF);
            pdu[pos++] = (byte)(item.DbNumber & 0xFF);
            int addr3 = (item.ByteOffset << 3) | item.BitOffset;
            pdu[pos++] = (byte)((addr3 >> 16) & 0xFF);
            pdu[pos++] = (byte)((addr3 >> 8) & 0xFF);
            pdu[pos++] = (byte)(addr3 & 0xFF);
            int bitLen = item.Length * 8;
            pdu[pos++] = (byte)((bitLen >> 8) & 0xFF);
            pdu[pos++] = (byte)(bitLen & 0xFF);

            // ---- Data Section ----
            // Item header
            pdu[pos++] = 0x00; // Return code = OK placeholder
            pdu[pos++] = 0x00; // Transport size (written later)
            if (item.Area == S7Area.PE || item.Area == S7Area.PA)
                pdu[pos - 1] = 0x03; // BIT
            else
                pdu[pos - 1] = 0x04; // BYTE/WORD/DWORD/REAL → 0x04=BYTE

            pdu[pos++] = (byte)((item.Length >> 8) & 0xFF);
            pdu[pos++] = (byte)(item.Length & 0xFF);

            // Copy actual value bytes
            Buffer.BlockCopy(values, 0, pdu, pos, values.Length);

            return WrapTPKT(pdu, 4 + 1 + totalLen);
        }

        // ==================== 解析 Read Response ====================
        public static byte[][] ParseReadResponse(byte[] tpktFrame, int itemCount)
        {
            // Skip TPKT(4) + COTP-DT(2) = 6
            int baseOff = 6;

            if (tpktFrame[baseOff] != 0x32 || tpktFrame[baseOff + 1] != FUNC_READ)
                throw new S7Exception("非Read响应");

            // 数据区起始:S7头(10) + 2(itemCountHeader) + 每个item的返回头(4B+data)
            int off = baseOff + 10 + 2;

            byte[][] results = new byte[itemCount][];

            for (int i = 0; i < itemCount; i++)
            {
                byte retCode = tpktFrame[off + 0];
                byte transportSize = tpktFrame[off + 1];
                int dataLen = (tpktFrame[off + 2] << 8) | tpktFrame[off + 3];

                off += 4; // skip return header

                byte[] data = new byte[dataLen];
                Buffer.BlockCopy(tpktFrame, off, data, 0, dataLen);
                results[i] = data;
                off += dataLen;
            }

            return results;
        }

        public static void CheckWriteResponse(byte[] tpktFrame)
        {
            int baseOff = 6;
            if (tpktFrame[baseOff + 1] != FUNC_WRITE)
                throw new S7Exception("非Write响应");

            // 跳过 S7头(10) + reserved(2) + item(1) + return(1)
            byte retCode = tpktFrame[baseOff + 10 + 2 + 1];
            if (retCode != 0xFF)
                throw new S7Exception($"PLC拒绝写入: 返回码 0x{retCode:X2}");
        }

        // ==================== 工具 ====================
        private static byte[] WrapTPKT(byte[] payload, int len)
        {
            byte[] tpkt = new byte[len + 4];
            tpkt[0] = 3;
            tpkt[1] = 0;
            tpkt[2] = (byte)((len + 4) >> 8);
            tpkt[3] = (byte)((len + 4) & 0xFF);
            Buffer.BlockCopy(payload, 0, tpkt, 4, len);
            return tpkt;
        }

        // ===== BigEndian 数据转换 =====
        public static bool GetBool(byte[] buf, int byteOff, int bitOff)
            => ((buf[byteOff] >> bitOff) & 0x01) != 0;

        public static short GetInt(byte[] buf, int off)
            => (short)((buf[off] << 8) | buf[off + 1]);

        public static ushort GetUInt(byte[] buf, int off)
            => (ushort)((buf[off] << 8) | buf[off + 1]);

        public static int GetDInt(byte[] buf, int off)
            => (buf[off] << 24) | (buf[off + 1] << 16) | (buf[off + 2] << 8) | buf[off + 3];

        public static uint GetUDInt(byte[] buf, int off)
            => (uint)((buf[off] << 24) | (buf[off + 1] << 16) | (buf[off + 2] << 8) | buf[off + 3]);

        public static float GetReal(byte[] buf, int off)
        {
            byte[] be = new byte[4] { buf[off], buf[off + 1], buf[off + 2], buf[off + 3] };
            if (BitConverter.IsLittleEndian) Array.Reverse(be);
            return BitConverter.ToSingle(be, 0);
        }

        public static double GetDWordToDouble(byte[] buf, int off)
        {
            uint val = GetUDInt(buf, off);
            return val; // 如需真正的DWord→Double转换需按IEEE754展开,这里只返回raw
        }

        public static string GetString(byte[] buf, int off, int maxLen)
        {
            int len = Math.Min(buf[off], maxLen - 1);
            return Encoding.ASCII.GetString(buf, off + 1, len);
        }

        public static void SetBool(byte[] buf, int byteOff, int bitOff, bool v)
        {
            if (v) buf[byteOff] |= (byte)(1 << bitOff);
            else   buf[byteOff] &= (byte)~(1 << bitOff);
        }

        public static void SetInt(byte[] buf, int off, short v)
        {
            buf[off]     = (byte)((v >> 8) & 0xFF);
            buf[off + 1] = (byte)(v & 0xFF);
        }

        public static void SetUInt(byte[] buf, int off, ushort v)
        {
            buf[off]     = (byte)((v >> 8) & 0xFF);
            buf[off + 1] = (byte)(v & 0xFF);
        }

        public static void SetDInt(byte[] buf, int off, int v)
        {
            buf[off]     = (byte)((v >> 24) & 0xFF);
            buf[off + 1] = (byte)((v >> 16) & 0xFF);
            buf[off + 2] = (byte)((v >> 8) & 0xFF);
            buf[off + 3] = (byte)(v & 0xFF);
        }

        public static void SetReal(byte[] buf, int off, float v)
        {
            byte[] raw = BitConverter.GetBytes(v);
            if (BitConverter.IsLittleEndian) Array.Reverse(raw);
            Buffer.BlockCopy(raw, 0, buf, off, 4);
        }
    }
}

4. 主客户端 API (S7Client.cs) ← ★ 你只用这一个类就够了

using System;

namespace SiemensS7Comm
{
    public class S7Client : IDisposable
    {
        private IsoTcpTransport _transport;
        private S7Config _cfg;

        public S7Config Config => _cfg;
        public bool Connected => _transport?.IsConnected ?? false;

        public S7Client() { _cfg = new S7Config(); }

        public S7Client(S7Config cfg) { _cfg = cfg; }

        // ===================== 连接 / 断开 =====================
        public void Connect()
        {
            if (_transport != null) Disconnect();

            _transport = new IsoTcpTransport();
            _transport.Connect(
                _cfg.IpAddress,
                _cfg.Port,
                _cfg.RemoteTSAP,
                _cfg.LocalTSAP,
                _cfg.TimeoutMs
            );
        }

        public void Disconnect()
        {
            _transport?.Disconnect();
            _transport = null;
        }

        // ===================== 单区读取 =====================
        public byte[] ReadRaw(S7Area area, int dbNum, int byteOff, int byteLen)
        {
            var req = S7Protocol.BuildReadRequest(new[]
            {
                new S7Item(area, dbNum, byteOff, byteLen)
            });

            byte[] resp = _transport.Transact(req);
            var datas = S7Protocol.ParseReadResponse(resp, 1);
            return datas[0];
        }

        // ===================== 多区批量读取 =====================
        public byte[][] ReadMulti(params S7Item[] items)
        {
            var req = S7Protocol.BuildReadRequest(items);
            byte[] resp = _transport.Transact(req);
            return S7Protocol.ParseReadResponse(resp, items.Length);
        }

        // ===================== 单区写入 =====================
        public void WriteRaw(S7Area area, int dbNum, int byteOff, byte[] data)
        {
            var req = S7Protocol.BuildWriteRequest(
                new S7Item(area, dbNum, byteOff, data.Length),
                data
            );

            byte[] resp = _transport.Transact(req);
            S7Protocol.CheckWriteResponse(resp);
        }

        // ==========================================================
        //  快捷读写(强类型 API —— 最常用)
        // ==========================================================

        // ---- Bool (I/Q/M/DBX) ----
        public bool ReadBool(S7Area area, int dbNum, int byteOff, int bitOff)
        {
            byte[] b = ReadRaw(area, dbNum, byteOff, 1);
            return S7Protocol.GetBool(b, 0, bitOff);
        }

        public void WriteBool(S7Area area, int dbNum, int byteOff, int bitOff, bool v)
        {
            byte[] b = ReadRaw(area, dbNum, byteOff, 1); // 先读保持其它位
            S7Protocol.SetBool(b, 0, bitOff, v);
            WriteRaw(area, dbNum, byteOff, b);
        }

        // ---- Int (MW / DBW) ----
        public short ReadInt(S7Area area, int dbNum, int byteOff)
        {
            byte[] b = ReadRaw(area, dbNum, byteOff, 2);
            return S7Protocol.GetInt(b, 0);
        }

        public void WriteInt(S7Area area, int dbNum, int byteOff, short v)
        {
            byte[] b = new byte[2];
            S7Protocol.SetInt(b, 0, v);
            WriteRaw(area, dbNum, byteOff, b);
        }

        // ---- UInt ----
        public ushort ReadUInt(S7Area area, int dbNum, int byteOff)
        {
            byte[] b = ReadRaw(area, dbNum, byteOff, 2);
            return S7Protocol.GetUInt(b, 0);
        }

        public void WriteUInt(S7Area area, int dbNum, int byteOff, ushort v)
        {
            byte[] b = new byte[2];
            S7Protocol.SetUInt(b, 0, v);
            WriteRaw(area, dbNum, byteOff, b);
        }

        // ---- DInt ---
        public int ReadDInt(S7Area area, int dbNum, int byteOff)
        {
            byte[] b = ReadRaw(area, dbNum, byteOff, 4);
            return S7Protocol.GetDInt(b, 0);
        }

        public void WriteDInt(S7Area area, int dbNum, int byteOff, int v)
        {
            byte[] b = new byte[4];
            S7Protocol.SetDInt(b, 0, v);
            WriteRaw(area, dbNum, byteOff, b);
        }

        // ---- Real (Float) ----
        public float ReadReal(S7Area area, int dbNum, int byteOff)
        {
            byte[] b = ReadRaw(area, dbNum, byteOff, 4);
            return S7Protocol.GetReal(b, 0);
        }

        public void WriteReal(S7Area area, int dbNum, int byteOff, float v)
        {
            byte[] b = new byte[4];
            S7Protocol.SetReal(b, 0, v);
            WriteRaw(area, dbNum, byteOff, b);
        }

        // ---- String (S7标准STRING:开头两个字节=maxlen/curlen) ----
        public string ReadString(S7Area area, int dbNum, int byteOff, int maxLen = 254)
        {
            // 读 maxLen+2 字节
            byte[] b = ReadRaw(area, dbNum, byteOff, maxLen + 2);
            return S7Protocol.GetString(b, 0, maxLen);
        }

        public void Dispose() => Disconnect();
    }
}

5. 自定义异常 (S7Exception.cs)

using System;

namespace SiemensS7Comm
{
    public class S7Exception : Exception
    {
        public S7Exception(string msg) : base(msg) { }
        public S7Exception(string msg, Exception inner) : base(msg, inner) { }
    }
}

三、使用示例

using System;
using SiemensS7Comm;

class Program
{
    static void Main()
    {
        var plc = new S7Client(new S7Config
        {
            IpAddress = "192.168.2.10",
            Port = 102
        });

        plc.Config.AutoSetByCpu(S7CpuType.S7200_SMART); // 200SMART
        // 如果是 S7-300:AutoSetByCpu(S7CpuType.S7300);
        // 如果是 S7-1200/1500:AutoSetByCpu(S7CpuType.S71200);

        plc.Connect();
        Console.WriteLine("PLC 已连接!");

        // ===== 读 DB1.DBD0(Real) =====
        float temp = plc.ReadReal(S7Area.DB, 1, 0);
        Console.WriteLine($"DB1.DBD0 = {temp}");

        // ===== 写 DB1.DBW4(Int) =====
        plc.WriteInt(S7Area.DB, 1, 4, 12345);

        // ===== 读 M区 Bool =====
        bool sensor1 = plc.ReadBool(S7Area.MK, 0, 10, 0); // M10.0
        Console.WriteLine($"M10.0 = {sensor1}");

        // ===== 写 Q区线圈(如果PLC允许输出写) =====
        // plc.WriteBool(S7Area.PA, 0, 0, 0, true);   // Q0.0 = TRUE

        // ===== 批量读(效率更高) =====
        var datas = plc.ReadMulti(
            new S7Item(S7Area.DB, 1, 0, 4),   // DB1.DBB0 ~ DBB3
            new S7Item(S7Area.MK, 0, 100, 2)  // MB100~MB101
        );

        short v1 = S7Protocol.GetInt(datas[0], 0);
        ushort m100 = S7Protocol.GetUInt(datas[1], 0);
        Console.WriteLine($"DB1.DBW0={v1}, MW100={m100}");

        plc.Disconnect();
    }
}

参考代码 西门子PLC以太网通讯组件 www.youwenfan.com/contentcsv/111957.html

四、支持的 PLC & 注意事项

PLC Rack Slot 备注
S7-200 SMART 0 1 最常用,端口102直接通
S7-300 (CPU315+) 0 2 PUT/GET 需在硬件配置里启用
S7-400 0 3 同上
S7-1200 / 1500 0 1 必须在 TIA Portal 中勾选「允许来自远程伙伴的PUT/GET通信」

如果 S7-1200/1500 连接报返回码异常,99% 是因为没开 PUT/GET:
设备组态 → CPU 属性 → 保护 & 安全 → 连接机制 → ☑ 允许PUT/GET

更多推荐