2
votes

I'm fairly new to C# programming and usually deal with PLC programming and HMI/SCADA systems development. The last one involves only a bit od C/VBS scripting.

Recently, in one of my projects I upgraded/migrated a SCADA systems initially developed in LabVIEW to the Siemens WinCC application. In the LabVIEW project, there was a part responsible for sending tags values (double values bundled together with tags names and converted to string) over TCP socket to another PC running C# application (TCP Client) which was receiving array of bytes and converting it to double value.

LabVIEW TCP Server Example

And part of the code of TCP Client running on another PC.

TcpClient                   _client;
NetworkStream               _stream = null;
byte[]                      _data = new byte[8192];

{...}
try
{
    _client     = new TcpClient(properties.IpAddress, properties.Port);
}
{...}
    try
    {
        _stream     = _client.GetStream();
{...}
        int NumBytesInRecvBuffer = _stream.Read(_data, 0, 8192);

{...}
            if(_data[3] == paramCount)
            {
                for(int i = 0; i < paramCount; i++)
                {
                    double Value = MakeDouble(_data, 4+(i*8));
                    fmacsParams[i].NumericalValue = Value;
                }
            }
    }

MakeDouble() function in TCP Client.

private unsafe double MakeDouble(byte[] data, int StartIndex)
{
    double result;
    byte* b = (byte*)&result;
    b[0] = data[StartIndex + 7];
    b[1] = data[StartIndex + 6];
    b[2] = data[StartIndex + 5];
    b[3] = data[StartIndex + 4];
    b[4] = data[StartIndex + 3];
    b[5] = data[StartIndex + 2];
    b[6] = data[StartIndex + 1];
    b[7] = data[StartIndex + 0];
    return(result);         
}

I managed to develop a simple application in C#, which is a TCP listener and reading the tags values from WinCC Runtime as Object

 object ft107Flow = oType.InvokeMember("GetValue", System.Reflection.BindingFlags.InvokeMethod, null, wincc, new object[] { "FT107_Flow" });

Then it is converted to double and to byte array. Finally all the bytes in the array are swapped with function SwapBytes() and put to data array so it can be sent as byte array over TCP.

{...}
const int paramCount = 30;
double[] WinCC_Tags = new double[paramCount];

WinCC_Tags[0] = Convert.ToDouble(ft107Flow);
{...}
byte[] data = new byte[4 + paramCount * 8];
Array.Clear(data, 0, data.Length);

byte[] UnswappedByte;
byte[] SwappedByte;

data[3] = paramCount;

for (int i = 0; i < paramCount; i++)
{
     int j;

     UnswappedByte = BitConverter.GetBytes(WinCC_Tags[i]);
     SwappedByte = SwapBytes(UnswappedByte, 0);

     j = (4 + i * 8);

     data[j] = SwappedByte[0];
     data[j + 1] = SwappedByte[1];
     data[j + 2] = SwappedByte[2];
     data[j + 3] = SwappedByte[3];
     data[j + 4] = SwappedByte[4];
     data[j + 5] = SwappedByte[5];
     data[j + 6] = SwappedByte[6];
     data[j + 7] = SwappedByte[7];
}

The problem I'm facing at the moment is these values received by client are not the same. It seems like the precision is lost because of this double or even triple conversion (object -> double, double->byte array; byte array -> double).

For example, in WinCC Runtime the tag value reads 15.3. When it is converted to double, it equals 15.300000190734863. Then the data[12..19] = {0x40, 0x2e, 0x99, 0x99, 0xA0, 0x00, 0x00, 0x00}.

The data received by client looks as follows:

_data[12..19] = {0x40, 0x2e, 0x99, 0x99, 0xA0, 0x00, 0x00, 0x00}

Value = 15.001172065734863

It looks like the same data (byte array) are received by client, however the value after conversion does not equal to the value in WinCC Runtime.

Could you please confirm if there is a way of modifying the TCP Listener side so the TCP Client would receive the right data after conversion using MakeDouble() function and without touching TCP Client code (not allowed to modify it).

2
Simple : byte[] data = new byte[] { 0x40, 0x2e, 0x99, 0x99, 0xA0, 0x00, 0x00, 0x00 }; double number = BitConverter.ToDouble(data.Reverse().ToArray(), 0); I get 15.300. - jdweng
His make MakeDouble() function is working fine - Jan-Fokke

2 Answers

1
votes

Instead of swapping bytes yourself I recommend you use this package. It's a BigEdian BinaryReader/Writer

You could use it like this

using (MemoryStream stream = new MemoryStream())
using (BeBinaryReader BeReader = new BeBinaryReader(stream))
{
    stream.Position = 3;
    if(BeReader.ReadByte() == paramCount)
    for (int i = 0; i < paramCount; i++)
    {
       double Value = BeReader.ReadDouble();
       fmacsParams[i].NumericalValue = Value;
    }
 }
0
votes

I'm not super familiar with how C# handles unsafe code and pointers, but if I were answering this from a C perspective, it looks like the 6th byte is being overwritten by something.

The difference between 15.300000190734863 and 15.0011720657349 is {0x00, 0x00, 0x00, 0xa0, 0x99, 0x99, 0x2e, 0x40} and {0x00, 0x00, 0x00, 0xa0, 0x99, 0x00, 0x2e, 0x40}.

Try the following code

    private double MakeDouble(byte[] data, int StartIndex)
    {
        double result;
        byte[] b = new byte[8];
        b[0] = data[StartIndex + 7];
        b[1] = data[StartIndex + 6];
        b[2] = data[StartIndex + 5];
        b[3] = data[StartIndex + 4];
        b[4] = data[StartIndex + 3];
        b[5] = data[StartIndex + 2];
        b[6] = data[StartIndex + 1];
        b[7] = data[StartIndex + 0];
        result = BitConverter.ToDouble(b, 0);
        return (result);
    }