diff --git a/CHANGELOG.md b/CHANGELOG.md index 682e69bb..f367f5df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - StatusNotification compatibility ([#247](https://github.com/matth-x/MicroOcpp/pull/247)) - ChangeAvailability compatibility ([#285](https://github.com/matth-x/MicroOcpp/pull/285)) - Reset compatibility, UCs B11 - B12 ([#286](https://github.com/matth-x/MicroOcpp/pull/286)) + - RequestStart-/StopTransaction, UCs F01 - F02 ([#289](https://github.com/matth-x/MicroOcpp/pull/289)) ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index d4846ec7..4b4dccc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,8 @@ set(MO_SRC src/MicroOcpp/Operations/NotifyReport.cpp src/MicroOcpp/Operations/RemoteStartTransaction.cpp src/MicroOcpp/Operations/RemoteStopTransaction.cpp + src/MicroOcpp/Operations/RequestStartTransaction.cpp + src/MicroOcpp/Operations/RequestStopTransaction.cpp src/MicroOcpp/Operations/ReserveNow.cpp src/MicroOcpp/Operations/Reset.cpp src/MicroOcpp/Operations/SendLocalList.cpp diff --git a/README.md b/README.md index 78505c20..05814a92 100644 --- a/README.md +++ b/README.md @@ -80,12 +80,13 @@ The following OCPP 2.0.1 use cases are implemented: | UC | Description | Note | | :--- | :--- | :--- | | M03 - M05 | Certificate management | Enable Mbed-TLS to use the built-in certificate store | -| B05 - B07 | Variables | No persistency yet | +| B05 - B07 | Variables | | | B01 - B04
B11 - B12 | Provisioning | Ported from OCPP 1.6 | | E01 - E12 | Transactions | | +| F01 - F02 | Remote Start/Stop Tx | | | - | Protocol negotiation | The charger can select the OCPP version at runtime | -The OCPP 2.0.1 features are in an early development stage. By default, they are disabled and excluded from the build, so they have no impact on the firmware size. To enable, set the build flag `MO_ENABLE_V201=1` and initialize the library with the ProtocolVersion parameter `2.0.1` (see [this example](https://github.com/matth-x/MicroOcppSimulator/blob/657e606c3b178d3add242935d413c72624130ff3/src/main.cpp#L43-L47) in the Simulator). +The OCPP 2.0.1 features are in an alpha development stage (no persistency yet). By default, they are disabled and excluded from the build, so they have no impact on the firmware size. To enable, set the build flag `MO_ENABLE_V201=1` and initialize the library with the ProtocolVersion parameter `2.0.1` (see [this example](https://github.com/matth-x/MicroOcppSimulator/blob/657e606c3b178d3add242935d413c72624130ff3/src/main.cpp#L43-L47) in the Simulator). An integration of the library for OCPP 1.6 will also be functional with the 2.0.1 upgrade. It works with the same API in MicroOcpp.h. diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp index 94bc250e..b13eb516 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.cpp +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -27,6 +27,42 @@ IdToken::IdToken(const char *token, Type type) : type(type) { } } +bool IdToken::parseCstr(const char *token, const char *typeCstr) { + if (!token || !typeCstr) { + MO_DBG_DEBUG("TRACE"); + return false; + } + + if (!strcmp(typeCstr, "Central")) { + type = Type::Central; + } else if (!strcmp(typeCstr, "eMAID")) { + type = Type::eMAID; + } else if (!strcmp(typeCstr, "ISO14443")) { + type = Type::ISO14443; + } else if (!strcmp(typeCstr, "ISO15693")) { + type = Type::ISO15693; + } else if (!strcmp(typeCstr, "KeyCode")) { + MO_DBG_DEBUG("TRACE"); + type = Type::KeyCode; + } else if (!strcmp(typeCstr, "Local")) { + type = Type::Local; + } else if (!strcmp(typeCstr, "MacAddress")) { + type = Type::MacAddress; + } else if (!strcmp(typeCstr, "NoAuthorization")) { + type = Type::NoAuthorization; + } else { + MO_DBG_DEBUG("TRACE"); + return false; + } + + auto ret = snprintf(idToken, sizeof(idToken), "%s", token); + if (ret < 0 || (size_t)ret >= sizeof(idToken)) { + return false; + } + + return true; +} + const char *IdToken::get() const { return *idToken ? idToken : nullptr;; } diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h index 492f02a2..8f4ba190 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.h +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -39,6 +39,8 @@ class IdToken { IdToken(); IdToken(const char *token, Type type = Type::ISO14443); + bool parseCstr(const char *token, const char *typeCstr); + const char *get() const; const char *getTypeCstr() const; diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 788655c2..9f67ed20 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -173,11 +173,10 @@ class Transaction { #include +#include #include #include -#define MO_TXID_LEN_MAX 36 - namespace MicroOcpp { namespace Ocpp201 { @@ -264,14 +263,19 @@ class Transaction { char transactionId [MO_TXID_LEN_MAX + 1] = {'\0'}; int remoteStartId = -1; - bool idTokenTransmitted = true; + //if to fill next TxEvent with optional fields + bool notifyEvseId = false; + bool notifyIdToken = false; + bool notifyStopIdToken = false; + bool notifyReservationId = false; + bool notifyChargingState = false; + bool notifyRemoteStartId = false; bool evConnectionTimeoutListen = true; StopReason stopReason = StopReason::UNDEFINED; TransactionEventTriggerReason stopTrigger = TransactionEventTriggerReason::UNDEFINED; std::unique_ptr stopIdToken; // if null, then stopIdToken equals idToken - bool stopIdTokenTransmitted = true; }; // TransactionEventRequest (1.60.1) @@ -305,6 +309,7 @@ class TransactionEventData { int numberOfPhasesUsed = -1; int cableMaxCurrent = -1; int reservationId = -1; + int remoteStartId = -1; // TransactionType (2.48) ChargingState chargingState = ChargingState::UNDEFINED; diff --git a/src/MicroOcpp/Model/Transactions/TransactionDefs.h b/src/MicroOcpp/Model/Transactions/TransactionDefs.h new file mode 100644 index 00000000..1a871b4b --- /dev/null +++ b/src/MicroOcpp/Model/Transactions/TransactionDefs.h @@ -0,0 +1,20 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_TRANSACTIONDEFS_H +#define MO_TRANSACTIONDEFS_H + +#include + +#if MO_ENABLE_V201 + +#define MO_TXID_LEN_MAX 36 + +typedef enum RequestStartStopStatus { + RequestStartStopStatus_Accepted, + RequestStartStopStatus_Rejected +} RequestStartStopStatus; + +#endif //MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index 62520f3f..41c5d0e8 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include @@ -102,23 +104,32 @@ void TransactionService::Evse::loop() { txStopCondition = true; triggerReason = transaction->stopTrigger; stopReason = transaction->stopReason; - } else if (txService.isTxStopPoint(TxStartStopPoint::EVConnected) && + } else if ((txService.isTxStopPoint(TxStartStopPoint::EVConnected) || + txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) && connectorPluggedInput && !connectorPluggedInput() && (txService.stopTxOnEVSideDisconnectBool->getBool() || !transaction || !transaction->started)) { txStopCondition = true; triggerReason = TransactionEventTriggerReason::EVCommunicationLost; stopReason = Ocpp201::Transaction::StopReason::EVDisconnected; - } else if (txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed) && - evReadyInput && !evReadyInput()) { - txStopCondition = true; - triggerReason = TransactionEventTriggerReason::ChargingStateChanged; - stopReason = Ocpp201::Transaction::StopReason::StoppedByEV; - } else if (txService.isTxStopPoint(TxStartStopPoint::Authorized) && + } else if ((txService.isTxStopPoint(TxStartStopPoint::Authorized) || + txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) && (!transaction || !transaction->isAuthorized)) { // user revoked authorization (or EV or any "local" entity) txStopCondition = true; triggerReason = TransactionEventTriggerReason::StopAuthorized; stopReason = Ocpp201::Transaction::StopReason::Local; + } else if (txService.isTxStopPoint(TxStartStopPoint::EnergyTransfer) && + evReadyInput && !evReadyInput()) { + txStopCondition = true; + triggerReason = TransactionEventTriggerReason::ChargingStateChanged; + stopReason = Ocpp201::Transaction::StopReason::StoppedByEV; + } else if (txService.isTxStopPoint(TxStartStopPoint::EnergyTransfer) && + (evReadyInput || evseReadyInput) && // at least one of the two defined + !(evReadyInput && evReadyInput()) && + !(evseReadyInput && evseReadyInput())) { + txStopCondition = true; + triggerReason = TransactionEventTriggerReason::ChargingStateChanged; + stopReason = Ocpp201::Transaction::StopReason::Other; } else if (txService.isTxStopPoint(TxStartStopPoint::Authorized) && transaction && transaction->isDeauthorized && txService.stopTxOnInvalidIdBool->getBool()) { @@ -164,18 +175,33 @@ void TransactionService::Evse::loop() { TransactionEventTriggerReason triggerReason = TransactionEventTriggerReason::UNDEFINED; // tx should be started? - if (txService.isTxStartPoint(TxStartStopPoint::EVConnected) && + if (txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) && + (!connectorPluggedInput || connectorPluggedInput()) && + transaction && transaction->isAuthorized) { + txStartCondition = true; + if (transaction->remoteStartId >= 0) { + triggerReason = TransactionEventTriggerReason::RemoteStart; + } else { + triggerReason = TransactionEventTriggerReason::Authorized; + } + } else if (txService.isTxStartPoint(TxStartStopPoint::Authorized) && + transaction && transaction->isAuthorized) { + txStartCondition = true; + if (transaction->remoteStartId >= 0) { + triggerReason = TransactionEventTriggerReason::RemoteStart; + } else { + triggerReason = TransactionEventTriggerReason::Authorized; + } + } else if (txService.isTxStartPoint(TxStartStopPoint::EVConnected) && connectorPluggedInput && connectorPluggedInput()) { txStartCondition = true; triggerReason = TransactionEventTriggerReason::CablePluggedIn; - } else if (txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) && - evReadyInput && evReadyInput()) { + } else if (txService.isTxStartPoint(TxStartStopPoint::EnergyTransfer) && + (evReadyInput || evseReadyInput) && // at least one of the two defined + (!evReadyInput || evReadyInput()) && + (!evseReadyInput || evseReadyInput())) { txStartCondition = true; triggerReason = TransactionEventTriggerReason::ChargingStateChanged; - } else if (txService.isTxStartPoint(TxStartStopPoint::Authorized) && - transaction && transaction->isAuthorized) { - txStartCondition = true; - triggerReason = TransactionEventTriggerReason::Authorized; } if (txStartCondition && @@ -189,6 +215,9 @@ void TransactionService::Evse::loop() { // OOM return; } + if (evseId > 0) { + transaction->notifyEvseId = true; + } } txEvent = std::make_shared(transaction, transaction->seqNoCounter++); @@ -211,7 +240,7 @@ void TransactionService::Evse::loop() { chargingState = TransactionEventData::ChargingState::SuspendedEVSE; } else if (evReadyInput && !evReadyInput()) { chargingState = TransactionEventData::ChargingState::SuspendedEV; - } else if (transaction && transaction->started && transaction->active) { + } else if (ocppPermitsCharge()) { chargingState = TransactionEventData::ChargingState::Charging; } @@ -225,10 +254,19 @@ void TransactionService::Evse::loop() { if (chargingState != trackChargingState) { txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::ChargingStateChanged; + transaction->notifyChargingState = true; } trackChargingState = chargingState; - if (connectorPluggedInput && connectorPluggedInput() && !transaction->trackEvConnected) { + if (transaction->isAuthorized && !transaction->trackAuthorized) { + transaction->trackAuthorized = true; + txUpdateCondition = true; + if (transaction->remoteStartId >= 0) { + triggerReason = TransactionEventTriggerReason::RemoteStart; + } else { + triggerReason = TransactionEventTriggerReason::Authorized; + } + } else if (connectorPluggedInput && connectorPluggedInput() && !transaction->trackEvConnected) { transaction->trackEvConnected = true; txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::CablePluggedIn; @@ -236,10 +274,6 @@ void TransactionService::Evse::loop() { transaction->trackEvConnected = false; txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::EVCommunicationLost; - } else if (transaction->isAuthorized && !transaction->trackAuthorized) { - transaction->trackAuthorized = true; - txUpdateCondition = true; - triggerReason = TransactionEventTriggerReason::Authorized; } else if (!transaction->isAuthorized && transaction->trackAuthorized) { transaction->trackAuthorized = false; txUpdateCondition = true; @@ -266,16 +300,26 @@ void TransactionService::Evse::loop() { if (txEvent) { txEvent->timestamp = context.getModel().getClock().now(); - txEvent->chargingState = chargingState; - txEvent->evse = evseId; + if (transaction->notifyChargingState) { + txEvent->chargingState = chargingState; + transaction->notifyChargingState = false; + } + if (transaction->notifyEvseId) { + txEvent->evse = EvseId(evseId, 1); + transaction->notifyEvseId = false; + } + if (transaction->notifyRemoteStartId) { + txEvent->remoteStartId = transaction->remoteStartId; + transaction->notifyRemoteStartId = false; + } // meterValue not supported - if (!transaction->stopIdTokenTransmitted && transaction->stopIdToken) { + if (transaction->notifyStopIdToken && transaction->stopIdToken) { txEvent->idToken = std::unique_ptr(new IdToken(*transaction->stopIdToken.get())); - transaction->stopIdTokenTransmitted = true; - } else if (!transaction->idTokenTransmitted) { + transaction->notifyStopIdToken = false; + } else if (transaction->notifyIdToken) { txEvent->idToken = std::unique_ptr(new IdToken(transaction->idToken)); - transaction->idTokenTransmitted = true; + transaction->notifyIdToken = false; } } @@ -298,13 +342,10 @@ void TransactionService::Evse::setEvseReadyInput(std::function connector this->evseReadyInput = connectorEnergized; } -bool TransactionService::Evse::beginAuthorization(IdToken idToken) { +bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validateIdToken) { MO_DBG_DEBUG("begin auth: %s", idToken.get()); - MO_DBG_DEBUG("patch idTokenType KeyCode for OCTT"); - idToken = IdToken(idToken.get(), IdToken::Type::KeyCode); - - if (transaction && transaction->idToken.get()) { + if (transaction && (transaction->idToken.get() || !transaction->active)) { MO_DBG_WARN("tx process still running. Please call endTransaction(...) before"); return false; } @@ -315,6 +356,9 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken) { MO_DBG_ERR("could not allocate Tx"); return false; } + if (evseId > 0) { + transaction->notifyEvseId = true; + } } transaction->idToken = idToken; @@ -322,28 +366,35 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken) { auto tx = transaction; - auto authorize = makeRequest(new Authorize(context.getModel(), idToken)); - if (!authorize) { - // OOM - return false; - } - - authorize->setOnReceiveConfListener([this, tx] (JsonObject response) { - if (strcmp(response["idTokenInfo"]["status"] | "_Undefined", "Accepted")) { - MO_DBG_DEBUG("Authorize rejected (%s), abort tx process", tx->idToken.get()); - tx->isDeauthorized = true; - return; + if (validateIdToken) { + auto authorize = makeRequest(new Authorize(context.getModel(), idToken)); + if (!authorize) { + // OOM + return false; } - MO_DBG_DEBUG("Authorized transaction process (%s)", tx->idToken.get()); + authorize->setOnReceiveConfListener([this, tx] (JsonObject response) { + if (strcmp(response["idTokenInfo"]["status"] | "_Undefined", "Accepted")) { + MO_DBG_DEBUG("Authorize rejected (%s), abort tx process", tx->idToken.get()); + tx->isDeauthorized = true; + return; + } + + MO_DBG_DEBUG("Authorized tx with validation (%s)", tx->idToken.get()); + tx->isAuthorized = true; + tx->notifyIdToken = true; + }); + authorize->setTimeout(20 * 1000); + context.initiateRequest(std::move(authorize)); + } else { + MO_DBG_DEBUG("Authorized tx directly (%s)", tx->idToken.get()); tx->isAuthorized = true; - tx->idTokenTransmitted = false; - }); - authorize->setTimeout(20 * 1000); - context.initiateRequest(std::move(authorize)); + tx->notifyIdToken = true; + } + return true; } -bool TransactionService::Evse::endAuthorization(IdToken idToken) { +bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateIdToken) { if (!transaction || !transaction->active) { //transaction already ended / not active anymore @@ -356,7 +407,11 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken) { if (transaction->idToken.equals(idToken)) { // use same idToken like tx start transaction->isAuthorized = false; - transaction->idTokenTransmitted = false; + transaction->notifyIdToken = true; + } else if (!validateIdToken) { + transaction->stopIdToken = std::unique_ptr(new IdToken(idToken)); + transaction->isAuthorized = false; + transaction->notifyStopIdToken = true; } else { // use a different idToken for stopping the tx @@ -389,7 +444,7 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken) { } tx->isAuthorized = false; - tx->stopIdTokenTransmitted = false; + tx->notifyStopIdToken = true; }); authorize->setTimeout(20 * 1000); context.initiateRequest(std::move(authorize)); @@ -413,14 +468,15 @@ bool TransactionService::Evse::abortTransaction(Ocpp201::Transaction::StopReason return true; } -const std::shared_ptr& TransactionService::Evse::getTransaction() { +std::shared_ptr& TransactionService::Evse::getTransaction() { return transaction; } bool TransactionService::Evse::ocppPermitsCharge() { return transaction && transaction->active && - transaction->isAuthorized; + transaction->isAuthorized && + !transaction->isDeauthorized; } bool TransactionService::isTxStartPoint(TxStartStopPoint check) { @@ -490,11 +546,13 @@ TransactionService::TransactionService(Context& context) : context(context) { auto variableService = context.getModel().getVariableService(); txStartPointString = variableService->declareVariable("TxCtrlr", "TxStartPoint", "PowerPathClosed"); - txStopPointString = variableService->declareVariable("TxCtrlr", "TxStopPoint", "EVConnected"); + txStopPointString = variableService->declareVariable("TxCtrlr", "TxStopPoint", "PowerPathClosed"); stopTxOnInvalidIdBool = variableService->declareVariable("TxCtrlr", "StopTxOnInvalidId", true); stopTxOnEVSideDisconnectBool = variableService->declareVariable("TxCtrlr", "StopTxOnEVSideDisconnect", true); evConnectionTimeOutInt = variableService->declareVariable("TxCtrlr", "EVConnectionTimeOut", 30); + variableService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false, MO_VARIABLE_VOLATILE, Variable::Mutability::ReadOnly); + variableService->registerValidator("TxCtrlr", "TxStartPoint", [this] (const char *value) -> bool { std::vector validated; return this->parseTxStartStopPoint(value, validated); @@ -505,9 +563,21 @@ TransactionService::TransactionService(Context& context) : context(context) { return this->parseTxStartStopPoint(value, validated); }); - for (unsigned int evseId = 1; evseId < MO_NUM_EVSE; evseId++) { + evses.reserve(MO_NUM_EVSE); + + for (unsigned int evseId = 0; evseId < MO_NUM_EVSE; evseId++) { evses.emplace_back(context, *this, evseId); } + + //make sure EVSE 0 will only trigger transactions if TxStartPoint is Authorized + evses[0].connectorPluggedInput = [] () {return false;}; + evses[0].evReadyInput = [] () {return false;}; + evses[0].evseReadyInput = [] () {return false;}; + + context.getOperationRegistry().registerOperation("RequestStartTransaction", [this] () { + return new RequestStartTransaction(*this);}); + context.getOperationRegistry().registerOperation("RequestStopTransaction", [this] () { + return new RequestStopTransaction(*this);}); } void TransactionService::loop() { @@ -522,15 +592,77 @@ void TransactionService::loop() { if (txStopPointString->getWriteCount() != trackTxStopPoint) { parseTxStartStopPoint(txStopPointString->getString(), txStopPointParsed); } + + // assign tx on evseId 0 to an EVSE + if (auto& tx0 = evses[0].getTransaction()) { + //pending tx on evseId 0 + if (tx0->active) { + for (unsigned int evseId = 1; evseId < MO_NUM_EVSE; evseId++) { + if (!evses[evseId].getTransaction() && + (!evses[evseId].connectorPluggedInput || evses[evseId].connectorPluggedInput())) { + MO_DBG_INFO("assign tx to evse %u", evseId); + tx0->notifyEvseId = true; + evses[evseId].transaction = std::move(tx0); + } + } + } + } } TransactionService::Evse *TransactionService::getEvse(unsigned int evseId) { - if (evseId >= 1 && evseId - 1 < evses.size()) { - return &evses[evseId - 1]; + if (evseId < evses.size()) { + return &evses[evseId]; } else { MO_DBG_ERR("invalid arg"); return nullptr; } } +RequestStartStopStatus TransactionService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, char *transactionIdOut) { + auto evse = getEvse(evseId); + if (!evse) { + return RequestStartStopStatus_Rejected; + } + + auto tx = evse->getTransaction(); + if (tx) { + auto ret = snprintf(transactionIdOut, MO_TXID_LEN_MAX + 1, "%s", tx->transactionId); + if (ret < 0 || ret >= MO_TXID_LEN_MAX + 1) { + MO_DBG_ERR("internal error"); + return RequestStartStopStatus_Rejected; + } + } + + if (!evse->beginAuthorization(idToken, false)) { + MO_DBG_INFO("EVSE still occupied with pending tx"); + return RequestStartStopStatus_Rejected; + } + + tx = evse->getTransaction(); + if (!tx) { + MO_DBG_ERR("internal error"); + return RequestStartStopStatus_Rejected; + } + + tx->remoteStartId = remoteStartId; + tx->notifyRemoteStartId = true; + + return RequestStartStopStatus_Accepted; +} + +RequestStartStopStatus TransactionService::requestStopTransaction(const char *transactionId) { + bool success = false; + + for (Evse& evse : evses) { + if (evse.getTransaction() && !strcmp(evse.getTransaction()->transactionId, transactionId)) { + success = evse.abortTransaction(Ocpp201::Transaction::StopReason::Remote, TransactionEventTriggerReason::RemoteStop); + break; + } + } + + return success ? + RequestStartStopStatus_Accepted : + RequestStartStopStatus_Rejected; +} + #endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.h b/src/MicroOcpp/Model/Transactions/TransactionService.h index 0f0b78bd..20edc212 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService.h @@ -14,6 +14,7 @@ #if MO_ENABLE_V201 #include +#include #include #include @@ -52,18 +53,18 @@ class TransactionService { void setEvReadyInput(std::function evRequestsEnergy); void setEvseReadyInput(std::function connectorEnergized); - bool beginAuthorization(IdToken idToken); // authorize by swipe RFID - bool endAuthorization(IdToken idToken = IdToken()); // stop authorization by swipe RFID + bool beginAuthorization(IdToken idToken, bool validateIdToken = true); // authorize by swipe RFID + bool endAuthorization(IdToken idToken = IdToken(), bool validateIdToken = true); // stop authorization by swipe RFID // stop transaction, but neither upon user request nor OCPP server request (e.g. after PowerLoss) bool abortTransaction(Ocpp201::Transaction::StopReason stopReason = Ocpp201::Transaction::StopReason::Other, Ocpp201::TransactionEventTriggerReason stopTrigger = Ocpp201::TransactionEventTriggerReason::AbnormalCondition); - const std::shared_ptr& getTransaction(); + std::shared_ptr& getTransaction(); bool ocppPermitsCharge(); - }; - friend Evse; + friend TransactionService; + }; private: @@ -100,6 +101,10 @@ class TransactionService { void loop(); Evse *getEvse(unsigned int evseId); + + RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, char *transactionIdOut); //ChargingProfile, GroupIdToken not supported yet + + RequestStartStopStatus requestStopTransaction(const char *transactionId); }; } // namespace MicroOcpp diff --git a/src/MicroOcpp/Operations/GetBaseReport.h b/src/MicroOcpp/Operations/GetBaseReport.h index 2c276131..2eb85ab2 100644 --- a/src/MicroOcpp/Operations/GetBaseReport.h +++ b/src/MicroOcpp/Operations/GetBaseReport.h @@ -20,8 +20,6 @@ class VariableService; namespace Ocpp201 { - - class GetBaseReport : public Operation { private: VariableService& variableService; diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.cpp b/src/MicroOcpp/Operations/RequestStartTransaction.cpp new file mode 100644 index 00000000..29ac3627 --- /dev/null +++ b/src/MicroOcpp/Operations/RequestStartTransaction.cpp @@ -0,0 +1,76 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +using MicroOcpp::Ocpp201::RequestStartTransaction; + +RequestStartTransaction::RequestStartTransaction(TransactionService& txService) : txService(txService) { + +} + +const char* RequestStartTransaction::getOperationType(){ + return "RequestStartTransaction"; +} + +void RequestStartTransaction::processReq(JsonObject payload) { + + int evseId = payload["evseId"] | 0; + if (evseId < 0 || evseId >= MO_NUM_EVSE) { + errorCode = "PropertyConstraintViolation"; + return; + } + + int remoteStartId = payload["remoteStartId"] | 0; + if (remoteStartId < 0) { + errorCode = "PropertyConstraintViolation"; + MO_DBG_ERR("IDs must be >= 0"); + return; + } + + IdToken idToken; + if (!idToken.parseCstr(payload["idToken"]["idToken"] | (const char*)nullptr, payload["idToken"]["type"] | (const char*)nullptr)) { //parseCstr rejects nullptr as argument + MO_DBG_ERR("could not parse idToken"); + errorCode = "FormationViolation"; + return; + } + + status = txService.requestStartTransaction(evseId, remoteStartId, idToken, transactionId); +} + +std::unique_ptr RequestStartTransaction::createConf(){ + + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2))); + JsonObject payload = doc->to(); + + const char *statusCstr = ""; + + switch (status) { + case RequestStartStopStatus_Accepted: + statusCstr = "Accepted"; + break; + case RequestStartStopStatus_Rejected: + statusCstr = "Rejected"; + break; + default: + MO_DBG_ERR("internal error"); + break; + } + + payload["status"] = statusCstr; + + if (*transactionId) { + payload["transactionId"] = (const char*)transactionId; //force zero-copy mode + } + + return doc; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.h b/src/MicroOcpp/Operations/RequestStartTransaction.h new file mode 100644 index 00000000..9a4b6ab4 --- /dev/null +++ b/src/MicroOcpp/Operations/RequestStartTransaction.h @@ -0,0 +1,48 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_REQUESTSTARTTRANSACTION_H +#define MO_REQUESTSTARTTRANSACTION_H + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +namespace MicroOcpp { + +class TransactionService; + +namespace Ocpp201 { + +class RequestStartTransaction : public Operation { +private: + TransactionService& txService; + + RequestStartStopStatus status; + char transactionId [MO_TXID_LEN_MAX + 1] = {'\0'}; + + const char *errorCode = nullptr; +public: + RequestStartTransaction(TransactionService& txService); + + const char* getOperationType() override; + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} + +}; + +} //namespace Ocpp201 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V201 + +#endif diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.cpp b/src/MicroOcpp/Operations/RequestStopTransaction.cpp new file mode 100644 index 00000000..6baf7bcb --- /dev/null +++ b/src/MicroOcpp/Operations/RequestStopTransaction.cpp @@ -0,0 +1,59 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +using MicroOcpp::Ocpp201::RequestStopTransaction; + +RequestStopTransaction::RequestStopTransaction(TransactionService& txService) : txService(txService) { + +} + +const char* RequestStopTransaction::getOperationType(){ + return "RequestStopTransaction"; +} + +void RequestStopTransaction::processReq(JsonObject payload) { + + if (!payload.containsKey("transactionId") || + !payload["transactionId"].is() || + strlen(payload["transactionId"].as()) > MO_TXID_LEN_MAX) { + errorCode = "FormationViolation"; + return; + } + + status = txService.requestStopTransaction(payload["transactionId"].as()); +} + +std::unique_ptr RequestStopTransaction::createConf(){ + + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); + JsonObject payload = doc->to(); + + const char *statusCstr = ""; + + switch (status) { + case RequestStartStopStatus_Accepted: + statusCstr = "Accepted"; + break; + case RequestStartStopStatus_Rejected: + statusCstr = "Rejected"; + break; + default: + MO_DBG_ERR("internal error"); + break; + } + + payload["status"] = statusCstr; + + return doc; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.h b/src/MicroOcpp/Operations/RequestStopTransaction.h new file mode 100644 index 00000000..40222c7a --- /dev/null +++ b/src/MicroOcpp/Operations/RequestStopTransaction.h @@ -0,0 +1,47 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_REQUESTSTOPTRANSACTION_H +#define MO_REQUESTSTOPTRANSACTION_H + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +namespace MicroOcpp { + +class TransactionService; + +namespace Ocpp201 { + +class RequestStopTransaction : public Operation { +private: + TransactionService& txService; + + RequestStartStopStatus status; + + const char *errorCode = nullptr; +public: + RequestStopTransaction(TransactionService& txService); + + const char* getOperationType() override; + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} + +}; + +} //namespace Ocpp201 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V201 + +#endif diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp index 2de39cb6..cdcd80a5 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.cpp +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -256,8 +256,8 @@ std::unique_ptr TransactionEvent::createReq() { transactionInfo["stoppedReason"] = stoppedReason; } - if (txEvent->transaction->remoteStartId >= 0) { - payload["remoteStartId"] = txEvent->transaction->remoteStartId; + if (txEvent->remoteStartId >= 0) { + transactionInfo["remoteStartId"] = txEvent->transaction->remoteStartId; } if (txEvent->idToken) {