From 94057aca41fba2c1f67542cf3c87b066051fb20e Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:29:07 +0200 Subject: [PATCH 01/18] multiple fixes after v201 tests --- CMakeLists.txt | 4 + src/MicroOcpp.cpp | 75 +++++-- src/MicroOcpp/Core/Memory.cpp | 3 +- .../Availability/AvailabilityService.cpp | 178 +++++++++++++++++ .../Model/Availability/AvailabilityService.h | 90 +++++++++ src/MicroOcpp/Model/Model.cpp | 13 ++ src/MicroOcpp/Model/Model.h | 5 + .../Model/Transactions/Transaction.h | 10 +- src/MicroOcpp/Operations/CustomOperation.cpp | 4 +- src/MicroOcpp/Operations/GetVariables.cpp | 2 +- .../Operations/SecurityEventNotification.cpp | 54 +++++ .../Operations/SecurityEventNotification.h | 48 +++++ tests/Boot.cpp | 46 +++++ tests/Reset.cpp | 75 ++++++- tests/Security.cpp | 58 ++++++ tests/Transactions.cpp | 97 ++++++++- tests/Variables.cpp | 188 +++++++++++++++++- 17 files changed, 902 insertions(+), 48 deletions(-) create mode 100644 src/MicroOcpp/Model/Availability/AvailabilityService.cpp create mode 100644 src/MicroOcpp/Model/Availability/AvailabilityService.h create mode 100644 src/MicroOcpp/Operations/SecurityEventNotification.cpp create mode 100644 src/MicroOcpp/Operations/SecurityEventNotification.h create mode 100644 tests/Security.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d8fffc8..7a4d838d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ set(MO_SRC src/MicroOcpp/Operations/RequestStopTransaction.cpp src/MicroOcpp/Operations/ReserveNow.cpp src/MicroOcpp/Operations/Reset.cpp + src/MicroOcpp/Operations/SecurityEventNotification.cpp src/MicroOcpp/Operations/SendLocalList.cpp src/MicroOcpp/Operations/SetChargingProfile.cpp src/MicroOcpp/Operations/SetVariables.cpp @@ -65,6 +66,7 @@ set(MO_SRC src/MicroOcpp/Debug.cpp src/MicroOcpp/Platform.cpp src/MicroOcpp/Core/OperationRegistry.cpp + src/MicroOcpp/Model/Availability/AvailabilityService.cpp src/MicroOcpp/Model/Authorization/AuthorizationData.cpp src/MicroOcpp/Model/Authorization/AuthorizationList.cpp src/MicroOcpp/Model/Authorization/AuthorizationService.cpp @@ -149,6 +151,7 @@ set(MO_SRC_UNIT tests/FirmwareManagement.cpp tests/ChargePointError.cpp tests/Boot.cpp + tests/Security.cpp ) add_executable(mo_unit_tests @@ -192,6 +195,7 @@ target_compile_definitions(mo_unit_tests PUBLIC MO_ENABLE_CERT_MGMT=1 MO_ENABLE_CONNECTOR_LOCK=1 MO_REPORT_NOERROR=1 + MO_ENABLE_V201=1 MO_OVERRIDE_ALLOCATION=1 MO_ENABLE_HEAP_PROFILER=1 MO_HEAP_PROFILER_EXTERNAL_CONTROL=1 diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 3b2d070c..671e335a 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -278,17 +279,30 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden auto& model = context->getModel(); - model.setTransactionStore(std::unique_ptr( - new TransactionStore(MO_NUMCONNECTORS, filesystem))); model.setBootService(std::unique_ptr( new BootService(*context, filesystem))); - model.setConnectorsCommon(std::unique_ptr( - new ConnectorsCommon(*context, MO_NUMCONNECTORS, filesystem))); - auto connectors = makeVector>("v16.ConnectorBase.Connector"); - for (unsigned int connectorId = 0; connectorId < MO_NUMCONNECTORS; connectorId++) { - connectors.emplace_back(new Connector(*context, filesystem, connectorId)); + +#if MO_ENABLE_V201 + if (version.major == 2) { + model.setAvailabilityService(std::unique_ptr( + new AvailabilityService(*context, MO_NUM_EVSE))); + model.setVariableService(std::unique_ptr( + new VariableService(*context, filesystem))); + model.setTransactionService(std::unique_ptr( + new TransactionService(*context))); + } else +#endif + { + model.setTransactionStore(std::unique_ptr( + new TransactionStore(MO_NUMCONNECTORS, filesystem))); + model.setConnectorsCommon(std::unique_ptr( + new ConnectorsCommon(*context, MO_NUMCONNECTORS, filesystem))); + auto connectors = makeVector>("v16.ConnectorBase.Connector"); + for (unsigned int connectorId = 0; connectorId < MO_NUMCONNECTORS; connectorId++) { + connectors.emplace_back(new Connector(*context, filesystem, connectorId)); + } + model.setConnectors(std::move(connectors)); } - model.setConnectors(std::move(connectors)); model.setHeartbeatService(std::unique_ptr( new HeartbeatService(*context))); @@ -302,13 +316,6 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden new ReservationService(*context, MO_NUMCONNECTORS))); #endif -#if MO_ENABLE_V201 - model.setVariableService(std::unique_ptr( - new VariableService(*context, filesystem))); - model.setTransactionService(std::unique_ptr( - new TransactionService(*context))); -#endif - #if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS std::unique_ptr certStore = makeCertificateStoreMbedTLS(filesystem); if (certStore) { @@ -366,7 +373,7 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden configuration_load(); - MO_DBG_INFO("initialized MicroOcpp v" MO_VERSION); + MO_DBG_INFO("initialized MicroOcpp v" MO_VERSION " running OCPP %i.%i.%i", version.major, version.minor, version.patch); } void mocpp_deinitialize() { @@ -612,6 +619,15 @@ ChargePointStatus getChargePointStatus(unsigned int connectorId) { MO_DBG_WARN("OCPP uninitialized"); return ChargePointStatus_UNDEFINED; } +#if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + if (auto availabilityService = context->getModel().getAvailabilityService()) { + if (auto evse = availabilityService->getEvse(connectorId)) { + return evse->getStatus(); + } + } + } +#endif auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); @@ -627,6 +643,11 @@ void setConnectorPluggedInput(std::function pluggedInput, unsigned int c } #if MO_ENABLE_V201 if (context->getVersion().major == 2) { + if (auto availabilityService = context->getModel().getAvailabilityService()) { + if (auto evse = availabilityService->getEvse(connectorId)) { + evse->setConnectorPluggedInput(pluggedInput); + } + } if (auto txService = context->getModel().getTransactionService()) { if (auto evse = txService->getEvse(connectorId)) { evse->setConnectorPluggedInput(pluggedInput); @@ -886,6 +907,15 @@ void setOccupiedInput(std::function occupied, unsigned int connectorId) MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } +#if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + if (auto availabilityService = context->getModel().getAvailabilityService()) { + if (auto evse = availabilityService->getEvse(connectorId)) { + evse->setOccupiedInput(occupied); + } + } + } +#endif auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); @@ -953,6 +983,19 @@ bool isOperative(unsigned int connectorId) { MO_DBG_WARN("OCPP uninitialized"); return true; //assume "true" as default state } +#if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + if (auto availabilityService = context->getModel().getAvailabilityService()) { + auto chargePoint = availabilityService->getEvse(OCPP_ID_OF_CP); + auto connector = availabilityService->getEvse(connectorId); + if (!chargePoint || !connector) { + MO_DBG_ERR("could not find connector"); + return true; //assume "true" as default state + } + return chargePoint->isOperative() && connector->isOperative(); + } + } +#endif auto& model = context->getModel(); auto chargePoint = model.getConnector(OCPP_ID_OF_CP); auto connector = model.getConnector(connectorId); diff --git a/src/MicroOcpp/Core/Memory.cpp b/src/MicroOcpp/Core/Memory.cpp index 26500b2b..f8ea7a2b 100644 --- a/src/MicroOcpp/Core/Memory.cpp +++ b/src/MicroOcpp/Core/Memory.cpp @@ -164,8 +164,7 @@ void mo_mem_deinit() { void mo_mem_reset() { MO_DBG_DEBUG("Reset all maximum values to current values"); - auto tagInfo = memTags.begin(); - if (tagInfo != memTags.end()) { + for (auto tagInfo = (memTags).begin(); tagInfo != memTags.end(); ++tagInfo) { tagInfo->second.reset(); } diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp new file mode 100644 index 00000000..294701b3 --- /dev/null +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -0,0 +1,178 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include +#include +#include +#include + +using namespace MicroOcpp; + +AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, unsigned int evseId) : MemoryManaged("v201.Availability.AvailabilityServiceEvse"), context(context), evseId(evseId) { + + snprintf(availabilityBoolKey, sizeof(availabilityBoolKey), MO_CONFIG_EXT_PREFIX "AVAIL_CONN_%d", evseId); + availabilityBool = declareConfiguration(availabilityBoolKey, true, MO_KEYVALUE_FN, false, false, false); +} + +void AvailabilityServiceEvse::loop() { + + if (evseId >= 1) { + auto status = getStatus(); + + if (status != reportedStatus && + context.getModel().getClock().now() >= MIN_TIME) { + + auto statusNotification = makeRequest(new Ocpp201::StatusNotification(evseId, status, context.getModel().getClock().now())); + statusNotification->setTimeout(0); + context.initiateRequest(std::move(statusNotification)); + reportedStatus = status; + return; + } + } +} + +void AvailabilityServiceEvse::setConnectorPluggedInput(std::function connectorPluggedInput) { + this->connectorPluggedInput = connectorPluggedInput; +} + +void AvailabilityServiceEvse::setOccupiedInput(std::function occupiedInput) { + this->occupiedInput = occupiedInput; +} + +ChargePointStatus AvailabilityServiceEvse::getStatus() { + ChargePointStatus res = ChargePointStatus_UNDEFINED; + + /* + * Handle special case: This is the Connector for the whole CP (i.e. evseId=0) --> only states Available, Unavailable, Faulted are possible + */ + if (evseId == 0) { + if (isFaulted()) { + res = ChargePointStatus_Faulted; + } else if (!isOperative()) { + res = ChargePointStatus_Unavailable; + } else { + res = ChargePointStatus_Available; + } + return res; + } + + if (isFaulted()) { + res = ChargePointStatus_Faulted; + } else if (!isOperative()) { + res = ChargePointStatus_Unavailable; + } + #if MO_ENABLE_RESERVATION + else if (context.getModel().getReservationService() && context.getModel().getReservationService()->getReservation(evseId)) { + res = ChargePointStatus_Reserved; + } + #endif + else if ((!connectorPluggedInput || !connectorPluggedInput()) && //no vehicle plugged + (!occupiedInput || !occupiedInput())) { //occupied override clear + res = ChargePointStatus_Available; + } else { + res = ChargePointStatus_Occupied; + } + + return res; +} + +void AvailabilityServiceEvse::setInoperative(void *requesterId) { + for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { + if (!inoperativeRequesters[i]) { + inoperativeRequesters[i] = requesterId; + return; + } + } + MO_DBG_ERR("exceeded max. inoperative requesters"); +} + +void AvailabilityServiceEvse::resetInoperative(void *requesterId) { + for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { + if (inoperativeRequesters[i] == requesterId) { + inoperativeRequesters[i] = nullptr; + return; + } + } + MO_DBG_ERR("could not find inoperative requester"); +} + +void AvailabilityServiceEvse::setFaulted(void *requesterId) { + for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) { + if (!faultedRequesters[i]) { + faultedRequesters[i] = requesterId; + return; + } + } + MO_DBG_ERR("exceeded max. faulted requesters"); +} + +void AvailabilityServiceEvse::resetFaulted(void *requesterId) { + for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) { + if (faultedRequesters[i] == requesterId) { + faultedRequesters[i] = nullptr; + return; + } + } + MO_DBG_ERR("could not find faulted requester"); +} + +bool AvailabilityServiceEvse::isOperative() { + if (availabilityBool && !availabilityBool->getBool()) { + return false; + } + + for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { + if (inoperativeRequesters[i]) { + return false; + } + } + return true; +} + +bool AvailabilityServiceEvse::isFaulted() { + for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) { + if (faultedRequesters[i]) { + return true; + } + } + return false; +} + +AvailabilityService::AvailabilityService(Context& context, size_t numEvses) : MemoryManaged("v201.Availability.AvailabilityService"), context(context) { + + for (size_t i = 0; i < numEvses && i < MO_NUM_EVSE; i++) { + evses[i] = new AvailabilityServiceEvse(context, (unsigned int)i); + } + + context.getOperationRegistry().registerOperation("StatusNotification", [&context] () { + return new Ocpp16::StatusNotification(-1, ChargePointStatus_UNDEFINED, Timestamp());}); +} + +AvailabilityService::~AvailabilityService() { + for (size_t i = 0; evses[i]; i++) { + delete evses[i]; + } +} + +void AvailabilityService::loop() { + for (size_t i = 0; evses[i]; i++) { + evses[i]->loop(); + } +} + +AvailabilityServiceEvse *AvailabilityService::getEvse(unsigned int evseId) { + if (evseId >= MO_NUM_EVSE) { + MO_DBG_ERR("invalid arg"); + return nullptr; + } + return evses[evseId]; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h new file mode 100644 index 00000000..5e836e87 --- /dev/null +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -0,0 +1,90 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs G01, G03, G04. + * + * G02 (Heartbeat) is implemented in the HeartbeatService + */ + +#ifndef MO_AVAILABILITYSERVICE_H +#define MO_AVAILABILITYSERVICE_H + +#include + +#if MO_ENABLE_V201 + +#include + +#include +#include +#include +#include + +#ifndef MO_INOPERATIVE_REQUESTERS_MAX +#define MO_INOPERATIVE_REQUESTERS_MAX 3 +#endif + +#ifndef MO_FAULTED_REQUESTERS_MAX +#define MO_FAULTED_REQUESTERS_MAX 3 +#endif + +namespace MicroOcpp { + +class Context; + +class AvailabilityServiceEvse : public MemoryManaged { +private: + Context& context; + const unsigned int evseId; + + std::function connectorPluggedInput; + std::function occupiedInput; //instead of Available, go into Occupied + + std::shared_ptr availabilityBool; + char availabilityBoolKey [sizeof(MO_CONFIG_EXT_PREFIX "AVAIL_CONN_xxxx") + 1]; + void *inoperativeRequesters [MO_INOPERATIVE_REQUESTERS_MAX] = {nullptr}; + void *faultedRequesters [MO_FAULTED_REQUESTERS_MAX] = {nullptr}; + + ChargePointStatus reportedStatus = ChargePointStatus_UNDEFINED; +public: + AvailabilityServiceEvse(Context& context, unsigned int evseId); + + void loop(); + + void setConnectorPluggedInput(std::function connectorPluggedInput); + void setOccupiedInput(std::function occupiedInput); + + ChargePointStatus getStatus(); + + void setInoperative(void *requesterId); + void resetInoperative(void *requesterId); + + void setFaulted(void *requesterId); + void resetFaulted(void *requesterId); + + bool isOperative(); + bool isFaulted(); +}; + +class AvailabilityService : public MemoryManaged { +private: + Context& context; + + AvailabilityServiceEvse* evses [MO_NUM_EVSE + 1] = {nullptr}; + +public: + AvailabilityService(Context& context, size_t numEvses); + ~AvailabilityService(); + + void loop(); + + AvailabilityServiceEvse *getEvse(unsigned int evseId); +}; + +} // namespace MicroOcpp + +#endif // MO_ENABLE_V201 + +#endif diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 571f19cb..b9a2b29e 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -77,6 +78,9 @@ void Model::loop() { resetService->loop(); #if MO_ENABLE_V201 + if (availabilityService) + availabilityService->loop(); + if (transactionService) transactionService->loop(); @@ -214,6 +218,15 @@ CertificateService *Model::getCertificateService() const { #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_V201 +void Model::setAvailabilityService(std::unique_ptr as) { + this->availabilityService = std::move(as); + capabilitiesUpdated = true; +} + +AvailabilityService *Model::getAvailabilityService() const { + return availabilityService.get(); +} + void Model::setVariableService(std::unique_ptr vs) { this->variableService = std::move(vs); capabilitiesUpdated = true; diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 67639ee1..5188c87e 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -37,6 +37,7 @@ class CertificateService; #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_V201 +class AvailabilityService; class VariableService; class TransactionService; @@ -71,6 +72,7 @@ class Model : public MemoryManaged { #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_V201 + std::unique_ptr availabilityService; std::unique_ptr variableService; std::unique_ptr transactionService; std::unique_ptr resetServiceV201; @@ -142,6 +144,9 @@ class Model : public MemoryManaged { #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_V201 + void setAvailabilityService(std::unique_ptr as); + AvailabilityService *getAvailabilityService() const; + void setVariableService(std::unique_ptr vs); VariableService *getVariableService() const; diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 0e7dfd90..d95c7fa0 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -255,11 +255,11 @@ class Transaction : public MemoryManaged { * Transaction substates. Notify server about any change when transaction is running */ //bool trackParkingBayOccupancy; // not supported - bool trackEvConnected; - bool trackAuthorized; - bool trackDataSigned; - bool trackPowerPathClosed; - bool trackEnergyTransfer; + bool trackEvConnected = false; + bool trackAuthorized = false; + bool trackDataSigned = false; + bool trackPowerPathClosed = false; + bool trackEnergyTransfer = false; /* * Transaction lifecycle diff --git a/src/MicroOcpp/Operations/CustomOperation.cpp b/src/MicroOcpp/Operations/CustomOperation.cpp index 78643d43..317d01ef 100644 --- a/src/MicroOcpp/Operations/CustomOperation.cpp +++ b/src/MicroOcpp/Operations/CustomOperation.cpp @@ -11,7 +11,7 @@ CustomOperation::CustomOperation(const char *operationType, std::function ()> fn_createReq, std::function fn_processConf, std::function fn_processErr) : - MemoryManaged("v16.Operation.", operationType), + MemoryManaged("Operation.Custom.", operationType), operationType{makeString(getMemoryTag(), operationType)}, fn_createReq{fn_createReq}, fn_processConf{fn_processConf}, @@ -25,7 +25,7 @@ CustomOperation::CustomOperation(const char *operationType, std::function fn_getErrorCode, std::function fn_getErrorDescription, std::function ()> fn_getErrorDetails) : - MemoryManaged("v16.Operation.", operationType), + MemoryManaged("Operation.Custom.", operationType), operationType{makeString(getMemoryTag(), operationType)}, fn_processReq{fn_processReq}, fn_createConf{fn_createConf}, diff --git a/src/MicroOcpp/Operations/GetVariables.cpp b/src/MicroOcpp/Operations/GetVariables.cpp index af75f644..30f8dc7f 100644 --- a/src/MicroOcpp/Operations/GetVariables.cpp +++ b/src/MicroOcpp/Operations/GetVariables.cpp @@ -20,7 +20,7 @@ GetVariableData::GetVariableData(const char *memory_tag) : componentName{makeStr } -GetVariables::GetVariables(VariableService& variableService) : MemoryManaged("v201.Operation.", "GetVariableData"), variableService(variableService), queries(makeVector(getMemoryTag())) { +GetVariables::GetVariables(VariableService& variableService) : MemoryManaged("v201.Operation.", "GetVariables"), variableService(variableService), queries(makeVector(getMemoryTag())) { } diff --git a/src/MicroOcpp/Operations/SecurityEventNotification.cpp b/src/MicroOcpp/Operations/SecurityEventNotification.cpp new file mode 100644 index 00000000..d4837315 --- /dev/null +++ b/src/MicroOcpp/Operations/SecurityEventNotification.cpp @@ -0,0 +1,54 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include + +using MicroOcpp::Ocpp201::SecurityEventNotification; +using MicroOcpp::JsonDoc; + +SecurityEventNotification::SecurityEventNotification(const char *type, const Timestamp& timestamp) : MemoryManaged("v201.Operation.", "SecurityEventNotification"), type(makeString(getMemoryTag(), type ? type : "")), timestamp(timestamp) { + +} + +const char* SecurityEventNotification::getOperationType(){ + return "SecurityEventNotification"; +} + +std::unique_ptr SecurityEventNotification::createReq() { + + auto doc = makeJsonDoc(getMemoryTag(), + JSON_OBJECT_SIZE(2) + + JSONDATE_LENGTH + 1); + + JsonObject payload = doc->to(); + + payload["type"] = type.c_str(); + + char timestampStr [JSONDATE_LENGTH + 1]; + timestamp.toJsonString(timestampStr, sizeof(timestampStr)); + payload["timestamp"] = timestampStr; + + return doc; +} + +void SecurityEventNotification::processConf(JsonObject) { + //empty payload +} + +void SecurityEventNotification::processReq(JsonObject payload) { + /** + * Ignore Contents of this Req-message, because this is for debug purposes only + */ +} + +std::unique_ptr SecurityEventNotification::createConf() { + return createEmptyDocument(); +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/SecurityEventNotification.h b/src/MicroOcpp/Operations/SecurityEventNotification.h new file mode 100644 index 00000000..5d618c00 --- /dev/null +++ b/src/MicroOcpp/Operations/SecurityEventNotification.h @@ -0,0 +1,48 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_SECURITYEVENTNOTIFICATION_H +#define MO_SECURITYEVENTNOTIFICATION_H + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +namespace MicroOcpp { + +namespace Ocpp201 { + +class SecurityEventNotification : public Operation, public MemoryManaged { +private: + String type; + Timestamp timestamp; + + const char *errorCode = nullptr; +public: + SecurityEventNotification(const char *type, const Timestamp& timestamp); + + const char* getOperationType() override; + + std::unique_ptr createReq() override; + + void processConf(JsonObject payload) override; + + const char *getErrorCode() override {return errorCode;} + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + +}; + +} //namespace Ocpp201 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V201 + +#endif diff --git a/tests/Boot.cpp b/tests/Boot.cpp index 4beadf31..c759b808 100644 --- a/tests/Boot.cpp +++ b/tests/Boot.cpp @@ -426,5 +426,51 @@ TEST_CASE( "Boot Behavior" ) { REQUIRE( !strcmp(declareConfiguration("neverDeclaredInsideMO", "newVal")->getString(), "newVal") ); //config has been removed } + SECTION("Boot with v201") { + + mocpp_deinitialize(); + + mocpp_initialize(loopback, ChargerCredentials::v201(CHARGEPOINTMODEL, CHARGEPOINTVENDOR), filesystem, false, ProtocolVersion(2,0,1)); + + bool checkProcessed = false; + + getOcppContext()->getOperationRegistry().registerOperation("BootNotification", + [&checkProcessed] () { + return new Ocpp16::CustomOperation("BootNotification", + [ &checkProcessed] (JsonObject payload) { + //process req + checkProcessed = true; + REQUIRE( !strcmp(payload["reason"] | "_Undefined", "PowerUp") ); + REQUIRE( !strcmp(payload["chargingStation"]["model"] | "_Undefined", CHARGEPOINTMODEL) ); + REQUIRE( !strcmp(payload["chargingStation"]["vendorName"] | "_Undefined", CHARGEPOINTVENDOR) ); + }, + [] () { + //create conf + auto conf = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(3)); + (*conf)["currentTime"] = BASE_TIME; + (*conf)["interval"] = 3600; + (*conf)["status"] = "Accepted"; + return conf; + }); + }); + + MO_MEM_RESET(); + + loop(); + + REQUIRE(checkProcessed); + REQUIRE(getOcppContext()->getModel().getClock().now() >= MIN_TIME); + + MO_MEM_PRINT_STATS(); + + MO_MEM_RESET(); + + mtime += 3600 * 1000; + loop(); + + MO_DBG_INFO("Memory requirements UC G02:"); + MO_MEM_PRINT_STATS(); + } + mocpp_deinitialize(); } diff --git a/tests/Reset.cpp b/tests/Reset.cpp index 8036393f..857104f8 100644 --- a/tests/Reset.cpp +++ b/tests/Reset.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,17 @@ TEST_CASE( "Reset" ) { mocpp_set_timer(custom_timer_cb); + getOcppContext()->getOperationRegistry().registerOperation("Authorize", [] () { + return new Ocpp16::CustomOperation("Authorize", + [] (JsonObject) {}, //ignore req + [] () { + //create conf + auto doc = makeJsonDoc("UnitTests", 2 * JSON_OBJECT_SIZE(1)); + auto payload = doc->to(); + payload["idTokenInfo"]["status"] = "Accepted"; + return doc; + });}); + // Register Reset handlers bool checkNotified [MO_NUM_EVSE] = {false}; bool checkExecuted [MO_NUM_EVSE] = {false}; @@ -68,6 +80,43 @@ TEST_CASE( "Reset" ) { loop(); + SECTION("B11 - Reset - Without ongoing transaction") { + + MO_MEM_RESET(); + + bool checkProcessed = false; + + auto resetRequest = makeRequest(new Ocpp16::CustomOperation( + "Reset", + [] () { + //create req + auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(1)); + auto payload = doc->to(); + payload["type"] = "OnIdle"; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE(!strcmp(payload["status"], "Accepted")); + } + )); + + context->initiateRequest(std::move(resetRequest)); + + loop(); + mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately + loop(); + + REQUIRE(checkProcessed); + + for (size_t i = 0; i < MO_NUM_EVSE; i++) { + REQUIRE( checkNotified[i] ); + } + + MO_MEM_PRINT_STATS(); + } + SECTION("Schedule full charger Reset") { REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr ); @@ -132,7 +181,7 @@ TEST_CASE( "Reset" ) { REQUIRE( !ocppPermitsCharge(1) ); REQUIRE( ocppPermitsCharge(2) ); - REQUIRE( context->getModel().getConnector(1)->getStatus() == ChargePointStatus_Unavailable ); + REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); context->getModel().getTransactionService()->getEvse(2)->endAuthorization("mIdToken"); setConnectorPluggedInput([] () {return false;}, 2); @@ -150,8 +199,8 @@ TEST_CASE( "Reset" ) { REQUIRE( checkExecuted[0] ); // Technically, Reset failed at this point, because the program is still running. Check if connectors are Available agin - REQUIRE( context->getModel().getConnector(1)->getStatus() == ChargePointStatus_Available ); - REQUIRE( context->getModel().getConnector(2)->getStatus() == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available ); } SECTION("Immediate full charger Reset") { @@ -167,6 +216,8 @@ TEST_CASE( "Reset" ) { loop(); + MO_MEM_RESET(); + REQUIRE( ocppPermitsCharge(1) ); REQUIRE( ocppPermitsCharge(2) ); @@ -224,11 +275,13 @@ TEST_CASE( "Reset" ) { REQUIRE( checkExecuted[0] ); + MO_MEM_PRINT_STATS(); + loop(); // Technically, Reset failed at this point, because the program is still running. Check if connectors are Available agin - REQUIRE( context->getModel().getConnector(1)->getStatus() == ChargePointStatus_Available ); - REQUIRE( context->getModel().getConnector(2)->getStatus() == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available ); } SECTION("Reject Reset") { @@ -264,9 +317,9 @@ TEST_CASE( "Reset" ) { REQUIRE(checkProcessed); REQUIRE(checkNotified[2]); - REQUIRE( context->getModel().getConnector(0)->getStatus() == ChargePointStatus_Available ); - REQUIRE( context->getModel().getConnector(1)->getStatus() == ChargePointStatus_Available ); - REQUIRE( context->getModel().getConnector(2)->getStatus() == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(0) == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available ); } SECTION("Reset single EVSE") { @@ -297,14 +350,14 @@ TEST_CASE( "Reset" ) { REQUIRE(checkProcessed); REQUIRE(checkNotified[1]); - REQUIRE( context->getModel().getConnector(1)->getStatus() == ChargePointStatus_Unavailable ); - REQUIRE( context->getModel().getConnector(2)->getStatus() == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); + REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available ); mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately loop(); REQUIRE(checkExecuted[1]); - REQUIRE( context->getModel().getConnector(1)->getStatus() == ChargePointStatus_Available ); + REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available ); } mocpp_deinitialize(); diff --git a/tests/Security.cpp b/tests/Security.cpp new file mode 100644 index 00000000..43bf2d7a --- /dev/null +++ b/tests/Security.cpp @@ -0,0 +1,58 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "./helpers/testHelper.h" + +#define BASE_TIME "2023-01-01T00:00:00.000Z" + +using namespace MicroOcpp; + + +TEST_CASE( "Security" ) { + printf("\nRun %s\n", "Security"); + + mocpp_set_timer(custom_timer_cb); + + //initialize Context with dummy socket + LoopbackConnection loopback; + auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail); + mocpp_initialize(loopback, + ChargerCredentials(), + filesystem, + false, + ProtocolVersion(2,0,1)); + + SECTION("Manual SecurityEventNotification") { + + loop(); + + MO_MEM_RESET(); + + getOcppContext()->initiateRequest(makeRequest(new Ocpp201::SecurityEventNotification( + "ReconfigurationOfSecurityParameters", + getOcppContext()->getModel().getClock().now()))); + + loop(); + + MO_MEM_PRINT_STATS(); + } + + mocpp_deinitialize(); +} + +#endif // MO_ENABLE_V201 diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp index aa4a3be9..7fedfdc4 100644 --- a/tests/Transactions.cpp +++ b/tests/Transactions.cpp @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include #include #include "./helpers/testHelper.h" @@ -33,7 +35,6 @@ TEST_CASE( "Transactions" ) { ProtocolVersion(2,0,1)); auto context = getOcppContext(); - auto& checkMsg = context->getOperationRegistry(); mocpp_set_timer(custom_timer_cb); @@ -101,6 +102,100 @@ TEST_CASE( "Transactions" ) { context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped)); } + SECTION("UC C01-04") { + + //scenario preparation + + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Available ); + + getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStartPoint", "")->setString("PowerPathClosed"); + getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStopPoint", "")->setString("PowerPathClosed"); + + setConnectorPluggedInput([] () {return false;}); + + loop(); + + MO_MEM_RESET(); + + context->getModel().getTransactionService()->getEvse(1)->beginAuthorization("mIdToken"); + loop(); + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() != nullptr ); + REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started ); + REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Available ); + + MO_DBG_INFO("Memory requirements UC C01-04:"); + + MO_MEM_PRINT_STATS(); + } + + SECTION("UC E01 - S5 / E06") { + + //scenario preparation + + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Available ); + + getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStartPoint", "")->setString("PowerPathClosed"); + getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStopPoint", "")->setString("PowerPathClosed"); + + setConnectorPluggedInput([] () {return false;}); + + loop(); + + MO_MEM_RESET(); + + //run scenario + + setConnectorPluggedInput([] () {return true;}); + loop(); + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied ); + + context->getModel().getTransactionService()->getEvse(1)->beginAuthorization("mIdToken"); + loop(); + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() != nullptr ); + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started ); + REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied ); + + MO_DBG_INFO("Memory requirements UC E01 - S5:"); + + MO_MEM_PRINT_STATS(); + + auto trackTx = context->getModel().getTransactionService()->getEvse(1)->getTransaction(); + + MO_MEM_RESET(); + + setConnectorPluggedInput([] () {return false;}); + loop(); + + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr ); + REQUIRE( trackTx->stopped ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Available ); + trackTx.reset(); + + MO_DBG_INFO("Memory requirements UC E06:"); + MO_MEM_PRINT_STATS(); + + } + + SECTION("UC G01") { + + setConnectorPluggedInput([] () {return false;}); + loop(); + REQUIRE( getChargePointStatus() == ChargePointStatus_Available ); + MO_MEM_RESET(); + + setConnectorPluggedInput([] () {return true;}); + loop(); + REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied ); + + MO_DBG_INFO("Memory requirements UC G01:"); + MO_MEM_PRINT_STATS(); + } + mocpp_deinitialize(); } diff --git a/tests/Variables.cpp b/tests/Variables.cpp index 6813dd11..1d6399c4 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -16,6 +16,7 @@ #include #include +#include #include using namespace MicroOcpp; @@ -116,7 +117,7 @@ TEST_CASE( "Variable" ) { SECTION("Variable API") { //declare configs - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); auto vs = getOcppContext()->getModel().getVariableService(); auto cInt = vs->declareVariable("mComponent", "cInt", 42); REQUIRE( cInt != nullptr ); @@ -188,7 +189,7 @@ TEST_CASE( "Variable" ) { #else mocpp_deinitialize(); - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); #endif //config accessibility / permissions @@ -240,7 +241,7 @@ TEST_CASE( "Variable" ) { SECTION("Main lib integration") { //basic lifecycle - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); REQUIRE( getVariablePublic("ConnectionTimeOut") ); REQUIRE( !getVariableContainersPublic().empty() ); mocpp_deinitialize(); @@ -248,7 +249,7 @@ TEST_CASE( "Variable" ) { REQUIRE( getVariableContainersPublic().empty() ); //modify standard config ConnectionTimeOut. This config is not modified by the main lib during normal initialization / deinitialization - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); auto config = getVariablePublic("ConnectionTimeOut"); config->setInt(1234); //update @@ -256,7 +257,7 @@ TEST_CASE( "Variable" ) { mocpp_deinitialize(); - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); REQUIRE( getVariablePublic("ConnectionTimeOut")->getInt() == 1234 ); mocpp_deinitialize(); @@ -266,7 +267,7 @@ TEST_CASE( "Variable" ) { #if 0 SECTION("GetVariables") { - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); loop(); vs->declareVariable(KNOWN_KEY, 1234, MO_FILENAME_PREFIX "persistent1.jsn", false); @@ -357,7 +358,7 @@ TEST_CASE( "Variable" ) { SECTION("ChangeVariable") { - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); loop(); vs->declareVariable(KNOWN_KEY, 0, MO_FILENAME_PREFIX "persistent1.jsn", false); @@ -485,7 +486,7 @@ TEST_CASE( "Variable" ) { configuration_init(filesystem); auto factoryConnectionTimeOut = vs->declareVariable("ConnectionTimeOut", 1234, MO_FILENAME_PREFIX "factory.jsn"); - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); auto connectionTimeout2 = vs->declareVariable("ConnectionTimeOut", 4321); REQUIRE( connectionTimeout2->getInt() == 1234 ); @@ -495,20 +496,187 @@ TEST_CASE( "Variable" ) { mocpp_deinitialize(); //this time, factory default is not given (will lead to duplicates, should be considered in sanitization) - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); REQUIRE( getVariablePublic("ConnectionTimeOut")->getInt() != 1234 ); mocpp_deinitialize(); //provide factory default again configuration_init(filesystem); vs->declareVariable("ConnectionTimeOut", 4321, MO_FILENAME_PREFIX "factory.jsn"); - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); REQUIRE( getVariablePublic("ConnectionTimeOut")->getInt() == 1234 ); mocpp_deinitialize(); } #endif + SECTION("GetVariables request") { + + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); + + auto vs = getOcppContext()->getModel().getVariableService(); + auto varString = vs->declareVariable("mComponent", "mString", "mValue", MO_VARIABLE_VOLATILE); + REQUIRE( varString != nullptr ); + REQUIRE( !strcmp(varString->getString(), "mValue") ); + + loop(); + + MO_MEM_RESET(); + + bool checkProcessed = false; + + getOcppContext()->initiateRequest(makeRequest( + new Ocpp16::CustomOperation("GetVariables", + [] () { + //create req + auto doc = makeJsonDoc("UnitTests", + JSON_OBJECT_SIZE(1) + + JSON_ARRAY_SIZE(1) + + JSON_OBJECT_SIZE(2) + + JSON_OBJECT_SIZE(1) + + JSON_OBJECT_SIZE(1)); + auto payload = doc->to(); + auto getVariableData = payload.createNestedArray("getVariableData"); + getVariableData[0]["component"]["name"] = "mComponent"; + getVariableData[0]["variable"]["name"] = "mString"; + return doc; + }, + [&checkProcessed] (JsonObject payload) { + //process conf + JsonArray getVariableResult = payload["getVariableResult"]; + REQUIRE( !strcmp(getVariableResult[0]["attributeStatus"] | "_Undefined", "Accepted") ); + REQUIRE( !strcmp(getVariableResult[0]["component"]["name"] | "_Undefined", "mComponent") ); + REQUIRE( !strcmp(getVariableResult[0]["variable"]["name"] | "_Undefined", "mString") ); + REQUIRE( !strcmp(getVariableResult[0]["attributeValue"] | "_Undefined", "mValue") ); + checkProcessed = true; + }))); + + loop(); + + REQUIRE( checkProcessed ); + + MO_MEM_PRINT_STATS(); + + } + + SECTION("SetVariables request") { + + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); + + auto vs = getOcppContext()->getModel().getVariableService(); + auto varString = vs->declareVariable("mComponent", "mString", "", MO_VARIABLE_VOLATILE); + REQUIRE( varString != nullptr ); + REQUIRE( !strcmp(varString->getString(), "") ); + + loop(); + + MO_MEM_RESET(); + + bool checkProcessed = false; + + getOcppContext()->initiateRequest(makeRequest( + new Ocpp16::CustomOperation("SetVariables", + [] () { + //create req + auto doc = makeJsonDoc("UnitTests", + JSON_OBJECT_SIZE(1) + + JSON_ARRAY_SIZE(1) + + JSON_OBJECT_SIZE(3) + + JSON_OBJECT_SIZE(1) + + JSON_OBJECT_SIZE(1)); + auto payload = doc->to(); + auto setVariableData = payload.createNestedArray("setVariableData"); + setVariableData[0]["component"]["name"] = "mComponent"; + setVariableData[0]["variable"]["name"] = "mString"; + setVariableData[0]["attributeValue"] = "mValue"; + return doc; + }, + [&checkProcessed] (JsonObject payload) { + //process conf + JsonArray setVariableResult = payload["setVariableResult"]; + REQUIRE( !strcmp(setVariableResult[0]["attributeStatus"] | "_Undefined", "Accepted") ); + REQUIRE( !strcmp(setVariableResult[0]["component"]["name"] | "_Undefined", "mComponent") ); + REQUIRE( !strcmp(setVariableResult[0]["variable"]["name"] | "_Undefined", "mString") ); + checkProcessed = true; + }))); + + loop(); + + REQUIRE( checkProcessed ); + + MO_MEM_PRINT_STATS(); + } + + SECTION("GetBaseReport request") { + + mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1)); + + auto vs = getOcppContext()->getModel().getVariableService(); + auto varString = vs->declareVariable("mComponent", "mString", "", MO_VARIABLE_VOLATILE); + REQUIRE( varString != nullptr ); + REQUIRE( !strcmp(varString->getString(), "") ); + + loop(); + + MO_MEM_RESET(); + + bool checkProcessedNotification = false; + Timestamp checkTimestamp; + + getOcppContext()->getOperationRegistry().registerOperation("NotifyReport", + [&checkProcessedNotification, &checkTimestamp] () { + return new Ocpp16::CustomOperation("NotifyReport", + [ &checkProcessedNotification, &checkTimestamp] (JsonObject payload) { + //process req + checkProcessedNotification = true; + REQUIRE( (payload["requestId"] | -1) == 1); + checkTimestamp.setTime(payload["generatedAt"] | "_Undefined"); + REQUIRE( (payload["seqNo"] | -1) == 0); + + bool foundVar = false; + for (auto reportData : payload["reportData"].as()) { + if (!strcmp(reportData["component"]["name"] | "_Undefined", "mComponent") && + !strcmp(reportData["variable"]["name"] | "_Undefined", "mString")) { + foundVar = true; + } + } + REQUIRE( foundVar ); + }, + [] () { + //create conf + return createEmptyDocument(); + }); + }); + + bool checkProcessed = false; + + getOcppContext()->initiateRequest(makeRequest( + new Ocpp16::CustomOperation("GetBaseReport", + [] () { + //create req + auto doc = makeJsonDoc("UnitTests", + JSON_OBJECT_SIZE(2)); + auto payload = doc->to(); + payload["requestId"] = 1; + payload["reportBase"] = "FullInventory"; + return doc; + }, + [&checkProcessed] (JsonObject payload) { + //process conf + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Accepted") ); + checkProcessed = true; + }))); + + loop(); + + REQUIRE( checkProcessed ); + REQUIRE( checkProcessedNotification ); + REQUIRE( std::abs(getOcppContext()->getModel().getClock().now() - checkTimestamp) <= 10 ); + + MO_MEM_PRINT_STATS(); + + } + mocpp_deinitialize(); } From 6ec363d8ba7871c8f52bfc8a33e4d56827c99732 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 15 Sep 2024 13:58:35 +0200 Subject: [PATCH 02/18] add tx meterData support --- CMakeLists.txt | 2 + src/MicroOcpp.cpp | 66 +++- src/MicroOcpp/Core/Configuration_c.cpp | 4 +- .../Model/ConnectorBase/Connector.cpp | 4 +- src/MicroOcpp/Model/Metering/MeterStore.cpp | 2 +- src/MicroOcpp/Model/Metering/MeterValue.cpp | 4 +- .../Model/Metering/MeterValuesV201.cpp | 351 ++++++++++++++++++ .../Model/Metering/MeterValuesV201.h | 152 ++++++++ .../Model/Metering/MeteringConnector.cpp | 14 +- .../Model/Metering/ReadingContext.cpp | 65 ++++ src/MicroOcpp/Model/Metering/ReadingContext.h | 28 ++ src/MicroOcpp/Model/Metering/SampledValue.cpp | 62 +--- src/MicroOcpp/Model/Metering/SampledValue.h | 20 +- src/MicroOcpp/Model/Model.cpp | 10 + src/MicroOcpp/Model/Model.h | 5 + .../Model/Transactions/Transaction.h | 50 ++- .../Model/Transactions/TransactionService.cpp | 106 +++++- .../Model/Transactions/TransactionService.h | 5 + .../Model/Transactions/TransactionStore.cpp | 4 +- src/MicroOcpp/Operations/StopTransaction.cpp | 4 +- src/MicroOcpp/Operations/TransactionEvent.cpp | 46 ++- tests/Transactions.cpp | 70 ++++ 22 files changed, 936 insertions(+), 138 deletions(-) create mode 100644 src/MicroOcpp/Model/Metering/MeterValuesV201.cpp create mode 100644 src/MicroOcpp/Model/Metering/MeterValuesV201.h create mode 100644 src/MicroOcpp/Model/Metering/ReadingContext.cpp create mode 100644 src/MicroOcpp/Model/Metering/ReadingContext.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a4d838d..ff836b7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,8 @@ set(MO_SRC src/MicroOcpp/Model/Metering/MeteringService.cpp src/MicroOcpp/Model/Metering/MeterStore.cpp src/MicroOcpp/Model/Metering/MeterValue.cpp + src/MicroOcpp/Model/Metering/MeterValuesV201.cpp + src/MicroOcpp/Model/Metering/ReadingContext.cpp src/MicroOcpp/Model/Metering/SampledValue.cpp src/MicroOcpp/Model/Reservation/Reservation.cpp src/MicroOcpp/Model/Reservation/ReservationService.cpp diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 671e335a..0d26dfe2 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -653,6 +653,7 @@ void setConnectorPluggedInput(std::function pluggedInput, unsigned int c evse->setConnectorPluggedInput(pluggedInput); } } + return; } #endif auto connector = context->getModel().getConnector(connectorId); @@ -668,11 +669,14 @@ void setEnergyMeterInput(std::function energyInput, unsigned int connecto MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - auto& model = context->getModel(); - if (!model.getMeteringService()) { - model.setMeteringSerivce(std::unique_ptr( - new MeteringService(*context, MO_NUMCONNECTORS, filesystem))); + + #if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + addMeterValueInput([energyInput] () {return static_cast(energyInput());}, "Energy.Active.Import.Register", "Wh", nullptr, nullptr, connectorId); + return; } + #endif + SampledValueProperties meterProperties; meterProperties.setMeasurand("Energy.Active.Import.Register"); meterProperties.setUnit("Wh"); @@ -681,7 +685,7 @@ void setEnergyMeterInput(std::function energyInput, unsigned int connecto meterProperties, [energyInput] (ReadingContext) {return energyInput();} )); - model.getMeteringService()->addMeterValueSampler(connectorId, std::move(mvs)); + addMeterValueInput(std::move(mvs), connectorId); } void setPowerMeterInput(std::function powerInput, unsigned int connectorId) { @@ -690,11 +694,13 @@ void setPowerMeterInput(std::function powerInput, unsigned int connecto return; } - auto& model = context->getModel(); - if (!model.getMeteringService()) { - model.setMeteringSerivce(std::unique_ptr( - new MeteringService(*context, MO_NUMCONNECTORS, filesystem))); + #if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + addMeterValueInput([powerInput] () {return static_cast(powerInput());}, "Power.Active.Import", "W", nullptr, nullptr, connectorId); + return; } + #endif + SampledValueProperties meterProperties; meterProperties.setMeasurand("Power.Active.Import"); meterProperties.setUnit("W"); @@ -703,7 +709,7 @@ void setPowerMeterInput(std::function powerInput, unsigned int connecto meterProperties, [powerInput] (ReadingContext) {return powerInput();} )); - model.getMeteringService()->addMeterValueSampler(connectorId, std::move(mvs)); + addMeterValueInput(std::move(mvs), connectorId); } void setSmartChargingPowerOutput(std::function chargingLimitOutput, unsigned int connectorId) { @@ -820,6 +826,7 @@ void setEvseReadyInput(std::function evseReadyInput, unsigned int connec evse->setEvseReadyInput(evseReadyInput); } } + return; } #endif auto connector = context->getModel().getConnector(connectorId); @@ -872,6 +879,33 @@ void addMeterValueInput(std::function valueInput, const char *measuran MO_DBG_WARN("measurand unspecified; assume %s", measurand); } + #if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + auto& model = context->getModel(); + if (!model.getMeteringServiceV201()) { + model.setMeteringServiceV201(std::unique_ptr( + new Ocpp201::MeteringService(context->getModel(), MO_NUM_EVSE))); + } + if (auto mEvse = model.getMeteringServiceV201()->getEvse(connectorId)) { + + Ocpp201::SampledValueProperties properties; + properties.setMeasurand(measurand); //mandatory for MO + + if (unit) + properties.setUnitOfMeasureUnit(unit); + if (location) + properties.setLocation(location); + if (phase) + properties.setPhase(phase); + + mEvse->addMeterValueInput([valueInput] (ReadingContext) {return static_cast(valueInput());}, properties); + } else { + MO_DBG_ERR("inalid arg"); + } + return; + } + #endif + SampledValueProperties properties; properties.setMeasurand(measurand); //mandatory for MO @@ -885,7 +919,7 @@ void addMeterValueInput(std::function valueInput, const char *measuran auto valueSampler = std::unique_ptr>>( new MicroOcpp::SampledValueSamplerConcrete>( properties, - [valueInput] (MicroOcpp::ReadingContext) {return valueInput();})); + [valueInput] (ReadingContext) {return valueInput();})); addMeterValueInput(std::move(valueSampler), connectorId); } @@ -894,6 +928,12 @@ void addMeterValueInput(std::unique_ptr valueInput, unsigne MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } + #if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + MO_DBG_ERR("addMeterValueInput(std::unique_ptr...) not compatible with v201. Use addMeterValueInput(std::function...) instead"); + return; + } + #endif auto& model = context->getModel(); if (!model.getMeteringService()) { model.setMeteringSerivce(std::unique_ptr( @@ -1219,7 +1259,7 @@ bool startTransaction(const char *idTag, OnReceiveConfListener onConf, OnAbortLi } if (auto mService = context->getModel().getMeteringService()) { - auto meterStart = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext::TransactionBegin); + auto meterStart = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionBegin); if (meterStart && *meterStart) { transaction->setMeterStart(meterStart->toInteger()); } else { @@ -1275,7 +1315,7 @@ bool stopTransaction(OnReceiveConfListener onConf, OnAbortListener onAbort, OnTi } if (auto mService = context->getModel().getMeteringService()) { - auto meterStop = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext::TransactionEnd); + auto meterStop = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionEnd); if (meterStop && *meterStop) { transaction->setMeterStop(meterStop->toInteger()); } else { diff --git a/src/MicroOcpp/Core/Configuration_c.cpp b/src/MicroOcpp/Core/Configuration_c.cpp index f04101f2..a85a1edd 100644 --- a/src/MicroOcpp/Core/Configuration_c.cpp +++ b/src/MicroOcpp/Core/Configuration_c.cpp @@ -179,7 +179,7 @@ class ConfigurationContainerC : public ConfigurationContainer, public MemoryMana std::shared_ptr getConfiguration(const char *key) override { ocpp_configuration *config = container->get_configuration_by_key(container->user_data, key); if (config) { - return std::make_shared(config); + return std::allocate_shared(makeAllocator(getMemoryTag()), config); } else { return nullptr; } @@ -193,5 +193,5 @@ class ConfigurationContainerC : public ConfigurationContainer, public MemoryMana }; void ocpp_configuration_container_add(ocpp_configuration_container *container, const char *container_path, bool accessible) { - addConfigurationContainer(std::make_shared(container, container_path, accessible)); + addConfigurationContainer(std::allocate_shared(makeAllocator("v16.Configuration.ContainerC.", container_path), container, container_path, accessible)); } diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp index c0911858..244185bd 100644 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp +++ b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp @@ -342,7 +342,7 @@ void Connector::loop() { auto meteringService = model.getMeteringService(); if (transaction->getMeterStart() < 0 && meteringService) { - auto meterStart = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext::TransactionBegin); + auto meterStart = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionBegin); if (meterStart && *meterStart) { transaction->setMeterStart(meterStart->toInteger()); } else { @@ -386,7 +386,7 @@ void Connector::loop() { auto meteringService = model.getMeteringService(); if (transaction->getMeterStop() < 0 && meteringService) { - auto meterStop = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext::TransactionEnd); + auto meterStop = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionEnd); if (meterStop && *meterStop) { transaction->setMeterStop(meterStop->toInteger()); } else { diff --git a/src/MicroOcpp/Model/Metering/MeterStore.cpp b/src/MicroOcpp/Model/Metering/MeterStore.cpp index 68e84376..e4bc9e85 100644 --- a/src/MicroOcpp/Model/Metering/MeterStore.cpp +++ b/src/MicroOcpp/Model/Metering/MeterStore.cpp @@ -187,7 +187,7 @@ std::shared_ptr MeterStore::getTxMeterData(MeterValueBuild //create new object and cache weak pointer - auto tx = std::make_shared(connectorId, txNr, filesystem); + auto tx = std::allocate_shared(makeAllocator(getMemoryTag()), connectorId, txNr, filesystem); if (filesystem) { char fn [MO_MAX_PATH_SIZE] = {'\0'}; diff --git a/src/MicroOcpp/Model/Metering/MeterValue.cpp b/src/MicroOcpp/Model/Metering/MeterValue.cpp index dd52741d..740905da 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValue.cpp @@ -62,11 +62,11 @@ void MeterValue::setTimestamp(Timestamp timestamp) { ReadingContext MeterValue::getReadingContext() { //all sampledValues have the same ReadingContext. Just get the first result for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) { - if ((*sample)->getReadingContext() != ReadingContext::NOT_SET) { + if ((*sample)->getReadingContext() != ReadingContext_UNDEFINED) { return (*sample)->getReadingContext(); } } - return ReadingContext::NOT_SET; + return ReadingContext_UNDEFINED; } void MeterValue::setTxNr(unsigned int txNr) { diff --git a/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp b/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp new file mode 100644 index 00000000..560b12ac --- /dev/null +++ b/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp @@ -0,0 +1,351 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include +#include +#include + +//helper function +namespace MicroOcpp { + +bool csvContains(const char *csv, const char *elem) { + + if (!csv || !elem) { + return false; + } + + size_t elemLen = strlen(elem); + + size_t sl = 0, sr = 0; + while (csv[sr]) { + while (csv[sr]) { + if (csv[sr] == ',') { + break; + } + sr++; + } + //csv[sr] is either ',' or '\0' + + if (sr - sl == elemLen && !strncmp(csv + sl, elem, sr - sl)) { + return true; + } + + if (csv[sr]) { + sr++; + } + sl = sr; + } + return false; +} + +} //namespace MicroOcpp + +using namespace MicroOcpp::Ocpp201; + +SampledValueProperties::SampledValueProperties() : MemoryManaged("v201.MeterValues.SampledValueProperties") { } +SampledValueProperties::SampledValueProperties(const SampledValueProperties& other) : + MemoryManaged(other.getMemoryTag()), + format(other.format), + measurand(other.measurand), + phase(other.phase), + location(other.location), + unitOfMeasureUnit(other.unitOfMeasureUnit), + unitOfMeasureMultiplier(other.unitOfMeasureMultiplier) { + +} + +void SampledValueProperties::setFormat(const char *format) {this->format = format;} +const char *SampledValueProperties::getFormat() const {return format;} +void SampledValueProperties::setMeasurand(const char *measurand) {this->measurand = measurand;} +const char *SampledValueProperties::getMeasurand() const {return measurand;} +void SampledValueProperties::setPhase(const char *phase) {this->phase = phase;} +const char *SampledValueProperties::getPhase() const {return phase;} +void SampledValueProperties::setLocation(const char *location) {this->location = location;} +const char *SampledValueProperties::getLocation() const {return location;} +void SampledValueProperties::setUnitOfMeasureUnit(const char *unitOfMeasureUnit) {this->unitOfMeasureUnit = unitOfMeasureUnit;} +const char *SampledValueProperties::getUnitOfMeasureUnit() const {return unitOfMeasureUnit;} +void SampledValueProperties::setUnitOfMeasureMultiplier(int unitOfMeasureMultiplier) {this->unitOfMeasureMultiplier = unitOfMeasureMultiplier;} +int SampledValueProperties::getUnitOfMeasureMultiplier() const {return unitOfMeasureMultiplier;} + +SampledValue::SampledValue(double value, ReadingContext readingContext, SampledValueProperties& properties) + : MemoryManaged("v201.MeterValues.SampledValue"), value(value), readingContext(readingContext), properties(properties) { + +} + +bool SampledValue::toJson(JsonDoc& out) { + + size_t unitOfMeasureElements = + (properties.getUnitOfMeasureUnit() ? 1 : 0) + + (properties.getUnitOfMeasureMultiplier() ? 1 : 0); + + out = initJsonDoc(getMemoryTag(), + JSON_OBJECT_SIZE( + 1 + //value + (readingContext != ReadingContext_SamplePeriodic ? 1 : 0) + + (properties.getMeasurand() ? 1 : 0) + + (properties.getPhase() ? 1 : 0) + + (properties.getLocation() ? 1 : 0) + + (unitOfMeasureElements ? 1 : 0) + ) + + (unitOfMeasureElements ? JSON_OBJECT_SIZE(unitOfMeasureElements) : 0) + ); + + out["value"] = value; + if (readingContext != ReadingContext_SamplePeriodic) + out["context"] = serializeReadingContext(readingContext); + if (properties.getMeasurand()) + out["measurand"] = properties.getMeasurand(); + if (properties.getPhase()) + out["phase"] = properties.getPhase(); + if (properties.getLocation()) + out["location"] = properties.getLocation(); + if (properties.getUnitOfMeasureUnit()) + out["unitOfMeasure"]["unit"] = properties.getUnitOfMeasureUnit(); + if (properties.getUnitOfMeasureMultiplier()) + out["unitOfMeasure"]["multiplier"] = properties.getUnitOfMeasureMultiplier(); + + return true; +} + +SampledValueInput::SampledValueInput(std::function valueInput, const SampledValueProperties& properties) + : MemoryManaged("v201.MeterValues.SampledValueInput"), valueInput(valueInput), properties(properties) { + +} + +SampledValue *SampledValueInput::takeSampledValue(ReadingContext readingContext) { + return new SampledValue(valueInput(readingContext), readingContext, properties); +} + +const SampledValueProperties& SampledValueInput::getProperties() { + return properties; +} + +uint8_t& SampledValueInput::getMeasurandTypeFlags() { + return measurandTypeFlags; +} + +MeterValue::MeterValue(const Timestamp& timestamp, SampledValue **sampledValue, size_t sampledValueSize) : + MemoryManaged("v201.MeterValues.MeterValue"), timestamp(timestamp), sampledValue(sampledValue), sampledValueSize(sampledValueSize) { + +} + +MeterValue::~MeterValue() { + for (size_t i = 0; i < sampledValueSize; i++) { + delete sampledValue[i]; + } + MO_FREE(sampledValue); +} + +bool MeterValue::toJson(JsonDoc& out) { + + size_t capacity = 0; + + for (size_t i = 0; i < sampledValueSize; i++) { + //just measure, discard sampledValueJson afterwards + JsonDoc sampledValueJson = initJsonDoc(getMemoryTag()); + sampledValue[i]->toJson(sampledValueJson); + capacity += sampledValueJson.capacity(); + } + + capacity += JSON_OBJECT_SIZE(2) + + JSONDATE_LENGTH + 1 + + JSON_ARRAY_SIZE(sampledValueSize); + + + out = initJsonDoc("v201.MeterValues.MeterValue", capacity); + + char timestampStr [JSONDATE_LENGTH + 1]; + timestamp.toJsonString(timestampStr, sizeof(timestampStr)); + + out["timestamp"] = timestampStr; + JsonArray sampledValueArray = out.createNestedArray("sampledValue"); + + for (size_t i = 0; i < sampledValueSize; i++) { + JsonDoc sampledValueJson = initJsonDoc(getMemoryTag()); + sampledValue[i]->toJson(sampledValueJson); + sampledValueArray.add(sampledValueJson); + } + + return true; +} + +const MicroOcpp::Timestamp& MeterValue::getTimestamp() { + return timestamp; +} + +MeteringServiceEvse::MeteringServiceEvse(Model& model, unsigned int evseId) + : MemoryManaged("v201.MeterValues.MeteringServiceEvse"), model(model), evseId(evseId), sampledValueInputs(makeVector(getMemoryTag())) { + + auto varService = model.getVariableService(); + + sampledDataTxStartedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxStartedMeasurands", ""); + sampledDataTxUpdatedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxUpdatedMeasurands", ""); + sampledDataTxEndedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", ""); + alignedDataMeasurands = varService->declareVariable("AlignedDataCtrlr", "AlignedDataMeasurands", ""); +} + +void MeteringServiceEvse::addMeterValueInput(std::function valueInput, const SampledValueProperties& properties) { + sampledValueInputs.emplace_back(valueInput, properties); +} + +std::unique_ptr MeteringServiceEvse::takeMeterValue(Variable *measurands, uint16_t& trackMeasurandsWriteCount, size_t& trackInputsSize, uint8_t measurandsMask, ReadingContext readingContext) { + + if (measurands->getWriteCount() != trackMeasurandsWriteCount || + sampledValueInputs.size() != trackInputsSize) { + MO_DBG_DEBUG("Updating observed samplers due to config change or samplers added"); + for (size_t i = 0; i < sampledValueInputs.size(); i++) { + if (csvContains(measurands->getString(), sampledValueInputs[i].getProperties().getMeasurand())) { + sampledValueInputs[i].getMeasurandTypeFlags() |= measurandsMask; + } else { + sampledValueInputs[i].getMeasurandTypeFlags() &= ~measurandsMask; + } + } + + trackMeasurandsWriteCount = measurands->getWriteCount(); + trackInputsSize = sampledValueInputs.size(); + } + + size_t samplesSize = 0; + + for (size_t i = 0; i < sampledValueInputs.size(); i++) { + if (sampledValueInputs[i].getMeasurandTypeFlags() & measurandsMask) { + samplesSize++; + } + } + + if (samplesSize == 0) { + return nullptr; + } + + SampledValue **sampledValue = static_cast(MO_MALLOC(getMemoryTag(), samplesSize * sizeof(SampledValue*))); + if (!sampledValue) { + MO_DBG_ERR("OOM"); + return nullptr; + } + size_t samplesWritten = 0; + + bool memoryErr = false; + + for (size_t i = 0; i < sampledValueInputs.size(); i++) { + if (sampledValueInputs[i].getMeasurandTypeFlags() & measurandsMask) { + auto sample = sampledValueInputs[i].takeSampledValue(readingContext); + if (!sample) { + MO_DBG_ERR("OOM"); + memoryErr = true; + break; + } + sampledValue[samplesWritten++] = sample; + } + } + + std::unique_ptr meterValue = std::unique_ptr(new MeterValue(model.getClock().now(), sampledValue, samplesWritten)); + if (!meterValue) { + MO_DBG_ERR("OOM"); + memoryErr = true; + } + + if (memoryErr) { + if (!meterValue) { + //meterValue did not take ownership, so clean resources manually + for (size_t i = 0; i < samplesWritten; i++) { + delete sampledValue[i]; + } + delete sampledValue; + } + return nullptr; + } + + return meterValue; +} + +std::unique_ptr MeteringServiceEvse::takeTxStartedMeterValue(ReadingContext readingContext) { + return takeMeterValue(sampledDataTxStartedMeasurands, trackSampledDataTxStartedMeasurandsWriteCount, trackSampledValueInputsSizeTxStarted, MO_MEASURAND_TYPE_TXSTARTED, readingContext); +} +std::unique_ptr MeteringServiceEvse::takeTxUpdatedMeterValue(ReadingContext readingContext) { + return takeMeterValue(sampledDataTxUpdatedMeasurands, trackSampledDataTxUpdatedMeasurandsWriteCount, trackSampledValueInputsSizeTxUpdated, MO_MEASURAND_TYPE_TXUPDATED, readingContext); +} +std::unique_ptr MeteringServiceEvse::takeTxEndedMeterValue(ReadingContext readingContext) { + return takeMeterValue(sampledDataTxEndedMeasurands, trackSampledDataTxEndedMeasurandsWriteCount, trackSampledValueInputsSizeTxEnded, MO_MEASURAND_TYPE_TXENDED, readingContext); +} +std::unique_ptr MeteringServiceEvse::takeTriggeredMeterValues() { + return takeMeterValue(alignedDataMeasurands, trackAlignedDataMeasurandsWriteCount, trackSampledValueInputsSizeAligned, MO_MEASURAND_TYPE_ALIGNED, ReadingContext_Trigger); +} + +bool MeteringServiceEvse::existsMeasurand(const char *measurand, size_t len) { + for (size_t i = 0; i < sampledValueInputs.size(); i++) { + const char *sviMeasurand = sampledValueInputs[i].getProperties().getMeasurand(); + if (sviMeasurand && len == strlen(sviMeasurand) && !strncmp(sviMeasurand, measurand, len)) { + return true; + } + } + return false; +} + +MeteringService::MeteringService(Model& model, size_t numEvses) { + + auto varService = model.getVariableService(); + + //define factory defaults + varService->declareVariable("SampledDataCtrlr", "TxStartedMeasurands", ""); + varService->declareVariable("SampledDataCtrlr", "TxUpdatedMeasurands", ""); + varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", "Energy.Active.Import.Register"); + varService->declareVariable("AlignedDataCtrlr", "AlignedDataMeasurands", ""); + + std::function validateSelectString = [this] (const char *csl) { + bool isValid = true; + const char *l = csl; //the beginning of an entry of the comma-separated list + const char *r = l; //one place after the last character of the entry beginning with l + while (*l) { + if (*l == ',') { + l++; + continue; + } + r = l + 1; + while (*r != '\0' && *r != ',') { + r++; + } + bool found = false; + for (size_t evseId = 0; evseId < MO_NUM_EVSE && evses[evseId]; evseId++) { + if (evses[evseId]->existsMeasurand(l, (size_t) (r - l))) { + found = true; + break; + } + } + if (!found) { + isValid = false; + MO_DBG_WARN("could not find metering device for %.*s", (int) (r - l), l); + break; + } + l = r; + } + return isValid; + }; + + varService->registerValidator("SampledDataCtrlr", "TxStartedMeasurands", validateSelectString); + varService->registerValidator("SampledDataCtrlr", "TxUpdatedMeasurands", validateSelectString); + varService->registerValidator("SampledDataCtrlr", "TxEndedMeasurands", validateSelectString); + varService->registerValidator("AlignedDataCtrlr", "AlignedDataMeasurands", validateSelectString); + + for (size_t evseId = 0; evseId < std::min(numEvses, (size_t)MO_NUM_EVSE); evseId++) { + evses[evseId] = new MeteringServiceEvse(model, evseId); + } +} + +MeteringService::~MeteringService() { + for (size_t evseId = 0; evseId < MO_NUM_EVSE && evses[evseId]; evseId++) { + delete evses[evseId]; + } +} + +MeteringServiceEvse *MeteringService::getEvse(unsigned int evseId) { + return evses[evseId]; +} + +#endif diff --git a/src/MicroOcpp/Model/Metering/MeterValuesV201.h b/src/MicroOcpp/Model/Metering/MeterValuesV201.h new file mode 100644 index 00000000..e272b4f1 --- /dev/null +++ b/src/MicroOcpp/Model/Metering/MeterValuesV201.h @@ -0,0 +1,152 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs E01 - E12 + */ + +#ifndef MO_METERVALUESV201_H +#define MO_METERVALUESV201_H + +#include + +#if MO_ENABLE_V201 + +#include + +#include +#include +#include +#include + +namespace MicroOcpp { + +class Model; +class Variable; + +namespace Ocpp201 { + +class SampledValueProperties : public MemoryManaged { +private: + const char *format = nullptr; + const char *measurand = nullptr; + const char *phase = nullptr; + const char *location = nullptr; + const char *unitOfMeasureUnit = nullptr; + int unitOfMeasureMultiplier = 0; + +public: + SampledValueProperties(); + SampledValueProperties(const SampledValueProperties&); + + void setFormat(const char *format); //zero-copy + const char *getFormat() const; + void setMeasurand(const char *measurand); //zero-copy + const char *getMeasurand() const; + void setPhase(const char *phase); //zero-copy + const char *getPhase() const; + void setLocation(const char *location); //zero-copy + const char *getLocation() const; + void setUnitOfMeasureUnit(const char *unitOfMeasureUnit); //zero-copy + const char *getUnitOfMeasureUnit() const; + void setUnitOfMeasureMultiplier(int unitOfMeasureMultiplier); + int getUnitOfMeasureMultiplier() const; +}; + +class SampledValue : public MemoryManaged { +private: + double value = 0.; + ReadingContext readingContext; + SampledValueProperties& properties; + //std::unique_ptr ... this could be an abstract type +public: + SampledValue(double value, ReadingContext readingContext, SampledValueProperties& properties); + + bool toJson(JsonDoc& out); +}; + +#define MO_MEASURAND_TYPE_TXSTARTED (1 << 0) +#define MO_MEASURAND_TYPE_TXUPDATED (1 << 1) +#define MO_MEASURAND_TYPE_TXENDED (1 << 2) +#define MO_MEASURAND_TYPE_ALIGNED (1 << 3) + +class SampledValueInput : public MemoryManaged { +private: + std::function valueInput; + SampledValueProperties properties; + + uint8_t measurandTypeFlags = 0; +public: + SampledValueInput(std::function valueInput, const SampledValueProperties& properties); + SampledValue *takeSampledValue(ReadingContext readingContext); + + const SampledValueProperties& getProperties(); + + uint8_t& getMeasurandTypeFlags(); +}; + +class MeterValue : public MemoryManaged { +private: + Timestamp timestamp; + SampledValue **sampledValue = nullptr; + size_t sampledValueSize = 0; +public: + MeterValue(const Timestamp& timestamp, SampledValue **sampledValue, size_t sampledValueSize); + ~MeterValue(); + + bool toJson(JsonDoc& out); + + const Timestamp& getTimestamp(); +}; + +class MeteringServiceEvse : public MemoryManaged { +private: + Model& model; + const unsigned int evseId; + + Vector sampledValueInputs; + + Variable *sampledDataTxStartedMeasurands = nullptr; + Variable *sampledDataTxUpdatedMeasurands = nullptr; + Variable *sampledDataTxEndedMeasurands = nullptr; + Variable *alignedDataMeasurands = nullptr; + + size_t trackSampledValueInputsSizeTxStarted = 0; + size_t trackSampledValueInputsSizeTxUpdated = 0; + size_t trackSampledValueInputsSizeTxEnded = 0; + size_t trackSampledValueInputsSizeAligned = 0; + uint16_t trackSampledDataTxStartedMeasurandsWriteCount = -1; + uint16_t trackSampledDataTxUpdatedMeasurandsWriteCount = -1; + uint16_t trackSampledDataTxEndedMeasurandsWriteCount = -1; + uint16_t trackAlignedDataMeasurandsWriteCount = -1; + + std::unique_ptr takeMeterValue(Variable *measurands, uint16_t& trackMeasurandsWriteCount, size_t& trackInputsSize, uint8_t measurandsMask, ReadingContext context); +public: + MeteringServiceEvse(Model& model, unsigned int evseId); + + void addMeterValueInput(std::function valueInput, const SampledValueProperties& properties); + + std::unique_ptr takeTxStartedMeterValue(ReadingContext context = ReadingContext_TransactionBegin); + std::unique_ptr takeTxUpdatedMeterValue(ReadingContext context = ReadingContext_SamplePeriodic); + std::unique_ptr takeTxEndedMeterValue(ReadingContext context); + std::unique_ptr takeTriggeredMeterValues(); + + bool existsMeasurand(const char *measurand, size_t len); +}; + +class MeteringService : public MemoryManaged { +private: + MeteringServiceEvse* evses [MO_NUM_EVSE] = {nullptr}; +public: + MeteringService(Model& model, size_t numEvses); + ~MeteringService(); + + MeteringServiceEvse *getEvse(unsigned int evseId); +}; + +} +} + +#endif +#endif diff --git a/src/MicroOcpp/Model/Metering/MeteringConnector.cpp b/src/MicroOcpp/Model/Metering/MeteringConnector.cpp index ad3b6807..e9dfc628 100644 --- a/src/MicroOcpp/Model/Metering/MeteringConnector.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringConnector.cpp @@ -98,7 +98,7 @@ void MeteringConnector::loop() { "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); if (abs(dt) <= 60) { //is measurement still "clock-aligned"? - if (auto alignedMeterValue = alignedDataBuilder->takeSample(model.getClock().now(), ReadingContext::SampleClock)) { + if (auto alignedMeterValue = alignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock)) { if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { MO_DBG_INFO("MeterValue cache full. Drop old MV"); meterData.erase(meterData.begin()); @@ -111,7 +111,7 @@ void MeteringConnector::loop() { } if (stopTxnData) { - auto alignedStopTx = stopTxnAlignedDataBuilder->takeSample(model.getClock().now(), ReadingContext::SampleClock); + auto alignedStopTx = stopTxnAlignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock); if (alignedStopTx) { stopTxnData->addTxData(std::move(alignedStopTx)); } @@ -138,7 +138,7 @@ void MeteringConnector::loop() { //record periodic tx data if (mocpp_tick_ms() - lastSampleTime >= (unsigned long) (meterValueSampleIntervalInt->getInt() * 1000)) { - if (auto sampledMeterValue = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext::SamplePeriodic)) { + if (auto sampledMeterValue = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic)) { if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { MO_DBG_INFO("MeterValue cache full. Drop old MV"); meterData.erase(meterData.begin()); @@ -151,7 +151,7 @@ void MeteringConnector::loop() { } if (stopTxnData && stopTxnDataCapturePeriodicBool->getBool()) { - auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext::SamplePeriodic); + auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic); if (sampleStopTx) { stopTxnData->addTxData(std::move(sampleStopTx)); } @@ -163,7 +163,7 @@ void MeteringConnector::loop() { std::unique_ptr MeteringConnector::takeTriggeredMeterValues() { - auto sample = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext::Trigger); + auto sample = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_Trigger); if (!sample) { return nullptr; @@ -199,7 +199,7 @@ void MeteringConnector::beginTxMeterData(Transaction *transaction) { } if (stopTxnData) { - auto sampleTxBegin = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext::TransactionBegin); + auto sampleTxBegin = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionBegin); if (sampleTxBegin) { stopTxnData->addTxData(std::move(sampleTxBegin)); } @@ -212,7 +212,7 @@ std::shared_ptr MeteringConnector::endTxMeterData(Transact } if (stopTxnData) { - auto sampleTxEnd = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext::TransactionEnd); + auto sampleTxEnd = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionEnd); if (sampleTxEnd) { stopTxnData->addTxData(std::move(sampleTxEnd)); } diff --git a/src/MicroOcpp/Model/Metering/ReadingContext.cpp b/src/MicroOcpp/Model/Metering/ReadingContext.cpp new file mode 100644 index 00000000..2ea24886 --- /dev/null +++ b/src/MicroOcpp/Model/Metering/ReadingContext.cpp @@ -0,0 +1,65 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#include +#include + +namespace MicroOcpp { + +const char *serializeReadingContext(ReadingContext context) { + switch (context) { + case (ReadingContext_InterruptionBegin): + return "Interruption.Begin"; + case (ReadingContext_InterruptionEnd): + return "Interruption.End"; + case (ReadingContext_Other): + return "Other"; + case (ReadingContext_SampleClock): + return "Sample.Clock"; + case (ReadingContext_SamplePeriodic): + return "Sample.Periodic"; + case (ReadingContext_TransactionBegin): + return "Transaction.Begin"; + case (ReadingContext_TransactionEnd): + return "Transaction.End"; + case (ReadingContext_Trigger): + return "Trigger"; + default: + MO_DBG_ERR("ReadingContext not specified"); + /* fall through */ + case (ReadingContext_UNDEFINED): + return ""; + } +} +ReadingContext deserializeReadingContext(const char *context) { + if (!context) { + MO_DBG_ERR("Invalid argument"); + return ReadingContext_UNDEFINED; + } + + if (!strcmp(context, "Sample.Periodic")) { + return ReadingContext_SamplePeriodic; + } else if (!strcmp(context, "Sample.Clock")) { + return ReadingContext_SampleClock; + } else if (!strcmp(context, "Transaction.Begin")) { + return ReadingContext_TransactionBegin; + } else if (!strcmp(context, "Transaction.End")) { + return ReadingContext_TransactionEnd; + } else if (!strcmp(context, "Other")) { + return ReadingContext_Other; + } else if (!strcmp(context, "Interruption.Begin")) { + return ReadingContext_InterruptionBegin; + } else if (!strcmp(context, "Interruption.End")) { + return ReadingContext_InterruptionEnd; + } else if (!strcmp(context, "Trigger")) { + return ReadingContext_Trigger; + } + + MO_DBG_ERR("ReadingContext not specified %.10s", context); + return ReadingContext_UNDEFINED; +} + +} //namespace MicroOcpp diff --git a/src/MicroOcpp/Model/Metering/ReadingContext.h b/src/MicroOcpp/Model/Metering/ReadingContext.h new file mode 100644 index 00000000..592914e1 --- /dev/null +++ b/src/MicroOcpp/Model/Metering/ReadingContext.h @@ -0,0 +1,28 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_READINGCONTEXT_H +#define MO_READINGCONTEXT_H + +typedef enum { + ReadingContext_UNDEFINED, + ReadingContext_InterruptionBegin, + ReadingContext_InterruptionEnd, + ReadingContext_Other, + ReadingContext_SampleClock, + ReadingContext_SamplePeriodic, + ReadingContext_TransactionBegin, + ReadingContext_TransactionEnd, + ReadingContext_Trigger +} ReadingContext; + +#ifdef __cplusplus + +namespace MicroOcpp { +const char *serializeReadingContext(ReadingContext context); +ReadingContext deserializeReadingContext(const char *serialized); +} + +#endif +#endif diff --git a/src/MicroOcpp/Model/Metering/SampledValue.cpp b/src/MicroOcpp/Model/Metering/SampledValue.cpp index 64f96152..4168a048 100644 --- a/src/MicroOcpp/Model/Metering/SampledValue.cpp +++ b/src/MicroOcpp/Model/Metering/SampledValue.cpp @@ -29,66 +29,6 @@ MicroOcpp::String SampledValueDeSerializer::serialize(float& val) { return makeString("v16.Metering.SampledValueDeSerializer", str); } -//helper function -namespace MicroOcpp { -namespace Ocpp16 { -const char *serializeReadingContext(ReadingContext context) { - switch (context) { - case (ReadingContext::InterruptionBegin): - return "Interruption.Begin"; - case (ReadingContext::InterruptionEnd): - return "Interruption.End"; - case (ReadingContext::Other): - return "Other"; - case (ReadingContext::SampleClock): - return "Sample.Clock"; - case (ReadingContext::SamplePeriodic): - return "Sample.Periodic"; - case (ReadingContext::TransactionBegin): - return "Transaction.Begin"; - case (ReadingContext::TransactionEnd): - return "Transaction.End"; - case (ReadingContext::Trigger): - return "Trigger"; - default: - MO_DBG_ERR("ReadingContext not specified"); - /* fall through */ - case (ReadingContext::NOT_SET): - return nullptr; - } -} -ReadingContext deserializeReadingContext(const char *context) { - if (!context) { - MO_DBG_ERR("Invalid argument"); - return ReadingContext::NOT_SET; - } - - if (!strcmp(context, "NOT_SET")) { - MO_DBG_DEBUG("Deserialize Null-ReadingContext"); - return ReadingContext::NOT_SET; - } else if (!strcmp(context, "Sample.Periodic")) { - return ReadingContext::SamplePeriodic; - } else if (!strcmp(context, "Sample.Clock")) { - return ReadingContext::SampleClock; - } else if (!strcmp(context, "Transaction.Begin")) { - return ReadingContext::TransactionBegin; - } else if (!strcmp(context, "Transaction.End")) { - return ReadingContext::TransactionEnd; - } else if (!strcmp(context, "Other")) { - return ReadingContext::Other; - } else if (!strcmp(context, "Interruption.Begin")) { - return ReadingContext::InterruptionBegin; - } else if (!strcmp(context, "Interruption.End")) { - return ReadingContext::InterruptionEnd; - } else if (!strcmp(context, "Trigger")) { - return ReadingContext::Trigger; - } - - MO_DBG_ERR("ReadingContext not specified %.10s", context); - return ReadingContext::NOT_SET; -} -}} //end namespaces - std::unique_ptr SampledValue::toJson() { auto value = serializeValue(); if (value.empty()) { @@ -100,7 +40,7 @@ std::unique_ptr SampledValue::toJson() { auto result = makeJsonDoc("v16.Metering.SampledValue", capacity); auto payload = result->to(); payload["value"] = value; - auto context_cstr = Ocpp16::serializeReadingContext(context); + auto context_cstr = serializeReadingContext(context); if (context_cstr) payload["context"] = context_cstr; if (*properties.getFormat()) diff --git a/src/MicroOcpp/Model/Metering/SampledValue.h b/src/MicroOcpp/Model/Metering/SampledValue.h index aabc674d..350990ae 100644 --- a/src/MicroOcpp/Model/Metering/SampledValue.h +++ b/src/MicroOcpp/Model/Metering/SampledValue.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -76,23 +77,6 @@ class SampledValueProperties { const char *getUnit() const {return unit.c_str();} }; -enum class ReadingContext { - InterruptionBegin, - InterruptionEnd, - Other, - SampleClock, - SamplePeriodic, - TransactionBegin, - TransactionEnd, - Trigger, - NOT_SET -}; - -namespace Ocpp16 { -const char *serializeReadingContext(ReadingContext context); -ReadingContext deserializeReadingContext(const char *serialized); -} - class SampledValue { protected: const SampledValueProperties& properties; @@ -153,7 +137,7 @@ class SampledValueSamplerConcrete : public SampledValueSampler, public MemoryMan std::unique_ptr deserializeValue(JsonObject svJson) override { return std::unique_ptr>(new SampledValueConcrete( properties, - Ocpp16::deserializeReadingContext(svJson["context"] | "NOT_SET"), + deserializeReadingContext(svJson["context"] | "NOT_SET"), DeSerializer::deserialize(svJson["value"] | ""))); } }; diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index b9a2b29e..95ef843e 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -253,6 +254,15 @@ void Model::setResetServiceV201(std::unique_ptr rs) { Ocpp201::ResetService *Model::getResetServiceV201() const { return resetServiceV201.get(); } + +void Model::setMeteringServiceV201(std::unique_ptr rs) { + this->meteringServiceV201 = std::move(rs); + capabilitiesUpdated = true; +} + +Ocpp201::MeteringService *Model::getMeteringServiceV201() const { + return meteringServiceV201.get(); +} #endif Clock& Model::getClock() { diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 5188c87e..89a65245 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -43,6 +43,7 @@ class TransactionService; namespace Ocpp201 { class ResetService; +class MeteringService; } #endif //MO_ENABLE_V201 @@ -76,6 +77,7 @@ class Model : public MemoryManaged { std::unique_ptr variableService; std::unique_ptr transactionService; std::unique_ptr resetServiceV201; + std::unique_ptr meteringServiceV201; #endif Clock clock; @@ -155,6 +157,9 @@ class Model : public MemoryManaged { void setResetServiceV201(std::unique_ptr rs); Ocpp201::ResetService *getResetServiceV201() const; + + void setMeteringServiceV201(std::unique_ptr ms); + Ocpp201::MeteringService *getMeteringServiceV201() const; #endif Clock &getClock(); diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index d95c7fa0..b38f2ad0 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -189,10 +189,16 @@ class Transaction : public MemoryManaged { #if MO_ENABLE_V201 #include +#include #include #include #include +#include + +#ifndef MO_SAMPLEDDATATXENDED_SIZE_MAX +#define MO_SAMPLEDDATATXENDED_SIZE_MAX 5 +#endif namespace MicroOcpp { namespace Ocpp201 { @@ -294,7 +300,46 @@ class Transaction : public MemoryManaged { TransactionEventTriggerReason stopTrigger = TransactionEventTriggerReason::UNDEFINED; std::unique_ptr stopIdToken; // if null, then stopIdToken equals idToken - Transaction() : MemoryManaged("v201.Transactions.Transaction") { } + /* + * Tx-related metering + */ + + Vector> sampledDataTxEnded; + + unsigned long lastSampleTimeTxUpdated = 0; //0 means not charging right now + unsigned long lastSampleTimeTxEnded = 0; + + /* + * Attributes for internal store + */ + unsigned int evseId = 0; + unsigned int txNr = 0; //internal key attribute (!= transactionId); {evseId*txNr} is unique key + + bool silent = false; //silent Tx: process tx locally, without reporting to the server + + Transaction() : MemoryManaged("v201.Transactions.Transaction"), sampledDataTxEnded(makeVector>(getMemoryTag())) { } + + void addSampledDataTxEnded(std::unique_ptr mv) { + if (sampledDataTxEnded.size() >= MO_SAMPLEDDATATXENDED_SIZE_MAX) { + int deltaMin = std::numeric_limits::max(); + size_t indexMin = sampledDataTxEnded.size(); + for (size_t i = 1; i + 1 <= sampledDataTxEnded.size(); i++) { + size_t t0 = sampledDataTxEnded.size() - i - 1; + size_t t1 = sampledDataTxEnded.size() - i; + + auto delta = sampledDataTxEnded[t1]->getTimestamp() - sampledDataTxEnded[t0]->getTimestamp(); + + if (delta < deltaMin) { + deltaMin = delta; + indexMin = t1; + } + } + + sampledDataTxEnded.erase(sampledDataTxEnded.begin() + indexMin); + } + + sampledDataTxEnded.push_back(std::move(mv)); + } }; // TransactionEventRequest (1.60.1) @@ -336,8 +381,9 @@ class TransactionEventData : public MemoryManaged { std::unique_ptr idToken; EvseId evse = -1; //meterValue not supported + Vector> meterValue; - TransactionEventData(std::shared_ptr transaction, unsigned int seqNo) : MemoryManaged("v201.Transactions.TransactionEventData"), transaction(transaction), seqNo(seqNo) { } + TransactionEventData(std::shared_ptr transaction, unsigned int seqNo) : MemoryManaged("v201.Transactions.TransactionEventData"), transaction(transaction), seqNo(seqNo), meterValue(makeVector>(getMemoryTag())) { } }; } // namespace Ocpp201 diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index c36126ac..770954af 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -24,7 +24,10 @@ using namespace MicroOcpp; using namespace MicroOcpp::Ocpp201; TransactionService::Evse::Evse(Context& context, TransactionService& txService, unsigned int evseId) : - MemoryManaged("v201.Transactions.TransactionServiceEvse"), context(context), txService(txService), evseId(evseId) { + MemoryManaged("v201.Transactions.TransactionServiceEvse"), + context(context), + txService(txService), + evseId(evseId) { } @@ -35,6 +38,9 @@ std::unique_ptr TransactionService::Evse::allocateTransact return nullptr; } + tx->evseId = evseId; + tx->txNr = txNrCounter; + //simple clock-based hash int v = context.getModel().getClock().now() - Timestamp(2020,0,0,0,0,0); unsigned int h = v; @@ -152,7 +158,7 @@ void TransactionService::Evse::loop() { (!stopTxReadyInput || stopTxReadyInput())) { // yes, stop running tx - txEvent = std::make_shared(transaction, transaction->seqNoCounter++); + txEvent = std::allocate_shared(makeAllocator(getMemoryTag()), transaction, transaction->seqNoCounter++); if (!txEvent) { // OOM return; @@ -219,7 +225,7 @@ void TransactionService::Evse::loop() { } } - txEvent = std::make_shared(transaction, transaction->seqNoCounter++); + txEvent = std::allocate_shared(makeAllocator(getMemoryTag()), transaction, transaction->seqNoCounter++); if (!txEvent) { // OOM return; @@ -277,6 +283,9 @@ void TransactionService::Evse::loop() { transaction->trackAuthorized = false; txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::StopAuthorized; + } else if (txService.sampledDataTxUpdatedInterval && txService.sampledDataTxUpdatedInterval->getInt() > 0 && mocpp_tick_ms() - transaction->lastSampleTimeTxUpdated >= (unsigned long)txService.sampledDataTxUpdatedInterval->getInt() * 1000UL) { + txUpdateCondition = true; + triggerReason = TransactionEventTriggerReason::MeterValuePeriodic; } else if (evReadyInput && evReadyInput() && !transaction->trackPowerPathClosed) { transaction->trackPowerPathClosed = true; } else if (evReadyInput && !evReadyInput() && transaction->trackPowerPathClosed) { @@ -286,7 +295,7 @@ void TransactionService::Evse::loop() { if (txUpdateCondition && !txEvent && transaction->started && !transaction->stopped) { // yes, updated - txEvent = std::make_shared(transaction, transaction->seqNoCounter++); + txEvent = std::allocate_shared(makeAllocator(getMemoryTag()), transaction, transaction->seqNoCounter++); if (!txEvent) { // OOM return; @@ -297,6 +306,22 @@ void TransactionService::Evse::loop() { } } + //General Metering behavior. There is another section for TxStarted, Updated and TxEnded MeterValues + if (transaction) { + + if (transaction->started && !transaction->stopped && + txService.sampledDataTxEndedInterval && txService.sampledDataTxEndedInterval->getInt() > 0 && + mocpp_tick_ms() - transaction->lastSampleTimeTxEnded >= (unsigned long)txService.sampledDataTxEndedInterval->getInt() * 1000UL) { + transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); + auto meteringService = context.getModel().getMeteringServiceV201(); + auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; + auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_SamplePeriodic) : nullptr; + if (mvTxEnded) { + transaction->addSampledDataTxEnded(std::move(mvTxEnded)); + } + } + } + if (txEvent) { txEvent->timestamp = context.getModel().getClock().now(); if (transaction->notifyChargingState) { @@ -311,7 +336,38 @@ void TransactionService::Evse::loop() { txEvent->remoteStartId = transaction->remoteStartId; transaction->notifyRemoteStartId = false; } - // meterValue not supported + if (txEvent->eventType == TransactionEventData::Type::Started) { + auto meteringService = context.getModel().getMeteringServiceV201(); + auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; + auto mvTxStarted = meteringEvse ? meteringEvse->takeTxStartedMeterValue() : nullptr; + if (mvTxStarted) { + txEvent->meterValue.push_back(std::move(mvTxStarted)); + } + auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_TransactionBegin) : nullptr; + if (mvTxEnded) { + transaction->addSampledDataTxEnded(std::move(mvTxEnded)); + } + transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); + transaction->lastSampleTimeTxUpdated = mocpp_tick_ms(); + } else if (txEvent->eventType == TransactionEventData::Type::Updated) { + if (txService.sampledDataTxUpdatedInterval && txService.sampledDataTxUpdatedInterval->getInt() > 0 && mocpp_tick_ms() - transaction->lastSampleTimeTxUpdated >= (unsigned long)txService.sampledDataTxUpdatedInterval->getInt() * 1000UL) { + transaction->lastSampleTimeTxUpdated = mocpp_tick_ms(); + auto meteringService = context.getModel().getMeteringServiceV201(); + auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; + auto mv = meteringEvse ? meteringEvse->takeTxUpdatedMeterValue() : nullptr; + if (mv) { + txEvent->meterValue.push_back(std::move(mv)); + } + } + } else if (txEvent->eventType == TransactionEventData::Type::Ended) { + auto meteringService = context.getModel().getMeteringServiceV201(); + auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; + auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_TransactionEnd) : nullptr; + if (mvTxEnded) { + transaction->addSampledDataTxEnded(std::move(mvTxEnded)); + } + transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); + } if (transaction->notifyStopIdToken && transaction->stopIdToken) { txEvent->idToken = std::unique_ptr(new IdToken(*transaction->stopIdToken.get(), getMemoryTag())); @@ -547,27 +603,41 @@ bool TransactionService::parseTxStartStopPoint(const char *csl, Vector(getMemoryTag())), txStartPointParsed(makeVector(getMemoryTag())), txStopPointParsed(makeVector(getMemoryTag())) { - auto variableService = context.getModel().getVariableService(); - - txStartPointString = variableService->declareVariable("TxCtrlr", "TxStartPoint", "PowerPathClosed"); - 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 { +TransactionService::TransactionService(Context& context) : + MemoryManaged("v201.Transactions.TransactionService"), + context(context), + evses(makeVector(getMemoryTag())), + txStartPointParsed(makeVector(getMemoryTag())), + txStopPointParsed(makeVector(getMemoryTag())) { + auto varService = context.getModel().getVariableService(); + + txStartPointString = varService->declareVariable("TxCtrlr", "TxStartPoint", "PowerPathClosed"); + txStopPointString = varService->declareVariable("TxCtrlr", "TxStopPoint", "PowerPathClosed"); + stopTxOnInvalidIdBool = varService->declareVariable("TxCtrlr", "StopTxOnInvalidId", true); + stopTxOnEVSideDisconnectBool = varService->declareVariable("TxCtrlr", "StopTxOnEVSideDisconnect", true); + evConnectionTimeOutInt = varService->declareVariable("TxCtrlr", "EVConnectionTimeOut", 30); + sampledDataTxUpdatedInterval = varService->declareVariable("SampledDataCtrlr", "TxUpdatedInterval", 0); + sampledDataTxEndedInterval = varService->declareVariable("SampledDataCtrlr", "TxEndedInterval", 5); + + varService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false, MO_VARIABLE_VOLATILE, Variable::Mutability::ReadOnly); + + varService->registerValidator("TxCtrlr", "TxStartPoint", [this] (const char *value) -> bool { auto validated = makeVector(getMemoryTag()); return this->parseTxStartStopPoint(value, validated); }); - variableService->registerValidator("TxCtrlr", "TxStopPoint", [this] (const char *value) -> bool { + varService->registerValidator("TxCtrlr", "TxStopPoint", [this] (const char *value) -> bool { auto validated = makeVector(getMemoryTag()); return this->parseTxStartStopPoint(value, validated); }); + std::function validateUnsignedInt = [] (int val) { + return val >= 0; + }; + + varService->registerValidator("SampledDataCtrlr", "TxUpdatedInterval", validateUnsignedInt); + varService->registerValidator("SampledDataCtrlr", "TxEndedInterval", validateUnsignedInt); + evses.reserve(MO_NUM_EVSE); for (unsigned int evseId = 0; evseId < MO_NUM_EVSE; evseId++) { diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.h b/src/MicroOcpp/Model/Transactions/TransactionService.h index 4b985b24..70ddc86b 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -33,6 +34,7 @@ class TransactionService : public MemoryManaged { Context& context; TransactionService& txService; const unsigned int evseId; + unsigned int txNrCounter = 0; std::shared_ptr transaction; Ocpp201::TransactionEventData::ChargingState trackChargingState = Ocpp201::TransactionEventData::ChargingState::UNDEFINED; @@ -44,6 +46,7 @@ class TransactionService : public MemoryManaged { std::function stopTxReadyInput; std::unique_ptr allocateTransaction(); + public: Evse(Context& context, TransactionService& txService, unsigned int evseId); @@ -86,6 +89,8 @@ class TransactionService : public MemoryManaged { Variable *stopTxOnInvalidIdBool = nullptr; Variable *stopTxOnEVSideDisconnectBool = nullptr; Variable *evConnectionTimeOutInt = nullptr; + Variable *sampledDataTxUpdatedInterval = nullptr; + Variable *sampledDataTxEndedInterval = nullptr; uint16_t trackTxStartPoint = -1; uint16_t trackTxStopPoint = -1; Vector txStartPointParsed; diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index 97c62d4b..01ea4111 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -76,7 +76,7 @@ std::shared_ptr ConnectorTransactionStore::getTransaction(unsigned return nullptr; } - auto transaction = std::make_shared(*this, connectorId, txNr); + auto transaction = std::allocate_shared(makeAllocator(getMemoryTag()), *this, connectorId, txNr); JsonObject txJson = doc->as(); if (!deserializeTransaction(*transaction, txJson)) { MO_DBG_ERR("deserialization error"); @@ -100,7 +100,7 @@ std::shared_ptr ConnectorTransactionStore::getTransaction(unsigned std::shared_ptr ConnectorTransactionStore::createTransaction(unsigned int txNr, bool silent) { - auto transaction = std::make_shared(*this, connectorId, txNr, silent); + auto transaction = std::allocate_shared(makeAllocator(getMemoryTag()), *this, connectorId, txNr, silent); if (!commit(transaction.get())) { MO_DBG_ERR("FS error"); diff --git a/src/MicroOcpp/Operations/StopTransaction.cpp b/src/MicroOcpp/Operations/StopTransaction.cpp index ecc495cc..a3aef101 100644 --- a/src/MicroOcpp/Operations/StopTransaction.cpp +++ b/src/MicroOcpp/Operations/StopTransaction.cpp @@ -58,9 +58,9 @@ std::unique_ptr StopTransaction::createReq() { for (auto mv = transactionData.begin(); mv != transactionData.end(); mv++) { if ((*mv)->getTimestamp() < MIN_TIME) { //time off. Try to adjust, otherwise send invalid value - if ((*mv)->getReadingContext() == ReadingContext::TransactionBegin) { + if ((*mv)->getReadingContext() == ReadingContext_TransactionBegin) { (*mv)->setTimestamp(transaction->getStartTimestamp()); - } else if ((*mv)->getReadingContext() == ReadingContext::TransactionEnd) { + } else if ((*mv)->getReadingContext() == ReadingContext_TransactionEnd) { (*mv)->setTimestamp(transaction->getStopTimestamp()); } else { (*mv)->setTimestamp(transaction->getStartTimestamp() + 1); diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp index a9e74a2f..16df68cf 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.cpp +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -24,13 +24,31 @@ const char* TransactionEvent::getOperationType() { } std::unique_ptr TransactionEvent::createReq() { - auto doc = makeJsonDoc(getMemoryTag(), - JSON_OBJECT_SIZE(12) + //total of 12 fields - JSONDATE_LENGTH + 1 + //timestamp string - JSON_OBJECT_SIZE(5) + //transactionInfo - MO_TXID_LEN_MAX + 1 + //transactionId - MO_IDTOKEN_LEN_MAX + 1); //idToken - //meterValue not supported + + size_t capacity = 0; + + if (txEvent->eventType == TransactionEventData::Type::Ended) { + for (size_t i = 0; i < txEvent->transaction->sampledDataTxEnded.size(); i++) { + JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); //just measure, create again for serialization later + txEvent->transaction->sampledDataTxEnded[i]->toJson(meterValueJson); + capacity += meterValueJson.capacity(); + } + } + + for (size_t i = 0; i < txEvent->meterValue.size(); i++) { + JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); //just measure, create again for serialization later + txEvent->meterValue[i]->toJson(meterValueJson); + capacity += meterValueJson.capacity(); + } + + capacity += + JSON_OBJECT_SIZE(12) + //total of 12 fields + JSONDATE_LENGTH + 1 + //timestamp string + JSON_OBJECT_SIZE(5) + //transactionInfo + MO_TXID_LEN_MAX + 1 + //transactionId + MO_IDTOKEN_LEN_MAX + 1; //idToken + + auto doc = makeJsonDoc(getMemoryTag(), capacity); JsonObject payload = doc->to(); const char *eventType = ""; @@ -255,7 +273,19 @@ std::unique_ptr TransactionEvent::createReq() { } } - // meterValue not supported + if (txEvent->eventType == TransactionEventData::Type::Ended) { + for (size_t i = 0; i < txEvent->transaction->sampledDataTxEnded.size(); i++) { + JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); + txEvent->transaction->sampledDataTxEnded[i]->toJson(meterValueJson); + payload["meterValue"].add(meterValueJson); + } + } + + for (size_t i = 0; i < txEvent->meterValue.size(); i++) { + JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); + txEvent->meterValue[i]->toJson(meterValueJson); + payload["meterValue"].add(meterValueJson); + } return doc; } diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp index 7fedfdc4..aa86df32 100644 --- a/tests/Transactions.cpp +++ b/tests/Transactions.cpp @@ -196,6 +196,76 @@ TEST_CASE( "Transactions" ) { MO_MEM_PRINT_STATS(); } + SECTION("UC J02") { + + //scenario preparation + + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Available ); + + getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStartPoint", "")->setString("PowerPathClosed"); + getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStopPoint", "")->setString("PowerPathClosed"); + + getOcppContext()->getModel().getVariableService()->declareVariable("SampledDataCtrlr", "SampledDataTxStartedMeasurands", "")->setString("Energy.Active.Import.Register"); + getOcppContext()->getModel().getVariableService()->declareVariable("SampledDataCtrlr", "SampledDataTxUpdatedMeasurands", "")->setString("Power.Active.Import"); + getOcppContext()->getModel().getVariableService()->declareVariable("SampledDataCtrlr", "TxUpdatedInterval", 0)->setInt(60); + getOcppContext()->getModel().getVariableService()->declareVariable("SampledDataCtrlr", "SampledDataTxEndedMeasurands", "")->setString("Current.Import"); + getOcppContext()->getModel().getVariableService()->declareVariable("SampledDataCtrlr", "TxEndededInterval", 0)->setInt(100); + + setConnectorPluggedInput([] () {return false;}); + setEnergyMeterInput([] () {return 100;}); + setPowerMeterInput([] () {return 200;}); + addMeterValueInput([] () {return 30;}, "Current.Import", "A"); + + Timestamp tStart, tUpdated, tEnded; + + setOnReceiveRequest("TransactionEvent", [&tStart, &tUpdated, &tEnded] (JsonObject request) { + const char *eventType = request["eventType"] | (const char*)nullptr; + bool eventTypeError = false; + if (!strcmp(eventType, "Started")) { + tStart = getOcppContext()->getModel().getClock().now(); + + REQUIRE( request["meterValue"].as().size() >= 1 ); + + Timestamp tMv; + tMv.setTime(request["meterValue"][0]["timestamp"]); + REQUIRE( std::abs(tStart - tMv) <= 1); + + REQUIRE( request["meterValue"][0]["sampledValue"].as().size() >= 1 ); + + REQUIRE( !strcmp(request["meterValue"][0]["sampledValue"][0]["measurand"] | "_Undefined", "Energy.Active.Import.Register") ); + REQUIRE( !strcmp(request["meterValue"][0]["sampledValue"][0]["measurand"] | "_Undefined", "Energy.Active.Import.Register") ); + } else if (!strcmp(eventType, "Updated")) { + tUpdated = getOcppContext()->getModel().getClock().now(); + + } else if (!strcmp(eventType, "Ended")) { + tEnded = getOcppContext()->getModel().getClock().now(); + + } else { + eventTypeError = true; + } + REQUIRE( !eventTypeError ); + }); + + loop(); + + MO_MEM_RESET(); + + //run scenario + + setConnectorPluggedInput([] () {return true;}); + context->getModel().getTransactionService()->getEvse(1)->beginAuthorization("mIdToken"); + loop(); + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() != nullptr ); + REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started ); + REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped ); + REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied ); + + MO_DBG_INFO("Memory requirements UC E01 - S5:"); + + MO_MEM_PRINT_STATS(); + } + mocpp_deinitialize(); } From a5f661519cc164783a50bb7ea479ce053e8a66dd Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 17 Sep 2024 09:10:03 +0200 Subject: [PATCH 03/18] add authorization active flag for begin- / endAuthorization --- .../Model/Transactions/Transaction.h | 1 + .../Model/Transactions/TransactionService.cpp | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index b38f2ad0..bf8a058c 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -278,6 +278,7 @@ class Transaction : public MemoryManaged { /* * Global transaction data */ + bool isAuthorizationActive = false; //period between beginAuthorization and endAuthorization bool isAuthorized = false; //if the given idToken was authorized bool isDeauthorized = false; //if the server revoked a local authorization unsigned int seqNoCounter = 0; // increment by 1 for each event diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index 770954af..8055fe1e 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -118,7 +118,7 @@ void TransactionService::Evse::loop() { stopReason = Ocpp201::Transaction::StopReason::EVDisconnected; } else if ((txService.isTxStopPoint(TxStartStopPoint::Authorized) || txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) && - (!transaction || !transaction->isAuthorized)) { + (!transaction || !transaction->isAuthorizationActive)) { // user revoked authorization (or EV or any "local" entity) txStopCondition = true; triggerReason = TransactionEventTriggerReason::StopAuthorized; @@ -182,7 +182,7 @@ void TransactionService::Evse::loop() { // tx should be started? if (txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) && (!connectorPluggedInput || connectorPluggedInput()) && - transaction && transaction->isAuthorized) { + transaction && transaction->isAuthorizationActive && transaction->isAuthorized) { txStartCondition = true; if (transaction->remoteStartId >= 0) { triggerReason = TransactionEventTriggerReason::RemoteStart; @@ -190,7 +190,7 @@ void TransactionService::Evse::loop() { triggerReason = TransactionEventTriggerReason::Authorized; } } else if (txService.isTxStartPoint(TxStartStopPoint::Authorized) && - transaction && transaction->isAuthorized) { + transaction && transaction->isAuthorizationActive && transaction->isAuthorized) { txStartCondition = true; if (transaction->remoteStartId >= 0) { triggerReason = TransactionEventTriggerReason::RemoteStart; @@ -239,7 +239,7 @@ void TransactionService::Evse::loop() { TransactionEventData::ChargingState chargingState = TransactionEventData::ChargingState::Idle; if (connectorPluggedInput && !connectorPluggedInput()) { chargingState = TransactionEventData::ChargingState::Idle; - } else if (!transaction || !transaction->isAuthorized) { + } else if (!transaction || !transaction->isAuthorizationActive || !transaction->isAuthorized) { chargingState = TransactionEventData::ChargingState::EVConnected; } else if (evseReadyInput && !evseReadyInput()) { chargingState = TransactionEventData::ChargingState::SuspendedEVSE; @@ -263,7 +263,7 @@ void TransactionService::Evse::loop() { } trackChargingState = chargingState; - if (transaction->isAuthorized && !transaction->trackAuthorized) { + if ((transaction->isAuthorizationActive && transaction->isAuthorized) && !transaction->trackAuthorized) { transaction->trackAuthorized = true; txUpdateCondition = true; if (transaction->remoteStartId >= 0) { @@ -279,7 +279,7 @@ void TransactionService::Evse::loop() { transaction->trackEvConnected = false; txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::EVCommunicationLost; - } else if (!transaction->isAuthorized && transaction->trackAuthorized) { + } else if (!(transaction->isAuthorizationActive && transaction->isAuthorized) && transaction->trackAuthorized) { transaction->trackAuthorized = false; txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::StopAuthorized; @@ -406,7 +406,7 @@ void TransactionService::Evse::setEvseReadyInput(std::function connector bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validateIdToken) { MO_DBG_DEBUG("begin auth: %s", idToken.get()); - if (transaction && (transaction->idToken.get() || !transaction->active)) { + if (transaction && transaction->isAuthorizationActive) { MO_DBG_WARN("tx process still running. Please call endTransaction(...) before"); return false; } @@ -422,6 +422,7 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate } } + transaction->isAuthorizationActive = true; transaction->idToken = idToken; transaction->beginTimestamp = context.getModel().getClock().now(); @@ -431,6 +432,7 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate auto authorize = makeRequest(new Authorize(context.getModel(), idToken)); if (!authorize) { // OOM + abortTransaction(); return false; } @@ -457,7 +459,7 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate } bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateIdToken) { - if (!transaction || !transaction->active) { + if (!transaction || !transaction->isAuthorizationActive) { //transaction already ended / not active anymore return false; } @@ -467,11 +469,11 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateId if (transaction->idToken.equals(idToken)) { // use same idToken like tx start - transaction->isAuthorized = false; + transaction->isAuthorizationActive = false; transaction->notifyIdToken = true; } else if (!validateIdToken) { transaction->stopIdToken = std::unique_ptr(new IdToken(idToken, getMemoryTag())); - transaction->isAuthorized = false; + transaction->isAuthorizationActive = false; transaction->notifyStopIdToken = true; } else { // use a different idToken for stopping the tx @@ -504,7 +506,7 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateId return; } - tx->isAuthorized = false; + tx->isAuthorizationActive = false; tx->notifyStopIdToken = true; }); authorize->setTimeout(20 * 1000); @@ -536,6 +538,7 @@ std::shared_ptr& TransactionService::Evse::getT bool TransactionService::Evse::ocppPermitsCharge() { return transaction && transaction->active && + transaction->isAuthorizationActive && transaction->isAuthorized && !transaction->isDeauthorized; } From 503b3a6548603456b55886b852aafc95d8eb5200 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 17 Sep 2024 09:11:00 +0200 Subject: [PATCH 04/18] patch transaction API for v201 --- src/MicroOcpp.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 0d26dfe2..b1d8d71a 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -536,6 +536,21 @@ bool isTransactionActive(unsigned int connectorId) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + + #if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + TransactionService::Evse *evse = nullptr; + if (auto txService = context->getModel().getTransactionService()) { + evse = txService->getEvse(connectorId); + } + if (!evse) { + MO_DBG_ERR("could not find EVSE"); + return false; + } + return evse->getTransaction() && evse->getTransaction()->active; + } + #endif + auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); @@ -550,6 +565,21 @@ bool isTransactionRunning(unsigned int connectorId) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + + #if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + TransactionService::Evse *evse = nullptr; + if (auto txService = context->getModel().getTransactionService()) { + evse = txService->getEvse(connectorId); + } + if (!evse) { + MO_DBG_ERR("could not find EVSE"); + return false; + } + return evse->getTransaction() && evse->getTransaction()->started && !evse->getTransaction()->stopped; + } + #endif + auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); @@ -564,6 +594,21 @@ const char *getTransactionIdTag(unsigned int connectorId) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return nullptr; } + + #if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + TransactionService::Evse *evse = nullptr; + if (auto txService = context->getModel().getTransactionService()) { + evse = txService->getEvse(connectorId); + } + if (!evse) { + MO_DBG_ERR("could not find EVSE"); + return nullptr; + } + return evse->getTransaction() ? evse->getTransaction()->idToken.get() : nullptr; + } + #endif + auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); From c4cb3be2ce28eca7c5bfc3cd7692cbab87ddc812 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 17 Sep 2024 09:20:06 +0200 Subject: [PATCH 05/18] restore non-v201-compatible ChangeAvailability --- .../Operations/ChangeAvailability.cpp | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/src/MicroOcpp/Operations/ChangeAvailability.cpp b/src/MicroOcpp/Operations/ChangeAvailability.cpp index b0bd2da3..cfcf04e3 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.cpp +++ b/src/MicroOcpp/Operations/ChangeAvailability.cpp @@ -21,40 +21,19 @@ const char* ChangeAvailability::getOperationType(){ } void ChangeAvailability::processReq(JsonObject payload) { - - unsigned int connectorId = 0; - const char *type = "_Undefined"; - - #if MO_ENABLE_V201 - if (model.getVersion().major == 2) { - //OCPP 2.0.1 - int connectorIdRaw = payload["evse"]["id"] | 0; - if (connectorIdRaw < 0) { - errorCode = "FormationViolation"; - return; - } - - connectorId = (unsigned int)connectorIdRaw; - type = payload["operationalStatus"] | type; - } else - #endif //MO_ENABLE_V201 - { - //OCPP 1.6 - int connectorIdRaw = payload["connectorId"] | -1; - if (connectorIdRaw < 0) { - errorCode = "FormationViolation"; - return; - } - - connectorId = (unsigned int)connectorIdRaw; - type = payload["type"] | type; + int connectorIdRaw = payload["connectorId"] | -1; + if (connectorIdRaw < 0) { + errorCode = "FormationViolation"; + return; } + unsigned int connectorId = (unsigned int)connectorIdRaw; if (connectorId >= model.getNumConnectors()) { errorCode = "PropertyConstraintViolation"; return; } + const char *type = payload["type"] | "INVALID"; bool available = false; if (!strcmp(type, "Operative")) { From 16606e4258f7dc99d225348dcc785d27fb6ee02c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:41:26 +0200 Subject: [PATCH 06/18] update v201 ChangeAvailability port --- .../Availability/AvailabilityService.cpp | 60 +++++++++++++- .../Model/Availability/AvailabilityService.h | 7 +- .../Availability/ChangeAvailabilityStatus.h | 25 ++++++ .../Operations/ChangeAvailability.cpp | 79 ++++++++++++++++++- src/MicroOcpp/Operations/ChangeAvailability.h | 35 ++++++++ 5 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 294701b3..c79691c8 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include using namespace MicroOcpp; @@ -103,6 +105,20 @@ void AvailabilityServiceEvse::resetInoperative(void *requesterId) { MO_DBG_ERR("could not find inoperative requester"); } +ChangeAvailabilityStatus AvailabilityServiceEvse::changeAvailability(bool operative) { + if (operative) { + resetInoperative(this); + } else { + setInoperative(this); + } + + if (isOperative() && !operative) { + return ChangeAvailabilityStatus::Scheduled; + } else { + return ChangeAvailabilityStatus::Accepted; + } +} + void AvailabilityServiceEvse::setFaulted(void *requesterId) { for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) { if (!faultedRequesters[i]) { @@ -128,6 +144,30 @@ bool AvailabilityServiceEvse::isOperative() { return false; } + auto txService = context.getModel().getTransactionService(); + + if (evseId == 0) { + for (unsigned int id = 1; id < MO_NUM_EVSE; id++) { + TransactionService::Evse *evse; + if (txService && (evse = txService->getEvse(id))) { + if (evse->getTransaction() && + evse->getTransaction()->started && + !evse->getTransaction()->stopped) { + return true; + } + } + } + } else { + TransactionService::Evse *evse; + if (txService && (evse = txService->getEvse(evseId))) { + if (evse->getTransaction() && + evse->getTransaction()->started && + !evse->getTransaction()->stopped) { + return true; + } + } + } + for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { if (inoperativeRequesters[i]) { return false; @@ -153,16 +193,18 @@ AvailabilityService::AvailabilityService(Context& context, size_t numEvses) : Me context.getOperationRegistry().registerOperation("StatusNotification", [&context] () { return new Ocpp16::StatusNotification(-1, ChargePointStatus_UNDEFINED, Timestamp());}); + context.getOperationRegistry().registerOperation("ChangeAvailability", [this] () { + return new Ocpp201::ChangeAvailability(*this);}); } AvailabilityService::~AvailabilityService() { - for (size_t i = 0; evses[i]; i++) { + for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { delete evses[i]; } } void AvailabilityService::loop() { - for (size_t i = 0; evses[i]; i++) { + for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { evses[i]->loop(); } } @@ -175,4 +217,18 @@ AvailabilityServiceEvse *AvailabilityService::getEvse(unsigned int evseId) { return evses[evseId]; } +ChangeAvailabilityStatus AvailabilityService::changeAvailability(bool operative) { + bool scheduled = false; + + for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { + scheduled |= evses[i]->changeAvailability(operative) == ChangeAvailabilityStatus::Scheduled; + } + + if (scheduled) { + return ChangeAvailabilityStatus::Scheduled; + } else { + return ChangeAvailabilityStatus::Accepted; + } +} + #endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h index 5e836e87..0f13ee0a 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -17,6 +17,7 @@ #include +#include #include #include #include @@ -61,6 +62,8 @@ class AvailabilityServiceEvse : public MemoryManaged { void setInoperative(void *requesterId); void resetInoperative(void *requesterId); + ChangeAvailabilityStatus changeAvailability(bool operative); + void setFaulted(void *requesterId); void resetFaulted(void *requesterId); @@ -72,7 +75,7 @@ class AvailabilityService : public MemoryManaged { private: Context& context; - AvailabilityServiceEvse* evses [MO_NUM_EVSE + 1] = {nullptr}; + AvailabilityServiceEvse* evses [MO_NUM_EVSE] = {nullptr}; public: AvailabilityService(Context& context, size_t numEvses); @@ -81,6 +84,8 @@ class AvailabilityService : public MemoryManaged { void loop(); AvailabilityServiceEvse *getEvse(unsigned int evseId); + + ChangeAvailabilityStatus changeAvailability(bool operative); }; } // namespace MicroOcpp diff --git a/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h b/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h new file mode 100644 index 00000000..3439cc89 --- /dev/null +++ b/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h @@ -0,0 +1,25 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_CHANGEAVAILABILITYSTATUS_H +#define MO_CHANGEAVAILABILITYSTATUS_H + +#include + +#if MO_ENABLE_V201 + +#include + +namespace MicroOcpp { + +enum class ChangeAvailabilityStatus : uint8_t { + Accepted, + Rejected, + Scheduled +}; + +} //namespace MicroOcpp + +#endif //MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Operations/ChangeAvailability.cpp b/src/MicroOcpp/Operations/ChangeAvailability.cpp index cfcf04e3..d37d2a15 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.cpp +++ b/src/MicroOcpp/Operations/ChangeAvailability.cpp @@ -9,8 +9,8 @@ #include -using MicroOcpp::Ocpp16::ChangeAvailability; -using MicroOcpp::JsonDoc; +namespace MicroOcpp { +namespace Ocpp16 { ChangeAvailability::ChangeAvailability(Model& model) : MemoryManaged("v16.Operation.", "ChangeAvailability"), model(model) { @@ -77,3 +77,78 @@ std::unique_ptr ChangeAvailability::createConf(){ return doc; } + +} // namespace Ocpp16 +} // namespace MicroOcpp + +#if MO_ENABLE_V201 + +#include + +namespace MicroOcpp { +namespace Ocpp201 { + +ChangeAvailability::ChangeAvailability(AvailabilityService& availabilityService) : MemoryManaged("v201.Operation.", "ChangeAvailability"), availabilityService(availabilityService) { + +} + +const char* ChangeAvailability::getOperationType(){ + return "ChangeAvailability"; +} + +void ChangeAvailability::processReq(JsonObject payload) { + int evseIdRaw = payload["evse"]["id"] | 0; + if (evseIdRaw < 0) { + errorCode = "FormationViolation"; + return; + } + unsigned int evseId = (unsigned int)evseIdRaw; + + if (!availabilityService.getEvse(evseId)) { + errorCode = "PropertyConstraintViolation"; + return; + } + + const char *type = payload["operationalStatus"] | "_Undefined"; + + bool operative = false; + + if (!strcmp(type, "Operative")) { + operative = true; + } else if (!strcmp(type, "Inoperative")) { + operative = false; + } else { + errorCode = "PropertyConstraintViolation"; + return; + } + + if (evseId == 0) { + status = availabilityService.changeAvailability(operative); + } else { + status = availabilityService.getEvse(evseId)->changeAvailability(operative); + } +} + +std::unique_ptr ChangeAvailability::createConf(){ + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); + JsonObject payload = doc->to(); + + switch (status) { + case ChangeAvailabilityStatus::Accepted: + payload["status"] = "Accepted"; + break; + case ChangeAvailabilityStatus::Scheduled: + payload["status"] = "Scheduled"; + break; + case ChangeAvailabilityStatus::Rejected: + payload["status"] = "Rejected"; + break; + } + + return doc; +} + +} // namespace Ocpp201 +} // namespace MicroOcpp + +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/ChangeAvailability.h b/src/MicroOcpp/Operations/ChangeAvailability.h index 2830d791..08914e52 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.h +++ b/src/MicroOcpp/Operations/ChangeAvailability.h @@ -34,4 +34,39 @@ class ChangeAvailability : public Operation, public MemoryManaged { } //end namespace Ocpp16 } //end namespace MicroOcpp + +#if MO_ENABLE_V201 + +#include +#include + +namespace MicroOcpp { + +class AvailabilityService; + +namespace Ocpp201 { + +class ChangeAvailability : public Operation, public MemoryManaged { +private: + AvailabilityService& availabilityService; + ChangeAvailabilityStatus status = ChangeAvailabilityStatus::Rejected; + + const char *errorCode {nullptr}; +public: + ChangeAvailability(AvailabilityService& availabilityService); + + const char* getOperationType() override; + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} +}; + +} //end namespace Ocpp201 +} //end namespace MicroOcpp + +#endif //MO_ENABLE_V201 + #endif From 1231e7b0904a0dbe30434312dc13aa466da66a31 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:50:36 +0200 Subject: [PATCH 07/18] fix OCTT test cases --- src/MicroOcpp/Model/Reset/ResetService.cpp | 2 +- .../Model/Transactions/TransactionService.cpp | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index ebe7ca07..e7e211a3 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -125,7 +125,7 @@ ResetService::~ResetService() { ResetService::Evse::Evse(Context& context, ResetService& resetService, unsigned int evseId) : context(context), resetService(resetService), evseId(evseId) { auto varService = context.getModel().getVariableService(); - varService->declareVariable(ComponentId("EVSE", evseId), "AllowReset", true, MO_VARIABLE_FN, Variable::Mutability::ReadOnly); + varService->declareVariable(ComponentId("EVSE", evseId >= 1 ? evseId : -1), "AllowReset", true, MO_VARIABLE_FN, Variable::Mutability::ReadOnly); } void ResetService::Evse::loop() { diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index 8055fe1e..d3196d5a 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -55,6 +55,8 @@ std::unique_ptr TransactionService::Evse::allocateTransact tx->beginTimestamp = context.getModel().getClock().now(); + MO_DBG_DEBUG("allocated tx %u-%s", evseId, tx->transactionId); + return tx; } @@ -90,6 +92,18 @@ void TransactionService::Evse::loop() { transaction->stopTrigger = TransactionEventTriggerReason::EVConnectTimeout; transaction->stopReason = Ocpp201::Transaction::StopReason::Timeout; } + + if (transaction->active && + transaction->isDeauthorized && + !transaction->started && + (txService.isTxStartPoint(TxStartStopPoint::Authorized) || txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) || + txService.isTxStopPoint(TxStartStopPoint::Authorized) || txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed))) { + + MO_DBG_INFO("Session mngt: Deauthorized before start"); + transaction->active = false; + transaction->stopTrigger = TransactionEventTriggerReason::Deauthorized; + transaction->stopReason = Ocpp201::Transaction::StopReason::DeAuthorized; + } } } @@ -187,7 +201,7 @@ void TransactionService::Evse::loop() { if (transaction->remoteStartId >= 0) { triggerReason = TransactionEventTriggerReason::RemoteStart; } else { - triggerReason = TransactionEventTriggerReason::Authorized; + triggerReason = TransactionEventTriggerReason::CablePluggedIn; } } else if (txService.isTxStartPoint(TxStartStopPoint::Authorized) && transaction && transaction->isAuthorizationActive && transaction->isAuthorized) { @@ -447,6 +461,10 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate tx->isAuthorized = true; tx->notifyIdToken = true; }); + authorize->setOnAbortListener([this, tx] () { + MO_DBG_DEBUG("Authorize timeout (%s)", tx->idToken.get()); + tx->isDeauthorized = true; + }); authorize->setTimeout(20 * 1000); context.initiateRequest(std::move(authorize)); } else { @@ -620,7 +638,7 @@ TransactionService::TransactionService(Context& context) : stopTxOnEVSideDisconnectBool = varService->declareVariable("TxCtrlr", "StopTxOnEVSideDisconnect", true); evConnectionTimeOutInt = varService->declareVariable("TxCtrlr", "EVConnectionTimeOut", 30); sampledDataTxUpdatedInterval = varService->declareVariable("SampledDataCtrlr", "TxUpdatedInterval", 0); - sampledDataTxEndedInterval = varService->declareVariable("SampledDataCtrlr", "TxEndedInterval", 5); + sampledDataTxEndedInterval = varService->declareVariable("SampledDataCtrlr", "TxEndedInterval", 0); varService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false, MO_VARIABLE_VOLATILE, Variable::Mutability::ReadOnly); From 2944815d590f704a9fed179d9ecf2f6e86a9344d Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:20:35 +0200 Subject: [PATCH 08/18] add RemoteControlService --- CMakeLists.txt | 1 + src/MicroOcpp.cpp | 3 + src/MicroOcpp/Model/Model.cpp | 10 ++ src/MicroOcpp/Model/Model.h | 5 + .../Model/RemoteControl/RemoteControlDefs.h | 34 ++++ .../RemoteControl/RemoteControlService.cpp | 158 ++++++++++++++++++ .../RemoteControl/RemoteControlService.h | 63 +++++++ .../Model/Transactions/TransactionDefs.h | 5 - .../Model/Transactions/TransactionService.cpp | 52 ------ .../Model/Transactions/TransactionService.h | 5 - .../Operations/RequestStartTransaction.cpp | 10 +- .../Operations/RequestStartTransaction.h | 11 +- .../Operations/RequestStopTransaction.cpp | 6 +- .../Operations/RequestStopTransaction.h | 8 +- src/MicroOcpp/Operations/UnlockConnector.cpp | 82 +++++++++ src/MicroOcpp/Operations/UnlockConnector.h | 42 +++++ 16 files changed, 416 insertions(+), 79 deletions(-) create mode 100644 src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h create mode 100644 src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp create mode 100644 src/MicroOcpp/Model/RemoteControl/RemoteControlService.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ff836b7a..2dfebce5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ set(MO_SRC src/MicroOcpp/Model/Metering/MeterValuesV201.cpp src/MicroOcpp/Model/Metering/ReadingContext.cpp src/MicroOcpp/Model/Metering/SampledValue.cpp + src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp src/MicroOcpp/Model/Reservation/Reservation.cpp src/MicroOcpp/Model/Reservation/ReservationService.cpp src/MicroOcpp/Model/Reset/ResetService.cpp diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index b1d8d71a..619f1261 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -290,6 +291,8 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden new VariableService(*context, filesystem))); model.setTransactionService(std::unique_ptr( new TransactionService(*context))); + model.setRemoteControlService(std::unique_ptr( + new RemoteControlService(*context, MO_NUM_EVSE))); } else #endif { diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 95ef843e..67c37f88 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -144,6 +145,15 @@ MeteringService* Model::getMeteringService() const { return meteringService.get(); } +void Model::setRemoteControlService(std::unique_ptr rs) { + remoteControlService = std::move(rs); + capabilitiesUpdated = true; +} + +RemoteControlService *Model::getRemoteControlService() const { + return remoteControlService.get(); +} + void Model::setFirmwareService(std::unique_ptr fws) { firmwareService = std::move(fws); capabilitiesUpdated = true; diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 89a65245..ff31bdb2 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -40,6 +40,7 @@ class CertificateService; class AvailabilityService; class VariableService; class TransactionService; +class RemoteControlService; namespace Ocpp201 { class ResetService; @@ -78,6 +79,7 @@ class Model : public MemoryManaged { std::unique_ptr transactionService; std::unique_ptr resetServiceV201; std::unique_ptr meteringServiceV201; + std::unique_ptr remoteControlService; #endif Clock clock; @@ -160,6 +162,9 @@ class Model : public MemoryManaged { void setMeteringServiceV201(std::unique_ptr ms); Ocpp201::MeteringService *getMeteringServiceV201() const; + + void setRemoteControlService(std::unique_ptr rs); + RemoteControlService *getRemoteControlService() const; #endif Clock &getClock(); diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h new file mode 100644 index 00000000..2edc83e3 --- /dev/null +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h @@ -0,0 +1,34 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_UNLOCKCONNECTOR_H +#define MO_UNLOCKCONNECTOR_H + +#include + +#if MO_ENABLE_V201 + +#include + +#include + +typedef enum { + RequestStartStopStatus_Accepted, + RequestStartStopStatus_Rejected +} RequestStartStopStatus; + +#if MO_ENABLE_CONNECTOR_LOCK + +typedef enum { + UnlockStatus_Unlocked, + UnlockStatus_UnlockFailed, + UnlockStatus_OngoingAuthorizedTransaction, + UnlockStatus_UnknownConnector, + UnlockStatus_PENDING // unlock action not finished yet, result still unknown (MO will check again later) +} UnlockStatus; + +#endif // MO_ENABLE_CONNECTOR_LOCK + +#endif // MO_ENABLE_V201 +#endif // MO_UNLOCKCONNECTOR_H diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp new file mode 100644 index 00000000..8c51c34d --- /dev/null +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -0,0 +1,158 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MicroOcpp; + +RemoteControlServiceEvse::RemoteControlServiceEvse(Context& context, unsigned int evseId) : MemoryManaged("v201.RemoteControl.RemoteControlServiceEvse"), context(context), evseId(evseId) { + +} + +#if MO_ENABLE_CONNECTOR_LOCK +void RemoteControlServiceEvse::setOnUnlockConnector(UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData) { + this->onUnlockConnector = onUnlockConnector; + this->onUnlockConnectorUserData = userData; +} + +UnlockStatus RemoteControlServiceEvse::unlockConnector() { + + if (!onUnlockConnector) { + return UnlockStatus_UnlockFailed; + } + + if (auto txService = context.getModel().getTransactionService()) { + if (auto evse = txService->getEvse(evseId)) { + if (auto tx = evse->getTransaction()) { + if (tx->started && !tx->stopped && tx->isAuthorized) { + return UnlockStatus_OngoingAuthorizedTransaction; + } else { + evse->abortTransaction(Ocpp201::Transaction::StopReason::Other,Ocpp201::TransactionEventTriggerReason::UnlockCommand); + } + } + } + } + + auto status = onUnlockConnector(evseId, onUnlockConnectorUserData); + switch (status) { + case UnlockConnectorResult_Pending: + return UnlockStatus_PENDING; + case UnlockConnectorResult_Unlocked: + return UnlockStatus_Unlocked; + case UnlockConnectorResult_UnlockFailed: + return UnlockStatus_UnlockFailed; + } + + MO_DBG_ERR("invalid onUnlockConnector result code"); + return UnlockStatus_UnlockFailed; +} +#endif + +RemoteControlService::RemoteControlService(Context& context, size_t numEvses) : MemoryManaged("v201.RemoteControl.RemoteControlService"), context(context) { + + for (size_t i = 0; i < numEvses && i < MO_NUM_EVSE; i++) { + evses[i] = new RemoteControlServiceEvse(context, (unsigned int)i); + } + + context.getOperationRegistry().registerOperation("RequestStartTransaction", [this] () -> Operation* { + if (!this->context.getModel().getTransactionService()) { + return nullptr; //-> NotSupported + } + return new Ocpp201::RequestStartTransaction(*this);}); + context.getOperationRegistry().registerOperation("RequestStopTransaction", [this] () -> Operation* { + if (!this->context.getModel().getTransactionService()) { + return nullptr; //-> NotSupported + } + return new Ocpp201::RequestStopTransaction(*this);}); +#if MO_ENABLE_CONNECTOR_LOCK + context.getOperationRegistry().registerOperation("UnlockConnector", [this] () { + return new Ocpp201::UnlockConnector(*this);}); +#endif + context.getOperationRegistry().registerOperation("TriggerMessage", [&context] () { + return new Ocpp16::TriggerMessage(context);}); +} + +RemoteControlService::~RemoteControlService() { + for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { + delete evses[i]; + } +} + +RemoteControlServiceEvse *RemoteControlService::getEvse(unsigned int evseId) { + if (evseId >= MO_NUM_EVSE) { + MO_DBG_ERR("invalid arg"); + return nullptr; + } + return evses[evseId]; +} + +RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, std::shared_ptr& transactionOut) { + + TransactionService *txService = context.getModel().getTransactionService(); + if (!txService) { + MO_DBG_ERR("TxService uninitialized"); + return RequestStartStopStatus_Rejected; + } + + auto evse = txService->getEvse(evseId); + if (!evse) { + MO_DBG_ERR("EVSE not found"); + return RequestStartStopStatus_Rejected; + } + + transactionOut = evse->getTransaction(); + + if (!evse->beginAuthorization(idToken, false)) { + MO_DBG_INFO("EVSE still occupied with pending tx"); + return RequestStartStopStatus_Rejected; + } + + transactionOut = evse->getTransaction(); + if (!transactionOut) { + MO_DBG_ERR("internal error"); + return RequestStartStopStatus_Rejected; + } + + transactionOut->remoteStartId = remoteStartId; + transactionOut->notifyRemoteStartId = true; + + return RequestStartStopStatus_Accepted; +} + +RequestStartStopStatus RemoteControlService::requestStopTransaction(const char *transactionId) { + + TransactionService *txService = context.getModel().getTransactionService(); + if (!txService) { + MO_DBG_ERR("TxService uninitialized"); + return RequestStartStopStatus_Rejected; + } + + bool success = false; + + for (unsigned int evseId = 0; evseId < MO_NUM_EVSE; evseId++) { + if (auto evse = txService->getEvse(evseId)) { + if (evse->getTransaction() && !strcmp(evse->getTransaction()->transactionId, transactionId)) { + success = evse->abortTransaction(Ocpp201::Transaction::StopReason::Remote, Ocpp201::TransactionEventTriggerReason::RemoteStop); + break; + } + } + } + + return success ? + RequestStartStopStatus_Accepted : + RequestStartStopStatus_Rejected; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h new file mode 100644 index 00000000..8496fcba --- /dev/null +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -0,0 +1,63 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_REMOTECONTROLSERVICE_H +#define MO_REMOTECONTROLSERVICE_H + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include +#include + +namespace MicroOcpp { + +class Context; + +class RemoteControlServiceEvse : public MemoryManaged { +private: + Context& context; + const unsigned int evseId; + +#if MO_ENABLE_CONNECTOR_LOCK + UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *user) = nullptr; + void *onUnlockConnectorUserData = nullptr; +#endif + +public: + RemoteControlServiceEvse(Context& context, unsigned int evseId); + +#if MO_ENABLE_CONNECTOR_LOCK + void setOnUnlockConnector(UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData); + + UnlockStatus unlockConnector(); +#endif + +}; + +class RemoteControlService : public MemoryManaged { +private: + Context& context; + + RemoteControlServiceEvse* evses [MO_NUM_EVSE] = {nullptr}; + +public: + RemoteControlService(Context& context, size_t numEvses); + ~RemoteControlService(); + + RemoteControlServiceEvse *getEvse(unsigned int evseId); + + RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, std::shared_ptr& transactionOut); //ChargingProfile, GroupIdToken not supported yet + + RequestStartStopStatus requestStopTransaction(const char *transactionId); +}; + +} // namespace MicroOcpp + +#endif // MO_ENABLE_V201 + +#endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionDefs.h b/src/MicroOcpp/Model/Transactions/TransactionDefs.h index 1a871b4b..62ef657a 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionDefs.h +++ b/src/MicroOcpp/Model/Transactions/TransactionDefs.h @@ -11,10 +11,5 @@ #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 d3196d5a..dac88ffb 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -669,11 +669,6 @@ TransactionService::TransactionService(Context& context) : 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() { @@ -714,51 +709,4 @@ TransactionService::Evse *TransactionService::getEvse(unsigned int evseId) { } } -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 70ddc86b..ca9704b7 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService.h @@ -14,7 +14,6 @@ #if MO_ENABLE_V201 #include -#include #include #include @@ -106,10 +105,6 @@ class TransactionService : public MemoryManaged { 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/RequestStartTransaction.cpp b/src/MicroOcpp/Operations/RequestStartTransaction.cpp index 9b35783b..72e1a761 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStartTransaction.cpp @@ -7,13 +7,13 @@ #if MO_ENABLE_V201 #include -#include +#include #include using MicroOcpp::Ocpp201::RequestStartTransaction; using MicroOcpp::JsonDoc; -RequestStartTransaction::RequestStartTransaction(TransactionService& txService) : MemoryManaged("v201.Operation.", "RequestStartTransaction"), txService(txService) { +RequestStartTransaction::RequestStartTransaction(RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStartTransaction"), rcService(rcService) { } @@ -43,7 +43,7 @@ void RequestStartTransaction::processReq(JsonObject payload) { return; } - status = txService.requestStartTransaction(evseId, remoteStartId, idToken, transactionId); + status = rcService.requestStartTransaction(evseId, remoteStartId, idToken, transaction); } std::unique_ptr RequestStartTransaction::createConf(){ @@ -67,8 +67,8 @@ std::unique_ptr RequestStartTransaction::createConf(){ payload["status"] = statusCstr; - if (*transactionId) { - payload["transactionId"] = (const char*)transactionId; //force zero-copy mode + if (transaction) { + payload["transactionId"] = (const char*)transaction->transactionId; //force zero-copy mode } return doc; diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.h b/src/MicroOcpp/Operations/RequestStartTransaction.h index d28ade11..dad7d5ae 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.h +++ b/src/MicroOcpp/Operations/RequestStartTransaction.h @@ -11,24 +11,25 @@ #include #include -#include +#include +#include namespace MicroOcpp { -class TransactionService; +class RemoteControlService; namespace Ocpp201 { class RequestStartTransaction : public Operation, public MemoryManaged { private: - TransactionService& txService; + RemoteControlService& rcService; RequestStartStopStatus status; - char transactionId [MO_TXID_LEN_MAX + 1] = {'\0'}; + std::shared_ptr transaction; const char *errorCode = nullptr; public: - RequestStartTransaction(TransactionService& txService); + RequestStartTransaction(RemoteControlService& rcService); const char* getOperationType() override; diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.cpp b/src/MicroOcpp/Operations/RequestStopTransaction.cpp index 9752895a..a316188e 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStopTransaction.cpp @@ -7,13 +7,13 @@ #if MO_ENABLE_V201 #include -#include +#include #include using MicroOcpp::Ocpp201::RequestStopTransaction; using MicroOcpp::JsonDoc; -RequestStopTransaction::RequestStopTransaction(TransactionService& txService) : MemoryManaged("v201.Operation.", "RequestStopTransaction"), txService(txService) { +RequestStopTransaction::RequestStopTransaction(RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStopTransaction"), rcService(rcService) { } @@ -30,7 +30,7 @@ void RequestStopTransaction::processReq(JsonObject payload) { return; } - status = txService.requestStopTransaction(payload["transactionId"].as()); + status = rcService.requestStopTransaction(payload["transactionId"].as()); } std::unique_ptr RequestStopTransaction::createConf(){ diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.h b/src/MicroOcpp/Operations/RequestStopTransaction.h index ba8f545c..30664b67 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.h +++ b/src/MicroOcpp/Operations/RequestStopTransaction.h @@ -11,23 +11,23 @@ #include #include -#include +#include namespace MicroOcpp { -class TransactionService; +class RemoteControlService; namespace Ocpp201 { class RequestStopTransaction : public Operation, public MemoryManaged { private: - TransactionService& txService; + RemoteControlService& rcService; RequestStartStopStatus status; const char *errorCode = nullptr; public: - RequestStopTransaction(TransactionService& txService); + RequestStopTransaction(RemoteControlService& rcService); const char* getOperationType() override; diff --git a/src/MicroOcpp/Operations/UnlockConnector.cpp b/src/MicroOcpp/Operations/UnlockConnector.cpp index 87cc03f6..66ef5aef 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.cpp +++ b/src/MicroOcpp/Operations/UnlockConnector.cpp @@ -78,3 +78,85 @@ std::unique_ptr UnlockConnector::createConf() { payload["status"] = status; return doc; } + + +#if MO_ENABLE_V201 + +#include + +namespace MicroOcpp { +namespace Ocpp201 { + +UnlockConnector::UnlockConnector(RemoteControlService& rcService) : MemoryManaged("v201.Operation.UnlockConnector"), rcService(rcService) { + +} + +const char* UnlockConnector::getOperationType(){ + return "UnlockConnector"; +} + +void UnlockConnector::processReq(JsonObject payload) { + + int evseId = payload["evseId"] | -1; + int connectorId = payload["connectorId"] | -1; + + if (evseId < 1 || evseId >= MO_NUM_EVSE || connectorId < 1) { + errorCode = "PropertyConstraintViolation"; + return; + } + + if (connectorId != 1) { + status = UnlockStatus_UnknownConnector; + return; + } + + rcEvse = rcService.getEvse(evseId); + if (!rcEvse) { + status = UnlockStatus_UnlockFailed; + return; + } + + status = rcEvse->unlockConnector(); + timerStart = mocpp_tick_ms(); +} + +std::unique_ptr UnlockConnector::createConf() { + + if (rcEvse && status == UnlockStatus_PENDING && mocpp_tick_ms() - timerStart < MO_UNLOCK_TIMEOUT) { + status = rcEvse->unlockConnector(); + + if (status == UnlockStatus_PENDING) { + return nullptr; //no result yet - delay confirmation response + } + } + + const char *statusStr = ""; + switch (status) { + case UnlockStatus_Unlocked: + statusStr = "Unlocked"; + break; + case UnlockStatus_UnlockFailed: + statusStr = "UnlockFailed"; + break; + case UnlockStatus_OngoingAuthorizedTransaction: + statusStr = "OngoingAuthorizedTransaction"; + break; + case UnlockStatus_UnknownConnector: + statusStr = "UnknownConnector"; + break; + case UnlockStatus_PENDING: + MO_DBG_ERR("UnlockConnector timeout"); + statusStr = "UnlockFailed"; + break; + } + + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); + JsonObject payload = doc->to(); + payload["status"] = statusStr; + return doc; +} + +} // namespace Ocpp201 +} // namespace MicroOcpp + +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/UnlockConnector.h b/src/MicroOcpp/Operations/UnlockConnector.h index 44b41bb9..518653cd 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.h +++ b/src/MicroOcpp/Operations/UnlockConnector.h @@ -5,6 +5,8 @@ #ifndef UNLOCKCONNECTOR_H #define UNLOCKCONNECTOR_H +#include + #include #include #include @@ -40,4 +42,44 @@ class UnlockConnector : public Operation, public MemoryManaged { } //end namespace Ocpp16 } //end namespace MicroOcpp + +#if MO_ENABLE_V201 +#if MO_ENABLE_CONNECTOR_LOCK + +#include + +namespace MicroOcpp { + +class RemoteControlService; +class RemoteControlServiceEvse; + +namespace Ocpp201 { + +class UnlockConnector : public Operation, public MemoryManaged { +private: + RemoteControlService& rcService; + RemoteControlServiceEvse *rcEvse = nullptr; + + UnlockStatus status; + unsigned long timerStart = 0; //for timeout + + const char *errorCode = nullptr; +public: + UnlockConnector(RemoteControlService& rcService); + + const char* getOperationType() override; + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} +}; + +} //end namespace Ocpp201 +} //end namespace MicroOcpp + +#endif //MO_ENABLE_CONNECTOR_LOCK +#endif //MO_ENABLE_V201 + #endif From c3b6b4674c1060b46979c45b5e84804179754ff5 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:28:51 +0200 Subject: [PATCH 09/18] reset TxEndedMeasurands factory default --- src/MicroOcpp/Model/Metering/MeterValuesV201.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp b/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp index 560b12ac..e6949703 100644 --- a/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp @@ -295,7 +295,7 @@ MeteringService::MeteringService(Model& model, size_t numEvses) { //define factory defaults varService->declareVariable("SampledDataCtrlr", "TxStartedMeasurands", ""); varService->declareVariable("SampledDataCtrlr", "TxUpdatedMeasurands", ""); - varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", "Energy.Active.Import.Register"); + varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", ""); varService->declareVariable("AlignedDataCtrlr", "AlignedDataMeasurands", ""); std::function validateSelectString = [this] (const char *csl) { From ca54084a01eebb0f5e8a4e027c374fa72b773f71 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:35:56 +0200 Subject: [PATCH 10/18] add AuthorizeRemoteStart --- src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp | 6 +++++- src/MicroOcpp/Model/RemoteControl/RemoteControlService.h | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index 8c51c34d..49fbe846 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,9 @@ RemoteControlService::RemoteControlService(Context& context, size_t numEvses) : evses[i] = new RemoteControlServiceEvse(context, (unsigned int)i); } + auto varService = context.getModel().getVariableService(); + authorizeRemoteStart = varService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false); + context.getOperationRegistry().registerOperation("RequestStartTransaction", [this] () -> Operation* { if (!this->context.getModel().getTransactionService()) { return nullptr; //-> NotSupported @@ -114,7 +118,7 @@ RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned in transactionOut = evse->getTransaction(); - if (!evse->beginAuthorization(idToken, false)) { + if (!evse->beginAuthorization(idToken, authorizeRemoteStart->getBool())) { MO_DBG_INFO("EVSE still occupied with pending tx"); return RequestStartStopStatus_Rejected; } diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h index 8496fcba..7baf6eae 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -17,6 +17,7 @@ namespace MicroOcpp { class Context; +class Variable; class RemoteControlServiceEvse : public MemoryManaged { private: @@ -42,9 +43,10 @@ class RemoteControlServiceEvse : public MemoryManaged { class RemoteControlService : public MemoryManaged { private: Context& context; - RemoteControlServiceEvse* evses [MO_NUM_EVSE] = {nullptr}; + Variable *authorizeRemoteStart = nullptr; + public: RemoteControlService(Context& context, size_t numEvses); ~RemoteControlService(); From 6d6c046d8bfc1933d3c1a1c22d1df54920ce681c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:46:31 +0200 Subject: [PATCH 11/18] fix sending empty MeterValuePeriodic if interval set, but no measurands --- .../Model/Transactions/TransactionService.cpp | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index dac88ffb..c620445e 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -263,6 +263,31 @@ void TransactionService::Evse::loop() { chargingState = TransactionEventData::ChargingState::Charging; } + //General Metering behavior. There is another section for TxStarted, Updated and TxEnded MeterValues + std::unique_ptr mvTxUpdated; + + if (transaction) { + + if (txService.sampledDataTxUpdatedInterval && txService.sampledDataTxUpdatedInterval->getInt() > 0 && mocpp_tick_ms() - transaction->lastSampleTimeTxUpdated >= (unsigned long)txService.sampledDataTxUpdatedInterval->getInt() * 1000UL) { + transaction->lastSampleTimeTxUpdated = mocpp_tick_ms(); + auto meteringService = context.getModel().getMeteringServiceV201(); + auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; + mvTxUpdated = meteringEvse ? meteringEvse->takeTxUpdatedMeterValue() : nullptr; + } + + if (transaction->started && !transaction->stopped && + txService.sampledDataTxEndedInterval && txService.sampledDataTxEndedInterval->getInt() > 0 && + mocpp_tick_ms() - transaction->lastSampleTimeTxEnded >= (unsigned long)txService.sampledDataTxEndedInterval->getInt() * 1000UL) { + transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); + auto meteringService = context.getModel().getMeteringServiceV201(); + auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; + auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_SamplePeriodic) : nullptr; + if (mvTxEnded) { + transaction->addSampledDataTxEnded(std::move(mvTxEnded)); + } + } + } + if (transaction) { // update tx? @@ -297,7 +322,7 @@ void TransactionService::Evse::loop() { transaction->trackAuthorized = false; txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::StopAuthorized; - } else if (txService.sampledDataTxUpdatedInterval && txService.sampledDataTxUpdatedInterval->getInt() > 0 && mocpp_tick_ms() - transaction->lastSampleTimeTxUpdated >= (unsigned long)txService.sampledDataTxUpdatedInterval->getInt() * 1000UL) { + } else if (mvTxUpdated) { txUpdateCondition = true; triggerReason = TransactionEventTriggerReason::MeterValuePeriodic; } else if (evReadyInput && evReadyInput() && !transaction->trackPowerPathClosed) { @@ -320,22 +345,6 @@ void TransactionService::Evse::loop() { } } - //General Metering behavior. There is another section for TxStarted, Updated and TxEnded MeterValues - if (transaction) { - - if (transaction->started && !transaction->stopped && - txService.sampledDataTxEndedInterval && txService.sampledDataTxEndedInterval->getInt() > 0 && - mocpp_tick_ms() - transaction->lastSampleTimeTxEnded >= (unsigned long)txService.sampledDataTxEndedInterval->getInt() * 1000UL) { - transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); - auto meteringService = context.getModel().getMeteringServiceV201(); - auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; - auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_SamplePeriodic) : nullptr; - if (mvTxEnded) { - transaction->addSampledDataTxEnded(std::move(mvTxEnded)); - } - } - } - if (txEvent) { txEvent->timestamp = context.getModel().getClock().now(); if (transaction->notifyChargingState) { @@ -363,16 +372,6 @@ void TransactionService::Evse::loop() { } transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); transaction->lastSampleTimeTxUpdated = mocpp_tick_ms(); - } else if (txEvent->eventType == TransactionEventData::Type::Updated) { - if (txService.sampledDataTxUpdatedInterval && txService.sampledDataTxUpdatedInterval->getInt() > 0 && mocpp_tick_ms() - transaction->lastSampleTimeTxUpdated >= (unsigned long)txService.sampledDataTxUpdatedInterval->getInt() * 1000UL) { - transaction->lastSampleTimeTxUpdated = mocpp_tick_ms(); - auto meteringService = context.getModel().getMeteringServiceV201(); - auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; - auto mv = meteringEvse ? meteringEvse->takeTxUpdatedMeterValue() : nullptr; - if (mv) { - txEvent->meterValue.push_back(std::move(mv)); - } - } } else if (txEvent->eventType == TransactionEventData::Type::Ended) { auto meteringService = context.getModel().getMeteringServiceV201(); auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; @@ -382,6 +381,9 @@ void TransactionService::Evse::loop() { } transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); } + if (mvTxUpdated) { + txEvent->meterValue.push_back(std::move(mvTxUpdated)); + } if (transaction->notifyStopIdToken && transaction->stopIdToken) { txEvent->idToken = std::unique_ptr(new IdToken(*transaction->stopIdToken.get(), getMemoryTag())); From 7b6ba344ebae057874301b9e1c610f4310f1d970 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:19:59 +0200 Subject: [PATCH 12/18] cumulative fixes --- src/MicroOcpp.cpp | 8 +- .../Availability/AvailabilityService.cpp | 119 +++++++----------- .../Model/Availability/AvailabilityService.h | 19 ++- src/MicroOcpp/Model/ConnectorBase/EvseId.h | 10 +- .../Model/Metering/MeterValuesV201.cpp | 6 +- .../Model/Metering/MeterValuesV201.h | 2 +- .../RemoteControl/RemoteControlService.cpp | 8 +- .../RemoteControl/RemoteControlService.h | 2 +- src/MicroOcpp/Model/Reset/ResetService.cpp | 8 +- .../Model/Transactions/TransactionService.cpp | 6 +- .../Operations/ChangeAvailability.cpp | 29 +++-- .../Operations/RequestStartTransaction.cpp | 2 +- src/MicroOcpp/Operations/Reset.cpp | 2 +- src/MicroOcpp/Operations/UnlockConnector.cpp | 2 +- 14 files changed, 98 insertions(+), 125 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 619f1261..de8e6ae8 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -286,13 +286,13 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden #if MO_ENABLE_V201 if (version.major == 2) { model.setAvailabilityService(std::unique_ptr( - new AvailabilityService(*context, MO_NUM_EVSE))); + new AvailabilityService(*context, MO_NUM_EVSEID))); model.setVariableService(std::unique_ptr( new VariableService(*context, filesystem))); model.setTransactionService(std::unique_ptr( new TransactionService(*context))); model.setRemoteControlService(std::unique_ptr( - new RemoteControlService(*context, MO_NUM_EVSE))); + new RemoteControlService(*context, MO_NUM_EVSEID))); } else #endif { @@ -932,7 +932,7 @@ void addMeterValueInput(std::function valueInput, const char *measuran auto& model = context->getModel(); if (!model.getMeteringServiceV201()) { model.setMeteringServiceV201(std::unique_ptr( - new Ocpp201::MeteringService(context->getModel(), MO_NUM_EVSE))); + new Ocpp201::MeteringService(context->getModel(), MO_NUM_EVSEID))); } if (auto mEvse = model.getMeteringServiceV201()->getEvse(connectorId)) { @@ -1080,7 +1080,7 @@ bool isOperative(unsigned int connectorId) { MO_DBG_ERR("could not find connector"); return true; //assume "true" as default state } - return chargePoint->isOperative() && connector->isOperative(); + return chargePoint->isAvailable() && connector->isAvailable(); } } #endif diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index c79691c8..b1e59419 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -17,10 +17,8 @@ using namespace MicroOcpp; -AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, unsigned int evseId) : MemoryManaged("v201.Availability.AvailabilityServiceEvse"), context(context), evseId(evseId) { +AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availabilityService, unsigned int evseId) : MemoryManaged("v201.Availability.AvailabilityServiceEvse"), context(context), availabilityService(availabilityService), evseId(evseId) { - snprintf(availabilityBoolKey, sizeof(availabilityBoolKey), MO_CONFIG_EXT_PREFIX "AVAIL_CONN_%d", evseId); - availabilityBool = declareConfiguration(availabilityBoolKey, true, MO_KEYVALUE_FN, false, false, false); } void AvailabilityServiceEvse::loop() { @@ -51,23 +49,9 @@ void AvailabilityServiceEvse::setOccupiedInput(std::function occupiedInp ChargePointStatus AvailabilityServiceEvse::getStatus() { ChargePointStatus res = ChargePointStatus_UNDEFINED; - /* - * Handle special case: This is the Connector for the whole CP (i.e. evseId=0) --> only states Available, Unavailable, Faulted are possible - */ - if (evseId == 0) { - if (isFaulted()) { - res = ChargePointStatus_Faulted; - } else if (!isOperative()) { - res = ChargePointStatus_Unavailable; - } else { - res = ChargePointStatus_Available; - } - return res; - } - if (isFaulted()) { res = ChargePointStatus_Faulted; - } else if (!isOperative()) { + } else if (!isAvailable()) { res = ChargePointStatus_Unavailable; } #if MO_ENABLE_RESERVATION @@ -85,38 +69,48 @@ ChargePointStatus AvailabilityServiceEvse::getStatus() { return res; } -void AvailabilityServiceEvse::setInoperative(void *requesterId) { +void AvailabilityServiceEvse::setUnavailable(void *requesterId) { for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { - if (!inoperativeRequesters[i]) { - inoperativeRequesters[i] = requesterId; + if (!unavailableRequesters[i]) { + unavailableRequesters[i] = requesterId; return; } } - MO_DBG_ERR("exceeded max. inoperative requesters"); + MO_DBG_ERR("exceeded max. unavailable requesters"); } -void AvailabilityServiceEvse::resetInoperative(void *requesterId) { +void AvailabilityServiceEvse::setAvailable(void *requesterId) { for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { - if (inoperativeRequesters[i] == requesterId) { - inoperativeRequesters[i] = nullptr; + if (unavailableRequesters[i] == requesterId) { + unavailableRequesters[i] = nullptr; return; } } - MO_DBG_ERR("could not find inoperative requester"); + MO_DBG_ERR("could not find unavailable requester"); } ChangeAvailabilityStatus AvailabilityServiceEvse::changeAvailability(bool operative) { if (operative) { - resetInoperative(this); + setAvailable(this); } else { - setInoperative(this); + setUnavailable(this); } - if (isOperative() && !operative) { - return ChangeAvailabilityStatus::Scheduled; - } else { - return ChangeAvailabilityStatus::Accepted; + if (!operative) { + if (isAvailable()) { + return ChangeAvailabilityStatus::Scheduled; + } + + if (evseId == 0) { + for (unsigned int id = 1; id < MO_NUM_EVSEID; id++) { + if (availabilityService.getEvse(id) && availabilityService.getEvse(id)->isAvailable()) { + return ChangeAvailabilityStatus::Scheduled; + } + } + } } + + return ChangeAvailabilityStatus::Accepted; } void AvailabilityServiceEvse::setFaulted(void *requesterId) { @@ -139,37 +133,26 @@ void AvailabilityServiceEvse::resetFaulted(void *requesterId) { MO_DBG_ERR("could not find faulted requester"); } -bool AvailabilityServiceEvse::isOperative() { - if (availabilityBool && !availabilityBool->getBool()) { - return false; - } +bool AvailabilityServiceEvse::isAvailable() { auto txService = context.getModel().getTransactionService(); - - if (evseId == 0) { - for (unsigned int id = 1; id < MO_NUM_EVSE; id++) { - TransactionService::Evse *evse; - if (txService && (evse = txService->getEvse(id))) { - if (evse->getTransaction() && - evse->getTransaction()->started && - !evse->getTransaction()->stopped) { - return true; - } - } + auto txEvse = txService ? txService->getEvse(evseId) : nullptr; + if (txEvse) { + if (txEvse->getTransaction() && + txEvse->getTransaction()->started && + !txEvse->getTransaction()->stopped) { + return true; } - } else { - TransactionService::Evse *evse; - if (txService && (evse = txService->getEvse(evseId))) { - if (evse->getTransaction() && - evse->getTransaction()->started && - !evse->getTransaction()->stopped) { - return true; - } + } + + if (evseId > 0) { + if (availabilityService.getEvse(0) && !availabilityService.getEvse(0)->isAvailable()) { + return false; } } for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { - if (inoperativeRequesters[i]) { + if (unavailableRequesters[i]) { return false; } } @@ -187,8 +170,8 @@ bool AvailabilityServiceEvse::isFaulted() { AvailabilityService::AvailabilityService(Context& context, size_t numEvses) : MemoryManaged("v201.Availability.AvailabilityService"), context(context) { - for (size_t i = 0; i < numEvses && i < MO_NUM_EVSE; i++) { - evses[i] = new AvailabilityServiceEvse(context, (unsigned int)i); + for (size_t i = 0; i < numEvses && i < MO_NUM_EVSEID; i++) { + evses[i] = new AvailabilityServiceEvse(context, *this, (unsigned int)i); } context.getOperationRegistry().registerOperation("StatusNotification", [&context] () { @@ -198,37 +181,23 @@ AvailabilityService::AvailabilityService(Context& context, size_t numEvses) : Me } AvailabilityService::~AvailabilityService() { - for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { + for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { delete evses[i]; } } void AvailabilityService::loop() { - for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { + for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { evses[i]->loop(); } } AvailabilityServiceEvse *AvailabilityService::getEvse(unsigned int evseId) { - if (evseId >= MO_NUM_EVSE) { + if (evseId >= MO_NUM_EVSEID) { MO_DBG_ERR("invalid arg"); return nullptr; } return evses[evseId]; } -ChangeAvailabilityStatus AvailabilityService::changeAvailability(bool operative) { - bool scheduled = false; - - for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { - scheduled |= evses[i]->changeAvailability(operative) == ChangeAvailabilityStatus::Scheduled; - } - - if (scheduled) { - return ChangeAvailabilityStatus::Scheduled; - } else { - return ChangeAvailabilityStatus::Accepted; - } -} - #endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h index 0f13ee0a..d8b74d61 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -20,7 +20,6 @@ #include #include #include -#include #include #ifndef MO_INOPERATIVE_REQUESTERS_MAX @@ -34,23 +33,23 @@ namespace MicroOcpp { class Context; +class AvailabilityService; class AvailabilityServiceEvse : public MemoryManaged { private: Context& context; + AvailabilityService& availabilityService; const unsigned int evseId; std::function connectorPluggedInput; std::function occupiedInput; //instead of Available, go into Occupied - std::shared_ptr availabilityBool; - char availabilityBoolKey [sizeof(MO_CONFIG_EXT_PREFIX "AVAIL_CONN_xxxx") + 1]; - void *inoperativeRequesters [MO_INOPERATIVE_REQUESTERS_MAX] = {nullptr}; + void *unavailableRequesters [MO_INOPERATIVE_REQUESTERS_MAX] = {nullptr}; void *faultedRequesters [MO_FAULTED_REQUESTERS_MAX] = {nullptr}; ChargePointStatus reportedStatus = ChargePointStatus_UNDEFINED; public: - AvailabilityServiceEvse(Context& context, unsigned int evseId); + AvailabilityServiceEvse(Context& context, AvailabilityService& availabilityService, unsigned int evseId); void loop(); @@ -59,15 +58,15 @@ class AvailabilityServiceEvse : public MemoryManaged { ChargePointStatus getStatus(); - void setInoperative(void *requesterId); - void resetInoperative(void *requesterId); + void setUnavailable(void *requesterId); + void setAvailable(void *requesterId); ChangeAvailabilityStatus changeAvailability(bool operative); void setFaulted(void *requesterId); void resetFaulted(void *requesterId); - bool isOperative(); + bool isAvailable(); bool isFaulted(); }; @@ -75,7 +74,7 @@ class AvailabilityService : public MemoryManaged { private: Context& context; - AvailabilityServiceEvse* evses [MO_NUM_EVSE] = {nullptr}; + AvailabilityServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; public: AvailabilityService(Context& context, size_t numEvses); @@ -84,8 +83,6 @@ class AvailabilityService : public MemoryManaged { void loop(); AvailabilityServiceEvse *getEvse(unsigned int evseId); - - ChangeAvailabilityStatus changeAvailability(bool operative); }; } // namespace MicroOcpp diff --git a/src/MicroOcpp/Model/ConnectorBase/EvseId.h b/src/MicroOcpp/Model/ConnectorBase/EvseId.h index 14a3c661..6ca5295a 100644 --- a/src/MicroOcpp/Model/ConnectorBase/EvseId.h +++ b/src/MicroOcpp/Model/ConnectorBase/EvseId.h @@ -9,14 +9,14 @@ #if MO_ENABLE_V201 -// number of EVSEs. Defaults to MO_NUMCONNECTORS if defined, otherwise to 2 -#ifndef MO_NUM_EVSE +// number of EVSE IDs (including 0). Defaults to MO_NUMCONNECTORS if defined, otherwise to 2 +#ifndef MO_NUM_EVSEID #if defined(MO_NUMCONNECTORS) -#define MO_NUM_EVSE MO_NUMCONNECTORS +#define MO_NUM_EVSEID MO_NUMCONNECTORS #else -#define MO_NUM_EVSE 2 +#define MO_NUM_EVSEID 2 #endif -#endif // MO_NUM_EVSE +#endif // MO_NUM_EVSEID namespace MicroOcpp { diff --git a/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp b/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp index e6949703..fc856204 100644 --- a/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp @@ -312,7 +312,7 @@ MeteringService::MeteringService(Model& model, size_t numEvses) { r++; } bool found = false; - for (size_t evseId = 0; evseId < MO_NUM_EVSE && evses[evseId]; evseId++) { + for (size_t evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) { if (evses[evseId]->existsMeasurand(l, (size_t) (r - l))) { found = true; break; @@ -333,13 +333,13 @@ MeteringService::MeteringService(Model& model, size_t numEvses) { varService->registerValidator("SampledDataCtrlr", "TxEndedMeasurands", validateSelectString); varService->registerValidator("AlignedDataCtrlr", "AlignedDataMeasurands", validateSelectString); - for (size_t evseId = 0; evseId < std::min(numEvses, (size_t)MO_NUM_EVSE); evseId++) { + for (size_t evseId = 0; evseId < std::min(numEvses, (size_t)MO_NUM_EVSEID); evseId++) { evses[evseId] = new MeteringServiceEvse(model, evseId); } } MeteringService::~MeteringService() { - for (size_t evseId = 0; evseId < MO_NUM_EVSE && evses[evseId]; evseId++) { + for (size_t evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) { delete evses[evseId]; } } diff --git a/src/MicroOcpp/Model/Metering/MeterValuesV201.h b/src/MicroOcpp/Model/Metering/MeterValuesV201.h index e272b4f1..f35ec146 100644 --- a/src/MicroOcpp/Model/Metering/MeterValuesV201.h +++ b/src/MicroOcpp/Model/Metering/MeterValuesV201.h @@ -137,7 +137,7 @@ class MeteringServiceEvse : public MemoryManaged { class MeteringService : public MemoryManaged { private: - MeteringServiceEvse* evses [MO_NUM_EVSE] = {nullptr}; + MeteringServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; public: MeteringService(Model& model, size_t numEvses); ~MeteringService(); diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index 49fbe846..7d49d2bc 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -63,7 +63,7 @@ UnlockStatus RemoteControlServiceEvse::unlockConnector() { RemoteControlService::RemoteControlService(Context& context, size_t numEvses) : MemoryManaged("v201.RemoteControl.RemoteControlService"), context(context) { - for (size_t i = 0; i < numEvses && i < MO_NUM_EVSE; i++) { + for (size_t i = 0; i < numEvses && i < MO_NUM_EVSEID; i++) { evses[i] = new RemoteControlServiceEvse(context, (unsigned int)i); } @@ -89,13 +89,13 @@ RemoteControlService::RemoteControlService(Context& context, size_t numEvses) : } RemoteControlService::~RemoteControlService() { - for (size_t i = 0; i < MO_NUM_EVSE && evses[i]; i++) { + for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { delete evses[i]; } } RemoteControlServiceEvse *RemoteControlService::getEvse(unsigned int evseId) { - if (evseId >= MO_NUM_EVSE) { + if (evseId >= MO_NUM_EVSEID) { MO_DBG_ERR("invalid arg"); return nullptr; } @@ -145,7 +145,7 @@ RequestStartStopStatus RemoteControlService::requestStopTransaction(const char * bool success = false; - for (unsigned int evseId = 0; evseId < MO_NUM_EVSE; evseId++) { + for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID; evseId++) { if (auto evse = txService->getEvse(evseId)) { if (evse->getTransaction() && !strcmp(evse->getTransaction()->transactionId, transactionId)) { success = evse->abortTransaction(Ocpp201::Transaction::StopReason::Remote, Ocpp201::TransactionEventTriggerReason::RemoteStop); diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h index 7baf6eae..a19adcc7 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -43,7 +43,7 @@ class RemoteControlServiceEvse : public MemoryManaged { class RemoteControlService : public MemoryManaged { private: Context& context; - RemoteControlServiceEvse* evses [MO_NUM_EVSE] = {nullptr}; + RemoteControlServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; Variable *authorizeRemoteStart = nullptr; diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index e7e211a3..e20c8b30 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -132,7 +132,7 @@ void ResetService::Evse::loop() { if (outstandingResetRetries && awaitTxStop) { - for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSE : evseId + 1); eId++) { + for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) { //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds > 0 auto txService = context.getModel().getTransactionService(); @@ -202,7 +202,7 @@ ResetService::Evse *ResetService::getOrCreateEvse(unsigned int evseId) { return evse; } - if (evseId >= MO_NUM_EVSE) { + if (evseId >= MO_NUM_EVSEID) { MO_DBG_ERR("evseId out of bound"); return nullptr; } @@ -266,7 +266,7 @@ ResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) { } //Check if EVSEs are ready for Reset - for (unsigned int eId = evseId; eId < (evseId == 0 ? MO_NUM_EVSE : evseId + 1); eId++) { + for (unsigned int eId = evseId; eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) { //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds if (auto it = getEvse(eId)) { @@ -294,7 +294,7 @@ ResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) { bool scheduled = false; //Tx-related behavior: if immediate Reset, stop txs; otherwise schedule Reset - for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSE : evseId + 1); eId++) { + for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) { //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds > 0 auto txService = context.getModel().getTransactionService(); diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index c620445e..7f74e7be 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -661,9 +661,9 @@ TransactionService::TransactionService(Context& context) : varService->registerValidator("SampledDataCtrlr", "TxUpdatedInterval", validateUnsignedInt); varService->registerValidator("SampledDataCtrlr", "TxEndedInterval", validateUnsignedInt); - evses.reserve(MO_NUM_EVSE); + evses.reserve(MO_NUM_EVSEID); - for (unsigned int evseId = 0; evseId < MO_NUM_EVSE; evseId++) { + for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID; evseId++) { evses.emplace_back(context, *this, evseId); } @@ -690,7 +690,7 @@ void TransactionService::loop() { if (auto& tx0 = evses[0].getTransaction()) { //pending tx on evseId 0 if (tx0->active) { - for (unsigned int evseId = 1; evseId < MO_NUM_EVSE; evseId++) { + for (unsigned int evseId = 1; evseId < MO_NUM_EVSEID; evseId++) { if (!evses[evseId].getTransaction() && (!evses[evseId].connectorPluggedInput || evses[evseId].connectorPluggedInput())) { MO_DBG_INFO("assign tx to evse %u", evseId); diff --git a/src/MicroOcpp/Operations/ChangeAvailability.cpp b/src/MicroOcpp/Operations/ChangeAvailability.cpp index d37d2a15..7377657c 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.cpp +++ b/src/MicroOcpp/Operations/ChangeAvailability.cpp @@ -97,14 +97,25 @@ const char* ChangeAvailability::getOperationType(){ } void ChangeAvailability::processReq(JsonObject payload) { - int evseIdRaw = payload["evse"]["id"] | 0; - if (evseIdRaw < 0) { - errorCode = "FormationViolation"; - return; + + unsigned int evseId = 0; + + if (payload.containsKey("evse")) { + int evseIdRaw = payload["evse"]["id"] | -1; + if (evseIdRaw < 0) { + errorCode = "FormationViolation"; + return; + } + evseId = (unsigned int)evseIdRaw; + + if ((payload["evse"]["connectorId"] | 1) != 1) { + errorCode = "PropertyConstraintViolation"; + return; + } } - unsigned int evseId = (unsigned int)evseIdRaw; - if (!availabilityService.getEvse(evseId)) { + auto availabilityEvse = availabilityService.getEvse(evseId); + if (!availabilityEvse) { errorCode = "PropertyConstraintViolation"; return; } @@ -122,11 +133,7 @@ void ChangeAvailability::processReq(JsonObject payload) { return; } - if (evseId == 0) { - status = availabilityService.changeAvailability(operative); - } else { - status = availabilityService.getEvse(evseId)->changeAvailability(operative); - } + status = availabilityEvse->changeAvailability(operative); } std::unique_ptr ChangeAvailability::createConf(){ diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.cpp b/src/MicroOcpp/Operations/RequestStartTransaction.cpp index 72e1a761..1b6c0b8f 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStartTransaction.cpp @@ -24,7 +24,7 @@ const char* RequestStartTransaction::getOperationType(){ void RequestStartTransaction::processReq(JsonObject payload) { int evseId = payload["evseId"] | 0; - if (evseId < 0 || evseId >= MO_NUM_EVSE) { + if (evseId < 0 || evseId >= MO_NUM_EVSEID) { errorCode = "PropertyConstraintViolation"; return; } diff --git a/src/MicroOcpp/Operations/Reset.cpp b/src/MicroOcpp/Operations/Reset.cpp index bf2b7592..9ab42422 100644 --- a/src/MicroOcpp/Operations/Reset.cpp +++ b/src/MicroOcpp/Operations/Reset.cpp @@ -81,7 +81,7 @@ void Reset::processReq(JsonObject payload) { int evseIdRaw = payload["evseId"] | 0; - if (evseIdRaw < 0 || evseIdRaw >= MO_NUM_EVSE) { + if (evseIdRaw < 0 || evseIdRaw >= MO_NUM_EVSEID) { errorCode = "PropertyConstraintViolation"; return; } diff --git a/src/MicroOcpp/Operations/UnlockConnector.cpp b/src/MicroOcpp/Operations/UnlockConnector.cpp index 66ef5aef..4599a816 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.cpp +++ b/src/MicroOcpp/Operations/UnlockConnector.cpp @@ -100,7 +100,7 @@ void UnlockConnector::processReq(JsonObject payload) { int evseId = payload["evseId"] | -1; int connectorId = payload["connectorId"] | -1; - if (evseId < 1 || evseId >= MO_NUM_EVSE || connectorId < 1) { + if (evseId < 1 || evseId >= MO_NUM_EVSEID || connectorId < 1) { errorCode = "PropertyConstraintViolation"; return; } From 4daa22e0923891f220724aa8d1e833a9d8cd454f Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:01:27 +0200 Subject: [PATCH 13/18] fix test cases --- src/MicroOcpp/Model/Model.cpp | 18 +++++++++--------- tests/Api.cpp | 6 +++--- tests/Reset.cpp | 16 ++++++++-------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 67c37f88..f2e19c1e 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -145,15 +145,6 @@ MeteringService* Model::getMeteringService() const { return meteringService.get(); } -void Model::setRemoteControlService(std::unique_ptr rs) { - remoteControlService = std::move(rs); - capabilitiesUpdated = true; -} - -RemoteControlService *Model::getRemoteControlService() const { - return remoteControlService.get(); -} - void Model::setFirmwareService(std::unique_ptr fws) { firmwareService = std::move(fws); capabilitiesUpdated = true; @@ -273,6 +264,15 @@ void Model::setMeteringServiceV201(std::unique_ptr rs) Ocpp201::MeteringService *Model::getMeteringServiceV201() const { return meteringServiceV201.get(); } + +void Model::setRemoteControlService(std::unique_ptr rs) { + remoteControlService = std::move(rs); + capabilitiesUpdated = true; +} + +RemoteControlService *Model::getRemoteControlService() const { + return remoteControlService.get(); +} #endif Clock& Model::getClock() { diff --git a/tests/Api.cpp b/tests/Api.cpp index ab0888c2..d7815e10 100644 --- a/tests/Api.cpp +++ b/tests/Api.cpp @@ -55,7 +55,7 @@ TEST_CASE( "C++ API test" ) { auto valueSampler = std::unique_ptr>>( new MicroOcpp::SampledValueSamplerConcrete>( svprops, - [c = &checkpoints[ncheck++]] (MicroOcpp::ReadingContext) -> int32_t {*c = true; return 0;})); + [c = &checkpoints[ncheck++]] (ReadingContext) -> int32_t {*c = true; return 0;})); addMeterValueInput(std::move(valueSampler)); setOccupiedInput([c = &checkpoints[ncheck++]] () -> bool {*c = true; return false;}); @@ -234,13 +234,13 @@ TEST_CASE( "C API test" ) { auto valueSampler = std::unique_ptr>>( new MicroOcpp::SampledValueSamplerConcrete>( svprops, - [] (MicroOcpp::ReadingContext) -> int32_t {checkpointsc[16] = true; return 0;})); ncheckc++; + [] (ReadingContext) -> int32_t {checkpointsc[16] = true; return 0;})); ncheckc++; ocpp_addMeterValueInput(reinterpret_cast(valueSampler.release())); valueSampler = std::unique_ptr>>( new MicroOcpp::SampledValueSamplerConcrete>( svprops, - [] (MicroOcpp::ReadingContext) -> int32_t {checkpointsc[17] = true; return 0;})); ncheckc++; + [] (ReadingContext) -> int32_t {checkpointsc[17] = true; return 0;})); ncheckc++; ocpp_addMeterValueInput_m(2, reinterpret_cast(valueSampler.release())); ocpp_setOccupiedInput([] () -> bool {checkpointsc[18] = true; return true;}); ncheckc++; diff --git a/tests/Reset.cpp b/tests/Reset.cpp index 857104f8..c5dfcd5b 100644 --- a/tests/Reset.cpp +++ b/tests/Reset.cpp @@ -51,8 +51,8 @@ TEST_CASE( "Reset" ) { });}); // Register Reset handlers - bool checkNotified [MO_NUM_EVSE] = {false}; - bool checkExecuted [MO_NUM_EVSE] = {false}; + bool checkNotified [MO_NUM_EVSEID] = {false}; + bool checkExecuted [MO_NUM_EVSEID] = {false}; setOnResetNotify([&checkNotified] (bool) { MO_DBG_DEBUG("Notify"); @@ -65,7 +65,7 @@ TEST_CASE( "Reset" ) { return false; // Reset fails because we're not actually exiting the process }); - for (size_t i = 1; i < MO_NUM_EVSE; i++) { + for (size_t i = 1; i < MO_NUM_EVSEID; i++) { context->getModel().getResetServiceV201()->setNotifyReset([&checkNotified, i] (ResetType) { MO_DBG_DEBUG("Notify %zu", i); checkNotified[i] = true; @@ -110,7 +110,7 @@ TEST_CASE( "Reset" ) { REQUIRE(checkProcessed); - for (size_t i = 0; i < MO_NUM_EVSE; i++) { + for (size_t i = 0; i < MO_NUM_EVSEID; i++) { REQUIRE( checkNotified[i] ); } @@ -163,7 +163,7 @@ TEST_CASE( "Reset" ) { REQUIRE(checkProcessed); - for (size_t i = 0; i < MO_NUM_EVSE; i++) { + for (size_t i = 0; i < MO_NUM_EVSEID; i++) { REQUIRE( checkNotified[i] ); } @@ -181,7 +181,7 @@ TEST_CASE( "Reset" ) { REQUIRE( !ocppPermitsCharge(1) ); REQUIRE( ocppPermitsCharge(2) ); - REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); + //REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); //change: Reset doesn't lead to Unavailable state context->getModel().getTransactionService()->getEvse(2)->endAuthorization("mIdToken"); setConnectorPluggedInput([] () {return false;}, 2); @@ -265,7 +265,7 @@ TEST_CASE( "Reset" ) { REQUIRE(checkProcessed); REQUIRE(checkProcessedTx); - for (size_t i = 0; i < MO_NUM_EVSE; i++) { + for (size_t i = 0; i < MO_NUM_EVSEID; i++) { REQUIRE( checkNotified[i] ); } @@ -350,7 +350,7 @@ TEST_CASE( "Reset" ) { REQUIRE(checkProcessed); REQUIRE(checkNotified[1]); - REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); + //REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); //change: Reset doesn't lead to Unavailable state REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available ); mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately From 05a89d3ad4657109572ef0c9cb3dce350faa0b53 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:18:17 +0200 Subject: [PATCH 14/18] fix compilation --- src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h b/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h index 3439cc89..113a3768 100644 --- a/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h +++ b/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h @@ -9,7 +9,7 @@ #if MO_ENABLE_V201 -#include +#include namespace MicroOcpp { From efb80ef07dc15d27bfb09c0e11d6843556ddcdea Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:31:07 +0200 Subject: [PATCH 15/18] fix compilation error --- src/MicroOcpp/Operations/UnlockConnector.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MicroOcpp/Operations/UnlockConnector.cpp b/src/MicroOcpp/Operations/UnlockConnector.cpp index 4599a816..3dfdbec9 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.cpp +++ b/src/MicroOcpp/Operations/UnlockConnector.cpp @@ -81,6 +81,7 @@ std::unique_ptr UnlockConnector::createConf() { #if MO_ENABLE_V201 +#if MO_ENABLE_CONNECTOR_LOCK #include @@ -159,4 +160,5 @@ std::unique_ptr UnlockConnector::createConf() { } // namespace Ocpp201 } // namespace MicroOcpp +#endif //MO_ENABLE_CONNECTOR_LOCK #endif //MO_ENABLE_V201 From ded451d8feec8a7164704023c775b983c264f2a1 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:48:38 +0200 Subject: [PATCH 16/18] fix ASAN error --- src/MicroOcpp/Core/RequestQueue.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MicroOcpp/Core/RequestQueue.cpp b/src/MicroOcpp/Core/RequestQueue.cpp index 383492aa..104a2e97 100644 --- a/src/MicroOcpp/Core/RequestQueue.cpp +++ b/src/MicroOcpp/Core/RequestQueue.cpp @@ -79,6 +79,7 @@ std::unique_ptr VolatileRequestQueue::fetchFrontRequest() { bool VolatileRequestQueue::pushRequestBack(std::unique_ptr request) { // Don't queue up multiple StatusNotification messages for the same connectorId + #if 0 // Leads to ASAN failure when executed by Unit test suite (CustomOperation is casted to StatusNotification) if (strcmp(request->getOperationType(), "StatusNotification") == 0) { size_t i = 0; @@ -103,6 +104,7 @@ bool VolatileRequestQueue::pushRequestBack(std::unique_ptr request) { } } } + #endif if (len >= MO_REQUEST_CACHE_MAXSIZE) { MO_DBG_INFO("Drop cached operation (cache full): %s", requests[front]->getOperationType()); From fb19cf07b811771f5231e888c364188ce1f85793 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:05:04 +0200 Subject: [PATCH 17/18] fix firmware size classifier --- tests/benchmarks/scripts/eval_firmware_size.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/benchmarks/scripts/eval_firmware_size.py b/tests/benchmarks/scripts/eval_firmware_size.py index 653cf00f..5d544028 100755 --- a/tests/benchmarks/scripts/eval_firmware_size.py +++ b/tests/benchmarks/scripts/eval_firmware_size.py @@ -41,6 +41,7 @@ def categorize_table(df): MODULE_AUTHORIZATION = 'C - Authorization' MODULE_LOCALAUTH = 'D - Local Authorization List Management' MODULE_TX = 'E - Transactions' + MODULE_REMOTECONTROL = 'F - RemoteControl' MODULE_AVAILABILITY = 'G - Availability' MODULE_RESERVATION = 'H - Reservation' MODULE_METERVALUES = 'J - MeterValues' @@ -154,8 +155,17 @@ def categorize_table(df): df.at['Model/Metering/MeterStore.cpp', 'Module'] = MODULE_METERVALUES df.at['Model/Metering/MeterValue.cpp', 'v16'] = TICK df.at['Model/Metering/MeterValue.cpp', 'Module'] = MODULE_METERVALUES + if 'Model/Metering/MeterValuesV201.cpp' in df.index: + df.at['Model/Metering/MeterValuesV201.cpp', 'v201'] = TICK + df.at['Model/Metering/MeterValuesV201.cpp', 'Module'] = MODULE_METERVALUES + if 'Model/Metering/ReadingContext.cpp' in df.index: + df.at['Model/Metering/ReadingContext.cpp', 'v201'] = TICK + df.at['Model/Metering/ReadingContext.cpp', 'Module'] = MODULE_METERVALUES df.at['Model/Metering/SampledValue.cpp', 'v16'] = TICK df.at['Model/Metering/SampledValue.cpp', 'Module'] = MODULE_METERVALUES + if 'Model/RemoteControl/RemoteControlService.cpp' in df.index: + df.at['Model/RemoteControl/RemoteControlService.cpp', 'v201'] = TICK + df.at['Model/RemoteControl/RemoteControlService.cpp', 'Module'] = MODULE_REMOTECONTROL df.at['Model/Model.cpp', 'v16'] = TICK df.at['Model/Model.cpp', 'v201'] = TICK df.at['Model/Model.cpp', 'Module'] = MODULE_GENERAL From 8d45ecd307127258421f4f191100b5d282a6e0b4 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:32:54 +0200 Subject: [PATCH 18/18] update changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e16f55..f47a253d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ - Relaxed temporal order of non-tx-related operations ([#345](https://github.com/matth-x/MicroOcpp/pull/345)) - Use pseudo-GUIDs as messageId ([#345](https://github.com/matth-x/MicroOcpp/pull/345)) - ISO 8601 milliseconds omitted by default ([352](https://github.com/matth-x/MicroOcpp/pull/352)) +- Rename `MO_NUM_EVSE` into `MO_NUM_EVSEID` (v2.0.1) ([#371](https://github.com/matth-x/MicroOcpp/pull/371)) +- Change `MicroOcpp::ReadingContext` into C-style struct ([#371](https://github.com/matth-x/MicroOcpp/pull/371)) +- Refactor RequestStartTransaction (v2.0.1) ([#371](https://github.com/matth-x/MicroOcpp/pull/371)) ### Added @@ -23,6 +26,9 @@ - Heap profiler and custom allocator support ([#350](https://github.com/matth-x/MicroOcpp/pull/350)) - Migration of persistent storage ([#355](https://github.com/matth-x/MicroOcpp/pull/355)) - Benchmarks pipeline ([#369](https://github.com/matth-x/MicroOcpp/pull/369)) +- MeterValues port for OCPP 2.0.1 ([#371](https://github.com/matth-x/MicroOcpp/pull/371)) +- UnlockConnector port for OCPP 2.0.1 ([#371](https://github.com/matth-x/MicroOcpp/pull/371)) +- More APIs ported to OCPP 2.0.1 ([#371](https://github.com/matth-x/MicroOcpp/pull/371)) ### Removed @@ -38,7 +44,7 @@ - Hold back error StatusNotifs when time not set ([#311](https://github.com/matth-x/MicroOcpp/issues/311)) - Don't send Available when tx occupies connector ([#315](https://github.com/matth-x/MicroOcpp/issues/315)) - Make ChargingScheduleAllowedChargingRateUnit read-only ([#328](https://github.com/matth-x/MicroOcpp/issues/328)) -- Don't send StatusNotifs while offline ([#344](https://github.com/matth-x/MicroOcpp/pull/344)) +- ~Don't send StatusNotifs while offline ([#344](https://github.com/matth-x/MicroOcpp/pull/344))~ (see ([#371](https://github.com/matth-x/MicroOcpp/pull/371))) - Don't change into Unavailable upon Reset ([#344](https://github.com/matth-x/MicroOcpp/pull/344)) - Reject DataTransfer by default ([#344](https://github.com/matth-x/MicroOcpp/pull/344)) - UnlockConnector NotSupported if connectorId invalid ([#344](https://github.com/matth-x/MicroOcpp/pull/344)) @@ -46,6 +52,7 @@ - Correct MeterValue PreBoot timestamp ([#354](https://github.com/matth-x/MicroOcpp/pull/354)) - Send errorCode in triggered StatusNotif ([#359](https://github.com/matth-x/MicroOcpp/pull/359)) - Remove int to bool conversion in ChangeConfig ([#362](https://github.com/matth-x/MicroOcpp/pull/362)) +- Multiple fixes of the OCPP 2.0.1 extension ([#371](https://github.com/matth-x/MicroOcpp/pull/371)) ## [1.1.0] - 2024-05-21