diff --git a/build.gradle b/build.gradle index b287658..acda1f6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' - id 'org.springframework.boot' version '2.7.4' - id 'io.spring.dependency-management' version '1.0.14.RELEASE' + id 'org.springframework.boot' version '3.2.0' + id 'io.spring.dependency-management' version '1.1.4' } group 'org.example' @@ -22,7 +22,7 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' implementation group: 'eu.chargetime.ocpp', name: 'v1_6', version: '1.0.1' - implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.3.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/com/yablochkov/ocppstub/AuthService.java b/src/main/java/com/yablochkov/ocppstub/AuthService.java new file mode 100644 index 0000000..80905d7 --- /dev/null +++ b/src/main/java/com/yablochkov/ocppstub/AuthService.java @@ -0,0 +1,17 @@ +package com.yablochkov.ocppstub; + +import eu.chargetime.ocpp.model.core.AuthorizationStatus; +import eu.chargetime.ocpp.model.core.AuthorizeConfirmation; +import eu.chargetime.ocpp.model.core.IdTagInfo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class AuthService { + + public IdTagInfo authorize(String idTag) { + log.info("Authorize id tag {}", idTag); + return new IdTagInfo(AuthorizationStatus.Accepted); + } +} diff --git a/src/main/java/com/yablochkov/ocppstub/EventHandler.java b/src/main/java/com/yablochkov/ocppstub/EventHandler.java index 11b06f8..9c1db65 100644 --- a/src/main/java/com/yablochkov/ocppstub/EventHandler.java +++ b/src/main/java/com/yablochkov/ocppstub/EventHandler.java @@ -20,10 +20,12 @@ public class EventHandler implements ServerCoreEventHandler, ServerEvents { private final ConfigService configService; private final ConnectorStatusService connectorService; private final Communicator communicator; + private final AuthService authService; + private final TransactionService transactionService; @Override public AuthorizeConfirmation handleAuthorizeRequest(UUID sessionIndex, AuthorizeRequest request) { - return null; + return new AuthorizeConfirmation(authService.authorize(request.getIdTag())); } @Override @@ -49,12 +51,12 @@ public class EventHandler implements ServerCoreEventHandler, ServerEvents { @Override public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, MeterValuesRequest request) { - return null; + return new MeterValuesConfirmation(); } @Override public StartTransactionConfirmation handleStartTransactionRequest(UUID sessionIndex, StartTransactionRequest request) { - return null; + return transactionService.startByRequest(sessionIndex, request.getConnectorId(), request.getIdTag()); } @Override @@ -65,7 +67,9 @@ public class EventHandler implements ServerCoreEventHandler, ServerEvents { @Override public StopTransactionConfirmation handleStopTransactionRequest(UUID sessionIndex, StopTransactionRequest request) { - return null; + log.info("Stop transaction request {}", request); + transactionService.stopByRequest(sessionIndex, request.getTransactionId()); + return new StopTransactionConfirmation(); } @Override diff --git a/src/main/java/com/yablochkov/ocppstub/OcppStub.java b/src/main/java/com/yablochkov/ocppstub/OcppStub.java index 76cdc27..eb038cf 100644 --- a/src/main/java/com/yablochkov/ocppstub/OcppStub.java +++ b/src/main/java/com/yablochkov/ocppstub/OcppStub.java @@ -6,10 +6,10 @@ import eu.chargetime.ocpp.UnsupportedFeatureException; import eu.chargetime.ocpp.YablJSONServer; import eu.chargetime.ocpp.model.Confirmation; import eu.chargetime.ocpp.model.Request; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.UUID; import java.util.concurrent.CompletionStage; @@ -21,7 +21,7 @@ public class OcppStub { @PostConstruct private void Init() { - server.open("0.0.0.0", 8080, eventHandler); + server.open("0.0.0.0", 8081, eventHandler); } public CompletionStage send(UUID sessionIndex, Request request) diff --git a/src/main/java/com/yablochkov/ocppstub/SessionService.java b/src/main/java/com/yablochkov/ocppstub/SessionService.java index 5afa14e..63c16ec 100644 --- a/src/main/java/com/yablochkov/ocppstub/SessionService.java +++ b/src/main/java/com/yablochkov/ocppstub/SessionService.java @@ -2,6 +2,8 @@ package com.yablochkov.ocppstub; import eu.chargetime.ocpp.YablSessionInformation; import eu.chargetime.ocpp.model.SessionInformation; +import java.util.Map.Entry; +import java.util.Objects; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.java_websocket.handshake.ClientHandshake; @@ -27,8 +29,7 @@ public class SessionService { public String getAuthBySessionId(UUID sessionId) { SessionInformation sessionInformation = cache.get(sessionId); - if (sessionInformation instanceof YablSessionInformation) { - YablSessionInformation session = (YablSessionInformation) sessionInformation; + if (sessionInformation instanceof YablSessionInformation session) { ClientHandshake clientHandshake = session.getClientHandshake(); if (clientHandshake != null) { @@ -39,4 +40,26 @@ public class SessionService { log.debug("Session {} auth status not found", sessionId); return null; } + + public String getIdentityBySessionId(UUID sessionId) { + SessionInformation sessionInformation = cache.get(sessionId); + if (Objects.nonNull(sessionInformation)) { + return sessionInformation.getIdentifier(); + } + return null; + } + + public UUID getSessionByIdentity(String identity) { + return cache.entrySet().stream() + .filter((entry) -> { + var entryValue = entry.getValue(); + if (Objects.nonNull(entryValue)) { + return Objects.equals(entryValue.getIdentifier(), identity); + } + return false; + }) + .findFirst() + .map(Entry::getKey) + .orElseThrow(); + } } diff --git a/src/main/java/com/yablochkov/ocppstub/TransactionService.java b/src/main/java/com/yablochkov/ocppstub/TransactionService.java new file mode 100644 index 0000000..62d84ee --- /dev/null +++ b/src/main/java/com/yablochkov/ocppstub/TransactionService.java @@ -0,0 +1,122 @@ +package com.yablochkov.ocppstub; + +import eu.chargetime.ocpp.NotConnectedException; +import eu.chargetime.ocpp.OccurenceConstraintException; +import eu.chargetime.ocpp.UnsupportedFeatureException; +import eu.chargetime.ocpp.model.Confirmation; +import eu.chargetime.ocpp.model.core.AuthorizationStatus; +import eu.chargetime.ocpp.model.core.IdTagInfo; +import eu.chargetime.ocpp.model.core.RemoteStartStopStatus; +import eu.chargetime.ocpp.model.core.RemoteStartTransactionConfirmation; +import eu.chargetime.ocpp.model.core.RemoteStartTransactionRequest; +import eu.chargetime.ocpp.model.core.RemoteStopTransactionConfirmation; +import eu.chargetime.ocpp.model.core.RemoteStopTransactionRequest; +import eu.chargetime.ocpp.model.core.StartTransactionConfirmation; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class TransactionService { + private final AtomicInteger transactionNumber = new AtomicInteger(); + private final Map transactionMap = new HashMap<>(); + + @Autowired private SessionService sessionService; + @Autowired private AuthService authService; + @Lazy @Autowired private OcppStub ocppStub; + + public StartTransactionConfirmation startByRequest(UUID sessionIndex, Integer connectorId, + String idTag) { + var identity = sessionService.getIdentityBySessionId(sessionIndex); + log.info("Try to start transaction for {}, connector {}, tag {}", identity, connectorId, idTag); + + if(Objects.nonNull(identity)) { + int transactionId = transactionNumber.incrementAndGet(); + var oldTransactionId = transactionMap.put(identity, transactionId); + if (Objects.nonNull(oldTransactionId)) { + log.warn("Already have transaction {}, continue", oldTransactionId); + } + return new StartTransactionConfirmation(authService.authorize(idTag), transactionId); + } + + log.error("Can't start transaction, identity not found"); + return new StartTransactionConfirmation(new IdTagInfo(AuthorizationStatus.Invalid), null); + } + + public void stopByRequest(UUID sessionIndex, Integer transactionId) { + var identity = sessionService.getIdentityBySessionId(sessionIndex); + log.info("Try to stop transaction for {}, with id {}", identity, transactionId); + + if (Objects.nonNull(identity)) { + transactionMap.remove(identity); + } + } + + public RemoteStartTransactionConfirmation remoteStart(String id) { + log.info("Try to start transaction for {}", id); + var session = sessionService.getSessionByIdentity(id); + log.info("Found session {}", session); + if (Objects.nonNull(session)) { + + try { + var future = ocppStub.send(session, + new RemoteStartTransactionRequest(UUID.randomUUID().toString())); + Confirmation confirmation = future.toCompletableFuture().get(); + + if (confirmation instanceof RemoteStartTransactionConfirmation startConfirmation) { + RemoteStartStopStatus status = startConfirmation.getStatus(); + log.info("Start transaction status {}", status); + if (RemoteStartStopStatus.Accepted.equals(status)) { + transactionMap.remove(id); + } + return startConfirmation; + } + } catch (InterruptedException | ExecutionException | OccurenceConstraintException | + UnsupportedFeatureException | NotConnectedException e) { + log.error("Caught exception on transaction start", e); + } + } + throw new RuntimeException("Can't start transaction"); + } + + public RemoteStopTransactionConfirmation remoteStop(String identity) { + log.info("Try to stop transaction for {}", identity); + var session = sessionService.getSessionByIdentity(identity); + log.info("Found session {}", session); + if (Objects.nonNull(session)) { + + Integer transactionId = transactionMap.get(identity); + log.info("Found transaction id {}", transactionId); + + if (Objects.nonNull(transactionId)) { + try { + var future = ocppStub.send(session, + new RemoteStopTransactionRequest(transactionId)); + + Confirmation confirmation = future.toCompletableFuture().get(); + + if (confirmation instanceof RemoteStopTransactionConfirmation stopConfirmation) { + RemoteStartStopStatus status = stopConfirmation.getStatus(); + log.info("Stop transaction status {}", status); + if (RemoteStartStopStatus.Accepted.equals(status)) { + transactionMap.remove(identity); + } + return stopConfirmation; + } + } catch (InterruptedException | ExecutionException | OccurenceConstraintException | + UnsupportedFeatureException | NotConnectedException e) { + log.error("Caught exception on transaction stop", e); + } + } + } + throw new RuntimeException("Can't stop transaction"); + } +} diff --git a/src/main/java/com/yablochkov/ocppstub/rest/ResetController.java b/src/main/java/com/yablochkov/ocppstub/rest/ResetController.java index 9c6128d..5e523e3 100644 --- a/src/main/java/com/yablochkov/ocppstub/rest/ResetController.java +++ b/src/main/java/com/yablochkov/ocppstub/rest/ResetController.java @@ -4,11 +4,14 @@ import static eu.chargetime.ocpp.model.core.ResetStatus.Rejected; import com.yablochkov.ocppstub.OcppStub; import com.yablochkov.ocppstub.SessionService; +import com.yablochkov.ocppstub.TransactionService; import eu.chargetime.ocpp.NotConnectedException; import eu.chargetime.ocpp.OccurenceConstraintException; import eu.chargetime.ocpp.UnsupportedFeatureException; import eu.chargetime.ocpp.model.Confirmation; import eu.chargetime.ocpp.model.SessionInformation; +import eu.chargetime.ocpp.model.core.RemoteStartTransactionConfirmation; +import eu.chargetime.ocpp.model.core.RemoteStopTransactionConfirmation; import eu.chargetime.ocpp.model.core.ResetConfirmation; import eu.chargetime.ocpp.model.core.ResetRequest; import java.util.Map; @@ -20,8 +23,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -33,19 +38,22 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j public class ResetController { @Autowired private SessionService sessionService; + @Autowired private TransactionService transactionService; @Lazy @Autowired private OcppStub ocppStub; @GetMapping("/list") public Map findClients() { return sessionService.getCache(); } - @PutMapping("/{id}") + @PutMapping("/reset/{identity}") @ResponseStatus(HttpStatus.OK) - public ResetConfirmation updateBook( - @PathVariable("id") final String id, @RequestBody final ResetRequest request) { + public ResetConfirmation reset( + @PathVariable("identity") final String identity, @RequestBody final ResetRequest request) { + // TODO REFACTOR THIS + log.info("Send reset to {}, parameters {}", identity, request); var session = sessionService.getCache() .entrySet().stream() - .filter((entry) -> Objects.equals(entry.getValue().getIdentifier(), id)) + .filter((entry) -> Objects.equals(entry.getValue().getIdentifier(), identity)) .map(Entry::getKey) .findFirst() .orElseThrow(); @@ -65,4 +73,14 @@ public class ResetController { return new ResetConfirmation(Rejected); } + + @PostMapping("/start/{identity}") + public RemoteStartTransactionConfirmation startTransaction(@PathVariable("identity") final String identity) { + return transactionService.remoteStart(identity); + } + + @DeleteMapping("/stop/{identity}") + public RemoteStopTransactionConfirmation stopTransaction(@PathVariable("identity") final String identity) { + return transactionService.remoteStop(identity); + } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml deleted file mode 100644 index 9d250ef..0000000 --- a/src/main/resources/application.yaml +++ /dev/null @@ -1,3 +0,0 @@ -springdoc: - swagger-ui: - path: "/api-docs.html" diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..e8e94c1 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,6 @@ +server: + port: 8080 + +springdoc: + swagger-ui: + path: "/api/docs.html" \ No newline at end of file