Skip to content

[ocpp] Tolerate meter-less StopTransaction requests - #351

Open
stamateviorel wants to merge 1 commit into
ConnectorIO:masterfrom
stamateviorel:pr/28-stoptransaction-tolerance
Open

stamateviorel wants to merge 1 commit into
ConnectorIO:masterfrom
stamateviorel:pr/28-stoptransaction-tolerance

Conversation

@stamateviorel

Copy link
Copy Markdown
Collaborator

A meter-less/FreeMode charge point (Phoenix Contact CHARX, no internal energy meter) can legitimately omit meterStop or transactionId from StopTransaction. The embedded library's StopTransactionRequest.validate() requires both plus timestamp, so the reply is a CallError instead of a confirmation — the charge point retries, gives up, and the transaction never closes, leaving the connector holding a phantom transaction.

Different layer than the merged "acknowledge StopTransaction for unknown transactions" fix, which runs after validate() passes, for a valid-shaped stop with an unrecognized id — this fixes validate() itself, which runs first and never lets a malformed-shaped stop reach that code.

OCPP 1.6 requires the CSMS to acknowledge StopTransaction regardless of its contents, so the override only requires a timestamp — implemented as a local class that shadows the embedded library one (our sources precede it on the Bundle-ClassPath), since validate() isn't something a handler can intercept.

Upstream's StopTransactionRequest.validate() requires meterStop and
transactionId to be present. A meter-less/FreeMode charge point (Phoenix
Contact CHARX with no energy meter) can legitimately omit either — the
library then replies with a CallError (OccurenceConstraintViolation)
instead of confirming, the charger retries and gives up, and the
never-acknowledged transaction wedges the connector as a phantom
transaction. OCPP 1.6 requires the CSMS to acknowledge StopTransaction
regardless, so this local override (shadowing the embedded library class
via Bundle-ClassPath ordering) only requires a timestamp and leaves the
rest to the handler, which already tolerates unknown/absent transaction
ids (see the merged "Acknowledge StopTransaction events for unknown
transactions" fix — that one runs after validate() passes; this fixes
validate() itself, which runs first and never reaches that code for a
malformed-shaped stop).

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@splatch

splatch commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Looking at the code I think there is one more way, however take this with grain of salt as I just look at the flow on our end, completely missing how library handles that internally.
Wouldn't it be better to implement a handleStopTransactionRequest which will return our own PermissibleStopTransactionRequest with relax validation logic?
We do not need to copy entire class, but as far I understand issue boils down to CoreEventHandlerWrapper.java#L79:

      if (confirmation == null && handlerConfirmation != null && handlerConfirmation.validate()) {
        confirmation = handlerConfirmation;
      }

The handlerConfirmation instance comes from our own handler, thus it should be possible to influence its creation.

@splatch splatch self-assigned this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants