mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 14:28:25 +08:00
Note: This keeps the old methods to be backward compatible. Note: Unforntunatly a lot of whitespace fixes, refactoring and other trivial stuff is included. It was to hard to split of in a seperate commit. Note: Async methods does not use exactly the same structure/signature as the existing methods. "Out" parameters like ReadClass and ReadStruct instead returns the struct in tuple. Async methods also rely on exceptions instead of ErrorCodes to communicate exception states to calling client. * Use TcpClient and use Async methods (ReadAsync/WriteAsync) * Implemnt async methods for all existing methods * Implemnt existing methods using tcpclient. * Split Plc.cs in more files. (Common, Async, Sync, Helpers) * Mark old methods as Obsolete * Split tests in two files * Implement Async tests
480 lines
24 KiB
C#
480 lines
24 KiB
C#
using S7.Net.Types;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Net.Sockets;
|
|
using System.Threading.Tasks;
|
|
|
|
//Implement obsolete synchronous methods here
|
|
namespace S7.Net
|
|
{
|
|
public partial class Plc
|
|
{
|
|
/// <summary>
|
|
/// 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()
|
|
{
|
|
if (Connect() != ErrorCode.NoError)
|
|
{
|
|
return LastErrorCode;
|
|
}
|
|
try
|
|
{
|
|
stream.Write(GetCOPTConnectionRequest(CPU), 0, 22);
|
|
var response = COTP.TPDU.Read(stream);
|
|
if (response.PDUType != 0xd0) //Connect Confirm
|
|
{
|
|
throw new WrongNumberOfBytesException("Waiting for COTP connect confirm");
|
|
}
|
|
|
|
stream.Write(GetS7ConnectionSetup(), 0, 25);
|
|
|
|
var s7data = COTP.TSDU.Read(stream);
|
|
if (s7data == null || s7data[1] != 0x03) //Check for S7 Ack Data
|
|
{
|
|
throw new WrongNumberOfBytesException("Waiting for S7 connection setup");
|
|
}
|
|
MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
|
|
}
|
|
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;
|
|
}
|
|
|
|
private ErrorCode Connect()
|
|
{
|
|
try
|
|
{
|
|
tcpClient = new TcpClient();
|
|
tcpClient.Connect(IP, 102);
|
|
stream = tcpClient.GetStream();
|
|
}
|
|
catch (SocketException sex)
|
|
{
|
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
|
|
if (sex.SocketErrorCode == SocketError.TimedOut)
|
|
{
|
|
LastErrorCode = ErrorCode.IPAddressNotAvailable;
|
|
}
|
|
else
|
|
{
|
|
LastErrorCode = ErrorCode.ConnectionError;
|
|
}
|
|
|
|
LastErrorString = sex.Message;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LastErrorCode = ErrorCode.ConnectionError;
|
|
LastErrorString = ex.Message;
|
|
}
|
|
return LastErrorCode;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
|
/// If the read 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>
|
|
/// <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="count">Byte count, if you want to read 120 bytes, set this to 120.</param>
|
|
/// <returns>Returns the bytes in an array</returns>
|
|
public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count)
|
|
{
|
|
List<byte> resultBytes = new List<byte>();
|
|
int index = startByteAdr;
|
|
while (count > 0)
|
|
{
|
|
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
|
|
var maxToRead = (int)Math.Min(count, MaxPDUSize - 18);
|
|
byte[] bytes = ReadBytesWithSingleRequest(dataType, db, index, maxToRead);
|
|
if (bytes == null)
|
|
return resultBytes.ToArray();
|
|
resultBytes.AddRange(bytes);
|
|
count -= maxToRead;
|
|
index += maxToRead;
|
|
}
|
|
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).
|
|
/// If the read 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>
|
|
/// <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, byte bitAdr = 0)
|
|
{
|
|
int cntBytes = VarTypeToByteLength(varType, varCount);
|
|
byte[] bytes = ReadBytes(dataType, db, startByteAdr, cntBytes);
|
|
|
|
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.
|
|
/// 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>
|
|
/// <returns>Returns an object that contains the value. This object must be cast accordingly.</returns>
|
|
public object Read(string variable)
|
|
{
|
|
var adr = new PLCAddress(variable);
|
|
return Read(adr.dataType, adr.DBNumber, adr.Address, adr.varType, 1, (byte)adr.BitNumber);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and return an object that can be casted to the struct.
|
|
/// </summary>
|
|
/// <param name="structType">Type of the struct to be readed (es.: TypeOf(MyStruct)).</param>
|
|
/// <param name="db">Address of the DB.</param>
|
|
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
|
/// <returns>Returns a struct that must be cast.</returns>
|
|
public object ReadStruct(Type structType, int db, int startByteAdr = 0)
|
|
{
|
|
int numBytes = Struct.GetStructSize(structType);
|
|
// now read the package
|
|
var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes);
|
|
|
|
// and decode it
|
|
return Struct.FromBytes(structType, resultBytes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and returns the struct or null if nothing was read.
|
|
/// </summary>
|
|
/// <typeparam name="T">The struct type</typeparam>
|
|
/// <param name="db">Address of the DB.</param>
|
|
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
|
/// <returns>Returns a nullable struct. If nothing was read null will be returned.</returns>
|
|
public T? ReadStruct<T>(int db, int startByteAdr = 0) where T : struct
|
|
{
|
|
return ReadStruct(typeof(T), db, startByteAdr) as T?;
|
|
}
|
|
|
|
|
|
/// <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.
|
|
/// 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>
|
|
/// <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>The number of read bytes</returns>
|
|
public int ReadClass(object sourceClass, int db, int startByteAdr = 0)
|
|
{
|
|
int numBytes = Class.GetClassSize(sourceClass);
|
|
if (numBytes <= 0)
|
|
{
|
|
throw new Exception("The size of the class is less than 1 byte and therefore cannot be read");
|
|
}
|
|
|
|
// now read the package
|
|
var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes);
|
|
// and decode it
|
|
Class.FromBytes(sourceClass, resultBytes);
|
|
return resultBytes.Length;
|
|
}
|
|
|
|
/// <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.
|
|
/// 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>
|
|
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.
|
|
/// 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>
|
|
public T ReadClass<T>(Func<T> classFactory, int db, int startByteAdr = 0) where T : class
|
|
{
|
|
var instance = classFactory();
|
|
int readBytes = ReadClass(instance, db, startByteAdr);
|
|
if (readBytes <= 0)
|
|
{
|
|
return null;
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
|
/// 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>
|
|
/// <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 write DB1.DBW200, this is 200.</param>
|
|
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
|
/// <returns>NoError if it was successful, or the error is specified</returns>
|
|
public ErrorCode WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
|
|
{
|
|
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)
|
|
{
|
|
return lastError;
|
|
}
|
|
count -= maxToWrite;
|
|
localIndex += maxToWrite;
|
|
}
|
|
return ErrorCode.NoError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a single bit from a DB with the specified index.
|
|
/// </summary>
|
|
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
|
/// <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 write DB1.DBW200, this is 200.</param>
|
|
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
|
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
|
/// <returns>NoError if it was successful, or the error is specified</returns>
|
|
public ErrorCode WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value)
|
|
{
|
|
if (bitAdr < 0 || bitAdr > 7)
|
|
throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr));
|
|
|
|
ErrorCode lastError = WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value);
|
|
if (lastError != ErrorCode.NoError)
|
|
{
|
|
return lastError; }
|
|
|
|
return ErrorCode.NoError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a single bit from a DB with the specified index.
|
|
/// </summary>
|
|
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
|
/// <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 write DB1.DBW200, this is 200.</param>
|
|
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
|
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
|
/// <returns>NoError if it was successful, or the error is specified</returns>
|
|
public ErrorCode WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value)
|
|
{
|
|
if (value < 0 || value > 1)
|
|
throw new ArgumentException("Value must be 0 or 1", nameof(value));
|
|
|
|
return WriteBit(dataType, db, startByteAdr, bitAdr, value == 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes in input an object and tries to parse it to an array of values. This can be used to write many data, all of the same type.
|
|
/// You must specify the memory area type, memory are address, byte start address and bytes count.
|
|
/// If the read 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>
|
|
/// <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="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
|
|
/// <param name="bitAdr">The address of the bit. (0-7)</param>
|
|
/// <returns>NoError if it was successful, or the error is specified</returns>
|
|
public ErrorCode Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
|
|
{
|
|
if (bitAdr != -1)
|
|
{
|
|
//Must be writing a bit value as bitAdr is specified
|
|
if (value is bool)
|
|
{
|
|
return WriteBit(dataType, db, startByteAdr, bitAdr, (bool)value);
|
|
}
|
|
else if (value is int intValue)
|
|
{
|
|
if (intValue < 0 || intValue > 7)
|
|
throw new ArgumentOutOfRangeException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr), nameof(bitAdr));
|
|
|
|
return WriteBit(dataType, db, startByteAdr, bitAdr, intValue == 1);
|
|
}
|
|
throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value));
|
|
}
|
|
return WriteBytes(dataType, db, startByteAdr, GetPackage(value));
|
|
}
|
|
|
|
/// <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 <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>
|
|
/// <returns>NoError if it was successful, or the error is specified</returns>
|
|
public ErrorCode Write(string variable, object value)
|
|
{
|
|
var adr = new PLCAddress(variable);
|
|
return Write(adr.dataType, adr.DBNumber, adr.Address, value, adr.BitNumber);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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>
|
|
/// <returns>NoError if it was successful, or the error is specified</returns>
|
|
public ErrorCode WriteStruct(object structValue, int db, int startByteAdr = 0)
|
|
{
|
|
return WriteStructAsync(structValue, db, startByteAdr).Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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>
|
|
/// <returns>NoError if it was successful, or the error is specified</returns>
|
|
public ErrorCode WriteClass(object classValue, int db, int startByteAdr = 0)
|
|
{
|
|
return WriteClassAsync(classValue, db, startByteAdr).Result;
|
|
}
|
|
|
|
private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, int count)
|
|
{
|
|
byte[] bytes = new byte[count];
|
|
try {
|
|
// first create the header
|
|
int packageSize = 31;
|
|
ByteArray package = new ByteArray(packageSize);
|
|
package.Add(ReadHeaderPackage());
|
|
// package.Add(0x02); // datenart
|
|
package.Add(CreateReadDataRequestPackage(dataType, db, startByteAdr, count));
|
|
|
|
stream.Write(package.array, 0, package.array.Length);
|
|
|
|
var s7data = COTP.TSDU.Read(stream);
|
|
if (s7data == null || s7data[14] != 0xff)
|
|
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
|
|
|
for (int cnt = 0; cnt < count; cnt++)
|
|
bytes[cnt] = s7data[cnt + 18];
|
|
|
|
return bytes;
|
|
}
|
|
catch (SocketException socketException)
|
|
{
|
|
LastErrorCode = ErrorCode.WriteData;
|
|
LastErrorString = socketException.Message;
|
|
return null;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
LastErrorCode = ErrorCode.WriteData;
|
|
LastErrorString = exc.Message;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
|
|
{
|
|
return WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr, value).Result;
|
|
}
|
|
|
|
private ErrorCode WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
|
|
{
|
|
return WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, bitValue).Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads multiple vars in a single request.
|
|
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
|
|
/// Values are stored in the property "Value" of the dataItem and are already converted.
|
|
/// If you don't want the conversion, just create a dataItem of bytes.
|
|
/// DataItems must not be more than 20 (protocol restriction) and bytes must not be more than 200 + 22 of header (protocol restriction).
|
|
/// </summary>
|
|
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read. Maximum 20 dataitems are accepted.</param>
|
|
[Obsolete("Use ReadMultipleVarsAsync. Note: different function signature")]
|
|
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
|
|
int packageSize = 19 + (dataItems.Count * 12);
|
|
Types.ByteArray package = new ByteArray(packageSize);
|
|
package.Add(ReadHeaderPackage(dataItems.Count));
|
|
// package.Add(0x02); // datenart
|
|
foreach (var dataItem in dataItems)
|
|
{
|
|
package.Add(CreateReadDataRequestPackage(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count)));
|
|
}
|
|
|
|
stream.Write(package.array, 0, package.array.Length);
|
|
|
|
var s7data = COTP.TSDU.Read(stream); //TODO use Async
|
|
if (s7data == null || s7data[14] != 0xff)
|
|
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
|
|
|
|
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] = s7data[i + offset];
|
|
}
|
|
|
|
offset += byteCnt + 4;
|
|
|
|
dataItem.Value = ParseBytes(dataItem.VarType, bytes, dataItem.Count);
|
|
}
|
|
}
|
|
catch (SocketException socketException)
|
|
{
|
|
LastErrorCode = ErrorCode.WriteData;
|
|
LastErrorString = socketException.Message;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
LastErrorCode = ErrorCode.WriteData;
|
|
LastErrorString = exc.Message;
|
|
}
|
|
}
|
|
}
|
|
}
|