mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
Merge pull request #108 from thoj/master
Take 2: Use TPKT/COTP for reading responses from PLS
This commit is contained in:
@@ -769,6 +769,41 @@ namespace S7.Net.UnitTest
|
||||
Assert.IsTrue(reachablePlc.IsAvailable);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void T26_ReadWriteDouble()
|
||||
{
|
||||
double test_value = 55.66;
|
||||
plc.Write("DB1.DBD0", test_value);
|
||||
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Write Double");
|
||||
var helper = plc.Read("DB1.DBD0");
|
||||
double test_value2 = Conversion.ConvertToDouble((uint)helper);
|
||||
|
||||
Assert.AreEqual(plc.LastErrorCode, ErrorCode.NoError, "Read Double");
|
||||
Assert.AreEqual(test_value, test_value2, 0.01, "Compare Write/Read"); //Need delta here because S7 only has 32 bit reals
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void T27_ReadWriteBytesMany()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
var count = 2000;
|
||||
var dataItems = new List<byte>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dataItems.Add((byte)(i%256));
|
||||
}
|
||||
|
||||
plc.WriteBytes(DataType.DataBlock, 2, 0, dataItems.ToArray());
|
||||
|
||||
var res = plc.ReadBytes(DataType.DataBlock, 2, 0, count);
|
||||
|
||||
for (int x = 0; x < count; x++)
|
||||
{
|
||||
Assert.AreEqual(x % 256, res[x]);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
|
||||
97
S7.Net/COTP.cs
Normal file
97
S7.Net/COTP.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// COTP Protocol functions and types
|
||||
/// </summary>
|
||||
internal class COTP
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a COTP TPDU (Transport protocol data unit)
|
||||
/// </summary>
|
||||
public class TPDU
|
||||
{
|
||||
public byte HeaderLength;
|
||||
public byte PDUType;
|
||||
public int TPDUNumber;
|
||||
public byte[] Data;
|
||||
public bool LastDataUnit;
|
||||
|
||||
public TPDU(TPKT tPKT)
|
||||
{
|
||||
var br = new BinaryReader(new MemoryStream(tPKT.Data));
|
||||
HeaderLength = br.ReadByte();
|
||||
if (HeaderLength >= 2)
|
||||
{
|
||||
PDUType = br.ReadByte();
|
||||
if (PDUType == 0xf0) //DT Data
|
||||
{
|
||||
var flags = br.ReadByte();
|
||||
TPDUNumber = flags & 0x7F;
|
||||
LastDataUnit = (flags & 0x80) > 0;
|
||||
Data = br.ReadBytes(tPKT.Length - HeaderLength - 4); //4 = TPKT Size
|
||||
return;
|
||||
}
|
||||
//TODO: Handle other PDUTypes
|
||||
}
|
||||
Data = new byte[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads COTP TPDU (Transport protocol data unit) from the network stream
|
||||
/// See: https://tools.ietf.org/html/rfc905
|
||||
/// </summary>
|
||||
/// <param name="socket">The socket to read from</param>
|
||||
/// <returns>COTP DPDU instance</returns>
|
||||
public static TPDU Read(Socket socket)
|
||||
{
|
||||
var tpkt = TPKT.Read(socket);
|
||||
if (tpkt.Length > 0) return new TPDU(tpkt);
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Length: {0} PDUType: {1} TPDUNumber: {2} Last: {3} Segment Data: {4}",
|
||||
HeaderLength,
|
||||
PDUType,
|
||||
TPDUNumber,
|
||||
LastDataUnit,
|
||||
BitConverter.ToString(Data)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a COTP TSDU (Transport service data unit). One TSDU consist of 1 ore more TPDUs
|
||||
/// </summary>
|
||||
public class TSDU
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the full COTP TSDU (Transport service data unit)
|
||||
/// See: https://tools.ietf.org/html/rfc905
|
||||
/// </summary>
|
||||
/// <returns>Data in TSDU</returns>
|
||||
public static byte[] Read(Socket socket)
|
||||
{
|
||||
var segment = TPDU.Read(socket);
|
||||
if (segment == null) return null;
|
||||
|
||||
var output = new MemoryStream(segment.Data.Length);
|
||||
output.Write(segment.Data, 0, segment.Data.Length);
|
||||
|
||||
while (!segment.LastDataUnit)
|
||||
{
|
||||
segment = TPDU.Read(socket);
|
||||
output.Write(segment.Data, (int)output.Position, segment.Data.Length);
|
||||
}
|
||||
return output.GetBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
170
S7.Net/PLC.cs
170
S7.Net/PLC.cs
@@ -4,11 +4,8 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using S7.Net.Types;
|
||||
using Double = System.Double;
|
||||
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
@@ -41,6 +38,11 @@ namespace S7.Net
|
||||
/// Slot of the CPU of the PLC
|
||||
/// </summary>
|
||||
public Int16 Slot { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Max PDU size this cpu supports
|
||||
/// </summary>
|
||||
public Int16 MaxPDUSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a connection to the PLC can be established
|
||||
@@ -119,6 +121,7 @@ namespace S7.Net
|
||||
IP = ip;
|
||||
Rack = rack;
|
||||
Slot = slot;
|
||||
MaxPDUSize = 240;
|
||||
}
|
||||
|
||||
private ErrorCode Connect(Socket socket)
|
||||
@@ -160,7 +163,6 @@ namespace S7.Net
|
||||
/// <returns>Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode</returns>
|
||||
public ErrorCode Open()
|
||||
{
|
||||
byte[] bReceive = new byte[256];
|
||||
|
||||
_mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
_mSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000);
|
||||
@@ -171,9 +173,25 @@ namespace S7.Net
|
||||
return LastErrorCode;
|
||||
}
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
byte[] bSend1 = { 3, 0, 0, 22, 17, 224, 0, 0, 0, 46, 0, 193, 2, 1, 0, 194, 2, 3, 0, 192, 1, 9 };
|
||||
byte[] bSend1 = {
|
||||
3, 0, 0, 22, //TPKT
|
||||
17, //COTP Header Length
|
||||
224, //Connect Request
|
||||
0, 0, //Destination Reference
|
||||
0, 46, //Source Reference
|
||||
0, //Flags
|
||||
193, //Parameter Code (src-tasp)
|
||||
2, //Parameter Length
|
||||
1, 0, //Source TASP
|
||||
194, //Parameter Code (dst-tasp)
|
||||
2, //Parameter Length
|
||||
3, 0, //Destination TASP
|
||||
192, //Parameter Code (tpdu-size)
|
||||
1, //Parameter Length
|
||||
9 //TPDU Size (2^9 = 512)
|
||||
};
|
||||
|
||||
switch (CPU) {
|
||||
case CpuType.S7200:
|
||||
@@ -229,28 +247,29 @@ namespace S7.Net
|
||||
return ErrorCode.WrongCPU_Type;
|
||||
}
|
||||
|
||||
//COTP Setup
|
||||
_mSocket.Send(bSend1, 22, SocketFlags.None);
|
||||
if (_mSocket.Receive(bReceive, 22, SocketFlags.None) != 22)
|
||||
{
|
||||
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
||||
}
|
||||
byte[] bsend2 = { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3,
|
||||
7, 80 //Try 1920 PDU Size. Same as libnodave.
|
||||
};
|
||||
|
||||
byte[] bsend2 = { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3, 1, 0 };
|
||||
_mSocket.Send(bsend2, 25, SocketFlags.None);
|
||||
|
||||
if (_mSocket.Receive(bReceive, 27, SocketFlags.None) != 27)
|
||||
var s7data = COTP.TSDU.Read(_mSocket);
|
||||
if (s7data == null || s7data[1] != 0x03) //Check for S7 Ack Data
|
||||
{
|
||||
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
||||
}
|
||||
}
|
||||
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
|
||||
|
||||
return ErrorCode.NoError;
|
||||
}
|
||||
catch(Exception exc)
|
||||
catch (Exception exc)
|
||||
{
|
||||
LastErrorCode = ErrorCode.ConnectionError;
|
||||
LastErrorString = string.Format("Couldn't establish the connection to {0}.\nMessage: {1}", IP, exc.Message);
|
||||
return ErrorCode.ConnectionError;
|
||||
}
|
||||
|
||||
return ErrorCode.NoError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -276,12 +295,14 @@ namespace S7.Net
|
||||
public void ReadMultipleVars(List<DataItem> dataItems)
|
||||
{
|
||||
int cntBytes = dataItems.Sum(dataItem => VarTypeToByteLength(dataItem.VarType, dataItem.Count));
|
||||
|
||||
|
||||
//TODO: Figure out how to use MaxPDUSize here
|
||||
//Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
//replies with bigger PDU size in connection setup.
|
||||
if (dataItems.Count > 20)
|
||||
throw new Exception("Too many vars requested");
|
||||
if (cntBytes > 222)
|
||||
throw new Exception("Too many bytes requested"); // TODO: proper TDU check + split in multiple requests
|
||||
|
||||
try
|
||||
{
|
||||
// first create the header
|
||||
@@ -296,25 +317,20 @@ namespace S7.Net
|
||||
|
||||
_mSocket.Send(package.array, package.array.Length, SocketFlags.None);
|
||||
|
||||
byte[] bReceive = new byte[512];
|
||||
int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
|
||||
if (bReceive[21] != 0xff)
|
||||
var s7data = COTP.TSDU.Read(_mSocket);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
||||
|
||||
int offset = 25;
|
||||
int offset = 18;
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count);
|
||||
byte[] bytes = new byte[byteCnt];
|
||||
|
||||
for (int i = 0; i < byteCnt; i++)
|
||||
{
|
||||
bytes[i] = bReceive[i + offset];
|
||||
}
|
||||
|
||||
dataItem.Value = ParseBytes(
|
||||
dataItem.VarType,
|
||||
s7data.Skip(offset).Take(byteCnt).ToArray(),
|
||||
dataItem.Count
|
||||
);
|
||||
offset += byteCnt + 4;
|
||||
|
||||
dataItem.Value = ParseBytes(dataItem.VarType, bytes, dataItem.Count);
|
||||
}
|
||||
}
|
||||
catch (SocketException socketException)
|
||||
@@ -344,7 +360,8 @@ namespace S7.Net
|
||||
int index = startByteAdr;
|
||||
while (count > 0)
|
||||
{
|
||||
var maxToRead = (int)Math.Min(count, 200);
|
||||
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
|
||||
var maxToRead = (int)Math.Min(count, MaxPDUSize-18);
|
||||
byte[] bytes = ReadBytesWithASingleRequest(dataType, db, index, maxToRead);
|
||||
if (bytes == null)
|
||||
return resultBytes.ToArray();
|
||||
@@ -621,7 +638,10 @@ namespace S7.Net
|
||||
int localIndex = 0;
|
||||
int count = value.Length;
|
||||
while (count > 0)
|
||||
{
|
||||
{
|
||||
//TODO: Figure out how to use MaxPDUSize here
|
||||
//Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
//replies with bigger PDU size in connection setup.
|
||||
var maxToWrite = (int)Math.Min(count, 200);
|
||||
ErrorCode lastError = WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
|
||||
if (lastError != ErrorCode.NoError)
|
||||
@@ -731,7 +751,7 @@ namespace S7.Net
|
||||
package = Types.DWord.ToByteArray((UInt32)value);
|
||||
break;
|
||||
case "Double":
|
||||
package = Types.Double.ToByteArray((Double)value);
|
||||
package = Types.Double.ToByteArray((double)value);
|
||||
break;
|
||||
case "Byte[]":
|
||||
package = (byte[])value;
|
||||
@@ -815,10 +835,11 @@ namespace S7.Net
|
||||
{
|
||||
return Write(DataType.DataBlock, mDB, dbIndex, (Int32)value);
|
||||
}
|
||||
else
|
||||
else if (value is double)
|
||||
{
|
||||
objValue = Convert.ChangeType(value, typeof(UInt32));
|
||||
return Write(DataType.DataBlock, mDB, dbIndex, value);
|
||||
}
|
||||
objValue = Convert.ChangeType(value, typeof(UInt32));
|
||||
return Write(DataType.DataBlock, mDB, dbIndex, (UInt32)objValue);
|
||||
case "DBX":
|
||||
mByte = dbIndex;
|
||||
@@ -1034,8 +1055,6 @@ namespace S7.Net
|
||||
|
||||
private byte[] ReadBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, int count)
|
||||
{
|
||||
byte[] bytes = new byte[count];
|
||||
|
||||
try
|
||||
{
|
||||
// first create the header
|
||||
@@ -1047,15 +1066,11 @@ namespace S7.Net
|
||||
|
||||
_mSocket.Send(package.array, package.array.Length, SocketFlags.None);
|
||||
|
||||
byte[] bReceive = new byte[512];
|
||||
int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
|
||||
if (bReceive[21] != 0xff)
|
||||
var s7data = COTP.TSDU.Read(_mSocket);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
||||
|
||||
for (int cnt = 0; cnt < count; cnt++)
|
||||
bytes[cnt] = bReceive[cnt + 25];
|
||||
|
||||
return bytes;
|
||||
return s7data.Skip(18).Take(count).ToArray();
|
||||
}
|
||||
catch (SocketException socketException)
|
||||
{
|
||||
@@ -1082,9 +1097,7 @@ namespace S7.Net
|
||||
/// <returns>NoError if it was successful, or the error is specified</returns>
|
||||
private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
|
||||
{
|
||||
byte[] bReceive = new byte[513];
|
||||
int varCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
varCount = value.Length;
|
||||
@@ -1113,8 +1126,8 @@ namespace S7.Net
|
||||
|
||||
_mSocket.Send(package.array, package.array.Length, SocketFlags.None);
|
||||
|
||||
int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
|
||||
if (bReceive[21] != 0xff)
|
||||
var s7data = COTP.TSDU.Read(_mSocket);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
{
|
||||
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
||||
}
|
||||
@@ -1131,7 +1144,6 @@ namespace S7.Net
|
||||
|
||||
private ErrorCode WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
|
||||
{
|
||||
byte[] bReceive = new byte[513];
|
||||
int varCount = 0;
|
||||
|
||||
try
|
||||
@@ -1163,11 +1175,9 @@ namespace S7.Net
|
||||
|
||||
_mSocket.Send(package.array, package.array.Length, SocketFlags.None);
|
||||
|
||||
int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
|
||||
if (bReceive[21] != 0xff)
|
||||
{
|
||||
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
||||
}
|
||||
var s7data = COTP.TSDU.Read(_mSocket);
|
||||
if (s7data == null || s7data[14] != 0xff)
|
||||
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
||||
|
||||
return ErrorCode.NoError;
|
||||
}
|
||||
@@ -1289,23 +1299,53 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable members
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources, disonnects from the PLC and closes the <see cref="Socket"/>
|
||||
/// </summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects).
|
||||
if (_mSocket != null)
|
||||
{
|
||||
if (_mSocket.Connected)
|
||||
{
|
||||
_mSocket.Shutdown(SocketShutdown.Both);
|
||||
_mSocket.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
|
||||
// TODO: set large fields to null.
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
|
||||
// ~Plc() {
|
||||
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
// Dispose(false);
|
||||
// }
|
||||
|
||||
// This code added to correctly implement the disposable pattern.
|
||||
/// <summary>
|
||||
/// Releases all resources, disonnects from the PLC and closes the <see cref="Socket"/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_mSocket != null)
|
||||
{
|
||||
if (_mSocket.Connected)
|
||||
{
|
||||
_mSocket.Shutdown(SocketShutdown.Both);
|
||||
_mSocket.Close();
|
||||
}
|
||||
}
|
||||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
Dispose(true);
|
||||
// TODO: uncomment the following line if the finalizer is overridden above.
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +77,11 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Conversion.cs" />
|
||||
<Compile Include="COTP.cs" />
|
||||
<Compile Include="Enums.cs" />
|
||||
<Compile Include="PLC.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TPKT.cs" />
|
||||
<Compile Include="Types\Bit.cs" />
|
||||
<Compile Include="Types\Boolean.cs" />
|
||||
<Compile Include="Types\Byte.cs" />
|
||||
|
||||
49
S7.Net/TPKT.cs
Normal file
49
S7.Net/TPKT.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Describes a TPKT Packet
|
||||
/// </summary>
|
||||
internal class TPKT
|
||||
{
|
||||
public byte Version;
|
||||
public byte Reserved1;
|
||||
public int Length;
|
||||
public byte[] Data;
|
||||
|
||||
/// <summary>
|
||||
/// Reds a TPKT from the socket
|
||||
/// </summary>
|
||||
/// <param name="socket">The socket to read from</param>
|
||||
/// <returns>TPKT Instace</returns>
|
||||
public static TPKT Read(Socket socket)
|
||||
{
|
||||
var buf = new byte[4];
|
||||
socket.Receive(buf, 4, SocketFlags.None);
|
||||
var pkt = new TPKT
|
||||
{
|
||||
Version = buf[0],
|
||||
Reserved1 = buf[1],
|
||||
Length = buf[2] * 256 + buf[3] //BigEndian
|
||||
};
|
||||
if (pkt.Length > 0)
|
||||
{
|
||||
pkt.Data = new byte[pkt.Length - 4];
|
||||
socket.Receive(pkt.Data, pkt.Length - 4, SocketFlags.None);
|
||||
}
|
||||
return pkt;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Version: {0} Length: {1} Data: {2}",
|
||||
Version,
|
||||
Length,
|
||||
BitConverter.ToString(Data)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ namespace S7.Net.Types
|
||||
wert += ((bytes[1] >> 4) & 0x0F) * 10.0;
|
||||
wert += ((bytes[1]) & 0x0F) * 1.0;
|
||||
|
||||
/// this value is not used... may for a nother exponation
|
||||
///int unknown = (bytes[0] >> 6) & 0x03;
|
||||
// this value is not used... may for a nother exponation
|
||||
//int unknown = (bytes[0] >> 6) & 0x03;
|
||||
|
||||
switch ((bytes[0] >> 4) & 0x03)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user