From fb7be73dec196f06d24fd2379f623603c64408d1 Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Mon, 15 Sep 2025 22:08:41 +0200 Subject: [PATCH 1/2] feat: enhance RemoteStartTransactionTask to support charging profiles and transaction IDs Backport of https://github.com/steve-community/steve/pull/1809 and https://github.com/steve-community/steve/pull/1816 --- .../ocpp/task/RemoteStartTransactionTask.java | 68 ++++++++++++++++--- .../ocpp/task/SetChargingProfileTask.java | 28 ++++++-- .../service/ChargePointServiceClient.java | 4 +- .../ocpp/RemoteStartTransactionParams.java | 5 +- .../dto/ocpp/SetChargingProfileParams.java | 2 + .../web/controller/Ocpp12Controller.java | 10 +++ .../web/controller/Ocpp16Controller.java | 5 ++ .../op-forms/RemoteStartTransactionForm.jsp | 13 +++- .../views/op-forms/SetChargingProfileForm.jsp | 6 +- 9 files changed, 122 insertions(+), 19 deletions(-) diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java b/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java index 544598799..1af65f188 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java @@ -18,16 +18,22 @@ */ package de.rwth.idsg.steve.ocpp.task; +import de.rwth.idsg.steve.SteveException; import de.rwth.idsg.steve.ocpp.CommunicationTask; import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.task.impl.OcppVersionHandler; import de.rwth.idsg.steve.ocpp.task.impl.TaskDefinition; +import de.rwth.idsg.steve.repository.ChargingProfileRepository; import de.rwth.idsg.steve.web.dto.ocpp.RemoteStartTransactionParams; +import ocpp.cp._2015._10.ChargingProfile; +import ocpp.cp._2015._10.ChargingProfilePurposeType; +import org.jspecify.annotations.Nullable; -public class RemoteStartTransactionTask extends CommunicationTask { +public class RemoteStartTransactionTask + extends CommunicationTask { - private static final TaskDefinition TASK_DEFINITION = - TaskDefinition.builder() + private static final TaskDefinition TASK_DEFINITION = + TaskDefinition.builder() .versionHandler( OcppVersion.V_12, new OcppVersionHandler<>( @@ -49,16 +55,62 @@ public class RemoteStartTransactionTask extends CommunicationTask( task -> new ocpp.cp._2015._10.RemoteStartTransactionRequest() .withIdTag(task.getParams().getIdTag()) - .withConnectorId(task.getParams().getConnectorId()), + .withConnectorId(task.getParams().getConnectorId()) + .withChargingProfile(task.getParams().chargingProfile), (ocpp.cp._2015._10.RemoteStartTransactionResponse r) -> r.getStatus().value())) .build(); - public RemoteStartTransactionTask(RemoteStartTransactionParams params) { - super(TASK_DEFINITION, params); + public RemoteStartTransactionTask( + RemoteStartTransactionParams params, ChargingProfileRepository chargingProfileRepository) { + super( + TASK_DEFINITION, + new RemoteStartTransactionWithProfileParams( + params, createChargingProfile(params.getChargingProfilePk(), chargingProfileRepository))); + } + + public RemoteStartTransactionTask( + RemoteStartTransactionParams params, String caller, ChargingProfileRepository chargingProfileRepository) { + super( + TASK_DEFINITION, + new RemoteStartTransactionWithProfileParams( + params, createChargingProfile(params.getChargingProfilePk(), chargingProfileRepository)), + caller); + } + + public static class RemoteStartTransactionWithProfileParams extends RemoteStartTransactionParams { + private final ocpp.cp._2015._10.@Nullable ChargingProfile chargingProfile; + + public RemoteStartTransactionWithProfileParams( + RemoteStartTransactionParams params, ocpp.cp._2015._10.@Nullable ChargingProfile chargingProfile) { + super(); + this.setIdTag(params.getIdTag()); + this.setConnectorId(params.getConnectorId()); + this.chargingProfile = chargingProfile; + } + + @Override + public @Nullable Integer getChargingProfilePk() { + if (chargingProfile == null) { + return null; + } + return chargingProfile.getChargingProfileId(); + } } - public RemoteStartTransactionTask(RemoteStartTransactionParams params, String caller) { - super(TASK_DEFINITION, params, caller); + private static @Nullable ChargingProfile createChargingProfile( + @Nullable Integer chargingProfilePk, ChargingProfileRepository chargingProfileRepository) { + if (chargingProfilePk == null) { + return null; + } + de.rwth.idsg.steve.repository.dto.ChargingProfile.Details details = chargingProfileRepository + .getDetails(chargingProfilePk) + .orElseThrow(() -> + new SteveException.BadRequest("ChargingProfile with PK " + chargingProfilePk + " not found")); + ocpp.cp._2015._10.ChargingProfile chargingProfile = SetChargingProfileTask.mapToOcpp(details, null); + if (chargingProfile.getChargingProfilePurpose() != ChargingProfilePurposeType.TX_PROFILE) { + throw new SteveException.BadRequest("ChargingProfilePurposeType is not TX_PROFILE"); + } + return chargingProfile; } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java b/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java index fa8f79e68..16962ab54 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java @@ -91,7 +91,8 @@ public OcppCallback createDefaultCallback() { @Override public void success(String chargeBoxId, String statusValue) { addNewResponse(chargeBoxId, statusValue); - if ("Accepted".equalsIgnoreCase(statusValue)) { + var purpose = ChargingProfilePurposeType.fromValue(details.getChargingProfilePurpose()); + if ("Accepted".equalsIgnoreCase(statusValue) && ChargingProfilePurposeType.TX_PROFILE != purpose) { repo.setProfile(details.getChargingProfilePk(), chargeBoxId, params.getConnectorId()); } } @@ -110,6 +111,14 @@ public void failed(String chargeBoxId, Exception e) { private static SetChargingProfileRequest buildRequestFromDb( SetChargingProfileParams params, ChargingProfile.Details details) { + var ocppProfile = mapToOcpp(details, params.getTransactionId()); + + return new SetChargingProfileRequest() + .withConnectorId(params.getConnectorId()) + .withCsChargingProfiles(ocppProfile); + } + + public static ocpp.cp._2015._10.ChargingProfile mapToOcpp(ChargingProfile.Details details, Integer transactionId) { var schedulePeriods = details.getPeriods().stream() .map(k -> { ChargingSchedulePeriod p = new ChargingSchedulePeriod(); @@ -127,8 +136,9 @@ private static SetChargingProfileRequest buildRequestFromDb( .withMinChargingRate(details.getMinChargingRate()) .withChargingSchedulePeriod(schedulePeriods); - var ocppProfile = new ocpp.cp._2015._10.ChargingProfile() + return new ocpp.cp._2015._10.ChargingProfile() .withChargingProfileId(details.getChargingProfilePk()) + .withTransactionId(transactionId) .withStackLevel(details.getStackLevel()) .withChargingProfilePurpose(ChargingProfilePurposeType.fromValue(details.getChargingProfilePurpose())) .withChargingProfileKind(ChargingProfileKindType.fromValue(details.getChargingProfileKind())) @@ -138,10 +148,6 @@ private static SetChargingProfileRequest buildRequestFromDb( .withValidFrom(toOffsetDateTime(details.getValidFrom())) .withValidTo(toOffsetDateTime(details.getValidTo())) .withChargingSchedule(schedule); - - return new SetChargingProfileRequest() - .withConnectorId(params.getConnectorId()) - .withCsChargingProfiles(ocppProfile); } private static void checkAdditionalConstraints(SetChargingProfileRequest request) { @@ -158,6 +164,16 @@ private static void checkAdditionalConstraints(SetChargingProfileRequest request throw new SteveException.InternalError( "TxProfile should only be set at Charge Point ConnectorId > 0"); } + + if (ChargingProfilePurposeType.TX_PROFILE == purpose + && request.getCsChargingProfiles().getTransactionId() == null) { + throw new SteveException.InternalError("transaction id is required for TxProfile"); + } + + if (ChargingProfilePurposeType.TX_PROFILE != purpose + && request.getCsChargingProfiles().getTransactionId() != null) { + throw new SteveException.InternalError("transaction id should only be set for TxProfile"); + } }); } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java b/steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java index 1b3d11027..98d389aec 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java @@ -189,14 +189,14 @@ public final int updateFirmware(UpdateFirmwareParams params, OcppCallback... callbacks) { - RemoteStartTransactionTask task = new RemoteStartTransactionTask(params); + RemoteStartTransactionTask task = new RemoteStartTransactionTask(params, chargingProfileRepository); return addRemoteStartTask(task, callbacks); } @SafeVarargs public final int remoteStartTransaction( RemoteStartTransactionParams params, String caller, OcppCallback... callbacks) { - RemoteStartTransactionTask task = new RemoteStartTransactionTask(params, caller); + RemoteStartTransactionTask task = new RemoteStartTransactionTask(params, caller, chargingProfileRepository); return addRemoteStartTask(task, callbacks); } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/RemoteStartTransactionParams.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/RemoteStartTransactionParams.java index 3ea29b553..d8e2c37d7 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/RemoteStartTransactionParams.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/RemoteStartTransactionParams.java @@ -25,20 +25,23 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; /** * @author Sevket Goekay * @since 01.01.2015 */ @Getter +@Setter public class RemoteStartTransactionParams extends SingleChargePointSelect { @Min(value = 0, message = "Connector ID must be at least {value}") @Nullable private Integer connectorId; @NotBlank(message = "User ID Tag is required") @IdTag - @Setter private String idTag; + @Positive private @Nullable Integer chargingProfilePk; + /** * Not for a specific connector, when frontend sends the value 0. * This corresponds to not to include the connector id parameter in OCPP request. diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/SetChargingProfileParams.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/SetChargingProfileParams.java index 5b0ac8d96..fd1a70e88 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/SetChargingProfileParams.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/SetChargingProfileParams.java @@ -36,4 +36,6 @@ public class SetChargingProfileParams extends MultipleChargePointSelect { @NotNull @Min(value = 0, message = "Connector ID must be at least {value}") private Integer connectorId; @NotNull @Positive private Integer chargingProfilePk; + + @Positive private Integer transactionId; } diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp12Controller.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp12Controller.java index 6f03b3e8e..aa9c146d6 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp12Controller.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp12Controller.java @@ -82,6 +82,14 @@ public class Ocpp12Controller { // Helpers // ------------------------------------------------------------------------- + /** + * https://github.com/steve-community/steve/issues/1759 + * used to create form in order to send charging profile within remote start tx for ocpp 1.6. + */ + protected void setCommonAttributesForRemoteStartTx(Model model) { + // nothing to do for versions below 1.6 + } + protected void setCommonAttributesForTx(Model model) { setCommonAttributes(model); } @@ -150,6 +158,7 @@ public String getGetDiag(Model model) { public String getRemoteStartTx(Model model) { setCommonAttributesForTx(model); setActiveUserIdTagList(model); + setCommonAttributesForRemoteStartTx(model); model.addAttribute(PARAMS, new RemoteStartTransactionParams()); return getPrefix() + REMOTE_START_TX_PATH; } @@ -233,6 +242,7 @@ public String postRemoteStartTx( if (result.hasErrors()) { setCommonAttributesForTx(model); setActiveUserIdTagList(model); + setCommonAttributesForRemoteStartTx(model); return getPrefix() + REMOTE_START_TX_PATH; } return REDIRECT_TASKS_PATH + chargePointServiceClient.remoteStartTransaction(params); diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp16Controller.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp16Controller.java index a38a49213..b02423dac 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp16Controller.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/Ocpp16Controller.java @@ -80,6 +80,11 @@ public Ocpp16Controller( // Helpers // ------------------------------------------------------------------------- + protected void setCommonAttributesForRemoteStartTx(Model model) { + model.addAttribute("profileForRemoteStartTx", Boolean.TRUE); + model.addAttribute("profileList", chargingProfileRepository.getBasicInfo()); + } + @Override protected void setCommonAttributesForTx(Model model) { model.addAttribute("cpList", chargePointHelperService.getChargePoints(OcppVersion.V_16)); diff --git a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp index 23e7b4969..ce02719a6 100644 --- a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp +++ b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp @@ -33,6 +33,17 @@ + + + Charging Profile ID: + + + + + + + +
- \ No newline at end of file + diff --git a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp index 8fc7a4b79..33cdc76a1 100644 --- a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp +++ b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp @@ -34,6 +34,10 @@ Connector ID (integer): + + Transaction ID (integer): + +
- \ No newline at end of file + From 48aaefe9b8d837c24837e9af01b60a4e0a4b1d74 Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Mon, 15 Sep 2025 22:24:19 +0200 Subject: [PATCH 2/2] fix: update after CodeRabbit review --- .../views/op-forms/RemoteStartTransactionForm.jsp | 2 +- .../WEB-INF/views/op-forms/SetChargingProfileForm.jsp | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp index ce02719a6..440fb5f3a 100644 --- a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp +++ b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/RemoteStartTransactionForm.jsp @@ -38,7 +38,7 @@ Charging Profile ID: - + -- Empty -- diff --git a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp index 33cdc76a1..94ef22e8d 100644 --- a/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp +++ b/steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/op-forms/SetChargingProfileForm.jsp @@ -32,11 +32,17 @@ Connector ID (integer): - + + + + Transaction ID (integer): - + + + +