Skip to content

feat(plc4j/eip): Detect Connection Manager and Message using "Get Attribute Single" - #2674

Draft
andvasp wants to merge 3 commits into
apache:developfrom
andvasp:feat/plc4j-eip-GetAttributeSingle
Draft

feat(plc4j/eip): Detect Connection Manager and Message using "Get Attribute Single" #2674
andvasp wants to merge 3 commits into
apache:developfrom
andvasp:feat/plc4j-eip-GetAttributeSingle

Conversation

@andvasp

@andvasp andvasp commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Hi @chrisdutz ,

Implement "Get Attribute Single" on EIP protocol for cases where "Get Attribute All" is not supported by the device to address #2135.

… cases where "Get Attribute All" is not supported by the device.
Comment on lines +253 to +258
if (!(response instanceof CipRRData rr) || rr.getStatus() != CIPStatus.Success.getValue() ||
!(rr.getTypeIds().get(1) instanceof UnConnectedDataItem di && di.getService() instanceof GetAttributeAllResponse gar)) {
return CompletableFuture.completedFuture(null);
}
if (gar.getStatus() == CIPStatus.ServiceNotSupported.getValue()) {
return;
return checkAttributesSingle();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admittedly I find this particular part of code quite hard to read ... Could you please simplify this a bit?

Admittedly I'm a big fan of the "di.getService() instanceof GetAttributeAllResponse gar" notation saving myself the explicit cast, but I am super unhappy with the decision of the Java group in a negated form to make the variable available outside the if statement (Which you are using) ... it's just challenging from a maintenance perspective.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also leaving a few comments here to what's happeing would be great. I know I didn't set a good example but I'm trying my best to leave more comments for my fellow maintainers if I think something's tricky to understand. I guess the problematic you're trying to solve would qualify for such a comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admittedly I find this particular part of code quite hard to read ... Could you please simplify this a bit?

Admittedly I'm a big fan of the "di.getService() instanceof GetAttributeAllResponse gar" notation saving myself the explicit cast, but I am super unhappy with the decision of the Java group in a negated form to make the variable available outside the if statement (Which you are using) ... it's just challenging from a maintenance perspective.

Here I see 3 options, where I prefer the sequential other. What do you think?

Option 1:
if (!(response instanceof CipRRData rr) || rr.getStatus() != CIPStatus.Success.getValue() ||
!(rr.getTypeIds().get(1) instanceof UnConnectedDataItem di) ||
!(di.getService() instanceof GetAttributeAllResponse gar)) {
return CompletableFuture.completedFuture(null);
}

Option 2:
if (!(response instanceof CipRRData rr) || rr.getStatus() != CIPStatus.Success.getValue()) {
return CompletableFuture.completedFuture(null);
}
if (!(rr.getTypeIds().get(1) instanceof UnConnectedDataItem di) ||
!(di.getService() instanceof GetAttributeAllResponse gar)) {
return CompletableFuture.completedFuture(null);
}

Option 3:
if (!(response instanceof CipRRData rr) || rr.getStatus() != CIPStatus.Success.getValue()) {
return CompletableFuture.completedFuture(null);
}
UnConnectedDataItem dataItem = (UnConnectedDataItem) rr.getTypeIds().get(1);
if (!(dataItem.getService() instanceof GetAttributeAllResponse gar)) {
return CompletableFuture.completedFuture(null);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more trying to wrap my head around what the code should do ...

If it's (not a CipRRData) or (it is and it's status is not success) or (it is and it's first type id is an UnConnectedDataItem) or (it is, it's first type id is a UnConnectedDataItem's service is a GetAttributeAllResponse ....

So much negation ... wouldn't it be an alternative to focus what we expect it to be?
Something like this?

if (response instanceof CipRRData rr
            && rr.getStatus() == CIPStatus.Success.getValue()
            && rr.getTypeIds().size() > 1
            && rr.getTypeIds().get(1) instanceof UnConnectedDataItem di
            && serviceType.isInstance(di.getService())) {
        return serviceType.cast(di.getService());
    }
    return null;

That I would instantly understand ;-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Chris!

I push a new version. Please check if it is better now.

}

private CompletableFuture<Void> checkAttributesSingle() {
private CipService getCipService(EipPacket response) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created this method to encapsulate the logic for get the CipService. I believe it could also be applied elsewhere, even though the logic isn't exactly the same.

Let me know what you think.

@andvasp
andvasp marked this pull request as draft August 7, 2026 18:55
@andvasp
andvasp force-pushed the feat/plc4j-eip-GetAttributeSingle branch from 7b6f222 to fa7a737 Compare August 7, 2026 19:21
…ce the return on processing the CompletableFuture instead of setting the future as completed.

Refactoring.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants