30 Commits

Author SHA1 Message Date
Michele Cattafesta
013ff5fd92 Merge pull request #117 from rapha-dev/master
Fixed bug in ReadMultipleVars on VarType.Bit
2018-05-12 23:03:57 +01:00
Raphael
642cf8169e Fixed bug for reading VarType.Bit and VarType.Byte on odd number of bytes in ReadMultipleVars() 2018-05-07 11:47:52 +02:00
Raphael
f6e370b162 Merge remote-tracking branch 'upstream/master' 2018-05-07 11:44:58 +02:00
Raphael
cbaa8921df import killnine master 2018-05-07 09:47:20 +02:00
Michele Cattafesta
b3458a8304 unit tests 2018-05-05 23:24:06 +01:00
Michele Cattafesta
a824344a4c Merge pull request #110 from Buchter/ImproveClassSizeCalculation
Fixed bug regarding size calculation of small S7 Structs
2018-05-05 23:00:15 +01:00
Michele Cattafesta
587e496497 Merge pull request #108 from thoj/master
Take 2: Use TPKT/COTP for reading responses from PLS
2018-05-05 22:09:13 +01:00
Raphael
0d1bc472c8 Added BitAdr to DataItem and fixed bug in ReadMultipleVars on VarType.Bit 2018-04-27 16:00:41 +02:00
Buchter
4b04ed74a1 Fixed bug regarding size calculation of small S7 Structs
There was an Error when you had Structs conaining less than 8 Bits. The size calculation in this case returned 0 and the Plc.ReadClass() method throwed an excpetion. Structs in Step7 within da DataBlock always starts with adresses that can by devided by two. The extended code ensures the correct size even if there are a couple of structs in a DataBlock containing only a few bits.
2018-04-19 12:09:30 +02:00
Thomas Jäger
50b026d7a5 Read TPKT/COTP packets / Read MaxPDU size from PLC
Read responses from the PLS using classes for TPKT and COPT. This
makes the communication more robust. It will now handle empty COTP
packets that SoftPLS and WinAC based PLCs send out. I use RFC names for
functions and classes.

Change logic to use COTP and S7Comm reponse codes instead of
relying on packet sizes.

Read Max PDU size from connection setup. Ref #21
Change logic to use MaxPDUSize when reading istead of hardcoded limit.

I tried using MaxPDUSize when writing data but this failed when packet size is
over 256 on snap7. So i decided to drop changes to write size.
I have done some tests against WinAC cpu and it seems to handle bigger pdu's
when writing if negotiated in the connection setup. This might just be a SNAP7 bug.

Fix MaxPDUSize for readbytes

Remove debug line

Simplify byte copy. Remove unessesarry buffer
2018-04-19 00:34:11 +02:00
Thomas Jäger
f740ba0078 Fix writing double to PLC 2018-04-19 00:34:11 +02:00
Thomas Jäger
1b4faf21d7 Code Lint: Standard dispose Pattern. /// to // comment 2018-04-19 00:34:11 +02:00
Thomas Jäger
3a18d13805 Add test for Read/Write Double and PDUSize Test 2018-04-19 00:34:10 +02:00
Michele Cattafesta
9fd515280a Revert "Merge pull request #107 from thoj/master"
This reverts commit d17fdf8efb, reversing
changes made to bfeacee08f.
2018-04-11 20:22:24 +01:00
Michele Cattafesta
d17fdf8efb Merge pull request #107 from thoj/master
Fix communication with WinAC/SoftPLC CPUs and Fix write double
2018-04-11 19:28:41 +01:00
Thomas Jäger
8a3db22629 Read TPKT/COTP packets / Read MaxPDU size from PLC
Read responses from the PLS using classes for TPKT and COPT. This
makes the communication more robust. It will now handle empty COTP
packets that SoftPLS and WinAC based PLCs send out. I use RFC names for
functions and classes.

Change logic to use COTP and S7Comm reponse codes instead of
relying on packet sizes.

Read Max PDU size from connection setup. Ref #21
Change logic to use MaxPDU from cpu limit instead of hardcoded limit.

Remove var count limit.
2018-04-10 15:26:04 +02:00
Thomas Jäger
6470f8d076 Document bSend1 data packet 2018-04-10 15:25:20 +02:00
Thomas Jäger
723b0ffd42 Fix writing double to PLC 2018-04-10 15:23:46 +02:00
Michele Cattafesta
bfeacee08f Merge pull request #102 from mbalous/master
Non breaking changes. Code styling, comments, etc...
2018-04-08 21:12:22 +01:00
Michele Cattafesta
b6b53078f9 Merge pull request #101 from tomsoftware/master
fix data type converting for num-types
2018-04-08 21:08:27 +01:00
mbalous
a99ea469ce Correct exceptions. Constructor parameter checking. 2018-03-21 22:40:25 +01:00
mbalous
dd71e1bf0b Checking constructor parameters. 2018-03-21 22:32:23 +01:00
mbalous
530072b70f Improved XML function comments. 2018-03-21 22:18:37 +01:00
Thomas Ze
117ad5cd1b fix variable length bug for StringEx 2018-03-20 22:37:44 +01:00
Thomas Ze
7cedec5909 - fix data type converting
- add new string string data type
2018-03-18 21:26:22 +01:00
Michele Cattafesta
f6a2e11045 0.1.8 2018-02-05 20:07:11 +00:00
Michele Cattafesta
8005304827 Merge pull request #97 from GS770/master
Added Bit to Types
2017-12-14 23:00:19 +00:00
shen.jz
ef5e060948 Added Bit to Types
get bool or BitArray from byte array
2017-12-14 10:38:10 +08:00
Michele Cattafesta
3178d2aa09 Merge pull request #96 from GS770/master
Get bit of read bytes
2017-12-13 08:57:52 +00:00
shen.jz
6a2bc708a9 Get bit of read bytes 2017-12-12 15:56:18 +08:00
29 changed files with 708 additions and 386 deletions

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace S7.UnitTest.Helpers
{
class TestSmallClass
{
public bool Bool1 { get; set; }
}
}

View File

@@ -60,6 +60,7 @@
<Compile Include="Helpers\TestClassWithCustomType.cs" />
<Compile Include="Helpers\TestClassWithPrivateSetters.cs" />
<Compile Include="Helpers\TestLongClass.cs" />
<Compile Include="Helpers\TestSmallClass.cs" />
<Compile Include="Snap7\snap7.net.cs" />
<Compile Include="Helpers\TestClass.cs" />
<Compile Include="Helpers\TestStruct.cs" />

View File

@@ -386,19 +386,51 @@ namespace S7.Net.UnitTest
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
ushort val = 16384;
plc.Write("DB2.DBW16384", val);
ushort result = (ushort)plc.Read("DB2.DBW16384");
Assert.AreEqual(val, result, "A ushort goes from 0 to 64512");
bool val = true;
plc.Write("DB2.DBX0.5", val);
bool result = (bool)plc.Read("DB2.DBX0.5");
Assert.AreEqual(val, result);
ushort val2 = 129;
plc.Write("DB2.DBW16", val2);
ushort result2 = (ushort)plc.Read("DB2.DBW16");
Assert.AreEqual(val2, result2, "A ushort goes from 0 to 64512");
ushort val1 = 16384;
plc.Write("DB2.DBW16384", val1);
ushort result1 = (ushort)plc.Read("DB2.DBW16384");
Assert.AreEqual(val1, result1, "A ushort goes from 0 to 64512");
bool val2 = true;
plc.Write("DB2.DBX8192.7", val2);
bool result2 = (bool)plc.Read("DB2.DBX8192.7");
Assert.AreEqual(val2, result2);
ushort val3 = 129;
plc.Write("DB2.DBW16", val3);
ushort result3 = (ushort)plc.Read("DB2.DBW16");
Assert.AreEqual(val3, result3, "A ushort goes from 0 to 64512");
byte[] val4 = new byte[] { 0x12, 0x34 };
plc.Write("DB2.DBB2048", val4[0]);
plc.Write("DB2.DBB2049", val4[1]);
byte result4b0 = (byte)plc.Read("DB2.DBB2048");
byte result4b1 = (byte)plc.Read("DB2.DBB2049");
Assert.AreEqual(val4[0], result4b0);
Assert.AreEqual(val4[1], result4b1);
bool val6 = true;
plc.Write("DB2.DBX16384.6", val6);
bool result6 = (bool)plc.Read("DB2.DBX16384.6");
Assert.AreEqual(val6, result6);
var dataItems = new List<DataItem>()
{
new DataItem
{
Count = 1,
DataType = DataType.DataBlock,
DB = 2,
StartByteAdr = 0,
BitAdr = 5,
VarType = VarType.Bit
}
,new DataItem
{
Count = 1,
DataType = DataType.DataBlock,
@@ -407,19 +439,61 @@ namespace S7.Net.UnitTest
VarType = VarType.Word
},
new DataItem
{
Count = 1,
DataType = DataType.DataBlock,
DB = 2,
StartByteAdr = 8192,
BitAdr = 7,
VarType = VarType.Bit
},
new DataItem
{
Count = 1,
DataType = DataType.DataBlock,
DB = 2,
StartByteAdr = 16,
VarType = VarType.Word
}
},
// single byte
new DataItem
{
Count = 1,
DataType = DataType.DataBlock,
DB = 2,
StartByteAdr = 2048,
VarType = VarType.Byte
},
// multiple bytes
new DataItem
{
Count = 2,
DataType = DataType.DataBlock,
DB = 2,
StartByteAdr = 2048,
VarType = VarType.Byte
},
new DataItem
{
Count = 1,
DataType = DataType.DataBlock,
DB = 2,
StartByteAdr = 16384,
BitAdr = 6,
VarType = VarType.Bit
},
};
plc.ReadMultipleVars(dataItems);
Assert.AreEqual(dataItems[0].Value, val);
Assert.AreEqual(dataItems[1].Value, val2);
Assert.AreEqual(dataItems[1].Value, val1);
Assert.AreEqual(dataItems[2].Value, val2);
Assert.AreEqual(dataItems[3].Value, val3);
Assert.AreEqual(dataItems[4].Value, val4[0]);
Assert.AreEqual(((byte[])dataItems[5].Value)[0], val4[0]); //dataItem[5].Value should be byte[2]
Assert.AreEqual(((byte[])dataItems[5].Value)[1], val4[1]);
Assert.AreEqual(dataItems[6].Value, val6);
}
/// <summary>
@@ -769,6 +843,55 @@ 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]);
}
}
[TestMethod]
public void T28_ReadClass_DoesntCrash_When_ReadingLessThan1Byte()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
var tc = new TestSmallClass();
tc.Bool1 = true;
plc.WriteClass(tc, DB2);
var tc2 = plc.ReadClass<TestSmallClass>(DB2);
Assert.AreEqual(tc.Bool1, tc2.Bool1);
}
#endregion
#region Private methods

97
S7.Net/COTP.cs Normal file
View 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();
}
}
}
}

View File

@@ -15,15 +15,11 @@ namespace S7.Net
/// <returns></returns>
public static int BinStringToInt32(this string txt)
{
int cnt = 0;
int ret = 0;
for (cnt = txt.Length - 1; cnt >= 0; cnt += -1)
for (int i = 0; i < txt.Length; i++)
{
if (int.Parse(txt.Substring(cnt, 1)) == 1)
{
ret += (int)(Math.Pow(2, (txt.Length - 1 - cnt)));
}
ret = (ret << 1) | ((txt[i] == '1') ? 1 : 0);
}
return ret;
}
@@ -35,20 +31,7 @@ namespace S7.Net
/// <returns></returns>
public static byte? BinStringToByte(this string txt)
{
int cnt = 0;
int ret = 0;
if (txt.Length == 8)
{
for (cnt = 7; cnt >= 0; cnt += -1)
{
if (int.Parse(txt.Substring(cnt, 1)) == 1)
{
ret += (int)(Math.Pow(2, (txt.Length - 1 - cnt)));
}
}
return (byte)ret;
}
if (txt.Length == 8) return (byte)BinStringToInt32(txt);
return null;
}

View File

@@ -163,6 +163,11 @@
/// </summary>
String,
/// <summary>
/// String variable type (variable)
/// </summary>
StringEx,
/// <summary>
/// Timer variable type
/// </summary>

View File

@@ -1,13 +1,11 @@
using System;
using System;
using System.Collections;
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
{
@@ -17,31 +15,37 @@ namespace S7.Net
public class Plc : IDisposable
{
private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060;
private Socket _mSocket; //TCP connection to device
//TCP connection to device
private Socket _mSocket;
/// <summary>
/// Ip address of the plc
/// IP address of the PLC
/// </summary>
public string IP { get; private set; }
/// <summary>
/// Cpu type of the plc
/// CPU type of the PLC
/// </summary>
public CpuType CPU { get; private set; }
/// <summary>
/// Rack of the plc
/// Rack of the PLC
/// </summary>
public Int16 Rack { get; private set; }
/// <summary>
/// Slot of the CPU of the plc
/// 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
/// Returns true if a connection to the PLC can be established
/// </summary>
public bool IsAvailable
{
@@ -61,7 +65,7 @@ namespace S7.Net
/// <summary>
/// Checks if the socket is connected and polls the other peer (the plc) to see if it's connected.
/// Checks if the socket is connected and polls the other peer (the PLC) to see if it's connected.
/// This is the variable that you should continously check to see if the communication is working
/// See also: http://stackoverflow.com/questions/2661764/how-to-check-if-a-socket-is-connected-disconnected-in-c
/// </summary>
@@ -100,17 +104,24 @@ namespace S7.Net
/// You need slot > 0 if you are connecting to external ethernet card (CP).
/// For S7-300 and S7-400 the default is rack = 0 and slot = 2.
/// </summary>
/// <param name="cpu">CpuType of the plc (select from the enum)</param>
/// <param name="ip">Ip address of the plc</param>
/// <param name="rack">rack of the plc, usually it's 0, but check in the hardware configuration of Step7 or TIA portal</param>
/// <param name="slot">slot of the CPU of the plc, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
/// <param name="cpu">CpuType of the PLC (select from the enum)</param>
/// <param name="ip">Ip address of the PLC</param>
/// <param name="rack">rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal</param>
/// <param name="slot">slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
/// If you use an external ethernet card, this must be set accordingly.</param>
public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
{
IP = ip;
if (!Enum.IsDefined(typeof(CpuType), cpu))
throw new InvalidEnumArgumentException(nameof(cpu), (int) cpu, typeof(CpuType));
if (string.IsNullOrEmpty(ip))
throw new ArgumentException("IP address must valid.", nameof(ip));
CPU = cpu;
IP = ip;
Rack = rack;
Slot = slot;
MaxPDUSize = 240;
}
private ErrorCode Connect(Socket socket)
@@ -146,12 +157,12 @@ namespace S7.Net
}
/// <summary>
/// Open a socket and connects to the plc, sending all the corrected package and returning if the connection was successful (ErroreCode.NoError) of it was wrong.
/// Open a <see cref="Socket"/> and connects to the PLC, sending all the corrected package
/// and returning if the connection was successful (<see cref="ErrorCode.NoError"/>) of it was wrong.
/// </summary>
/// <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);
@@ -162,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:
@@ -220,32 +247,33 @@ 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 = "Couldn't establish the connection to " + IP + ".\nMessage: " + exc.Message;
LastErrorString = string.Format("Couldn't establish the connection to {0}.\nMessage: {1}", IP, exc.Message);
return ErrorCode.ConnectionError;
}
return ErrorCode.NoError;
}
/// <summary>
/// Disonnects from the plc and close the socket
/// Disonnects from the PLC and close the socket
/// </summary>
public void Close()
{
@@ -267,10 +295,14 @@ namespace S7.Net
public void ReadMultipleVars(List<DataItem> dataItems)
{
int cntBytes = dataItems.Sum(dataItem => VarTypeToByteLength(dataItem.VarType, dataItem.Count));
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
//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
@@ -285,24 +317,34 @@ 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) throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
var s7data = COTP.TSDU.Read(_mSocket);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
int offset = 25;
int offset = 14;
foreach (var dataItem in dataItems)
{
// check for Return Code = Success
if (s7data[offset] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
// to Data bytes
offset += 4;
int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count);
byte[] bytes = new byte[byteCnt];
dataItem.Value = ParseBytes(
dataItem.VarType,
s7data.Skip(offset).Take(byteCnt).ToArray(),
dataItem.Count,
dataItem.BitAdr
);
for (int i = 0; i < byteCnt; i++)
{
bytes[i] = bReceive[i + offset];
}
// next Item
offset += byteCnt;
offset += byteCnt + 4;
dataItem.Value = ParseBytes(dataItem.VarType, bytes, dataItem.Count);
// Fill byte in response when bytecount is odd
if (dataItem.Count % 2 != 0 && (dataItem.VarType == VarType.Byte || dataItem.VarType == VarType.Bit))
offset++;
}
}
catch (SocketException socketException)
@@ -332,7 +374,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();
@@ -343,6 +386,8 @@ namespace S7.Net
return resultBytes.ToArray();
}
/// <summary>
/// Read and decode a certain number of bytes of the "VarType" provided.
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
@@ -352,17 +397,18 @@ namespace S7.Net
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <param name="varType">Type of the variable/s that you are reading</param>
/// <param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
/// <param name="varCount"></param>
public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount)
public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
{
int cntBytes = VarTypeToByteLength(varType, varCount);
byte[] bytes = ReadBytes(dataType, db, startByteAdr, cntBytes);
return ParseBytes(varType, bytes, varCount);
return ParseBytes(varType, bytes, varCount, bitAdr);
}
/// <summary>
/// Reads a single variable from the plc, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
/// Reads a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
/// If the read was not successful, check LastErrorCode or LastErrorString.
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
@@ -484,7 +530,8 @@ namespace S7.Net
}
string txt2 = txt.Substring(1);
if (txt2.IndexOf(".") == -1) throw new Exception();
if (txt2.IndexOf(".") == -1)
throw new Exception();
mByte = int.Parse(txt2.Substring(0, txt2.IndexOf(".")));
mBit = int.Parse(txt2.Substring(txt2.IndexOf(".") + 1));
@@ -532,7 +579,7 @@ namespace S7.Net
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the plc.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
/// </summary>
/// <param name="sourceClass">Instance of the class that will store the values</param>
@@ -556,28 +603,28 @@ namespace S7.Net
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the plc.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic
/// type, the class needs a default constructor.
/// </summary>
/// <typeparam name="T">The class that will be instantiated. Requires a default constructor</typeparam>
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the plc. If no data has been read, null will be returned</returns>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public T ReadClass<T>(int db, int startByteAdr = 0) where T : class
{
return ReadClass(() => Activator.CreateInstance<T>(), db, startByteAdr);
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the plc.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
/// </summary>
/// <typeparam name="T">The class that will be instantiated</typeparam>
/// <param name="classFactory">Function to instantiate the class</param>
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>An instance of the class with the values read from the plc. If no data has been read, null will be returned</returns>
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public T ReadClass<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
{
var instance = classFactory();
@@ -605,7 +652,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)
@@ -685,7 +735,7 @@ namespace S7.Net
{
var intValue = (int) value;
if (intValue < 0 || intValue > 7)
throw new Exception(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
throw new ArgumentOutOfRangeException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr), nameof(bitAdr));
bitValue = intValue == 1;
}
@@ -715,7 +765,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;
@@ -746,11 +796,11 @@ namespace S7.Net
}
/// <summary>
/// Writes a single variable from the plc, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
/// If the write was not successful, check LastErrorCode or LastErrorString.
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
/// If the write was not successful, check <see cref="LastErrorCode"/> or <see cref="LastErrorString"/>.
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <param name="value">Value to be written to the plc</param>
/// <param name="value">Value to be written to the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode Write(string variable, object value)
{
@@ -799,10 +849,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;
@@ -908,17 +959,17 @@ namespace S7.Net
catch(Exception exc)
{
LastErrorCode = ErrorCode.WrongVarFormat;
LastErrorString = "The variable'" + variable + "' could not be parsed. Please check the syntax and try again.\nException: " + exc.Message;
LastErrorString = string.Format("The variable'{0}' could not be parsed. Please check the syntax and try again.\nException: {1}", variable, exc.Message);
return LastErrorCode;
}
}
/// <summary>
/// Writes a C# struct to a DB in the plc
/// Writes a C# struct to a DB in the PLC
/// </summary>
/// <param name="structValue">The struct to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the plc</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteStruct(object structValue, int db, int startByteAdr = 0)
{
@@ -928,11 +979,11 @@ namespace S7.Net
}
/// <summary>
/// Writes a C# class to a DB in the plc
/// Writes a C# class to a DB in the PLC
/// </summary>
/// <param name="classValue">The class to be written</param>
/// <param name="db">Db address</param>
/// <param name="startByteAdr">Start bytes on the plc</param>
/// <param name="startByteAdr">Start bytes on the PLC</param>
/// <returns>NoError if it was successful, or the error is specified</returns>
public ErrorCode WriteClass(object classValue, int db, int startByteAdr = 0)
{
@@ -942,7 +993,7 @@ namespace S7.Net
}
/// <summary>
/// Sets the LastErrorCode to NoError and LastErrorString to String.Empty
/// Sets the <see cref="LastErrorCode"/> to <see cref="ErrorCode.NoError"/> and <see cref="LastErrorString"/> to <see cref="string.Empty"/>.
/// </summary>
public void ClearLastError()
{
@@ -951,7 +1002,7 @@ namespace S7.Net
}
/// <summary>
/// Creates the header to read bytes from the plc
/// Creates the header to read bytes from the PLC
/// </summary>
/// <param name="amount"></param>
/// <returns></returns>
@@ -973,7 +1024,7 @@ namespace S7.Net
}
/// <summary>
/// Create the bytes-package to request data from the plc. You have to specify the memory type (dataType),
/// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType),
/// the address of the memory, the address of the byte and the bytes count.
/// </summary>
/// <param name="dataType">MemoryType (DB, Timer, Counter, etc.)</param>
@@ -1018,8 +1069,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
@@ -1031,15 +1080,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)
{
@@ -1056,7 +1101,7 @@ namespace S7.Net
}
/// <summary>
/// Writes up to 200 bytes to the plc and returns NoError if successful. You must specify the memory area type, memory are address, byte start address and bytes count.
/// Writes up to 200 bytes to the PLC and returns NoError if successful. You must specify the memory area type, memory are address, byte start address and bytes count.
/// If the write was not successful, check LastErrorCode or LastErrorString.
/// </summary>
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
@@ -1066,9 +1111,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;
@@ -1097,8 +1140,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());
}
@@ -1115,7 +1158,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
@@ -1136,7 +1178,7 @@ namespace S7.Net
package.Add(Types.Word.ToByteArray((ushort)varCount));
package.Add(Types.Word.ToByteArray((ushort)(db)));
package.Add((byte)dataType);
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
int overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
package.Add((byte)overflow);
package.Add(Types.Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr)));
package.Add(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit
@@ -1147,11 +1189,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;
}
@@ -1169,10 +1209,12 @@ namespace S7.Net
/// <param name="varType"></param>
/// <param name="bytes"></param>
/// <param name="varCount"></param>
/// <param name="bitAdr"></param>
/// <returns></returns>
private object ParseBytes(VarType varType, byte[] bytes, int varCount)
private object ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0)
{
if (bytes == null) return null;
if (bytes == null)
return null;
switch (varType)
{
@@ -1206,8 +1248,12 @@ namespace S7.Net
return Types.Double.FromByteArray(bytes);
else
return Types.Double.ToArray(bytes);
case VarType.String:
return Types.String.FromByteArray(bytes);
case VarType.StringEx:
return Types.StringEx.FromByteArray(bytes);
case VarType.Timer:
if (varCount == 1)
return Types.Timer.FromByteArray(bytes);
@@ -1219,18 +1265,28 @@ namespace S7.Net
else
return Types.Counter.ToArray(bytes);
case VarType.Bit:
return null; //TODO
if (varCount == 1)
{
if (bitAdr > 7)
return null;
else
return Types.Bit.FromByte(bytes[0], bitAdr);
}
else
{
return Types.Bit.ToBitArray(bytes);
}
default:
return null;
}
}
/// <summary>
/// Given a S7 variable type (Bool, Word, DWord, etc.), it returns how many bytes to read.
/// Given a S7 <see cref="VarType"/> (Bool, Word, DWord, etc.), it returns how many bytes to read.
/// </summary>
/// <param name="varType"></param>
/// <param name="varCount"></param>
/// <returns></returns>
/// <returns>Byte lenght of variable</returns>
private int VarTypeToByteLength(VarType varType, int varCount = 1)
{
switch (varType)
@@ -1241,6 +1297,8 @@ namespace S7.Net
return (varCount < 1) ? 1 : varCount;
case VarType.String:
return varCount;
case VarType.StringEx:
return varCount + 2;
case VarType.Word:
case VarType.Timer:
case VarType.Int:
@@ -1255,23 +1313,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 socket
/// Releases all resources, disonnects from the PLC and closes the <see cref="Socket"/>
/// </summary>
public void Dispose()
protected virtual void Dispose(bool disposing)
{
if (_mSocket != null)
if (!disposedValue)
{
if (_mSocket.Connected)
if (disposing)
{
_mSocket.Shutdown(SocketShutdown.Both);
_mSocket.Close();
// 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()
{
// 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
}
}

View File

@@ -77,9 +77,12 @@
</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" />
<Compile Include="Types\ByteArray.cs" />
@@ -90,6 +93,7 @@
<Compile Include="Types\Double.cs" />
<Compile Include="Types\DWord.cs" />
<Compile Include="Types\Int.cs" />
<Compile Include="Types\StringEx.cs" />
<Compile Include="Types\String.cs" />
<Compile Include="Types\Struct.cs" />
<Compile Include="Types\Timer.cs" />

49
S7.Net/TPKT.cs Normal file
View 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)
);
}
}
}

28
S7.Net/Types/Bit.cs Normal file
View File

@@ -0,0 +1,28 @@
using System;
using System.Collections;
namespace S7.Net.Types
{
/// <summary>
/// Contains the conversion methods to convert Bit from S7 plc to C#.
/// </summary>
public static class Bit
{
/// <summary>
/// Converts a Bit to bool
/// </summary>
public static bool FromByte(byte v, byte bitAdr)
{
return (((int)v & (1 << bitAdr)) != 0);
}
/// <summary>
/// Converts an array of bytes to a BitArray
/// </summary>
public static BitArray ToBitArray(byte[] bytes)
{
BitArray bitArr = new BitArray(bytes);
return bitArr;
}
}
}

View File

@@ -12,10 +12,7 @@ namespace S7.Net.Types
/// </summary>
public static bool GetValue(byte value, int bit)
{
if ((value & (int)Math.Pow(2, bit)) != 0)
return true;
else
return false;
return (((int)value & (1 << bit)) != 0);
}
/// <summary>
@@ -23,7 +20,7 @@ namespace S7.Net.Types
/// </summary>
public static byte SetBit(byte value, int bit)
{
return (byte)(value | (byte)Math.Pow(2, bit));
return (byte)((value | (1 << bit)) & 0xFF);
}
/// <summary>
@@ -31,7 +28,7 @@ namespace S7.Net.Types
/// </summary>
public static byte ClearBit(byte value, int bit)
{
return (byte)(value & (byte)(~(byte)Math.Pow(2, bit)));
return (byte)((value | (~(1 << bit))) & 0xFF);
}
}

View File

@@ -12,8 +12,7 @@ namespace S7.Net.Types
/// </summary>
public static byte[] ToByteArray(byte value)
{
byte[] bytes = new byte[] { value};
return bytes;
return new byte[] { value }; ;
}
/// <summary>

View File

@@ -99,6 +99,10 @@ namespace S7.Net.Types
numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType);
}
}
// enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
return (int)numBytes;
}

View File

@@ -18,16 +18,9 @@ namespace S7.Net.Types
}
// bytes[0] -> HighByte
// bytes[1] -> LowByte
return FromBytes(bytes[1], bytes[0]);
return (UInt16)((bytes[0] << 8) | bytes[1]);
}
/// <summary>
/// Converts a Counter (2 bytes) to ushort (UInt16)
/// </summary>
public static UInt16 FromBytes(byte LoVal, byte HiVal)
{
return (UInt16)(HiVal * 256 + LoVal);
}
/// <summary>
/// Converts a ushort (UInt16) to word (2 bytes)
@@ -35,16 +28,10 @@ namespace S7.Net.Types
public static byte[] ToByteArray(UInt16 value)
{
byte[] bytes = new byte[2];
int x = 2;
long valLong = (long)((UInt16)value);
for (int cnt = 0; cnt < x; cnt++)
{
Int64 x1 = (Int64)Math.Pow(256, (cnt));
Int64 x3 = (Int64)(valLong / x1);
bytes[x - cnt - 1] = (byte)(x3 & 255);
valLong -= bytes[x - cnt - 1] * x1;
}
bytes[0] = (byte)((value << 8) & 0xFF);
bytes[1] = (byte)((value) & 0xFF);
return bytes;
}

View File

@@ -16,16 +16,9 @@ namespace S7.Net.Types
{
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes.");
}
return FromBytes(bytes[3], bytes[2], bytes[1], bytes[0]);
return bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3];
}
/// <summary>
/// Converts a S7 DInt (4 bytes) to int (Int32)
/// </summary>
public static Int32 FromBytes(byte v1, byte v2, byte v3, byte v4)
{
return (Int32)(v1 + v2 * Math.Pow(2, 8) + v3 * Math.Pow(2, 16) + v4 * Math.Pow(2, 24));
}
/// <summary>
/// Converts a int (Int32) to S7 DInt (4 bytes)
@@ -33,16 +26,12 @@ namespace S7.Net.Types
public static byte[] ToByteArray(Int32 value)
{
byte[] bytes = new byte[4];
int x = 4;
long valLong = (long)((Int32)value);
for (int cnt = 0; cnt < x; cnt++)
{
Int64 x1 = (Int64)Math.Pow(256, (cnt));
Int64 x3 = (Int64)(valLong / x1);
bytes[x - cnt - 1] = (byte)(x3 & 255);
valLong -= bytes[x - cnt - 1] * x1;
}
bytes[0] = (byte)((value >> 24) & 0xFF);
bytes[1] = (byte)((value >> 16) & 0xFF);
bytes[2] = (byte)((value >> 8) & 0xFF);
bytes[3] = (byte)((value) & 0xFF);
return bytes;
}
@@ -71,18 +60,6 @@ namespace S7.Net.Types
return values;
}
/// <summary>
/// Converts from C# long (Int64) to C# int (Int32)
/// </summary>
public static Int32 CDWord(Int64 value)
{
if (value > Int32.MaxValue)
{
value -= (long)Int32.MaxValue + 1;
value = (long)Int32.MaxValue + 1 - value;
value *= -1;
}
return (int)value;
}
}
}

View File

@@ -12,36 +12,39 @@ namespace S7.Net.Types
/// </summary>
public static UInt32 FromByteArray(byte[] bytes)
{
return FromBytes(bytes[3], bytes[2], bytes[1], bytes[0]);
return (UInt32)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]);
}
/// <summary>
/// Converts a S7 DWord (4 bytes) to uint (UInt32)
/// Converts 4 bytes to DWord (UInt32)
/// </summary>
public static UInt32 FromBytes(byte v1, byte v2, byte v3, byte v4)
public static UInt32 FromBytes(byte b1, byte b2, byte b3, byte b4)
{
return (UInt32)(v1 + v2 * Math.Pow(2, 8) + v3 * Math.Pow(2, 16) + v4 * Math.Pow(2, 24));
return (UInt32)((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
}
/// <summary>
/// Converts a uint (UInt32) to S7 DWord (4 bytes)
/// </summary>
public static byte[] ToByteArray(UInt32 value)
{
byte[] bytes = new byte[4];
int x = 4;
long valLong = (long)((UInt32)value);
for (int cnt = 0; cnt < x; cnt++)
{
Int64 x1 = (Int64)Math.Pow(256, (cnt));
Int64 x3 = (Int64)(valLong / x1);
bytes[x - cnt - 1] = (byte)(x3 & 255);
valLong -= bytes[x - cnt - 1] * x1;
}
bytes[0] = (byte)((value >> 24) & 0xFF);
bytes[1] = (byte)((value >> 16) & 0xFF);
bytes[2] = (byte)((value >> 8) & 0xFF);
bytes[3] = (byte)((value) & 0xFF);
return bytes;
}
/// <summary>
/// Converts an array of uint (UInt32) to an array of S7 DWord (4 bytes)
/// </summary>

View File

@@ -25,6 +25,11 @@
/// </summary>
public int StartByteAdr { get; set; }
/// <summary>
/// Addess of bit to read from StartByteAdr
/// </summary>
public byte BitAdr { get; set; }
/// <summary>
/// Number of variables to read
/// </summary>

View File

@@ -16,38 +16,15 @@ namespace S7.Net.Types
{
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes.");
}
byte v1 = bytes[0];
byte v2 = bytes[1];
byte v3 = bytes[2];
byte v4 = bytes[3];
if ((int)v1 + v2 + v3 + v4 == 0)
// sps uses bigending so we have to reverse if platform needs
if (BitConverter.IsLittleEndian)
{
return 0.0;
// create deep copy of the array and reverse
bytes = new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
}
else
{
// nun String bilden
string txt = ValToBinString(v1) + ValToBinString(v2) + ValToBinString(v3) + ValToBinString(v4);
// erstmal das Vorzeichen
int vz = int.Parse(txt.Substring(0, 1));
int exd = Conversion.BinStringToInt32(txt.Substring(1, 8));
string ma = txt.Substring(9, 23);
double mantisse = 1;
double faktor = 1.0;
//das ist die Anzahl der restlichen bit's
for (int cnt = 0; cnt <= 22; cnt++)
{
faktor = faktor / 2.0;
//entspricht 2^-y
if (ma.Substring(cnt, 1) == "1")
{
mantisse = mantisse + faktor;
}
}
return Math.Pow((-1), vz) * Math.Pow(2, (exd - 127)) * mantisse;
}
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
@@ -76,48 +53,13 @@ namespace S7.Net.Types
/// </summary>
public static byte[] ToByteArray(double value)
{
double wert = (double)value;
string binString = "";
byte[] bytes = new byte[4];
if (wert != 0f)
{
if (wert < 0)
{
wert *= -1;
binString = "1";
}
else
{
binString = "0";
}
int exponent = (int)Math.Floor((double)Math.Log(wert) / Math.Log(2.0));
wert = wert / (Math.Pow(2, exponent)) - 1;
byte[] bytes = BitConverter.GetBytes((float)(value));
binString += ValToBinString((byte)(exponent + 127));
for (int cnt = 1; cnt <= 23; cnt++)
{
if (!(wert - System.Math.Pow(2, -cnt) < 0))
{
wert = wert - System.Math.Pow(2, -cnt);
binString += "1";
}
else
binString += "0";
}
bytes[0] = (byte)BinStringToByte(binString.Substring(0, 8));
bytes[1] = (byte)BinStringToByte(binString.Substring(8, 8));
bytes[2] = (byte)BinStringToByte(binString.Substring(16, 8));
bytes[3] = (byte)BinStringToByte(binString.Substring(24, 8));
}
else
{
bytes[0] = 0;
bytes[1] = 0;
bytes[2] = 0;
bytes[3] = 0;
}
return bytes;
// sps uses bigending so we have to check if platform is same
if (!BitConverter.IsLittleEndian) return bytes;
// create deep copy of the array and reverse
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
}
/// <summary>
@@ -145,38 +87,5 @@ namespace S7.Net.Types
return values;
}
private static string ValToBinString(byte value)
{
string txt = "";
for (int cnt = 7; cnt >= 0; cnt += -1)
{
if ((value & (byte)Math.Pow(2, cnt)) > 0)
txt += "1";
else
txt += "0";
}
return txt;
}
private static byte? BinStringToByte(string txt)
{
int cnt = 0;
int ret = 0;
if (txt.Length == 8)
{
for (cnt = 7; cnt >= 0; cnt += -1)
{
if (int.Parse(txt.Substring(cnt, 1)) == 1)
{
ret += (int)(Math.Pow(2, (txt.Length - 1 - cnt)));
}
}
return (byte)ret;
}
return null;
}
}
}

View File

@@ -10,7 +10,7 @@ namespace S7.Net.Types
/// <summary>
/// Converts a S7 Int (2 bytes) to short (Int16)
/// </summary>
public static Int16 FromByteArray(byte[] bytes)
public static short FromByteArray(byte[] bytes)
{
if (bytes.Length != 2)
{
@@ -18,16 +18,9 @@ namespace S7.Net.Types
}
// bytes[0] -> HighByte
// bytes[1] -> LowByte
return FromBytes(bytes[1], bytes[0]);
return (short)((int)(bytes[1]) | ((int)(bytes[0]) << 8));
}
/// <summary>
/// Converts a S7 Int (2 bytes) to short (Int16)
/// </summary>
public static Int16 FromBytes(byte LoVal, byte HiVal)
{
return (Int16)(HiVal * 256 + LoVal);
}
/// <summary>
/// Converts a short (Int16) to a S7 Int byte array (2 bytes)
@@ -35,16 +28,10 @@ namespace S7.Net.Types
public static byte[] ToByteArray(Int16 value)
{
byte[] bytes = new byte[2];
int x = 2;
long valLong = (long)((Int16)value);
for (int cnt = 0; cnt < x; cnt++)
{
Int64 x1 = (Int64)Math.Pow(256, (cnt));
Int64 x3 = (Int64)(valLong / x1);
bytes[x - cnt - 1] = (byte)(x3 & 255);
valLong -= bytes[x - cnt - 1] * x1;
}
bytes[1] = (byte)((int)value & 0xFF);
bytes[0] = (byte)((int)value >> 8 & 0xFF);
return bytes;
}
@@ -53,10 +40,15 @@ namespace S7.Net.Types
/// </summary>
public static byte[] ToByteArray(Int16[] value)
{
ByteArray arr = new ByteArray();
foreach (Int16 val in value)
arr.Add(ToByteArray(val));
return arr.array;
byte[] bytes = new byte[value.Length * 2];
int bytesPos = 0;
for(int i=0; i< value.Length; i++)
{
bytes[bytesPos++] = (byte)((int)value[i] & 0xFF);
bytes[bytesPos++] = (byte)(((int)value[i] >> 8) & 0xFF);
}
return bytes;
}
/// <summary>
@@ -64,10 +56,12 @@ namespace S7.Net.Types
/// </summary>
public static Int16[] ToArray(byte[] bytes)
{
Int16[] values = new Int16[bytes.Length / 2];
int shortsCount = bytes.Length / 2;
Int16[] values = new Int16[shortsCount];
int counter = 0;
for (int cnt = 0; cnt < bytes.Length / 2; cnt++)
for (int cnt = 0; cnt < shortsCount; cnt++)
values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++] });
return values;

View File

@@ -3,19 +3,14 @@
/// <summary>
/// Contains the methods to convert from S7 strings to C# strings
/// </summary>
public static class String
public class String
{
/// <summary>
/// Converts a string to S7 bytes
/// </summary>
public static byte[] ToByteArray(string value)
{
string txt = (string)value;
char[] ca = txt.ToCharArray();
byte[] bytes = new byte[txt.Length];
for (int cnt = 0; cnt <= ca.Length - 1; cnt++)
bytes[cnt] = (byte)Asc(ca[cnt].ToString());
return bytes;
return System.Text.Encoding.ASCII.GetBytes(value);
}
/// <summary>
@@ -27,13 +22,6 @@
{
return System.Text.Encoding.ASCII.GetString(bytes);
}
private static int Asc(string s)
{
byte[] b = System.Text.Encoding.ASCII.GetBytes(s);
if (b.Length > 0)
return b[0];
return 0;
}
}
}

26
S7.Net/Types/StringEx.cs Normal file
View File

@@ -0,0 +1,26 @@
namespace S7.Net.Types
{
/// <summary>
/// Contains the methods to convert from S7 strings to C# strings
/// there are two kinds how strings a send. This one is with a pre of two bytes
/// they contain the length of the string
/// </summary>
public static class StringEx
{
/// <summary>
/// Converts S7 bytes to a string
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string FromByteArray(byte[] bytes)
{
if (bytes.Length < 2) return "";
int size = bytes[0];
int length = bytes[1];
return System.Text.Encoding.ASCII.GetString(bytes, 2, length);
}
}
}

View File

@@ -13,26 +13,30 @@ namespace S7.Net.Types
public static double FromByteArray(byte[] bytes)
{
double wert = 0;
Int16 value = (Int16)Types.Word.FromBytes(bytes[1], bytes[0]);
string txt = Conversion.ValToBinString(value);
wert = Conversion.BinStringToInt32(txt.Substring(4, 4)) * 100.0;
wert += Conversion.BinStringToInt32(txt.Substring(8, 4)) * 10.0;
wert += Conversion.BinStringToInt32(txt.Substring(12, 4));
switch (txt.Substring(2, 2))
wert = ((bytes[0]) & 0x0F) * 100.0;
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;
switch ((bytes[0] >> 4) & 0x03)
{
case "00":
case 0:
wert *= 0.01;
break;
case "01":
case 1:
wert *= 0.1;
break;
case "10":
case 2:
wert *= 1.0;
break;
case "11":
case 3:
wert *= 10.0;
break;
}
return wert;
}
@@ -42,16 +46,9 @@ namespace S7.Net.Types
public static byte[] ToByteArray(UInt16 value)
{
byte[] bytes = new byte[2];
int x = 2;
long valLong = (long)((UInt16)value);
for (int cnt = 0; cnt < x; cnt++)
{
Int64 x1 = (Int64)Math.Pow(256, (cnt));
bytes[1] = (byte)((int)value & 0xFF);
bytes[0] = (byte)((int)value >> 8 & 0xFF);
Int64 x3 = (Int64)(valLong / x1);
bytes[x - cnt - 1] = (byte)(x3 & 255);
valLong -= bytes[x - cnt - 1] * x1;
}
return bytes;
}

View File

@@ -16,35 +16,30 @@ namespace S7.Net.Types
{
throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes.");
}
// bytes[0] -> HighByte
// bytes[1] -> LowByte
return FromBytes(bytes[1], bytes[0]);
return (UInt16)((bytes[0] << 8) | bytes[1]);
}
/// <summary>
/// Converts a word (2 bytes) to ushort (UInt16)
/// Converts 2 bytes to ushort (UInt16)
/// </summary>
public static UInt16 FromBytes(byte LoVal, byte HiVal)
public static UInt16 FromBytes(byte b1, byte b2)
{
return (UInt16) (HiVal*256 + LoVal);
return (UInt16)((b2 << 8) | b1);
}
/// <summary>
/// Converts a ushort (UInt16) to word (2 bytes)
/// </summary>
public static byte[] ToByteArray(UInt16 value)
{
byte[] bytes = new byte[2];
int x = 2;
long valLong = (long) ((UInt16) value);
for (int cnt = 0; cnt < x; cnt++)
{
Int64 x1 = (Int64) Math.Pow(256, (cnt));
Int64 x3 = (Int64) (valLong/x1);
bytes[x - cnt - 1] = (byte) (x3 & 255);
valLong -= bytes[x - cnt - 1]*x1;
}
bytes[1] = (byte)(value & 0xFF);
bytes[0] = (byte)((value>>8) & 0xFF);
return bytes;
}

Binary file not shown.

View File

@@ -343,7 +343,7 @@
<param name="count">Byte count, if you want to read 120 bytes, set this to 120.</param>
<returns>Returns the bytes in an array</returns>
</member>
<member name="M:S7.Net.Plc.Read(S7.Net.DataType,System.Int32,System.Int32,S7.Net.VarType,System.Int32)">
<member name="M:S7.Net.Plc.Read(S7.Net.DataType,System.Int32,System.Int32,S7.Net.VarType,System.Int32,System.Byte)">
<summary>
Read and decode a certain number of bytes of the "VarType" provided.
This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
@@ -353,6 +353,7 @@
<param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
<param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
<param name="varType">Type of the variable/s that you are reading</param>
<param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
<param name="varCount"></param>
</member>
<member name="M:S7.Net.Plc.Read(System.String)">
@@ -520,13 +521,14 @@
<param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
<returns>NoError if it was successful, or the error is specified</returns>
</member>
<member name="M:S7.Net.Plc.ParseBytes(S7.Net.VarType,System.Byte[],System.Int32)">
<member name="M:S7.Net.Plc.ParseBytes(S7.Net.VarType,System.Byte[],System.Int32,System.Byte)">
<summary>
Given a S7 variable type (Bool, Word, DWord, etc.), it converts the bytes in the appropriate C# format.
</summary>
<param name="varType"></param>
<param name="bytes"></param>
<param name="varCount"></param>
<param name="bitAdr"></param>
<returns></returns>
</member>
<member name="M:S7.Net.Plc.VarTypeToByteLength(S7.Net.VarType,System.Int32)">
@@ -542,6 +544,21 @@
Releases all resources, disonnects from the plc and closes the socket
</summary>
</member>
<member name="T:S7.Net.Types.Bit">
<summary>
Contains the conversion methods to convert Bit from S7 plc to C#.
</summary>
</member>
<member name="M:S7.Net.Types.Bit.FromByte(System.Byte,System.Byte)">
<summary>
Converts a Bit to bool
</summary>
</member>
<member name="M:S7.Net.Types.Bit.ToBitArray(System.Byte[])">
<summary>
Converts an array of bytes to a BitArray
</summary>
</member>
<member name="T:S7.Net.Types.Boolean">
<summary>
Contains the methods to read, set and reset bits inside bytes

Binary file not shown.

View File

@@ -343,7 +343,7 @@
<param name="count">Byte count, if you want to read 120 bytes, set this to 120.</param>
<returns>Returns the bytes in an array</returns>
</member>
<member name="M:S7.Net.Plc.Read(S7.Net.DataType,System.Int32,System.Int32,S7.Net.VarType,System.Int32)">
<member name="M:S7.Net.Plc.Read(S7.Net.DataType,System.Int32,System.Int32,S7.Net.VarType,System.Int32,System.Byte)">
<summary>
Read and decode a certain number of bytes of the "VarType" provided.
This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
@@ -353,6 +353,7 @@
<param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
<param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
<param name="varType">Type of the variable/s that you are reading</param>
<param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
<param name="varCount"></param>
</member>
<member name="M:S7.Net.Plc.Read(System.String)">
@@ -520,13 +521,14 @@
<param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
<returns>NoError if it was successful, or the error is specified</returns>
</member>
<member name="M:S7.Net.Plc.ParseBytes(S7.Net.VarType,System.Byte[],System.Int32)">
<member name="M:S7.Net.Plc.ParseBytes(S7.Net.VarType,System.Byte[],System.Int32,System.Byte)">
<summary>
Given a S7 variable type (Bool, Word, DWord, etc.), it converts the bytes in the appropriate C# format.
</summary>
<param name="varType"></param>
<param name="bytes"></param>
<param name="varCount"></param>
<param name="bitAdr"></param>
<returns></returns>
</member>
<member name="M:S7.Net.Plc.VarTypeToByteLength(S7.Net.VarType,System.Int32)">
@@ -542,6 +544,21 @@
Releases all resources, disonnects from the plc and closes the socket
</summary>
</member>
<member name="T:S7.Net.Types.Bit">
<summary>
Contains the conversion methods to convert Bit from S7 plc to C#.
</summary>
</member>
<member name="M:S7.Net.Types.Bit.FromByte(System.Byte,System.Byte)">
<summary>
Converts a Bit to bool
</summary>
</member>
<member name="M:S7.Net.Types.Bit.ToBitArray(System.Byte[])">
<summary>
Converts an array of bytes to a BitArray
</summary>
</member>
<member name="T:S7.Net.Types.Boolean">
<summary>
Contains the methods to read, set and reset bits inside bytes

Binary file not shown.

View File

@@ -343,7 +343,7 @@
<param name="count">Byte count, if you want to read 120 bytes, set this to 120.</param>
<returns>Returns the bytes in an array</returns>
</member>
<member name="M:S7.Net.Plc.Read(S7.Net.DataType,System.Int32,System.Int32,S7.Net.VarType,System.Int32)">
<member name="M:S7.Net.Plc.Read(S7.Net.DataType,System.Int32,System.Int32,S7.Net.VarType,System.Int32,System.Byte)">
<summary>
Read and decode a certain number of bytes of the "VarType" provided.
This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
@@ -353,6 +353,7 @@
<param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
<param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
<param name="varType">Type of the variable/s that you are reading</param>
<param name="bitAdr">Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.</param>
<param name="varCount"></param>
</member>
<member name="M:S7.Net.Plc.Read(System.String)">
@@ -520,13 +521,14 @@
<param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
<returns>NoError if it was successful, or the error is specified</returns>
</member>
<member name="M:S7.Net.Plc.ParseBytes(S7.Net.VarType,System.Byte[],System.Int32)">
<member name="M:S7.Net.Plc.ParseBytes(S7.Net.VarType,System.Byte[],System.Int32,System.Byte)">
<summary>
Given a S7 variable type (Bool, Word, DWord, etc.), it converts the bytes in the appropriate C# format.
</summary>
<param name="varType"></param>
<param name="bytes"></param>
<param name="varCount"></param>
<param name="bitAdr"></param>
<returns></returns>
</member>
<member name="M:S7.Net.Plc.VarTypeToByteLength(S7.Net.VarType,System.Int32)">
@@ -542,6 +544,21 @@
Releases all resources, disonnects from the plc and closes the socket
</summary>
</member>
<member name="T:S7.Net.Types.Bit">
<summary>
Contains the conversion methods to convert Bit from S7 plc to C#.
</summary>
</member>
<member name="M:S7.Net.Types.Bit.FromByte(System.Byte,System.Byte)">
<summary>
Converts a Bit to bool
</summary>
</member>
<member name="M:S7.Net.Types.Bit.ToBitArray(System.Byte[])">
<summary>
Converts an array of bytes to a BitArray
</summary>
</member>
<member name="T:S7.Net.Types.Boolean">
<summary>
Contains the methods to read, set and reset bits inside bytes