apache / plc4x

PLC4X The Industrial IoT adapter
https://plc4x.apache.org/
Apache License 2.0
1.19k stars 388 forks source link

[Bug]: [PLC4J] [EtherNet/IP] All values read are null for Allen-Bradley PLC with EtherNet/IP or Logix #1646

Open meichf opened 3 weeks ago

meichf commented 3 weeks ago

What happened?

I wrote two programs according to the documentation on the official website of PLC4X to read Allen Bradley PLC, model Logix5571, but all the values read were null.

Here is the code. The result is that all four tag values in asPlcValue are null.

code for EtherNet/IP protocol:

    String connectionString = "eip://192.168.0.2:44818?slot=0&bigEndian=false";

    try (PlcConnection plcConnection = PlcDriverManager.getDefault().getConnectionManager().getConnection(connectionString)) {
        final PlcReadRequest.Builder readrequest = plcConnection.readRequestBuilder();

    //(3.1)
        readrequest.addTagAddress("tag1", "%A:DINT");
        readrequest.addTagAddress("tag2", "%B:INT");
        readrequest.addTagAddress("tag3", "%C:SINT");
        readrequest.addTagAddress("tag4", "%D:REAL");

        //(3.2)
        final PlcReadRequest rr = readrequest.build();
        //(3.3)
        final PlcReadResponse szlresponse = rr.execute().get();

        PlcValue asPlcValue = szlresponse.getAsPlcValue();

    } catch (PlcConnectionException e) {
        throw new RuntimeException(e);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

code for Logix protocol:

   String connectionString = "logix://192.168.0.2:44818?slot=0&bigEndian=false";

    try (PlcConnection plcConnection = PlcDriverManager.getDefault().getConnectionManager().getConnection(connectionString)) {
        PlcConnectionMetadata metadata = plcConnection.getMetadata();
        final PlcReadRequest.Builder readrequest = plcConnection.readRequestBuilder();

        //(3.1)
        readrequest.addTagAddress("tag1", "A:DINT");
        readrequest.addTagAddress("tag2", "B:INT");
        readrequest.addTagAddress("tag3", "C:SINT");
        readrequest.addTagAddress("tag4", "D:REAL");

        //(3.2)
        final PlcReadRequest rr = readrequest.build();
        //(3.3)
        final PlcReadResponse szlresponse = rr.execute().get();
        //(3.4)
        PlcValue asPlcValue = szlresponse.getAsPlcValue();
        log.info("Tags: " + asPlcValue);
    } catch (PlcConnectionException e) {
        throw new RuntimeException(e);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }`

here is the tags:

tags

Version

v0.12.0

Programming Languages

Protocols

chrisdutz commented 2 weeks ago

So I've just created a ManualTest for my Logix device. I created several Controller Tags (Including UDTs) They are all called "hurz_{datatypeName}" for simplicity. the Following program works when reading:

    public static void main(String[] args) throws Exception {
        ManualEipLogixDriverTest test = new ManualEipLogixDriverTest("logix://192.168.23.40");
        // This is the very limited number of types my controller supports.
        test.addTestCase("hurz_BOOL", new PlcBOOL(true));
        test.addTestCase("hurz_SINT", new PlcSINT(-42));
        test.addTestCase("hurz_INT", new PlcINT(-2424));
        test.addTestCase("hurz_DINT", new PlcDINT(-242442424));
        test.addTestCase("hurz_REAL", new PlcREAL(3.141593F));
        //test.addTestCase("hurz_UDT", new PlcStruct());

        long start = System.currentTimeMillis();
        test.run();
        long end = System.currentTimeMillis();
        System.out.printf("Finished in %d ms", end - start);
    }

However I can see the UDT being correctly sent on the wire using WireShark, just it seems that decoding it is a problem for the driver. Also can't I write. As soon as I try that I get errors.

chrisdutz commented 2 weeks ago

So if I look at your code, omit the "%" and the datatypes. Just "A", "B", "C", or "D" should be enough.

chrisdutz commented 2 weeks ago

Ok ... I learned something more ... when reading, you can omit the datatype ... however when writing it seems to be mandatory. I think that in general we will have to implement some sort of "load the symbol table when connecting" similar to what we do for ADS and use that information throughout the session ... especially for UDTs I otherwise have no way to process them correctly.

meichf commented 1 week ago

So if I look at your code, omit the "%" and the datatypes. Just "A", "B", "C", or "D" should be enough.

I‘ve tried this, but it doesn't work

chrisdutz commented 1 week ago

Are the tags you are trying to read controller tags or program tags? I tried with my logix device and above only worked for controller tags.

meichf commented 1 week ago

Are the tags you are trying to read controller tags or program tags? I tried with my logix device and above only worked for controller tags. It's controller tags. My device is the Allen Bradley 1756-L71, I don't know if the logix protocol is valid for this device.

hutcheb commented 1 week ago

The L71 is a little older than the one Chris has, it is probably running v20 and I think he should be using v35 or something. Can you confirm which version? There is probably going to be differences between v20 and the higher versions.

meichf commented 1 week ago

s Do you mean programming tools? I'm using the studio 5000, v33.

hutcheb commented 1 week ago

Then thats probably not the issue, Which version of PLC4X are you using?

meichf commented 1 week ago

I have tried both 0.12.0 and 0.13.0-SNAPSHOT

meichf commented 1 week ago

I have identified the problem, it works on version 0.11.0 .

chrisdutz commented 1 week ago

I think that is just plain EIP/CIP and no Logix extensions, right? I think we completely rewrote the driver for 0.12.0

hutcheb commented 1 week ago

@meichf Why are you setting the bigEndian parameter to false? Specifically for the logix driver in 0.13-SNAPSHOT.

meichf commented 1 week ago

I think that is just plain EIP/CIP and no Logix extensions, right? I think we completely rewrote the driver for 0.12.0

In version 0.11.0, both EtherNet/IP and Logix were successful, while in version 0.12.0, neither was successful.

meichf commented 1 week ago

@meichf Why are you setting the bigEndian parameter to false? Specifically for the logix driver in 0.13-SNAPSHOT.

It seems that without this parameter, I am unable to connect to the device.

meichf commented 1 week ago

I found that using version 0.11.0 to read data is slow, and the duration of returning data increases with the number of points. Then, I found the problem in issue #1373, so I rewrote the method. Now, reading values is relatively normal.


private CompletableFuture<PlcReadResponse> readWithoutMessageRouter(PlcReadRequest readRequest) {
    CompletableFuture<PlcReadResponse> future = new CompletableFuture<>();
    Map<String, ResponseItem<PlcValue>> values = new HashMap<>();
    List<CompletableFuture<Void>> internalFutures = new ArrayList<>();
    PathSegment classSegment = new LogicalSegment(new ClassID((byte) 0, (short) 6));
    PathSegment instanceSegment = new LogicalSegment(new InstanceID((byte) 0, (short) 1));

    DefaultPlcReadRequest request = (DefaultPlcReadRequest) readRequest;
    for (String tagName : request.getTagNames()) {
        CompletableFuture<Void> internalFuture = new CompletableFuture<>();
        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
        EipTag eipTag = (EipTag) request.getTag(tagName);
        String tag = eipTag.getTag();

        try {
            CipReadRequest req = new CipReadRequest(toAnsi(tag), 1);
            CipUnconnectedRequest requestItem = new CipUnconnectedRequest(classSegment, instanceSegment, req, (byte) this.configuration.getBackplane(), (byte) this.configuration.getSlot());
            List<TypeId> typeIds = new ArrayList<>(2);
            typeIds.add(nullAddressItem);
            typeIds.add(new UnConnectedDataItem(requestItem));
            CipRRData pkt = new CipRRData(sessionHandle, CIPStatus.Success.getValue(), DEFAULT_SENDER_CONTEXT, 0L, 0L, 0, typeIds);

            transaction.submit(() -> context.sendRequest(pkt)
                    .expectResponse(EipPacket.class, REQUEST_TIMEOUT)
                    .onTimeout(future::completeExceptionally)
                    .onError((p, e) -> future.completeExceptionally(e))
                    .check(p -> p instanceof CipRRData)
                    .unwrap(p -> (CipRRData) p)
                    .check(p -> p.getSessionHandle() == sessionHandle)
                    .handle(p -> {
                        List<TypeId> responseTypeIds = p.getTypeIds();
                        UnConnectedDataItem dataItem = (UnConnectedDataItem) responseTypeIds.get(1);
                        Map<String, ResponseItem<PlcValue>> readResponse = decodeSingleReadResponse(dataItem.getService(), tagName, eipTag);
                        values.putAll(readResponse);
                        internalFuture.complete(null);
                        transaction.endRequest();
                    }));
            internalFutures.add(internalFuture);
        } catch (SerializationException e) {
            future.completeExceptionally(e);
        }
    }

    CompletableFuture.allOf(internalFutures.toArray(new CompletableFuture[0])).thenRun(() -> {
        PlcReadResponse readResponse = new DefaultPlcReadResponse(readRequest, values);
        future.complete(readResponse);
    }).exceptionally(e -> {
        future.completeExceptionally(e);
        return null;
    });

    return future;
}
acfly commented 1 week ago

@chrisdutz The Rewritten method readWithoutMessageRouter realy works. Hope it will be merged into the next version.

chrisdutz commented 1 week ago

I think the new version of the driver sort of does an auto-detection of the PLC and decides which technique to use, based on that. Will see if I can have a look tomorrow afternoon.