diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/CommunicationTask.java b/src/main/java/de/rwth/idsg/steve/ocpp/CommunicationTask.java index b9c988cff..4081a4325 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/CommunicationTask.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/CommunicationTask.java @@ -73,6 +73,10 @@ public CommunicationTask(S params) { this(params, TaskOrigin.INTERNAL, "SteVe"); } + public CommunicationTask(S params, String caller) { + this(params, TaskOrigin.EXTERNAL, caller); + } + /** * Do not expose the constructor, make it package-private */ diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java b/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java index bba9d19b6..bc47d559c 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStartTransactionTask.java @@ -34,6 +34,10 @@ public RemoteStartTransactionTask(RemoteStartTransactionParams params) { super(params); } + public RemoteStartTransactionTask(RemoteStartTransactionParams params, String caller) { + super(params, caller); + } + @Override public OcppCallback defaultCallback() { return new StringOcppCallback(); diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStopTransactionTask.java b/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStopTransactionTask.java index 64117625f..0e9ead947 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStopTransactionTask.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/task/RemoteStopTransactionTask.java @@ -34,6 +34,10 @@ public RemoteStopTransactionTask(RemoteStopTransactionParams params) { super(params); } + public RemoteStopTransactionTask(RemoteStopTransactionParams params, String caller) { + super(params, caller); + } + @Override public OcppCallback defaultCallback() { return new StringOcppCallback(); diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/task/UnlockConnectorTask.java b/src/main/java/de/rwth/idsg/steve/ocpp/task/UnlockConnectorTask.java index c20afde32..81b43571a 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/task/UnlockConnectorTask.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/task/UnlockConnectorTask.java @@ -34,6 +34,10 @@ public UnlockConnectorTask(UnlockConnectorParams params) { super(params); } + public UnlockConnectorTask(UnlockConnectorParams params, String caller) { + super(params, caller); + } + @Override public OcppCallback defaultCallback() { return new StringOcppCallback(); diff --git a/src/main/java/de/rwth/idsg/steve/repository/ChargePointRepository.java b/src/main/java/de/rwth/idsg/steve/repository/ChargePointRepository.java index 0e909d588..e403edbf8 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/ChargePointRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/ChargePointRepository.java @@ -45,6 +45,8 @@ default List getChargePointSelect(OcppProtocol protocol, List return getChargePointSelect(protocol, inStatusFilter, Collections.emptyList()); } + List getChargePointSelect(String chageBoxID); + List getChargeBoxIds(); Map getChargeBoxIdPkPair(List chargeBoxIdList); diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index adb662863..ae786abe5 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -30,11 +30,14 @@ * @since 19.08.2014 */ public interface TransactionRepository { + Transaction getTransaction(int transactionPk); + List getTransactions(TransactionQueryForm form); void writeTransactionsCSV(TransactionQueryForm form, Writer writer); List getActiveTransactionIds(String chargeBoxId); + Integer getActiveTransactionId(String chargeBoxId, Integer connectorId); TransactionDetails getDetails(int transactionPk); } diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/ConnectorStatus.java b/src/main/java/de/rwth/idsg/steve/repository/dto/ConnectorStatus.java index 50609db06..0c82aea96 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/ConnectorStatus.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/ConnectorStatus.java @@ -18,7 +18,9 @@ */ package de.rwth.idsg.steve.repository.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; import de.rwth.idsg.steve.ocpp.OcppProtocol; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -32,17 +34,32 @@ @Getter @Builder public final class ConnectorStatus { - private final String chargeBoxId, timeStamp, status, errorCode; - private final int chargeBoxPk, connectorId; + @JsonIgnore + @Schema(description = "Charge Box DB key", hidden = true) + private final int chargeBoxPk; + @Schema(description = "Charge Box ID") + private final String chargeBoxId; + @Schema(description = "Connector ID") + private final int connectorId; + @Schema(description = "Status") + private final String status; + @Schema(description = "Error code") + private final String errorCode; + + @Schema(description = "Timestamp") + private final String timeStamp; // For additional internal processing. Not related to the humanized // String version above, which is for representation on frontend + @Schema(description = "Timestamp of the status") private final DateTime statusTimestamp; + @Schema(description = "OCPP version") private final OcppProtocol ocppProtocol; // This is true, if the chargeBox this connector belongs to is a WS/JSON station // and it is disconnected at the moment of building this DTO. + @Schema(description = "Json and Disconnected") @Setter @Builder.Default private boolean jsonAndDisconnected = false; diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/TaskOverview.java b/src/main/java/de/rwth/idsg/steve/repository/dto/TaskOverview.java index d1016d2a2..4c8dec5d3 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/TaskOverview.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/TaskOverview.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.repository.dto; import de.rwth.idsg.steve.ocpp.TaskOrigin; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -32,8 +33,17 @@ @EqualsAndHashCode @Builder public final class TaskOverview implements Comparable { - private final int taskId, responseCount, requestCount; - private final DateTime start, end; + @Schema(description = "Task ID") + private final int taskId; + @Schema(description = "Response count") + private final int responseCount; + @Schema(description = "Request count") + private final int requestCount; + @Schema(description = "Starttime") + private final DateTime start; + @Schema(description = "Endtime") + private final DateTime end; + @Schema(description = "Task triggered internal or external") private final TaskOrigin origin; /** diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java index b6af1ca6f..9584ff164 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java @@ -103,6 +103,16 @@ public List getChargePointSelect(OcppProtocol protocol, List< .map(r -> new ChargePointSelect(protocol, r.value1(), r.value2())); } + @Override // returns List of zero or one ChargeBox + public List getChargePointSelect(String chageBoxID) { + return ctx.select(CHARGE_BOX.CHARGE_BOX_ID, CHARGE_BOX.ENDPOINT_ADDRESS, CHARGE_BOX.OCPP_PROTOCOL) + .from(CHARGE_BOX) + .where(CHARGE_BOX.CHARGE_BOX_ID.eq(chageBoxID)) + .fetch() + .map(r -> new ChargePointSelect(OcppProtocol.fromCompositeValue(r.value3()), + r.value1(), r.value2())); + } + @Override public List getChargeBoxIds() { return ctx.select(CHARGE_BOX.CHARGE_BOX_ID) diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 26e928c91..64b15b41e 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -66,6 +66,15 @@ public TransactionRepositoryImpl(DSLContext ctx) { this.ctx = ctx; } + @Override + public Transaction getTransaction(int transactionPk) { + TransactionQueryForm form = new TransactionQueryForm(); + form.setTransactionPk(transactionPk); + form.setReturnCSV(false); + form.setType(TransactionQueryForm.QueryType.ALL); + return getInternal(form).fetchAny(new TransactionMapper()); + } + @Override public List getTransactions(TransactionQueryForm form) { return getInternal(form).fetch() @@ -89,6 +98,19 @@ public List getActiveTransactionIds(String chargeBoxId) { .fetch(TRANSACTION.TRANSACTION_PK); } + @Override + public Integer getActiveTransactionId(String chargeBoxId, Integer connectorId) { + return ctx.select(TRANSACTION.TRANSACTION_PK) + .from(TRANSACTION) + .join(CONNECTOR) + .on(TRANSACTION.CONNECTOR_PK.equal(CONNECTOR.CONNECTOR_PK)) + .and(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) + .where(TRANSACTION.STOP_TIMESTAMP.isNull()) + .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) + .orderBy(TRANSACTION.TRANSACTION_PK.desc()) // to avoid fetching ghost transactions, fetch the latest + .fetchAny(TRANSACTION.TRANSACTION_PK); + } + @Override public TransactionDetails getDetails(int transactionPk) { diff --git a/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java b/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java index 32633f3da..dbbb60a66 100644 --- a/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java +++ b/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java @@ -21,7 +21,6 @@ import de.rwth.idsg.steve.SteveException; import de.rwth.idsg.steve.ocpp.ChargePointServiceInvokerImpl; import de.rwth.idsg.steve.ocpp.OcppCallback; -import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.task.CancelReservationTask; import de.rwth.idsg.steve.ocpp.task.ChangeAvailabilityTask; import de.rwth.idsg.steve.ocpp.task.ChangeConfigurationTask; @@ -197,12 +196,21 @@ public final int updateFirmware(UpdateFirmwareParams params, // ------------------------------------------------------------------------- // Single Execution - since OCPP 1.2 // ------------------------------------------------------------------------- - @SafeVarargs public final int remoteStartTransaction(RemoteStartTransactionParams params, OcppCallback... callbacks) { RemoteStartTransactionTask task = new RemoteStartTransactionTask(params); + return addRemoteStartTask(task, callbacks); + } + @SafeVarargs + public final int remoteStartTransaction(RemoteStartTransactionParams params, String caller, + OcppCallback... callbacks) { + RemoteStartTransactionTask task = new RemoteStartTransactionTask(params, caller); + return addRemoteStartTask(task, callbacks); + } + private int addRemoteStartTask(RemoteStartTransactionTask task, + OcppCallback... callbacks) { for (var callback : callbacks) { task.addCallback(callback); } @@ -218,7 +226,18 @@ public final int remoteStartTransaction(RemoteStartTransactionParams params, public final int remoteStopTransaction(RemoteStopTransactionParams params, OcppCallback... callbacks) { RemoteStopTransactionTask task = new RemoteStopTransactionTask(params); + return addRemoteStopTask(task,callbacks); + } + + @SafeVarargs + public final int remoteStopTransaction(RemoteStopTransactionParams params, String caller, + OcppCallback... callbacks) { + RemoteStopTransactionTask task = new RemoteStopTransactionTask(params, caller); + return addRemoteStopTask(task,callbacks); + } + private int addRemoteStopTask(RemoteStopTransactionTask task, + OcppCallback... callbacks) { for (var callback : callbacks) { task.addCallback(callback); } @@ -234,7 +253,18 @@ public final int remoteStopTransaction(RemoteStopTransactionParams params, public final int unlockConnector(UnlockConnectorParams params, OcppCallback... callbacks) { UnlockConnectorTask task = new UnlockConnectorTask(params); + return addUnlockConnectorTask(task, callbacks); + } + @SafeVarargs + public final int unlockConnector(UnlockConnectorParams params, String caller, + OcppCallback... callbacks) { + UnlockConnectorTask task = new UnlockConnectorTask(params, caller); + return addUnlockConnectorTask(task, callbacks); + } + + private int addUnlockConnectorTask(UnlockConnectorTask task, + OcppCallback... callbacks) { for (var callback : callbacks) { task.addCallback(callback); } diff --git a/src/main/java/de/rwth/idsg/steve/web/api/ConnectorRestController.java b/src/main/java/de/rwth/idsg/steve/web/api/ConnectorRestController.java new file mode 100644 index 000000000..b2934da62 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/ConnectorRestController.java @@ -0,0 +1,144 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2024 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api; + +import de.rwth.idsg.steve.repository.ChargePointRepository; +import de.rwth.idsg.steve.repository.dto.ConnectorStatus; +import de.rwth.idsg.steve.service.ChargePointHelperService; +import de.rwth.idsg.steve.utils.ConnectorStatusFilter; +import de.rwth.idsg.steve.web.api.dto.ApiConnectorList; +import de.rwth.idsg.steve.web.dto.ConnectorStatusForm; +import de.rwth.idsg.steve.web.dto.OcppJsonStatus; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.List; +import static java.util.Objects.isNull; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + + +import de.rwth.idsg.steve.web.api.ApiControllerAdvice.ApiErrorResponse; + + +//import org.springframework.web.bind.annotation.DeleteMapping; + +//import org.springframework.web.bind.annotation.PathVariable; +//import org.springframework.web.bind.annotation.PostMapping; +//import org.springframework.web.bind.annotation.PutMapping; + + +/** + * + * @author fnkbsi + * since 20.10.2023 + * + */ +@Slf4j +@RestController +@RequestMapping(value = "/api/v1/connectors", produces = MediaType.APPLICATION_JSON_VALUE) +@RequiredArgsConstructor +public class ConnectorRestController { + + @Autowired private ChargePointRepository chargePointRepository; + @Autowired private ChargePointHelperService chargePointHelperService; + + // ------------------------------------------------------------------------- + // HTTP methods + // ------------------------------------------------------------------------- + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "") + @ResponseBody + public ApiConnectorList getConnectors(@Valid ConnectorStatusForm queryParams) { + ApiConnectorList conList = new ApiConnectorList(); + conList.setChargeBoxList(chargePointRepository.getChargeBoxIds()); + + conList.setIsFiltered(isFilterd(queryParams)); + List latestList = chargePointHelperService.getChargePointConnectorStatus(queryParams); + List sortedList; + if (queryParams.getStrategy() == ConnectorStatusForm.Strategy.PreferZero) { + sortedList = ConnectorStatusFilter.filterAndPreferZero(latestList); + } else { + sortedList = ConnectorStatusFilter.filterAndPreferOthersWithStatusOfZero(latestList); + } + conList.setConnectors(sortedList); + return conList; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "OCPP_JSON_STATUS") + @ResponseBody + public List getOcppJsonStatus() { + return chargePointHelperService.getOcppJsonStatus(); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private Boolean isFilterd(ConnectorStatusForm queryParams) { + if (!isNull(queryParams)) { + if (!isNull(queryParams.getChargeBoxId())) { + if (!queryParams.getChargeBoxId().isBlank()) { + return true; + } + } + if (!isNull(queryParams.getStatus())) { + if (!queryParams.getStatus().isBlank()) { + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/de/rwth/idsg/steve/web/api/RemoteStartStopRestController.java b/src/main/java/de/rwth/idsg/steve/web/api/RemoteStartStopRestController.java new file mode 100644 index 000000000..42def1b9b --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/RemoteStartStopRestController.java @@ -0,0 +1,332 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api; + +import de.rwth.idsg.steve.ocpp.CommunicationTask; +import de.rwth.idsg.steve.ocpp.OcppVersion; +import de.rwth.idsg.steve.repository.ChargePointRepository; +import de.rwth.idsg.steve.repository.TaskStore; +import de.rwth.idsg.steve.repository.TransactionRepository; +import de.rwth.idsg.steve.repository.dto.ChargePointSelect; +import de.rwth.idsg.steve.repository.dto.TaskOverview; + +import de.rwth.idsg.steve.service.ChargePointHelperService; +import de.rwth.idsg.steve.service.ChargePointServiceClient; + +import de.rwth.idsg.steve.web.dto.ocpp.RemoteStartTransactionParams; +import de.rwth.idsg.steve.web.dto.ocpp.RemoteStopTransactionParams; + +import de.rwth.idsg.steve.web.dto.ocpp.UnlockConnectorParams; + +import org.springframework.beans.factory.annotation.Autowired; + +import de.rwth.idsg.steve.web.api.ApiControllerAdvice.ApiErrorResponse; +import de.rwth.idsg.steve.web.api.dto.ApiChargePointList; +import de.rwth.idsg.steve.web.api.dto.ApiChargePointStart; +import de.rwth.idsg.steve.web.api.exception.BadRequestException; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.validation.Valid; + +import java.util.List; +import static java.util.Objects.isNull; + + + +/** + * @author fnkbsi + * @since 18.10.2023 + */ + +@Slf4j +@RestController +@RequestMapping(value = "/api/v1/remote", produces = MediaType.APPLICATION_JSON_VALUE) +@RequiredArgsConstructor +public class RemoteStartStopRestController { + + @Autowired private ChargePointHelperService chargePointHelperService; + @Autowired private ChargePointRepository chargePointRepository; + @Autowired private TransactionRepository transactionRepository; + @Autowired private TaskStore taskStore; + + + @Autowired + private ChargePointServiceClient client; + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private ApiChargePointList getChargePoints() { + List chargePoints = chargePointHelperService.getChargePoints(OcppVersion.V_12); + chargePoints.addAll(chargePointHelperService.getChargePoints(OcppVersion.V_15)); + chargePoints.addAll(chargePointHelperService.getChargePoints(OcppVersion.V_16)); + ApiChargePointList lsCp = new ApiChargePointList(); + try { + for (ChargePointSelect chargeBox : chargePoints) { + List conList = chargePointRepository.getNonZeroConnectorIds(chargeBox.getChargeBoxId()); + if (!conList.isEmpty()) { + lsCp.addCP(chargeBox.getChargeBoxId(), conList); + } + } + } catch (Exception e) { + System.out.println(e.toString()); + } + return lsCp; + } + + private Boolean activeTaskOnChargeBox(String chargeBoxId) { + Boolean retValue = false; + List taskList = taskStore.getOverview(); + for (TaskOverview taOverview : taskList) { + CommunicationTask task = taskStore.get(taOverview.getTaskId()); + if (!task.isFinished()) { + if (!isNull(task.getResultMap().get(chargeBoxId))) { + retValue = true; + break; + } + } + } + return retValue; + } + + // ------------------------------------------------------------------------- + // Http methods (GET) + // ------------------------------------------------------------------------- + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "") + @ResponseBody + public ApiChargePointList getBase() { + return getChargePoints(); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "start") + @ResponseBody + public ApiChargePointList getRemoteStartTx() { + return getChargePoints(); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "stop") + @ResponseBody + public ApiChargePointList getRemoteStopTx() { + return getChargePoints(); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "unlock") + @ResponseBody + public ApiChargePointList getUnlockCon() { + return getChargePoints(); + } + + // ------------------------------------------------------------------------- + // Http methods (POST) + // the methods return the taskID, check the sucess with the TaskRestController + // ------------------------------------------------------------------------- + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @PostMapping(value = "start") + @ResponseBody + public Integer postRemoteStartTx(@Valid ApiChargePointStart params) { + // only one active task per charge box over api; to be discussed! + if (activeTaskOnChargeBox(params.getChargeBoxId())) { + String errMsg = String.format("Active task found on ChargeBox %s!", + params.getChargeBoxId() + ); + throw new BadRequestException(errMsg); + } + + // Check for acctive transactions on the connector, If a active transaction is found, don't send RemoteStart. + Integer transactionId = transactionRepository.getActiveTransactionId(params.getChargeBoxId(), + params.getConnectorId()); + if (!isNull(transactionId)) { + String errMsg = String.format("Active transaction found for connector %s at ChargeBox %s!", + params.getConnectorId(), + params.getChargeBoxId() + ); + throw new BadRequestException(errMsg); + } + // Check if OCPP-Tag is allowed to use the connector? To be discussed and t.b.d.! + + RemoteStartTransactionParams transactionParams = new RemoteStartTransactionParams(); + transactionParams.setChargePointSelectList(chargePointRepository.getChargePointSelect(params.getChargeBoxId())); + transactionParams.setConnectorId(params.getConnectorId()); + transactionParams.setIdTag(params.getOcppTag()); + return client.remoteStartTransaction(transactionParams, "SteveWebApi"); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @PostMapping(value = "stop") + @ResponseBody + public Integer postRemoteStopTx(@Valid ApiChargePointStart params) { + // only one active task per charge box over api; to be discussed! + if (activeTaskOnChargeBox(params.getChargeBoxId())) { + String errMsg = String.format("Active task found on ChargeBox %s!", + params.getChargeBoxId() + ); + throw new BadRequestException(errMsg); + } + + RemoteStopTransactionParams transactionParams = new RemoteStopTransactionParams(); + // set the ChargPointSelectionList, maybe check nessesary that length is one + transactionParams.setChargePointSelectList(chargePointRepository.getChargePointSelect(params.getChargeBoxId())); + + // Get the transactionId of the active transaction on the connector. + // If no transaction active don't send RemoteStop + Integer transactionId = transactionRepository.getActiveTransactionId(params.getChargeBoxId(), + params.getConnectorId()); + if (isNull(transactionId)) { + String errMsg = String.format("No active transaction found for connector %s at ChargeBox %s!", + params.getConnectorId(), + params.getChargeBoxId() + ); + throw new BadRequestException(errMsg); + } + + // check the user is allowed to stop this transaction (actual only the one who started it!) + String ocppTag = transactionRepository.getTransaction(transactionId).getOcppIdTag(); + if (!params.getOcppTag().contentEquals(ocppTag)) { + throw new BadRequestException("The transaction was authorised with another OCPP Tag!"); + } + transactionParams.setTransactionId(transactionId); + + return client.remoteStopTransaction(transactionParams, "SteveWebApi"); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @PostMapping(value = "unlock") + @ResponseBody + public Integer postUnlockCon(@Valid ApiChargePointStart params) { + // only one active task per charge box over api; to be discussed! + if (activeTaskOnChargeBox(params.getChargeBoxId())) { + String errMsg = String.format("Active task found on ChargeBox %s!", + params.getChargeBoxId() + ); + throw new BadRequestException(errMsg); + } + + UnlockConnectorParams transactionParams = new UnlockConnectorParams(); + transactionParams.setChargePointSelectList(chargePointRepository.getChargePointSelect(params.getChargeBoxId())); + + /* If a active transaction is found, don't unlock the connection. */ + Integer transactionId = transactionRepository.getActiveTransactionId(params.getChargeBoxId(), + params.getConnectorId()); + if (!isNull(transactionId)) { + String errMsg = String.format("Active transaction found for connector %s at ChargeBox %s!", + params.getConnectorId(), + params.getChargeBoxId() + ); + throw new BadRequestException(errMsg); + } + + transactionParams.setConnectorId(params.getConnectorId()); + return client.unlockConnector(transactionParams, "SteveWebApi"); + } +} diff --git a/src/main/java/de/rwth/idsg/steve/web/api/TaskRestController.java b/src/main/java/de/rwth/idsg/steve/web/api/TaskRestController.java new file mode 100644 index 000000000..ebddb085f --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/TaskRestController.java @@ -0,0 +1,126 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api; + +import de.rwth.idsg.steve.repository.TaskStore; + +import org.springframework.beans.factory.annotation.Autowired; + +import de.rwth.idsg.steve.web.api.ApiControllerAdvice.ApiErrorResponse; +import de.rwth.idsg.steve.web.api.dto.ApiTaskInfo; +import de.rwth.idsg.steve.web.api.dto.ApiTaskList; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * @author fnkbsi + * @since 18.10.2023 + */ +@Slf4j +@RestController +@RequestMapping(value = "/api/v1/tasks", produces = MediaType.APPLICATION_JSON_VALUE) +@RequiredArgsConstructor +public class TaskRestController { + + @Autowired private TaskStore taskStore; + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private ApiTaskList getTaskList() { + ApiTaskList taskList = new ApiTaskList(); + taskList.setTasks(taskStore.getOverview()); + return taskList; + } + + // ------------------------------------------------------------------------- + // Http methods (GET) + // ------------------------------------------------------------------------- + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "taskoverview") + @ResponseBody + public ApiTaskList getOverview() { + return getTaskList(); + } + + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @PostMapping(value = "clearfinishedtasks") + @ResponseBody + public ApiTaskList clearFinished() { + taskStore.clearFinished(); + return getTaskList(); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = ApiErrorResponse.class))})} + ) + @GetMapping(value = "task") + @ResponseBody + public ApiTaskInfo getTaskDetails(@RequestParam(name = "id") @Valid Integer taskId) { + ApiTaskInfo taskInfo = new ApiTaskInfo(taskId, taskStore.get(taskId)); + return taskInfo; + } +} diff --git a/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiChargePointList.java b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiChargePointList.java new file mode 100644 index 000000000..2537ed953 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiChargePointList.java @@ -0,0 +1,58 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * @author fnkbsi + * @since 18.10.2023 + */ + +@Getter +@Setter +@RequiredArgsConstructor +public class ApiChargePointList { + @Schema(description = "List of charge points") + private List chargePointList = new ArrayList<>(); + + public void addCP(String chargeBoxId, List connectorIds) { + ChargePointInfo cp = new ChargePointInfo(chargeBoxId, connectorIds); + this.chargePointList.add(cp); + } + + @Getter + @Setter + class ChargePointInfo { + @Schema(description = "Charge Box ID") + private String chargeBoxId; + @Schema(description = "List of the charge box connectors") + private List connectorIds; + + ChargePointInfo(String chargeBoxId, List connectorIds) { + this.chargeBoxId = chargeBoxId; + this.connectorIds = connectorIds; + } + } +} diff --git a/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiChargePointStart.java b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiChargePointStart.java new file mode 100644 index 000000000..49ae81e0e --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiChargePointStart.java @@ -0,0 +1,41 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * @author fnkbsi + * @since 18.10.2023 + */ + +@Getter +@Setter +@RequiredArgsConstructor +public class ApiChargePointStart { + @Schema(description = "Charge Box ID") + private String chargeBoxId; + @Schema(description = "Connector ID") + private Integer connectorId; + @Schema(description = "OCPP Tag") + private String ocppTag; +} diff --git a/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiConnectorList.java b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiConnectorList.java new file mode 100644 index 000000000..729e1bfd9 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiConnectorList.java @@ -0,0 +1,53 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api.dto; + +//import de.rwth.idsg.steve.ocpp.OcppTransport; +import de.rwth.idsg.steve.repository.dto.ConnectorStatus; +import de.rwth.idsg.steve.utils.ConnectorStatusCountFilter; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * @author fnkbsi + * @since 20.10.2023 + */ + +@Getter +@Setter +@RequiredArgsConstructor +public class ApiConnectorList { + + @Schema(description = "List of charge boxes") + private List chargeBoxList = new ArrayList<>(); + + @Schema(description = "List of possible states") + private final Set statusFilterValues = ConnectorStatusCountFilter.ALL_STATUS_VALUES; + + @Schema(description = "List of connectors is filtered") + private Boolean isFiltered = false; + + @Schema(description = "List of connectors") + private List connectors = new ArrayList<>(); +} diff --git a/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiTaskInfo.java b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiTaskInfo.java new file mode 100644 index 000000000..c1ca4e955 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiTaskInfo.java @@ -0,0 +1,86 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api.dto; + +import de.rwth.idsg.steve.ocpp.CommunicationTask; +import de.rwth.idsg.steve.ocpp.OcppVersion; +import de.rwth.idsg.steve.ocpp.RequestResult; +import de.rwth.idsg.steve.ocpp.TaskOrigin; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import lombok.Getter; +import lombok.Setter; +import org.joda.time.DateTime; + +/** + * @author fnkbsi + * @since 18.10.2023 + */ + +@Getter +@Setter +public class ApiTaskInfo { + @Schema(description = "Task ID") + private Integer taskId; + @Schema(description = "OCPP version") + private OcppVersion ocppVersion; + @Schema(description = "OCPP operation") + private String operationName; + @Schema(description = "external / internal") + private TaskOrigin origin; + @Schema(description = "Caller of the Task") + private String caller; + + @Schema(description = "Results") + private Map resultMap; + @Schema(description = "Count of Results") + private int resultSize; + + @Schema(description = "Starttime") + private DateTime startTimestamp = DateTime.now(); + @Schema(description = "Endtime") + private DateTime endTimestamp; + + @Schema(description = "Error count") + private AtomicInteger errorCount = new AtomicInteger(0); + @Schema(description = "Response count") + private AtomicInteger responseCount = new AtomicInteger(0); + + + public ApiTaskInfo(Integer taskId, CommunicationTask r) { + this.taskId = taskId; + //this.ocppVersion = r.getOcppVersion(); + this.operationName = r.getOperationName(); + this.origin = r.getOrigin(); + this.caller = r.getCaller(); + + + this.resultMap = r.getResultMap(); + this.resultSize = r.getResultSize(); + + this.startTimestamp = r.getStartTimestamp(); + this.endTimestamp = r.getEndTimestamp(); + + this.errorCount = r.getErrorCount(); + this.responseCount = r.getResponseCount(); + } + +} diff --git a/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiTaskList.java b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiTaskList.java new file mode 100644 index 000000000..7efb067bb --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/api/dto/ApiTaskList.java @@ -0,0 +1,39 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.api.dto; + +import de.rwth.idsg.steve.repository.dto.TaskOverview; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author fnkbsi + * @since 18.10.2023 + */ + +@Getter +@Setter +//@RequiredArgsConstructor +public class ApiTaskList { + @Schema(description = "List of tasks") + private List tasks; +} diff --git a/src/main/java/de/rwth/idsg/steve/web/controller/HomeController.java b/src/main/java/de/rwth/idsg/steve/web/controller/HomeController.java index 7ea7b468d..39e2ed457 100644 --- a/src/main/java/de/rwth/idsg/steve/web/controller/HomeController.java +++ b/src/main/java/de/rwth/idsg/steve/web/controller/HomeController.java @@ -78,7 +78,12 @@ public String getConnectorStatusQuery(@ModelAttribute(PARAMS) ConnectorStatusFor model.addAttribute(PARAMS, params); List latestList = chargePointHelperService.getChargePointConnectorStatus(params); - List filteredList = ConnectorStatusFilter.filterAndPreferZero(latestList); + List filteredList; + if (params.getStrategy() == ConnectorStatusForm.Strategy.PreferZero) { + filteredList = ConnectorStatusFilter.filterAndPreferZero(latestList); + } else { + filteredList = ConnectorStatusFilter.filterAndPreferOthersWithStatusOfZero(latestList); + } model.addAttribute("connectorStatusList", filteredList); return "connectorStatus"; } diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/ConnectorStatusForm.java b/src/main/java/de/rwth/idsg/steve/web/dto/ConnectorStatusForm.java index 6bad67403..f054eb48d 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/ConnectorStatusForm.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/ConnectorStatusForm.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.web.dto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -30,6 +31,15 @@ @Setter @ToString public class ConnectorStatusForm { + @Schema(description = "Charge Box Id") private String chargeBoxId; + @Schema(description = "Connector Status") private String status; + @Schema(description = "Strategy of listing the connector") + private Strategy strategy = Strategy.PreferZero; + + public enum Strategy { + PreferZero, + PreferOthersWithStatusOfZero; + } } diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/OcppJsonStatus.java b/src/main/java/de/rwth/idsg/steve/web/dto/OcppJsonStatus.java index 69905d4bf..55a3466a3 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/OcppJsonStatus.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/OcppJsonStatus.java @@ -18,7 +18,9 @@ */ package de.rwth.idsg.steve.web.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; import de.rwth.idsg.steve.ocpp.OcppVersion; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -32,9 +34,17 @@ @Builder @ToString public final class OcppJsonStatus { + @JsonIgnore + @Schema(description = "Charge Box Pk", hidden = true) private final int chargeBoxPk; - private final String chargeBoxId, connectedSince; + @Schema(description = "Charge Box Id") + private final String chargeBoxId; + @Schema(description = "Connected since") + private final String connectedSince; + @Schema(description = "Duration of the Connection") private final String connectionDuration; + @Schema(description = "Ocpp version") private final OcppVersion version; + @Schema(description = "Connected since as DT", hidden = true) private final DateTime connectedSinceDT; } diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/ConfigurationKeyReadWriteEnum.java b/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/ConfigurationKeyReadWriteEnum.java index 07ada5870..44460a465 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/ConfigurationKeyReadWriteEnum.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/ConfigurationKeyReadWriteEnum.java @@ -1,31 +1,31 @@ -/* - * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2025 SteVe Community Team - * All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.rwth.idsg.steve.web.dto.ocpp; - -/** - * Determines if a configuration key is read-only ("R") or read-write ("RW"). In case the key is read-only, the Central - * System can read the value for the key using GetConfiguration, but not write it. In case the accessibility is - * read-write, the Central System can also write the value for the key using ChangeConfiguration. - * - * This distinction was added in OCPP 1.6. - */ -public enum ConfigurationKeyReadWriteEnum { - R, - RW -} +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.web.dto.ocpp; + +/** + * Determines if a configuration key is read-only ("R") or read-write ("RW"). In case the key is read-only, the Central + * System can read the value for the key using GetConfiguration, but not write it. In case the accessibility is + * read-write, the Central System can also write the value for the key using ChangeConfiguration. + * + * This distinction was added in OCPP 1.6. + */ +public enum ConfigurationKeyReadWriteEnum { + R, + RW +} diff --git a/src/main/resources/config/dev/main.properties b/src/main/resources/config/dev/main.properties index a45321f04..765f32919 100644 --- a/src/main/resources/config/dev/main.properties +++ b/src/main/resources/config/dev/main.properties @@ -21,7 +21,7 @@ auth.password = 1234 # Both must be set for Web APIs to be enabled. Otherwise, we will block all calls. # webapi.key = STEVE-API-KEY -webapi.value = +webapi.value = 1234 # Jetty configuration # diff --git a/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp b/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp index 4e7396d4d..8425c6b3f 100644 --- a/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp +++ b/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp @@ -51,6 +51,14 @@ Connector Status + + Status: + + + + + +