thomas-v2 / S7CommPlusDriver

Development of Communication Driver for Siemens S7-1200/1500 Plcs
GNU Lesser General Public License v3.0
118 stars 33 forks source link

ValueTimestampArray and ValueTimespanArray. #20

Open aarmando73 opened 10 months ago

aarmando73 commented 10 months ago

Here is the code I'm using, you will probably need to verify it before merging...

I shared the ToString method (so I modified also ValueTimestamp and ValueTimeSpan):

public class ValueTimestamp : PValue
{
    UInt64 Value;

    public ValueTimestamp(UInt64 value) : this(value, 0)
    {
    }

    public ValueTimestamp(UInt64 value, byte flags)
    {
        DatatypeFlags = flags;
        Value = value;
    }

    public UInt64 GetValue()
    {
        return Value;
    }

    public override int Serialize(Stream buffer)
    {
        int ret = 0;
        ret += S7p.EncodeByte(buffer, DatatypeFlags);
        ret += S7p.EncodeByte(buffer, Datatype.Timestamp);
        ret += S7p.EncodeUInt64(buffer, Value);
        return ret;
    }

    public static string ToString(UInt64 Value)
    {
        DateTime dt = new DateTime(1970, 1, 1);
        ulong v, ns;
        string fmt;
        v = Value;
        ns = v % 1000000000;
        v /= 1000000000;

        dt = dt.AddSeconds(v);

        if ((ns % 1000) > 0)
        {
            fmt = "{0}.{1:D09}";
        }
        else if ((ns % 1000000) > 0)
        {
            fmt = "{0}.{1:D06}";
            ns /= 1000;
        }
        else if ((ns % 1000000000) > 0)
        {
            fmt = "{0}.{1:D03}";
            ns /= 1000000;
        }
        else
        {
            return dt.ToString();
        }
        return String.Format(fmt, dt.ToString(), ns);
    }
    public override string ToString()
    {
        string str = ToString(Value);
        return "<Value type=\"Timestamp\">" + str + "</Value>";
    }

    public static ValueTimestamp Deserialize(Stream buffer, byte flags)
    {
        UInt64 value;
        S7p.DecodeUInt64(buffer, out value);
        return new ValueTimestamp(value, flags);
    }
}

public class ValueTimestampArray : PValue
{
    UInt64[] Value;

    public ValueTimestampArray(UInt64[] value) : this(value, 0)
    {
    }

    public ValueTimestampArray(UInt64[] value, byte flags)
    {
        DatatypeFlags = flags;
        Value = value;
    }

    public UInt64[] GetValue()
    {
        return Value;
    }

    public override int Serialize(Stream buffer)
    {
        int ret = 0;
        ret += S7p.EncodeByte(buffer, DatatypeFlags);
        ret += S7p.EncodeByte(buffer, Datatype.Timestamp);
        for (int i = 0; i < Value.Length; i++)
        {
            ret += S7p.EncodeUInt64(buffer, Value[i]);
        }
        return ret;
    }

    public override string ToString()
    {
        string s = "<Value type =\"TimestampArray\" size=\"" + Value.Length.ToString() + "\">";
        for (int i = 0; i < Value.Length; i++)
        {
            s += String.Format("<Value>{0}</Value>", ValueTimestamp.ToString(Value[i]));
        }
        s += "</Value>";
        return s;
    }

    public static ValueTimestampArray Deserialize(Stream buffer, byte flags, bool disableVlq)
    {
        UInt64[] value;
        UInt32 size = 0;
        if (!disableVlq)
        {
            S7p.DecodeUInt32Vlq(buffer, out size);
        }
        else
        {
            S7p.DecodeUInt32(buffer, out size);
        }
        value = new UInt64[size];
        for (int i = 0; i < size; i++)
        {
            if (!disableVlq)
            {
                S7p.DecodeUInt64Vlq(buffer, out value[i]);
            }
            else
            {
                S7p.DecodeUInt64(buffer, out value[i]);
            }
        }
        return new ValueTimestampArray(value, flags);
    }
}

public class ValueTimespan : PValue
{
    Int64 Value;

    public ValueTimespan(Int64 value) : this(value, 0)
    {
    }

    public ValueTimespan(Int64 value, byte flags)
    {
        DatatypeFlags = flags;
        Value = value;
    }

    public Int64 GetValue()
    {
        return Value;
    }

    public override int Serialize(Stream buffer)
    {
        int ret = 0;
        ret += S7p.EncodeByte(buffer, DatatypeFlags);
        ret += S7p.EncodeByte(buffer, Datatype.Timespan);
        ret += S7p.EncodeInt64Vlq(buffer, Value);
        return ret;
    }

    public static string ToString(Int64 Value)
    {
        string str;
        long[] divs = { 86400000000000, 3600000000000, 60000000000, 1000000000, 1000000, 1000, 1 };
        string[] vfmt = { "{0}d", "{0:00}h", "{0:00}m", "{0:00}s", "{0:000}ms", "{0:000}us", "{0:000}ns" };
        long val;
        long timespan = Value;
        bool time_negative = false;
        if (timespan == 0)
        {
            str = "LT#000ns";
        }
        else
        {
            if (timespan < 0)
            {
                str = "LT#-";
                time_negative = true;
                for (int i = 0; i < 7; i++)
                {
                    divs[i] = -divs[i];
                }
            }
            else
            {
                str = "LT#";
            }

            for (int i = 0; i < 7; i++)
            {
                val = timespan / divs[i];
                timespan -= val * divs[i];
                if (val > 0)
                {
                    str += String.Format(vfmt[i], (Int32)val);
                    if ((!time_negative && timespan > 0) || (time_negative && timespan < 0))
                    {
                        str += "_";
                    }
                }
            }
        }
        return str;
    }

    public override string ToString()
    {
        string str = ToString(Value);
        return ("<Value type=\"Timespan\">" + str + "</Value>");
    }

    public static ValueTimespan Deserialize(Stream buffer, byte flags, bool disableVlq)
    {
        Int64 value;
        if (!disableVlq)
        {
            S7p.DecodeInt64Vlq(buffer, out value);
        }
        else
        {
            S7p.DecodeInt64(buffer, out value);
        }
        return new ValueTimespan(value, flags);
    }
}

public class ValueTimespanArray : PValue
{
    Int64[] Value;

    public ValueTimespanArray(Int64[] value) : this(value, FLAGS_ARRAY)
    {
    }

    public ValueTimespanArray(Int64[] value, byte flags)
    {
        DatatypeFlags = flags;
        if (value != null)
        {
            Value = new Int64[value.Length];
            Array.Copy(value, Value, value.Length);
        }
    }

    public Int64[] GetValue()
    {
        return Value;
    }

    public override int Serialize(Stream buffer)
    {
        int ret = 0;
        ret += S7p.EncodeByte(buffer, DatatypeFlags);
        ret += S7p.EncodeByte(buffer, Datatype.LReal);
        ret += S7p.EncodeUInt32Vlq(buffer, (uint)Value.Length);
        for (int i = 0; i < Value.Length; i++)
        {
            ret += S7p.EncodeInt64Vlq(buffer, Value[i]);
        }
        return ret;
    }

    public override string ToString()
    {
        string s = "<Value type =\"ValueTimespanArray\" size=\"" + Value.Length.ToString() + "\">";
        for (int i = 0; i < Value.Length; i++)
        {
            s += String.Format("<Value>{0}</Value>", ValueTimespan.ToString(Value[i]));
        }
        s += "</Value>";
        return s;
    }

    public static ValueTimespanArray Deserialize(Stream buffer, byte flags, bool disableVlq)
    {
        Int64[] value;
        UInt32 size = 0;
        if (!disableVlq)
        {
            S7p.DecodeUInt32Vlq(buffer, out size);
        }
        else
        {
            S7p.DecodeUInt32(buffer, out size);
        }
        value = new Int64[size];
        for (int i = 0; i < size; i++)
        {
            if (!disableVlq)
            {
                S7p.DecodeInt64Vlq(buffer, out value[i]);
            }
            else
            {
                S7p.DecodeInt64(buffer, out value[i]);
            }
        }
        return new ValueTimespanArray(value, flags);
    }
}
aarmando73 commented 10 months ago

Sorry, ValueTimeStampArray fix (no Vlq in items):

    public static ValueTimestampArray Deserialize(Stream buffer, byte flags, bool disableVlq)
    {
        UInt64[] value;
        UInt32 size = 0;
        if (!disableVlq)
        {
            S7p.DecodeUInt32Vlq(buffer, out size);
        }
        else
        {
            S7p.DecodeUInt32(buffer, out size);
        }
        value = new UInt64[size];
        for (int i = 0; i < size; i++)
        {
            S7p.DecodeUInt64(buffer, out value[i]);
        }
        return new ValueTimestampArray(value, flags);
    }
thomas-v2 commented 10 months ago

Hi Alberto, thank you for the code, I'll add this. The "disableVlq" option is kind of "addon", which is (so far) only needed in the SystemEvents. And I'm not sure if it's then allowed for all values, or if the Siemens programmer who implemented the SystemEvents, didn't know how other methods do the encoding (it seems to be that there are other codeparts in a similar manner, e.g. switching the endianess).

Is there a way to produce these arrays? I've never seen them.

aarmando73 commented 10 months ago

Yes, with an array of LTIME(ValueTimeSpanArray) and LDT (ValueTimeStampArray) I know, may be it is not really necessary in a SCADA environment, but I'm testing systematically all these data types (combined in different sequences and array or not):

BOOL
BYTE
WORD
DWORD
LWORD
SINT
INT
DINT
USINT
UINT
UDINT
LINT
ULINT
REAL
LREAL
S5TIME
TIME
LTIME
DATE
TIME_OF_DAY (TOD)
TIME_OF_DAY (LTOD)
DATE_AND_TIME (DT)
LDT
DTL
CHAR
WCHAR
STRING
WSTRING 
aarmando73 commented 10 months ago

another error of mine in TimestampArray (forgot array length): public override int Serialize(Stream buffer) { int ret = 0; ret += S7p.EncodeByte(buffer, DatatypeFlags); ret += S7p.EncodeByte(buffer, Datatype.Timestamp); ret += S7p.EncodeUInt32Vlq(buffer, (uint)Value.Length); for (int i = 0; i < Value.Length; i++) { ret += S7p.EncodeUInt64(buffer, Value[i]); } return ret; }__