From f068daf591e9d24c8b1f4d2cec86be2eaabfb4a1 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 7 Jan 2024 15:53:57 +0100 Subject: [PATCH 01/23] proto version switch --- src/MicroOcpp.cpp | 37 ++++++++- src/MicroOcpp.h | 26 ++++++- src/MicroOcpp/Core/Context.cpp | 8 +- src/MicroOcpp/Core/Context.h | 7 +- .../Model/ConnectorBase/ChargePointStatus.h | 15 +++- .../Model/ConnectorBase/Connector.cpp | 75 +++++++++++++------ src/MicroOcpp/Model/Model.cpp | 8 +- src/MicroOcpp/Model/Model.h | 9 ++- src/MicroOcpp/Operations/BootNotification.cpp | 13 +++- .../Operations/StatusNotification.cpp | 75 +++++++++++++++++-- src/MicroOcpp/Operations/StatusNotification.h | 30 +++++++- src/MicroOcpp/Version.h | 35 +++++++++ 12 files changed, 287 insertions(+), 51 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 7d23f5a3..3eea5c96 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -28,7 +28,6 @@ #include #include -#include namespace MicroOcpp { namespace Facade { @@ -199,7 +198,39 @@ ChargerCredentials::ChargerCredentials(const char *cpModel, const char *cpVendor } } -void mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr fs, bool autoRecover) { +ChargerCredentials ChargerCredentials::v201(const char *cpModel, const char *cpVendor, const char *fWv, const char *cpSNr, const char *meterSNr, const char *meterType, const char *cbSNr, const char *iccid, const char *imsi) { + + ChargerCredentials res; + + StaticJsonDocument<512> creds; + if (cpSNr) + creds["serialNumber"] = cpSNr; + if (cpModel) + creds["model"] = cpModel; + if (cpVendor) + creds["vendorName"] = cpVendor; + if (fWv) + creds["firmwareVersion"] = fWv; + if (iccid) + creds["modem"]["iccid"] = iccid; + if (imsi) + creds["modem"]["imsi"] = imsi; + + if (creds.overflowed()) { + MO_DBG_ERR("Charger Credentials too long"); + } + + size_t written = serializeJson(creds, res.payload, 512); + + if (written < 2) { + MO_DBG_ERR("Charger Credentials could not be written"); + sprintf(res.payload, "{}"); + } + + return res; +} + +void mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr fs, bool autoRecover, ProtocolVersion version) { if (context) { MO_DBG_WARN("already initialized. To reinit, call mocpp_deinitialize() before"); return; @@ -234,7 +265,7 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden configuration_init(filesystem); //call before each other library call - context = new Context(connection, filesystem, bootstats.bootNr); + context = new Context(connection, filesystem, bootstats.bootNr, version); auto& model = context->getModel(); model.setTransactionStore(std::unique_ptr( diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index db672b35..28a63484 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -1,9 +1,9 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License -#ifndef ARDUINOOCPP_H -#define ARDUINOOCPP_H +#ifndef MO_MICROOCPP_H +#define MO_MICROOCPP_H #include #include @@ -18,6 +18,7 @@ #include #include #include +#include using MicroOcpp::OnReceiveConfListener; using MicroOcpp::OnReceiveReqListener; @@ -69,6 +70,22 @@ struct ChargerCredentials { const char *chargeBoxSerialNumber = nullptr, const char *iccid = nullptr, const char *imsi = nullptr); + + /* + * OCPP 2.0.1 compatible charger credentials. + * + * DEPRECATED: This construction method is only temporary for testing v2.0.1. It will be removed again soon and the original constructor will suit both v1.6 and v2.0.1 + */ + static ChargerCredentials v201( + const char *chargePointModel = "Demo Charger", + const char *chargePointVendor = "My Company Ltd.", + const char *firmwareVersion = nullptr, + const char *chargePointSerialNumber = nullptr, + const char *meterSerialNumber = nullptr, + const char *meterType = nullptr, + const char *chargeBoxSerialNumber = nullptr, + const char *iccid = nullptr, + const char *imsi = nullptr); operator const char *() {return payload;} @@ -93,7 +110,8 @@ void mocpp_initialize( const char *bootNotificationCredentials = ChargerCredentials("Demo Charger", "My Company Ltd."), //e.g. '{"chargePointModel":"Demo Charger","chargePointVendor":"My Company Ltd."}' (refer to OCPP 1.6 Specification - Edition 2 p. 60) std::shared_ptr filesystem = MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail), //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h - bool autoRecover = false); //automatically sanitize the local data store when the lib detects recurring crashes. Not recommended during development + bool autoRecover = false, //automatically sanitize the local data store when the lib detects recurring crashes. Not recommended during development + MicroOcpp::ProtocolVersion version = MicroOcpp::ProtocolVersion(1,6)); /* * Stop the OCPP library and release allocated resources. diff --git a/src/MicroOcpp/Core/Context.cpp b/src/MicroOcpp/Core/Context.cpp index 425354ac..17227d5e 100644 --- a/src/MicroOcpp/Core/Context.cpp +++ b/src/MicroOcpp/Core/Context.cpp @@ -11,8 +11,8 @@ using namespace MicroOcpp; -Context::Context(Connection& connection, std::shared_ptr filesystem, uint16_t bootNr) - : connection(connection), model{bootNr}, reqQueue{operationRegistry, &model, filesystem} { +Context::Context(Connection& connection, std::shared_ptr filesystem, uint16_t bootNr, ProtocolVersion version) + : connection(connection), model{bootNr, version}, reqQueue{operationRegistry, &model, filesystem} { preBootQueue = std::unique_ptr(new RequestQueue(operationRegistry, &model, nullptr)); //pre boot queue doesn't need persistency preBootQueue->setConnection(connection); @@ -64,3 +64,7 @@ Model& Context::getModel() { OperationRegistry& Context::getOperationRegistry() { return operationRegistry; } + +const ProtocolVersion& Context::getVersion() { + return model.getVersion(); +} diff --git a/src/MicroOcpp/Core/Context.h b/src/MicroOcpp/Core/Context.h index dfe5ff2c..cdb0d80b 100644 --- a/src/MicroOcpp/Core/Context.h +++ b/src/MicroOcpp/Core/Context.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #ifndef MO_CONTEXT_H @@ -10,6 +10,7 @@ #include #include #include +#include namespace MicroOcpp { @@ -26,7 +27,7 @@ class Context { std::unique_ptr preBootQueue; public: - Context(Connection& connection, std::shared_ptr filesystem, uint16_t bootNr); + Context(Connection& connection, std::shared_ptr filesystem, uint16_t bootNr, ProtocolVersion version); ~Context(); void loop(); @@ -41,6 +42,8 @@ class Context { Model& getModel(); OperationRegistry& getOperationRegistry(); + + const ProtocolVersion& getVersion(); }; } //end namespace MicroOcpp diff --git a/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h b/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h index 308e04b8..1fa02c00 100644 --- a/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h +++ b/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #ifndef OCPP_EVSE_STATE @@ -20,6 +20,19 @@ enum class ChargePointStatus { NOT_SET //internal value for "undefined" }; +namespace Ocpp201 { + +enum class ConnectorStatus { + Available, + Occupied, + Reserved, + Unavailable, + Faulted, + NOT_SET //internal value for "undefined" +}; + +} //end namespace Ocpp201 + } //end namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp index 4c0274ab..abebd491 100644 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp +++ b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #include @@ -29,7 +29,6 @@ #endif using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; Connector::Connector(Context& context, int connectorId) : context(context), model(context.getModel()), connectorId{connectorId} { @@ -270,7 +269,7 @@ void Connector::loop() { model.getMeteringService()->beginTxMeterData(transaction.get()); } - auto startTx = makeRequest(new StartTransaction(model, transaction)); + auto startTx = makeRequest(new Ocpp16::StartTransaction(model, transaction)); startTx->setTimeout(0); startTx->setOnReceiveConfListener([this] (JsonObject response) { //fetch authorization status from StartTransaction.conf() for user notification @@ -334,9 +333,9 @@ void Connector::loop() { std::unique_ptr stopTx; if (stopTxData) { - stopTx = makeRequest(new StopTransaction(model, std::move(transaction), stopTxData->retrieveStopTxData())); + stopTx = makeRequest(new Ocpp16::StopTransaction(model, std::move(transaction), stopTxData->retrieveStopTxData())); } else { - stopTx = makeRequest(new StopTransaction(model, std::move(transaction))); + stopTx = makeRequest(new Ocpp16::StopTransaction(model, std::move(transaction))); } stopTx->setTimeout(0); context.initiateRequest(std::move(stopTx)); @@ -366,25 +365,39 @@ void Connector::loop() { auto status = getStatus(); - for (auto i = std::min(errorDataInputs.size(), trackErrorDataInputs.size()); i >= 1; i--) { - auto index = i - 1; - auto error = errorDataInputs[index].operator()(); - if (error.isError && !trackErrorDataInputs[index]) { - //new error - auto statusNotification = makeRequest( - new StatusNotification(connectorId, status, model.getClock().now(), error)); - statusNotification->setTimeout(0); - context.initiateRequest(std::move(statusNotification)); + if (model.getVersion().v16()) { + //OCPP 1.6: use StatusNotification to send error codes + for (auto i = std::min(errorDataInputs.size(), trackErrorDataInputs.size()); i >= 1; i--) { + auto index = i - 1; + auto error = errorDataInputs[index].operator()(); + if (error.isError && !trackErrorDataInputs[index]) { + //new error + auto statusNotification = makeRequest( + new Ocpp16::StatusNotification(connectorId, status, model.getClock().now(), error)); + statusNotification->setTimeout(0); + context.initiateRequest(std::move(statusNotification)); + + currentStatus = status; + reportedStatus = status; + trackErrorDataInputs[index] = true; + } else if (!error.isError && trackErrorDataInputs[index]) { + //reset error + trackErrorDataInputs[index] = false; + } + } + } - currentStatus = status; - reportedStatus = status; - trackErrorDataInputs[index] = true; - } else if (!error.isError && trackErrorDataInputs[index]) { - //reset error - trackErrorDataInputs[index] = false; + if (model.getVersion().v2()) { + //OCPP 2.0.1: use Preparing as alias for the Occupied state + if (status == ChargePointStatus::Preparing || + status == ChargePointStatus::Charging || + status == ChargePointStatus::SuspendedEV || + status == ChargePointStatus::SuspendedEVSE || + status == ChargePointStatus::Finishing) { + status = ChargePointStatus::Preparing; } } - + if (status != currentStatus) { currentStatus = status; t_statusTransition = mocpp_tick_ms(); @@ -399,8 +412,22 @@ void Connector::loop() { Timestamp reportedTimestamp = model.getClock().now(); reportedTimestamp -= (mocpp_tick_ms() - t_statusTransition) / 1000UL; - auto statusNotification = makeRequest( - new StatusNotification(connectorId, reportedStatus, reportedTimestamp, getErrorCode())); + auto statusNotification = + #if MO_ENABLE_V201 + model.getVersion().v2() ? + makeRequest( + new Ocpp201::StatusNotification(connectorId, + reportedStatus == ChargePointStatus::Available ? Ocpp201::ConnectorStatus::Available : + reportedStatus == ChargePointStatus::Preparing ? Ocpp201::ConnectorStatus::Occupied : + reportedStatus == ChargePointStatus::Reserved ? Ocpp201::ConnectorStatus::Reserved : + reportedStatus == ChargePointStatus::Unavailable ? Ocpp201::ConnectorStatus::Unavailable : + reportedStatus == ChargePointStatus::Faulted ? Ocpp201::ConnectorStatus::Faulted : + Ocpp201::ConnectorStatus::NOT_SET, + reportedTimestamp, 1)) : + #endif //MO_ENABLE_V201 + makeRequest( + new Ocpp16::StatusNotification(connectorId, reportedStatus, reportedTimestamp, getErrorCode())); + statusNotification->setTimeout(0); context.initiateRequest(std::move(statusNotification)); return; @@ -621,7 +648,7 @@ std::shared_ptr Connector::beginTransaction(const char *idTag) { transaction->commit(); - auto authorize = makeRequest(new Authorize(context.getModel(), idTag)); + auto authorize = makeRequest(new Ocpp16::Authorize(context.getModel(), idTag)); authorize->setTimeout(authorizationTimeoutInt && authorizationTimeoutInt->getInt() > 0 ? authorizationTimeoutInt->getInt() * 1000UL : 20UL * 1000UL); auto tx = transaction; authorize->setOnReceiveConfListener([this, tx] (JsonObject response) { diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index f0bd7beb..7fb34119 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #include @@ -24,7 +24,7 @@ using namespace MicroOcpp; -Model::Model(uint16_t bootNr) : bootNr(bootNr) { +Model::Model(uint16_t bootNr, ProtocolVersion version) : bootNr(bootNr), version(version) { } @@ -191,6 +191,10 @@ Clock& Model::getClock() { return clock; } +const ProtocolVersion& Model::getVersion() const { + return version; +} + uint16_t Model::getBootNr() { return bootNr; } diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 1273153f..efe927eb 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #ifndef MO_MODEL_H @@ -8,6 +8,7 @@ #include #include +#include #include namespace MicroOcpp { @@ -40,6 +41,8 @@ class Model { std::unique_ptr resetService; Clock clock; + ProtocolVersion version; + bool capabilitiesUpdated = true; void updateSupportedStandardProfiles(); @@ -48,7 +51,7 @@ class Model { const uint16_t bootNr = 0; //each boot of this lib has a unique number public: - Model(uint16_t bootNr = 0); + Model(uint16_t bootNr = 0, ProtocolVersion version = ProtocolVersion(1,6)); Model(const Model& rhs) = delete; ~Model(); @@ -94,6 +97,8 @@ class Model { Clock &getClock(); + const ProtocolVersion& getVersion() const; + uint16_t getBootNr(); }; diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index f59c1bfe..200a8124 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #include @@ -22,7 +22,16 @@ const char* BootNotification::getOperationType(){ std::unique_ptr BootNotification::createReq() { if (credentials) { - return std::unique_ptr(new DynamicJsonDocument(*credentials)); + std::unique_ptr doc; + if (model.getVersion().major == 2) { + doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2) + credentials->memoryUsage())); + JsonObject payload = doc->to(); + payload["reason"] = "PowerUp"; + payload["chargingStation"] = *credentials; + } else { + doc = std::unique_ptr(new DynamicJsonDocument(*credentials)); + } + return doc; } else { MO_DBG_ERR("payload undefined"); return createEmptyDocument(); diff --git a/src/MicroOcpp/Operations/StatusNotification.cpp b/src/MicroOcpp/Operations/StatusNotification.cpp index ada65894..c4dc33e7 100644 --- a/src/MicroOcpp/Operations/StatusNotification.cpp +++ b/src/MicroOcpp/Operations/StatusNotification.cpp @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #include @@ -8,11 +8,10 @@ #include -using MicroOcpp::Ocpp16::StatusNotification; - -//helper function namespace MicroOcpp { namespace Ocpp16 { + +//helper function const char *cstrFromOcppEveState(ChargePointStatus state) { switch (state) { case (ChargePointStatus::Available): @@ -41,7 +40,6 @@ const char *cstrFromOcppEveState(ChargePointStatus state) { return "NOT_SET"; } } -}} //end namespaces StatusNotification::StatusNotification(int connectorId, ChargePointStatus currentStatus, const Timestamp ×tamp, ErrorData errorData) : connectorId(connectorId), currentStatus(currentStatus), timestamp(timestamp), errorData(errorData) { @@ -56,11 +54,10 @@ const char* StatusNotification::getOperationType(){ return "StatusNotification"; } -//TODO if the status has changed again when sendReq() is called, abort the operation completely (note: if req is already sent, stick with listening to conf). The ChargePointStatusService will enqueue a new operation itself std::unique_ptr StatusNotification::createReq() { auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(7) + (JSONDATE_LENGTH + 1))); JsonObject payload = doc->to(); - + payload["connectorId"] = connectorId; if (errorData.isError) { if (errorData.errorCode) { @@ -91,7 +88,6 @@ std::unique_ptr StatusNotification::createReq() { return doc; } - void StatusNotification::processConf(JsonObject payload) { /* * Empty payload @@ -111,3 +107,66 @@ void StatusNotification::processReq(JsonObject payload) { std::unique_ptr StatusNotification::createConf(){ return createEmptyDocument(); } + +} //end namespace Ocpp16 + +#if MO_ENABLE_V201 + +namespace Ocpp201 { + +const char *cstrFromOcppEveState(ConnectorStatus state) { + switch (state) { + case (ConnectorStatus::Available): + return "Available"; + case (ConnectorStatus::Occupied): + return "Occupied"; + case (ConnectorStatus::Reserved): + return "Reserved"; + case (ConnectorStatus::Unavailable): + return "Unavailable"; + case (ConnectorStatus::Faulted): + return "Faulted"; + default: + MO_DBG_ERR("ConnectorStatus not specified"); + (void)0; + /* fall through */ + case (ConnectorStatus::NOT_SET): + return "NOT_SET"; + } +} + +StatusNotification::StatusNotification(int evseId, ConnectorStatus currentStatus, const Timestamp ×tamp, int connectorId) + : evseId(evseId), currentStatus(currentStatus), timestamp(timestamp), connectorId(connectorId) { + +} + +const char* StatusNotification::getOperationType(){ + return "StatusNotification"; +} + +std::unique_ptr StatusNotification::createReq() { + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(4) + (JSONDATE_LENGTH + 1))); + JsonObject payload = doc->to(); + + char timestamp_cstr[JSONDATE_LENGTH + 1] = {'\0'}; + timestamp.toJsonString(timestamp_cstr, JSONDATE_LENGTH + 1); + payload["timestamp"] = timestamp_cstr; + payload["connectorStatus"] = cstrFromOcppEveState(currentStatus); + payload["evseId"] = connectorId; + payload["connectorId"] = 1; + + return doc; +} + + +void StatusNotification::processConf(JsonObject payload) { + /* + * Empty payload + */ +} + +} //end namespace Ocpp201 + +#endif //MO_ENABLE_V201 + +} //end namespace MicroOcpp diff --git a/src/MicroOcpp/Operations/StatusNotification.h b/src/MicroOcpp/Operations/StatusNotification.h index 74f329d6..31c638b6 100644 --- a/src/MicroOcpp/Operations/StatusNotification.h +++ b/src/MicroOcpp/Operations/StatusNotification.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #ifndef STATUSNOTIFICATION_H @@ -9,6 +9,7 @@ #include #include #include +#include namespace MicroOcpp { namespace Ocpp16 { @@ -36,5 +37,32 @@ class StatusNotification : public Operation { const char *cstrFromOcppEveState(ChargePointStatus state); } //end namespace Ocpp16 + +#if MO_ENABLE_V201 + +namespace Ocpp201 { + +class StatusNotification : public Operation { +private: + Timestamp timestamp; + ConnectorStatus currentStatus = ConnectorStatus::NOT_SET; + int evseId; + int connectorId; +public: + StatusNotification(int evseId, ConnectorStatus currentStatus, const Timestamp ×tamp, int connectorId); + + const char* getOperationType() override; + + std::unique_ptr createReq() override; + + void processConf(JsonObject payload) override; +}; + +const char *cstrFromOcppEveState(ConnectorStatus state); + +} //end namespace Ocpp201 + +#endif //MO_ENABLE_V201 + } //end namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Version.h b/src/MicroOcpp/Version.h index d4604681..6c8807ec 100644 --- a/src/MicroOcpp/Version.h +++ b/src/MicroOcpp/Version.h @@ -1 +1,36 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_VERSION_H +#define MO_VERSION_H + +/* + * Version specification of MicroOcpp library (unrelated with the OCPP version) + */ #define MO_VERSION "1.0.0" + +/* + * Enable OCPP 2.0.1 support. If enabled, library can be initialized with both v1.6 and v2.0.1. The choice + * of the protocol is done dynamically during initialization + */ +#ifndef MO_ENABLE_V201 +#define MO_ENABLE_V201 0 +#endif + +namespace MicroOcpp { + +/* + * OCPP version type, defined in Model + */ +struct ProtocolVersion { + const int major, minor, patch; + ProtocolVersion(int major = 1, int minor = 6, int patch = 0) : major(major), minor(minor), patch(patch) { } + + inline bool v16() const {return major == 1 && minor == 6;} + inline bool v2() const {return major == 2;} +}; + +} + +#endif From c97a771a703b5cbe0db29918a1b6004b533b0590 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 7 Jan 2024 18:21:38 +0100 Subject: [PATCH 02/23] fix Wreorder --- src/MicroOcpp/Core/Context.cpp | 2 +- src/MicroOcpp/Model/Model.cpp | 2 +- src/MicroOcpp/Model/Model.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MicroOcpp/Core/Context.cpp b/src/MicroOcpp/Core/Context.cpp index 17227d5e..1a557eab 100644 --- a/src/MicroOcpp/Core/Context.cpp +++ b/src/MicroOcpp/Core/Context.cpp @@ -12,7 +12,7 @@ using namespace MicroOcpp; Context::Context(Connection& connection, std::shared_ptr filesystem, uint16_t bootNr, ProtocolVersion version) - : connection(connection), model{bootNr, version}, reqQueue{operationRegistry, &model, filesystem} { + : connection(connection), model{version, bootNr}, reqQueue{operationRegistry, &model, filesystem} { preBootQueue = std::unique_ptr(new RequestQueue(operationRegistry, &model, nullptr)); //pre boot queue doesn't need persistency preBootQueue->setConnection(connection); diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 7fb34119..abdaffad 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -24,7 +24,7 @@ using namespace MicroOcpp; -Model::Model(uint16_t bootNr, ProtocolVersion version) : bootNr(bootNr), version(version) { +Model::Model(ProtocolVersion version, uint16_t bootNr) : version(version), bootNr(bootNr) { } diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index efe927eb..9493251b 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -51,7 +51,7 @@ class Model { const uint16_t bootNr = 0; //each boot of this lib has a unique number public: - Model(uint16_t bootNr = 0, ProtocolVersion version = ProtocolVersion(1,6)); + Model(ProtocolVersion version = ProtocolVersion(1,6), uint16_t bootNr = 0); Model(const Model& rhs) = delete; ~Model(); From bed6220eca03c6e2c7def572e9498dd634a3d210 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 7 Jan 2024 19:02:15 +0100 Subject: [PATCH 03/23] pin ArduinoJson for esp-idf CI --- .github/workflows/esp-idf.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/esp-idf.yml b/.github/workflows/esp-idf.yml index 90043470..f0f756cb 100644 --- a/.github/workflows/esp-idf.yml +++ b/.github/workflows/esp-idf.yml @@ -40,6 +40,7 @@ jobs: uses: actions/checkout@v3 with: repository: bblanchon/ArduinoJson + ref: 3e1be980d93e47b2a0073efeeb9a9396fd7a83be path: examples/ESP-IDF/components/ArduinoJson - name: esp-idf build uses: espressif/esp-idf-ci-action@v1 From 10c0b7d4a5502c496ce4dac6e1d0429856875966 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 4 Feb 2024 20:22:23 +0100 Subject: [PATCH 04/23] VariableType class definitions --- src/MicroOcpp/Model/Variables/Variable.h | 140 +++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/MicroOcpp/Model/Variables/Variable.h diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h new file mode 100644 index 00000000..9ce838f7 --- /dev/null +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -0,0 +1,140 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_VARIABLE_H +#define MO_VARIABLE_H + +#include +#include +#include + +#include + +#if MO_ENABLE_V201 + +namespace MicroOcpp { + +/* + * MO-internal optimization: store data in more compact integer representation if applicable + */ +enum class VariableAttributeType : uint8_t { + Int, + Bool, + String +}; + +/* + * Corresponds to VariableAttributeType (2.50) + * + * Template method pattern: this is a super-class which has hook-methods for storing and fetching + * the value of the variable. To make it use the host system's key-value store, extend this class + * with a custom implementation and pass its instances to MO. + */ +class VariableAttribute { +public: + //AttributeEnumType (3.2) + enum class Type : uint8_t { + Actual, + Target, + MinSet, + MaxSet + }; + + //MutabilityEnumType (3.58) + enum class Mutability : uint8_t { + ReadOnly, + WriteOnly, + ReadWrite + }; + +protected: + uint16_t writeCounter = 0; //write access counter; used to check if this config has been changed +private: + Type type = Type::Actual; + Mutability mutability = Mutability::ReadWrite; + bool persistent = false; + bool constant = false; + + bool rebootRequired = false; + +public: + virtual ~VariableAttribute(); + + virtual void setInt(int); + virtual void setBool(bool); + virtual bool setString(const char*); + + virtual int getInt(); + virtual bool getBool(); + virtual const char *getString(); //always returns c-string (empty if undefined) + + uint16_t getWriteCounter(); //get write count (use this as a pre-check if the value changed) + + void setType(Type t); + Type getType(); + + void setMutability(Mutability m); + Mutability getMutability(); + + void setPersistent(); + bool isPersistent(); + + void setConstant(); + bool isConstant(); + + void setRebootRequired(); + bool isRebootRequired(); +}; + +/* + * Corresponds to VariableMonitoringType (2.52) + */ +class VariableMonitor { +public: + //MonitorEnumType (3.55) + enum class Type { + UpperThreshold, + LowerThreshold, + Delta, + Periodic, + PeriodicClockAligned + }; +private: + int id; + bool transaction; + float value; + Type type; + int severity; +public: + VariableMonitor() = delete; + VariableMonitor(int id, bool transaction, float value, Type type, int severity); +}; + +/* + * Corresponds to VariableType (2.53) + */ +class Variable { +private: + const char *name = nullptr; + //const char *instance = nullptr; //<-- instance not supported in this implementation + const char *componentId = nullptr; + std::vector> attributes; //attr's are initialized in client code and ownership is transferred to Variable + std::vector monitors; +public: + + void setName(const char *key); //zero-copy + const char *getName() const; + + void setComponentId(const char *componentId); //zero-copy + const char *getComponentId() const; + + bool addAttribute(std::unique_ptr attr); + + bool addMonitor(int id, bool transaction, float value, VariableMonitor::Type type, int severity); +}; + +} + +#endif //MO_ENABLE_V201 +#endif From c50899f610236ac83f985b59e07a1bc86d187a7b Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 4 Feb 2024 21:56:44 +0100 Subject: [PATCH 05/23] SetVariables op type --- src/MicroOcpp/Model/Variables/Variable.h | 4 +++ src/MicroOcpp/Operations/SetVariables.h | 45 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/MicroOcpp/Operations/SetVariables.h diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index 9ce838f7..c1e5f5c9 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -2,6 +2,10 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License +/* + * Implementation of the UCs B05 - B07 + */ + #ifndef MO_VARIABLE_H #define MO_VARIABLE_H diff --git a/src/MicroOcpp/Operations/SetVariables.h b/src/MicroOcpp/Operations/SetVariables.h new file mode 100644 index 00000000..9895dec4 --- /dev/null +++ b/src/MicroOcpp/Operations/SetVariables.h @@ -0,0 +1,45 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_SETVARIABLES_H +#define MO_SETVARIABLES_H + +#include + +#if MO_ENABLE_V201 + +#include + +#include + +namespace MicroOcpp { + +namespace Ocpp201 { + +class SetVariables : public Operation { +private: + const char *attributeType = nullptr; + const char *attributeStatus = nullptr; + Variable *variable = nullptr; //contains ptr to `component` + + const char *errorCode = nullptr; +public: + SetVariables(); + + const char* getOperationType() override; + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} + +}; + +} //namespace Ocpp201 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V201 + +#endif From 1b8316535357948e1d64aba3871a34ab45b5843f Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 18 Feb 2024 15:07:40 +0100 Subject: [PATCH 06/23] Variable draft (WIP) --- src/MicroOcpp/Model/Variables/Variable.h | 174 ++++++++++-------- .../Model/Variables/VariableContainer.h | 84 +++++++++ 2 files changed, 181 insertions(+), 77 deletions(-) create mode 100644 src/MicroOcpp/Model/Variables/VariableContainer.h diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index c1e5f5c9..fa383722 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -3,7 +3,7 @@ // MIT License /* - * Implementation of the UCs B05 - B07 + * Implementation of the UCs B05 - B06 */ #ifndef MO_VARIABLE_H @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -19,76 +20,27 @@ namespace MicroOcpp { -/* - * MO-internal optimization: store data in more compact integer representation if applicable - */ -enum class VariableAttributeType : uint8_t { - Int, - Bool, - String -}; - -/* - * Corresponds to VariableAttributeType (2.50) - * - * Template method pattern: this is a super-class which has hook-methods for storing and fetching - * the value of the variable. To make it use the host system's key-value store, extend this class - * with a custom implementation and pass its instances to MO. - */ -class VariableAttribute { -public: - //AttributeEnumType (3.2) - enum class Type : uint8_t { - Actual, - Target, - MinSet, - MaxSet - }; - - //MutabilityEnumType (3.58) - enum class Mutability : uint8_t { - ReadOnly, - WriteOnly, - ReadWrite +//VariableCharacteristicsType (2.51) +struct VariableCharacteristics { + + //DataEnumType (3.26) + enum class DataType : uint8_t { + string, + decimal, + integer, + dateTime, + boolean, + OptionList, + SequenceList, + MemberList }; -protected: - uint16_t writeCounter = 0; //write access counter; used to check if this config has been changed -private: - Type type = Type::Actual; - Mutability mutability = Mutability::ReadWrite; - bool persistent = false; - bool constant = false; - - bool rebootRequired = false; - -public: - virtual ~VariableAttribute(); - - virtual void setInt(int); - virtual void setBool(bool); - virtual bool setString(const char*); - - virtual int getInt(); - virtual bool getBool(); - virtual const char *getString(); //always returns c-string (empty if undefined) - - uint16_t getWriteCounter(); //get write count (use this as a pre-check if the value changed) - - void setType(Type t); - Type getType(); - - void setMutability(Mutability m); - Mutability getMutability(); - - void setPersistent(); - bool isPersistent(); - - void setConstant(); - bool isConstant(); - - void setRebootRequired(); - bool isRebootRequired(); + const char *unit = nullptr; //no copy + //DataType dataType; //stored in Variable + int minLimit = std::numeric_limits::min(); + int maxLimit = std::numeric_limits::max(); + const char *valuesList = nullptr; //no copy + //bool supportsMonitoring; //stored in Variable }; /* @@ -117,14 +69,54 @@ class VariableMonitor { /* * Corresponds to VariableType (2.53) + * + * Template method pattern: this is a super-class which has hook-methods for storing and fetching + * the value of the variable. To make it use the host system's key-value store, extend this class + * with a custom implementation of the virtual methods and pass its instances to MO. */ class Variable { +public: + //AttributeEnumType (3.2) + enum class AttributeType : uint8_t { + Actual, + Target, + MinSet, + MaxSet + }; + + //MutabilityEnumType (3.58) + enum class Mutability : uint8_t { + ReadOnly, + WriteOnly, + ReadWrite + }; + + //MO-internal optimization: if value is only in int range, store it in more compact representation + enum class InternalDataType : uint8_t { + Int, + Bool, + String + }; private: - const char *name = nullptr; + const char *variableName = nullptr; //const char *instance = nullptr; //<-- instance not supported in this implementation - const char *componentId = nullptr; - std::vector> attributes; //attr's are initialized in client code and ownership is transferred to Variable - std::vector monitors; + const char *componentName = nullptr; + + // VariableCharacteristicsType (2.51) + std::unique_ptr characteristics; //optional VariableCharacteristics + VariableCharacteristics::DataType dataType; //mandatory + bool supportsMonitoring = false; //mandatory + bool rebootRequired = false; //MO-internal: if to respond status RebootRequired on SetVariables + + // VariableAttributeType (2.50) + Mutability mutability = Mutability::ReadWrite; + bool persistent = false; + bool constant = false; + + //VariableMonitoring + //std::vector monitors; +protected: + uint16_t writeCount = 0; //write access counter; used to check if this config has been changed public: void setName(const char *key); //zero-copy @@ -133,12 +125,40 @@ class Variable { void setComponentId(const char *componentId); //zero-copy const char *getComponentId() const; - bool addAttribute(std::unique_ptr attr); + // set Value of Variable + virtual void setInt(int val, AttributeType attrType = AttributeType::Actual); + virtual void setBool(bool val, AttributeType attrType = AttributeType::Actual); + virtual bool setString(const char *val, AttributeType attrType = AttributeType::Actual); + + // get Value of Variable + virtual int getInt(AttributeType attrType = AttributeType::Actual); + virtual bool getBool(AttributeType attrType = AttributeType::Actual); + virtual const char *getString(AttributeType attrType = AttributeType::Actual); //always returns c-string (empty if undefined) + + virtual InternalDataType getInternalDataType() = 0; //corresponds to MO internal value representation + + void setVariableDataType(VariableCharacteristics::DataType dataType); //corresponds to OCPP DataEnumType (3.26) + VariableCharacteristics::DataType getVariableDataType(); //corresponds to OCPP DataEnumType (3.26) + bool getSupportsMonitoring(); + void setSupportsMonitoring(); + bool isRebootRequired(); + void setRebootRequired(); - bool addMonitor(int id, bool transaction, float value, VariableMonitor::Type type, int severity); + void setMutability(Mutability m); + Mutability getMutability(); + + void setPersistent(); + bool isPersistent(); + + void setConstant(); + bool isConstant(); + + //bool addMonitor(int id, bool transaction, float value, VariableMonitor::Type type, int severity); + + uint16_t getWriteCount(); //get write count (use this as a pre-check if the value changed) }; -} +} // namespace MicroOcpp -#endif //MO_ENABLE_V201 +#endif // MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.h b/src/MicroOcpp/Model/Variables/VariableContainer.h new file mode 100644 index 00000000..954a396b --- /dev/null +++ b/src/MicroOcpp/Model/Variables/VariableContainer.h @@ -0,0 +1,84 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs B05 - B06 + */ + +#ifndef MO_VARIABLECONTAINER_H +#define MO_VARIABLECONTAINER_H + +#include +#include + +#include + +#include + +#if MO_ENABLE_V201 + +namespace MicroOcpp { + +class VariableContainer { +private: + const char *filename; + bool accessible; +public: + VariableContainer(const char *filename, bool accessible) : filename(filename), accessible(accessible) { } + + virtual ~VariableContainer(); + + const char *getFilename() {return filename;} + bool isAccessible() {return accessible;} //accessible by OCPP server or only used as internal persistent store + + virtual bool load() = 0; //called at the end of mocpp_intialize, to load the Variables with the stored value + virtual bool save() = 0; + + /* + * Factory method to create Variable objects which are managed by VariableContainer. + * The function signature consists of the requested low-level data type (Int, Bool, or String) and + * a composite key to identify the Variable to create (componentName x variableName x attribute type) + * + * Variable::InternalDataType dtype: internal low-level data type. Defines which value getters / setters are valid. + * if dtype == InternalDataType::Int, then getInt() and setInt(...) are valid + * if dtype == InternalDataType::String, then getString() and setString(...) are valid. Etc. + * const char *componentName: name of the Component to which this Variable(attribute) belongs (key attribute) + * const char *variableName: name of the Variable to which this VariableAttribue belongs (key attribute) + * Variable::Type type: type of the Attribute to create (key attribute) + */ + virtual std::shared_ptr createVariable(Variable::InternalDataType dtype, const char *componentName, const char *variableName) = 0; // factory method + virtual void remove(Variable *variable) = 0; + + virtual size_t size() = 0; + virtual Variable *getVariable(size_t i) = 0; + virtual std::shared_ptr getVariable(const char *componentName, const char *variableName) = 0; + + virtual void loadStaticKey(Variable& variable, const char *variableName) { } //possible optimization: can replace internal key with passed static key +}; + +class VariableContainerVolatile : public VariableContainer { +private: + std::vector> variables; +public: + VariableContainerVolatile(const char *filename, bool accessible); + + //VariableContainer definitions + bool load() override; + bool save() override; + std::shared_ptr createVariable(Variable::InternalDataType dtype, const char *componentName, const char *variableName) override; + void remove(Variable *config) override; + size_t size() override; + Variable *getVariable(size_t i) override; + std::shared_ptr getVariable(const char *componentName, const char *variableName) override; + + //add custom Variable object + void add(std::shared_ptr variable); +}; + +std::unique_ptr makeVariableContainerVolatile(const char *filename, bool accessible); + +} //end namespace MicroOcpp + +#endif //MO_ENABLE_V201 +#endif From a327bd64bbac6f2a5fc9f2cc83a38978811b6370 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 24 Feb 2024 12:41:56 +0100 Subject: [PATCH 07/23] Variables implementation (UCs B05, B06) --- CMakeLists.txt | 3 + src/MicroOcpp.cpp | 6 + src/MicroOcpp/Model/Model.cpp | 12 + src/MicroOcpp/Model/Model.h | 14 + src/MicroOcpp/Model/Variables/Variable.cpp | 397 ++++++++++++++++++ src/MicroOcpp/Model/Variables/Variable.h | 90 +++- .../Model/Variables/VariableContainer.cpp | 88 ++++ .../Model/Variables/VariableContainer.h | 23 +- .../Model/Variables/VariableService.cpp | 358 ++++++++++++++++ .../Model/Variables/VariableService.h | 87 ++++ src/MicroOcpp/Operations/GetVariables.cpp | 220 ++++++++++ src/MicroOcpp/Operations/GetVariables.h | 63 +++ src/MicroOcpp/Operations/SetVariables.cpp | 175 ++++++++ src/MicroOcpp/Operations/SetVariables.h | 28 +- 14 files changed, 1531 insertions(+), 33 deletions(-) create mode 100644 src/MicroOcpp/Model/Variables/Variable.cpp create mode 100644 src/MicroOcpp/Model/Variables/VariableContainer.cpp create mode 100644 src/MicroOcpp/Model/Variables/VariableService.cpp create mode 100644 src/MicroOcpp/Model/Variables/VariableService.h create mode 100644 src/MicroOcpp/Operations/GetVariables.cpp create mode 100644 src/MicroOcpp/Operations/GetVariables.h create mode 100644 src/MicroOcpp/Operations/SetVariables.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 95810e11..da65088f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,9 @@ set(MO_SRC src/MicroOcpp/Model/Transactions/Transaction.cpp src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp src/MicroOcpp/Model/Transactions/TransactionStore.cpp + src/MicroOcpp/Model/Variables/Variable.cpp + src/MicroOcpp/Model/Variables/VariableContainer.cpp + src/MicroOcpp/Model/Variables/VariableService.cpp src/MicroOcpp.cpp src/MicroOcpp_c.cpp ) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 3eea5c96..ede9f5d6 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -288,6 +289,11 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden model.setResetService(std::unique_ptr( new ResetService(*context))); +#if MO_ENABLE_V201 + model.setVariableService(std::unique_ptr( + new VariableService(filesystem))); +#endif + #if !defined(MO_CUSTOM_UPDATER) && !defined(MO_CUSTOM_WS) model.setFirmwareService(std::unique_ptr( EspWiFi::makeFirmwareService(*context))); //instantiate FW service + ESP installation routine diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index abdaffad..80cf6cac 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -187,6 +188,17 @@ ResetService *Model::getResetService() const { return resetService.get(); } +#if MO_ENABLE_V201 +void Model::setVariableService(std::unique_ptr rs) { + this->variableService = std::move(rs); + capabilitiesUpdated = true; +} + +VariableService *Model::getVariableService() const { + return variableService.get(); +} +#endif + Clock& Model::getClock() { return clock; } diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 9493251b..895be67f 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -25,6 +25,10 @@ class ReservationService; class BootService; class ResetService; +#if MO_ENABLE_V201 +class VariableService; +#endif + class Model { private: std::vector> connectors; @@ -39,6 +43,11 @@ class Model { std::unique_ptr reservationService; std::unique_ptr bootService; std::unique_ptr resetService; + +#if MO_ENABLE_V201 + std::unique_ptr variableService; +#endif + Clock clock; ProtocolVersion version; @@ -95,6 +104,11 @@ class Model { void setResetService(std::unique_ptr rs); ResetService *getResetService() const; +#if MO_ENABLE_V201 + void setVariableService(std::unique_ptr vs); + VariableService *getVariableService() const; +#endif + Clock &getClock(); const ProtocolVersion& getVersion() const; diff --git a/src/MicroOcpp/Model/Variables/Variable.cpp b/src/MicroOcpp/Model/Variables/Variable.cpp new file mode 100644 index 00000000..53d28a37 --- /dev/null +++ b/src/MicroOcpp/Model/Variables/Variable.cpp @@ -0,0 +1,397 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs B05 - B06 + */ + +#include + +#if MO_ENABLE_V201 + +#include + +#include + +#include + +using namespace MicroOcpp; + +ComponentId::ComponentId(const char *name) : name(name) { } +ComponentId::ComponentId(const char *name, EvseId evse) : name(name), evse(evse) { } + +bool ComponentId::equals(const ComponentId& other) const { + return !strcmp(name, other.name) && + ((evse.id < 0 && other.evse.id < 0) || (evse.id == other.evse.id)) && // evseId undefined or equal + ((evse.connectorId < 0 && other.evse.connectorId < 0) || (evse.connectorId == other.evse.connectorId)); // connectorId undefined or equal +} + +bool Variable::AttributeTypeSet::has(AttributeType type) { + switch(type) { + case AttributeType::Actual: + return flag & (1 << 0); + case AttributeType::Target: + return flag & (1 << 1); + case AttributeType::MinSet: + return flag & (1 << 2); + case AttributeType::MaxSet: + return flag & (1 << 3); + } + MO_DBG_ERR("internal error"); + return false; +} + +Variable::AttributeTypeSet& Variable::AttributeTypeSet::set(AttributeType type) { + switch(type) { + case AttributeType::Actual: + flag |= (1 << 0); + break; + case AttributeType::Target: + flag |= (1 << 1); + break; + case AttributeType::MinSet: + flag |= (1 << 2); + break; + case AttributeType::MaxSet: + flag |= (1 << 3); + break; + default: + MO_DBG_ERR("internal error"); + break; + } + return *this; +} + +size_t Variable::AttributeTypeSet::count() { + return (flag & (1 << 0) ? 1 : 0) + + (flag & (1 << 1) ? 1 : 0) + + (flag & (1 << 2) ? 1 : 0) + + (flag & (1 << 3) ? 1 : 0); +} + +Variable::AttributeTypeSet::AttributeTypeSet(AttributeType attrType) { + set(attrType); +} + +Variable::Variable(AttributeTypeSet attributes) : attributes(attributes) { } + +Variable::~Variable() { + +} + +void Variable::setName(const char *name) { + this->variableName = name; +} +const char *Variable::getName() const { + return variableName; +} + +void Variable::setComponentId(const ComponentId& componentId) { + this->component = componentId; +} +const ComponentId& Variable::getComponentId() const { + return component; +} + +void Variable::setInt(int val, AttributeType) { + MO_DBG_ERR("type err"); + (void)0; +} +void Variable::setBool(bool val, AttributeType) { + MO_DBG_ERR("type err"); + (void)0; +} +bool Variable::setString(const char *val, AttributeType) { + MO_DBG_ERR("type err"); + return false; +} + +int Variable::getInt(AttributeType) { + MO_DBG_ERR("type err"); + return 0; +} +bool Variable::getBool(AttributeType) { + MO_DBG_ERR("type err"); + return false; +} +const char *Variable::getString(AttributeType) { + MO_DBG_ERR("type err"); + return nullptr; +} + +bool Variable::hasAttribute(AttributeType attrType) { + return attributes.has(attrType); +} + +void Variable::setVariableDataType(VariableCharacteristics::DataType dataType) { + this->dataType = dataType; +} +VariableCharacteristics::DataType Variable::getVariableDataType() { + return dataType; +} + +bool Variable::getSupportsMonitoring() { + return supportsMonitoring; +} +void Variable::setSupportsMonitoring() { + supportsMonitoring = true; +} + +bool Variable::isRebootRequired() { + return rebootRequired; +} +void Variable::setRebootRequired() { + rebootRequired = true; +} + +void Variable::setMutability(Mutability m) { + this->mutability = m; +} +Variable::Mutability Variable::getMutability() { + return mutability; +} + +void Variable::setPersistent() { + persistent = true; +} +bool Variable::isPersistent() { + return persistent; +} + +void Variable::setConstant() { + constant = true; +} +bool Variable::isConstant() { + return constant; +} + +template +struct VariableSingleData { + T value = 0; + + T& get(Variable::AttributeType attribute) { + return value; + } +}; + +template +struct VariableFullData { + T actual = 0; + T target = 0; + T minSet = 0; + T maxSet = 0; + + T& get(Variable::AttributeType attribute) { + switch(attribute) { + case Variable::AttributeType::Actual: + return actual; + case Variable::AttributeType::Target: + return target; + case Variable::AttributeType::MinSet: + return minSet; + case Variable::AttributeType::MaxSet: + return maxSet; + } + MO_DBG_ERR("internal error"); + return actual; + } +}; + +template class VariableData> +class VariableInt : public Variable { +private: + VariableData value; + uint16_t writeCount = 0; + + #if MO_VARIABLE_TYPECHECK + AttributeTypeSet attributes; + #endif +public: + VariableInt(AttributeTypeSet attributes) : + Variable(attributes) + #if MO_VARIABLE_TYPECHECK + , attributes(attributes) + #endif + { + + } + + void setInt(int val, AttributeType attrType) override { + #if MO_VARIABLE_TYPECHECK + if (!attributes.has(attrType)) { + MO_DBG_ERR("type err"); + return; + } + #endif + value.get(attrType) = val; + writeCount++; + } + + int getInt(AttributeType attrType) override { + #if MO_VARIABLE_TYPECHECK + if (!attributes.has(attrType)) { + MO_DBG_ERR("type err"); + return 0; + } + #endif + return value.get(attrType); + } + + InternalDataType getInternalDataType() override { + return InternalDataType::Int; + } + + uint16_t getWriteCount() override { + return writeCount; + } +}; + +template class VariableData> +class VariableBool : public Variable { +private: + VariableData value; + uint16_t writeCount = 0; + + #if MO_VARIABLE_TYPECHECK + AttributeTypeSet attributes; + #endif +public: + VariableBool(AttributeTypeSet attributes) : + Variable(attributes) + #if MO_VARIABLE_TYPECHECK + , attributes(attributes) + #endif + { + + } + + void setBool(bool val, AttributeType attrType) override { + #if MO_VARIABLE_TYPECHECK + if (!attributes.has(attrType)) { + MO_DBG_ERR("type err"); + return; + } + #endif + value.get(attrType) = val; + writeCount++; + } + + bool getBool(AttributeType attrType) override { + #if MO_VARIABLE_TYPECHECK + if (!attributes.has(attrType)) { + MO_DBG_ERR("type err"); + return 0; + } + #endif + return value.get(attrType); + } + + InternalDataType getInternalDataType() override { + return InternalDataType::Bool; + } + + uint16_t getWriteCount() override { + return writeCount; + } +}; + +template class VariableData> +class VariableString : public Variable { +private: + VariableData value; + uint16_t writeCount = 0; + + #if MO_VARIABLE_TYPECHECK + AttributeTypeSet attributes; + #endif +public: + VariableString(AttributeTypeSet attributes) : + Variable(attributes) + #if MO_VARIABLE_TYPECHECK + , attributes(attributes) + #endif + { + + } + + ~VariableString() { + delete[] value.get(AttributeType::Actual); + value.get(AttributeType::Actual) = nullptr; + delete[] value.get(AttributeType::Target); + value.get(AttributeType::Target) = nullptr; + delete[] value.get(AttributeType::MinSet); + value.get(AttributeType::MinSet) = nullptr; + delete[] value.get(AttributeType::MaxSet); + value.get(AttributeType::MaxSet) = nullptr; + } + + bool setString(const char *val, AttributeType attrType) override { + #if MO_VARIABLE_TYPECHECK + if (!attributes.has(attrType)) { + MO_DBG_ERR("type err"); + return false; + } + #endif + + size_t len = strlen(val); + char *valNew = nullptr; + if (len != 0) { + valNew = new char[len + 1]; + if (!valNew) { + MO_DBG_ERR("OOM"); + return false; + } + } + delete[] value.get(attrType); + value.get(attrType) = valNew; + writeCount++; + return true; + } + + const char *getString(AttributeType attrType) override { + #if MO_VARIABLE_TYPECHECK + if (!attributes.has(attrType)) { + MO_DBG_ERR("type err"); + return 0; + } + #endif + return value.get(attrType) ? value.get(attrType) : ""; + } + + InternalDataType getInternalDataType() override { + return InternalDataType::String; + } + + uint16_t getWriteCount() override { + return writeCount; + } +}; + +std::unique_ptr MicroOcpp::makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes) { + switch(dtype) { + case Variable::InternalDataType::Int: + if (supportAttributes.count() > 1) { + return std::unique_ptr(new VariableInt(supportAttributes)); + } else { + return std::unique_ptr(new VariableInt(supportAttributes)); + } + case Variable::InternalDataType::Bool: + if (supportAttributes.count() > 1) { + return std::unique_ptr(new VariableBool(supportAttributes)); + } else { + return std::unique_ptr(new VariableBool(supportAttributes)); + } + case Variable::InternalDataType::String: + if (supportAttributes.count() > 1) { + return std::unique_ptr(new VariableString(supportAttributes)); + } else { + return std::unique_ptr(new VariableString(supportAttributes)); + } + } + + MO_DBG_ERR("internal error"); + return nullptr; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index fa383722..7ed98800 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -18,12 +18,16 @@ #if MO_ENABLE_V201 +#ifndef MO_VARIABLE_TYPECHECK +#define MO_VARIABLE_TYPECHECK 1 +#endif + namespace MicroOcpp { -//VariableCharacteristicsType (2.51) +// VariableCharacteristicsType (2.51) struct VariableCharacteristics { - //DataEnumType (3.26) + // DataEnumType (3.26) enum class DataType : uint8_t { string, decimal, @@ -43,9 +47,26 @@ struct VariableCharacteristics { //bool supportsMonitoring; //stored in Variable }; -/* - * Corresponds to VariableMonitoringType (2.52) - */ +// SetVariableStatusEnumType (3.79) +enum class SetVariableStatus : uint8_t { + Accepted, + Rejected, + UnknownComponent, + UnknownVariable, + NotSupportedAttributeType, + RebootRequired +}; + +// GetVariableStatusEnumType (3.41) +enum class GetVariableStatus : uint8_t { + Accepted, + Rejected, + UnknownComponent, + UnknownVariable, + NotSupportedAttributeType +}; + +// VariableMonitoringType (2.52) class VariableMonitor { public: //MonitorEnumType (3.55) @@ -64,7 +85,29 @@ class VariableMonitor { int severity; public: VariableMonitor() = delete; - VariableMonitor(int id, bool transaction, float value, Type type, int severity); + VariableMonitor(int id, bool transaction, float value, Type type, int severity) : + id(id), transaction(transaction), value(value), type(type), severity(severity) { } +}; + +// EVSEType (2.23) +struct EvseId { + int id; + int connectorId = -1; //optional + + EvseId(int id) : id(id) { } + EvseId(int id, int connectorId) : id(id), connectorId(connectorId) { } +}; + +// ComponentType (2.16) +struct ComponentId { + const char *name; // zero copy + //const char *instance; // not supported in this implementation + EvseId evse {-1}; + + ComponentId(const char *name = nullptr); + ComponentId(const char *name, EvseId evse); + + bool equals(const ComponentId& other) const; }; /* @@ -84,6 +127,16 @@ class Variable { MaxSet }; + struct AttributeTypeSet { + uint8_t flag = 0; + + bool has(Variable::AttributeType type); + AttributeTypeSet& set(Variable::AttributeType type); + size_t count(); + + AttributeTypeSet(AttributeType attrType = AttributeType::Actual); + }; + //MutabilityEnumType (3.58) enum class Mutability : uint8_t { ReadOnly, @@ -99,8 +152,7 @@ class Variable { }; private: const char *variableName = nullptr; - //const char *instance = nullptr; //<-- instance not supported in this implementation - const char *componentName = nullptr; + ComponentId component; // VariableCharacteristicsType (2.51) std::unique_ptr characteristics; //optional VariableCharacteristics @@ -113,17 +165,20 @@ class Variable { bool persistent = false; bool constant = false; - //VariableMonitoring - //std::vector monitors; -protected: - uint16_t writeCount = 0; //write access counter; used to check if this config has been changed + AttributeTypeSet attributes; + + // VariableMonitoringType (2.52) + //std::vector monitors; // uncomment when testing Monitors public: + Variable(AttributeTypeSet attributes); - void setName(const char *key); //zero-copy + virtual ~Variable(); + + void setName(const char *name); //zero-copy const char *getName() const; - void setComponentId(const char *componentId); //zero-copy - const char *getComponentId() const; + void setComponentId(const ComponentId& componentId); //zero-copy + const ComponentId& getComponentId() const; // set Value of Variable virtual void setInt(int val, AttributeType attrType = AttributeType::Actual); @@ -136,6 +191,7 @@ class Variable { virtual const char *getString(AttributeType attrType = AttributeType::Actual); //always returns c-string (empty if undefined) virtual InternalDataType getInternalDataType() = 0; //corresponds to MO internal value representation + bool hasAttribute(AttributeType attrType); void setVariableDataType(VariableCharacteristics::DataType dataType); //corresponds to OCPP DataEnumType (3.26) VariableCharacteristics::DataType getVariableDataType(); //corresponds to OCPP DataEnumType (3.26) @@ -155,9 +211,11 @@ class Variable { //bool addMonitor(int id, bool transaction, float value, VariableMonitor::Type type, int severity); - uint16_t getWriteCount(); //get write count (use this as a pre-check if the value changed) + virtual uint16_t getWriteCount() = 0; //get write count (use this as a pre-check if the value changed) }; +std::unique_ptr makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes); + } // namespace MicroOcpp #endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.cpp b/src/MicroOcpp/Model/Variables/VariableContainer.cpp new file mode 100644 index 00000000..b316b8a1 --- /dev/null +++ b/src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -0,0 +1,88 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs B05 - B06 + */ + +#include + +#if MO_ENABLE_V201 + +#include + +#include + +#include + +using namespace MicroOcpp; + +VariableContainer::~VariableContainer() { + +} + +VariableContainerVolatile::VariableContainerVolatile(const char *filename, bool accessible) : + VariableContainer(filename, accessible) { + +} + +VariableContainerVolatile::~VariableContainerVolatile() { + +} + +bool VariableContainerVolatile::load() { + return true; +} + +bool VariableContainerVolatile::save() { + return true; +} + +std::shared_ptr VariableContainerVolatile::createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes, const ComponentId& component, const char *variableName) { + auto res = makeVariable(dtype, attributes); + if (res) { + res->setName(variableName); + res->setComponentId(component); + } + return res; +} + +void VariableContainerVolatile::remove(Variable *config) { + auto it = variables.begin(); + while (it != variables.end()) { + if (it->get() == config) { + variables.erase(it); + return; + } + } +} + +size_t VariableContainerVolatile::size() const { + return variables.size(); +} + +Variable *VariableContainerVolatile::getVariable(size_t i) const { + return variables[i].get(); +} + +std::shared_ptr VariableContainerVolatile::getVariable(const ComponentId& component, const char *variableName) const { + auto it = variables.begin(); + while (it != variables.end()) { + if (!strcmp((*it)->getName(), variableName) && + (*it)->getComponentId().equals(component)) { + return *it; + } + } + return nullptr; +} + +void VariableContainerVolatile::add(std::shared_ptr variable) { + variables.push_back(std::move(variable)); +} + +std::unique_ptr MicroOcpp::makeVariableContainerVolatile(const char *filename, bool accessible) { + return std::unique_ptr(new VariableContainerVolatile(filename, accessible)); +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.h b/src/MicroOcpp/Model/Variables/VariableContainer.h index 954a396b..056231bb 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.h +++ b/src/MicroOcpp/Model/Variables/VariableContainer.h @@ -30,7 +30,7 @@ class VariableContainer { virtual ~VariableContainer(); const char *getFilename() {return filename;} - bool isAccessible() {return accessible;} //accessible by OCPP server or only used as internal persistent store + bool isAccessible() const {return accessible;} //accessible by OCPP server or only used as internal persistent store virtual bool load() = 0; //called at the end of mocpp_intialize, to load the Variables with the stored value virtual bool save() = 0; @@ -43,18 +43,16 @@ class VariableContainer { * Variable::InternalDataType dtype: internal low-level data type. Defines which value getters / setters are valid. * if dtype == InternalDataType::Int, then getInt() and setInt(...) are valid * if dtype == InternalDataType::String, then getString() and setString(...) are valid. Etc. - * const char *componentName: name of the Component to which this Variable(attribute) belongs (key attribute) + * const ComponentId& component: name of the Component to which this Variable(attribute) belongs (key attribute) * const char *variableName: name of the Variable to which this VariableAttribue belongs (key attribute) * Variable::Type type: type of the Attribute to create (key attribute) */ - virtual std::shared_ptr createVariable(Variable::InternalDataType dtype, const char *componentName, const char *variableName) = 0; // factory method + virtual std::shared_ptr createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes, const ComponentId& component, const char *variableName) = 0; // factory method virtual void remove(Variable *variable) = 0; - virtual size_t size() = 0; - virtual Variable *getVariable(size_t i) = 0; - virtual std::shared_ptr getVariable(const char *componentName, const char *variableName) = 0; - - virtual void loadStaticKey(Variable& variable, const char *variableName) { } //possible optimization: can replace internal key with passed static key + virtual size_t size() const = 0; + virtual Variable *getVariable(size_t i) const = 0; + virtual std::shared_ptr getVariable(const ComponentId& component, const char *variableName) const = 0; }; class VariableContainerVolatile : public VariableContainer { @@ -62,15 +60,16 @@ class VariableContainerVolatile : public VariableContainer { std::vector> variables; public: VariableContainerVolatile(const char *filename, bool accessible); + ~VariableContainerVolatile(); //VariableContainer definitions bool load() override; bool save() override; - std::shared_ptr createVariable(Variable::InternalDataType dtype, const char *componentName, const char *variableName) override; + std::shared_ptr createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes, const ComponentId& component, const char *variableName) override; void remove(Variable *config) override; - size_t size() override; - Variable *getVariable(size_t i) override; - std::shared_ptr getVariable(const char *componentName, const char *variableName) override; + size_t size() const override; + Variable *getVariable(size_t i) const override; + std::shared_ptr getVariable(const ComponentId& component, const char *variableName) const override; //add custom Variable object void add(std::shared_ptr variable); diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp new file mode 100644 index 00000000..591734e7 --- /dev/null +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -0,0 +1,358 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs B05 - B06 + */ + +#include + +#if MO_ENABLE_V201 + +#include + +#include +#include + +#include + +namespace MicroOcpp { + +VariableValidator *VariableService::getValidatorInt(const char *variableName) { + for (auto& validator : validatorInt) { + if (!strcmp(validator.key, variableName)) { + return &validator; + } + } + return nullptr; +} + +VariableValidator *VariableService::getValidatorBool(const char *variableName) { + for (auto& validator : validatorBool) { + if (!strcmp(validator.key, variableName)) { + return &validator; + } + } + return nullptr; +} + +VariableValidator *VariableService::getValidatorString(const char *variableName) { + for (auto& validator : validatorString) { + if (!strcmp(validator.key, variableName)) { + return &validator; + } + } + return nullptr; +} + +std::unique_ptr VariableService::createContainer(const char *filename, bool accessible) const { + //create non-persistent Variable store (i.e. lives only in RAM) if + // - Flash FS usage is switched off OR + // - Filename starts with "/volatile" +// if (!filesystem || +// !strncmp(filename, MO_VARIABLE_VOLATILE, strlen(MO_VARIABLE_VOLATILE))) { + return makeVariableContainerVolatile(filename, accessible); +// } else { +// //create persistent Variable store. This is the normal case +// return makeVariableContainerFlash(filesystem, filename, accessible); +// } +} + +void VariableService::addContainer(std::shared_ptr container) { + containers.push_back(std::move(container)); +} + +std::shared_ptr VariableService::getContainer(const char *filename) { + for (auto& container : containers) { + if (!strcmp(filename, container->getFilename())) { + return container; + } + } + return nullptr; +} + +VariableContainer *VariableService::declareContainer(const char *filename, bool accessible) { + + auto container = getContainer(filename); + + if (!container) { + MO_DBG_DEBUG("init new configurations container: %s", filename); + + container = createContainer(filename, accessible); + if (!container) { + MO_DBG_ERR("OOM"); + return nullptr; + } + containers.push_back(container); + } + + if (container->isAccessible() != accessible) { + MO_DBG_ERR("%s: conflicting accessibility declarations (expect %s)", filename, container->isAccessible() ? "accessible" : "inaccessible"); + (void)0; + } + + return container.get(); +} + +std::shared_ptr VariableService::loadVariable(Variable::InternalDataType type, const ComponentId& component, const char *name, bool accessible) { + for (auto& container : containers) { + if (auto variable = container->getVariable(component, name)) { + if (variable->getInternalDataType() != type) { + MO_DBG_ERR("conflicting type for %s - remove old variable", name); + container->remove(variable.get()); + continue; + } + if (container->isAccessible() != accessible) { + MO_DBG_ERR("conflicting accessibility for %s", name); + (void)0; + } + return variable; + } + } + return nullptr; +} + +template +bool loadVariableFactoryDefault(Variable& variable, T factoryDef); + +template<> +bool loadVariableFactoryDefault(Variable& variable, int factoryDef) { + variable.setInt(factoryDef); + return true; +} + +template<> +bool loadVariableFactoryDefault(Variable& variable, bool factoryDef) { + variable.setBool(factoryDef); + return true; +} + +template<> +bool loadVariableFactoryDefault(Variable& variable, const char *factoryDef) { + return variable.setString(factoryDef); +} + +void loadVariableCharacteristics(Variable& variable, Variable::Mutability mutability, bool rebootRequired) { + if (variable.getMutability() == Variable::Mutability::ReadWrite) { + variable.setMutability(mutability); + } + + if (rebootRequired) { + variable.setRebootRequired(); + } +} + +template +Variable::InternalDataType convertType(); + +template<> Variable::InternalDataType convertType() {return Variable::InternalDataType::Int;} +template<> Variable::InternalDataType convertType() {return Variable::InternalDataType::Bool;} +template<> Variable::InternalDataType convertType() {return Variable::InternalDataType::String;} + +template +std::shared_ptr VariableService::declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath, Variable::Mutability mutability, Variable::AttributeTypeSet attributes, bool rebootRequired, bool accessible) { + + std::shared_ptr res = loadVariable(convertType(), component, name, accessible); + if (!res) { + auto container = declareContainer(containerPath, accessible); + if (!container) { + return nullptr; + } + + res = container->createVariable(convertType(), attributes, component, name); + if (!res) { + return nullptr; + } + + if (!loadVariableFactoryDefault(*res.get(), factoryDefault)) { + container->remove(res.get()); + return nullptr; + } + } + + loadVariableCharacteristics(*res.get(), mutability, rebootRequired); + return res; +} + +template std::shared_ptr VariableService::declareVariable(const char*, int, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template std::shared_ptr VariableService::declareVariable(const char*, bool, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template std::shared_ptr VariableService::declareVariable(const char*,const char*, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); + +SetVariableStatus VariableService::setVariable(Variable::AttributeType attrType, const char *value, const ComponentId& component, const char *variableName) { + + Variable *variable = nullptr; + + bool foundComponent = false; + for (const auto& container : containers) { + if (!container->isAccessible()) { + // container intended for internal use only + continue; + } + for (size_t i = 0; i < container->size(); i++) { + auto entry = container->getVariable(i); + + if (entry->getComponentId().equals(component)) { + foundComponent = true; + + if (!strcmp(entry->getName(), variableName)) { + // found variable. Search terminated in this block + + variable = entry; + break; + } + } + } + if (variable) { + // result found in inner for-loop + break; + } + } + + if (!variable) { + if (foundComponent) { + return SetVariableStatus::UnknownVariable; + } else { + return SetVariableStatus::UnknownComponent; + } + } + + if (variable->getMutability() == Variable::Mutability::ReadOnly) { + return SetVariableStatus::Rejected; + } + + if (!variable->hasAttribute(attrType)) { + return SetVariableStatus::NotSupportedAttributeType; + } + + //write config + + /* + * Try to interpret input as number + */ + + bool convertibleInt = true; + int numInt = 0; + bool convertibleBool = true; + bool numBool = false; + + int nDigits = 0, nNonDigits = 0, nDots = 0, nSign = 0; //"-1.234" has 4 digits, 0 nonDigits, 1 dot and 1 sign. Don't allow comma as seperator. Don't allow e-expressions (e.g. 1.23e-7) + for (const char *c = value; *c; ++c) { + if (*c >= '0' && *c <= '9') { + //int interpretation + if (nDots == 0) { //only append number if before floating point + nDigits++; + numInt *= 10; + numInt += *c - '0'; + } + } else if (*c == '.') { + nDots++; + } else if (c == value && *c == '-') { + nSign++; + } else { + nNonDigits++; + } + } + + if (nSign == 1) { + numInt = -numInt; + } + + int INT_MAXDIGITS; //plausibility check: this allows a numerical range of (-999,999,999 to 999,999,999), or (-9,999 to 9,999) respectively + if (sizeof(int) >= 4UL) + INT_MAXDIGITS = 9; + else + INT_MAXDIGITS = 4; + + if (nNonDigits > 0 || nDigits == 0 || nSign > 1 || nDots > 1) { + convertibleInt = false; + } + + if (nDigits > INT_MAXDIGITS) { + MO_DBG_DEBUG("Possible integer overflow: key = %s, value = %s", variableName, value); + convertibleInt = false; + } + + if (tolower(value[0]) == 't' && tolower(value[1]) == 'r' && tolower(value[2]) == 'u' && tolower(value[3]) == 'e' && !value[4]) { + numBool = true; + } else if (tolower(value[0]) == 'f' && tolower(value[1]) == 'a' && tolower(value[2]) == 'l' && tolower(value[3]) == 's' && tolower(value[4]) == 'e' && !value[5]) { + numBool = false; + } else if (convertibleInt) { + numBool = numInt != 0; + } else { + convertibleBool = false; + } + + // validate and store (parsed) value to Config + + if (variable->getInternalDataType() == Variable::InternalDataType::Int && convertibleInt) { + auto validator = getValidatorInt(variableName); + if (validator && !validator->validate(numInt)) { + MO_DBG_WARN("validation failed for variable=%s", variableName); + return SetVariableStatus::Rejected; + } + variable->setInt(numInt); + } else if (variable->getInternalDataType() == Variable::InternalDataType::Bool && convertibleBool) { + auto validator = getValidatorBool(variableName); + if (validator && !validator->validate(numBool)) { + MO_DBG_WARN("validation failed for variable=%s", variableName); + return SetVariableStatus::Rejected; + } + variable->setBool(numBool); + } else if (variable->getInternalDataType() == Variable::InternalDataType::String) { + auto validator = getValidatorString(variableName); + if (validator && !validator->validate(value)) { + MO_DBG_WARN("validation failed for variable=%s", variableName); + return SetVariableStatus::Rejected; + } + variable->setString(value); + } else { + MO_DBG_WARN("Value has incompatible type"); + return SetVariableStatus::Rejected; + } + + if (variable->isRebootRequired()) { + return SetVariableStatus::RebootRequired; + } + + return SetVariableStatus::Accepted; +} + +GetVariableStatus VariableService::getVariable(Variable::AttributeType attrType, const ComponentId& component, const char *variableName, Variable **result) { + + bool foundComponent = false; + for (const auto& container : containers) { + for (size_t i = 0; i < container->size(); i++) { + auto variable = container->getVariable(i); + + if (variable->getComponentId().equals(component)) { + foundComponent = true; + + if (!strcmp(variable->getName(), variableName)) { + // found variable. Search terminated in this block + + if (variable->getMutability() == Variable::Mutability::WriteOnly) { + return GetVariableStatus::Rejected; + } + + if (variable->hasAttribute(attrType)) { + *result = variable; + return GetVariableStatus::Accepted; + } else { + return GetVariableStatus::NotSupportedAttributeType; + } + } + } + } + } + + if (foundComponent) { + return GetVariableStatus::UnknownVariable; + } else { + return GetVariableStatus::UnknownComponent; + } +} + +} // namespace MicroOcpp + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/VariableService.h b/src/MicroOcpp/Model/Variables/VariableService.h new file mode 100644 index 00000000..da63a2ce --- /dev/null +++ b/src/MicroOcpp/Model/Variables/VariableService.h @@ -0,0 +1,87 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs B05 - B06 + */ + +#ifndef MO_VARIABLESERVICE_H +#define MO_VARIABLESERVICE_H + +#include +#include +#include +#include +#include + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +#ifndef MO_VARIABLE_FN +#define MO_VARIABLE_FN (MO_FILENAME_PREFIX "ocpp-vars.jsn") +#endif + +#ifndef MO_VARIABLE_VOLATILE +#define MO_VARIABLE_VOLATILE "/volatile" +#endif + +#ifndef MO_VARIABLE_INTERNAL_FN +#define MO_VARIABLE_INTERNAL_FN (MO_FILENAME_PREFIX "mo-vars.jsn") +#endif + +namespace MicroOcpp { + +template +struct VariableValidator { + const char *key = nullptr; + std::function validate; + VariableValidator(const char *key, std::function validate) : key(key), validate(validate) { } +}; + +class VariableService { +private: + std::shared_ptr filesystem; + std::vector> containers; + + std::vector> validatorInt; + std::vector> validatorBool; + std::vector> validatorString; + + VariableValidator *getValidatorInt(const char *variableName); + VariableValidator *getValidatorBool(const char *variableName); + VariableValidator *getValidatorString(const char *variableName); + + std::unique_ptr createContainer(const char *filename, bool accessible) const; + + VariableContainer *declareContainer(const char *filename, bool accessible); + + std::shared_ptr loadVariable(Variable::InternalDataType type, const ComponentId& component, const char *name, bool accessible); + +public: + VariableService(std::shared_ptr filesystem) : filesystem(filesystem) { } + + template + std::shared_ptr declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath = MO_VARIABLE_FN, Variable::Mutability mutability = Variable::Mutability::ReadWrite, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false, bool accessible = true); + + void addContainer(std::shared_ptr container); + + std::shared_ptr getContainer(const char *filename); + + SetVariableStatus setVariable(Variable::AttributeType attrType, const char *attrVal, const ComponentId& component, const char *variableName); + + bool commit(); + + GetVariableStatus getVariable(Variable::AttributeType attrType, const ComponentId& component, const char *variableName, Variable **result); +}; + +} // namespace MicroOcpp + +#endif // MO_ENABLE_V201 + +#endif diff --git a/src/MicroOcpp/Operations/GetVariables.cpp b/src/MicroOcpp/Operations/GetVariables.cpp new file mode 100644 index 00000000..4c0639db --- /dev/null +++ b/src/MicroOcpp/Operations/GetVariables.cpp @@ -0,0 +1,220 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +#include //for tolower + +using MicroOcpp::Ocpp201::GetVariables; + +GetVariables::GetVariables(VariableService& variableService) : variableService(variableService) { + +} + +const char* GetVariables::getOperationType(){ + return "GetVariables"; +} + +void GetVariables::processReq(JsonObject payload) { + for (JsonObject getVariable : payload["getVariableData"].as()) { + + queries.emplace_back(); + auto& data = queries.back(); + + if (getVariable.containsKey("attributeType")) { + const char *attributeTypeCstr = getVariable["attributeType"] | "_Undefined"; + if (!strcmp(attributeTypeCstr, "Actual")) { + data.attributeType = Variable::AttributeType::Actual; + } else if (!strcmp(attributeTypeCstr, "Target")) { + data.attributeType = Variable::AttributeType::Target; + } else if (!strcmp(attributeTypeCstr, "MinSet")) { + data.attributeType = Variable::AttributeType::MinSet; + } else if (!strcmp(attributeTypeCstr, "MaxSet")) { + data.attributeType = Variable::AttributeType::MaxSet; + } else { + errorCode = "FormationViolation"; + MO_DBG_ERR("invalid attributeType"); + return; + } + } + + const char *componentNameCstr = getVariable["component"]["name"] | (const char*) nullptr; + const char *variableNameCstr = getVariable["variable"]["name"] | (const char*) nullptr; + + if (!componentNameCstr || + !variableNameCstr) { + errorCode = "FormationViolation"; + return; + } + + data.componentName = componentNameCstr; + data.variableName = variableNameCstr; + + // TODO check against ConfigurationValueSize + + data.componentEvseId = getVariable["component"]["evse"]["id"] | -1; + data.componentEvseConnectorId = getVariable["component"]["evse"]["connectorId"] | -1; + + if (getVariable["component"].containsKey("evse") && data.componentEvseId < 0) { + errorCode = "FormationViolation"; + MO_DBG_ERR("malformatted / missing evseId"); + return; + } + } + + if (queries.empty()) { + errorCode = "FormationViolation"; + return; + } +} + +std::unique_ptr GetVariables::createConf(){ + + // process GetVariables queries + for (auto& query : queries) { + query.attributeStatus = variableService.getVariable( + query.attributeType, + ComponentId(query.componentName.c_str(), + EvseId(query.componentEvseId, query.componentEvseConnectorId)), + query.variableName.c_str(), + &query.variable); + } + + #define VALUE_BUFSIZE 30 // for primitives (int) + + size_t capacity = JSON_ARRAY_SIZE(queries.size()); + for (const auto& data : queries) { + size_t valueCapacity = 0; + if (data.variable) { + switch (data.variable->getInternalDataType()) { + case Variable::InternalDataType::Int: + // measure int size by printing to a dummy buf + char valbuf [VALUE_BUFSIZE]; + auto ret = snprintf(valbuf, VALUE_BUFSIZE, "%i", data.variable->getInt()); + if (ret < 0 || ret >= VALUE_BUFSIZE) { + continue; + } + valueCapacity = (size_t) ret + 1; + break; + case Variable::InternalDataType::Bool: + // bool will be stored in zero-copy mode (string literal "true" or "false") + valueCapacity = 0; + break; + case Variable::InternalDataType::String: + valueCapacity = strlen(data.variable->getString()); // TODO limit by ReportingValueSize + break; + default: + MO_DBG_ERR("internal error"); + break; + } + } + + capacity += + JSON_OBJECT_SIZE(5) + // getVariableResult + valueCapacity + // capacity needed for storing the value + JSON_OBJECT_SIZE(2) + // component + data.componentName.length() + 1 + + JSON_OBJECT_SIZE(2) + // evse + JSON_OBJECT_SIZE(2) + // variable + data.variableName.length() + 1; + } + + auto doc = std::unique_ptr(new DynamicJsonDocument(capacity)); + + JsonObject payload = doc->to(); + JsonArray getVariableResult = payload["getVariableResult"]; + + for (const auto& data : queries) { + JsonObject getVariable = getVariableResult.add(); + + const char *attributeStatusCstr = "Rejected"; + switch (data.attributeStatus) { + case GetVariableStatus::Accepted: + attributeStatusCstr = "Accepted"; + break; + case GetVariableStatus::Rejected: + attributeStatusCstr = "Rejected"; + break; + case GetVariableStatus::UnknownComponent: + attributeStatusCstr = "UnknownComponent"; + break; + case GetVariableStatus::UnknownVariable: + attributeStatusCstr = "UnknownVariable"; + break; + case GetVariableStatus::NotSupportedAttributeType: + attributeStatusCstr = "NotSupportedAttributeType"; + break; + default: + MO_DBG_ERR("internal error"); + break; + } + getVariable["attributeStatus"] = attributeStatusCstr; + + const char *attributeTypeCstr = nullptr; + switch (data.attributeType) { + case Variable::AttributeType::Actual: + // leave blank when Actual + break; + case Variable::AttributeType::Target: + attributeTypeCstr = "Target"; + break; + case Variable::AttributeType::MinSet: + attributeTypeCstr = "MinSet"; + break; + case Variable::AttributeType::MaxSet: + attributeTypeCstr = "MaxSet"; + break; + default: + MO_DBG_ERR("internal error"); + break; + } + if (attributeTypeCstr) { + getVariable["attributeType"] = attributeTypeCstr; + } + + if (data.variable) { + switch (data.variable->getInternalDataType()) { + case Variable::InternalDataType::Int: + char valbuf [VALUE_BUFSIZE]; + auto ret = snprintf(valbuf, VALUE_BUFSIZE, "%i", data.variable->getInt()); + if (ret < 0 || ret >= VALUE_BUFSIZE) { + break; + } + getVariable["attributeValue"] = valbuf; + break; + case Variable::InternalDataType::Bool: + getVariable["attributeValue"] = data.variable->getBool() ? "true" : "false"; + break; + case Variable::InternalDataType::String: + getVariable["attributeValue"] = (char*) data.variable->getString(); // force zero-copy mode + break; + default: + MO_DBG_ERR("internal error"); + break; + } + } + + getVariable["component"]["name"] = (char*) data.componentName.c_str(); // force copy-mode + + if (data.componentEvseId >= 0) { + getVariable["component"]["evse"]["id"] = data.componentEvseId; + } + + if (data.componentEvseConnectorId >= 0) { + getVariable["component"]["evse"]["connectorId"] = data.componentEvseConnectorId; + } + + getVariable["variable"]["name"] = (char*) data.variableName.c_str(); // force copy-mode + } + + return doc; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/GetVariables.h b/src/MicroOcpp/Operations/GetVariables.h new file mode 100644 index 00000000..a28f744b --- /dev/null +++ b/src/MicroOcpp/Operations/GetVariables.h @@ -0,0 +1,63 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_GETVARIABLES_H +#define MO_GETVARIABLES_H + +#include + +#if MO_ENABLE_V201 + +#include +#include + +#include +#include + +namespace MicroOcpp { + +class VariableService; + +namespace Ocpp201 { + +// GetVariableDataType (2.25) and +// GetVariableResultType (2.26) +struct GetVariableData { + // GetVariableDataType + Variable::AttributeType attributeType = Variable::AttributeType::Actual; + std::string componentName; + int componentEvseId = -1; + int componentEvseConnectorId = -1; + std::string variableName; + + // GetVariableResultType + GetVariableStatus attributeStatus; + Variable *variable = nullptr; +}; + +class GetVariables : public Operation { +private: + VariableService& variableService; + std::vector queries; + + const char *errorCode = nullptr; +public: + GetVariables(VariableService& variableService); + + const char* getOperationType() override; + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} + +}; + +} //namespace Ocpp201 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V201 + +#endif diff --git a/src/MicroOcpp/Operations/SetVariables.cpp b/src/MicroOcpp/Operations/SetVariables.cpp new file mode 100644 index 00000000..eeb3a445 --- /dev/null +++ b/src/MicroOcpp/Operations/SetVariables.cpp @@ -0,0 +1,175 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include +#include +#include + +using MicroOcpp::Ocpp201::SetVariables; + +SetVariables::SetVariables(VariableService& variableService) : variableService(variableService) { + +} + +const char* SetVariables::getOperationType(){ + return "SetVariables"; +} + +void SetVariables::processReq(JsonObject payload) { + for (JsonObject setVariable : payload["setVariableData"].as()) { + + queries.emplace_back(); + auto& data = queries.back(); + + if (setVariable.containsKey("attributeType")) { + const char *attributeTypeCstr = setVariable["attributeType"] | "_Undefined"; + if (!strcmp(attributeTypeCstr, "Actual")) { + data.attributeType = Variable::AttributeType::Actual; + } else if (!strcmp(attributeTypeCstr, "Target")) { + data.attributeType = Variable::AttributeType::Target; + } else if (!strcmp(attributeTypeCstr, "MinSet")) { + data.attributeType = Variable::AttributeType::MinSet; + } else if (!strcmp(attributeTypeCstr, "MaxSet")) { + data.attributeType = Variable::AttributeType::MaxSet; + } else { + errorCode = "FormationViolation"; + MO_DBG_ERR("invalid attributeType"); + return; + } + } + + const char *attributeValueCstr = setVariable["attributeValue"] | (const char*) nullptr; + const char *componentNameCstr = setVariable["component"]["name"] | (const char*) nullptr; + const char *variableNameCstr = setVariable["variable"]["name"] | (const char*) nullptr; + + if (!attributeValueCstr || + !componentNameCstr || + !variableNameCstr) { + errorCode = "FormationViolation"; + return; + } + + data.attributeValue = attributeValueCstr; + data.componentName = componentNameCstr; + data.variableName = variableNameCstr; + + // TODO check against ConfigurationValueSize + + data.componentEvseId = setVariable["component"]["evse"]["id"] | -1; + data.componentEvseConnectorId = setVariable["component"]["evse"]["connectorId"] | -1; + + if (setVariable["component"].containsKey("evse") && data.componentEvseId < 0) { + errorCode = "FormationViolation"; + MO_DBG_ERR("malformatted / missing evseId"); + return; + } + } + + if (queries.empty()) { + errorCode = "FormationViolation"; + return; + } + + MO_DBG_DEBUG("processing %zu setVariable queries", queries.size()); + + for (auto query : queries) { + query.attributeStatus = variableService.setVariable( + query.attributeType, + query.attributeValue, + ComponentId(query.componentName.c_str(), + EvseId(query.componentEvseId, query.componentEvseConnectorId)), + query.variableName.c_str()); + } + + variableService.commit(); +} + +std::unique_ptr SetVariables::createConf(){ + size_t capacity = JSON_ARRAY_SIZE(queries.size()); + for (const auto& data : queries) { + capacity += + JSON_OBJECT_SIZE(5) + // setVariableResult + JSON_OBJECT_SIZE(2) + // component + data.componentName.length() + 1 + + JSON_OBJECT_SIZE(2) + // evse + JSON_OBJECT_SIZE(2) + // variable + data.variableName.length() + 1; + } + auto doc = std::unique_ptr(new DynamicJsonDocument(capacity)); + + JsonObject payload = doc->to(); + JsonArray setVariableResult = payload["setVariableResult"]; + + for (const auto& data : queries) { + JsonObject setVariable = setVariableResult.add(); + + const char *attributeTypeCstr = nullptr; + switch (data.attributeType) { + case Variable::AttributeType::Actual: + // leave blank when Actual + break; + case Variable::AttributeType::Target: + attributeTypeCstr = "Target"; + break; + case Variable::AttributeType::MinSet: + attributeTypeCstr = "MinSet"; + break; + case Variable::AttributeType::MaxSet: + attributeTypeCstr = "MaxSet"; + break; + default: + MO_DBG_ERR("internal error"); + break; + } + if (attributeTypeCstr) { + setVariable["attributeType"] = attributeTypeCstr; + } + + const char *attributeStatusCstr = "Rejected"; + switch (data.attributeStatus) { + case SetVariableStatus::Accepted: + attributeStatusCstr = "Accepted"; + break; + case SetVariableStatus::Rejected: + attributeStatusCstr = "Rejected"; + break; + case SetVariableStatus::UnknownComponent: + attributeStatusCstr = "UnknownComponent"; + break; + case SetVariableStatus::UnknownVariable: + attributeStatusCstr = "UnknownVariable"; + break; + case SetVariableStatus::NotSupportedAttributeType: + attributeStatusCstr = "NotSupportedAttributeType"; + break; + case SetVariableStatus::RebootRequired: + attributeStatusCstr = "RebootRequired"; + break; + default: + MO_DBG_ERR("internal error"); + break; + } + setVariable["attributeStatus"] = attributeStatusCstr; + + setVariable["component"]["name"] = (char*) data.componentName.c_str(); // force copy-mode + + if (data.componentEvseId >= 0) { + setVariable["component"]["evse"]["id"] = data.componentEvseId; + } + + if (data.componentEvseConnectorId >= 0) { + setVariable["component"]["evse"]["connectorId"] = data.componentEvseConnectorId; + } + + setVariable["variable"]["name"] = (char*) data.variableName.c_str(); // force copy-mode + } + + return doc; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/SetVariables.h b/src/MicroOcpp/Operations/SetVariables.h index 9895dec4..83527a4e 100644 --- a/src/MicroOcpp/Operations/SetVariables.h +++ b/src/MicroOcpp/Operations/SetVariables.h @@ -9,23 +9,41 @@ #if MO_ENABLE_V201 -#include +#include +#include +#include #include namespace MicroOcpp { +class VariableService; + namespace Ocpp201 { +// SetVariableDataType (2.44) and +// SetVariableResultType (2.45) +struct SetVariableData { + // SetVariableDataType + Variable::AttributeType attributeType = Variable::AttributeType::Actual; + const char *attributeValue; // will become invalid after processReq + std::string componentName; + int componentEvseId = -1; + int componentEvseConnectorId = -1; + std::string variableName; + + // SetVariableResultType + SetVariableStatus attributeStatus; +}; + class SetVariables : public Operation { private: - const char *attributeType = nullptr; - const char *attributeStatus = nullptr; - Variable *variable = nullptr; //contains ptr to `component` + VariableService& variableService; + std::vector queries; const char *errorCode = nullptr; public: - SetVariables(); + SetVariables(VariableService& variableService); const char* getOperationType() override; From 71abdd8a854dc8d8b71e521d55d9eaf2db8a71a0 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 24 Feb 2024 17:39:17 +0100 Subject: [PATCH 08/23] lifetime of Variable depends on its Container --- src/MicroOcpp/Model/Variables/Variable.cpp | 8 ++++ src/MicroOcpp/Model/Variables/Variable.h | 5 ++ .../Model/Variables/VariableContainer.cpp | 30 ++++-------- .../Model/Variables/VariableContainer.h | 23 ++++----- .../Model/Variables/VariableService.cpp | 47 ++++++++++++++----- .../Model/Variables/VariableService.h | 8 ++-- 6 files changed, 69 insertions(+), 52 deletions(-) diff --git a/src/MicroOcpp/Model/Variables/Variable.cpp b/src/MicroOcpp/Model/Variables/Variable.cpp index 53d28a37..65ff402e 100644 --- a/src/MicroOcpp/Model/Variables/Variable.cpp +++ b/src/MicroOcpp/Model/Variables/Variable.cpp @@ -166,6 +166,13 @@ bool Variable::isConstant() { return constant; } +void Variable::detach() { + detached = true; +} +bool Variable::isDetached() { + return detached; +} + template struct VariableSingleData { T value = 0; @@ -342,6 +349,7 @@ class VariableString : public Variable { MO_DBG_ERR("OOM"); return false; } + memcpy(valNew, val, len + 1); } delete[] value.get(attrType); value.get(attrType) = valNew; diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index 7ed98800..69839d5c 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -167,6 +167,8 @@ class Variable { AttributeTypeSet attributes; + bool detached = false; // MO-internal: if a conflicting declaration comes in, discard the old Variable + // VariableMonitoringType (2.52) //std::vector monitors; // uncomment when testing Monitors public: @@ -212,6 +214,9 @@ class Variable { //bool addMonitor(int id, bool transaction, float value, VariableMonitor::Type type, int severity); virtual uint16_t getWriteCount() = 0; //get write count (use this as a pre-check if the value changed) + + void detach(); + bool isDetached(); }; std::unique_ptr makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes); diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.cpp b/src/MicroOcpp/Model/Variables/VariableContainer.cpp index b316b8a1..1c71344a 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.cpp +++ b/src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -39,23 +39,13 @@ bool VariableContainerVolatile::save() { return true; } -std::shared_ptr VariableContainerVolatile::createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes, const ComponentId& component, const char *variableName) { - auto res = makeVariable(dtype, attributes); - if (res) { - res->setName(variableName); - res->setComponentId(component); - } - return res; +std::unique_ptr VariableContainerVolatile::createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes) { + return makeVariable(dtype, attributes); } -void VariableContainerVolatile::remove(Variable *config) { - auto it = variables.begin(); - while (it != variables.end()) { - if (it->get() == config) { - variables.erase(it); - return; - } - } +bool VariableContainerVolatile::add(std::unique_ptr variable) { + variables.push_back(std::move(variable)); + return true; } size_t VariableContainerVolatile::size() const { @@ -66,21 +56,17 @@ Variable *VariableContainerVolatile::getVariable(size_t i) const { return variables[i].get(); } -std::shared_ptr VariableContainerVolatile::getVariable(const ComponentId& component, const char *variableName) const { +Variable *VariableContainerVolatile::getVariable(const ComponentId& component, const char *variableName) const { auto it = variables.begin(); - while (it != variables.end()) { + for (auto it = variables.begin(); it != variables.end(); it++) { if (!strcmp((*it)->getName(), variableName) && (*it)->getComponentId().equals(component)) { - return *it; + return it->get(); } } return nullptr; } -void VariableContainerVolatile::add(std::shared_ptr variable) { - variables.push_back(std::move(variable)); -} - std::unique_ptr MicroOcpp::makeVariableContainerVolatile(const char *filename, bool accessible) { return std::unique_ptr(new VariableContainerVolatile(filename, accessible)); } diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.h b/src/MicroOcpp/Model/Variables/VariableContainer.h index 056231bb..90ee41c9 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.h +++ b/src/MicroOcpp/Model/Variables/VariableContainer.h @@ -36,28 +36,26 @@ class VariableContainer { virtual bool save() = 0; /* - * Factory method to create Variable objects which are managed by VariableContainer. + * Factory method to create Variable objects. This doesn't add the returned Variable to the managed + * variable store. Instead, the caller must add the returned Variable via `add(...)` * The function signature consists of the requested low-level data type (Int, Bool, or String) and * a composite key to identify the Variable to create (componentName x variableName x attribute type) * * Variable::InternalDataType dtype: internal low-level data type. Defines which value getters / setters are valid. * if dtype == InternalDataType::Int, then getInt() and setInt(...) are valid * if dtype == InternalDataType::String, then getString() and setString(...) are valid. Etc. - * const ComponentId& component: name of the Component to which this Variable(attribute) belongs (key attribute) - * const char *variableName: name of the Variable to which this VariableAttribue belongs (key attribute) - * Variable::Type type: type of the Attribute to create (key attribute) */ - virtual std::shared_ptr createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes, const ComponentId& component, const char *variableName) = 0; // factory method - virtual void remove(Variable *variable) = 0; + virtual std::unique_ptr createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes) = 0; // factory method + virtual bool add(std::unique_ptr variable) = 0; virtual size_t size() const = 0; virtual Variable *getVariable(size_t i) const = 0; - virtual std::shared_ptr getVariable(const ComponentId& component, const char *variableName) const = 0; + virtual Variable *getVariable(const ComponentId& component, const char *variableName) const = 0; }; class VariableContainerVolatile : public VariableContainer { private: - std::vector> variables; + std::vector> variables; public: VariableContainerVolatile(const char *filename, bool accessible); ~VariableContainerVolatile(); @@ -65,14 +63,11 @@ class VariableContainerVolatile : public VariableContainer { //VariableContainer definitions bool load() override; bool save() override; - std::shared_ptr createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes, const ComponentId& component, const char *variableName) override; - void remove(Variable *config) override; + std::unique_ptr createVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet attributes) override; + bool add(std::unique_ptr config) override; size_t size() const override; Variable *getVariable(size_t i) const override; - std::shared_ptr getVariable(const ComponentId& component, const char *variableName) const override; - - //add custom Variable object - void add(std::shared_ptr variable); + Variable *getVariable(const ComponentId& component, const char *variableName) const override; }; std::unique_ptr makeVariableContainerVolatile(const char *filename, bool accessible); diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp index 591734e7..599c469a 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.cpp +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -95,12 +95,15 @@ VariableContainer *VariableService::declareContainer(const char *filename, bool return container.get(); } -std::shared_ptr VariableService::loadVariable(Variable::InternalDataType type, const ComponentId& component, const char *name, bool accessible) { +Variable *VariableService::getVariable(Variable::InternalDataType type, const ComponentId& component, const char *name, bool accessible) { for (auto& container : containers) { if (auto variable = container->getVariable(component, name)) { + if (variable->isDetached()) { + continue; + } if (variable->getInternalDataType() != type) { MO_DBG_ERR("conflicting type for %s - remove old variable", name); - container->remove(variable.get()); + variable->detach(); continue; } if (container->isAccessible() != accessible) { @@ -151,33 +154,53 @@ template<> Variable::InternalDataType convertType() {return Variable::Inte template<> Variable::InternalDataType convertType() {return Variable::InternalDataType::String;} template -std::shared_ptr VariableService::declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath, Variable::Mutability mutability, Variable::AttributeTypeSet attributes, bool rebootRequired, bool accessible) { +Variable *VariableService::declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath, Variable::Mutability mutability, Variable::AttributeTypeSet attributes, bool rebootRequired, bool accessible) { - std::shared_ptr res = loadVariable(convertType(), component, name, accessible); + auto res = getVariable(convertType(), component, name, accessible); if (!res) { auto container = declareContainer(containerPath, accessible); if (!container) { return nullptr; } - res = container->createVariable(convertType(), attributes, component, name); - if (!res) { + auto variable = container->createVariable(convertType(), attributes); + if (!variable) { return nullptr; } - if (!loadVariableFactoryDefault(*res.get(), factoryDefault)) { - container->remove(res.get()); + variable->setName(name); + variable->setComponentId(component); + + if (!loadVariableFactoryDefault(*variable, factoryDefault)) { + return nullptr; + } + + res = variable.get(); + + if (!container->add(std::move(variable))) { return nullptr; } } - loadVariableCharacteristics(*res.get(), mutability, rebootRequired); + loadVariableCharacteristics(*res, mutability, rebootRequired); return res; } -template std::shared_ptr VariableService::declareVariable(const char*, int, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); -template std::shared_ptr VariableService::declareVariable(const char*, bool, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); -template std::shared_ptr VariableService::declareVariable(const char*,const char*, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template Variable *VariableService::declareVariable(const char*, int, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template Variable *VariableService::declareVariable(const char*, bool, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template Variable *VariableService::declareVariable(const char*,const char*, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); + +bool VariableService::commit() { + bool success = true; + + for (auto& container : containers) { + if (!container->save()) { + success = false; + } + } + + return success; +} SetVariableStatus VariableService::setVariable(Variable::AttributeType attrType, const char *value, const ComponentId& component, const char *variableName) { diff --git a/src/MicroOcpp/Model/Variables/VariableService.h b/src/MicroOcpp/Model/Variables/VariableService.h index da63a2ce..46b69c32 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.h +++ b/src/MicroOcpp/Model/Variables/VariableService.h @@ -61,13 +61,15 @@ class VariableService { VariableContainer *declareContainer(const char *filename, bool accessible); - std::shared_ptr loadVariable(Variable::InternalDataType type, const ComponentId& component, const char *name, bool accessible); + Variable *getVariable(Variable::InternalDataType type, const ComponentId& component, const char *name, bool accessible); public: VariableService(std::shared_ptr filesystem) : filesystem(filesystem) { } template - std::shared_ptr declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath = MO_VARIABLE_FN, Variable::Mutability mutability = Variable::Mutability::ReadWrite, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false, bool accessible = true); + Variable *declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath = MO_VARIABLE_FN, Variable::Mutability mutability = Variable::Mutability::ReadWrite, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false, bool accessible = true); + + bool commit(); void addContainer(std::shared_ptr container); @@ -75,8 +77,6 @@ class VariableService { SetVariableStatus setVariable(Variable::AttributeType attrType, const char *attrVal, const ComponentId& component, const char *variableName); - bool commit(); - GetVariableStatus getVariable(Variable::AttributeType attrType, const ComponentId& component, const char *variableName, Variable **result); }; From 82fae7b3593c84390f3bbc39d8db9ce7a3500ab9 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 24 Feb 2024 17:39:46 +0100 Subject: [PATCH 09/23] Variables unit test (WIP) --- CMakeLists.txt | 3 + src/MicroOcpp/Operations/GetVariables.cpp | 8 +- src/MicroOcpp/Operations/SetVariables.cpp | 2 +- tests/Variables.cpp | 500 ++++++++++++++++++++++ 4 files changed, 509 insertions(+), 4 deletions(-) create mode 100644 tests/Variables.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index da65088f..64372054 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ set(MO_SRC src/MicroOcpp/Operations/GetConfiguration.cpp src/MicroOcpp/Operations/GetDiagnostics.cpp src/MicroOcpp/Operations/GetLocalListVersion.cpp + src/MicroOcpp/Operations/GetVariables.cpp src/MicroOcpp/Operations/Heartbeat.cpp src/MicroOcpp/Operations/MeterValues.cpp src/MicroOcpp/Operations/RemoteStartTransaction.cpp @@ -44,6 +45,7 @@ set(MO_SRC src/MicroOcpp/Operations/Reset.cpp src/MicroOcpp/Operations/SendLocalList.cpp src/MicroOcpp/Operations/SetChargingProfile.cpp + src/MicroOcpp/Operations/SetVariables.cpp src/MicroOcpp/Operations/StartTransaction.cpp src/MicroOcpp/Operations/StatusNotification.cpp src/MicroOcpp/Operations/StopTransaction.cpp @@ -130,6 +132,7 @@ set(MO_SRC_UNIT tests/Configuration.cpp tests/Reservation.cpp tests/LocalAuthList.cpp + tests/Variables.cpp ) add_executable(mo_unit_tests diff --git a/src/MicroOcpp/Operations/GetVariables.cpp b/src/MicroOcpp/Operations/GetVariables.cpp index 4c0639db..f4c3575b 100644 --- a/src/MicroOcpp/Operations/GetVariables.cpp +++ b/src/MicroOcpp/Operations/GetVariables.cpp @@ -94,7 +94,7 @@ std::unique_ptr GetVariables::createConf(){ size_t valueCapacity = 0; if (data.variable) { switch (data.variable->getInternalDataType()) { - case Variable::InternalDataType::Int: + case Variable::InternalDataType::Int: { // measure int size by printing to a dummy buf char valbuf [VALUE_BUFSIZE]; auto ret = snprintf(valbuf, VALUE_BUFSIZE, "%i", data.variable->getInt()); @@ -103,6 +103,7 @@ std::unique_ptr GetVariables::createConf(){ } valueCapacity = (size_t) ret + 1; break; + } case Variable::InternalDataType::Bool: // bool will be stored in zero-copy mode (string literal "true" or "false") valueCapacity = 0; @@ -132,7 +133,7 @@ std::unique_ptr GetVariables::createConf(){ JsonArray getVariableResult = payload["getVariableResult"]; for (const auto& data : queries) { - JsonObject getVariable = getVariableResult.add(); + JsonObject getVariable = getVariableResult.createNestedObject(); const char *attributeStatusCstr = "Rejected"; switch (data.attributeStatus) { @@ -181,7 +182,7 @@ std::unique_ptr GetVariables::createConf(){ if (data.variable) { switch (data.variable->getInternalDataType()) { - case Variable::InternalDataType::Int: + case Variable::InternalDataType::Int: { char valbuf [VALUE_BUFSIZE]; auto ret = snprintf(valbuf, VALUE_BUFSIZE, "%i", data.variable->getInt()); if (ret < 0 || ret >= VALUE_BUFSIZE) { @@ -189,6 +190,7 @@ std::unique_ptr GetVariables::createConf(){ } getVariable["attributeValue"] = valbuf; break; + } case Variable::InternalDataType::Bool: getVariable["attributeValue"] = data.variable->getBool() ? "true" : "false"; break; diff --git a/src/MicroOcpp/Operations/SetVariables.cpp b/src/MicroOcpp/Operations/SetVariables.cpp index eeb3a445..26e7b6a3 100644 --- a/src/MicroOcpp/Operations/SetVariables.cpp +++ b/src/MicroOcpp/Operations/SetVariables.cpp @@ -106,7 +106,7 @@ std::unique_ptr SetVariables::createConf(){ JsonArray setVariableResult = payload["setVariableResult"]; for (const auto& data : queries) { - JsonObject setVariable = setVariableResult.add(); + JsonObject setVariable = setVariableResult.createNestedObject(); const char *attributeTypeCstr = nullptr; switch (data.attributeType) { diff --git a/tests/Variables.cpp b/tests/Variables.cpp new file mode 100644 index 00000000..26187181 --- /dev/null +++ b/tests/Variables.cpp @@ -0,0 +1,500 @@ +#include +#include +#include "./catch2/catch.hpp" +#include "./helpers/testHelper.h" + +#include +#include +#include + +#include +#include +#include + +using namespace MicroOcpp; + +#define GET_CONFIG_ALL "[2,\"test-msg\",\"GetVariable\",{}]" +#define KNOWN_KEY "__ExistingKey" +#define UNKOWN_KEY "__UnknownKey" +#define GET_CONFIG_KNOWN_UNKOWN "[2,\"test-mst\",\"GetVariable\",{\"key\":[\"" KNOWN_KEY "\",\"" UNKOWN_KEY "\"]}]" + +TEST_CASE( "Variable" ) { + printf("\nRun %s\n", "Variable"); + + //clean state + auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail); + FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;}); + + SECTION("Basic container operations"){ + std::unique_ptr container; + + SECTION("Volatile storage") { + container = makeVariableContainerVolatile(MO_VARIABLE_VOLATILE "/volatile1", true); + } + +#if 0 + SECTION("Persistent storage") { + container = makeVariableContainerFlash(filesystem, MO_FILENAME_PREFIX "persistent1.jsn", true); + } +#endif + + //check emptyness + REQUIRE( container->size() == 0 ); + + //add first config, fetch by index + Variable::AttributeTypeSet attrs = Variable::AttributeType::Actual; + auto configFirst = container->createVariable(Variable::InternalDataType::Int, attrs); + configFirst->setName("cFirst"); + configFirst->setComponentId("mComponent"); + auto configFirstRaw = configFirst.get(); + REQUIRE( container->size() == 0 ); + REQUIRE( container->add(std::move(configFirst)) ); + REQUIRE( container->size() == 1 ); + REQUIRE( container->getVariable((size_t) 0) == configFirstRaw); + + //add one config of each type + auto cInt = container->createVariable(Variable::InternalDataType::Int, attrs); + cInt->setName("cInt"); + cInt->setComponentId("mComponent"); + auto cBool = container->createVariable(Variable::InternalDataType::Bool, attrs); + cBool->setName("cBool"); + cBool->setComponentId("mComponent"); + auto cBoolRaw = cBool.get(); + auto cString = container->createVariable(Variable::InternalDataType::String, attrs); + cString->setName("cString"); + cString->setComponentId("mComponent"); + + container->add(std::move(cInt)); + container->add(std::move(cBool)); + container->add(std::move(cString)); + + REQUIRE( container->size() == 4 ); + + //fetch config by key + REQUIRE( container->getVariable(cBoolRaw->getComponentId(), cBoolRaw->getName()) == cBoolRaw); + } + +#if 0 + SECTION("Persistency on filesystem") { + + auto container = makeVariableContainerFlash(filesystem, MO_FILENAME_PREFIX "persistent1.jsn", true); + + //trivial load call + REQUIRE( container->load() ); + REQUIRE( container->size() == 0 ); + + //add config, store, load again + auto cString = container->createVariable(Variable::InternalDataType::String, "cString"); + cString->setString("mValue"); + + REQUIRE( container->save() ); //store + + container.reset(); //destroy + + //...load again + auto container2 = makeVariableContainerFlash(filesystem, MO_FILENAME_PREFIX "persistent1.jsn", true); + REQUIRE( container2->size() == 0 ); + REQUIRE( container2->load() ); + REQUIRE( container2->size() == 1 ); + + auto cString2 = container2->getVariable("cString"); + REQUIRE( cString2 != nullptr ); + REQUIRE( !strcmp(cString2->getString(), "mValue") ); + } +#endif + + SECTION("Variable API") { + + //declare configs + auto vs = std::unique_ptr(new VariableService(filesystem)); + auto cInt = vs->declareVariable("cInt", 42, "mComponent"); + REQUIRE( cInt != nullptr ); + vs->declareVariable("cBool", true, "mComponent"); + vs->declareVariable("cString", "mValue", "mComponent"); + + //fetch config + REQUIRE( vs->declareVariable("cInt", -1, "mComponent")->getInt() == 42 ); + +#if 0 + //store, destroy, reload + REQUIRE( configuration_save() ); + cInt.reset(); + configuration_deinit(); + REQUIRE( getVariablePublic("cInt") == nullptr); + + REQUIRE( configuration_init(filesystem) ); //reload + + //fetch configs (declare with different factory default - should remain at original value) + auto cInt2 = vs->declareVariable("cInt", -1); + auto cBool2 = vs->declareVariable("cBool", false); + auto cString2 = vs->declareVariable("cString", "no effect"); + REQUIRE( configuration_load() ); //load config objects with stored values + + //check load result + REQUIRE( cInt2->getInt() == 42 ); + REQUIRE( cBool2->getBool() == true ); + REQUIRE( !strcmp(cString2->getString(), "mValue") ); +#else + auto cInt2 = cInt; +#endif + + //declare config twice + auto cInt3 = vs->declareVariable("cInt", -1, "mComponent"); + REQUIRE( cInt3 == cInt2 ); + + //declare config twice but in different container + auto cInt4 = vs->declareVariable("cInt", -1, "mComponent", MO_VARIABLE_VOLATILE "/volatile2"); + REQUIRE( cInt4 == cInt2 ); + + //declare config twice but with conflicting type (will supersede old type because to simplify FW upgrades) + auto cNewType = vs->declareVariable("cInt", "mValue2", "mComponent"); + REQUIRE( cNewType != cInt2 ); + REQUIRE( cInt2->isDetached() ); // Container should not store this + REQUIRE( !strcmp(cNewType->getString(), "mValue2") ); + +#if 0 + //store, destroy, reload + REQUIRE( configuration_save() ); + configuration_deinit(); + REQUIRE( getVariablePublic("cInt") == nullptr); + REQUIRE( configuration_init(filesystem) ); //reload + auto cNewType2 = vs->declareVariable("cInt", "no effect"); + REQUIRE( configuration_load() ); + REQUIRE( !strcmp(cNewType2->getString(), "mValue2") ); + + //get config before declared (container needs to be declared already at this point) + auto cString3 = getVariablePublic("cString"); + REQUIRE( !strcmp(cString3->getString(), "mValue") ); + configuration_deinit(); + + //value needs to outlive container + configuration_init(filesystem); + auto cString4 = vs->declareVariable("cString2", "mValue3"); + configuration_deinit(); + REQUIRE( !strcmp(cString4->getString(), "mValue3") ); + + FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;}); +#else + vs.reset(); +#endif + + //config accessibility / permissions + vs = std::unique_ptr(new VariableService(filesystem)); + Variable::Mutability mutability = Variable::Mutability::ReadWrite; + Variable::AttributeTypeSet attrs = Variable::AttributeType::Actual; + bool rebootRequired = false; + bool isAccessible = true; + auto cInt6 = vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + REQUIRE( cInt6->getMutability() == Variable::Mutability::ReadWrite ); + REQUIRE( !cInt6->isRebootRequired() ); + REQUIRE( vs->declareVariable("cInt", 42, "mComponent") ); + + //revoke permissions + mutability = Variable::Mutability::ReadOnly; + rebootRequired = true; + vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + REQUIRE( cInt6->getMutability() == mutability ); + REQUIRE( cInt6->isRebootRequired() ); + + //revoked permissions cannot be reverted + mutability = Variable::Mutability::ReadWrite; + rebootRequired = false; + auto cInt7 = vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + REQUIRE( cInt7->getMutability() == Variable::Mutability::ReadOnly ); + REQUIRE( cInt7->isRebootRequired() ); + + //accessibility cannot be changed after first initialization + isAccessible = false; + vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + vs->declareVariable("cInt2", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + Variable *result = nullptr; + REQUIRE( vs->getVariable(Variable::AttributeType::Actual, "mComponent", "cInt", &result) == GetVariableStatus::Accepted ); + REQUIRE( result != nullptr ); + result = nullptr; + REQUIRE( vs->getVariable(Variable::AttributeType::Actual, "mComponent", "cInt2", &result) == GetVariableStatus::Accepted ); + REQUIRE( result != nullptr ); + + //create config in hidden container + isAccessible = false; + auto cHidden = vs->declareVariable("cHidden", 42, "mComponent", MO_VARIABLE_VOLATILE "/hidden.json", mutability, attrs, rebootRequired, isAccessible); + result = nullptr; + REQUIRE( vs->getVariable(Variable::AttributeType::Actual, "mComponent", "cHidden", &result) == GetVariableStatus::Accepted ); + REQUIRE( result != nullptr ); + } + + LoopbackConnection loopback; //initialize Context with dummy socket + mocpp_set_timer(custom_timer_cb); + +#if 0 + SECTION("Main lib integration") { + + //basic lifecycle + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + REQUIRE( getVariablePublic("ConnectionTimeOut") ); + REQUIRE( !getVariableContainersPublic().empty() ); + mocpp_deinitialize(); + REQUIRE( !getVariablePublic("ConnectionTimeOut") ); + 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")); + auto config = getVariablePublic("ConnectionTimeOut"); + + config->setInt(1234); //update + configuration_save(); //write back + + mocpp_deinitialize(); + + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + REQUIRE( getVariablePublic("ConnectionTimeOut")->getInt() == 1234 ); + + mocpp_deinitialize(); + } +#endif + +#if 0 + SECTION("GetVariables") { + + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + loop(); + + vs->declareVariable(KNOWN_KEY, 1234, MO_FILENAME_PREFIX "persistent1.jsn", false); + + bool checkProcessed = false; + + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "GetVariables", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); + auto payload = doc->to(); + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + JsonArray configurationKey = payload["configurationKey"]; + + bool foundCustomConfig = false; + bool foundStandardConfig = false; + for (JsonObject keyvalue : configurationKey) { + MO_DBG_DEBUG("key %s", keyvalue["key"] | "_Undefined"); + if (!strcmp(keyvalue["key"] | "_Undefined", KNOWN_KEY)) { + foundCustomConfig = true; + REQUIRE( (keyvalue["readonly"] | true) == false ); + REQUIRE( !strcmp(keyvalue["value"] | "_Undefined", "1234") ); + } else if (!strcmp(keyvalue["key"] | "_Undefined", "ConnectionTimeOut")) { + foundStandardConfig = true; + } + } + + REQUIRE( foundCustomConfig ); + REQUIRE( foundStandardConfig ); + } + ))); + + loop(); + + REQUIRE(checkProcessed); + + checkProcessed = false; + + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "GetVariable", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2))); + auto payload = doc->to(); + auto key = payload.createNestedArray("key"); + key.add(KNOWN_KEY); + key.add(UNKOWN_KEY); + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + JsonArray configurationKey = payload["configurationKey"]; + + bool foundCustomConfig = false; + for (JsonObject keyvalue : configurationKey) { + if (!strcmp(keyvalue["key"] | "_Undefined", KNOWN_KEY)) { + foundCustomConfig = true; + break; + } + } + REQUIRE( foundCustomConfig ); + + JsonArray unknownKey = payload["unknownKey"]; + + bool foundUnkownKey = false; + for (const char *key : unknownKey) { + if (!strcmp(key, UNKOWN_KEY)) { + foundUnkownKey = true; + } + } + + REQUIRE( foundUnkownKey ); + } + ))); + + loop(); + + REQUIRE(checkProcessed); + + mocpp_deinitialize(); + } + + SECTION("ChangeVariable") { + + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + loop(); + + vs->declareVariable(KNOWN_KEY, 0, MO_FILENAME_PREFIX "persistent1.jsn", false); + + //update existing config + bool checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "ChangeVariable", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2))); + auto payload = doc->to(); + payload["key"] = KNOWN_KEY; + payload["value"] = "1234"; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Accepted") ); + } + ))); + loop(); + REQUIRE(checkProcessed); + REQUIRE( getVariablePublic(KNOWN_KEY)->getInt() == 1234 ); + + //try to update not existing key + checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "ChangeVariable", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2))); + auto payload = doc->to(); + payload["key"] = UNKOWN_KEY; + payload["value"] = "no effect"; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "NotSupported") ); + } + ))); + loop(); + REQUIRE( checkProcessed ); + + //try to update config with malformatted value + checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "ChangeVariable", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2))); + auto payload = doc->to(); + payload["key"] = KNOWN_KEY; + payload["value"] = "not convertible to int"; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Rejected") ); + } + ))); + loop(); + REQUIRE( checkProcessed ); + + //try to update config with value validation + //value is valid if it begins with 1 + registerVariableValidator(KNOWN_KEY, [] (const char *v) { + return v[0] == '1'; + }); + + //validation success + checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "ChangeVariable", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2))); + auto payload = doc->to(); + payload["key"] = KNOWN_KEY; + payload["value"] = "100234"; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Accepted") ); + } + ))); + loop(); + REQUIRE( checkProcessed ); + REQUIRE( getVariablePublic(KNOWN_KEY)->getInt() == 100234 ); + + //validation failure + checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "ChangeVariable", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2))); + auto payload = doc->to(); + payload["key"] = KNOWN_KEY; + payload["value"] = "4321"; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Rejected") ); + } + ))); + loop(); + REQUIRE( checkProcessed ); + REQUIRE( getVariablePublic(KNOWN_KEY)->getInt() == 100234 ); //keep old value + + mocpp_deinitialize(); + } + + SECTION("Define factory defaults for standard configs") { + + //set factory default for standard config ConnectionTimeOut + configuration_init(filesystem); + auto factoryConnectionTimeOut = vs->declareVariable("ConnectionTimeOut", 1234, MO_FILENAME_PREFIX "factory.jsn"); + + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + + auto connectionTimeout2 = vs->declareVariable("ConnectionTimeOut", 4321); + REQUIRE( connectionTimeout2->getInt() == 1234 ); + REQUIRE( connectionTimeout2 == factoryConnectionTimeOut ); + + configuration_save(); + mocpp_deinitialize(); + + //this time, factory default is not given (will lead to duplicates, should be considered in sanitization) + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + 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")); + REQUIRE( getVariablePublic("ConnectionTimeOut")->getInt() == 1234 ); + mocpp_deinitialize(); + + } +#endif +} From 35742733f70ff1403eafc958338a77c12d69f546 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 24 Feb 2024 18:08:25 +0100 Subject: [PATCH 10/23] fix Variables unit tests --- CMakeLists.txt | 2 ++ src/MicroOcpp/Operations/StatusNotification.cpp | 2 +- tests/Configuration.cpp | 2 +- tests/LocalAuthList.cpp | 2 +- tests/Reservation.cpp | 2 +- tests/Variables.cpp | 7 +++++++ 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 64372054..0beb1825 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,12 +159,14 @@ target_compile_definitions(mo_unit_tests PUBLIC MO_FILENAME_PREFIX="./mo_store/" MO_LocalAuthListMaxLength=8 MO_SendLocalListMaxLength=4 + MO_ENABLE_V201=1 ) target_compile_options(mo_unit_tests PUBLIC -O0 -g --coverage + -Wall ) target_link_options(mo_unit_tests PUBLIC diff --git a/src/MicroOcpp/Operations/StatusNotification.cpp b/src/MicroOcpp/Operations/StatusNotification.cpp index c4dc33e7..76d041d7 100644 --- a/src/MicroOcpp/Operations/StatusNotification.cpp +++ b/src/MicroOcpp/Operations/StatusNotification.cpp @@ -136,7 +136,7 @@ const char *cstrFromOcppEveState(ConnectorStatus state) { } StatusNotification::StatusNotification(int evseId, ConnectorStatus currentStatus, const Timestamp ×tamp, int connectorId) - : evseId(evseId), currentStatus(currentStatus), timestamp(timestamp), connectorId(connectorId) { + : timestamp(timestamp), currentStatus(currentStatus), evseId(evseId), connectorId(connectorId) { } diff --git a/tests/Configuration.cpp b/tests/Configuration.cpp index c890f96f..8d918ccf 100644 --- a/tests/Configuration.cpp +++ b/tests/Configuration.cpp @@ -275,7 +275,7 @@ TEST_CASE( "Configuration" ) { [] () { //create req auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); - auto payload = doc->to(); + doc->to(); return doc;}, [&checkProcessed] (JsonObject payload) { //receive conf diff --git a/tests/LocalAuthList.cpp b/tests/LocalAuthList.cpp index 9104ad87..e87678fa 100644 --- a/tests/LocalAuthList.cpp +++ b/tests/LocalAuthList.cpp @@ -818,7 +818,7 @@ TEST_CASE( "LocalAuth" ) { }))); loop(); - REQUIRE( authService->getLocalListVersion() == i ); + REQUIRE( authService->getLocalListVersion() == (int)i ); REQUIRE( authService->getLocalListSize() == i + 1 ); REQUIRE( checkAccepted ); } diff --git a/tests/Reservation.cpp b/tests/Reservation.cpp index cf113f03..8b5c5a50 100644 --- a/tests/Reservation.cpp +++ b/tests/Reservation.cpp @@ -278,7 +278,7 @@ TEST_CASE( "Reservation" ) { auto reservation = getOcppContext()->getModel().getReservationService()->getReservationById(reservationId); REQUIRE( reservation->getReservationId() == reservationId ); - REQUIRE( reservation->getConnectorId() == connectorId ); + REQUIRE( reservation->getConnectorId() == (int)connectorId ); REQUIRE( reservation->getExpiryDate() == expiryDate ); REQUIRE( !strcmp(reservation->getIdTag(), idTag) ); REQUIRE( !strcmp(reservation->getParentIdTag(), parentIdTag) ); diff --git a/tests/Variables.cpp b/tests/Variables.cpp index 26187181..76fe01d5 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -1,3 +1,7 @@ +#include + +#if MO_ENABLE_V201 + #include #include #include "./catch2/catch.hpp" @@ -217,6 +221,7 @@ TEST_CASE( "Variable" ) { //create config in hidden container isAccessible = false; auto cHidden = vs->declareVariable("cHidden", 42, "mComponent", MO_VARIABLE_VOLATILE "/hidden.json", mutability, attrs, rebootRequired, isAccessible); + (void)cHidden; result = nullptr; REQUIRE( vs->getVariable(Variable::AttributeType::Actual, "mComponent", "cHidden", &result) == GetVariableStatus::Accepted ); REQUIRE( result != nullptr ); @@ -498,3 +503,5 @@ TEST_CASE( "Variable" ) { } #endif } + +#endif // MO_ENABLE_V201 From b1cbff0876a05ea9c13961144565a259bc0b33c6 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:01:05 +0100 Subject: [PATCH 11/23] fix Set-/GetVariables --- src/MicroOcpp.cpp | 2 +- .../Model/Variables/VariableContainer.cpp | 1 - .../Model/Variables/VariableService.cpp | 22 ++++++++++++++----- .../Model/Variables/VariableService.h | 4 +++- src/MicroOcpp/Operations/GetVariables.cpp | 2 +- src/MicroOcpp/Operations/SetVariables.cpp | 10 ++++++--- tests/Variables.cpp | 15 ++++++++----- 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index ede9f5d6..7cfd782c 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -291,7 +291,7 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden #if MO_ENABLE_V201 model.setVariableService(std::unique_ptr( - new VariableService(filesystem))); + new VariableService(*context, filesystem))); #endif #if !defined(MO_CUSTOM_UPDATER) && !defined(MO_CUSTOM_WS) diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.cpp b/src/MicroOcpp/Model/Variables/VariableContainer.cpp index 1c71344a..6d229c9c 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.cpp +++ b/src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -57,7 +57,6 @@ Variable *VariableContainerVolatile::getVariable(size_t i) const { } Variable *VariableContainerVolatile::getVariable(const ComponentId& component, const char *variableName) const { - auto it = variables.begin(); for (auto it = variables.begin(); it != variables.end(); it++) { if (!strcmp((*it)->getName(), variableName) && (*it)->getComponentId().equals(component)) { diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp index 599c469a..82cf1bfe 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.cpp +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -11,6 +11,9 @@ #if MO_ENABLE_V201 #include +#include +#include +#include #include #include @@ -116,6 +119,13 @@ Variable *VariableService::getVariable(Variable::InternalDataType type, const Co return nullptr; } +VariableService::VariableService(Context& context, std::shared_ptr filesystem) : filesystem(filesystem) { + context.getOperationRegistry().registerOperation("SetVariables", [this] () { + return new Ocpp201::SetVariables(*this);}); + context.getOperationRegistry().registerOperation("GetVariables", [this] () { + return new Ocpp201::GetVariables(*this);}); +} + template bool loadVariableFactoryDefault(Variable& variable, T factoryDef); @@ -147,23 +157,23 @@ void loadVariableCharacteristics(Variable& variable, Variable::Mutability mutabi } template -Variable::InternalDataType convertType(); +Variable::InternalDataType getInternalDataType(); -template<> Variable::InternalDataType convertType() {return Variable::InternalDataType::Int;} -template<> Variable::InternalDataType convertType() {return Variable::InternalDataType::Bool;} -template<> Variable::InternalDataType convertType() {return Variable::InternalDataType::String;} +template<> Variable::InternalDataType getInternalDataType() {return Variable::InternalDataType::Int;} +template<> Variable::InternalDataType getInternalDataType() {return Variable::InternalDataType::Bool;} +template<> Variable::InternalDataType getInternalDataType() {return Variable::InternalDataType::String;} template Variable *VariableService::declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath, Variable::Mutability mutability, Variable::AttributeTypeSet attributes, bool rebootRequired, bool accessible) { - auto res = getVariable(convertType(), component, name, accessible); + auto res = getVariable(getInternalDataType(), component, name, accessible); if (!res) { auto container = declareContainer(containerPath, accessible); if (!container) { return nullptr; } - auto variable = container->createVariable(convertType(), attributes); + auto variable = container->createVariable(getInternalDataType(), attributes); if (!variable) { return nullptr; } diff --git a/src/MicroOcpp/Model/Variables/VariableService.h b/src/MicroOcpp/Model/Variables/VariableService.h index 46b69c32..4d46fbd7 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.h +++ b/src/MicroOcpp/Model/Variables/VariableService.h @@ -44,6 +44,8 @@ struct VariableValidator { VariableValidator(const char *key, std::function validate) : key(key), validate(validate) { } }; +class Context; + class VariableService { private: std::shared_ptr filesystem; @@ -64,7 +66,7 @@ class VariableService { Variable *getVariable(Variable::InternalDataType type, const ComponentId& component, const char *name, bool accessible); public: - VariableService(std::shared_ptr filesystem) : filesystem(filesystem) { } + VariableService(Context& context, std::shared_ptr filesystem); template Variable *declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath = MO_VARIABLE_FN, Variable::Mutability mutability = Variable::Mutability::ReadWrite, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false, bool accessible = true); diff --git a/src/MicroOcpp/Operations/GetVariables.cpp b/src/MicroOcpp/Operations/GetVariables.cpp index f4c3575b..e9500f8b 100644 --- a/src/MicroOcpp/Operations/GetVariables.cpp +++ b/src/MicroOcpp/Operations/GetVariables.cpp @@ -130,7 +130,7 @@ std::unique_ptr GetVariables::createConf(){ auto doc = std::unique_ptr(new DynamicJsonDocument(capacity)); JsonObject payload = doc->to(); - JsonArray getVariableResult = payload["getVariableResult"]; + JsonArray getVariableResult = payload.createNestedArray("getVariableResult"); for (const auto& data : queries) { JsonObject getVariable = getVariableResult.createNestedObject(); diff --git a/src/MicroOcpp/Operations/SetVariables.cpp b/src/MicroOcpp/Operations/SetVariables.cpp index 26e7b6a3..6333353e 100644 --- a/src/MicroOcpp/Operations/SetVariables.cpp +++ b/src/MicroOcpp/Operations/SetVariables.cpp @@ -77,7 +77,7 @@ void SetVariables::processReq(JsonObject payload) { MO_DBG_DEBUG("processing %zu setVariable queries", queries.size()); - for (auto query : queries) { + for (auto& query : queries) { query.attributeStatus = variableService.setVariable( query.attributeType, query.attributeValue, @@ -86,7 +86,11 @@ void SetVariables::processReq(JsonObject payload) { query.variableName.c_str()); } - variableService.commit(); + if (!variableService.commit()) { + errorCode = "InternalError"; + MO_DBG_ERR("Variables could not be stored. Rollback not possible"); + return; + } } std::unique_ptr SetVariables::createConf(){ @@ -103,7 +107,7 @@ std::unique_ptr SetVariables::createConf(){ auto doc = std::unique_ptr(new DynamicJsonDocument(capacity)); JsonObject payload = doc->to(); - JsonArray setVariableResult = payload["setVariableResult"]; + JsonArray setVariableResult = payload.createNestedArray("setVariableResult"); for (const auto& data : queries) { JsonObject setVariable = setVariableResult.createNestedObject(); diff --git a/tests/Variables.cpp b/tests/Variables.cpp index 76fe01d5..c7747187 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -107,10 +107,14 @@ TEST_CASE( "Variable" ) { } #endif + LoopbackConnection loopback; //initialize Context with dummy socket + mocpp_set_timer(custom_timer_cb); + SECTION("Variable API") { //declare configs - auto vs = std::unique_ptr(new VariableService(filesystem)); + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + auto vs = getOcppContext()->getModel().getVariableService(); auto cInt = vs->declareVariable("cInt", 42, "mComponent"); REQUIRE( cInt != nullptr ); vs->declareVariable("cBool", true, "mComponent"); @@ -179,11 +183,13 @@ TEST_CASE( "Variable" ) { FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;}); #else - vs.reset(); + mocpp_deinitialize(); + + mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); #endif //config accessibility / permissions - vs = std::unique_ptr(new VariableService(filesystem)); + vs = getOcppContext()->getModel().getVariableService(); Variable::Mutability mutability = Variable::Mutability::ReadWrite; Variable::AttributeTypeSet attrs = Variable::AttributeType::Actual; bool rebootRequired = false; @@ -227,9 +233,6 @@ TEST_CASE( "Variable" ) { REQUIRE( result != nullptr ); } - LoopbackConnection loopback; //initialize Context with dummy socket - mocpp_set_timer(custom_timer_cb); - #if 0 SECTION("Main lib integration") { From d3554e75f678370accfc7c126e47a478832a5a39 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:16:14 +0100 Subject: [PATCH 12/23] OCPP 2.0.1 transactions (non-persistent) --- CMakeLists.txt | 4 + src/MicroOcpp.cpp | 30 ++ src/MicroOcpp/Model/Authorization/IdToken.cpp | 69 ++++ src/MicroOcpp/Model/Authorization/IdToken.h | 49 +++ src/MicroOcpp/Model/ConnectorBase/EvseId.h | 35 ++ src/MicroOcpp/Model/Model.cpp | 17 +- src/MicroOcpp/Model/Model.h | 5 + .../Model/Transactions/Transaction.h | 141 +++++++- .../Model/Transactions/TransactionService.cpp | 319 ++++++++++++++++++ .../Model/Transactions/TransactionService.h | 99 ++++++ src/MicroOcpp/Model/Variables/Variable.h | 17 +- .../Model/Variables/VariableService.cpp | 76 +++-- .../Model/Variables/VariableService.h | 16 +- src/MicroOcpp/Operations/TransactionEvent.cpp | 255 ++++++++++++++ src/MicroOcpp/Operations/TransactionEvent.h | 44 +++ tests/Transactions.cpp | 60 ++++ tests/Variables.cpp | 28 +- 17 files changed, 1205 insertions(+), 59 deletions(-) create mode 100644 src/MicroOcpp/Model/Authorization/IdToken.cpp create mode 100644 src/MicroOcpp/Model/Authorization/IdToken.h create mode 100644 src/MicroOcpp/Model/ConnectorBase/EvseId.h create mode 100644 src/MicroOcpp/Model/Transactions/TransactionService.cpp create mode 100644 src/MicroOcpp/Model/Transactions/TransactionService.h create mode 100644 src/MicroOcpp/Operations/TransactionEvent.cpp create mode 100644 src/MicroOcpp/Operations/TransactionEvent.h create mode 100644 tests/Transactions.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0beb1825..4bc12a8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ set(MO_SRC src/MicroOcpp/Operations/StartTransaction.cpp src/MicroOcpp/Operations/StatusNotification.cpp src/MicroOcpp/Operations/StopTransaction.cpp + src/MicroOcpp/Operations/TransactionEvent.cpp src/MicroOcpp/Operations/TriggerMessage.cpp src/MicroOcpp/Operations/UnlockConnector.cpp src/MicroOcpp/Operations/UpdateFirmware.cpp @@ -58,6 +59,7 @@ set(MO_SRC src/MicroOcpp/Model/Authorization/AuthorizationData.cpp src/MicroOcpp/Model/Authorization/AuthorizationList.cpp src/MicroOcpp/Model/Authorization/AuthorizationService.cpp + src/MicroOcpp/Model/Authorization/IdToken.cpp src/MicroOcpp/Model/Boot/BootService.cpp src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp src/MicroOcpp/Model/ConnectorBase/Connector.cpp @@ -77,6 +79,7 @@ set(MO_SRC src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp src/MicroOcpp/Model/Transactions/Transaction.cpp src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp + src/MicroOcpp/Model/Transactions/TransactionService.cpp src/MicroOcpp/Model/Transactions/TransactionStore.cpp src/MicroOcpp/Model/Variables/Variable.cpp src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -133,6 +136,7 @@ set(MO_SRC_UNIT tests/Reservation.cpp tests/LocalAuthList.cpp tests/Variables.cpp + tests/Transactions.cpp ) add_executable(mo_unit_tests diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 7cfd782c..6c86e710 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -292,6 +293,8 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden #if MO_ENABLE_V201 model.setVariableService(std::unique_ptr( new VariableService(*context, filesystem))); + model.setTransactionService(std::unique_ptr( + new TransactionService(*context))); #endif #if !defined(MO_CUSTOM_UPDATER) && !defined(MO_CUSTOM_WS) @@ -517,6 +520,15 @@ void setConnectorPluggedInput(std::function pluggedInput, unsigned int c MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } +#if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + if (auto txService = context->getModel().getTransactionService()) { + if (auto evse = txService->getEvse(connectorId)) { + evse->setConnectorPluggedInput(pluggedInput); + } + } + } +#endif auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); @@ -653,6 +665,15 @@ void setEvReadyInput(std::function evReadyInput, unsigned int connectorI MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } +#if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + if (auto txService = context->getModel().getTransactionService()) { + if (auto evse = txService->getEvse(connectorId)) { + evse->setEvReadyInput(evReadyInput); + } + } + } +#endif auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); @@ -666,6 +687,15 @@ void setEvseReadyInput(std::function evseReadyInput, unsigned int connec MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } +#if MO_ENABLE_V201 + if (context->getVersion().major == 2) { + if (auto txService = context->getModel().getTransactionService()) { + if (auto evse = txService->getEvse(connectorId)) { + evse->setEvseReadyInput(evseReadyInput); + } + } + } +#endif auto connector = context->getModel().getConnector(connectorId); if (!connector) { MO_DBG_ERR("could not find connector"); diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp new file mode 100644 index 00000000..0210bd64 --- /dev/null +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -0,0 +1,69 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include + +#include +#include + +#include + +using namespace MicroOcpp; + +IdToken::IdToken() { + idToken[0] = '\0'; +} + +IdToken::IdToken(const char *token, Type type) : type(type) { + auto ret = snprintf(idToken, MO_IDTOKEN_LEN_MAX + 1, "%s", token); + if (ret < 0 || ret >= MO_IDTOKEN_LEN_MAX + 1) { + MO_DBG_ERR("invalid token"); + *idToken = '\0'; + } +} + +const char *IdToken::get() { + return *idToken ? idToken : nullptr;; +} + +const char *IdToken::getTypeCstr() { + const char *res = nullptr; + switch (type) { + case Type::Central: + res = "Central"; + break; + case Type::eMAID: + res = "eMAID"; + break; + case Type::ISO14443: + res = "ISO14443"; + break; + case Type::ISO15693: + res = "ISO15693"; + break; + case Type::KeyCode: + res = "KeyCode"; + break; + case Type::Local: + res = "Local"; + break; + case Type::MacAddress: + res = "MacAddress"; + break; + case Type::NoAuthorization: + res = "NoAuthorization"; + break; + } + + if (!res) { + MO_DBG_ERR("internal error"); + } + return res ? res : ""; +} + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h new file mode 100644 index 00000000..7b03ce10 --- /dev/null +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -0,0 +1,49 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_IDTOKEN_H +#define MO_IDTOKEN_H + +#include + +#if MO_ENABLE_V201 + +#include + +#define MO_IDTOKEN_LEN_MAX 36 + +namespace MicroOcpp { + +// IdTokenType (2.28) +class IdToken { +public: + + // IdTokenEnumType (3.43) + enum class Type : uint8_t { + Central, + eMAID, + ISO14443, + ISO15693, + KeyCode, + Local, + MacAddress, + NoAuthorization, + UNDEFINED + }; + +private: + char idToken [MO_IDTOKEN_LEN_MAX + 1]; + Type type = Type::UNDEFINED; +public: + IdToken(); + IdToken(const char *token, Type type = Type::ISO14443); + + const char *get(); + const char *getTypeCstr(); +}; + +} // namespace MicroOcpp + +#endif // MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Model/ConnectorBase/EvseId.h b/src/MicroOcpp/Model/ConnectorBase/EvseId.h new file mode 100644 index 00000000..14a3c661 --- /dev/null +++ b/src/MicroOcpp/Model/ConnectorBase/EvseId.h @@ -0,0 +1,35 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_EVSEID_H +#define MO_EVSEID_H + +#include + +#if MO_ENABLE_V201 + +// number of EVSEs. Defaults to MO_NUMCONNECTORS if defined, otherwise to 2 +#ifndef MO_NUM_EVSE +#if defined(MO_NUMCONNECTORS) +#define MO_NUM_EVSE MO_NUMCONNECTORS +#else +#define MO_NUM_EVSE 2 +#endif +#endif // MO_NUM_EVSE + +namespace MicroOcpp { + +// EVSEType (2.23) +struct EvseId { + int id; + int connectorId = -1; //optional + + EvseId(int id) : id(id) { } + EvseId(int id, int connectorId) : id(id), connectorId(connectorId) { } +}; + +} + +#endif // MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 80cf6cac..ff3b0b86 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -73,6 +74,9 @@ void Model::loop() { if (resetService) resetService->loop(); + + if (transactionService) + transactionService->loop(); } void Model::setTransactionStore(std::unique_ptr ts) { @@ -189,14 +193,23 @@ ResetService *Model::getResetService() const { } #if MO_ENABLE_V201 -void Model::setVariableService(std::unique_ptr rs) { - this->variableService = std::move(rs); +void Model::setVariableService(std::unique_ptr vs) { + this->variableService = std::move(vs); capabilitiesUpdated = true; } VariableService *Model::getVariableService() const { return variableService.get(); } + +void Model::setTransactionService(std::unique_ptr ts) { + this->transactionService = std::move(ts); + capabilitiesUpdated = true; +} + +TransactionService *Model::getTransactionService() const { + return transactionService.get(); +} #endif Clock& Model::getClock() { diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 895be67f..bc9ff2ba 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -27,6 +27,7 @@ class ResetService; #if MO_ENABLE_V201 class VariableService; +class TransactionService; #endif class Model { @@ -46,6 +47,7 @@ class Model { #if MO_ENABLE_V201 std::unique_ptr variableService; + std::unique_ptr transactionService; #endif Clock clock; @@ -107,6 +109,9 @@ class Model { #if MO_ENABLE_V201 void setVariableService(std::unique_ptr vs); VariableService *getVariableService() const; + + void setTransactionService(std::unique_ptr ts); + TransactionService *getTransactionService() const; #endif Clock &getClock(); diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 1c711da6..df7531ce 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -165,7 +165,146 @@ class Transaction { bool isSilent() {return silent;} //no data will be sent to server and server will not assign transactionId }; -} +} // namespace MicroOcpp + +#include + +#if MO_ENABLE_V201 + +#include +#include + +#define MO_TXID_LEN_MAX 36 + +namespace MicroOcpp { +namespace Ocpp201 { + +class Transaction { +public: + struct SubStatus { + bool triggered = false; + bool untriggered = false; + SendStatus remote; + }; + +//private: + //SubStatus parkingBayOccupancy; // not supported + SubStatus evConnected; + SubStatus authorized; + SubStatus dataSigned; + SubStatus powerPathClosed; + SubStatus energyTransfer; + + /* + * Global transaction data + */ + bool active = true; //once active is false, the tx must stop (or cannot start at all) + 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 + IdToken idToken; + Timestamp startTime = MIN_TIME; + char transactionId [MO_TXID_LEN_MAX + 1] = {'\0'}; + int remoteStartId = -1; +}; + +// TransactionEventRequest (1.60.1) +class TransactionEventData { +public: + + // TransactionEventEnumType (3.80) + enum class Type : uint8_t { + Ended, + Started, + Updated + }; + + // TriggerReasonEnumType (3.82) + enum class TriggerReason : uint8_t { + Authorized, + CablePluggedIn, + ChargingRateChanged, + ChargingStateChanged, + Deauthorized, + EnergyLimitReached, + EVCommunicationLost, + EVConnectTimeout, + MeterValueClock, + MeterValuePeriodic, + TimeLimitReached, + Trigger, + UnlockCommand, + StopAuthorized, + EVDeparted, + EVDetected, + RemoteStop, + RemoteStart, + AbnormalCondition, + SignedDataReceived, + ResetCommand + }; + + // ChargingStateEnumType (3.16) + enum class ChargingState : uint8_t { + UNDEFINED, // not part of OCPP + Charging, + EVConnected, + SuspendedEV, + SuspendedEVSE, + Idle + }; + + // ReasonEnumType (3.67) + enum class StopReason : uint8_t { + UNDEFINED, // not part of OCPP + DeAuthorized, + EmergencyStop, + EnergyLimitReached, + EVDisconnected, + GroundFault, + ImmediateReset, + Local, + LocalOutOfCredit, + MasterPass, + Other, + OvercurrentFault, + PowerLoss, + PowerQuality, + Reboot, + Remote, + SOCLimitReached, + StoppedByEV, + TimeLimitReached, + Timeout + }; + +//private: + Transaction& transaction; + Type eventType; + Timestamp timestamp; + TriggerReason triggerReason; + const unsigned int seqNo; + bool offline = false; + int numberOfPhasesUsed = -1; + int cableMaxCurrent = -1; + int reservationId = -1; + + // TransactionType (2.48) + ChargingState chargingState = ChargingState::UNDEFINED; + //int timeSpentCharging = 0; // not supported + StopReason stoppedReason = StopReason::UNDEFINED; + + bool idTokenTransmit = false; // if the idToken has been updated should be transmitted with this Event + EvseId evse = -1; + //meterValue not supported + + TransactionEventData(Transaction& transaction, unsigned int seqNo) : transaction(transaction), seqNo(seqNo) { } +}; + +} // namespace Ocpp201 +} // namespace MicroOcpp + +#endif // MO_ENABLE_V201 extern "C" { #endif //__cplusplus diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp new file mode 100644 index 00000000..020703db --- /dev/null +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -0,0 +1,319 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#if MO_ENABLE_V201 + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; + +TransactionService::Evse::Evse(Context& context, TransactionService& txService, unsigned int evseId) : + context(context), txService(txService), evseId(evseId) { + +} + +std::unique_ptr TransactionService::Evse::allocateTransaction() { + return std::unique_ptr(new Ocpp201::Transaction()); +} + +void TransactionService::Evse::loop() { + + bool txStarted = false; + bool txStopped = false; + + bool txUpdated = false; + bool txUpdatedIdToken = false; + + TransactionEventData::TriggerReason triggerReason; // only valid if txStarted || txStopped || txUpdated + TransactionEventData::StopReason stoppedReason; // only valid if txStopped + + if (connectorPluggedInput && connectorPluggedInput()) { + if (!transaction && txService.isTxStartPoint(TxStartStopPoint::EVConnected)) { + transaction = allocateTransaction(); + txStarted = true; + } + + if (transaction && !transaction->evConnected.triggered) { + transaction->evConnected.triggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::CablePluggedIn; + } + } else if (connectorPluggedInput && !connectorPluggedInput()) { + if (transaction && transaction->evConnected.triggered) { + if (!transaction->evConnected.untriggered) { + transaction->evConnected.untriggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::EVCommunicationLost; + } + + if (txService.isTxStopPoint(TxStartStopPoint::EVConnected)) { + txStopped = true; + stoppedReason = TransactionEventData::StopReason::EVDisconnected; + } + } + } + + if (evseReadyInput && evseReadyInput()) { + if (!transaction && txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed)) { + transaction = allocateTransaction(); + txStarted = true; + } + + if (transaction && !transaction->powerPathClosed.triggered) { + transaction->powerPathClosed.triggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::ChargingStateChanged; + } + } else if (evseReadyInput && !evseReadyInput()) { + if (transaction && transaction->powerPathClosed.triggered) { + if (!transaction->powerPathClosed.untriggered) { + transaction->powerPathClosed.untriggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::ChargingStateChanged; + } + + if (txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) { + txStopped = true; + stoppedReason = TransactionEventData::StopReason::StoppedByEV; + } + } + } + + if (authorization.get()) { + if (!transaction && txService.isTxStartPoint(TxStartStopPoint::Authorized)) { + transaction = allocateTransaction(); + txStarted = true; + } + + if (transaction && !transaction->authorized.triggered) { + transaction->authorized.triggered = true; + txUpdated = true; + txUpdatedIdToken = true; + triggerReason = TransactionEventData::TriggerReason::Authorized; + } + } else { + if (transaction && transaction->authorized.triggered) { + if (!transaction->authorized.untriggered) { + transaction->authorized.untriggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::StopAuthorized; + } + + if (txService.isTxStopPoint(TxStartStopPoint::EVConnected)) { + txStopped = true; + stoppedReason = TransactionEventData::StopReason::Local; + } + } + } + + TransactionEventData::ChargingState chargingState = TransactionEventData::ChargingState::Idle; + if (transaction) { + chargingState = TransactionEventData::ChargingState::Charging; + + if (connectorPluggedInput && !connectorPluggedInput()) { + chargingState = TransactionEventData::ChargingState::Idle; + } else if (!authorization.get()) { + chargingState = TransactionEventData::ChargingState::EVConnected; + } else if (evseReadyInput && !evseReadyInput()) { + chargingState = TransactionEventData::ChargingState::SuspendedEVSE; + } else if (evReadyInput && !evReadyInput()) { + chargingState = TransactionEventData::ChargingState::SuspendedEV; + } else { + chargingState = TransactionEventData::ChargingState::Charging; + } + } + + if (txStarted && txStopped) { + MO_DBG_ERR("tx started and stopped"); + transaction.reset(); + return; + } else if (txStarted || txStopped || txUpdated) { + auto txEvent = std::make_shared(*transaction.get(), transaction->seqNoCounter++); + if (!txEvent) { + // OOM + transaction->active = false; + return; + } + + txEvent->eventType = txStarted ? TransactionEventData::Type::Started : + txStopped ? TransactionEventData::Type::Ended : + TransactionEventData::Type::Updated; + + txEvent->timestamp = context.getModel().getClock().now(); + txEvent->triggerReason = triggerReason; + + if (chargingState != trackChargingState) { + txEvent->chargingState = chargingState; + } + trackChargingState = chargingState; + + if (txStopped) { + txEvent->stoppedReason = stoppedReason; + } + + if (txUpdatedIdToken) { + txEvent->idTokenTransmit = true; + } + + txEvent->evse = evseId; + + // meterValue not supported + + auto txEventRequest = makeRequest(new Ocpp201::TransactionEvent(context.getModel(), txEvent)); + txEventRequest->setTimeout(0); + context.initiateRequest(std::move(txEventRequest)); + + if (txStopped) { + MO_DBG_DEBUG("drop completed transaction"); + transaction.reset(); + } + } +} + +void TransactionService::Evse::setConnectorPluggedInput(std::function connectorPlugged) { + this->connectorPluggedInput = connectorPlugged; +} + +void TransactionService::Evse::setEvReadyInput(std::function evRequestsEnergy) { + this->evReadyInput = evRequestsEnergy; +} + +void TransactionService::Evse::setEvseReadyInput(std::function connectorEnergized) { + this->evseReadyInput = connectorEnergized; +} + +bool TransactionService::Evse::beginAuthorization(IdToken idToken) { + authorization = idToken; + return true; +} +bool TransactionService::Evse::endAuthorization(IdToken idToken) { + authorization = IdToken(); + return true; +} +const char *TransactionService::Evse::getAuthorization() { + return authorization.get(); +} + +bool TransactionService::isTxStartPoint(TxStartStopPoint check) { + for (auto& v : txStartPointParsed) { + if (v == check) { + return true; + } + } + return false; +} +bool TransactionService::isTxStopPoint(TxStartStopPoint check) { + for (auto& v : txStopPointParsed) { + if (v == check) { + return true; + } + } + return false; +} + +bool TransactionService::parseTxStartStopPoint(const char *csl, std::vector& dst) { + dst.clear(); + + while (*csl == ',') { + csl++; + } + + while (*csl) { + if (!strncmp(csl, "ParkingBayOccupancy", sizeof("ParkingBayOccupancy") - 1) + && (csl[sizeof("ParkingBayOccupancy") - 1] == '\0' || csl[sizeof("ParkingBayOccupancy") - 1] == ',')) { + dst.push_back(TxStartStopPoint::ParkingBayOccupancy); + csl += sizeof("ParkingBayOccupancy") - 1; + } else if (!strncmp(csl, "EVConnected", sizeof("EVConnected") - 1) + && (csl[sizeof("EVConnected") - 1] == '\0' || csl[sizeof("EVConnected") - 1] == ',')) { + dst.push_back(TxStartStopPoint::EVConnected); + csl += sizeof("EVConnected") - 1; + } else if (!strncmp(csl, "Authorized", sizeof("Authorized") - 1) + && (csl[sizeof("Authorized") - 1] == '\0' || csl[sizeof("Authorized") - 1] == ',')) { + dst.push_back(TxStartStopPoint::Authorized); + csl += sizeof("Authorized") - 1; + } else if (!strncmp(csl, "DataSigned", sizeof("DataSigned") - 1) + && (csl[sizeof("DataSigned") - 1] == '\0' || csl[sizeof("DataSigned") - 1] == ',')) { + dst.push_back(TxStartStopPoint::DataSigned); + csl += sizeof("DataSigned") - 1; + } else if (!strncmp(csl, "PowerPathClosed", sizeof("PowerPathClosed") - 1) + && (csl[sizeof("PowerPathClosed") - 1] == '\0' || csl[sizeof("PowerPathClosed") - 1] == ',')) { + dst.push_back(TxStartStopPoint::PowerPathClosed); + csl += sizeof("PowerPathClosed") - 1; + } else if (!strncmp(csl, "EnergyTransfer", sizeof("EnergyTransfer") - 1) + && (csl[sizeof("EnergyTransfer") - 1] == '\0' || csl[sizeof("EnergyTransfer") - 1] == ',')) { + dst.push_back(TxStartStopPoint::EnergyTransfer); + csl += sizeof("EnergyTransfer") - 1; + } else { + MO_DBG_ERR("unkown TxStartStopPoint"); + dst.clear(); + return false; + } + + while (*csl == ',') { + csl++; + } + } + + return true; +} + +TransactionService::TransactionService(Context& context) : context(context) { + auto variableService = context.getModel().getVariableService(); + + TxStartPointString = variableService->declareVariable("TxCtrlr", "TxStartPoint", "PowerPathClosed"); + TxStopPointString = variableService->declareVariable("TxCtrlr", "TxStopPoint", "Authorized,EVConnected"); + + variableService->registerValidator("TxCtrlr", "TxStartPoint", [this] (const char *value) -> bool { + std::vector validated; + return this->parseTxStartStopPoint(value, validated); + }); + + variableService->registerValidator("TxCtrlr", "TxStopPoint", [this] (const char *value) -> bool { + std::vector validated; + return this->parseTxStartStopPoint(value, validated); + }); + + for (unsigned int evseId = 1; evseId < MO_NUM_EVSE; evseId++) { + evses.emplace_back(context, *this, evseId); + } +} + +void TransactionService::loop() { + for (Evse& evse : evses) { + evse.loop(); + } + + if (TxStartPointString->getWriteCount() != trackTxStartPoint) { + parseTxStartStopPoint(TxStartPointString->getString(), txStartPointParsed); + } + + if (TxStopPointString->getWriteCount() != trackTxStopPoint) { + parseTxStartStopPoint(TxStopPointString->getString(), txStopPointParsed); + } +} + +TransactionService::Evse *TransactionService::getEvse(unsigned int evseId) { + if (evseId >= 1 && evseId - 1 < evses.size()) { + return &evses[evseId - 1]; + } else { + MO_DBG_ERR("invalid arg"); + return nullptr; + } +} + + +#endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.h b/src/MicroOcpp/Model/Transactions/TransactionService.h new file mode 100644 index 00000000..6e39981e --- /dev/null +++ b/src/MicroOcpp/Model/Transactions/TransactionService.h @@ -0,0 +1,99 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs E01 - E12 + */ + +#ifndef MO_TRANSACTIONSERVICE_H +#define MO_TRANSACTIONSERVICE_H + +#include + +#if MO_ENABLE_V201 + +#include + +#include +#include +#include + +namespace MicroOcpp { + +class Context; +class Variable; + +class TransactionService { +public: + + class Evse { + private: + Context& context; + TransactionService& txService; + const unsigned int evseId; + std::shared_ptr transaction; + Ocpp201::TransactionEventData::ChargingState trackChargingState = Ocpp201::TransactionEventData::ChargingState::UNDEFINED; + + std::function connectorPluggedInput; + std::function evReadyInput; + std::function evseReadyInput; + + IdToken authorization; + + std::unique_ptr allocateTransaction(); + public: + Evse(Context& context, TransactionService& txService, unsigned int evseId); + + void loop(); + + void setConnectorPluggedInput(std::function connectorPlugged); + void setEvReadyInput(std::function evRequestsEnergy); + void setEvseReadyInput(std::function connectorEnergized); + + bool beginAuthorization(IdToken idToken); + bool endAuthorization(IdToken idToken = IdToken()); + const char *getAuthorization(); + }; + + friend Evse; + +private: + + // TxStartStopPoint (2.6.4.1) + enum class TxStartStopPoint : uint8_t { + ParkingBayOccupancy, + EVConnected, + Authorized, + DataSigned, + PowerPathClosed, + EnergyTransfer + }; + + Context& context; + std::vector evses; + + Variable *TxStartPointString = nullptr; + Variable *TxStopPointString = nullptr; + uint16_t trackTxStartPoint = -1; + uint16_t trackTxStopPoint = -1; + std::vector txStartPointParsed; + std::vector txStopPointParsed; + bool isTxStartPoint(TxStartStopPoint check); + bool isTxStopPoint(TxStartStopPoint check); + + bool parseTxStartStopPoint(const char *src, std::vector& dst); + +public: + TransactionService(Context& context); + + void loop(); + + Evse *getEvse(unsigned int evseId); +}; + +} // namespace MicroOcpp + +#endif // MO_ENABLE_V201 + +#endif diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index 69839d5c..9f9061bb 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -9,14 +9,16 @@ #ifndef MO_VARIABLE_H #define MO_VARIABLE_H +#include + +#if MO_ENABLE_V201 + #include #include #include #include -#include - -#if MO_ENABLE_V201 +#include #ifndef MO_VARIABLE_TYPECHECK #define MO_VARIABLE_TYPECHECK 1 @@ -89,15 +91,6 @@ class VariableMonitor { id(id), transaction(transaction), value(value), type(type), severity(severity) { } }; -// EVSEType (2.23) -struct EvseId { - int id; - int connectorId = -1; //optional - - EvseId(int id) : id(id) { } - EvseId(int id, int connectorId) : id(id), connectorId(connectorId) { } -}; - // ComponentType (2.16) struct ComponentId { const char *name; // zero copy diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp index 82cf1bfe..94ad0822 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.cpp +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -22,31 +22,32 @@ namespace MicroOcpp { -VariableValidator *VariableService::getValidatorInt(const char *variableName) { - for (auto& validator : validatorInt) { - if (!strcmp(validator.key, variableName)) { - return &validator; - } - } - return nullptr; +template +VariableValidator::VariableValidator(const ComponentId& component, const char *name, std::function validate) : + component(component), name(name), validate(validate) { + } -VariableValidator *VariableService::getValidatorBool(const char *variableName) { - for (auto& validator : validatorBool) { - if (!strcmp(validator.key, variableName)) { +template +VariableValidator *getVariableValidator(std::vector>& collection, const ComponentId& component, const char *name) { + for (auto& validator : collection) { + if (!strcmp(name, validator.name) && component.equals(validator.component)) { return &validator; } } return nullptr; } -VariableValidator *VariableService::getValidatorString(const char *variableName) { - for (auto& validator : validatorString) { - if (!strcmp(validator.key, variableName)) { - return &validator; - } - } - return nullptr; +VariableValidator *VariableService::getValidatorInt(const ComponentId& component, const char *name) { + return getVariableValidator(validatorInt, component, name); +} + +VariableValidator *VariableService::getValidatorBool(const ComponentId& component, const char *name) { + return getVariableValidator(validatorBool, component, name); +} + +VariableValidator *VariableService::getValidatorString(const ComponentId& component, const char *name) { + return getVariableValidator(validatorString, component, name); } std::unique_ptr VariableService::createContainer(const char *filename, bool accessible) const { @@ -75,6 +76,33 @@ std::shared_ptr VariableService::getContainer(const char *fil return nullptr; } +template +bool registerVariableValidator(std::vector>& collection, const ComponentId& component, const char *name, std::function validate) { + for (auto it = collection.begin(); it != collection.end(); it++) { + if (!strcmp(name, it->name) && component.equals(it->component)) { + collection.erase(it); + break; + } + } + collection.emplace_back(component, name, validate); + return true; +} + +template <> +bool VariableService::registerValidator(const ComponentId& component, const char *name, std::function validate) { + return registerVariableValidator(validatorInt, component, name, validate); +} + +template <> +bool VariableService::registerValidator(const ComponentId& component, const char *name, std::function validate) { + return registerVariableValidator(validatorBool, component, name, validate); +} + +template <> +bool VariableService::registerValidator(const ComponentId& component, const char *name, std::function validate) { + return registerVariableValidator(validatorString, component, name, validate); +} + VariableContainer *VariableService::declareContainer(const char *filename, bool accessible) { auto container = getContainer(filename); @@ -164,7 +192,7 @@ template<> Variable::InternalDataType getInternalDataType() {return Variab template<> Variable::InternalDataType getInternalDataType() {return Variable::InternalDataType::String;} template -Variable *VariableService::declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath, Variable::Mutability mutability, Variable::AttributeTypeSet attributes, bool rebootRequired, bool accessible) { +Variable *VariableService::declareVariable(const ComponentId& component, const char *name, T factoryDefault, const char *containerPath, Variable::Mutability mutability, Variable::AttributeTypeSet attributes, bool rebootRequired, bool accessible) { auto res = getVariable(getInternalDataType(), component, name, accessible); if (!res) { @@ -196,9 +224,9 @@ Variable *VariableService::declareVariable(const char *name, T factoryDefault, c return res; } -template Variable *VariableService::declareVariable(const char*, int, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); -template Variable *VariableService::declareVariable(const char*, bool, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); -template Variable *VariableService::declareVariable(const char*,const char*, const ComponentId&, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template Variable *VariableService::declareVariable(const ComponentId&, const char*, int, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template Variable *VariableService::declareVariable(const ComponentId&, const char*, bool, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); +template Variable *VariableService::declareVariable(const ComponentId&, const char*, const char*, const char*, Variable::Mutability, Variable::AttributeTypeSet, bool, bool); bool VariableService::commit() { bool success = true; @@ -319,21 +347,21 @@ SetVariableStatus VariableService::setVariable(Variable::AttributeType attrType, // validate and store (parsed) value to Config if (variable->getInternalDataType() == Variable::InternalDataType::Int && convertibleInt) { - auto validator = getValidatorInt(variableName); + auto validator = getValidatorInt(component, variableName); if (validator && !validator->validate(numInt)) { MO_DBG_WARN("validation failed for variable=%s", variableName); return SetVariableStatus::Rejected; } variable->setInt(numInt); } else if (variable->getInternalDataType() == Variable::InternalDataType::Bool && convertibleBool) { - auto validator = getValidatorBool(variableName); + auto validator = getValidatorBool(component, variableName); if (validator && !validator->validate(numBool)) { MO_DBG_WARN("validation failed for variable=%s", variableName); return SetVariableStatus::Rejected; } variable->setBool(numBool); } else if (variable->getInternalDataType() == Variable::InternalDataType::String) { - auto validator = getValidatorString(variableName); + auto validator = getValidatorString(component, variableName); if (validator && !validator->validate(value)) { MO_DBG_WARN("validation failed for variable=%s", variableName); return SetVariableStatus::Rejected; diff --git a/src/MicroOcpp/Model/Variables/VariableService.h b/src/MicroOcpp/Model/Variables/VariableService.h index 4d46fbd7..2cf5b1b7 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.h +++ b/src/MicroOcpp/Model/Variables/VariableService.h @@ -39,9 +39,10 @@ namespace MicroOcpp { template struct VariableValidator { - const char *key = nullptr; + ComponentId component; + const char *name; std::function validate; - VariableValidator(const char *key, std::function validate) : key(key), validate(validate) { } + VariableValidator(const ComponentId& component, const char *name, std::function validate); }; class Context; @@ -55,9 +56,9 @@ class VariableService { std::vector> validatorBool; std::vector> validatorString; - VariableValidator *getValidatorInt(const char *variableName); - VariableValidator *getValidatorBool(const char *variableName); - VariableValidator *getValidatorString(const char *variableName); + VariableValidator *getValidatorInt(const ComponentId& component, const char *name); + VariableValidator *getValidatorBool(const ComponentId& component, const char *name); + VariableValidator *getValidatorString(const ComponentId& component, const char *name); std::unique_ptr createContainer(const char *filename, bool accessible) const; @@ -69,7 +70,7 @@ class VariableService { VariableService(Context& context, std::shared_ptr filesystem); template - Variable *declareVariable(const char *name, T factoryDefault, const ComponentId& component, const char *containerPath = MO_VARIABLE_FN, Variable::Mutability mutability = Variable::Mutability::ReadWrite, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false, bool accessible = true); + Variable *declareVariable(const ComponentId& component, const char *name, T factoryDefault, const char *containerPath = MO_VARIABLE_FN, Variable::Mutability mutability = Variable::Mutability::ReadWrite, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false, bool accessible = true); bool commit(); @@ -77,6 +78,9 @@ class VariableService { std::shared_ptr getContainer(const char *filename); + template + bool registerValidator(const ComponentId& component, const char *name, std::function validate); + SetVariableStatus setVariable(Variable::AttributeType attrType, const char *attrVal, const ComponentId& component, const char *variableName); GetVariableStatus getVariable(Variable::AttributeType attrType, const ComponentId& component, const char *variableName, Variable **result); diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp new file mode 100644 index 00000000..f75020b8 --- /dev/null +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -0,0 +1,255 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2023 +// MIT License + +#include +#include +#include +#include + +using MicroOcpp::Ocpp201::TransactionEvent; + +TransactionEvent::TransactionEvent(Model& model, std::shared_ptr txEvent) + : model(model), txEvent(txEvent) { + +} + +const char* TransactionEvent::getOperationType() { + return "TransactionEvent"; +} + +std::unique_ptr TransactionEvent::createReq() { + auto doc = std::unique_ptr(new DynamicJsonDocument( + 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 + JsonObject payload = doc->to(); + + const char *eventType = ""; + switch (txEvent->eventType) { + case TransactionEventData::Type::Ended: + eventType = "Ended"; + break; + case TransactionEventData::Type::Started: + eventType = "Started"; + break; + case TransactionEventData::Type::Updated: + eventType = "Updated"; + break; + } + + payload["eventType"] = eventType; + + char timestamp [JSONDATE_LENGTH + 1]; + txEvent->timestamp.toJsonString(timestamp, JSONDATE_LENGTH + 1); + payload["timestamp"] = timestamp; + + const char *triggerReason = ""; + switch(txEvent->triggerReason) { + case TransactionEventData::TriggerReason::Authorized: + triggerReason = "Authorized"; + break; + case TransactionEventData::TriggerReason::CablePluggedIn: + triggerReason = "CablePluggedIn"; + break; + case TransactionEventData::TriggerReason::ChargingRateChanged: + triggerReason = "ChargingRateChanged"; + break; + case TransactionEventData::TriggerReason::ChargingStateChanged: + triggerReason = "ChargingStateChanged"; + break; + case TransactionEventData::TriggerReason::Deauthorized: + triggerReason = "Deauthorized"; + break; + case TransactionEventData::TriggerReason::EnergyLimitReached: + triggerReason = "EnergyLimitReached"; + break; + case TransactionEventData::TriggerReason::EVCommunicationLost: + triggerReason = "EVCommunicationLost"; + break; + case TransactionEventData::TriggerReason::EVConnectTimeout: + triggerReason = "EVConnectTimeout"; + break; + case TransactionEventData::TriggerReason::MeterValueClock: + triggerReason = "MeterValueClock"; + break; + case TransactionEventData::TriggerReason::MeterValuePeriodic: + triggerReason = "MeterValuePeriodic"; + break; + case TransactionEventData::TriggerReason::TimeLimitReached: + triggerReason = "TimeLimitReached"; + break; + case TransactionEventData::TriggerReason::Trigger: + triggerReason = "Trigger"; + break; + case TransactionEventData::TriggerReason::UnlockCommand: + triggerReason = "UnlockCommand"; + break; + case TransactionEventData::TriggerReason::StopAuthorized: + triggerReason = "StopAuthorized"; + break; + case TransactionEventData::TriggerReason::EVDeparted: + triggerReason = "EVDeparted"; + break; + case TransactionEventData::TriggerReason::EVDetected: + triggerReason = "EVDetected"; + break; + case TransactionEventData::TriggerReason::RemoteStop: + triggerReason = "RemoteStop"; + break; + case TransactionEventData::TriggerReason::RemoteStart: + triggerReason = "RemoteStart"; + break; + case TransactionEventData::TriggerReason::AbnormalCondition: + triggerReason = "AbnormalCondition"; + break; + case TransactionEventData::TriggerReason::SignedDataReceived: + triggerReason = "SignedDataReceived"; + break; + case TransactionEventData::TriggerReason::ResetCommand: + triggerReason = "ResetCommand"; + break; + } + + payload["triggerReason"] = triggerReason; + + payload["seqNo"] = txEvent->seqNo; + + if (txEvent->offline) { + payload["offline"] = txEvent->offline; + } + + if (txEvent->numberOfPhasesUsed >= 0) { + payload["numberOfPhasesUsed"] = txEvent->numberOfPhasesUsed; + } + + if (txEvent->cableMaxCurrent >= 0) { + payload["cableMaxCurrent"] = txEvent->cableMaxCurrent; + } + + if (txEvent->reservationId >= 0) { + payload["reservationId"] = txEvent->reservationId; + } + + JsonObject transactionInfo = payload.createNestedObject("transactionInfo"); + transactionInfo["transactionId"] = txEvent->transaction.transactionId; + + const char *chargingState = nullptr; + switch (txEvent->chargingState) { + case TransactionEventData::ChargingState::Charging: + chargingState = "Charging"; + break; + case TransactionEventData::ChargingState::EVConnected: + chargingState = "EVConnected"; + break; + case TransactionEventData::ChargingState::SuspendedEV: + chargingState = "SuspendedEV"; + break; + case TransactionEventData::ChargingState::SuspendedEVSE: + chargingState = "SuspendedEVSE"; + break; + case TransactionEventData::ChargingState::Idle: + chargingState = "Idle"; + break; + } + if (chargingState) { // optional + transactionInfo["chargingState"] = chargingState; + } + + const char *stoppedReason = nullptr; + switch (txEvent->stoppedReason) { + case TransactionEventData::StopReason::DeAuthorized: + stoppedReason = "DeAuthorized"; + break; + case TransactionEventData::StopReason::EmergencyStop: + stoppedReason = "EmergencyStop"; + break; + case TransactionEventData::StopReason::EnergyLimitReached: + stoppedReason = "EnergyLimitReached"; + break; + case TransactionEventData::StopReason::EVDisconnected: + stoppedReason = "EVDisconnected"; + break; + case TransactionEventData::StopReason::GroundFault: + stoppedReason = "GroundFault"; + break; + case TransactionEventData::StopReason::ImmediateReset: + stoppedReason = "ImmediateReset"; + break; + case TransactionEventData::StopReason::LocalOutOfCredit: + stoppedReason = "LocalOutOfCredit"; + break; + case TransactionEventData::StopReason::MasterPass: + stoppedReason = "MasterPass"; + break; + case TransactionEventData::StopReason::Other: + stoppedReason = "Other"; + break; + case TransactionEventData::StopReason::OvercurrentFault: + stoppedReason = "OvercurrentFault"; + break; + case TransactionEventData::StopReason::PowerLoss: + stoppedReason = "PowerLoss"; + break; + case TransactionEventData::StopReason::PowerQuality: + stoppedReason = "PowerQuality"; + break; + case TransactionEventData::StopReason::Reboot: + stoppedReason = "Reboot"; + break; + case TransactionEventData::StopReason::Remote: + stoppedReason = "Remote"; + break; + case TransactionEventData::StopReason::SOCLimitReached: + stoppedReason = "SOCLimitReached"; + break; + case TransactionEventData::StopReason::StoppedByEV: + stoppedReason = "StoppedByEV"; + break; + case TransactionEventData::StopReason::TimeLimitReached: + stoppedReason = "TimeLimitReached"; + break; + case TransactionEventData::StopReason::Timeout: + stoppedReason = "Timeout"; + break; + } + if (stoppedReason) { // optional + transactionInfo["stoppedReason"] = stoppedReason; + } + + if (txEvent->transaction.remoteStartId >= 0) { + payload["remoteStartId"] = txEvent->transaction.remoteStartId; + } + + if (txEvent->idTokenTransmit) { + JsonObject idToken = payload.createNestedObject("idToken"); + idToken["idToken"] = txEvent->transaction.idToken.get(); + idToken["type"] = txEvent->transaction.idToken.getTypeCstr(); + } + + if (txEvent->evse.id >= 0) { + JsonObject evse = payload.createNestedObject("evse"); + evse["id"] = txEvent->evse.id; + if (txEvent->evse.connectorId >= 0) { + evse["connectorId"] = txEvent->evse.connectorId; + } + } + + // meterValue not supported + + return doc; +} + +void TransactionEvent::processConf(JsonObject payload) { + + if (payload.containsKey("idTokenInfo")) { + if (strcmp(payload["idTokenInfo"]["status"], "Accepted")) { + MO_DBG_INFO("transaction deAuthorized"); + txEvent->transaction.active = false; + txEvent->transaction.isDeauthorized = true; + } + } +} diff --git a/src/MicroOcpp/Operations/TransactionEvent.h b/src/MicroOcpp/Operations/TransactionEvent.h new file mode 100644 index 00000000..04d34c0a --- /dev/null +++ b/src/MicroOcpp/Operations/TransactionEvent.h @@ -0,0 +1,44 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2023 +// MIT License + +#ifndef MO_TRANSACTIONEVENT_H +#define MO_TRANSACTIONEVENT_H + +#include + +#if MO_ENABLE_V201 + +#include + +namespace MicroOcpp { + +class Model; + +namespace Ocpp201 { + +class TransactionEventData; + +class TransactionEvent : public Operation { +private: + Model& model; + std::shared_ptr txEvent; + + const char *errorCode; +public: + + TransactionEvent(Model& model, std::shared_ptr txEvent); + + const char* getOperationType() override; + + std::unique_ptr createReq() override; + + void processConf(JsonObject payload) override; + + const char *getErrorCode() override {return errorCode;} +}; + +} //end namespace Ocpp201 +} //end namespace MicroOcpp +#endif // MO_ENABLE_V201 +#endif diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp new file mode 100644 index 00000000..42ad5335 --- /dev/null +++ b/tests/Transactions.cpp @@ -0,0 +1,60 @@ +#include + +#if MO_ENABLE_V201 + +#include +#include +#include +#include +#include +#include +#include +#include "./catch2/catch.hpp" +#include "./helpers/testHelper.h" + +#define BASE_TIME "2023-01-01T00:00:00.000Z" + +using namespace MicroOcpp; + + +TEST_CASE( "Transactions" ) { + printf("\nRun %s\n", "Transactions"); + + //initialize Context with dummy socket + LoopbackConnection loopback; + mocpp_initialize(loopback, + ChargerCredentials("test-runner1234"), + makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail), + false, + ProtocolVersion(2,0,1)); + + auto context = getOcppContext(); + auto& checkMsg = context->getOperationRegistry(); + + mocpp_set_timer(custom_timer_cb); + + loop(); + + SECTION("Basic transaction") { + + setConnectorPluggedInput([] () {return true;}); + setEvReadyInput([] () {return true;}); + setEvseReadyInput([] () {return true;}); + + context->getModel().getTransactionService()->getEvse(1)->beginAuthorization("mIdToken"); + + loop(); + + setConnectorPluggedInput([] () {return false;}); + setEvReadyInput([] () {return false;}); + setEvseReadyInput([] () {return false;}); + + context->getModel().getTransactionService()->getEvse(1)->endAuthorization(); + + loop(); + } + + mocpp_deinitialize(); +} + +#endif // MO_ENABLE_V201 diff --git a/tests/Variables.cpp b/tests/Variables.cpp index c7747187..06518997 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -115,13 +115,13 @@ TEST_CASE( "Variable" ) { //declare configs mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); auto vs = getOcppContext()->getModel().getVariableService(); - auto cInt = vs->declareVariable("cInt", 42, "mComponent"); + auto cInt = vs->declareVariable("mComponent", "cInt", 42); REQUIRE( cInt != nullptr ); - vs->declareVariable("cBool", true, "mComponent"); - vs->declareVariable("cString", "mValue", "mComponent"); + vs->declareVariable("mComponent", "cBool", true); + vs->declareVariable("mComponent", "cString", "mValue"); //fetch config - REQUIRE( vs->declareVariable("cInt", -1, "mComponent")->getInt() == 42 ); + REQUIRE( vs->declareVariable("mComponent", "cInt", -1)->getInt() == 42 ); #if 0 //store, destroy, reload @@ -147,15 +147,15 @@ TEST_CASE( "Variable" ) { #endif //declare config twice - auto cInt3 = vs->declareVariable("cInt", -1, "mComponent"); + auto cInt3 = vs->declareVariable("mComponent", "cInt", -1); REQUIRE( cInt3 == cInt2 ); //declare config twice but in different container - auto cInt4 = vs->declareVariable("cInt", -1, "mComponent", MO_VARIABLE_VOLATILE "/volatile2"); + auto cInt4 = vs->declareVariable("mComponent", "cInt", -1, MO_VARIABLE_VOLATILE "/volatile2"); REQUIRE( cInt4 == cInt2 ); //declare config twice but with conflicting type (will supersede old type because to simplify FW upgrades) - auto cNewType = vs->declareVariable("cInt", "mValue2", "mComponent"); + auto cNewType = vs->declareVariable("mComponent", "cInt", "mValue2"); REQUIRE( cNewType != cInt2 ); REQUIRE( cInt2->isDetached() ); // Container should not store this REQUIRE( !strcmp(cNewType->getString(), "mValue2") ); @@ -194,29 +194,29 @@ TEST_CASE( "Variable" ) { Variable::AttributeTypeSet attrs = Variable::AttributeType::Actual; bool rebootRequired = false; bool isAccessible = true; - auto cInt6 = vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + auto cInt6 = vs->declareVariable("mComponent", "cInt", 42, MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); REQUIRE( cInt6->getMutability() == Variable::Mutability::ReadWrite ); REQUIRE( !cInt6->isRebootRequired() ); - REQUIRE( vs->declareVariable("cInt", 42, "mComponent") ); + REQUIRE( vs->declareVariable("mComponent", "cInt", 42) ); //revoke permissions mutability = Variable::Mutability::ReadOnly; rebootRequired = true; - vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + vs->declareVariable("mComponent", "cInt", 42, MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); REQUIRE( cInt6->getMutability() == mutability ); REQUIRE( cInt6->isRebootRequired() ); //revoked permissions cannot be reverted mutability = Variable::Mutability::ReadWrite; rebootRequired = false; - auto cInt7 = vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + auto cInt7 = vs->declareVariable("mComponent", "cInt", 42, MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); REQUIRE( cInt7->getMutability() == Variable::Mutability::ReadOnly ); REQUIRE( cInt7->isRebootRequired() ); //accessibility cannot be changed after first initialization isAccessible = false; - vs->declareVariable("cInt", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); - vs->declareVariable("cInt2", 42, "mComponent", MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + vs->declareVariable("mComponent", "cInt", 42, MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); + vs->declareVariable("mComponent", "cInt2", 42, MO_VARIABLE_VOLATILE, mutability, attrs, rebootRequired, isAccessible); Variable *result = nullptr; REQUIRE( vs->getVariable(Variable::AttributeType::Actual, "mComponent", "cInt", &result) == GetVariableStatus::Accepted ); REQUIRE( result != nullptr ); @@ -226,7 +226,7 @@ TEST_CASE( "Variable" ) { //create config in hidden container isAccessible = false; - auto cHidden = vs->declareVariable("cHidden", 42, "mComponent", MO_VARIABLE_VOLATILE "/hidden.json", mutability, attrs, rebootRequired, isAccessible); + auto cHidden = vs->declareVariable("mComponent", "cHidden", 42, MO_VARIABLE_VOLATILE "/hidden.json", mutability, attrs, rebootRequired, isAccessible); (void)cHidden; result = nullptr; REQUIRE( vs->getVariable(Variable::AttributeType::Actual, "mComponent", "cHidden", &result) == GetVariableStatus::Accepted ); From 8b03ac912da12115b3fde40411bcf1b7485416a5 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:38:06 +0100 Subject: [PATCH 13/23] fix compilation without MO_ENABLE_V201 --- src/MicroOcpp/Model/Model.cpp | 4 +++- src/MicroOcpp/Operations/TransactionEvent.cpp | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index ff3b0b86..8acf20b0 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -74,9 +74,11 @@ void Model::loop() { if (resetService) resetService->loop(); - + +#if MO_ENABLE_V201 if (transactionService) transactionService->loop(); +#endif } void Model::setTransactionStore(std::unique_ptr ts) { diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp index f75020b8..4f4bff32 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.cpp +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -2,6 +2,10 @@ // Copyright Matthias Akstaller 2019 - 2023 // MIT License +#include + +#if MO_ENABLE_V201 + #include #include #include @@ -253,3 +257,5 @@ void TransactionEvent::processConf(JsonObject payload) { } } } + +#endif // MO_ENABLE_V201 From 2ff0afce362ff1bbca896d4718ae53d28591ff7c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:43:45 +0100 Subject: [PATCH 14/23] fix unit tests --- tests/Variables.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Variables.cpp b/tests/Variables.cpp index 06518997..24809643 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -505,6 +505,8 @@ TEST_CASE( "Variable" ) { } #endif + + mocpp_deinitialize(); } #endif // MO_ENABLE_V201 From b10bc29dc57726e111561307698e82420ed3033c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:45:23 +0100 Subject: [PATCH 15/23] TransactionEventData owns Transaction --- .../Model/Transactions/Transaction.h | 6 +++-- .../Model/Transactions/TransactionService.cpp | 23 ++++++++++++++++--- src/MicroOcpp/Operations/TransactionEvent.cpp | 14 +++++------ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index df7531ce..12f1af46 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -171,6 +171,8 @@ class Transaction { #if MO_ENABLE_V201 +#include + #include #include @@ -279,7 +281,7 @@ class TransactionEventData { }; //private: - Transaction& transaction; + std::shared_ptr transaction; Type eventType; Timestamp timestamp; TriggerReason triggerReason; @@ -298,7 +300,7 @@ class TransactionEventData { EvseId evse = -1; //meterValue not supported - TransactionEventData(Transaction& transaction, unsigned int seqNo) : transaction(transaction), seqNo(seqNo) { } + TransactionEventData(std::shared_ptr transaction, unsigned int seqNo) : transaction(transaction), seqNo(seqNo) { } }; } // namespace Ocpp201 diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index 020703db..0c3129fd 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -27,7 +27,24 @@ TransactionService::Evse::Evse(Context& context, TransactionService& txService, } std::unique_ptr TransactionService::Evse::allocateTransaction() { - return std::unique_ptr(new Ocpp201::Transaction()); + auto tx = std::unique_ptr(new Ocpp201::Transaction()); + if (!tx) { + // OOM + return nullptr; + } + + //simple clock-based hash + int v = context.getModel().getClock().now() - Timestamp(2020,0,0,0,0,0); + unsigned int h = v; + h *= 749572633U; + h %= 24593209U; + for (size_t i = 0; i < sizeof(tx->transactionId) - 3; i += 2) { + sprintf(tx->transactionId + i, "%02X", (uint8_t)h); + h *= 749572633U; + h %= 24593209U; + } + + return tx; } void TransactionService::Evse::loop() { @@ -142,7 +159,7 @@ void TransactionService::Evse::loop() { transaction.reset(); return; } else if (txStarted || txStopped || txUpdated) { - auto txEvent = std::make_shared(*transaction.get(), transaction->seqNoCounter++); + auto txEvent = std::make_shared(transaction, transaction->seqNoCounter++); if (!txEvent) { // OOM transaction->active = false; @@ -166,6 +183,7 @@ void TransactionService::Evse::loop() { } if (txUpdatedIdToken) { + txEvent->transaction->idToken = authorization; txEvent->idTokenTransmit = true; } @@ -315,5 +333,4 @@ TransactionService::Evse *TransactionService::getEvse(unsigned int evseId) { } } - #endif // MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp index 4f4bff32..e221c8b0 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.cpp +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -139,7 +139,7 @@ std::unique_ptr TransactionEvent::createReq() { } JsonObject transactionInfo = payload.createNestedObject("transactionInfo"); - transactionInfo["transactionId"] = txEvent->transaction.transactionId; + transactionInfo["transactionId"] = txEvent->transaction->transactionId; const char *chargingState = nullptr; switch (txEvent->chargingState) { @@ -224,14 +224,14 @@ std::unique_ptr TransactionEvent::createReq() { transactionInfo["stoppedReason"] = stoppedReason; } - if (txEvent->transaction.remoteStartId >= 0) { - payload["remoteStartId"] = txEvent->transaction.remoteStartId; + if (txEvent->transaction->remoteStartId >= 0) { + payload["remoteStartId"] = txEvent->transaction->remoteStartId; } if (txEvent->idTokenTransmit) { JsonObject idToken = payload.createNestedObject("idToken"); - idToken["idToken"] = txEvent->transaction.idToken.get(); - idToken["type"] = txEvent->transaction.idToken.getTypeCstr(); + idToken["idToken"] = txEvent->transaction->idToken.get(); + idToken["type"] = txEvent->transaction->idToken.getTypeCstr(); } if (txEvent->evse.id >= 0) { @@ -252,8 +252,8 @@ void TransactionEvent::processConf(JsonObject payload) { if (payload.containsKey("idTokenInfo")) { if (strcmp(payload["idTokenInfo"]["status"], "Accepted")) { MO_DBG_INFO("transaction deAuthorized"); - txEvent->transaction.active = false; - txEvent->transaction.isDeauthorized = true; + txEvent->transaction->active = false; + txEvent->transaction->isDeauthorized = true; } } } From 3d58a3da2ddeca8371988164ab7a36a48318f309 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:50:26 +0100 Subject: [PATCH 16/23] avoid superfluous TransactionEvents --- .../Model/Transactions/Transaction.h | 2 + .../Model/Transactions/TransactionService.cpp | 135 ++++++++++-------- tests/Transactions.cpp | 32 ++++- 3 files changed, 106 insertions(+), 63 deletions(-) diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 12f1af46..c85287f5 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -208,6 +208,8 @@ class Transaction { Timestamp startTime = MIN_TIME; char transactionId [MO_TXID_LEN_MAX + 1] = {'\0'}; int remoteStartId = -1; + + bool idTokenTransmitted = false; }; // TransactionEventRequest (1.60.1) diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index 0c3129fd..652f85b8 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -53,90 +53,100 @@ void TransactionService::Evse::loop() { bool txStopped = false; bool txUpdated = false; - bool txUpdatedIdToken = false; TransactionEventData::TriggerReason triggerReason; // only valid if txStarted || txStopped || txUpdated TransactionEventData::StopReason stoppedReason; // only valid if txStopped - if (connectorPluggedInput && connectorPluggedInput()) { - if (!transaction && txService.isTxStartPoint(TxStartStopPoint::EVConnected)) { - transaction = allocateTransaction(); - txStarted = true; - } + if (!txStarted && !txStopped && !txUpdated) { + if (connectorPluggedInput && connectorPluggedInput()) { + if (!transaction && txService.isTxStartPoint(TxStartStopPoint::EVConnected)) { + transaction = allocateTransaction(); + txStarted = true; + } - if (transaction && !transaction->evConnected.triggered) { - transaction->evConnected.triggered = true; - txUpdated = true; - triggerReason = TransactionEventData::TriggerReason::CablePluggedIn; - } - } else if (connectorPluggedInput && !connectorPluggedInput()) { - if (transaction && transaction->evConnected.triggered) { - if (!transaction->evConnected.untriggered) { - transaction->evConnected.untriggered = true; + if (transaction && !transaction->evConnected.triggered) { + transaction->evConnected.triggered = true; txUpdated = true; - triggerReason = TransactionEventData::TriggerReason::EVCommunicationLost; + triggerReason = TransactionEventData::TriggerReason::CablePluggedIn; } - - if (txService.isTxStopPoint(TxStartStopPoint::EVConnected)) { - txStopped = true; - stoppedReason = TransactionEventData::StopReason::EVDisconnected; + } else if (connectorPluggedInput && !connectorPluggedInput()) { + if (transaction && transaction->evConnected.triggered) { + if (!transaction->evConnected.untriggered) { + transaction->evConnected.untriggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::EVCommunicationLost; + } + + if (txService.isTxStopPoint(TxStartStopPoint::EVConnected)) { + txStopped = true; + stoppedReason = TransactionEventData::StopReason::EVDisconnected; + } } } } - if (evseReadyInput && evseReadyInput()) { - if (!transaction && txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed)) { - transaction = allocateTransaction(); - txStarted = true; - } + if (!txStarted && !txStopped && !txUpdated) { + if (evseReadyInput && evseReadyInput()) { + if (!transaction && txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed)) { + transaction = allocateTransaction(); + txStarted = true; + } - if (transaction && !transaction->powerPathClosed.triggered) { - transaction->powerPathClosed.triggered = true; - txUpdated = true; - triggerReason = TransactionEventData::TriggerReason::ChargingStateChanged; - } - } else if (evseReadyInput && !evseReadyInput()) { - if (transaction && transaction->powerPathClosed.triggered) { - if (!transaction->powerPathClosed.untriggered) { - transaction->powerPathClosed.untriggered = true; + if (transaction && !transaction->powerPathClosed.triggered) { + transaction->powerPathClosed.triggered = true; txUpdated = true; triggerReason = TransactionEventData::TriggerReason::ChargingStateChanged; } - - if (txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) { - txStopped = true; - stoppedReason = TransactionEventData::StopReason::StoppedByEV; + } else if (evseReadyInput && !evseReadyInput()) { + if (transaction && transaction->powerPathClosed.triggered) { + if (!transaction->powerPathClosed.untriggered) { + transaction->powerPathClosed.untriggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::ChargingStateChanged; + } + + if (txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) { + txStopped = true; + stoppedReason = TransactionEventData::StopReason::StoppedByEV; + } } } } - if (authorization.get()) { - if (!transaction && txService.isTxStartPoint(TxStartStopPoint::Authorized)) { - transaction = allocateTransaction(); - txStarted = true; - } + if (!txStarted && !txStopped && !txUpdated) { + if (authorization.get()) { + if (!transaction && txService.isTxStartPoint(TxStartStopPoint::Authorized)) { + transaction = allocateTransaction(); + txStarted = true; + } - if (transaction && !transaction->authorized.triggered) { - transaction->authorized.triggered = true; - txUpdated = true; - txUpdatedIdToken = true; - triggerReason = TransactionEventData::TriggerReason::Authorized; - } - } else { - if (transaction && transaction->authorized.triggered) { - if (!transaction->authorized.untriggered) { - transaction->authorized.untriggered = true; + if (transaction && !transaction->authorized.triggered) { + transaction->authorized.triggered = true; txUpdated = true; - triggerReason = TransactionEventData::TriggerReason::StopAuthorized; + triggerReason = TransactionEventData::TriggerReason::Authorized; } - - if (txService.isTxStopPoint(TxStartStopPoint::EVConnected)) { - txStopped = true; - stoppedReason = TransactionEventData::StopReason::Local; + } else { + if (transaction && transaction->authorized.triggered) { + if (!transaction->authorized.untriggered) { + transaction->authorized.untriggered = true; + txUpdated = true; + triggerReason = TransactionEventData::TriggerReason::StopAuthorized; + } + + if (txService.isTxStopPoint(TxStartStopPoint::EVConnected)) { + txStopped = true; + stoppedReason = TransactionEventData::StopReason::Local; + } } } } + if (txStarted) { + transaction->evConnected.triggered |= connectorPluggedInput && connectorPluggedInput(); + transaction->powerPathClosed.triggered |= evseReadyInput && evseReadyInput(); + transaction->authorized.triggered |= authorization.get() != nullptr; + } + TransactionEventData::ChargingState chargingState = TransactionEventData::ChargingState::Idle; if (transaction) { chargingState = TransactionEventData::ChargingState::Charging; @@ -182,9 +192,10 @@ void TransactionService::Evse::loop() { txEvent->stoppedReason = stoppedReason; } - if (txUpdatedIdToken) { + if (authorization.get() && !transaction->idTokenTransmitted) { txEvent->transaction->idToken = authorization; txEvent->idTokenTransmit = true; + transaction->idTokenTransmitted = true; } txEvent->evse = evseId; @@ -216,10 +227,16 @@ void TransactionService::Evse::setEvseReadyInput(std::function connector bool TransactionService::Evse::beginAuthorization(IdToken idToken) { authorization = idToken; + if (transaction) { + transaction->idTokenTransmitted = false; + } return true; } bool TransactionService::Evse::endAuthorization(IdToken idToken) { authorization = IdToken(); + if (transaction) { + transaction->idTokenTransmitted = false; + } return true; } const char *TransactionService::Evse::getAuthorization() { diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp index 42ad5335..d615b48c 100644 --- a/tests/Transactions.cpp +++ b/tests/Transactions.cpp @@ -37,19 +37,43 @@ TEST_CASE( "Transactions" ) { SECTION("Basic transaction") { + MO_DBG_DEBUG("plug EV"); setConnectorPluggedInput([] () {return true;}); - setEvReadyInput([] () {return true;}); - setEvseReadyInput([] () {return true;}); + loop(); + + MO_DBG_DEBUG("authorize"); context->getModel().getTransactionService()->getEvse(1)->beginAuthorization("mIdToken"); loop(); - setConnectorPluggedInput([] () {return false;}); + MO_DBG_DEBUG("EV requests charge"); + setEvReadyInput([] () {return true;}); + + loop(); + + MO_DBG_DEBUG("power circuit closed"); + setEvseReadyInput([] () {return true;}); + + loop(); + + MO_DBG_DEBUG("EV idle"); setEvReadyInput([] () {return false;}); + + loop(); + + MO_DBG_DEBUG("power circuit opened"); setEvseReadyInput([] () {return false;}); - context->getModel().getTransactionService()->getEvse(1)->endAuthorization(); + loop(); + + MO_DBG_DEBUG("deauthorize"); + context->getModel().getTransactionService()->getEvse(1)->endAuthorization("mIdToken"); + + loop(); + + MO_DBG_DEBUG("unplug EV"); + setConnectorPluggedInput([] () {return false;}); loop(); } From 689141209aad3daa638e74a185c6f483602279e6 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 26 Feb 2024 22:48:23 +0100 Subject: [PATCH 17/23] Authorize idToken --- src/MicroOcpp/Model/Authorization/IdToken.cpp | 4 +-- src/MicroOcpp/Model/Authorization/IdToken.h | 4 +-- .../Model/Transactions/TransactionService.cpp | 29 ++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp index 0210bd64..aab55a7b 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.cpp +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -27,11 +27,11 @@ IdToken::IdToken(const char *token, Type type) : type(type) { } } -const char *IdToken::get() { +const char *IdToken::get() const { return *idToken ? idToken : nullptr;; } -const char *IdToken::getTypeCstr() { +const char *IdToken::getTypeCstr() const { const char *res = nullptr; switch (type) { case Type::Central: diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h index 7b03ce10..441055dc 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.h +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -39,8 +39,8 @@ class IdToken { IdToken(); IdToken(const char *token, Type type = Type::ISO14443); - const char *get(); - const char *getTypeCstr(); + const char *get() const; + const char *getTypeCstr() const; }; } // namespace MicroOcpp diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index 652f85b8..14863b91 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -226,13 +227,33 @@ void TransactionService::Evse::setEvseReadyInput(std::function connector } bool TransactionService::Evse::beginAuthorization(IdToken idToken) { - authorization = idToken; - if (transaction) { - transaction->idTokenTransmitted = false; - } + MO_DBG_DEBUG("begin auth: %s", idToken.get()); + IdToken idTokenCpy = idToken; + auto authorize = makeRequest(new Ocpp16::CustomOperation( + "Authorize", + [idTokenCpy] () { + auto doc = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(3) + + JSON_OBJECT_SIZE(2) + + MO_IDTOKEN_LEN_MAX + 1)); + auto payload = doc->to(); + payload["idToken"]["idToken"] = idTokenCpy.get(); + payload["idToken"]["type"] = idTokenCpy.getTypeCstr(); + return doc; + }, [this, idTokenCpy] (JsonObject response) { + if (!strcmp(response["idTokenInfo"]["status"], "Accepted")) { + authorization = idTokenCpy; + if (transaction) { + transaction->idTokenTransmitted = false; + } + MO_DBG_DEBUG("confirmed auth: %s", idTokenCpy.get()); + } + })); + context.initiateRequest(std::move(authorize)); return true; } bool TransactionService::Evse::endAuthorization(IdToken idToken) { + MO_DBG_DEBUG("end auth: %s", idToken.get()? idToken.get() : "(empty)"); authorization = IdToken(); if (transaction) { transaction->idTokenTransmitted = false; From 7b9bdd077153398b9fdcfbc850adbe328a36e318 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:10:19 +0100 Subject: [PATCH 18/23] fix Transactions unit test --- .../Model/Transactions/TransactionService.cpp | 15 +++++++-------- tests/Transactions.cpp | 11 +++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService.cpp index 14863b91..7a8ed1c6 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService.cpp @@ -228,25 +228,24 @@ void TransactionService::Evse::setEvseReadyInput(std::function connector bool TransactionService::Evse::beginAuthorization(IdToken idToken) { MO_DBG_DEBUG("begin auth: %s", idToken.get()); - IdToken idTokenCpy = idToken; auto authorize = makeRequest(new Ocpp16::CustomOperation( "Authorize", - [idTokenCpy] () { + [idToken] () { auto doc = std::unique_ptr(new DynamicJsonDocument( JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(2) + MO_IDTOKEN_LEN_MAX + 1)); auto payload = doc->to(); - payload["idToken"]["idToken"] = idTokenCpy.get(); - payload["idToken"]["type"] = idTokenCpy.getTypeCstr(); + payload["idToken"]["idToken"] = idToken.get(); + payload["idToken"]["type"] = idToken.getTypeCstr(); return doc; - }, [this, idTokenCpy] (JsonObject response) { - if (!strcmp(response["idTokenInfo"]["status"], "Accepted")) { - authorization = idTokenCpy; + }, [this, idToken] (JsonObject response) { + if (!strcmp(response["idTokenInfo"]["status"] | "_Undefined", "Accepted")) { + authorization = idToken; if (transaction) { transaction->idTokenTransmitted = false; } - MO_DBG_DEBUG("confirmed auth: %s", idTokenCpy.get()); + MO_DBG_DEBUG("confirmed auth: %s", idToken.get()); } })); context.initiateRequest(std::move(authorize)); diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp index d615b48c..b739f672 100644 --- a/tests/Transactions.cpp +++ b/tests/Transactions.cpp @@ -33,6 +33,17 @@ TEST_CASE( "Transactions" ) { mocpp_set_timer(custom_timer_cb); + getOcppContext()->getOperationRegistry().registerOperation("Authorize", [] () { + return new Ocpp16::CustomOperation("Authorize", + [] (JsonObject) {}, //ignore req + [] () { + //create conf + auto doc = std::unique_ptr(new DynamicJsonDocument(2 * JSON_OBJECT_SIZE(1))); + auto payload = doc->to(); + payload["idTokenInfo"]["status"] = "Accepted"; + return doc; + });}); + loop(); SECTION("Basic transaction") { From b7bb2646c1b3ddfb20150871377a20760d168985 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 27 Feb 2024 21:18:55 +0100 Subject: [PATCH 19/23] clean up OCPP 2.0.1 integration --- .../Model/ConnectorBase/ChargePointStatus.h | 16 +++---- .../Model/ConnectorBase/Connector.cpp | 22 ++++----- src/MicroOcpp/Operations/BootNotification.cpp | 11 ++--- .../Operations/StatusNotification.cpp | 45 +++++++------------ src/MicroOcpp/Operations/StatusNotification.h | 24 +++++----- src/MicroOcpp/Version.h | 3 -- 6 files changed, 48 insertions(+), 73 deletions(-) diff --git a/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h b/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h index 1fa02c00..4a603577 100644 --- a/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h +++ b/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h @@ -5,6 +5,8 @@ #ifndef OCPP_EVSE_STATE #define OCPP_EVSE_STATE +#include + namespace MicroOcpp { enum class ChargePointStatus { @@ -17,22 +19,14 @@ enum class ChargePointStatus { Reserved, Unavailable, Faulted, - NOT_SET //internal value for "undefined" -}; -namespace Ocpp201 { - -enum class ConnectorStatus { - Available, +#if MO_ENABLE_V201 Occupied, - Reserved, - Unavailable, - Faulted, +#endif + NOT_SET //internal value for "undefined" }; -} //end namespace Ocpp201 - } //end namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp index abebd491..4f82c90c 100644 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp +++ b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -365,7 +366,7 @@ void Connector::loop() { auto status = getStatus(); - if (model.getVersion().v16()) { + if (model.getVersion().major == 1) { //OCPP 1.6: use StatusNotification to send error codes for (auto i = std::min(errorDataInputs.size(), trackErrorDataInputs.size()); i >= 1; i--) { auto index = i - 1; @@ -387,16 +388,18 @@ void Connector::loop() { } } - if (model.getVersion().v2()) { - //OCPP 2.0.1: use Preparing as alias for the Occupied state +#if MO_ENABLE_V201 + if (model.getVersion().major == 2) { + //OCPP 2.0.1: map v1.6 status onto v2.0.1 if (status == ChargePointStatus::Preparing || status == ChargePointStatus::Charging || status == ChargePointStatus::SuspendedEV || status == ChargePointStatus::SuspendedEVSE || status == ChargePointStatus::Finishing) { - status = ChargePointStatus::Preparing; + status = ChargePointStatus::Occupied; } } +#endif if (status != currentStatus) { currentStatus = status; @@ -414,16 +417,9 @@ void Connector::loop() { auto statusNotification = #if MO_ENABLE_V201 - model.getVersion().v2() ? + model.getVersion().major == 2 ? makeRequest( - new Ocpp201::StatusNotification(connectorId, - reportedStatus == ChargePointStatus::Available ? Ocpp201::ConnectorStatus::Available : - reportedStatus == ChargePointStatus::Preparing ? Ocpp201::ConnectorStatus::Occupied : - reportedStatus == ChargePointStatus::Reserved ? Ocpp201::ConnectorStatus::Reserved : - reportedStatus == ChargePointStatus::Unavailable ? Ocpp201::ConnectorStatus::Unavailable : - reportedStatus == ChargePointStatus::Faulted ? Ocpp201::ConnectorStatus::Faulted : - Ocpp201::ConnectorStatus::NOT_SET, - reportedTimestamp, 1)) : + new Ocpp201::StatusNotification(connectorId, reportedStatus, reportedTimestamp)) : #endif //MO_ENABLE_V201 makeRequest( new Ocpp16::StatusNotification(connectorId, reportedStatus, reportedTimestamp, getErrorCode())); diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index 200a8124..e0236b0a 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -22,16 +23,16 @@ const char* BootNotification::getOperationType(){ std::unique_ptr BootNotification::createReq() { if (credentials) { - std::unique_ptr doc; +#if MO_ENABLE_V201 if (model.getVersion().major == 2) { - doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2) + credentials->memoryUsage())); + std::unique_ptr doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(2) + credentials->memoryUsage())); JsonObject payload = doc->to(); payload["reason"] = "PowerUp"; payload["chargingStation"] = *credentials; - } else { - doc = std::unique_ptr(new DynamicJsonDocument(*credentials)); + return doc; } - return doc; +#endif + return std::unique_ptr(new DynamicJsonDocument(*credentials)); } else { MO_DBG_ERR("payload undefined"); return createEmptyDocument(); diff --git a/src/MicroOcpp/Operations/StatusNotification.cpp b/src/MicroOcpp/Operations/StatusNotification.cpp index 76d041d7..40dab1aa 100644 --- a/src/MicroOcpp/Operations/StatusNotification.cpp +++ b/src/MicroOcpp/Operations/StatusNotification.cpp @@ -9,7 +9,6 @@ #include namespace MicroOcpp { -namespace Ocpp16 { //helper function const char *cstrFromOcppEveState(ChargePointStatus state) { @@ -32,6 +31,10 @@ const char *cstrFromOcppEveState(ChargePointStatus state) { return "Unavailable"; case (ChargePointStatus::Faulted): return "Faulted"; +#if MO_ENABLE_V201 + case (ChargePointStatus::Occupied): + return "Occupied"; +#endif default: MO_DBG_ERR("ChargePointStatus not specified"); (void)0; @@ -41,6 +44,8 @@ const char *cstrFromOcppEveState(ChargePointStatus state) { } } +namespace Ocpp16 { + StatusNotification::StatusNotification(int connectorId, ChargePointStatus currentStatus, const Timestamp ×tamp, ErrorData errorData) : connectorId(connectorId), currentStatus(currentStatus), timestamp(timestamp), errorData(errorData) { @@ -108,35 +113,16 @@ std::unique_ptr StatusNotification::createConf(){ return createEmptyDocument(); } -} //end namespace Ocpp16 +} // namespace Ocpp16 +} // namespace MicroOcpp #if MO_ENABLE_V201 +namespace MicroOcpp { namespace Ocpp201 { -const char *cstrFromOcppEveState(ConnectorStatus state) { - switch (state) { - case (ConnectorStatus::Available): - return "Available"; - case (ConnectorStatus::Occupied): - return "Occupied"; - case (ConnectorStatus::Reserved): - return "Reserved"; - case (ConnectorStatus::Unavailable): - return "Unavailable"; - case (ConnectorStatus::Faulted): - return "Faulted"; - default: - MO_DBG_ERR("ConnectorStatus not specified"); - (void)0; - /* fall through */ - case (ConnectorStatus::NOT_SET): - return "NOT_SET"; - } -} - -StatusNotification::StatusNotification(int evseId, ConnectorStatus currentStatus, const Timestamp ×tamp, int connectorId) - : timestamp(timestamp), currentStatus(currentStatus), evseId(evseId), connectorId(connectorId) { +StatusNotification::StatusNotification(EvseId evseId, ChargePointStatus currentStatus, const Timestamp ×tamp) + : evseId(evseId), timestamp(timestamp), currentStatus(currentStatus) { } @@ -152,8 +138,8 @@ std::unique_ptr StatusNotification::createReq() { timestamp.toJsonString(timestamp_cstr, JSONDATE_LENGTH + 1); payload["timestamp"] = timestamp_cstr; payload["connectorStatus"] = cstrFromOcppEveState(currentStatus); - payload["evseId"] = connectorId; - payload["connectorId"] = 1; + payload["evseId"] = evseId.id; + payload["connectorId"] = evseId.connectorId >= 0 ? evseId.connectorId : 1; return doc; } @@ -165,8 +151,7 @@ void StatusNotification::processConf(JsonObject payload) { */ } -} //end namespace Ocpp201 +} // namespace Ocpp201 +} // namespace MicroOcpp #endif //MO_ENABLE_V201 - -} //end namespace MicroOcpp diff --git a/src/MicroOcpp/Operations/StatusNotification.h b/src/MicroOcpp/Operations/StatusNotification.h index 31c638b6..ef0e6a1f 100644 --- a/src/MicroOcpp/Operations/StatusNotification.h +++ b/src/MicroOcpp/Operations/StatusNotification.h @@ -12,6 +12,9 @@ #include namespace MicroOcpp { + +const char *cstrFromOcppEveState(ChargePointStatus state); + namespace Ocpp16 { class StatusNotification : public Operation { @@ -34,22 +37,23 @@ class StatusNotification : public Operation { std::unique_ptr createConf() override; }; -const char *cstrFromOcppEveState(ChargePointStatus state); - -} //end namespace Ocpp16 +} // namespace Ocpp16 +} // namespace MicroOcpp #if MO_ENABLE_V201 +#include + +namespace MicroOcpp { namespace Ocpp201 { class StatusNotification : public Operation { private: + EvseId evseId; Timestamp timestamp; - ConnectorStatus currentStatus = ConnectorStatus::NOT_SET; - int evseId; - int connectorId; + ChargePointStatus currentStatus = ChargePointStatus::NOT_SET; public: - StatusNotification(int evseId, ConnectorStatus currentStatus, const Timestamp ×tamp, int connectorId); + StatusNotification(EvseId evseId, ChargePointStatus currentStatus, const Timestamp ×tamp); const char* getOperationType() override; @@ -58,11 +62,9 @@ class StatusNotification : public Operation { void processConf(JsonObject payload) override; }; -const char *cstrFromOcppEveState(ConnectorStatus state); - -} //end namespace Ocpp201 +} // namespace Ocpp201 +} // namespace MicroOcpp #endif //MO_ENABLE_V201 -} //end namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Version.h b/src/MicroOcpp/Version.h index 6c8807ec..7c8a2905 100644 --- a/src/MicroOcpp/Version.h +++ b/src/MicroOcpp/Version.h @@ -26,9 +26,6 @@ namespace MicroOcpp { struct ProtocolVersion { const int major, minor, patch; ProtocolVersion(int major = 1, int minor = 6, int patch = 0) : major(major), minor(minor), patch(patch) { } - - inline bool v16() const {return major == 1 && minor == 6;} - inline bool v2() const {return major == 2;} }; } From c213b7aaa78c075c643db413fb0b62889f816922 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 27 Feb 2024 21:22:24 +0100 Subject: [PATCH 20/23] update ChargerCredentials comment --- src/MicroOcpp.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index 28a63484..8c28206e 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -72,10 +72,8 @@ struct ChargerCredentials { const char *imsi = nullptr); /* - * OCPP 2.0.1 compatible charger credentials. - * - * DEPRECATED: This construction method is only temporary for testing v2.0.1. It will be removed again soon and the original constructor will suit both v1.6 and v2.0.1 - */ + * OCPP 2.0.1 compatible charger credentials. Use this if initializing the library with ProtocolVersion(2,0,1) + */ static ChargerCredentials v201( const char *chargePointModel = "Demo Charger", const char *chargePointVendor = "My Company Ltd.", From 62fd5e95c0fe37f90f2a712b0548f11ca91cc798 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:12:13 +0100 Subject: [PATCH 21/23] fixes after merge --- CMakeLists.txt | 2 -- src/MicroOcpp.cpp | 2 +- src/MicroOcpp_c.cpp | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16cda8de..f617af66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,7 +180,6 @@ target_compile_definitions(mo_unit_tests PUBLIC MO_FILENAME_PREFIX="./mo_store/" MO_LocalAuthListMaxLength=8 MO_SendLocalListMaxLength=4 - MO_ENABLE_V201=1 #MO_ENABLE_MBEDTLS=1 ) @@ -188,7 +187,6 @@ target_compile_options(mo_unit_tests PUBLIC -O0 -g --coverage - -Wall ) target_link_options(mo_unit_tests PUBLIC diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index a1156266..c12ac5b6 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -234,7 +234,7 @@ ChargerCredentials ChargerCredentials::v201(const char *cpModel, const char *cpV return res; } -void mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr fs, bool autoRecover, std::unique_ptr certStore) { +void mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr fs, bool autoRecover, MicroOcpp::ProtocolVersion version, std::unique_ptr certStore) { if (context) { MO_DBG_WARN("already initialized. To reinit, call mocpp_deinitialize() before"); return; diff --git a/src/MicroOcpp_c.cpp b/src/MicroOcpp_c.cpp index 50475f5b..b04fc9f5 100644 --- a/src/MicroOcpp_c.cpp +++ b/src/MicroOcpp_c.cpp @@ -30,7 +30,7 @@ void ocpp_initialize_full(OCPP_Connection *conn, const char *bootNotificationCre certsCwrapper = MicroOcpp::makeCertificateStoreCwrapper(certs); } - mocpp_initialize(*ocppSocket, bootNotificationCredentials, MicroOcpp::makeDefaultFilesystemAdapter(adaptFsopt), autoRecover, std::move(certsCwrapper)); + mocpp_initialize(*ocppSocket, bootNotificationCredentials, MicroOcpp::makeDefaultFilesystemAdapter(adaptFsopt), autoRecover, MicroOcpp::ProtocolVersion(1,6),std::move(certsCwrapper)); } void ocpp_deinitialize() { From cf998cee7aa0e5f414aa235f616b59380ecf6762 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:18:24 +0100 Subject: [PATCH 22/23] update copyright --- src/MicroOcpp/Model/Certificates/Certificate.cpp | 4 ++++ src/MicroOcpp/Model/Certificates/Certificate.h | 4 ++++ src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp | 4 ++++ src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h | 4 ++++ src/MicroOcpp/Model/Certificates/CertificateService.cpp | 4 ++++ src/MicroOcpp/Model/Certificates/CertificateService.h | 4 ++++ src/MicroOcpp/Model/Certificates/Certificate_c.cpp | 4 ++++ src/MicroOcpp/Model/Certificates/Certificate_c.h | 4 ++++ src/MicroOcpp/Model/Transactions/Transaction.h | 2 +- src/MicroOcpp/Operations/DeleteCertificate.cpp | 4 ++++ src/MicroOcpp/Operations/DeleteCertificate.h | 4 ++++ src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp | 4 ++++ src/MicroOcpp/Operations/GetInstalledCertificateIds.h | 4 ++++ src/MicroOcpp/Operations/InstallCertificate.cpp | 4 ++++ src/MicroOcpp/Operations/InstallCertificate.h | 4 ++++ tests/Certificates.cpp | 4 ++++ tests/Transactions.cpp | 4 ++++ tests/Variables.cpp | 4 ++++ 18 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/MicroOcpp/Model/Certificates/Certificate.cpp b/src/MicroOcpp/Model/Certificates/Certificate.cpp index 3caa9dd6..a61a88cb 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate.cpp +++ b/src/MicroOcpp/Model/Certificates/Certificate.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #include diff --git a/src/MicroOcpp/Model/Certificates/Certificate.h b/src/MicroOcpp/Model/Certificates/Certificate.h index 6dcf0cc6..137e3658 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate.h +++ b/src/MicroOcpp/Model/Certificates/Certificate.h @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #ifndef MO_CERTIFICATE_H #define MO_CERTIFICATE_H diff --git a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp index a20cb0f4..4bcce8e2 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp +++ b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #if MO_ENABLE_MBEDTLS diff --git a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h index f06b4c2c..1ecd52ee 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h +++ b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #ifndef MO_CERTIFICATE_MBEDTLS_H #define MO_CERTIFICATE_MBEDTLS_H diff --git a/src/MicroOcpp/Model/Certificates/CertificateService.cpp b/src/MicroOcpp/Model/Certificates/CertificateService.cpp index 57f502f7..37ef7c9c 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateService.cpp +++ b/src/MicroOcpp/Model/Certificates/CertificateService.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #include #include diff --git a/src/MicroOcpp/Model/Certificates/CertificateService.h b/src/MicroOcpp/Model/Certificates/CertificateService.h index ce9b1e64..7a45eca9 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateService.h +++ b/src/MicroOcpp/Model/Certificates/CertificateService.h @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + /* * Functional Block M: ISO 15118 Certificate Management * diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp index 285d0078..46d0875a 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #include diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.h b/src/MicroOcpp/Model/Certificates/Certificate_c.h index cbc256bf..fb9833fe 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.h +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.h @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #ifndef MO_CERTIFICATE_C_H #define MO_CERTIFICATE_C_H diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index c85287f5..cfc8a6f3 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // MIT License #ifndef TRANSACTION_H diff --git a/src/MicroOcpp/Operations/DeleteCertificate.cpp b/src/MicroOcpp/Operations/DeleteCertificate.cpp index 27a76d10..9af4ad04 100644 --- a/src/MicroOcpp/Operations/DeleteCertificate.cpp +++ b/src/MicroOcpp/Operations/DeleteCertificate.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #include #include diff --git a/src/MicroOcpp/Operations/DeleteCertificate.h b/src/MicroOcpp/Operations/DeleteCertificate.h index e0b3f66f..2c09a363 100644 --- a/src/MicroOcpp/Operations/DeleteCertificate.h +++ b/src/MicroOcpp/Operations/DeleteCertificate.h @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #ifndef MO_DELETECERTIFICATE_H #define MO_DELETECERTIFICATE_H diff --git a/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp index fae8e150..07d385ab 100644 --- a/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp +++ b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #include #include diff --git a/src/MicroOcpp/Operations/GetInstalledCertificateIds.h b/src/MicroOcpp/Operations/GetInstalledCertificateIds.h index 58c34d87..dae66c31 100644 --- a/src/MicroOcpp/Operations/GetInstalledCertificateIds.h +++ b/src/MicroOcpp/Operations/GetInstalledCertificateIds.h @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #ifndef MO_GETINSTALLEDCERTIFICATEIDS_H #define MO_GETINSTALLEDCERTIFICATEIDS_H diff --git a/src/MicroOcpp/Operations/InstallCertificate.cpp b/src/MicroOcpp/Operations/InstallCertificate.cpp index 69be0d4d..ed2e6c1f 100644 --- a/src/MicroOcpp/Operations/InstallCertificate.cpp +++ b/src/MicroOcpp/Operations/InstallCertificate.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #include #include diff --git a/src/MicroOcpp/Operations/InstallCertificate.h b/src/MicroOcpp/Operations/InstallCertificate.h index 24fb725e..e6bbbcc6 100644 --- a/src/MicroOcpp/Operations/InstallCertificate.h +++ b/src/MicroOcpp/Operations/InstallCertificate.h @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #ifndef MO_INSTALLCERTIFICATE_H #define MO_INSTALLCERTIFICATE_H diff --git a/tests/Certificates.cpp b/tests/Certificates.cpp index 01535e67..7266e5b1 100644 --- a/tests/Certificates.cpp +++ b/tests/Certificates.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #include #include "./catch2/catch.hpp" diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp index b739f672..edc0456c 100644 --- a/tests/Transactions.cpp +++ b/tests/Transactions.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #if MO_ENABLE_V201 diff --git a/tests/Variables.cpp b/tests/Variables.cpp index 24809643..156840ea 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -1,3 +1,7 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + #include #if MO_ENABLE_V201 From 5ac9e31142146d875d1bb7b6641b2df5770f5b8e Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:26:22 +0100 Subject: [PATCH 23/23] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d64aca3..e8e89c4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ - Operation GetInstalledCertificateIds, UC M03 ([#262](https://github.com/matth-x/MicroOcpp/pull/262)) - Operation DeleteCertificate, UC M04 ([#262](https://github.com/matth-x/MicroOcpp/pull/262)) - Operation InstallCertificate, UC M05 ([#262](https://github.com/matth-x/MicroOcpp/pull/262)) +- `ProtocolVersion` selects v1.6 or v2.0.1 ([#247](https://github.com/matth-x/MicroOcpp/pull/247)) +- Build flag `MO_ENABLE_V201` set to 1 enables OCPP 2.0.1 features ([#247](https://github.com/matth-x/MicroOcpp/pull/247)) + - Variables (non-persistent), UCs B05 - B06 ([#247](https://github.com/matth-x/MicroOcpp/pull/247)) + - Transactions (preview only), UCs E01 - E12 ([#247](https://github.com/matth-x/MicroOcpp/pull/247)) + - StatusNotification compatibility ([#247](https://github.com/matth-x/MicroOcpp/pull/247)) ## [1.1.0] - 2024-02-27