PLC通讯实现-C#实现西门子PLC以太网通讯Sharp7(六)
背景
本人近十年的工作都与工业软件相关、其中工控系统开发过程中有一个必要环节就是跟各大厂商的PLC进行通讯,而对于从互联网行业跨入工业互联网行业的从业人员来说要实现各型号PLC通讯还是需要一个过程的,本人在此对主流型号PLC通讯实现进行总结以便大家参考。
抽象设计
首先我们要进行一下抽象设计,先设计一个抽象类(接口也可以,此处因为还有其他业务使用了抽象类)BaseEquip,对PLC的常规操作进行定义,即Open、Read、Write、Close,业务代码调用BaseEquip进行PLC的读写,然后在实现各型号的Equip类,对Open、Read、Write、Close进行实现,根据配置在业务代码中对BaseEquip进行实例化,这样后期更改PLC型号后,只需修改配置即可,不用修改业务代码。
西门子以太网通讯实现Sharp7
实现语言C#
抽象基类BaseEquip
public class BaseEquip
{
/// <summary>
/// 打开设备
/// </summary>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Open();
/// <summary>
/// 读取信息
/// </summary>
/// <param name="block">数据块</param>
/// <param name="start">起始地址</param>
/// <param name="len">长度</param>
/// <param name="buff">读取返回信息</param>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Read(string block, int start, int len, out object[] buff);
/// <summary>
/// 写入信息
/// </summary>
/// <param name="block">数据块</param>
/// <param name="start">起始地址</param>
/// <param name="buff">要写入的数据</param>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Write(int block, int start, object[] buff);
/// <summary>
/// 关闭设备
/// </summary>
public abstract void Close();
}
设备实现类Equip实现
using System;
using System.Runtime.InteropServices;
using System.Text;
using Mesnac.Equips;
using Mesnac.Equips.BaseInfo;
namespace Mesnac.Equip.Siemens.S7.Net
{
/// <summary>
/// 西门子S7系列以太网通讯类
/// </summary>
public class Equip : BaseEquip
{
#region 字段定义
private string _ip = "192.168.0.250"; //PLC IP Address
private int _rack = 0; //机架号
private int _slot = 0; //槽号,1200~1500设置为0,300系列设置为2
private bool _isOpen = false; //是否打开连接
private int _errorCount = 0; //记录读取失败次数
private int _openErrorCount = 0; //打开PLC失败的次数
private S7Client _client = new S7Client();
private int[] _area =
{
S7Consts.S7AreaPE,
S7Consts.S7AreaPA,
S7Consts.S7AreaMK,
S7Consts.S7AreaDB,
S7Consts.S7AreaCT,
S7Consts.S7AreaTM
};
private int[] _wordLen =
{
S7Consts.S7WLBit,
S7Consts.S7WLByte,
S7Consts.S7WLChar,
S7Consts.S7WLWord,
S7Consts.S7WLInt,
S7Consts.S7WLDWord,
S7Consts.S7WLDInt,
S7Consts.S7WLReal,
S7Consts.S7WLCounter,
S7Consts.S7WLTimer
};
#endregion
#region 属性定义
/// <summary>
/// IP
/// </summary>
private string IP
{
get
{
return this._ip;
}
}
/// <summary>
/// 机架号
/// </summary>
private int Rack
{
get
{
return this._rack;
}
}
/// <summary>
/// 插槽号
/// </summary>
private int Slot
{
get
{
return this._slot;
}
}
#endregion
private int Swap(int a)
{
return (a >> 8 & 0xFF) + (a << 8 & 0xFF00);
}
#region 实现BaseEquip成员
/// <summary>
/// 建立与PLC的连接
/// </summary>
/// <returns></returns>
public override bool Open()
{
lock (this)
{
if (this._isOpen == true)
{
return true;
}
this.State = false;
/
int result = this._client.ConnectTo(this.IP, this.Rack, this.Slot);
///
if (result != 0)
{
Console.WriteLine("PLC连接失败:" + this.GetErrInfo(result));
this.State = false;
if (this._openErrorCount > 1)
{
System.Threading.Thread.Sleep(10000); //2次连接不上暂停10秒
this._openErrorCount = 0;
}
this._openErrorCount++;
return this.State;
}
else
{
Console.WriteLine("PLC连接成功!");
this.State = true;
this._isOpen = true;
}
return this.State;
}
}
/// <summary>
/// PLC数据读取方法
/// </summary>
/// <param name="block">要读取的块号</param>
/// <param name="start">要读取的起始字</param>
/// <param name="len">要读取的长度</param>
/// <param name="buff"></param>
/// <returns></returns>
public override bool Read(string block, int start, int len, out object[] buff)
{
lock (this)
{
int sizeRead = 0;
int iblock = Convert.ToInt32(block);
buff = new object[len];
for (int i = 0; i < len; i++)
{
buff[i] = 0;
}
if (!this.Open())
{
return false;
}
byte[] _buff = new byte[len * 2];
int result = this._client.ReadArea(S7Consts.S7AreaDB, iblock, start * 2, len, S7Consts.S7WLInt, _buff, ref sizeRead);
if (result != 0)
{
Console.WriteLine(this.GetErrInfo(result) + "\t" + String.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.Now));
if (this._errorCount > 5)
{
this.Close(); //5次读取失败,关闭PLC,再次读取自动连接
}
this._errorCount++;
}
if (sizeRead != len * 2)
{
Console.WriteLine(String.Format("block={0}, start={1}, len={2} 读取的字节数与设置的字数不成2倍关系!", block, start, len));
}
else
{
for (int i = 0; i < len; i++)
{
byte[] curr = new byte[2];
//高低位对调
//curr[0] = _buff[i * 2];
//curr[1] = _buff[i * 2 + 1];
//buff[i] = this.Swap(BitConverter.ToInt16(curr, 0));
curr[1] = _buff[i * 2];
curr[0] = _buff[i * 2 + 1];
buff[i] = BitConverter.ToInt16(curr, 0);
}
//ICSharpCode.Core.LoggingService<Equip>.Warn("内部处理完毕" + "\t" + String.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.Now));
}
if (result != 0)
{
return false;
}
else
{
return true;
}
}
}
/// <summary>
/// PLC数据写入方法
/// </summary>
/// <param name="block">要写入的块号</param>
/// <param name="start">起始字</param>
/// <param name="buff">要写入PLC的数据</param>
/// <returns>写入成功返回true,失败返回false</returns>
public override bool Write(int block, int start, object[] buff)
{
lock (this)
{
if (!this.Open())
{
return false;
}
int iblock = Convert.ToInt32(block);
int sizeWrite = 0;
byte[] _buff = new byte[buff.Length * 2];
for (int i = 0; i < buff.Length; i++)
{
System.Int16 value = 0;
System.Int16.TryParse(buff[i].ToString(), out value);
byte[] curr = BitConverter.GetBytes(value);
//高低位对调
_buff[i * 2] = curr[1];
_buff[i * 2 + 1] = curr[0];
}
int result = this._client.WriteArea(S7Consts.S7AreaDB, iblock, start * 2, buff.Length, S7Consts.S7WLInt, _buff, ref sizeWrite);
if (result != 0)
{
Console.WriteLine(this.GetErrInfo(result) + "\t" + String.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.Now));
}
if (sizeWrite != buff.Length * 2)
{
Console.WriteLine(String.Format("block={0}, start={1}, len={2} 写入的字节数与设置的字数不成2倍关系!", block, start, buff.Length));
}
if (result != 0)
{
return false;
}
return true;
}
}
/// <summary>
/// 断开与PLC的连接
/// </summary>
public override void Close()
{
lock (this)
{
try
{
int result = this._client.Disconnect();
if (result != 0)
{
Console.WriteLine("PLC关闭失败:" + this.GetErrInfo(result));
}
else
{
Console.WriteLine("PLC已断开连接!");
this._errorCount = 0;
this.State = false;
this._isOpen = false;
}
}
catch (Exception ex)
{
Console.WriteLine("PLC关闭失败:" + ex.Message);
}
}
}
#endregion
#region 辅助方法
/// <summary>根据错误代码返回错误信息
/// 例如int errCode=ActiveConn(1); sring errInfo = GetErrInfo(err);
/// </summary>
/// <param name="errCode">错误码</param>
/// <returns>错误信息</returns>
public string GetErrInfo(int errCode)
{
if (errCode == 0)
return " Success(" + this._client.ExecutionTime.ToString() + " ms)";
else
return this._client.ErrorText(errCode);
}
#endregion
}
}
Sharp7类实现
/*=============================================================================|
| PROJECT Sharp7 1.0.0 |
|==============================================================================|
| Copyright (C) 2016 Davide Nardella |
| All rights reserved. |
|==============================================================================|
| Sharp7 is free software: you can redistribute it and/or modify |
| it under the terms of the Lesser GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| It means that you can distribute your commercial software which includes |
| Sharp7 without the requirement to distribute the source code of your |
| application and without the requirement that your application be itself |
| distributed under LGPL. |
| |
| Sharp7 is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| Lesser GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License and a |
| copy of Lesser GNU General Public License along with Sharp7. |
| If not, see http://www.gnu.org/licenses/ |
|=============================================================================*/
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
//------------------------------------------------------------------------------
// If you are compiling for UWP verify that WINDOWS_UWP or NETFX_CORE are
// defined into Project Properties->Build->Conditional compilation symbols
//------------------------------------------------------------------------------
#if WINDOWS_UWP || NETFX_CORE
using System.Threading.Tasks;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
#else // <-- Including MONO
using System.Net.Sockets;
#endif
namespace Mesnac.Equip.Siemens.S7
{
#region [Async Sockets UWP(W10,IoT,Phone)/Windows 8/Windows 8 Phone]
#if WINDOWS_UWP || NETFX_CORE
class MsgSocket
{
private DataReader Reader = null;
private DataWriter Writer = null;
private StreamSocket TCPSocket;
private bool _Connected;
private int _ReadTimeout = 2000;
private int _WriteTimeout = 2000;
private int _ConnectTimeout = 1000;
public static int LastError = 0;
private void CreateSocket()
{
TCPSocket = new StreamSocket();
TCPSocket.Control.NoDelay = true;
_Connected = false;
}
public MsgSocket()
{
}
public void Close()
{
if (Reader != null)
{
Reader.Dispose();
Reader = null;
}
if (Writer != null)
{
Writer.Dispose();
Writer = null;
}
if (TCPSocket != null)
{
TCPSocket.Dispose();
TCPSocket = null;
}
_Connected = false;
}
private async Task AsConnect(string Host, string port, CancellationTokenSource cts)
{
HostName ServerHost = new HostName(Host);
try
{
await TCPSocket.ConnectAsync(ServerHost, port).AsTask(cts.Token);
_Connected = true;
}
catch (TaskCanceledException)
{
LastError = S7Consts.errTCPConnectionTimeout;
}
catch
{
LastError = S7Consts.errTCPConnectionFailed; // Maybe unreachable peer
}
}
public int Connect(string Host, int Port)
{
LastError = 0;
if (!Connected)
{
CreateSocket();
CancellationTokenSource cts = new CancellationTokenSource();
try
{
try
{
cts.CancelAfter(_ConnectTimeout);
Task.WaitAny(Task.Run(async () => await AsConnect(Host, Port.ToString(), cts)));
}
catch
{
LastError = S7Consts.errTCPConnectionFailed;
}
}
finally
{
if (cts != null)
{
try
{
cts.Cancel();
cts.Dispose();
cts = null;
}
catch {
}
}
}
if (LastError == 0)
{
Reader = new DataReader(TCPSocket.InputStream);
Reader.InputStreamOptions = InputStreamOptions.Partial;
Writer = new DataWriter(TCPSocket.OutputStream);
_Connected = true;
}
else
Close();
}
return LastError;
}
private async Task AsReadBuffer(byte[] Buffer, int Size, CancellationTokenSource cts)
{
try
{
await Reader.LoadAsync((uint)Size).AsTask(cts.Token);
Reader.ReadBytes(Buffer);
}
catch
{
LastError = S7Consts.errTCPDataReceive;
}
}
public int Receive(byte[] Buffer, int Start, int Size)
{
byte[] InBuffer = new byte[Size];
CancellationTokenSource cts = new CancellationTokenSource();
LastError = 0;
try
{
try
{
cts.CancelAfter(_ReadTimeout);
Task.WaitAny(Task.Run(async () => await AsReadBuffer(InBuffer, Size, cts)));
}
catch
{
LastError = S7Consts.errTCPDataReceive;
}
}
finally
{
if (cts != null)
{
try
{
cts.Cancel();
cts.Dispose();
cts = null;
}
catch {
}
}
}
if (LastError == 0)
Array.Copy(InBuffer, 0, Buffer, Start, Size);
else
Close();
return LastError;
}
private async Task WriteBuffer(byte[] Buffer, CancellationTokenSource cts)
{
try
{
Writer.WriteBytes(Buffer);
await Writer.StoreAsync().AsTask(cts.Token);
}
catch
{
LastError = S7Consts.errTCPDataSend;
}
}
public int Send(byte[] Buffer, int Size)
{
byte[] OutBuffer = new byte[Size];
CancellationTokenSource cts = new CancellationTokenSource();
Array.Copy(Buffer, 0, OutBuffer, 0, Size);
LastError = 0;
try
{
try
{
cts.CancelAfter(_WriteTimeout);
Task.WaitAny(Task.Run(async () => await WriteBuffer(OutBuffer, cts)));
}
catch
{
LastError = S7Consts.errTCPDataSend;
}
}
finally
{
if (cts != null)
{
try
{
cts.Cancel();
cts.Dispose();
cts = null;
}
catch {
}
}
}
if (LastError != 0)
Close();
return LastError;
}
~MsgSocket()
{
Close();
}
public bool Connected
{
get
{
return (TCPSocket != null) && _Connected;
}
}
public int ReadTimeout
{
get
{
return _ReadTimeout;
}
set
{
_ReadTimeout = value;
}
}
public int WriteTimeout
{
get
{
return _WriteTimeout;
}
set
{
_WriteTimeout = value;
}
}
public int ConnectTimeout
{
get
{
return _ConnectTimeout;
}
set
{
_ConnectTimeout = value;
}
}
}
#endif
#endregion
#region [Sync Sockets Win32/Win64 Desktop Application]
#if !WINDOWS_UWP && !NETFX_CORE
class MsgSocket
{
private Socket TCPSocket;
private int _ReadTimeout = 2000;
private int _WriteTimeout = 2000;
private int _ConnectTimeout = 1000;
public int LastError = 0;
public MsgSocket()
{
}
~MsgSocket()
{
Close();
}
public void Close()
{
if (TCPSocket != null)
{
TCPSocket.Dispose();
TCPSocket = null;
}
}
private void CreateSocket()
{
TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
TCPSocket.NoDelay = true;
}
private void TCPPing(string Host, int Port)
{
// To Ping the PLC an Asynchronous socket is used rather then an ICMP packet.
// This allows the use also across Internet and Firewalls (obviously the port must be opened)
LastError = 0;
Socket PingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
IAsyncResult result = PingSocket.BeginConnect(Host, Port, null, null);
bool success = result.AsyncWaitHandle.WaitOne(_ConnectTimeout, true);
if (!success)
{
LastError = S7Consts.errTCPConnectionFailed;
}
}
catch
{
LastError = S7Consts.errTCPConnectionFailed;
};
PingSocket.Close();
}
public int Connect(string Host, int Port)
{
LastError = 0;
if (!Connected)
{
TCPPing(Host, Port);
if (LastError == 0)
try
{
CreateSocket();
TCPSocket.Connect(Host, Port);
}
catch
{
LastError = S7Consts.errTCPConnectionFailed;
}
}
return LastError;
}
private int WaitForData(int Size, int Timeout)
{
bool Expired = false;
int SizeAvail;
int Elapsed = Environment.TickCount;
LastError = 0;
try
{
SizeAvail = TCPSocket.Available;
while ((SizeAvail < Size) && (!Expired))
{
Thread.Sleep(2);
SizeAvail = TCPSocket.Available;
Expired = Environment.TickCount - Elapsed > Timeout;
// If timeout we clean the buffer
if (Expired && (SizeAvail > 0))
try
{
byte[] Flush = new byte[SizeAvail];
TCPSocket.Receive(Flush, 0, SizeAvail, SocketFlags.None);
}
catch {
}
}
}
catch
{
LastError = S7Consts.errTCPDataReceive;
}
if (Expired)
{
LastError = S7Consts.errTCPDataReceive;
}
return LastError;
}
public int Receive(byte[] Buffer, int Start, int Size)
{
int BytesRead = 0;
LastError = WaitForData(Size, _ReadTimeout);
if (LastError == 0)
{
try
{
BytesRead = TCPSocket.Receive(Buffer, Start, Size, SocketFlags.None);
}
catch
{
LastError = S7Consts.errTCPDataReceive;
}
if (BytesRead == 0) // Connection Reset by the peer
{
LastError = S7Consts.errTCPDataReceive;
Close();
}
}
return LastError;
}
public int Send(byte[] Buffer, int Size)
{
LastError = 0;
try
{
int BytesSent = TCPSocket.Send(Buffer, Size, SocketFlags.None);
}
catch
{
LastError = S7Consts.errTCPDataSend;
Close();
}
return LastError;
}
public bool Connected
{
get
{
return (TCPSocket != null) && (TCPSocket.Connected);
}
}
public int ReadTimeout
{
get
{
return _ReadTimeout;
}
set
{
_ReadTimeout = value;
}
}
public int WriteTimeout
{
get
{
return _WriteTimeout;
}
set
{
_WriteTimeout = value;
}
}
public int ConnectTimeout
{
get
{
return _ConnectTimeout;
}
set
{
_ConnectTimeout = value;
}
}
}
#endif
#endregion
public static class S7Consts
{
#region [Exported Consts]
// Error codes
//------------------------------------------------------------------------------
// ERRORS
//------------------------------------------------------------------------------
public const int errTCPSocketCreation = 0x00000001;
public const int errTCPConnectionTimeout = 0x00000002;
public const int errTCPConnectionFailed = 0x00000003;
public const int errTCPReceiveTimeout = 0x00000004;
public const int errTCPDataReceive = 0x00000005;
public const int errTCPSendTimeout = 0x00000006;
public const int errTCPDataSend = 0x00000007;
public const int errTCPConnectionReset = 0x00000008;
public const int errTCPNotConnected = 0x00000009;
public const int errTCPUnreachableHost = 0x00002751;
public const int errIsoConnect = 0x00010000; // Connection error
public const int errIsoInvalidPDU = 0x00030000; // Bad format
public const int errIsoInvalidDataSize = 0x00040000; // Bad Datasize passed to send/recv : buffer is invalid
public const int errCliNegotiatingPDU = 0x00100000;
public const int errCliInvalidParams = 0x00200000;
public const int errCliJobPending = 0x00300000;
public const int errCliTooManyItems = 0x00400000;
public const int errCliInvalidWordLen = 0x00500000;
public const int errCliPartialDataWritten = 0x00600000;
public const int errCliSizeOverPDU = 0x00700000;
public const int errCliInvalidPlcAnswer = 0x00800000;
public const int errCliAddressOutOfRange = 0x00900000;
public const int errCliInvalidTransportSize = 0x00A00000;
public const int errCliWriteDataSizeMismatch = 0x00B00000;
public const int errCliItemNotAvailable = 0x00C00000;
public const int errCliInvalidValue = 0x00D00000;
public const int errCliCannotStartPLC = 0x00E00000;
public const int errCliAlreadyRun = 0x00F00000;
public const int errCliCannotStopPLC = 0x01000000;
public const int errCliCannotCopyRamToRom = 0x01100000;
public const int errCliCannotCompress = 0x01200000;
public const int errCliAlreadyStop = 0x01300000;
public const int errCliFunNotAvailable = 0x01400000;
public const int errCliUploadSequenceFailed = 0x01500000;
public const int errCliInvalidDataSizeRecvd = 0x01600000;
public const int errCliInvalidBlockType = 0x01700000;
public const int errCliInvalidBlockNumber = 0x01800000;
public const int errCliInvalidBlockSize = 0x01900000;
public const int errCliNeedPassword = 0x01D00000;
public const int errCliInvalidPassword = 0x01E00000;
public const int errCliNoPasswordToSetOrClear = 0x01F00000;
public const int errCliJobTimeout = 0x02000000;
public const int errCliPartialDataRead = 0x02100000;
public const int errCliBufferTooSmall = 0x02200000;
public const int errCliFunctionRefused = 0x02300000;
public const int errCliDestroying = 0x02400000;
public const int errCliInvalidParamNumber = 0x02500000;
public const int errCliCannotChangeParam = 0x02600000;
public const int errCliFunctionNotImplemented = 0x02700000;
//------------------------------------------------------------------------------
// PARAMS LIST FOR COMPATIBILITY WITH Snap7.net.cs
//------------------------------------------------------------------------------
public const Int32 p_u16_LocalPort = 1; // Not applicable here
public const Int32 p_u16_RemotePort = 2;
public const Int32 p_i32_PingTimeout = 3;
public const Int32 p_i32_SendTimeout = 4;
public const Int32 p_i32_RecvTimeout = 5;
public const Int32 p_i32_WorkInterval = 6; // Not applicable here
public const Int32 p_u16_SrcRef = 7; // Not applicable here
public const Int32 p_u16_DstRef = 8; // Not applicable here
public const Int32 p_u16_SrcTSap = 9; // Not applicable here
public const Int32 p_i32_PDURequest = 10;
public const Int32 p_i32_MaxClients = 11; // Not applicable here
public const Int32 p_i32_BSendTimeout = 12; // Not applicable here
public const Int32 p_i32_BRecvTimeout = 13; // Not applicable here
public const Int32 p_u32_RecoveryTime = 14; // Not applicable here
public const Int32 p_u32_KeepAliveTime = 15; // Not applicable here
// Area ID
public const byte S7AreaPE = 0x81;
public const byte S7AreaPA = 0x82;
public const byte S7AreaMK = 0x83;
public const byte S7AreaDB = 0x84;
public const byte S7AreaCT = 0x1C;
public const byte S7AreaTM = 0x1D;
// Word Length
public const int S7WLBit = 0x01;
public const int S7WLByte = 0x02;
public const int S7WLChar = 0x03;
public const int S7WLWord = 0x04;
public const int S7WLInt = 0x05;
public const int S7WLDWord = 0x06;
public const int S7WLDInt = 0x07;
public const int S7WLReal = 0x08;
public const int S7WLCounter = 0x1C;
public const int S7WLTimer = 0x1D;
// PLC Status
public const int S7CpuStatusUnknown = 0x00;
public const int S7CpuStatusRun = 0x08;
public const int S7CpuStatusStop = 0x04;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct S7Tag
{
public Int32 Area;
public Int32 DBNumber;
public Int32 Start;
public Int32 Elements;
public Int32 WordLen;
}
#endregion
}
public static class S7
{
#region [Help Functions]
private static Int64 bias = 621355968000000000; // "decimicros" between 0001-01-01 00:00:00 and 1970-01-01 00:00:00
private static int BCDtoByte(byte B)
{
return ((B >> 4) * 10) + (B & 0x0F);
}
private static byte ByteToBCD(int Value)
{
return (byte)(((Value / 10) << 4) | (Value % 10));
}
private static byte[] CopyFrom(byte[] Buffer, int Pos, int Size)
{
byte[] Result=new byte[Size];
Array.Copy(Buffer, Pos, Result, 0, Size);
return Result;
}
public static int DataSizeByte(int WordLength)
{
switch (WordLength)