thomas-v2 / S7CommPlusDriver

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

ValueStruct and OptimizedAddress #23

Open aarmando73 opened 11 months ago

aarmando73 commented 11 months ago

Hello Thomas, while doing my test I discovered following issues, all found empyrically. I know they are all related to struct serialization/deserialization, and you are discouraging this approach, but I need it:

1) In POffsetInfoType_Std OptimizedAddress and NonoptimizedAddress looks inverted...

2) In Value_Struct.Serialize you use a constant Transport_Flag. I discovered that it is 2 for Optimized DBs and 3 for NonOptimized DBs.

thomas-v2 commented 11 months ago

To 1: From what I've seen in my tests, is if Bitoffsetinfo is of type "Classic" (Non-Optimized DB), both addresses give the same value. In optimized DBs, the optimized address follows the principle of packing the variables by their type (and bool takes 1 byte in memory / no bitoffset). And the "NonOptimized" address has the address, which the variable would have if the DB would be "Classic". I haven't tested if you can use the "NonOptimized" address if the DB is "Optimized", I guess you cannot. What do you mean with "inverted", what would be the "inverted" value of 0?

2. My guess is, that the TransportFlags is a bitfield. But I only know 2 bits as I've seen only a change in 2 Bits. Bit 1 seems to be always set, and if bit 10 is set, then there's an additional 32-Bit VLQ value, which for whatever reason a duplicate of the elementcount was. Because of the Bit 10 I guessed the TransportFlags are also a 32 Bit VLQ (with this processing, I get at least no error in Wireshark) If you see a 3 there, then it may be that bit 0 is for NonOptimized DBs. I haven't seen any communication where the bit was set, maybe it's because Siemens clients aren't using read/write on complete structs. At least writing a struct is fiddly, because you have also to set the correct timestamp of the struct description.

aarmando73 commented 11 months ago

To 1: I mean in the deserialization: public static POffsetInfoType_Std Deserialize(Stream buffer, out int length) { int ret = 0; POffsetInfoType_Std oi = new POffsetInfoType_Std();

        ushort v;
        ret += S7p.DecodeUInt16LE(buffer, out v);
        oi.OptimizedAddress = v; <<<<<<
        ret += S7p.DecodeUInt16LE(buffer, out v);
        oi.NonoptimizedAddress = v; <<<<<<

        length = ret;
        return oi;
    }

As I inverted it, I can deserialize correctly the values inside the struct ByteArray.

To 2: My observation is purely empyrical, if I set 2 for OptimizedBD and 3 for NonOptimizedDB it works, I just use the same transport_flag that I found by reading the same struct before. I saw the duplicate of elementCount... really curious...

I can write structs now, I keep the TimeStamp from initial browse at attribute 529. I know it is a hard work, and I need more the read struct feature, but I'm trying to do a complete test.

thomas-v2 commented 11 months ago

Yes, you're right. The order OptimizedAddress / NonoptimizedAddress is different of how I've done it in the Wireshark dissector.

I can change the TransportFlags in ValueStruct to a public accessible variable, then you can set it from outside if you need this.

thomas-v2 commented 11 months ago

I can confirm, that bit 0 in the PackedStructTransportFlags is set, when reading a packed struct from a not-optimized datablock. I'd call this "ClassicNonoptimizedOffsets", as from the explore result, the difference is that in these structs the bit in Bitoffsetinfo for "Classic" is set. In the Attributes the flag for "OptimizedAccess" is set in both cases. Or my interpretation of the flags is not correct.

That's really an odd design. If you want to use this in practice, I'd also check the results of "Retain" settings. As Siemens once stated, it makes at least a difference in plc programming, if this data is passed as reference or copy when one part is retain and another non-retain.

aarmando73 commented 11 months ago

If you need I understood the double elementCount in ValueStruct: S7p.DecodeUInt32Vlq(buffer, out transp_flags); S7p.DecodeUInt32Vlq(buffer, out elementcount); if ((transp_flags & 0x400) != 0) { // Here's an additional counter value, for whatever reason... S7p.DecodeUInt32Vlq(buffer, out elementcount); }

when the object is a StructArray, the first one is the size of the Item in bytes, the second one is the size of the whole array (itemLength*arrayLength)

thomas-v2 commented 10 months ago

I' testing with Plcsim, and I can't produce different values in the fields.

I have a UDT:

TYPE "dt_xxxbxxbwxx" STRUCT xBool1 : Bool; xBool2 : Bool; xBool3 : Bool; bByte4 : Byte; xBool5 : Bool; xBool6 : Bool; bByte7 : Byte; wWord8 : Word; xBool9 : Bool; xBool10 : Bool; END_STRUCT; END_TYPE

Then two datablocks, one "optimized", and one "not-optimized", with each two variables:

Var_dt: "dt_xxxbxxbwxx"; Array_0x3_of_Var_dt : Array[0..3] of "dt_xxxbxxbwxx";

Then I've tried to read:

1: readlist.Add(new ItemAddress("8A0E03E9.C")); // DB1001_OPT.Var_dt 2: readlist.Add(new ItemAddress("8A0E03E9.10")); // DB1001_OPT.Array_0x3_of_Var_dt 3: readlist.Add(new ItemAddress("8A0E03E9.10.0.1")); // DB1001_OPT.Array_0x3_of_Var_dt[0] 4: readlist.Add(new ItemAddress("8A0E03EA.C")); // DB1002_NOPT.Var_dt 5: readlist.Add(new ItemAddress("8A0E03EA.10")); // DB1002_NOPT.Array_0x3_of_Var_dt 6: readlist.Add(new ItemAddress("8A0E03EA.10.0.1")); // DB1002_NOPT.Array_0x3_of_Var_dt[0]

Read result: 1: Datatypeflags: 0x00, Transportflags: 0x0002, ElementCount: 11 2: failed to read 3: Datatypeflags: 0x10, Transportflags: 0x0402, ElementCount: 11, ElementCount2: 11 4: Datatypeflags: 0x00, Transportflags: 0x0003, ElementCount: 7 5: failed to read 6: Datatypeflags: 0x10, Transportflags: 0x0403, ElementCount: 7, ElementCount2: 7

Why in 3 and 6 in the datatype-flag the bit for "Array" is set, I don't know. All 4 results sets contain only one single struct dataset. This is again completely different to all other arrays. Maybe thats's why Siemens isn't using this.

thomas-v2 commented 10 months ago

Another test, with flags are set different, but result is the same:

readlist.Add(new ItemAddress("8A0E03E9.C")); // DB1001_OPT.Var_dt readlist.Add(new ItemAddress("8A0E03E9.10")); // DB1001_OPT.Array_0x3_of_Var_dt readlist.Add(new ItemAddress("8A0E03E9.10.0")); // DB1001_OPT.Array_0x3_of_Var_dt readlist.Add(new ItemAddress("8A0E03E9.10.0.1")); // DB1001_OPT.Array_0x3_of_Var_dt[0] readlist.Add(new ItemAddress("8A0E03EA.C")); // DB1002_NOPT.Var_dt readlist.Add(new ItemAddress("8A0E03EA.10")); // DB1002_NOPT.Array_0x3_of_Var_dt readlist.Add(new ItemAddress("8A0E03EA.10.0")); // DB1002_NOPT.Array_0x3_of_Var_dt readlist.Add(new ItemAddress("8A0E03EA.10.0.1")); // DB1002_NOPT.Array_0x3_of_Var_dt[0]

1: Datatypeflags: 0x00, Transportflags: 0x0002, ElementCount: 11 2: failed 3: Datatypeflags: 0x00, Transportflags: 0x0002, ElementCount: 11 4: Datatypeflags: 0x10, Transportflags: 0x0402, ElementCount: 11, ElementCount2: 11 5: Datatypeflags: 0x00, Transportflags: 0x0003, ElementCount: 7 6: failed 7: Datatypeflags: 0x00, Transportflags: 0x0003, ElementCount: 7 8: Datatypeflags: 0x10, Transportflags: 0x0403, ElementCount: 7, ElementCount2: 7

The plc doesn support to give a complete array of struct as response, When the array flag is set, it's not an array (so for now I raise an exception). Or do other plc types give different results?

aarmando73 commented 10 months ago

Thanks, it's interesting. I can confirm that I cannot read an Array of STRUCT, I tried several options, but it does not work and I have to read each single ITEM. ElementCount is the size of the ByteArray result of STRUCT serialization. It is curious that an Array of DTL (that return a Struct as value) can be read... Bit "Array" may be it is "It is an ArrayItem"... ?

thomas-v2 commented 10 months ago

Array of DTL can be read. Don't know why, as it's nothing more than a struct/udt.

Also I've made a test reading Array of Bools. The result is different when the data is inside a optimized or not-optimized datablock. The result is, that if you want to read an array, you must know in advance if the data is located in an optimized or not-optimized datablock, as you can't see this in the result.

1: readlist.Add(new ItemAddress("8A0E03E9.16")); // DB1001_OPT.Array_0x3_of_DTL 2: readlist.Add(new ItemAddress("8A0E03E9.16.0")); // DB1001_OPT.Array_0x3_of_DTL 3: readlist.Add(new ItemAddress("8A0E03E9.16.0.1")); // DB1001_OPT.Array_0x3_of_DTL[0] 4: readlist.Add(new ItemAddress("8A0E03EA.14")); // DB1002_NOPT.Array_0x3_of_DTL 5: readlist.Add(new ItemAddress("8A0E03EA.14.0")); // DB1002_NOPT.Array_0x3_of_DTL 6: readlist.Add(new ItemAddress("8A0E03EA.14.0.1")); // DB1002_NOPT.Array_0x3_of_DTL[0]

7: readlist.Add(new ItemAddress("8A0E03E9.19")); // DB1001_OPT.Array_0x9_of_Bool 8: readlist.Add(new ItemAddress("8A0E03E9.19.0")); // DB1001_OPT.Array_0x9_of_Bool[0] 9: readlist.Add(new ItemAddress("8A0E03EA.16")); // DB1002_NOPT.Array_0x9_of_Bool 10:readlist.Add(new ItemAddress("8A0E03EA.16.0")); // DB1002_NOPT.Array_0x9_of_Bool[0]

Results: 1: Datatypeflags: 0x10, Transportflags: 0x0402, ElementCount: 12, ElementCount2: 48 2: Datatypeflags: 0x00, Transportflags: 0x0002, ElementCount: 12 3: Datatypeflags: 0x10, Transportflags: 0x0402, ElementCount: 12, ElementCount2: 12 4: Datatypeflags: 0x10, Transportflags: 0x0403, ElementCount: 12, ElementCount2: 48 5: Datatypeflags: 0x00, Transportflags: 0x0003, ElementCount: 12 6: Datatypeflags: 0x10, Transportflags: 0x0403, ElementCount: 12, ElementCount2: 12

7: Datatypeflags: 0x10, Bool-Array[16] 8: Datatypeflags: 0x00, Bool 9: Datatypeflags: 0x10, Byte Array[2] 10:Datatypeflags: 0x00, Bool

aarmando73 commented 10 months ago

Thanks, I already analyzed bool reading and I confirm your job. With single BOOL I always receive a ValueBool. With Array of BOOL, I receive a ValueBoolArray in OptimizedAccess and a ValueByteArray in NonOptimizedAccess (some easy bitwise job to do).

By analyzing the OptimizedAddress and NonOptimizedAddress I have also a similar difference: OPtimized >> one byte for each bool / NonOptimized >> one byte every 8 bools with bitwise values.

I can read/write a Struct Array if it is inside another struct, but with byteArray deserialization... ;-)

I use attribute 1502 to know the byteArray size of one struct node, while when in Array Struct I can also check OptimizedStructSize and NonoptimizedStructSize.

thomas-v2 commented 10 months ago

I think it should be enough to use the softdatatype. In an optimized DB it's BBOOL (Byte Bool?) and in an not-optimized it's Bool. That's how I differ between the ID calculation on browsing the plc, as the last access ID is also calculated different.

zhongjanwen commented 10 months ago

Hello, the issue I encountered during testing, as mentioned by the owner, is that I am unable to read the array of the structure 1705107945827 1705107969678 1705108003313

thomas-v2 commented 10 months ago

Should be fixed. The calculation for the access string was decimal and not hexadecimal, so the generated addresses were wrong.