apache / plc4x

PLC4X The Industrial IoT adapter

Home Page:https://plc4x.apache.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: PLC4X - 0.11.0 - Issue writing value to a Beckhoff PLC using ads driver

patrickboisclair opened this issue · comments

What happened?

Hi all,

I upgraded from version 0.9.1 to 0.11.0 and I cannot write to my Beckhoff PLC anymore.

When I write 1 to a DINT, 16777216 is stored.

Is it a BIG-ENDIAN vs LITTLE-ENDIAN issue ?
Is it possible to specify the “endianness” of the PLC ?

According to the TWINCat doc, it seems they use LITTLE-ENDIAN.

The PLC type is a "CX8190 / Standard

Attached is 3 REALLY simple Java application.
Write "1" to a DINT and read it back.

First with version 0.9.1 -> WORKS
Second with version 0.10.0 -> DOESN'T WORK (same issue) Third with version 0.11.0 -> DOESN'T WORK (same issue)

I did upgrade our "big" project from 0.9.1 to 0.11.0 yesterday, so I did not notice the issue with 0.10.0... my bad on this.

Hope it can help, bring some light as what changed from 0.9.1 to 0.10.0 and 0.11.0

I will work on getting a Wireshark log during the day and will do more testing.
plc4xTests.zip

Version

v0.11.0

Programming Languages

  • plc4j
  • plc4go
  • plc4c
  • plc4net

Protocols

  • AB-Ethernet
  • ADS /AMS
  • BACnet/IP
  • CANopen
  • DeltaV
  • DF1
  • EtherNet/IP
  • Firmata
  • KNXnet/IP
  • Modbus
  • OPC-UA
  • S7

@patrickboisclair can you check 0.11 with different syntax of tag - i.e. MAIN.Valeur:DINT/MAIN.ValeurDINT:DINT? Currently your tag does not specify type, I believe it should be resolved through symbol lookup, its just to ensure its not a typo. Sorry for asking about extra verification step.
While there were changes in how tags are handled between versions you tested, tag syntax remains similar.

I'll try, think I get "invalid tag", but I will test it out right now

I get the following exception:
xception in thread "main" org.apache.plc4x.java.api.exceptions.PlcInvalidTagException: Couldn't resolve symbolic address: MAIN.ValeurDINT:DINT invalid
at org.apache.plc4x.java.ads.protocol.AdsProtocolLogic.getDirectAdsTagForSymbolicName(AdsProtocolLogic.java:1689)
at org.apache.plc4x.java.ads.protocol.AdsProtocolLogic.lambda$110(AdsProtocolLogic.java:1467)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.apache.plc4x.java.ads.protocol.AdsProtocolLogic.getDirectAddresses(AdsProtocolLogic.java:1468)
at org.apache.plc4x.java.ads.protocol.AdsProtocolLogic.write(AdsProtocolLogic.java:935)
at org.apache.plc4x.java.spi.connection.AbstractPlcConnection.write(AbstractPlcConnection.java:188)
at org.apache.plc4x.java.spi.messages.DefaultPlcWriteRequest.execute(DefaultPlcWriteRequest.java:58)
at com.noovelia.TestBeckhoff.test(TestBeckhoff.java:27)
at com.noovelia.Main.main(Main.java:10)

The tag in the PLC is named ValeurDINT

Yeah, I see now why you got it without :DINT. There was a change in how symbolic tags are handled, they got smarter from 0.8 release where, beyond symbol name, its type had to be specified.

Please check and/or adjust following code:

try (PlcConnection plcConnection = new DefaultPlcDriverManager().getConnection(connectionString)) {
    PlcBrowseResponse response = plcConnection.browseRequestBuilder().addQuery("symbols", "")
        .build().execute().get();
    for (PlcBrowseItem symbol : response.getValues("symbols")) {
        System.out.println(symbol.getName() + " " + symbol.getTag() + " " + symbol.getOptions());
    }
}

It should print you all tags which got detected when connection was established. I don't think any more that discovery is a problem, its rather question of type mapping and/or eventual encoding.

Yep, it works:

Constants.bMulticoreSupport SymbolicAdsTag{symbolicAddress='Constants.bMulticoreSupport'} {group-id=16448, offset=385430, size-in-bytes=1, comment=""}
MAIN.E_reset SymbolicAdsTag{symbolicAddress='MAIN.E_reset'} {group-id=61472, offset=385293, size-in-bytes=1, comment=""}
 Fix Patrick Boisclair  SymbolicAdsTag{symbolicAddress='MAIN.I_Lum_1'} {group-id=16448, offset=385291, size-in-bytes=1, comment=" Fix Patrick Boisclair "}
MAIN.Fb_Rand SymbolicAdsTag{symbolicAddress='MAIN.Fb_Rand'} {group-id=16448, offset=384728, size-in-bytes=544, comment=""}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.CompilerVersionNumeric'} {group-id=16448, offset=385436, size-in-bytes=4, comment=" Does the target support multiple cores?"}
Timer 3000ms SymbolicAdsTag{symbolicAddress='MAIN.T_Rand'} {group-id=16448, offset=384720, size-in-bytes=4, comment="Timer 3000ms"}
TwinCAT_SystemInfoVarList._TaskPouOid_PlcTask SymbolicAdsTag{symbolicAddress='TwinCAT_SystemInfoVarList._TaskPouOid_PlcTask'} {group-id=16448, offset=385824, size-in-bytes=4, comment=""}
MAIN.RAND SymbolicAdsTag{symbolicAddress='MAIN.RAND'} {group-id=16448, offset=385280, size-in-bytes=8, comment=""}
Global_Version.stLibVersion_Tc2_Utilities SymbolicAdsTag{symbolicAddress='Global_Version.stLibVersion_Tc2_Utilities'} {group-id=16448, offset=384036, size-in-bytes=36, comment=""}
TwinCAT_SystemInfoVarList._TaskOid_PlcTask SymbolicAdsTag{symbolicAddress='TwinCAT_SystemInfoVarList._TaskOid_PlcTask'} {group-id=16448, offset=385828, size-in-bytes=4, comment=""}
 Signal d'arr?t de la navette de transition si navette r?frig?r?e est pr?sente dans zone commune SymbolicAdsTag{symbolicAddress='Variables_Globales_GOX.bARRETZONECOMMNAVTRANSIT'} {group-id=16448, offset=385301, size-in-bytes=1, comment=" Signal d'arr?t de la navette de transition si navette r?frig?r?e est pr?sente dans zone commune"}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.bFPUSupport'} {group-id=16448, offset=385425, size-in-bytes=1, comment=" Does the target support multiple cores?"}
Global_Version.stLibVersion_Tc3_Module SymbolicAdsTag{symbolicAddress='Global_Version.stLibVersion_Tc3_Module'} {group-id=16448, offset=384108, size-in-bytes=36, comment=""}
Entr?e autoriant la g?n?ration d'un nombre al?atoire par Random3 SymbolicAdsTag{symbolicAddress='MAIN.E_Rand'} {group-id=61472, offset=385288, size-in-bytes=1, comment="Entr?e autoriant la g?n?ration d'un nombre al?atoire par Random3"}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.CompilerVersion'} {group-id=16448, offset=385416, size-in-bytes=8, comment=" Does the target support multiple cores?"}
MAIN.C_Lum SymbolicAdsTag{symbolicAddress='MAIN.C_Lum'} {group-id=16448, offset=385296, size-in-bytes=4, comment=""}
 Fix Patrick Boisclair  SymbolicAdsTag{symbolicAddress='MAIN.I_reset'} {group-id=16448, offset=385294, size-in-bytes=1, comment=" Fix Patrick Boisclair "}
MAIN.S_Lum_2 SymbolicAdsTag{symbolicAddress='MAIN.S_Lum_2'} {group-id=61488, offset=385292, size-in-bytes=1, comment=""}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.bLittleEndian'} {group-id=16448, offset=385303, size-in-bytes=1, comment=" Does the target support multiple cores?"}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.RuntimeVersion'} {group-id=16448, offset=385408, size-in-bytes=8, comment=" Does the target support multiple cores?"}
 Structure qui contient les donn?es de d?placement de la navette r?frig?r?e SymbolicAdsTag{symbolicAddress='Variables_Globales_GOX.stCOMMANDENAVETREFRIG'} {group-id=16448, offset=385360, size-in-bytes=48, comment=" Structure qui contient les donn?es de d?placement de la navette r?frig?r?e"}
Global_Version.stLibVersion_Tc2_Standard SymbolicAdsTag{symbolicAddress='Global_Version.stLibVersion_Tc2_Standard'} {group-id=16448, offset=384000, size-in-bytes=36, comment=""}
Global_Version.stLibVersion_Tc2_System SymbolicAdsTag{symbolicAddress='Global_Version.stLibVersion_Tc2_System'} {group-id=16448, offset=384072, size-in-bytes=36, comment=""}
MAIN.ValeurDINT SymbolicAdsTag{symbolicAddress='MAIN.ValeurDINT'} {group-id=16448, offset=394112, size-in-bytes=4, comment=""}
Nombre al?atoire entre 1-5000 ? toutes les 1s SymbolicAdsTag{symbolicAddress='MAIN.Rand_2'} {group-id=16448, offset=385272, size-in-bytes=4, comment="Nombre al?atoire entre 1-5000 ? toutes les 1s"}
Nombre al?atoire entre 1-5000 ? toutes les 10ms SymbolicAdsTag{symbolicAddress='MAIN.Rand_1'} {group-id=16448, offset=384724, size-in-bytes=4, comment="Nombre al?atoire entre 1-5000 ? toutes les 10ms"}
Nombre al?atoire entre 0-60 SymbolicAdsTag{symbolicAddress='MAIN.Rand_3'} {group-id=16448, offset=385276, size-in-bytes=4, comment="Nombre al?atoire entre 0-60"}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.nPackMode'} {group-id=16448, offset=385428, size-in-bytes=2, comment=" Does the target support multiple cores?"}
 Fix Patrick Boisclair  SymbolicAdsTag{symbolicAddress='MAIN.I_test'} {group-id=16448, offset=385300, size-in-bytes=1, comment=" Fix Patrick Boisclair "}
 Fix Patrick Boisclair  SymbolicAdsTag{symbolicAddress='MAIN.I_Rand'} {group-id=16448, offset=385289, size-in-bytes=1, comment=" Fix Patrick Boisclair "}
Timer 10ms SymbolicAdsTag{symbolicAddress='MAIN.T_2'} {group-id=16448, offset=384712, size-in-bytes=4, comment="Timer 10ms"}
Timer 1000ms SymbolicAdsTag{symbolicAddress='MAIN.T_3'} {group-id=16448, offset=384716, size-in-bytes=4, comment="Timer 1000ms"}
La SymbolicAdsTag{symbolicAddress='MAIN.T_1'} {group-id=16448, offset=384708, size-in-bytes=4, comment="La"}
TwinCAT_SystemInfoVarList._AppInfo SymbolicAdsTag{symbolicAddress='TwinCAT_SystemInfoVarList._AppInfo'} {group-id=16448, offset=385440, size-in-bytes=256, comment=""}
MAIN.E_Lum_1 SymbolicAdsTag{symbolicAddress='MAIN.E_Lum_1'} {group-id=61472, offset=385290, size-in-bytes=1, comment=""}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.bSimulationMode'} {group-id=16448, offset=385424, size-in-bytes=1, comment=" Does the target support multiple cores?"}
TwinCAT_SystemInfoVarList.__PlcTask SymbolicAdsTag{symbolicAddress='TwinCAT_SystemInfoVarList.__PlcTask'} {group-id=16448, offset=385832, size-in-bytes=88, comment=""}
MAIN.Patrick SymbolicAdsTag{symbolicAddress='MAIN.Patrick'} {group-id=16448, offset=462576, size-in-bytes=11, comment=""}
 Structure qui contient les donn?es de d?placement de la navette de transition SymbolicAdsTag{symbolicAddress='Variables_Globales_GOX.stCOMMANDENAVETTRANSIT'} {group-id=16448, offset=385312, size-in-bytes=48, comment=" Structure qui contient les donn?es de d?placement de la navette de transition"}
MAIN.E_test SymbolicAdsTag{symbolicAddress='MAIN.E_test'} {group-id=61472, offset=385295, size-in-bytes=1, comment=""}
TwinCAT_SystemInfoVarList._TaskInfo SymbolicAdsTag{symbolicAddress='TwinCAT_SystemInfoVarList._TaskInfo'} {group-id=16448, offset=385696, size-in-bytes=128, comment=""}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.nRegisterSize'} {group-id=16448, offset=385426, size-in-bytes=2, comment=" Does the target support multiple cores?"}
 Does the target support multiple cores? SymbolicAdsTag{symbolicAddress='Constants.RuntimeVersionNumeric'} {group-id=16448, offset=385432, size-in-bytes=4, comment=" Does the target support multiple cores?"}

I did notice tho, that since 0.11.0, in order to write a value, we need to use:
writeBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT", new PlcDINT(value));

Prior to 0.11.0:
writeBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT", value);

If we dont use a PlcXXX type, it write Null

Looks like when writing a tag, without specifying a PLCxxx datatype, in the class "PlcValueHandler.java":
public static PlcValue of(PlcTag tag, Object[] values) {
if (values.length == 1) {
Object value = values[0];
if(tag.getPlcValueType() == null) {
// TODO: This is a hacky shortcut ..
if(value instanceof PlcValue) {
return (PlcValue) value;
}
return new PlcNull();
}

the if (tag.getPlcValueType()... always return null

So if you do this it works:

writeBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT", new PlcDINT(value));
And if you do this, it fails?

writeBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT", value);

Well works, let me explain:

writeBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT", new PlcDINT(value));
Passing a "new PlcXXX(value)" write a value to the PLC, but not the good one as mentionned above.
Example: writing 1 writes 16777216 with this syntax.

writeBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT", value);
Passing a value without a "PlcTYPE" parameter, always writes null to the Plc

Checking the code in PlcValueHandler.java:

public static PlcValue of(PlcTag tag, Object[] values) {
if (values.length == 1) {
    Object value = values[0];
    if(tag.getPlcValueType() == null) {
    // TODO: This is a hacky shortcut ..
   if(value instanceof PlcValue) {
         return (PlcValue) value;
   }
   return new PlcNull();
}

Passing the "new PlcDINT(value)" is "fixed" by the "hacky shortcut, mentionned in the comment of the code.
Its becausse:
the if (tag.getPlcValueType()... always return null

Hope it make senses. Let me know, if you need more infos / tests on this.

Well I guess a real fix would be to automatically create a PLCValue of a type that certainly can fit the value and to add an additional check on "isUInt()" before accessing it. Problem is that while assembling the request with "addTagAddress" the tag is not yet resolved and we therefore don't know the type yet.

I'll try to whip up a fix for this asap ...

Hi Chris,
Thank you you very much. When the fix will be available Im willing to test it.
Sorry for the delay, had some busy days.

Well ... if the world would let me have a bit control over my free time I could say something about that ... but for now I'll just say: It's done when it's done :-/

Just as 2023 was a pretty turbulent year ... is this still an issue?

In my testsuite, that I'm running against my ADS device I'm reading each supported datatype, then write it back, then do 100 reads of all fields at the same time, but with random order ... so writing generally should work.

Have you tried with the latest SNAPSHOT version? A lot of work has been put into that.

And ideally, if it doesn't work ... please create a Wireshark recording of the interaction and attach it to this issue.

pc4x_11_WireShark.zip

Hi Chris,
Attached is the WireShark results of my test. I ran the test twice,

Here is the Java code:
`
final DefaultPlcDriverManager driver = new DefaultPlcDriverManager();
final PlcConnection plcConnection = driver.getConnection("ads:tcp://10.10.11.77?sourceAmsNetId=172.29.48.1.1.1&sourceAmsPort=34&targetAmsNetId=5.62.206.200.1.1&targetAmsPort=851");

    final Integer expectedValue = 1;

    // Write value to plc
    PlcWriteRequest.Builder writeBuilder = plcConnection.writeRequestBuilder();
    writeBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT", new PlcDINT(expectedValue)); // With PLC4X 0.11.0, we need to pass a PlcXXX object, otherwise null is used ?

    PlcWriteRequest writeRequest = writeBuilder.build();
    writeRequest.execute().get();

    // Read the written value
    PlcReadRequest.Builder reaBuilder = plcConnection.readRequestBuilder();
    reaBuilder.addTagAddress("ValueDINT", "MAIN.ValeurDINT");

    PlcReadRequest readRequest = reaBuilder.build();
    PlcReadResponse response = readRequest.execute().get();

    Integer result = response.getInteger("ValueDINT");

    System.out.println("Read value: " + result.toString());

`

As you can see, I write 1 and read it back, and of course I expect 1 as the result, but I get :
Read value: 16777216

If you need anything else, pls let me know.

Best regards !

PLC4X11_WrittenValue_TwinCAT

Oh and here is a screenshot of what is currently in the PLC using TwinCAT

NOTE: Using PLC4X 0.9.1 works as expected

Ok ... I can confirm that I am getting an odd value back ... but the funny thing is, the write request and the read response are absolutely correct (The screenshots are from your recording)
ads-write
ads-read
I'll look into this.

Ok ... so I think I have something here ;-) ... so if I enter 16777216 into my calculator and switch to hex, I get 0x01000000 ... so this is an endianess problem ... I really wonder why this has never been seen before cause I'm running an excessive testsuite on the ADS ... I would totally freak out, if the value I made up is accidentally an endianess-palindrome.

If you are write a value to the PLC and then reading that value back, I suspect you don't actually test endianess, just the fact that you can write the data and read the same data back.

What confuses me is that it would seem that the endianess of a Twincat system seems to be dependent on the architecture it is running on?

I'm just completely confused ... so in my tests I'm reading a value pre-defined in the PLC program and it's reading -242442424 = which is in hex: 0xF18C9F48 which I can see on the wire as 0x489F8CF1 ... I'm getting it in the exact same value and when I'm sending it back, I see the same values being sent to the PLC and the test then reads again and it's not changed.

Here however 1 is encoded as 0x00000001 which is not correct, it should be 0x01000000 ... I'll debug through this.

What value is it when you view it in Twincat though?

Well the thing is ... I was missing a "LittleEndian" switch in the parsePlcValue method. Now with this, the "writing of 1" works and still my big-ass test seems to work.

I've pushed a fix, so please pull and rebuild and try the SNAPSHOT. I'll continue trying to find out why it's not having any impact on my big test.

With my tests I have the expected values hard-coded in my PLC program and I read everything separately in a single element read and compare it with the expected value. If that matches, I write the same value back. If this has worked for all types, I then read all elements in a multi-element read and compare the results with the expected ones again. I am a bit frustrated, that this hasn't been revealed before ... I mean ... writing a single 1 and then reading it back should be a super-basic test.

I've got today an report of broken writes within AMSADS binding I am doing for openHAB. The target device is an ancient BX9000 with TwinCAT2. Binding is already on plc4x 0.11, with all discovery stuff we had prior release. I am cleaning my desk to check this report, so your findings are definitely interesting. I'll be able to check if I get a proper error answers, cause TC2 seem to have its own quirks.

I can imagine that with TC2 a lot of things are a lot different ... so if you find out things are not compatible with the TC3 driver, that I am building, we should probably have multiple variants.

Hi Chris,
I tried with the 0.12.0-SNAPSHOT and Im happy to say that it seems to work !

Thank you very much for your help, really appreciated !

Always happy to help ... and thanks for reporting and helping us find this issue.

And if you want to help any further ... the best thing you could do, would be to publicly talk about PLC4X ;-)