From c381f888e798d853f3afb828d2b206fd0d07c00d Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 24 Feb 2025 23:10:50 +0100 Subject: [PATCH 01/50] simplify Context and Model (WIP) --- CMakeLists.txt | 5 +- src/MicroOcpp.cpp | 10 +- src/MicroOcpp/Core/Connection.h | 3 +- src/MicroOcpp/Core/Context.cpp | 122 +++++- src/MicroOcpp/Core/Context.h | 80 +++- src/MicroOcpp/Core/RequestQueue.h | 4 + src/MicroOcpp/Core/Time.h | 16 +- ...nectorsCommon.cpp => ConnectorService.cpp} | 8 +- ...{ConnectorsCommon.h => ConnectorService.h} | 4 +- src/MicroOcpp/Model/ConnectorBase/EvseId.h | 9 - .../Model/Metering/MeteringConnector.cpp | 304 -------------- .../Model/Metering/MeteringConnector.h | 95 ----- .../Model/Metering/MeteringService.cpp | 385 ++++++++++++++---- .../Model/Metering/MeteringService.h | 84 +++- src/MicroOcpp/Model/Model.cpp | 328 +++++++++------ src/MicroOcpp/Model/Model.h | 148 ++++--- .../SmartCharging/SmartChargingService.cpp | 28 +- .../SmartCharging/SmartChargingService.h | 12 +- .../Model/Transactions/Transaction.h | 6 +- .../Model/Transactions/TransactionStore.cpp | 58 +-- .../Model/Transactions/TransactionStore.h | 26 +- src/MicroOcpp/Version.h | 34 +- tests/Metering.cpp | 3 +- .../benchmarks/scripts/eval_firmware_size.py | 6 +- 24 files changed, 898 insertions(+), 880 deletions(-) rename src/MicroOcpp/Model/ConnectorBase/{ConnectorsCommon.cpp => ConnectorService.cpp} (95%) rename src/MicroOcpp/Model/ConnectorBase/{ConnectorsCommon.h => ConnectorService.h} (78%) delete mode 100644 src/MicroOcpp/Model/Metering/MeteringConnector.cpp delete mode 100644 src/MicroOcpp/Model/Metering/MeteringConnector.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 963c1d8b..86ba9ba4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # matth-x/MicroOcpp -# Copyright Matthias Akstaller 2019 - 2024 +# Copyright Matthias Akstaller 2019 - 2025 # MIT License cmake_minimum_required(VERSION 3.15) @@ -77,12 +77,11 @@ set(MO_SRC src/MicroOcpp/Model/Certificates/Certificate_c.cpp src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp src/MicroOcpp/Model/Certificates/CertificateService.cpp - src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp + src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp src/MicroOcpp/Model/ConnectorBase/Connector.cpp src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp - src/MicroOcpp/Model/Metering/MeteringConnector.cpp src/MicroOcpp/Model/Metering/MeteringService.cpp src/MicroOcpp/Model/Metering/MeterStore.cpp src/MicroOcpp/Model/Metering/MeterValue.cpp diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 5dbd3714..ba356bf5 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -48,10 +48,6 @@ Connection *connection {nullptr}; Context *context {nullptr}; std::shared_ptr filesystem; -#ifndef MO_NUMCONNECTORS -#define MO_NUMCONNECTORS 2 -#endif - #define OCPP_ID_OF_CP 0 #define OCPP_ID_OF_CONNECTOR 1 @@ -300,8 +296,8 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden { model.setTransactionStore(std::unique_ptr( new TransactionStore(MO_NUMCONNECTORS, filesystem))); - model.setConnectorsCommon(std::unique_ptr( - new ConnectorsCommon(*context, MO_NUMCONNECTORS, filesystem))); + model.setConnectorService(std::unique_ptr( + new ConnectorService(*context, MO_NUMCONNECTORS, filesystem))); auto connectors = makeVector>("v16.ConnectorBase.Connector"); for (unsigned int connectorId = 0; connectorId < MO_NUMCONNECTORS; connectorId++) { connectors.emplace_back(new Connector(*context, filesystem, connectorId)); diff --git a/src/MicroOcpp/Core/Connection.h b/src/MicroOcpp/Core/Connection.h index 2c2f7d5c..caef2c6e 100644 --- a/src/MicroOcpp/Core/Connection.h +++ b/src/MicroOcpp/Core/Connection.h @@ -40,10 +40,11 @@ class Connection { virtual bool sendTXT(const char *msg, size_t length) = 0; /* + * Pass incoming data from the server to this function. The OCPP library will consume it * The OCPP library calls this function once during initialization. It passes a callback function to * the socket. The socket should forward any incoming payload from the OCPP server to the receiveTXT callback */ - virtual void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) = 0; + bool recvTXT(const char *msg, size_t len) = 0; /* * Returns the timestamp of the last incoming message. Use mocpp_tick_ms() for creating the correct timestamp diff --git a/src/MicroOcpp/Core/Context.cpp b/src/MicroOcpp/Core/Context.cpp index 1ab65fcb..25c2032b 100644 --- a/src/MicroOcpp/Core/Context.cpp +++ b/src/MicroOcpp/Core/Context.cpp @@ -3,37 +3,135 @@ // MIT License #include -#include #include -#include +#include +#include #include using namespace MicroOcpp; -Context::Context(Connection& connection, std::shared_ptr filesystem, uint16_t bootNr, ProtocolVersion version) - : MemoryManaged("Context"), connection(connection), model{version, bootNr}, reqQueue{connection, operationRegistry} { +Context::Context() : MemoryManaged("Context") { } Context::~Context() { + if (filesystem && isFilesystemOwner) { + delete filesystem; + filesystem = nullptr; + isFilesystemOwner = false; + } + + if (connection && isConnectionOwner) { + delete connection; + connection = nullptr; + isConnectionOwner = false; + } + if (ftpClient && isFtpClientOwner) { + delete ftpClient; + ftpClient = nullptr; + isFtpClientOwner = false; + } +} + +void Context::setDebugCb(void (*debugCb)(const char *msg)) { + this->debugCb = debugCb; +} + +void Context::setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)) { + this->debugCb2 = debugCb2; +} + +void Context::setTicksCb(unsigned long (*ticksCb)()) { + this->ticksCb = ticksCb; +} + +void Context::setFilesystemAdapter(FilesystemAdapter *filesystem) { + if (this->filesystem && isFilesystemOwner) { + delete this->filesystem; + this->filesystem = nullptr; + isFilesystemOwner = false; + } + this->filesystem = filesystem; } +FilesystemAdapter *Context::getFilesystemAdapter() { + return filesystem; +} + +void Context::setConnection(Connection *connection) { + if (connection && isConnectionOwner) { + delete connection; + connection = nullptr; + isConnectionOwner = false; + } + this->connection = connection; +} + +Connection *Context::getConnection() { + return connection; +} + +void Context::setFtpClient(FtpClient *ftpClient) { + if (ftpClient && isFtpClientOwner) { + delete ftpClient; + ftpClient = nullptr; + isFtpClientOwner = false; + } + ftpClient = ftpClient; +} + +FtpClient *Context::getFtpClient() { + return ftpClient; +} + +bool Context::setup(int protocolVersion) { + +} + + void Context::loop(); + + Model& getModel(); + + OperationRegistry& getOperationRegistry(); + + RequestQueue& getRequestQueue(); + + const ProtocolVersion& getVersion(); +}; + +} //end namespace MicroOcpp + +#endif // __cplusplus + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct mo_context; +typedef struct mo_context mo_context; + +mo_context *mo_context_make(); + +void mo_context_free(mo_context *ctx); + +void mo_context_dbg_cb_set(mo_context *ctx, void (*debug_cb)(const char *msg)); + +void mo_context_dbg_cb2_set(mo_context *ctx, void (*debug_cb)(int lvl, const char *fn, int line, const char *msg)); + +void mo_context_ticks_cb_set(unsigned long (*ticksCb)()); + +bool mo_context_setup(mo_context *ctx, int protocol_version); + +void mo_context_loop(mo_context *ctx); + void Context::loop() { connection.loop(); reqQueue.loop(); model.loop(); } -void Context::initiateRequest(std::unique_ptr op) { - if (!op) { - MO_DBG_ERR("invalid arg"); - return; - } - reqQueue.sendRequest(std::move(op)); -} - Model& Context::getModel() { return model; } diff --git a/src/MicroOcpp/Core/Context.h b/src/MicroOcpp/Core/Context.h index 2df691d1..e49615a5 100644 --- a/src/MicroOcpp/Core/Context.h +++ b/src/MicroOcpp/Core/Context.h @@ -1,55 +1,99 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_CONTEXT_H #define MO_CONTEXT_H -#include - +#include #include #include #include -#include -#include #include +#ifdef __cplusplus + namespace MicroOcpp { -class Connection; class FilesystemAdapter; +class Connection; +class FtpClient; class Context : public MemoryManaged { private: - Connection& connection; - OperationRegistry operationRegistry; + void (*debugCb)(const char *msg) = nullptr; + void (*debugCb2)(int lvl, const char *fn, int line, const char *msg) = nullptr; + + unsigned long (*ticksCb)() = nullptr; + + FilesystemAdapter *filesystem = nullptr; + Connection *connection = nullptr; + FtpClient *ftpClient = nullptr; + Model model; + OperationRegistry operationRegistry; RequestQueue reqQueue; - std::unique_ptr ftpClient; + bool isFilesystemOwner = false; + bool isConnectionOwner = false; + bool isFtpClientOwner = false; public: - Context(Connection& connection, std::shared_ptr filesystem, uint16_t bootNr, ProtocolVersion version); + Context(); ~Context(); - void loop(); + void setDebugCb(void (*debugCb)(const char *msg)); + void setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)); + + void setTicksCb(unsigned long (*ticksCb)()); - void initiateRequest(std::unique_ptr op); + void setFilesystemAdapter(FilesystemAdapter *filesystem); + FilesystemAdapter *getFilesystemAdapter(); + + void setConnection(Connection *connection); + Connection *getConnection(); + + void setFtpClient(FtpClient *ftpClient); + FtpClient *getFtpClient(); Model& getModel(); - OperationRegistry& getOperationRegistry(); + bool setup(int ocppVersion); - const ProtocolVersion& getVersion(); + void loop(); - Connection& getConnection(); + OperationRegistry& getOperationRegistry(); RequestQueue& getRequestQueue(); - - void setFtpClient(std::unique_ptr ftpClient); - FtpClient *getFtpClient(); }; } //end namespace MicroOcpp +#endif // __cplusplus + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct mo_context; +typedef struct mo_context mo_context; + +mo_context *mo_context_make(); + +void mo_context_free(mo_context *ctx); + +void mo_context_dbg_cb_set(mo_context *ctx, void (*debug_cb)(const char *msg)); + +void mo_context_dbg_cb2_set(mo_context *ctx, void (*debug_cb)(int lvl, const char *fn, int line, const char *msg)); + +void mo_context_ticks_cb_set(unsigned long (*ticksCb)()); + +bool mo_context_setup(mo_context *ctx, int ocpp_version); + +void mo_context_loop(mo_context *ctx); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + #endif diff --git a/src/MicroOcpp/Core/RequestQueue.h b/src/MicroOcpp/Core/RequestQueue.h index 0cdfe7bc..1dcb1b9e 100644 --- a/src/MicroOcpp/Core/RequestQueue.h +++ b/src/MicroOcpp/Core/RequestQueue.h @@ -83,6 +83,10 @@ class RequestQueue : public MemoryManaged { void sendRequest(std::unique_ptr request); //send an OCPP operation request to the server; adds request to default queue void sendRequestPreBoot(std::unique_ptr request); //send an OCPP operation request to the server; adds request to preBootQueue + void handleRequest(const char *operationType, Operation* (*createOperationCb)(const char *operationType, void* user_data), void *user_data = nullptr); + void setOnRequest(const char *operationType, void (*onRequest)(const char *payloadJson, size_t len)); + void setOnConfirmation(const char *operationType, void (*onConfirmation)(const char *payloadJson, size_t len)); + void addSendQueue(RequestEmitter* sendQueue); void setPreBootSendQueue(VolatileRequestQueue *preBootQueue); diff --git a/src/MicroOcpp/Core/Time.h b/src/MicroOcpp/Core/Time.h index 9a82753b..56dc9a8f 100644 --- a/src/MicroOcpp/Core/Time.h +++ b/src/MicroOcpp/Core/Time.h @@ -26,18 +26,10 @@ namespace MicroOcpp { class Timestamp : public MemoryManaged { private: - /* - * Internal representation of the current time. The initial values correspond to UNIX-time 0. January - * corresponds to month 0 and the first day in the month is day 0. - */ - int16_t year = 1970; - int16_t month = 0; - int16_t day = 0; - int32_t hour = 0; - int32_t minute = 0; - int32_t second = 0; + int32_t time; //Unix time (number of seconds since Jan 1, 1970 UTC, not counting leap seconds) + #if MO_ENABLE_TIMESTAMP_MILLISECONDS - int32_t ms = 0; + int16_t ms = 0; //fractional ms of timestamp. Compound timestamp = time + ms. Range should be [0, 999] #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS public: @@ -83,6 +75,8 @@ class Timestamp : public MemoryManaged { int operator-(const Timestamp &rhs) const; + operator int32_t() const; + friend Timestamp operator+(const Timestamp &lhs, int secs); friend Timestamp operator-(const Timestamp &lhs, int secs); diff --git a/src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp b/src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp similarity index 95% rename from src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp rename to src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp index cfc32c82..d7767015 100644 --- a/src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp +++ b/src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp @@ -2,7 +2,7 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include +#include #include #include #include @@ -27,8 +27,8 @@ using namespace MicroOcpp; -ConnectorsCommon::ConnectorsCommon(Context& context, unsigned int numConn, std::shared_ptr filesystem) : - MemoryManaged("v16.ConnectorBase.ConnectorsCommon"), context(context) { +ConnectorService::ConnectorService(Context& context, unsigned int numConn, std::shared_ptr filesystem) : + MemoryManaged("v16.ConnectorBase.ConnectorService"), context(context) { declareConfiguration("NumberOfConnectors", numConn >= 1 ? numConn - 1 : 0, CONFIGURATION_VOLATILE, true); @@ -87,6 +87,6 @@ ConnectorsCommon::ConnectorsCommon(Context& context, unsigned int numConn, std:: return new Ocpp16::StatusNotification(-1, ChargePointStatus_UNDEFINED, Timestamp());}); } -void ConnectorsCommon::loop() { +void ConnectorService::loop() { //do nothing } diff --git a/src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.h b/src/MicroOcpp/Model/ConnectorBase/ConnectorService.h similarity index 78% rename from src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.h rename to src/MicroOcpp/Model/ConnectorBase/ConnectorService.h index 4bc9f78a..c63a67db 100644 --- a/src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.h +++ b/src/MicroOcpp/Model/ConnectorBase/ConnectorService.h @@ -12,11 +12,11 @@ namespace MicroOcpp { class Context; -class ConnectorsCommon : public MemoryManaged { +class ConnectorService : public MemoryManaged { private: Context& context; public: - ConnectorsCommon(Context& context, unsigned int numConnectors, std::shared_ptr filesystem); + ConnectorService(Context& context, unsigned int numConnectors, std::shared_ptr filesystem); void loop(); }; diff --git a/src/MicroOcpp/Model/ConnectorBase/EvseId.h b/src/MicroOcpp/Model/ConnectorBase/EvseId.h index 6ca5295a..d2a45f03 100644 --- a/src/MicroOcpp/Model/ConnectorBase/EvseId.h +++ b/src/MicroOcpp/Model/ConnectorBase/EvseId.h @@ -9,15 +9,6 @@ #if MO_ENABLE_V201 -// number of EVSE IDs (including 0). Defaults to MO_NUMCONNECTORS if defined, otherwise to 2 -#ifndef MO_NUM_EVSEID -#if defined(MO_NUMCONNECTORS) -#define MO_NUM_EVSEID MO_NUMCONNECTORS -#else -#define MO_NUM_EVSEID 2 -#endif -#endif // MO_NUM_EVSEID - namespace MicroOcpp { // EVSEType (2.23) diff --git a/src/MicroOcpp/Model/Metering/MeteringConnector.cpp b/src/MicroOcpp/Model/Metering/MeteringConnector.cpp deleted file mode 100644 index 378232a5..00000000 --- a/src/MicroOcpp/Model/Metering/MeteringConnector.cpp +++ /dev/null @@ -1,304 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; - -MeteringConnector::MeteringConnector(Context& context, int connectorId, MeterStore& meterStore) - : MemoryManaged("v16.Metering.MeteringConnector"), context(context), model(context.getModel()), connectorId{connectorId}, meterStore(meterStore), meterData(makeVector>(getMemoryTag())), samplers(makeVector>(getMemoryTag())) { - - context.getRequestQueue().addSendQueue(this); - - auto meterValuesSampledDataString = declareConfiguration("MeterValuesSampledData", ""); - declareConfiguration("MeterValuesSampledDataMaxLength", 8, CONFIGURATION_VOLATILE, true); - meterValueSampleIntervalInt = declareConfiguration("MeterValueSampleInterval", 60); - registerConfigurationValidator("MeterValueSampleInterval", VALIDATE_UNSIGNED_INT); - - auto stopTxnSampledDataString = declareConfiguration("StopTxnSampledData", ""); - declareConfiguration("StopTxnSampledDataMaxLength", 8, CONFIGURATION_VOLATILE, true); - - auto meterValuesAlignedDataString = declareConfiguration("MeterValuesAlignedData", ""); - declareConfiguration("MeterValuesAlignedDataMaxLength", 8, CONFIGURATION_VOLATILE, true); - clockAlignedDataIntervalInt = declareConfiguration("ClockAlignedDataInterval", 0); - registerConfigurationValidator("ClockAlignedDataInterval", VALIDATE_UNSIGNED_INT); - - auto stopTxnAlignedDataString = declareConfiguration("StopTxnAlignedData", ""); - - meterValuesInTxOnlyBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "MeterValuesInTxOnly", true); - stopTxnDataCapturePeriodicBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "StopTxnDataCapturePeriodic", false); - - transactionMessageAttemptsInt = declareConfiguration("TransactionMessageAttempts", 3); - transactionMessageRetryIntervalInt = declareConfiguration("TransactionMessageRetryInterval", 60); - - sampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, meterValuesSampledDataString)); - alignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, meterValuesAlignedDataString)); - stopTxnSampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, stopTxnSampledDataString)); - stopTxnAlignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, stopTxnAlignedDataString)); -} - -void MeteringConnector::loop() { - - bool txBreak = false; - if (model.getConnector(connectorId)) { - auto &curTx = model.getConnector(connectorId)->getTransaction(); - txBreak = (curTx && curTx->isRunning()) != trackTxRunning; - trackTxRunning = (curTx && curTx->isRunning()); - } - - if (txBreak) { - lastSampleTime = mocpp_tick_ms(); - } - - if (model.getConnector(connectorId)) { - if (transaction != model.getConnector(connectorId)->getTransaction()) { - transaction = model.getConnector(connectorId)->getTransaction(); - } - - if (transaction && transaction->isRunning() && !transaction->isSilent()) { - //check during transaction - - if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { - MO_DBG_WARN("reload stopTxnData, %s, for tx-%u-%u", stopTxnData ? "replace" : "first time", connectorId, transaction->getTxNr()); - //reload (e.g. after power cut during transaction) - stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction.get()); - } - } else { - //check outside of transaction - - if (connectorId != 0 && meterValuesInTxOnlyBool->getBool()) { - //don't take any MeterValues outside of transactions on connectorIds other than 0 - return; - } - } - } - - if (clockAlignedDataIntervalInt->getInt() >= 1 && model.getClock().now() >= MIN_TIME) { - - auto& timestampNow = model.getClock().now(); - auto dt = nextAlignedTime - timestampNow; - if (dt < 0 || //normal case: interval elapsed - dt > clockAlignedDataIntervalInt->getInt()) { //special case: clock has been adjusted or first run - - MO_DBG_DEBUG("Clock aligned measurement %ds: %s", dt, - abs(dt) <= 60 ? - "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); - if (abs(dt) <= 60) { //is measurement still "clock-aligned"? - - if (auto alignedMeterValue = alignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock)) { - if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { - MO_DBG_INFO("MeterValue cache full. Drop old MV"); - meterData.erase(meterData.begin()); - } - alignedMeterValue->setOpNr(context.getRequestQueue().getNextOpNr()); - if (transaction) { - alignedMeterValue->setTxNr(transaction->getTxNr()); - } - meterData.push_back(std::move(alignedMeterValue)); - } - - if (stopTxnData) { - auto alignedStopTx = stopTxnAlignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock); - if (alignedStopTx) { - stopTxnData->addTxData(std::move(alignedStopTx)); - } - } - } - - Timestamp midnightBase = Timestamp(2010,0,0,0,0,0); - auto intervall = timestampNow - midnightBase; - intervall %= 3600 * 24; - Timestamp midnight = timestampNow - intervall; - intervall += clockAlignedDataIntervalInt->getInt(); - if (intervall >= 3600 * 24) { - //next measurement is tomorrow; set to precisely 00:00 - nextAlignedTime = midnight; - nextAlignedTime += 3600 * 24; - } else { - intervall /= clockAlignedDataIntervalInt->getInt(); - nextAlignedTime = midnight + (intervall * clockAlignedDataIntervalInt->getInt()); - } - } - } - - if (meterValueSampleIntervalInt->getInt() >= 1) { - //record periodic tx data - - if (mocpp_tick_ms() - lastSampleTime >= (unsigned long) (meterValueSampleIntervalInt->getInt() * 1000)) { - if (auto sampledMeterValue = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic)) { - if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { - MO_DBG_INFO("MeterValue cache full. Drop old MV"); - meterData.erase(meterData.begin()); - } - sampledMeterValue->setOpNr(context.getRequestQueue().getNextOpNr()); - if (transaction) { - sampledMeterValue->setTxNr(transaction->getTxNr()); - } - meterData.push_back(std::move(sampledMeterValue)); - } - - if (stopTxnData && stopTxnDataCapturePeriodicBool->getBool()) { - auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic); - if (sampleStopTx) { - stopTxnData->addTxData(std::move(sampleStopTx)); - } - } - lastSampleTime = mocpp_tick_ms(); - } - } -} - -std::unique_ptr MeteringConnector::takeTriggeredMeterValues() { - - auto sample = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_Trigger); - - if (!sample) { - return nullptr; - } - - std::shared_ptr transaction = nullptr; - if (model.getConnector(connectorId)) { - transaction = model.getConnector(connectorId)->getTransaction(); - } - - return std::unique_ptr(new MeterValues(model, std::move(sample), connectorId, transaction)); -} - -void MeteringConnector::addMeterValueSampler(std::unique_ptr meterValueSampler) { - if (!strcmp(meterValueSampler->getProperties().getMeasurand(), "Energy.Active.Import.Register")) { - energySamplerIndex = samplers.size(); - } - samplers.push_back(std::move(meterValueSampler)); -} - -std::unique_ptr MeteringConnector::readTxEnergyMeter(ReadingContext model) { - if (energySamplerIndex >= 0 && (size_t) energySamplerIndex < samplers.size()) { - return samplers[energySamplerIndex]->takeValue(model); - } else { - MO_DBG_DEBUG("Called readTxEnergyMeter(), but no energySampler or handling strategy set"); - return nullptr; - } -} - -void MeteringConnector::beginTxMeterData(Transaction *transaction) { - if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { - stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); - } - - if (stopTxnData) { - auto sampleTxBegin = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionBegin); - if (sampleTxBegin) { - stopTxnData->addTxData(std::move(sampleTxBegin)); - } - } -} - -std::shared_ptr MeteringConnector::endTxMeterData(Transaction *transaction) { - if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { - stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); - } - - if (stopTxnData) { - auto sampleTxEnd = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionEnd); - if (sampleTxEnd) { - stopTxnData->addTxData(std::move(sampleTxEnd)); - } - } - - return std::move(stopTxnData); -} - -void MeteringConnector::abortTxMeterData() { - stopTxnData.reset(); -} - -std::shared_ptr MeteringConnector::getStopTxMeterData(Transaction *transaction) { - auto txData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); - - if (!txData) { - MO_DBG_ERR("could not create TxData"); - return nullptr; - } - - return txData; -} - -bool MeteringConnector::existsSampler(const char *measurand, size_t len) { - for (size_t i = 0; i < samplers.size(); i++) { - if (strlen(samplers[i]->getProperties().getMeasurand()) == len && - !strncmp(measurand, samplers[i]->getProperties().getMeasurand(), len)) { - return true; - } - } - - return false; -} - -unsigned int MeteringConnector::getFrontRequestOpNr() { - if (!meterDataFront && !meterData.empty()) { - MO_DBG_DEBUG("advance MV front"); - meterDataFront = std::move(meterData.front()); - meterData.erase(meterData.begin()); - } - if (meterDataFront) { - return meterDataFront->getOpNr(); - } - return NoOperation; -} - -std::unique_ptr MeteringConnector::fetchFrontRequest() { - - if (!meterDataFront) { - return nullptr; - } - - if ((int)meterDataFront->getAttemptNr() >= transactionMessageAttemptsInt->getInt()) { - MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard MeterValue"); - meterDataFront.reset(); - return nullptr; - } - - if (mocpp_tick_ms() - meterDataFront->getAttemptTime() < meterDataFront->getAttemptNr() * (unsigned long)(std::max(0, transactionMessageRetryIntervalInt->getInt())) * 1000UL) { - return nullptr; - } - - meterDataFront->advanceAttemptNr(); - meterDataFront->setAttemptTime(mocpp_tick_ms()); - - //fetch tx for meterValue - std::shared_ptr tx; - if (meterDataFront->getTxNr() >= 0) { - tx = model.getTransactionStore()->getTransaction(connectorId, meterDataFront->getTxNr()); - } - - //discard MV if it belongs to silent tx - if (tx && tx->isSilent()) { - MO_DBG_DEBUG("Drop MeterValue belonging to silent tx"); - meterDataFront.reset(); - return nullptr; - } - - auto meterValues = makeRequest(new MeterValues(model, meterDataFront.get(), connectorId, tx)); - meterValues->setOnReceiveConfListener([this] (JsonObject) { - //operation success - MO_DBG_DEBUG("drop MV front"); - meterDataFront.reset(); - }); - - return meterValues; -} diff --git a/src/MicroOcpp/Model/Metering/MeteringConnector.h b/src/MicroOcpp/Model/Metering/MeteringConnector.h deleted file mode 100644 index 75873ff6..00000000 --- a/src/MicroOcpp/Model/Metering/MeteringConnector.h +++ /dev/null @@ -1,95 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_METERING_CONNECTOR_H -#define MO_METERING_CONNECTOR_H - -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifndef MO_METERVALUES_CACHE_MAXSIZE -#define MO_METERVALUES_CACHE_MAXSIZE MO_REQUEST_CACHE_MAXSIZE -#endif - -namespace MicroOcpp { - -class Context; -class Model; -class Operation; -class MeterStore; - -class MeteringConnector : public MemoryManaged, public RequestEmitter { -private: - Context& context; - Model& model; - const int connectorId; - MeterStore& meterStore; - - Vector> meterData; - std::unique_ptr meterDataFront; - std::shared_ptr stopTxnData; - - std::unique_ptr sampledDataBuilder; - std::unique_ptr alignedDataBuilder; - std::unique_ptr stopTxnSampledDataBuilder; - std::unique_ptr stopTxnAlignedDataBuilder; - - std::shared_ptr sampledDataSelectString; - std::shared_ptr alignedDataSelectString; - std::shared_ptr stopTxnSampledDataSelectString; - std::shared_ptr stopTxnAlignedDataSelectString; - - unsigned long lastSampleTime = 0; //0 means not charging right now - Timestamp nextAlignedTime; - std::shared_ptr transaction; - bool trackTxRunning = false; - - Vector> samplers; - int energySamplerIndex {-1}; - - std::shared_ptr meterValueSampleIntervalInt; - - std::shared_ptr clockAlignedDataIntervalInt; - - std::shared_ptr meterValuesInTxOnlyBool; - std::shared_ptr stopTxnDataCapturePeriodicBool; - - std::shared_ptr transactionMessageAttemptsInt; - std::shared_ptr transactionMessageRetryIntervalInt; -public: - MeteringConnector(Context& context, int connectorId, MeterStore& meterStore); - - void loop(); - - void addMeterValueSampler(std::unique_ptr meterValueSampler); - - std::unique_ptr readTxEnergyMeter(ReadingContext model); - - std::unique_ptr takeTriggeredMeterValues(); - - void beginTxMeterData(Transaction *transaction); - - std::shared_ptr endTxMeterData(Transaction *transaction); - - void abortTxMeterData(); - - std::shared_ptr getStopTxMeterData(Transaction *transaction); - - bool existsSampler(const char *measurand, size_t len); - - //RequestEmitter implementation - unsigned int getFrontRequestOpNr() override; - std::unique_ptr fetchFrontRequest() override; - -}; - -} //end namespace MicroOcpp -#endif diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index 73ab4deb..65b78fc3 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -1,9 +1,14 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License +#include +#include + #include #include +#include +#include #include #include #include @@ -12,9 +17,300 @@ #include using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +MeteringServiceEvse::MeteringServiceEvse(Context& context, int connectorId, MeterStore& meterStore) + : MemoryManaged("v16.Metering.MeteringServiceEvse"), context(context), model(context.getModel()), connectorId{connectorId}, meterStore(meterStore), meterData(makeVector>(getMemoryTag())), samplers(makeVector>(getMemoryTag())) { + + context.getRequestQueue().addSendQueue(this); + + auto meterValuesSampledDataString = declareConfiguration("MeterValuesSampledData", ""); + declareConfiguration("MeterValuesSampledDataMaxLength", 8, CONFIGURATION_VOLATILE, true); + meterValueSampleIntervalInt = declareConfiguration("MeterValueSampleInterval", 60); + registerConfigurationValidator("MeterValueSampleInterval", VALIDATE_UNSIGNED_INT); + + auto stopTxnSampledDataString = declareConfiguration("StopTxnSampledData", ""); + declareConfiguration("StopTxnSampledDataMaxLength", 8, CONFIGURATION_VOLATILE, true); + + auto meterValuesAlignedDataString = declareConfiguration("MeterValuesAlignedData", ""); + declareConfiguration("MeterValuesAlignedDataMaxLength", 8, CONFIGURATION_VOLATILE, true); + clockAlignedDataIntervalInt = declareConfiguration("ClockAlignedDataInterval", 0); + registerConfigurationValidator("ClockAlignedDataInterval", VALIDATE_UNSIGNED_INT); + + auto stopTxnAlignedDataString = declareConfiguration("StopTxnAlignedData", ""); + + meterValuesInTxOnlyBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "MeterValuesInTxOnly", true); + stopTxnDataCapturePeriodicBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "StopTxnDataCapturePeriodic", false); + + transactionMessageAttemptsInt = declareConfiguration("TransactionMessageAttempts", 3); + transactionMessageRetryIntervalInt = declareConfiguration("TransactionMessageRetryInterval", 60); + + sampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, meterValuesSampledDataString)); + alignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, meterValuesAlignedDataString)); + stopTxnSampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, stopTxnSampledDataString)); + stopTxnAlignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, stopTxnAlignedDataString)); +} + +void MeteringServiceEvse::loop() { + + bool txBreak = false; + if (model.getConnector(connectorId)) { + auto &curTx = model.getConnector(connectorId)->getTransaction(); + txBreak = (curTx && curTx->isRunning()) != trackTxRunning; + trackTxRunning = (curTx && curTx->isRunning()); + } + + if (txBreak) { + lastSampleTime = mocpp_tick_ms(); + } + + if (model.getConnector(connectorId)) { + if (transaction != model.getConnector(connectorId)->getTransaction()) { + transaction = model.getConnector(connectorId)->getTransaction(); + } + + if (transaction && transaction->isRunning() && !transaction->isSilent()) { + //check during transaction + + if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { + MO_DBG_WARN("reload stopTxnData, %s, for tx-%u-%u", stopTxnData ? "replace" : "first time", connectorId, transaction->getTxNr()); + //reload (e.g. after power cut during transaction) + stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction.get()); + } + } else { + //check outside of transaction + + if (connectorId != 0 && meterValuesInTxOnlyBool->getBool()) { + //don't take any MeterValues outside of transactions on connectorIds other than 0 + return; + } + } + } + + if (clockAlignedDataIntervalInt->getInt() >= 1 && model.getClock().now() >= MIN_TIME) { + + auto& timestampNow = model.getClock().now(); + auto dt = nextAlignedTime - timestampNow; + if (dt < 0 || //normal case: interval elapsed + dt > clockAlignedDataIntervalInt->getInt()) { //special case: clock has been adjusted or first run + + MO_DBG_DEBUG("Clock aligned measurement %ds: %s", dt, + abs(dt) <= 60 ? + "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); + if (abs(dt) <= 60) { //is measurement still "clock-aligned"? + + if (auto alignedMeterValue = alignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock)) { + if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { + MO_DBG_INFO("MeterValue cache full. Drop old MV"); + meterData.erase(meterData.begin()); + } + alignedMeterValue->setOpNr(context.getRequestQueue().getNextOpNr()); + if (transaction) { + alignedMeterValue->setTxNr(transaction->getTxNr()); + } + meterData.push_back(std::move(alignedMeterValue)); + } + + if (stopTxnData) { + auto alignedStopTx = stopTxnAlignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock); + if (alignedStopTx) { + stopTxnData->addTxData(std::move(alignedStopTx)); + } + } + } + + Timestamp midnightBase = Timestamp(2010,0,0,0,0,0); + auto intervall = timestampNow - midnightBase; + intervall %= 3600 * 24; + Timestamp midnight = timestampNow - intervall; + intervall += clockAlignedDataIntervalInt->getInt(); + if (intervall >= 3600 * 24) { + //next measurement is tomorrow; set to precisely 00:00 + nextAlignedTime = midnight; + nextAlignedTime += 3600 * 24; + } else { + intervall /= clockAlignedDataIntervalInt->getInt(); + nextAlignedTime = midnight + (intervall * clockAlignedDataIntervalInt->getInt()); + } + } + } + + if (meterValueSampleIntervalInt->getInt() >= 1) { + //record periodic tx data + + if (mocpp_tick_ms() - lastSampleTime >= (unsigned long) (meterValueSampleIntervalInt->getInt() * 1000)) { + if (auto sampledMeterValue = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic)) { + if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { + MO_DBG_INFO("MeterValue cache full. Drop old MV"); + meterData.erase(meterData.begin()); + } + sampledMeterValue->setOpNr(context.getRequestQueue().getNextOpNr()); + if (transaction) { + sampledMeterValue->setTxNr(transaction->getTxNr()); + } + meterData.push_back(std::move(sampledMeterValue)); + } + + if (stopTxnData && stopTxnDataCapturePeriodicBool->getBool()) { + auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic); + if (sampleStopTx) { + stopTxnData->addTxData(std::move(sampleStopTx)); + } + } + lastSampleTime = mocpp_tick_ms(); + } + } +} + +std::unique_ptr MeteringServiceEvse::takeTriggeredMeterValues() { + + auto sample = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_Trigger); + + if (!sample) { + return nullptr; + } + + std::shared_ptr transaction = nullptr; + if (model.getConnector(connectorId)) { + transaction = model.getConnector(connectorId)->getTransaction(); + } + + auto msg = std::unique_ptr(new MeterValues(model, std::move(sample), connectorId, transaction)); + auto meterValues = makeRequest(std::move(msg)); + meterValues->setTimeout(120000); + return meterValues; +} + +void MeteringServiceEvse::addMeterValueSampler(std::unique_ptr meterValueSampler) { + if (!strcmp(meterValueSampler->getProperties().getMeasurand(), "Energy.Active.Import.Register")) { + energySamplerIndex = samplers.size(); + } + samplers.push_back(std::move(meterValueSampler)); +} + +std::unique_ptr MeteringServiceEvse::readTxEnergyMeter(ReadingContext model) { + if (energySamplerIndex >= 0 && (size_t) energySamplerIndex < samplers.size()) { + return samplers[energySamplerIndex]->takeValue(model); + } else { + MO_DBG_DEBUG("Called readTxEnergyMeter(), but no energySampler or handling strategy set"); + return nullptr; + } +} + +void MeteringServiceEvse::beginTxMeterData(Transaction *transaction) { + if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { + stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); + } + + if (stopTxnData) { + auto sampleTxBegin = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionBegin); + if (sampleTxBegin) { + stopTxnData->addTxData(std::move(sampleTxBegin)); + } + } +} + +std::shared_ptr MeteringServiceEvse::endTxMeterData(Transaction *transaction) { + if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { + stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); + } + + if (stopTxnData) { + auto sampleTxEnd = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionEnd); + if (sampleTxEnd) { + stopTxnData->addTxData(std::move(sampleTxEnd)); + } + } + + return std::move(stopTxnData); +} + +void MeteringServiceEvse::abortTxMeterData() { + stopTxnData.reset(); +} + +std::shared_ptr MeteringServiceEvse::getStopTxMeterData(Transaction *transaction) { + auto txData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); + + if (!txData) { + MO_DBG_ERR("could not create TxData"); + return nullptr; + } + + return txData; +} + +bool MeteringServiceEvse::removeTxMeterData(unsigned int txNr) { + return meterStore.remove(connectorId, txNr); +} + +bool MeteringServiceEvse::existsSampler(const char *measurand, size_t len) { + for (size_t i = 0; i < samplers.size(); i++) { + if (strlen(samplers[i]->getProperties().getMeasurand()) == len && + !strncmp(measurand, samplers[i]->getProperties().getMeasurand(), len)) { + return true; + } + } + + return false; +} + +unsigned int MeteringServiceEvse::getFrontRequestOpNr() { + if (!meterDataFront && !meterData.empty()) { + MO_DBG_DEBUG("advance MV front"); + meterDataFront = std::move(meterData.front()); + meterData.erase(meterData.begin()); + } + if (meterDataFront) { + return meterDataFront->getOpNr(); + } + return NoOperation; +} + +std::unique_ptr MeteringServiceEvse::fetchFrontRequest() { + + if (!meterDataFront) { + return nullptr; + } + + if ((int)meterDataFront->getAttemptNr() >= transactionMessageAttemptsInt->getInt()) { + MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard MeterValue"); + meterDataFront.reset(); + return nullptr; + } + + if (mocpp_tick_ms() - meterDataFront->getAttemptTime() < meterDataFront->getAttemptNr() * (unsigned long)(std::max(0, transactionMessageRetryIntervalInt->getInt())) * 1000UL) { + return nullptr; + } + + meterDataFront->advanceAttemptNr(); + meterDataFront->setAttemptTime(mocpp_tick_ms()); + + //fetch tx for meterValue + std::shared_ptr tx; + if (meterDataFront->getTxNr() >= 0) { + tx = model.getTransactionStore()->getTransaction(connectorId, meterDataFront->getTxNr()); + } + + //discard MV if it belongs to silent tx + if (tx && tx->isSilent()) { + MO_DBG_DEBUG("Drop MeterValue belonging to silent tx"); + meterDataFront.reset(); + return nullptr; + } + + auto meterValues = makeRequest(new MeterValues(model, meterDataFront.get(), connectorId, tx)); + meterValues->setOnReceiveConfListener([this] (JsonObject) { + //operation success + MO_DBG_DEBUG("drop MV front"); + meterDataFront.reset(); + }); + + return meterValues; +} MeteringService::MeteringService(Context& context, int numConn, std::shared_ptr filesystem) - : MemoryManaged("v16.Metering.MeteringService"), context(context), meterStore(filesystem), connectors(makeVector>(getMemoryTag())) { + : MemoryManaged("v16.Metering.MeteringService"), context(context), meterStore(filesystem), connectors(makeVector>(getMemoryTag())) { //set factory defaults for Metering-related config keys declareConfiguration("MeterValuesSampledData", "Energy.Active.Import.Register,Power.Active.Import"); @@ -24,7 +320,7 @@ MeteringService::MeteringService(Context& context, int numConn, std::shared_ptr< connectors.reserve(numConn); for (int i = 0; i < numConn; i++) { - connectors.emplace_back(new MeteringConnector(context, i, meterStore)); + connectors.emplace_back(new MeteringServiceEvse(context, i, meterStore)); } std::function validateSelectString = [this] (const char *csl) { @@ -78,86 +374,3 @@ void MeteringService::loop(){ connectors[i]->loop(); } } - -void MeteringService::addMeterValueSampler(int connectorId, std::unique_ptr meterValueSampler) { - if (connectorId < 0 || connectorId >= (int) connectors.size()) { - MO_DBG_ERR("connectorId is out of bounds"); - return; - } - connectors[connectorId]->addMeterValueSampler(std::move(meterValueSampler)); -} - -std::unique_ptr MeteringService::readTxEnergyMeter(int connectorId, ReadingContext context) { - if (connectorId < 0 || (size_t) connectorId >= connectors.size()) { - MO_DBG_ERR("connectorId is out of bounds"); - return nullptr; - } - return connectors[connectorId]->readTxEnergyMeter(context); -} - -std::unique_ptr MeteringService::takeTriggeredMeterValues(int connectorId) { - if (connectorId < 0 || connectorId >= (int) connectors.size()) { - MO_DBG_ERR("connectorId out of bounds. Ignore"); - return nullptr; - } - - auto msg = connectors[connectorId]->takeTriggeredMeterValues(); - if (msg) { - auto meterValues = makeRequest(std::move(msg)); - meterValues->setTimeout(120000); - return meterValues; - } - MO_DBG_DEBUG("Did not take any samples for connectorId %d", connectorId); - return nullptr; -} - -void MeteringService::beginTxMeterData(Transaction *transaction) { - if (!transaction) { - MO_DBG_ERR("invalid argument"); - return; - } - auto connectorId = transaction->getConnectorId(); - if (connectorId >= connectors.size()) { - MO_DBG_ERR("connectorId is out of bounds"); - return; - } - connectors[connectorId]->beginTxMeterData(transaction); -} - -std::shared_ptr MeteringService::endTxMeterData(Transaction *transaction) { - if (!transaction) { - MO_DBG_ERR("invalid argument"); - return nullptr; - } - auto connectorId = transaction->getConnectorId(); - if (connectorId >= connectors.size()) { - MO_DBG_ERR("connectorId is out of bounds"); - return nullptr; - } - return connectors[connectorId]->endTxMeterData(transaction); -} - -void MeteringService::abortTxMeterData(unsigned int connectorId) { - if (connectorId >= connectors.size()) { - MO_DBG_ERR("connectorId is out of bounds"); - return; - } - connectors[connectorId]->abortTxMeterData(); -} - -std::shared_ptr MeteringService::getStopTxMeterData(Transaction *transaction) { - if (!transaction) { - MO_DBG_ERR("invalid argument"); - return nullptr; - } - auto connectorId = transaction->getConnectorId(); - if (connectorId >= connectors.size()) { - MO_DBG_ERR("connectorId is out of bounds"); - return nullptr; - } - return connectors[connectorId]->getStopTxMeterData(transaction); -} - -bool MeteringService::removeTxMeterData(unsigned int connectorId, unsigned int txNr) { - return meterStore.remove(connectorId, txNr); -} diff --git a/src/MicroOcpp/Model/Metering/MeteringService.h b/src/MicroOcpp/Model/Metering/MeteringService.h index 748a0b4a..50ddc8ef 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.h +++ b/src/MicroOcpp/Model/Metering/MeteringService.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_METERINGSERVICE_H @@ -8,45 +8,99 @@ #include #include -#include #include #include +#include +#include +#include +#include #include namespace MicroOcpp { class Context; +class Model; +class Operation; class Request; class FilesystemAdapter; -class MeteringService : public MemoryManaged { +class MeteringServiceEvse : public MemoryManaged, public RequestEmitter { private: Context& context; - MeterStore meterStore; - - Vector> connectors; + Model& model; + const int connectorId; + MeterStore& meterStore; + + Vector> meterData; + std::unique_ptr meterDataFront; + std::shared_ptr stopTxnData; + + std::unique_ptr sampledDataBuilder; + std::unique_ptr alignedDataBuilder; + std::unique_ptr stopTxnSampledDataBuilder; + std::unique_ptr stopTxnAlignedDataBuilder; + + std::shared_ptr sampledDataSelectString; + std::shared_ptr alignedDataSelectString; + std::shared_ptr stopTxnSampledDataSelectString; + std::shared_ptr stopTxnAlignedDataSelectString; + + unsigned long lastSampleTime = 0; //0 means not charging right now + Timestamp nextAlignedTime; + std::shared_ptr transaction; + bool trackTxRunning = false; + + Vector> samplers; + int energySamplerIndex {-1}; + + std::shared_ptr meterValueSampleIntervalInt; + + std::shared_ptr clockAlignedDataIntervalInt; + + std::shared_ptr meterValuesInTxOnlyBool; + std::shared_ptr stopTxnDataCapturePeriodicBool; + + std::shared_ptr transactionMessageAttemptsInt; + std::shared_ptr transactionMessageRetryIntervalInt; public: - MeteringService(Context& context, int numConnectors, std::shared_ptr filesystem); + MeteringServiceEvse(Context& context, int connectorId, MeterStore& meterStore); void loop(); - void addMeterValueSampler(int connectorId, std::unique_ptr meterValueSampler); + void addMeterValueSampler(std::unique_ptr meterValueSampler); - std::unique_ptr readTxEnergyMeter(int connectorId, ReadingContext reason); + std::unique_ptr readTxEnergyMeter(ReadingContext model); - std::unique_ptr takeTriggeredMeterValues(int connectorId); //snapshot of all meters now + std::unique_ptr takeTriggeredMeterValues(); void beginTxMeterData(Transaction *transaction); - std::shared_ptr endTxMeterData(Transaction *transaction); //use return value to keep data in cache + std::shared_ptr endTxMeterData(Transaction *transaction); + + void abortTxMeterData(); - void abortTxMeterData(unsigned int connectorId); //call this to free resources if txMeterData record is not ended normally. Does not remove files + std::shared_ptr getStopTxMeterData(Transaction *transaction); - std::shared_ptr getStopTxMeterData(Transaction *transaction); //prefer endTxMeterData when possible + bool removeTxMeterData(unsigned int txNr); - bool removeTxMeterData(unsigned int connectorId, unsigned int txNr); + bool existsSampler(const char *measurand, size_t len); - int getNumConnectors() {return connectors.size();} + //RequestEmitter implementation + unsigned int getFrontRequestOpNr() override; + std::unique_ptr fetchFrontRequest() override; + +}; + +class MeteringService : public MemoryManaged { +private: + Context& context; + MeterStore meterStore; +public: + MeteringService(Context& context, int numConnectors, std::shared_ptr filesystem); + + bool setup(); + + void loop(); }; } //end namespace MicroOcpp diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index f2e19c1e..e5b1ae3a 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -34,190 +34,256 @@ Model::Model(ProtocolVersion version, uint16_t bootNr) : MemoryManaged("Model"), Model::~Model() = default; -void Model::loop() { - - if (bootService) { - bootService->loop(); +void Model::setNumEvseId(unsigned int numEvseId) { + if (numEvseId >= MO_NUM_EVSEID) { + MO_DBG_ERR("invalid arg"); + return; } + this->numEvseId = numEvseId; +} - if (capabilitiesUpdated) { - updateSupportedStandardProfiles(); - capabilitiesUpdated = false; - } +unsigned int Model::getNumEvseId() { + return numEvseId; +} - if (!runTasks) { - return; +BootService *Model::getBootService() { + if (!bootService) { + bootService = new BootService(context); } + return bootService; +} - for (auto& connector : connectors) { - connector->loop(); +HeartbeatService *Model::getHeartbeatService() { + if (!heartbeatService) { + heartbeatService = new HeartbeatService(context); } + return heartbeatService; +} - if (chargeControlCommon) - chargeControlCommon->loop(); - - if (smartChargingService) - smartChargingService->loop(); - - if (heartbeatService) - heartbeatService->loop(); +ResetService *Model::getResetService() { + if (!resetService) { + resetService = new ResetService(context); + } + return resetService; +} - if (meteringService) - meteringService->loop(); +ConnectorService *Model::getConnectorService() { + if (!connectorService) { + connectorService = new ConnectorService(context); + } + return connectorService; +} - if (diagnosticsService) - diagnosticsService->loop(); +Connector *Model::getConnector(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("connector with evseId %u does not exist", evseId); + return nullptr; + } - if (firmwareService) - firmwareService->loop(); + if (!connectors) { + connectors = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(Connector*)); + if (connectors) { + memset(connectors, 0, sizeof(*connectors)); + } else { + return nullptr; //OOM + } + } -#if MO_ENABLE_RESERVATION - if (reservationService) - reservationService->loop(); -#endif //MO_ENABLE_RESERVATION + if (!connectors[evseId]) { + connectors[evseId] = new Connector(context); + } + return connectors[evseId]; +} - if (resetService) - resetService->loop(); +TransactionStoreEvse *Model::getTransactionStoreEvse(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("connector with evseId %u does not exist", evseId); + return nullptr; + } -#if MO_ENABLE_V201 - if (availabilityService) - availabilityService->loop(); + if (!transactionStoreEvse) { + transactionStoreEvse = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(TransactionStoreEvse*)); + if (transactionStoreEvse) { + memset(transactionStoreEvse, 0, sizeof(*transactionStoreEvse)); + } else { + return nullptr; //OOM + } + } - if (transactionService) - transactionService->loop(); - - if (resetServiceV201) - resetServiceV201->loop(); -#endif + if (!transactionStoreEvse[evseId]) { + transactionStoreEvse[evseId] = new TransactionStoreEvse(context); + } + return transactionStoreEvse[evseId]; } -void Model::setTransactionStore(std::unique_ptr ts) { - transactionStore = std::move(ts); - capabilitiesUpdated = true; +MeteringService* Model::getMeteringService() { + if (!meteringService) { + meteringService = new MeteringService(context); + } + return meteringService; } -TransactionStore *Model::getTransactionStore() { - return transactionStore.get(); -} +MeteringServiceEvse* Model::getMeteringServiceEvse(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("connector with evseId %u does not exist", evseId); + return nullptr; + } -void Model::setSmartChargingService(std::unique_ptr scs) { - smartChargingService = std::move(scs); - capabilitiesUpdated = true; + if (!meteringServiceEvse) { + meteringServiceEvse = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(MeteringServiceEvse*)); + if (meteringServiceEvse) { + memset(meteringServiceEvse, 0, sizeof(*meteringServiceEvse)); + } else { + return nullptr; //OOM + } + } + + if (!meteringServiceEvse[evseId]) { + meteringServiceEvse[evseId] = new MeteringServiceEvse(context); + } + return meteringServiceEvse[evseId]; } -SmartChargingService* Model::getSmartChargingService() const { - return smartChargingService.get(); +#if MO_ENABLE_FIRMWAREMANAGEMENT +FirmwareService *Model::getFirmwareService() { + if (!firmwareService) { + firmwareService = new FirmwareService(context); + } + return firmwareService; } -void Model::setConnectorsCommon(std::unique_ptr ccs) { - chargeControlCommon = std::move(ccs); - capabilitiesUpdated = true; +DiagnosticsService *Model::getDiagnosticsService() { + if (!diagnosticsService) { + diagnosticsService = new DiagnosticsService(context); + } + return diagnosticsService; } +#endif //MO_ENABLE_FIRMWAREMANAGEMENT -ConnectorsCommon *Model::getConnectorsCommon() { - return chargeControlCommon.get(); +#if MO_ENABLE_LOCAL_AUTH +AuthorizationService *Model::getAuthorizationService() { + if (!authorizationService) { + authorizationService = new AuthorizationService(context); + } + return authorizationService; } +#endif //MO_ENABLE_LOCAL_AUTH -void Model::setConnectors(Vector>&& connectors) { - this->connectors = std::move(connectors); - capabilitiesUpdated = true; +#if MO_ENABLE_RESERVATION +ReservationService *Model::getReservationService() { + if (!reservationService) { + reservationService = new ReservationService(context); + } + return reservationService; } +#endif //MO_ENABLE_RESERVATION -unsigned int Model::getNumConnectors() const { - return connectors.size(); +#if MO_ENABLE_SMARTCHARGING +SmartChargingService* Model::getSmartChargingService() { + if (!smartChargingService) { + smartChargingService = new SmartChargingService(context); + } + return smartChargingService; } -Connector *Model::getConnector(unsigned int connectorId) { - if (connectorId >= connectors.size()) { - MO_DBG_ERR("connector with connectorId %u does not exist", connectorId); +SmartChargingServiceEvse *Model::getSmartChargingServiceEvse(unsigned int evseId) { + if (evseId < 1 || evseId >= numEvseId) { + MO_DBG_ERR("connector with evseId %u does not exist", evseId); return nullptr; } - return connectors[connectorId].get(); -} + if (!smartChargingServiceEvse) { + smartChargingServiceEvse = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(SmartChargingServiceEvse*)); + if (smartChargingServiceEvse) { + memset(smartChargingServiceEvse, 0, sizeof(*smartChargingServiceEvse)); + } else { + return nullptr; //OOM + } + } -void Model::setMeteringSerivce(std::unique_ptr ms) { - meteringService = std::move(ms); - capabilitiesUpdated = true; + if (!smartChargingServiceEvse[evseId]) { + smartChargingServiceEvse[evseId] = new SmartChargingServiceEvse(context); + } + return smartChargingServiceEvse[evseId]; } +#endif //MO_ENABLE_SMARTCHARGING -MeteringService* Model::getMeteringService() const { - return meteringService.get(); +#if MO_ENABLE_CERT_MGMT +CertificateService *Model::getCertificateService() { + if (!certService) { + certService = new CertificateService(context); + } + return certService; } +#endif //MO_ENABLE_CERT_MGMT -void Model::setFirmwareService(std::unique_ptr fws) { - firmwareService = std::move(fws); - capabilitiesUpdated = true; +bool Model::setup() { + if (!getBootService()) { + return false; //OOM + } + getBootService()->setup(); + if (!getHeartbeatService()) { + return false; //OOM + } + getHeartbeatService()->setup(); } -FirmwareService *Model::getFirmwareService() const { - return firmwareService.get(); -} +void Model::loop() { -void Model::setDiagnosticsService(std::unique_ptr ds) { - diagnosticsService = std::move(ds); - capabilitiesUpdated = true; -} + if (bootService) { + bootService->loop(); + } -DiagnosticsService *Model::getDiagnosticsService() const { - return diagnosticsService.get(); -} + if (capabilitiesUpdated) { + updateSupportedStandardProfiles(); + capabilitiesUpdated = false; + } -void Model::setHeartbeatService(std::unique_ptr hs) { - heartbeatService = std::move(hs); - capabilitiesUpdated = true; -} + if (!runTasks) { + return; + } -#if MO_ENABLE_LOCAL_AUTH -void Model::setAuthorizationService(std::unique_ptr as) { - authorizationService = std::move(as); - capabilitiesUpdated = true; -} + for (auto& connector : connectors) { + connector->loop(); + } -AuthorizationService *Model::getAuthorizationService() { - return authorizationService.get(); -} -#endif //MO_ENABLE_LOCAL_AUTH + if (connectorService) + connectorService->loop(); -#if MO_ENABLE_RESERVATION -void Model::setReservationService(std::unique_ptr rs) { - reservationService = std::move(rs); - capabilitiesUpdated = true; -} + if (smartChargingService) + smartChargingService->loop(); -ReservationService *Model::getReservationService() { - return reservationService.get(); -} -#endif //MO_ENABLE_RESERVATION + if (heartbeatService) + heartbeatService->loop(); -void Model::setBootService(std::unique_ptr bs){ - bootService = std::move(bs); - capabilitiesUpdated = true; -} + if (meteringService) + meteringService->loop(); -BootService *Model::getBootService() const { - return bootService.get(); -} + if (diagnosticsService) + diagnosticsService->loop(); -void Model::setResetService(std::unique_ptr rs) { - this->resetService = std::move(rs); - capabilitiesUpdated = true; -} + if (firmwareService) + firmwareService->loop(); -ResetService *Model::getResetService() const { - return resetService.get(); -} +#if MO_ENABLE_RESERVATION + if (reservationService) + reservationService->loop(); +#endif //MO_ENABLE_RESERVATION -#if MO_ENABLE_CERT_MGMT -void Model::setCertificateService(std::unique_ptr cs) { - this->certService = std::move(cs); - capabilitiesUpdated = true; -} + if (resetService) + resetService->loop(); + +#if MO_ENABLE_V201 + if (availabilityService) + availabilityService->loop(); -CertificateService *Model::getCertificateService() const { - return certService.get(); + if (transactionService) + transactionService->loop(); + + if (resetServiceV201) + resetServiceV201->loop(); +#endif } -#endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_V201 void Model::setAvailabilityService(std::unique_ptr as) { @@ -299,7 +365,7 @@ void Model::updateSupportedStandardProfiles() { auto buf = makeString(getMemoryTag(), supportedFeatureProfilesString->getString()); - if (chargeControlCommon && + if (connectorService && heartbeatService && bootService) { if (!strstr(supportedFeatureProfilesString->getString(), "Core")) { diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index ff31bdb2..ce53512d 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -1,28 +1,41 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_MODEL_H #define MO_MODEL_H -#include - #include #include #include -#include + +// number of EVSE IDs (including 0). On a charger with one physical connector, NUM_EVSEID is 2 +#ifndef MO_NUM_EVSEID +// Use MO_NUMCONNECTORS if defined, for backwards compatibility +#if defined(MO_NUMCONNECTORS) +#define MO_NUM_EVSEID MO_NUMCONNECTORS +#else +#define MO_NUM_EVSEID 2 +#endif +#endif // MO_NUM_EVSEID namespace MicroOcpp { -class TransactionStore; -class SmartChargingService; -class ConnectorsCommon; +class Context; + +class BootService; +class HeartbeatService; +class ResetService; +class ConnectorService; +class Connector; +class TransactionStoreEvse; class MeteringService; +class MeteringServiceEvse; + +#if MO_ENABLE_FIRMWAREMANAGEMENT class FirmwareService; class DiagnosticsService; -class HeartbeatService; -class BootService; -class ResetService; +#endif //MO_ENABLE_FIRMWAREMANAGEMENT #if MO_ENABLE_LOCAL_AUTH class AuthorizationService; @@ -32,6 +45,11 @@ class AuthorizationService; class ReservationService; #endif //MO_ENABLE_RESERVATION +#if MO_ENABLE_SMARTCHARGING +class SmartChargingService; +class SmartChargingServiceEvse; +#endif //MO_ENABLE_SMARTCHARGING + #if MO_ENABLE_CERT_MGMT class CertificateService; #endif //MO_ENABLE_CERT_MGMT @@ -50,46 +68,58 @@ class MeteringService; class Model : public MemoryManaged { private: - Vector> connectors; - std::unique_ptr transactionStore; - std::unique_ptr smartChargingService; - std::unique_ptr chargeControlCommon; - std::unique_ptr meteringService; - std::unique_ptr firmwareService; - std::unique_ptr diagnosticsService; - std::unique_ptr heartbeatService; - std::unique_ptr bootService; - std::unique_ptr resetService; + Context& context; + + BootService *bootService = nullptr; + HeartbeatService *heartbeatService = nullptr; + ResetService *resetService = nullptr; + ConnectorService *connectorService = nullptr; + Connector **connectors = nullptr; + TransactionStoreEvse **transactionStoreEvse = nullptr; + MeteringService *meteringService = nullptr; + MeteringServiceEvse **meteringServiceEvse = nullptr; + +#if MO_ENABLE_FIRMWAREMANAGEMENT + FirmwareService *firmwareService = nullptr; + DiagnosticsService *diagnosticsService = nullptr; +#endif //MO_ENABLE_FIRMWAREMANAGEMENT #if MO_ENABLE_LOCAL_AUTH - std::unique_ptr authorizationService; + AuthorizationService *authorizationService = nullptr; #endif //MO_ENABLE_LOCAL_AUTH #if MO_ENABLE_RESERVATION - std::unique_ptr reservationService; + ReservationService *reservationService = nullptr; #endif //MO_ENABLE_RESERVATION +#if MO_ENABLE_SMARTCHARGING + SmartChargingService *smartChargingService = nullptr; + SmartChargingServiceEvse **smartChargingServiceEvse = nullptr; +#endif //MO_ENABLE_SMARTCHARGING + #if MO_ENABLE_CERT_MGMT - std::unique_ptr certService; + CertificateService *certService = nullptr; #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_V201 - std::unique_ptr availabilityService; - std::unique_ptr variableService; - std::unique_ptr transactionService; - std::unique_ptr resetServiceV201; - std::unique_ptr meteringServiceV201; - std::unique_ptr remoteControlService; + AvailabilityService *availabilityService = nullptr; + VariableService *variableService = nullptr; + TransactionService *transactionService = nullptr; + Ocpp201::ResetService *resetServiceV201 = nullptr; + Ocpp201::MeteringService *meteringServiceV201 = nullptr; + RemoteControlService *remoteControlService = nullptr; #endif Clock clock; - ProtocolVersion version; + int ocppVersion = MO_OCPP_V16; + unsigned int numEvseId = MO_NUM_EVSEID; bool capabilitiesUpdated = true; void updateSupportedStandardProfiles(); bool runTasks = false; + bool isSetup = false; const uint16_t bootNr = 0; //each boot of this lib has a unique number @@ -98,52 +128,38 @@ class Model : public MemoryManaged { Model(const Model& rhs) = delete; ~Model(); - void loop(); - - void activateTasks() {runTasks = true;} - - void setTransactionStore(std::unique_ptr transactionStore); - TransactionStore *getTransactionStore(); - - void setSmartChargingService(std::unique_ptr scs); - SmartChargingService* getSmartChargingService() const; - - void setConnectorsCommon(std::unique_ptr ccs); - ConnectorsCommon *getConnectorsCommon(); - - void setConnectors(Vector>&& connectors); - unsigned int getNumConnectors() const; - Connector *getConnector(unsigned int connectorId); - - void setMeteringSerivce(std::unique_ptr meteringService); - MeteringService* getMeteringService() const; + // Set number of EVSE IDs (including 0). On a charger with one physical connector, numEvseId is 2. Default value is MO_NUM_EVSEID + void setNumEvseId(unsigned int numEvseId); + unsigned int getNumEvseId(); - void setFirmwareService(std::unique_ptr firmwareService); - FirmwareService *getFirmwareService() const; + BootService *getBootService(); + HeartbeatService *getHeartbeatService(); + ResetService *getResetService(); + ConnectorService *getConnectorService(); + Connector *getConnector(unsigned int evseId); + TransactionStoreEvse *getTransactionStoreEvse(); + MeteringService *getMeteringService(); + MeteringServiceEvse *getMeteringServiceEvse(unsigned int evseId); - void setDiagnosticsService(std::unique_ptr diagnosticsService); - DiagnosticsService *getDiagnosticsService() const; - - void setHeartbeatService(std::unique_ptr heartbeatService); +#if MO_ENABLE_FIRMWAREMANAGEMENT + FirmwareService *getFirmwareService(); + DiagnosticsService *getDiagnosticsService(); +#endif //MO_ENABLE_FIRMWAREMANAGEMENT #if MO_ENABLE_LOCAL_AUTH - void setAuthorizationService(std::unique_ptr authorizationService); AuthorizationService *getAuthorizationService(); #endif //MO_ENABLE_LOCAL_AUTH #if MO_ENABLE_RESERVATION - void setReservationService(std::unique_ptr reservationService); ReservationService *getReservationService(); #endif //MO_ENABLE_RESERVATION - void setBootService(std::unique_ptr bs); - BootService *getBootService() const; - - void setResetService(std::unique_ptr rs); - ResetService *getResetService() const; +#if MO_ENABLE_SMARTCHARGING + SmartChargingService *getSmartChargingService(); + SmartChargingServiceEvse *getSmartChargingServiceEvse(unsigned int evseId); +#endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT - void setCertificateService(std::unique_ptr cs); CertificateService *getCertificateService() const; #endif //MO_ENABLE_CERT_MGMT @@ -167,6 +183,12 @@ class Model : public MemoryManaged { RemoteControlService *getRemoteControlService() const; #endif + bool setup(int ocppVersion); + + void loop(); + + void activateTasks() {runTasks = true;} + Clock &getClock(); const ProtocolVersion& getVersion() const; diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index 762bd6ac..a73f53ec 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -15,12 +15,12 @@ using namespace::MicroOcpp; -SmartChargingConnector::SmartChargingConnector(Model& model, std::shared_ptr filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile) : - MemoryManaged("v16.SmartCharging.SmartChargingConnector"), model(model), filesystem{filesystem}, connectorId{connectorId}, ChargePointMaxProfile(ChargePointMaxProfile), ChargePointTxDefaultProfile(ChargePointTxDefaultProfile) { +SmartChargingServiceEvse::SmartChargingServiceEvse(Model& model, std::shared_ptr filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile) : + MemoryManaged("v16.SmartCharging.SmartChargingServiceEvse"), model(model), filesystem{filesystem}, connectorId{connectorId}, ChargePointMaxProfile(ChargePointMaxProfile), ChargePointTxDefaultProfile(ChargePointTxDefaultProfile) { } -SmartChargingConnector::~SmartChargingConnector() { +SmartChargingServiceEvse::~SmartChargingServiceEvse() { } @@ -28,7 +28,7 @@ SmartChargingConnector::~SmartChargingConnector() { * limitOut: the calculated maximum charge rate at the moment, or the default value if no limit is defined * validToOut: The begin of the next SmartCharging restriction after time t */ -void SmartChargingConnector::calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut) { +void SmartChargingServiceEvse::calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut) { //initialize output parameters with the default values limitOut = ChargeRate(); @@ -100,7 +100,7 @@ void SmartChargingConnector::calculateLimit(const Timestamp &t, ChargeRate& limi limitOut = chargeRate_min(txLimit, cpLimit); } -void SmartChargingConnector::trackTransaction() { +void SmartChargingServiceEvse::trackTransaction() { Transaction *tx = nullptr; if (model.getConnector(connectorId)) { @@ -143,7 +143,7 @@ void SmartChargingConnector::trackTransaction() { } } -void SmartChargingConnector::loop(){ +void SmartChargingServiceEvse::loop(){ trackTransaction(); @@ -187,14 +187,14 @@ void SmartChargingConnector::loop(){ } } -void SmartChargingConnector::setSmartChargingOutput(std::function limitOutput) { +void SmartChargingServiceEvse::setSmartChargingOutput(std::function limitOutput) { if (this->limitOutput) { MO_DBG_WARN("replacing existing SmartChargingOutput"); } this->limitOutput = limitOutput; } -ChargingProfile *SmartChargingConnector::updateProfiles(std::unique_ptr chargingProfile) { +ChargingProfile *SmartChargingServiceEvse::updateProfiles(std::unique_ptr chargingProfile) { int stackLevel = chargingProfile->getStackLevel(); //already validated @@ -213,11 +213,11 @@ ChargingProfile *SmartChargingConnector::updateProfiles(std::unique_ptr filter) { +bool SmartChargingServiceEvse::clearChargingProfile(const std::function filter) { bool found = false; ProfileStack *profileStacks [] = {&TxProfile, &TxDefaultProfile}; @@ -237,7 +237,7 @@ bool SmartChargingConnector::clearChargingProfile(const std::function SmartChargingConnector::getCompositeSchedule(int duration, ChargingRateUnitType_Optional unit) { +std::unique_ptr SmartChargingServiceEvse::getCompositeSchedule(int duration, ChargingRateUnitType_Optional unit) { auto& startSchedule = model.getClock().now(); @@ -285,7 +285,7 @@ std::unique_ptr SmartChargingConnector::getCompositeSchedule(i return schedule; } -size_t SmartChargingConnector::getChargingProfilesCount() { +size_t SmartChargingServiceEvse::getChargingProfilesCount() { size_t chargingProfilesCount = 0; for (size_t i = 0; i < MO_ChargeProfileMaxStackLevel + 1; i++) { if (TxDefaultProfile[i]) { @@ -298,7 +298,7 @@ size_t SmartChargingConnector::getChargingProfilesCount() { return chargingProfilesCount; } -SmartChargingConnector *SmartChargingService::getScConnectorById(unsigned int connectorId) { +SmartChargingServiceEvse *SmartChargingService::getScConnectorById(unsigned int connectorId) { if (connectorId == 0) { return nullptr; } @@ -311,7 +311,7 @@ SmartChargingConnector *SmartChargingService::getScConnectorById(unsigned int co } SmartChargingService::SmartChargingService(Context& context, std::shared_ptr filesystem, unsigned int numConnectors) - : MemoryManaged("v16.SmartCharging.SmartChargingService"), context(context), filesystem{filesystem}, connectors{makeVector(getMemoryTag())}, numConnectors(numConnectors) { + : MemoryManaged("v16.SmartCharging.SmartChargingService"), context(context), filesystem{filesystem}, connectors{makeVector(getMemoryTag())}, numConnectors(numConnectors) { for (unsigned int cId = 1; cId < numConnectors; cId++) { connectors.emplace_back(context.getModel(), filesystem, cId, ChargePointMaxProfile, ChargePointTxDefaultProfile); diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h index 57952a72..995cf95e 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h @@ -28,7 +28,7 @@ class Model; using ProfileStack = std::array, MO_ChargeProfileMaxStackLevel + 1>; -class SmartChargingConnector : public MemoryManaged { +class SmartChargingServiceEvse : public MemoryManaged { private: Model& model; std::shared_ptr filesystem; @@ -54,9 +54,9 @@ class SmartChargingConnector : public MemoryManaged { void trackTransaction(); public: - SmartChargingConnector(Model& model, std::shared_ptr filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile); - SmartChargingConnector(SmartChargingConnector&&) = default; - ~SmartChargingConnector(); + SmartChargingServiceEvse(Model& model, std::shared_ptr filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile); + SmartChargingServiceEvse(SmartChargingServiceEvse&&) = default; + ~SmartChargingServiceEvse(); void loop(); @@ -77,8 +77,8 @@ class SmartChargingService : public MemoryManaged { private: Context& context; std::shared_ptr filesystem; - Vector connectors; //connectorId 0 excluded - SmartChargingConnector *getScConnectorById(unsigned int connectorId); + Vector connectors; //connectorId 0 excluded + SmartChargingServiceEvse *getScConnectorById(unsigned int connectorId); unsigned int numConnectors; //connectorId 0 included ProfileStack ChargePointMaxProfile; diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 5dc3e869..ecbb17fd 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -54,7 +54,7 @@ namespace MicroOcpp { * See OCPP 1.6 Specification - Edition 2, sections 3.6, 4.8, 4.10 and 5.11. */ -class ConnectorTransactionStore; +class TransactionStoreEvse; class SendStatus { private: @@ -80,7 +80,7 @@ class SendStatus { class Transaction : public MemoryManaged { private: - ConnectorTransactionStore& context; + TransactionStoreEvse& context; bool active = true; //once active is false, the tx must stop (or cannot start at all) @@ -123,7 +123,7 @@ class Transaction : public MemoryManaged { bool silent = false; //silent Tx: process tx locally, without reporting to the server public: - Transaction(ConnectorTransactionStore& context, unsigned int connectorId, unsigned int txNr, bool silent = false) : + Transaction(TransactionStoreEvse& context, unsigned int connectorId, unsigned int txNr, bool silent = false) : MemoryManaged("v16.Transactions.Transaction"), context(context), connectorId(connectorId), diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index 39c096ba..41a7e747 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -9,7 +9,7 @@ using namespace MicroOcpp; -ConnectorTransactionStore::ConnectorTransactionStore(TransactionStore& context, unsigned int connectorId, std::shared_ptr filesystem) : +TransactionStoreEvse::TransactionStoreEvse(TransactionStore& context, unsigned int connectorId, std::shared_ptr filesystem) : MemoryManaged("v16.Transactions.TransactionStore"), context(context), connectorId(connectorId), @@ -18,11 +18,11 @@ ConnectorTransactionStore::ConnectorTransactionStore(TransactionStore& context, } -ConnectorTransactionStore::~ConnectorTransactionStore() { +TransactionStoreEvse::~TransactionStoreEvse() { } -std::shared_ptr ConnectorTransactionStore::getTransaction(unsigned int txNr) { +std::shared_ptr TransactionStoreEvse::getTransaction(unsigned int txNr) { //check for most recent element of cache first because of temporal locality if (!transactions.empty()) { @@ -98,7 +98,7 @@ std::shared_ptr ConnectorTransactionStore::getTransaction(unsigned return transaction; } -std::shared_ptr ConnectorTransactionStore::createTransaction(unsigned int txNr, bool silent) { +std::shared_ptr TransactionStoreEvse::createTransaction(unsigned int txNr, bool silent) { auto transaction = std::allocate_shared(makeAllocator(getMemoryTag()), *this, connectorId, txNr, silent); @@ -122,7 +122,7 @@ std::shared_ptr ConnectorTransactionStore::createTransaction(unsign return transaction; } -bool ConnectorTransactionStore::commit(Transaction *transaction) { +bool TransactionStoreEvse::commit(Transaction *transaction) { if (!filesystem) { MO_DBG_DEBUG("no FS: nothing to commit"); @@ -151,7 +151,7 @@ bool ConnectorTransactionStore::commit(Transaction *transaction) { return true; } -bool ConnectorTransactionStore::remove(unsigned int txNr) { +bool TransactionStoreEvse::remove(unsigned int txNr) { if (!filesystem) { MO_DBG_DEBUG("no FS: nothing to remove"); @@ -176,52 +176,6 @@ bool ConnectorTransactionStore::remove(unsigned int txNr) { return filesystem->remove(fn); } -TransactionStore::TransactionStore(unsigned int nConnectors, std::shared_ptr filesystem) : - MemoryManaged{"v16.Transactions.TransactionStore"}, connectors{makeVector>(getMemoryTag())} { - - for (unsigned int i = 0; i < nConnectors; i++) { - connectors.push_back(std::unique_ptr( - new ConnectorTransactionStore(*this, i, filesystem))); - } -} - -bool TransactionStore::commit(Transaction *transaction) { - if (!transaction) { - MO_DBG_ERR("Invalid arg"); - return false; - } - auto connectorId = transaction->getConnectorId(); - if (connectorId >= connectors.size()) { - MO_DBG_ERR("Invalid tx"); - return false; - } - return connectors[connectorId]->commit(transaction); -} - -std::shared_ptr TransactionStore::getTransaction(unsigned int connectorId, unsigned int txNr) { - if (connectorId >= connectors.size()) { - MO_DBG_ERR("Invalid connectorId"); - return nullptr; - } - return connectors[connectorId]->getTransaction(txNr); -} - -std::shared_ptr TransactionStore::createTransaction(unsigned int connectorId, unsigned int txNr, bool silent) { - if (connectorId >= connectors.size()) { - MO_DBG_ERR("Invalid connectorId"); - return nullptr; - } - return connectors[connectorId]->createTransaction(txNr, silent); -} - -bool TransactionStore::remove(unsigned int connectorId, unsigned int txNr) { - if (connectorId >= connectors.size()) { - MO_DBG_ERR("Invalid connectorId"); - return false; - } - return connectors[connectorId]->remove(txNr); -} - #if MO_ENABLE_V201 #include diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.h b/src/MicroOcpp/Model/Transactions/TransactionStore.h index c5d3421e..133161c0 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.h +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.h @@ -14,7 +14,7 @@ namespace MicroOcpp { class TransactionStore; -class ConnectorTransactionStore : public MemoryManaged { +class TransactionStoreEvse : public MemoryManaged { private: TransactionStore& context; const unsigned int connectorId; @@ -24,12 +24,12 @@ class ConnectorTransactionStore : public MemoryManaged { Vector> transactions; public: - ConnectorTransactionStore(TransactionStore& context, unsigned int connectorId, std::shared_ptr filesystem); - ConnectorTransactionStore(const ConnectorTransactionStore&) = delete; - ConnectorTransactionStore(ConnectorTransactionStore&&) = delete; - ConnectorTransactionStore& operator=(const ConnectorTransactionStore&) = delete; + TransactionStoreEvse(TransactionStore& context, unsigned int connectorId, std::shared_ptr filesystem); + TransactionStoreEvse(const TransactionStoreEvse&) = delete; + TransactionStoreEvse(TransactionStoreEvse&&) = delete; + TransactionStoreEvse& operator=(const TransactionStoreEvse&) = delete; - ~ConnectorTransactionStore(); + ~TransactionStoreEvse(); bool commit(Transaction *transaction); @@ -39,20 +39,6 @@ class ConnectorTransactionStore : public MemoryManaged { bool remove(unsigned int txNr); }; -class TransactionStore : public MemoryManaged { -private: - Vector> connectors; -public: - TransactionStore(unsigned int nConnectors, std::shared_ptr filesystem); - - bool commit(Transaction *transaction); - - std::shared_ptr getTransaction(unsigned int connectorId, unsigned int txNr); - std::shared_ptr createTransaction(unsigned int connectorId, unsigned int txNr, bool silent = false); - - bool remove(unsigned int connectorId, unsigned int txNr); -}; - } #if MO_ENABLE_V201 diff --git a/src/MicroOcpp/Version.h b/src/MicroOcpp/Version.h index 92a2ea46..d767c173 100644 --- a/src/MicroOcpp/Version.h +++ b/src/MicroOcpp/Version.h @@ -11,28 +11,18 @@ #define MO_VERSION "1.2.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 + * OCPP version identifiers */ -#ifndef MO_ENABLE_V201 -#define MO_ENABLE_V201 0 -#endif - -#ifdef __cplusplus - -namespace MicroOcpp { +#define MO_OCPP_V16 160 // OCPP 1.6 +#define MO_OCPP_V201 201 // OCPP 2.0.1 /* - * OCPP version type, defined in Model + * Enable OCPP 2.0.1 support. If enabled, library can be initialized with v1.6 or v2.0.1. The choice + * of the protocol is done dynamically during initialization */ -struct ProtocolVersion { - const int major, minor, patch; - ProtocolVersion(int major = 1, int minor = 6, int patch = 0) : major(major), minor(minor), patch(patch) { } -}; - -} - -#endif //__cplusplus +#ifndef MO_ENABLE_V201 +#define MO_ENABLE_V201 1 +#endif // Certificate Management (UCs M03 - M05). Works with OCPP 1.6 and 2.0.1 #ifndef MO_ENABLE_CERT_MGMT @@ -49,4 +39,12 @@ struct ProtocolVersion { #define MO_ENABLE_LOCAL_AUTH 1 #endif +#ifndef MO_ENABLE_SMARTCHARGING +#define MO_ENABLE_SMARTCHARGING 1 +#endif + +#ifndef MO_ENABLE_FIRMWAREMANAGEMENT +#define MO_ENABLE_FIRMWAREMANAGEMENT 1 +#endif + #endif diff --git a/tests/Metering.cpp b/tests/Metering.cpp index aa555e99..712490fe 100644 --- a/tests/Metering.cpp +++ b/tests/Metering.cpp @@ -1,12 +1,11 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include #include #include #include -#include #include #include #include diff --git a/tests/benchmarks/scripts/eval_firmware_size.py b/tests/benchmarks/scripts/eval_firmware_size.py index a952f7f8..44cbc558 100755 --- a/tests/benchmarks/scripts/eval_firmware_size.py +++ b/tests/benchmarks/scripts/eval_firmware_size.py @@ -142,8 +142,8 @@ def categorize_table(df): df.at['Model/Certificates/CertificateService.cpp', 'Module'] = MODULE_CERTS df.at['Model/ConnectorBase/Connector.cpp', 'v16'] = TICK df.at['Model/ConnectorBase/Connector.cpp', 'Module'] = MODULE_CORE - df.at['Model/ConnectorBase/ConnectorsCommon.cpp', 'v16'] = TICK - df.at['Model/ConnectorBase/ConnectorsCommon.cpp', 'Module'] = MODULE_CORE + df.at['Model/ConnectorBase/ConnectorService.cpp', 'v16'] = TICK + df.at['Model/ConnectorBase/ConnectorService.cpp', 'Module'] = MODULE_CORE df.at['Model/Diagnostics/DiagnosticsService.cpp', 'v16'] = TICK df.at['Model/Diagnostics/DiagnosticsService.cpp', 'Module'] = MODULE_FW_MNGT df.at['Model/FirmwareManagement/FirmwareService.cpp', 'v16'] = TICK @@ -151,8 +151,6 @@ def categorize_table(df): df.at['Model/Heartbeat/HeartbeatService.cpp', 'v16'] = TICK df.at['Model/Heartbeat/HeartbeatService.cpp', 'v201'] = TICK df.at['Model/Heartbeat/HeartbeatService.cpp', 'Module'] = MODULE_AVAILABILITY - df.at['Model/Metering/MeteringConnector.cpp', 'v16'] = TICK - df.at['Model/Metering/MeteringConnector.cpp', 'Module'] = MODULE_METERVALUES df.at['Model/Metering/MeteringService.cpp', 'v16'] = TICK df.at['Model/Metering/MeteringService.cpp', 'Module'] = MODULE_METERVALUES df.at['Model/Metering/MeterStore.cpp', 'v16'] = TICK From 9baf8f07838cae0d25ee77929a75ebf4e04caa5b Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Fri, 13 Jun 2025 00:58:43 +0200 Subject: [PATCH 02/50] refactor code structure and API --- CMakeLists.txt | 137 +- src/MicroOcpp.cpp | 3549 ++++++++++++----- src/MicroOcpp.h | 818 ++-- src/MicroOcpp/Context.cpp | 317 ++ src/MicroOcpp/Context.h | 134 + src/MicroOcpp/Core/Configuration.cpp | 258 -- src/MicroOcpp/Core/Configuration.h | 45 - src/MicroOcpp/Core/ConfigurationContainer.cpp | 76 - src/MicroOcpp/Core/ConfigurationContainer.h | 65 - .../Core/ConfigurationContainerFlash.cpp | 362 -- .../Core/ConfigurationContainerFlash.h | 17 - src/MicroOcpp/Core/ConfigurationKeyValue.cpp | 298 -- src/MicroOcpp/Core/ConfigurationKeyValue.h | 89 - src/MicroOcpp/Core/ConfigurationOptions.h | 64 - src/MicroOcpp/Core/Configuration_c.cpp | 315 -- src/MicroOcpp/Core/Configuration_c.h | 84 - src/MicroOcpp/Core/Connection.cpp | 302 +- src/MicroOcpp/Core/Connection.h | 97 +- src/MicroOcpp/Core/Context.cpp | 161 - src/MicroOcpp/Core/Context.h | 99 - src/MicroOcpp/Core/FilesystemAdapter.cpp | 1329 +++--- src/MicroOcpp/Core/FilesystemAdapter.h | 143 +- src/MicroOcpp/Core/FilesystemUtils.cpp | 253 +- src/MicroOcpp/Core/FilesystemUtils.h | 62 +- src/MicroOcpp/Core/Ftp.h | 22 +- src/MicroOcpp/Core/FtpMbedTLS.cpp | 28 +- src/MicroOcpp/Core/FtpMbedTLS.h | 12 +- src/MicroOcpp/Core/Memory.cpp | 19 +- src/MicroOcpp/Core/MessageService.cpp | 490 +++ src/MicroOcpp/Core/MessageService.h | 101 + src/MicroOcpp/Core/OcppError.h | 2 +- src/MicroOcpp/Core/Operation.h | 7 +- src/MicroOcpp/Core/OperationRegistry.cpp | 80 - src/MicroOcpp/Core/OperationRegistry.h | 44 - src/MicroOcpp/Core/PersistencyUtils.cpp | 152 + src/MicroOcpp/Core/PersistencyUtils.h | 36 + src/MicroOcpp/Core/Request.cpp | 248 +- src/MicroOcpp/Core/Request.h | 58 +- src/MicroOcpp/Core/RequestCallbacks.h | 12 +- src/MicroOcpp/Core/RequestQueue.cpp | 299 +- src/MicroOcpp/Core/RequestQueue.h | 54 +- src/MicroOcpp/Core/Time.cpp | 708 ++-- src/MicroOcpp/Core/Time.h | 148 +- src/MicroOcpp/Core/UuidUtils.cpp | 18 +- src/MicroOcpp/Core/UuidUtils.h | 11 +- src/MicroOcpp/Debug.cpp | 87 +- src/MicroOcpp/Debug.h | 91 +- .../Model/Authorization/AuthorizationData.cpp | 125 +- .../Model/Authorization/AuthorizationData.h | 37 +- .../Model/Authorization/AuthorizationList.cpp | 289 +- .../Model/Authorization/AuthorizationList.h | 28 +- .../Authorization/AuthorizationService.cpp | 136 +- .../Authorization/AuthorizationService.h | 27 +- src/MicroOcpp/Model/Authorization/IdToken.cpp | 47 +- src/MicroOcpp/Model/Authorization/IdToken.h | 50 +- .../Model/Availability/AvailabilityDefs.cpp | 74 + .../Model/Availability/AvailabilityDefs.h | 81 + .../Availability/AvailabilityService.cpp | 583 ++- .../Model/Availability/AvailabilityService.h | 165 +- .../Availability/ChangeAvailabilityStatus.h | 25 - .../Model/Boot/BootNotificationData.h | 52 + src/MicroOcpp/Model/Boot/BootService.cpp | 394 +- src/MicroOcpp/Model/Boot/BootService.h | 79 +- .../Model/Certificates/Certificate.cpp | 34 +- .../Model/Certificates/Certificate.h | 30 +- .../Model/Certificates/CertificateMbedTLS.cpp | 162 +- .../Model/Certificates/CertificateMbedTLS.h | 16 +- .../Model/Certificates/CertificateService.cpp | 52 +- .../Model/Certificates/CertificateService.h | 11 +- .../Model/Certificates/Certificate_c.cpp | 18 +- .../Model/Certificates/Certificate_c.h | 24 +- src/MicroOcpp/Model/Common/EvseId.h | 42 + src/MicroOcpp/Model/Common/Mutability.h | 41 + .../Model/Configuration/Configuration.cpp | 192 + .../Model/Configuration/Configuration.h | 82 + .../Configuration/ConfigurationContainer.cpp | 258 ++ .../Configuration/ConfigurationContainer.h | 78 + .../Model/Configuration/ConfigurationDefs.h | 17 + .../Configuration/ConfigurationService.cpp | 497 +++ .../Configuration/ConfigurationService.h | 87 + .../ConnectorBase/ChargePointErrorData.h | 33 - .../Model/ConnectorBase/ChargePointStatus.h | 36 - .../Model/ConnectorBase/Connector.cpp | 1322 ------ src/MicroOcpp/Model/ConnectorBase/Connector.h | 166 - .../Model/ConnectorBase/ConnectorService.cpp | 92 - .../Model/ConnectorBase/ConnectorService.h | 26 - src/MicroOcpp/Model/ConnectorBase/EvseId.h | 26 - .../ConnectorBase/UnlockConnectorResult.h | 36 - .../Model/Diagnostics/Diagnostics.cpp | 101 + src/MicroOcpp/Model/Diagnostics/Diagnostics.h | 78 + .../Model/Diagnostics/DiagnosticsService.cpp | 1221 ++++-- .../Model/Diagnostics/DiagnosticsService.h | 149 +- .../Model/Diagnostics/DiagnosticsStatus.h | 20 - .../FirmwareManagement/FirmwareService.cpp | 307 +- .../FirmwareManagement/FirmwareService.h | 74 +- .../Model/FirmwareManagement/FirmwareStatus.h | 9 +- .../Model/Heartbeat/HeartbeatService.cpp | 145 +- .../Model/Heartbeat/HeartbeatService.h | 43 +- src/MicroOcpp/Model/Metering/MeterStore.cpp | 318 +- src/MicroOcpp/Model/Metering/MeterStore.h | 61 +- src/MicroOcpp/Model/Metering/MeterValue.cpp | 661 ++- src/MicroOcpp/Model/Metering/MeterValue.h | 189 +- .../Model/Metering/MeteringService.cpp | 1069 +++-- .../Model/Metering/MeteringService.h | 198 +- ...ValuesV201.cpp => MeteringServiceV201.cpp} | 0 ...eterValuesV201.h => MeteringServiceV201.h} | 0 .../Model/Metering/ReadingContext.cpp | 47 +- src/MicroOcpp/Model/Metering/ReadingContext.h | 32 +- src/MicroOcpp/Model/Metering/SampledValue.cpp | 61 - src/MicroOcpp/Model/Metering/SampledValue.h | 147 - src/MicroOcpp/Model/Model.cpp | 713 ++-- src/MicroOcpp/Model/Model.h | 240 +- .../Model/RemoteControl/RemoteControlDefs.h | 98 +- .../RemoteControl/RemoteControlService.cpp | 523 ++- .../RemoteControl/RemoteControlService.h | 102 +- .../Model/Reservation/Reservation.cpp | 137 +- src/MicroOcpp/Model/Reservation/Reservation.h | 47 +- .../Model/Reservation/ReservationService.cpp | 156 +- .../Model/Reservation/ReservationService.h | 29 +- src/MicroOcpp/Model/Reset/ResetDefs.h | 8 +- src/MicroOcpp/Model/Reset/ResetService.cpp | 362 +- src/MicroOcpp/Model/Reset/ResetService.h | 82 +- .../Model/SecurityEvent/SecurityEvent.h | 16 + .../SecurityEvent/SecurityEventService.cpp | 619 +++ .../SecurityEvent/SecurityEventService.h | 109 + .../SmartCharging/SmartChargingModel.cpp | 885 ++-- .../Model/SmartCharging/SmartChargingModel.h | 138 +- .../SmartCharging/SmartChargingService.cpp | 1159 ++++-- .../SmartCharging/SmartChargingService.h | 141 +- .../Model/Transactions/Transaction.cpp | 382 +- .../Model/Transactions/Transaction.h | 260 +- .../Model/Transactions/TransactionDefs.h | 83 +- .../Transactions/TransactionDeserialize.cpp | 280 -- .../Transactions/TransactionDeserialize.h | 20 - .../Model/Transactions/TransactionService.h | 144 - .../Transactions/TransactionService16.cpp | 1520 +++++++ .../Model/Transactions/TransactionService16.h | 176 + ...nService.cpp => TransactionService201.cpp} | 628 ++- .../Transactions/TransactionService201.h | 173 + .../Model/Transactions/TransactionStore.cpp | 804 ++-- .../Model/Transactions/TransactionStore.h | 79 +- src/MicroOcpp/Model/Variables/Variable.cpp | 16 +- src/MicroOcpp/Model/Variables/Variable.h | 25 +- .../Model/Variables/VariableContainer.cpp | 46 +- .../Model/Variables/VariableContainer.h | 16 +- .../Model/Variables/VariableService.cpp | 186 +- .../Model/Variables/VariableService.h | 54 +- src/MicroOcpp/Operations/Authorize.cpp | 64 +- src/MicroOcpp/Operations/Authorize.h | 33 +- src/MicroOcpp/Operations/BootNotification.cpp | 118 +- src/MicroOcpp/Operations/BootNotification.h | 31 +- .../Operations/CancelReservation.cpp | 8 +- src/MicroOcpp/Operations/CancelReservation.h | 13 +- .../Operations/ChangeAvailability.cpp | 62 +- src/MicroOcpp/Operations/ChangeAvailability.h | 23 +- .../Operations/ChangeConfiguration.cpp | 143 +- .../Operations/ChangeConfiguration.h | 19 +- src/MicroOcpp/Operations/ClearCache.cpp | 23 +- src/MicroOcpp/Operations/ClearCache.h | 15 +- .../Operations/ClearChargingProfile.cpp | 90 +- .../Operations/ClearChargingProfile.h | 17 +- src/MicroOcpp/Operations/CustomOperation.cpp | 212 +- src/MicroOcpp/Operations/CustomOperation.h | 58 +- src/MicroOcpp/Operations/DataTransfer.cpp | 36 +- src/MicroOcpp/Operations/DataTransfer.h | 14 +- .../Operations/DeleteCertificate.cpp | 16 +- src/MicroOcpp/Operations/DeleteCertificate.h | 12 +- .../DiagnosticsStatusNotification.cpp | 28 +- .../DiagnosticsStatusNotification.h | 16 +- .../Operations/FirmwareStatusNotification.cpp | 10 +- .../Operations/FirmwareStatusNotification.h | 15 +- src/MicroOcpp/Operations/GetBaseReport.cpp | 12 +- src/MicroOcpp/Operations/GetBaseReport.h | 12 +- .../Operations/GetCompositeSchedule.cpp | 84 +- .../Operations/GetCompositeSchedule.h | 24 +- src/MicroOcpp/Operations/GetConfiguration.cpp | 122 +- src/MicroOcpp/Operations/GetConfiguration.h | 19 +- src/MicroOcpp/Operations/GetDiagnostics.cpp | 26 +- src/MicroOcpp/Operations/GetDiagnostics.h | 17 +- .../Operations/GetInstalledCertificateIds.cpp | 18 +- .../Operations/GetInstalledCertificateIds.h | 15 +- .../Operations/GetLocalListVersion.cpp | 12 +- .../Operations/GetLocalListVersion.h | 15 +- src/MicroOcpp/Operations/GetLog.cpp | 77 + src/MicroOcpp/Operations/GetLog.h | 41 + src/MicroOcpp/Operations/GetVariables.cpp | 15 +- src/MicroOcpp/Operations/GetVariables.h | 14 +- src/MicroOcpp/Operations/Heartbeat.cpp | 46 +- src/MicroOcpp/Operations/Heartbeat.h | 24 +- .../Operations/InstallCertificate.cpp | 10 +- src/MicroOcpp/Operations/InstallCertificate.h | 12 +- .../Operations/LogStatusNotification.cpp | 49 + .../Operations/LogStatusNotification.h | 36 + src/MicroOcpp/Operations/MeterValues.cpp | 68 +- src/MicroOcpp/Operations/MeterValues.h | 25 +- src/MicroOcpp/Operations/NotifyReport.cpp | 33 +- src/MicroOcpp/Operations/NotifyReport.h | 24 +- .../Operations/RemoteStartTransaction.cpp | 112 +- .../Operations/RemoteStartTransaction.h | 23 +- .../Operations/RemoteStopTransaction.cpp | 34 +- .../Operations/RemoteStopTransaction.h | 20 +- .../Operations/RequestStartTransaction.cpp | 16 +- .../Operations/RequestStartTransaction.h | 11 +- .../Operations/RequestStopTransaction.cpp | 18 +- .../Operations/RequestStopTransaction.h | 10 +- src/MicroOcpp/Operations/ReserveNow.cpp | 109 +- src/MicroOcpp/Operations/ReserveNow.h | 21 +- src/MicroOcpp/Operations/Reset.cpp | 59 +- src/MicroOcpp/Operations/Reset.h | 26 +- .../Operations/SecurityEventNotification.cpp | 29 +- .../Operations/SecurityEventNotification.h | 22 +- src/MicroOcpp/Operations/SendLocalList.cpp | 12 +- src/MicroOcpp/Operations/SendLocalList.h | 15 +- .../Operations/SetChargingProfile.cpp | 140 +- src/MicroOcpp/Operations/SetChargingProfile.h | 16 +- src/MicroOcpp/Operations/SetVariables.cpp | 13 +- src/MicroOcpp/Operations/SetVariables.h | 10 +- src/MicroOcpp/Operations/StartTransaction.cpp | 59 +- src/MicroOcpp/Operations/StartTransaction.h | 26 +- .../Operations/StatusNotification.cpp | 116 +- src/MicroOcpp/Operations/StatusNotification.h | 47 +- src/MicroOcpp/Operations/StopTransaction.cpp | 152 +- src/MicroOcpp/Operations/StopTransaction.h | 32 +- src/MicroOcpp/Operations/TransactionEvent.cpp | 81 +- src/MicroOcpp/Operations/TransactionEvent.h | 24 +- src/MicroOcpp/Operations/TriggerMessage.cpp | 116 +- src/MicroOcpp/Operations/TriggerMessage.h | 16 +- src/MicroOcpp/Operations/UnlockConnector.cpp | 134 +- src/MicroOcpp/Operations/UnlockConnector.h | 49 +- src/MicroOcpp/Operations/UpdateFirmware.cpp | 20 +- src/MicroOcpp/Operations/UpdateFirmware.h | 28 +- src/MicroOcpp/Platform.cpp | 117 +- src/MicroOcpp/Platform.h | 94 +- src/MicroOcpp/Version.h | 34 +- src/MicroOcpp_c.cpp | 462 --- src/MicroOcpp_c.h | 217 - tests/Boot.cpp | 14 +- tests/ChargePointError.cpp | 6 +- tests/ChargingSessions.cpp | 20 +- tests/FirmwareManagement.cpp | 14 +- tests/LocalAuthList.cpp | 8 +- tests/Metering.cpp | 2 +- tests/Reservation.cpp | 30 +- tests/Reset.cpp | 8 +- tests/Security.cpp | 2 +- tests/SmartCharging.cpp | 4 +- tests/Transactions.cpp | 18 +- tests/Variables.cpp | 4 +- 248 files changed, 22306 insertions(+), 15047 deletions(-) create mode 100644 src/MicroOcpp/Context.cpp create mode 100644 src/MicroOcpp/Context.h delete mode 100644 src/MicroOcpp/Core/Configuration.cpp delete mode 100644 src/MicroOcpp/Core/Configuration.h delete mode 100644 src/MicroOcpp/Core/ConfigurationContainer.cpp delete mode 100644 src/MicroOcpp/Core/ConfigurationContainer.h delete mode 100644 src/MicroOcpp/Core/ConfigurationContainerFlash.cpp delete mode 100644 src/MicroOcpp/Core/ConfigurationContainerFlash.h delete mode 100644 src/MicroOcpp/Core/ConfigurationKeyValue.cpp delete mode 100644 src/MicroOcpp/Core/ConfigurationKeyValue.h delete mode 100644 src/MicroOcpp/Core/ConfigurationOptions.h delete mode 100644 src/MicroOcpp/Core/Configuration_c.cpp delete mode 100644 src/MicroOcpp/Core/Configuration_c.h delete mode 100644 src/MicroOcpp/Core/Context.cpp delete mode 100644 src/MicroOcpp/Core/Context.h create mode 100644 src/MicroOcpp/Core/MessageService.cpp create mode 100644 src/MicroOcpp/Core/MessageService.h delete mode 100644 src/MicroOcpp/Core/OperationRegistry.cpp delete mode 100644 src/MicroOcpp/Core/OperationRegistry.h create mode 100644 src/MicroOcpp/Core/PersistencyUtils.cpp create mode 100644 src/MicroOcpp/Core/PersistencyUtils.h create mode 100644 src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp create mode 100644 src/MicroOcpp/Model/Availability/AvailabilityDefs.h delete mode 100644 src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h create mode 100644 src/MicroOcpp/Model/Boot/BootNotificationData.h create mode 100644 src/MicroOcpp/Model/Common/EvseId.h create mode 100644 src/MicroOcpp/Model/Common/Mutability.h create mode 100644 src/MicroOcpp/Model/Configuration/Configuration.cpp create mode 100644 src/MicroOcpp/Model/Configuration/Configuration.h create mode 100644 src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp create mode 100644 src/MicroOcpp/Model/Configuration/ConfigurationContainer.h create mode 100644 src/MicroOcpp/Model/Configuration/ConfigurationDefs.h create mode 100644 src/MicroOcpp/Model/Configuration/ConfigurationService.cpp create mode 100644 src/MicroOcpp/Model/Configuration/ConfigurationService.h delete mode 100644 src/MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h delete mode 100644 src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h delete mode 100644 src/MicroOcpp/Model/ConnectorBase/Connector.cpp delete mode 100644 src/MicroOcpp/Model/ConnectorBase/Connector.h delete mode 100644 src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp delete mode 100644 src/MicroOcpp/Model/ConnectorBase/ConnectorService.h delete mode 100644 src/MicroOcpp/Model/ConnectorBase/EvseId.h delete mode 100644 src/MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h create mode 100644 src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp create mode 100644 src/MicroOcpp/Model/Diagnostics/Diagnostics.h delete mode 100644 src/MicroOcpp/Model/Diagnostics/DiagnosticsStatus.h rename src/MicroOcpp/Model/Metering/{MeterValuesV201.cpp => MeteringServiceV201.cpp} (100%) rename src/MicroOcpp/Model/Metering/{MeterValuesV201.h => MeteringServiceV201.h} (100%) delete mode 100644 src/MicroOcpp/Model/Metering/SampledValue.cpp delete mode 100644 src/MicroOcpp/Model/Metering/SampledValue.h create mode 100644 src/MicroOcpp/Model/SecurityEvent/SecurityEvent.h create mode 100644 src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp create mode 100644 src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h delete mode 100644 src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp delete mode 100644 src/MicroOcpp/Model/Transactions/TransactionDeserialize.h delete mode 100644 src/MicroOcpp/Model/Transactions/TransactionService.h create mode 100644 src/MicroOcpp/Model/Transactions/TransactionService16.cpp create mode 100644 src/MicroOcpp/Model/Transactions/TransactionService16.h rename src/MicroOcpp/Model/Transactions/{TransactionService.cpp => TransactionService201.cpp} (60%) create mode 100644 src/MicroOcpp/Model/Transactions/TransactionService201.h create mode 100644 src/MicroOcpp/Operations/GetLog.cpp create mode 100644 src/MicroOcpp/Operations/GetLog.h create mode 100644 src/MicroOcpp/Operations/LogStatusNotification.cpp create mode 100644 src/MicroOcpp/Operations/LogStatusNotification.h delete mode 100644 src/MicroOcpp_c.cpp delete mode 100644 src/MicroOcpp_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 86ba9ba4..71628754 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,102 +7,101 @@ cmake_minimum_required(VERSION 3.15) set(CMAKE_CXX_STANDARD 11) set(MO_SRC - src/MicroOcpp/Core/Configuration_c.cpp - src/MicroOcpp/Core/Configuration.cpp - src/MicroOcpp/Core/ConfigurationContainer.cpp - src/MicroOcpp/Core/ConfigurationContainerFlash.cpp - src/MicroOcpp/Core/ConfigurationKeyValue.cpp - src/MicroOcpp/Core/FilesystemAdapter.cpp - src/MicroOcpp/Core/FilesystemUtils.cpp - src/MicroOcpp/Core/FtpMbedTLS.cpp src/MicroOcpp/Core/Memory.cpp - src/MicroOcpp/Core/RequestQueue.cpp - src/MicroOcpp/Core/Context.cpp src/MicroOcpp/Core/Operation.cpp - src/MicroOcpp/Model/Model.cpp + src/MicroOcpp/Core/FilesystemAdapter.cpp + src/MicroOcpp/Core/PersistencyUtils.cpp src/MicroOcpp/Core/Request.cpp + src/MicroOcpp/Core/FilesystemUtils.cpp + src/MicroOcpp/Core/RequestQueue.cpp + src/MicroOcpp/Core/MessageService.cpp + src/MicroOcpp/Core/FtpMbedTLS.cpp + src/MicroOcpp/Core/UuidUtils.cpp src/MicroOcpp/Core/Connection.cpp src/MicroOcpp/Core/Time.cpp - src/MicroOcpp/Core/UuidUtils.cpp - src/MicroOcpp/Operations/Authorize.cpp - src/MicroOcpp/Operations/BootNotification.cpp - src/MicroOcpp/Operations/CancelReservation.cpp - src/MicroOcpp/Operations/ChangeAvailability.cpp - src/MicroOcpp/Operations/ChangeConfiguration.cpp - src/MicroOcpp/Operations/ClearCache.cpp - src/MicroOcpp/Operations/ClearChargingProfile.cpp - src/MicroOcpp/Operations/CustomOperation.cpp - src/MicroOcpp/Operations/DataTransfer.cpp - src/MicroOcpp/Operations/DeleteCertificate.cpp - src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp + src/MicroOcpp/Context.cpp + src/MicroOcpp/Operations/GetVariables.cpp src/MicroOcpp/Operations/FirmwareStatusNotification.cpp - src/MicroOcpp/Operations/GetBaseReport.cpp - src/MicroOcpp/Operations/GetCompositeSchedule.cpp - src/MicroOcpp/Operations/GetConfiguration.cpp - src/MicroOcpp/Operations/GetDiagnostics.cpp src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp - src/MicroOcpp/Operations/GetLocalListVersion.cpp - src/MicroOcpp/Operations/GetVariables.cpp - src/MicroOcpp/Operations/Heartbeat.cpp + src/MicroOcpp/Operations/UpdateFirmware.cpp src/MicroOcpp/Operations/MeterValues.cpp - src/MicroOcpp/Operations/NotifyReport.cpp + src/MicroOcpp/Operations/DataTransfer.cpp + src/MicroOcpp/Operations/GetCompositeSchedule.cpp src/MicroOcpp/Operations/RemoteStartTransaction.cpp + src/MicroOcpp/Operations/CustomOperation.cpp + src/MicroOcpp/Operations/TransactionEvent.cpp + src/MicroOcpp/Operations/ChangeAvailability.cpp src/MicroOcpp/Operations/RemoteStopTransaction.cpp + src/MicroOcpp/Operations/ReserveNow.cpp + src/MicroOcpp/Operations/CancelReservation.cpp + src/MicroOcpp/Operations/TriggerMessage.cpp + src/MicroOcpp/Operations/GetDiagnostics.cpp + src/MicroOcpp/Operations/GetLocalListVersion.cpp src/MicroOcpp/Operations/RequestStartTransaction.cpp + src/MicroOcpp/Operations/DeleteCertificate.cpp + src/MicroOcpp/Operations/LogStatusNotification.cpp + src/MicroOcpp/Operations/Heartbeat.cpp + src/MicroOcpp/Operations/GetLog.cpp + src/MicroOcpp/Operations/GetConfiguration.cpp + src/MicroOcpp/Operations/ClearCache.cpp + src/MicroOcpp/Operations/SetChargingProfile.cpp + src/MicroOcpp/Operations/Authorize.cpp + src/MicroOcpp/Operations/SecurityEventNotification.cpp src/MicroOcpp/Operations/RequestStopTransaction.cpp - src/MicroOcpp/Operations/ReserveNow.cpp + src/MicroOcpp/Operations/StatusNotification.cpp + src/MicroOcpp/Operations/StopTransaction.cpp src/MicroOcpp/Operations/Reset.cpp - src/MicroOcpp/Operations/SecurityEventNotification.cpp src/MicroOcpp/Operations/SendLocalList.cpp - src/MicroOcpp/Operations/SetChargingProfile.cpp - src/MicroOcpp/Operations/SetVariables.cpp + src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp + src/MicroOcpp/Operations/NotifyReport.cpp + src/MicroOcpp/Operations/GetBaseReport.cpp + src/MicroOcpp/Operations/ChangeConfiguration.cpp 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/InstallCertificate.cpp + src/MicroOcpp/Operations/BootNotification.cpp + src/MicroOcpp/Operations/SetVariables.cpp + src/MicroOcpp/Operations/ClearChargingProfile.cpp src/MicroOcpp/Operations/UnlockConnector.cpp - src/MicroOcpp/Operations/UpdateFirmware.cpp + src/MicroOcpp/Operations/InstallCertificate.cpp src/MicroOcpp/Debug.cpp - src/MicroOcpp/Platform.cpp - src/MicroOcpp/Core/OperationRegistry.cpp - src/MicroOcpp/Model/Availability/AvailabilityService.cpp - src/MicroOcpp/Model/Authorization/AuthorizationData.cpp - src/MicroOcpp/Model/Authorization/AuthorizationList.cpp - src/MicroOcpp/Model/Authorization/AuthorizationService.cpp - src/MicroOcpp/Model/Authorization/IdToken.cpp + src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp + src/MicroOcpp/Model/Configuration/Configuration.cpp + src/MicroOcpp/Model/Configuration/ConfigurationService.cpp src/MicroOcpp/Model/Boot/BootService.cpp - src/MicroOcpp/Model/Certificates/Certificate.cpp - src/MicroOcpp/Model/Certificates/Certificate_c.cpp + src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp + src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp + src/MicroOcpp/Model/Reset/ResetService.cpp + src/MicroOcpp/Model/Reservation/ReservationService.cpp + src/MicroOcpp/Model/Reservation/Reservation.cpp + src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp + src/MicroOcpp/Model/Certificates/Certificate_c.cpp + src/MicroOcpp/Model/Certificates/Certificate.cpp src/MicroOcpp/Model/Certificates/CertificateService.cpp - src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp - src/MicroOcpp/Model/ConnectorBase/Connector.cpp - src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp + src/MicroOcpp/Model/Transactions/TransactionStore.cpp + src/MicroOcpp/Model/Transactions/TransactionService201.cpp + src/MicroOcpp/Model/Transactions/TransactionService16.cpp + src/MicroOcpp/Model/Transactions/Transaction.cpp + src/MicroOcpp/Model/Variables/VariableContainer.cpp + src/MicroOcpp/Model/Variables/VariableService.cpp + src/MicroOcpp/Model/Variables/Variable.cpp + src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp + src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp + src/MicroOcpp/Model/Model.cpp + src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp + src/MicroOcpp/Model/Availability/AvailabilityService.cpp src/MicroOcpp/Model/Metering/MeteringService.cpp + src/MicroOcpp/Model/Metering/ReadingContext.cpp src/MicroOcpp/Model/Metering/MeterStore.cpp src/MicroOcpp/Model/Metering/MeterValue.cpp - src/MicroOcpp/Model/Metering/MeterValuesV201.cpp - src/MicroOcpp/Model/Metering/ReadingContext.cpp - src/MicroOcpp/Model/Metering/SampledValue.cpp src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp - src/MicroOcpp/Model/Reservation/Reservation.cpp - src/MicroOcpp/Model/Reservation/ReservationService.cpp - src/MicroOcpp/Model/Reset/ResetService.cpp - src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp - 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 - src/MicroOcpp/Model/Variables/VariableService.cpp + src/MicroOcpp/Model/Authorization/AuthorizationService.cpp + src/MicroOcpp/Model/Authorization/AuthorizationData.cpp + src/MicroOcpp/Model/Authorization/AuthorizationList.cpp + src/MicroOcpp/Model/Authorization/IdToken.cpp + src/MicroOcpp/Platform.cpp src/MicroOcpp.cpp - src/MicroOcpp_c.cpp ) if(ESP_PLATFORM) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index ba356bf5..a4aead8e 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -4,27 +4,29 @@ #include "MicroOcpp.h" -#include +#include #include +#include +#include +#include +#include +#include #include +#include + #include -#include #include #include #include #include #include #include -#include #include #include -#include #include #include -#include #include #include -#include #include #include #include @@ -37,22 +39,18 @@ #include -namespace MicroOcpp { -namespace Facade { +namespace MO_Facade { -#ifndef MO_CUSTOM_WS -WebSocketsClient *webSocket {nullptr}; -Connection *connection {nullptr}; -#endif +MicroOcpp::Context *g_context = nullptr; -Context *context {nullptr}; -std::shared_ptr filesystem; +} //namespace MO_Facade -#define OCPP_ID_OF_CP 0 -#define OCPP_ID_OF_CONNECTOR 1 +using namespace MO_Facade; -} //end namespace MicroOcpp::Facade -} //end namespace MicroOcpp +#define EVSE_ID_0 0 +#define EVSE_ID_1 1 + +#define CONFIGS_FN_FACADE "user-config.jsn" #if MO_ENABLE_HEAP_PROFILER #ifndef MO_HEAP_PROFILER_EXTERNAL_CONTROL @@ -60,1470 +58,2917 @@ std::shared_ptr filesystem; #endif #endif -using namespace MicroOcpp; -using namespace MicroOcpp::Facade; -using namespace MicroOcpp::Ocpp16; - -#ifndef MO_CUSTOM_WS -void mocpp_initialize(const char *backendUrl, const char *chargeBoxId, const char *chargePointModel, const char *chargePointVendor, FilesystemOpt fsOpt, const char *password, const char *CA_cert, bool autoRecover) { - if (context) { - MO_DBG_WARN("already initialized. To reinit, call mocpp_deinitialize() before"); - return; - } - - if (!backendUrl || !chargePointModel || !chargePointVendor) { - MO_DBG_ERR("invalid args"); - return; +//Begin the lifecycle of MO. Now, it is possible to set up the library. After configuring MO, +//call mo_setup() to finalize the setup +bool mo_initialize() { + if (g_context) { + MO_DBG_INFO("already initialized"); + return true; } - if (!chargeBoxId) { - chargeBoxId = ""; + g_context = new MicroOcpp::Context(); + if (!g_context) { + MO_DBG_ERR("OOM"); + return false; } + return true; +} - /* - * parse backendUrl so that it suits the links2004/arduinoWebSockets interface - */ - auto url = makeString("MicroOcpp.cpp", backendUrl); +//End the lifecycle of MO and free all resources +void mo_deinitialize() { + delete g_context; + g_context = nullptr; +} - //tolower protocol specifier - for (auto c = url.begin(); *c != ':' && c != url.end(); c++) { - *c = tolower(*c); - } +//Returns if library is initialized +bool mo_isInitialized() { + return g_context != nullptr; +} - bool isTLS = true; - if (!strncmp(url.c_str(),"wss://",strlen("wss://"))) { - isTLS = true; - } else if (!strncmp(url.c_str(),"ws://",strlen("ws://"))) { - isTLS = false; - } else { - MO_DBG_ERR("only ws:// and wss:// supported"); +#if MO_WS_USE == MO_WS_ARDUINO +//Setup MO with links2004/WebSockets library +void mo_setWebsocketUrl(const char *backendUrl, const char *chargeBoxId, const char *authorizationKey, const char *CA_cert) { + if (!g_context) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - //parse host, port - auto host_port_path = url.substr(url.find_first_of("://") + strlen("://")); - auto host_port = host_port_path.substr(0, host_port_path.find_first_of('/')); - auto path = host_port_path.substr(host_port.length()); - auto host = host_port.substr(0, host_port.find_first_of(':')); - if (host.empty()) { - MO_DBG_ERR("could not parse host: %s", url.c_str()); + if (!backendUrl) { + MO_DBG_ERR("invalid args"); return; } - uint16_t port = 0; - auto port_str = host_port.substr(host.length()); - if (port_str.empty()) { - port = isTLS ? 443U : 80U; - } else { - //skip leading ':' - port_str = port_str.substr(1); - for (auto c = port_str.begin(); c != port_str.end(); c++) { - if (*c < '0' || *c > '9') { - MO_DBG_ERR("could not parse port: %s", url.c_str()); - return; - } - auto p = port * 10U + (*c - '0'); - if (p < port) { - MO_DBG_ERR("could not parse port (overflow): %s", url.c_str()); - return; - } - port = p; - } - } - if (path.empty()) { - path = "/"; - } + MO_ConnectionConfig config; + memset(&config, 0, sizeof(config)); + config.backendUrl = backendUrl; + config.chargeBoxId = chargeBoxId; + config.authorizationKey = authorizationKey; + config.CA_cert = CA_cert; - if ((!*chargeBoxId) == '\0') { - if (path.back() != '/') { - path += '/'; - } + g_context->setDefaultConnectionConfig(config); +} +#endif + +#if __cplusplus +// Set a WebSocket Client +void mo_setConnection(MicroOcpp::Connection *connection) { + mo_setConnection2(mo_getApiContext(), connection); +} - path += chargeBoxId; +void mo_setConnection2(MO_Context *ctx, MicroOcpp::Connection *connection) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; } + auto context = mo_getContext2(ctx); - MO_DBG_INFO("connecting to %s -- (host: %s, port: %u, path: %s)", url.c_str(), host.c_str(), port, path.c_str()); + context->setConnection(connection); +} +#endif - if (!webSocket) - webSocket = new WebSocketsClient(); +#if MO_USE_FILEAPI != MO_CUSTOM_FS +//Set if MO can use the filesystem and if it needs to mount it +void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt) { + mo_setDefaultFilesystemConfig2(mo_getApiContext(), opt, MO_FILENAME_PREFIX); +} - if (isTLS) { - // server address, port, path and TLS certificate - webSocket->beginSslWithCA(host.c_str(), port, path.c_str(), CA_cert, "ocpp1.6"); - } else { - // server address, port, path - webSocket->begin(host.c_str(), port, path.c_str(), "ocpp1.6"); +void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; } + auto context = mo_getContext2(ctx); + + MO_FilesystemConfig filesystemConfig; + memset(&filesystemConfig, 0, sizeof(filesystemConfig)); + filesystemConfig.opt = opt; + filesystemConfig.path_prefix = pathPrefix; - // try ever 5000 again if connection has failed - webSocket->setReconnectInterval(5000); + context->setDefaultFilesystemConfig(filesystemConfig); +} +#endif // MO_USE_FILEAPI != MO_CUSTOM_FS - // start heartbeat (optional) - // ping server every 15000 ms - // expect pong from server within 3000 ms - // consider connection disconnected if pong is not received 2 times - webSocket->enableHeartbeat(15000, 3000, 2); //comment this one out to for specific OCPP servers +//Set the OCPP version +void mo_setOcppVersion(int ocppVersion) { + mo_setOcppVersion2(mo_getApiContext(), ocppVersion); +} - // add authentication data (optional) - if (password && strlen(password) + strlen(chargeBoxId) >= 4) { - webSocket->setAuthorization(chargeBoxId, password); +void mo_setOcppVersion2(MO_Context *ctx, int ocppVersion) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; } + auto context = mo_getContext2(ctx); - delete connection; - connection = new EspWiFi::WSClient(webSocket); + context->setOcppVersion(ocppVersion); +} + +//Set BootNotification fields +bool mo_setBootNotificationData(const char *chargePointModel, const char *chargePointVendor) { + MO_BootNotificationData bnData; + mo_BootNotificationData_init(&bnData); + bnData.chargePointModel = chargePointModel; + bnData.chargePointVendor = chargePointVendor; - mocpp_initialize(*connection, ChargerCredentials(chargePointModel, chargePointVendor), makeDefaultFilesystemAdapter(fsOpt), autoRecover); + return mo_setBootNotificationData2(mo_getApiContext(), bnData); } -#endif -ChargerCredentials::ChargerCredentials(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) { - - StaticJsonDocument<512> creds; - if (cbSNr) - creds["chargeBoxSerialNumber"] = cbSNr; - if (cpModel) - creds["chargePointModel"] = cpModel; - if (cpSNr) - creds["chargePointSerialNumber"] = cpSNr; - if (cpVendor) - creds["chargePointVendor"] = cpVendor; - if (fWv) - creds["firmwareVersion"] = fWv; - if (iccid) - creds["iccid"] = iccid; - if (imsi) - creds["imsi"] = imsi; - if (meterSNr) - creds["meterSerialNumber"] = meterSNr; - if (meterType) - creds["meterType"] = meterType; - - if (creds.overflowed()) { - MO_DBG_ERR("Charger Credentials too long"); +bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; } + auto context = mo_getContext2(ctx); - size_t written = serializeJson(creds, payload, 512); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { - if (written < 2) { - MO_DBG_ERR("Charger Credentials could not be written"); - sprintf(payload, "{}"); } -} - -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) { + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { - ChargerCredentials res; + } + #endif - 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; + MicroOcpp::BootService *bootService = nullptr; - if (creds.overflowed()) { - MO_DBG_ERR("Charger Credentials too long"); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + bootService = context->getModel16().getBootService(); } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + bootService = context->getModel201().getBootService(); + } + #endif - size_t written = serializeJson(creds, res.payload, 512); - - if (written < 2) { - MO_DBG_ERR("Charger Credentials could not be written"); - sprintf(res.payload, "{}"); + if (!bootService) { + MO_DBG_ERR("OOM"); + return false; } - return res; + return bootService->setBootNotificationData(bnData); } -void mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr fs, bool autoRecover, MicroOcpp::ProtocolVersion version) { - if (context) { - MO_DBG_WARN("already initialized. To reinit, call mocpp_deinitialize() before"); +//Input about if an EV is plugged to this EVSE +void mo_setConnectorPluggedInput(bool (*connectorPlugged)()) { + //convert and forward callback to `mo_setConnectorPluggedInput2()` + mo_setConnectorPluggedInput2(mo_getApiContext(), EVSE_ID_1, [] (unsigned int, void *userData) { + auto connectorPlugged = reinterpret_cast(userData); + return connectorPlugged(); + }, reinterpret_cast(connectorPlugged)); +} + +void mo_setConnectorPluggedInput2(MO_Context *ctx, unsigned int evseId, bool (*connectorPlugged2)(unsigned int, void*), void *userData) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } + auto context = mo_getContext2(ctx); - MO_DBG_DEBUG("initialize OCPP"); - - filesystem = fs; - MO_DBG_DEBUG("filesystem %s", filesystem ? "loaded" : "deactivated"); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + availSvcEvse->setConnectorPluggedInput(connectorPlugged2, userData); - BootStats bootstats; - BootService::loadBootStats(filesystem, bootstats); + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setConnectorPluggedInput(connectorPlugged2, userData); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto availSvc = context->getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + availSvcEvse->setConnectorPluggedInput(connectorPlugged2, userData); - if (autoRecover && bootstats.getBootFailureCount() > 3) { - BootService::recover(filesystem, bootstats); - bootstats = BootStats(); + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setConnectorPluggedInput(connectorPlugged2, userData); } + #endif +} - BootService::migrate(filesystem, bootstats); +//Input of the electricity meter register in Wh +bool mo_setEnergyMeterInput(int32_t (*energyInput)()) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_Int); + mInput.getInt = energyInput; + mInput.measurand = "Energy.Active.Import.Register"; + mInput.unit = "Wh"; + return mo_addMeterValueInput(mo_getApiContext(), EVSE_ID_1, mInput); +} - bootstats.bootNr++; //assign new boot number to this run - BootService::storeBootStats(filesystem, bootstats); +bool mo_setEnergyMeterInput2(MO_Context *ctx, unsigned int evseId, int32_t (*energyInput2)(MO_ReadingContext, unsigned int, void*), void *userData) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_IntWithArgs); + mInput.getInt2 = energyInput2; + mInput.measurand = "Energy.Active.Import.Register"; + mInput.unit = "Wh"; + mInput.user_data = userData; + return mo_addMeterValueInput(ctx, EVSE_ID_1, mInput); +} - configuration_init(filesystem); //call before each other library call +//Input of the power meter reading in W +bool mo_setPowerMeterInput(float (*powerInput)()) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_Float); + mInput.getFloat = powerInput; + mInput.measurand = "Power.Active.Import"; + mInput.unit = "W"; + return mo_addMeterValueInput(mo_getApiContext(), EVSE_ID_1, mInput); +} - context = new Context(connection, filesystem, bootstats.bootNr, version); +bool mo_setPowerMeterInput2(MO_Context *ctx, unsigned int evseId, float (*powerInput2)(MO_ReadingContext, unsigned int, void*), void *userData) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_Float); + mInput.getFloat2 = powerInput2; + mInput.measurand = "Power.Active.Import"; + mInput.unit = "W"; + return mo_addMeterValueInput(ctx, EVSE_ID_1, mInput); +} -#if MO_ENABLE_MBEDTLS - context->setFtpClient(makeFtpClientMbedTLS()); -#endif //MO_ENABLE_MBEDTLS +//Smart Charging Output +bool mo_setSmartChargingPowerOutput(void (*powerLimitOutput)(float)) { + //convert and forward callback to `mo_setSmartChargingOutput()` + return mo_setSmartChargingOutput(mo_getApiContext(), EVSE_ID_1, [] (MO_ChargeRate limit, unsigned int, void *userData) { + auto powerLimitOutput = reinterpret_cast(userData); - auto& model = context->getModel(); + powerLimitOutput(limit.power); + }, /*powerSupported*/ true, false, false, false, reinterpret_cast(powerLimitOutput)); +} - model.setBootService(std::unique_ptr( - new BootService(*context, filesystem))); +bool mo_setSmartChargingCurrentOutput(void (*currentLimitOutput)(float)) { + //convert and forward callback to `mo_setSmartChargingOutput()` + return mo_setSmartChargingOutput(mo_getApiContext(), EVSE_ID_1, [] (MO_ChargeRate limit, unsigned int, void *userData) { + auto currentLimitOutput = reinterpret_cast(userData); -#if MO_ENABLE_V201 - if (version.major == 2) { - model.setAvailabilityService(std::unique_ptr( - new AvailabilityService(*context, MO_NUM_EVSEID))); - model.setVariableService(std::unique_ptr( - new VariableService(*context, filesystem))); - model.setTransactionService(std::unique_ptr( - new TransactionService(*context, filesystem, MO_NUM_EVSEID))); - model.setRemoteControlService(std::unique_ptr( - new RemoteControlService(*context, MO_NUM_EVSEID))); - model.setResetServiceV201(std::unique_ptr( - new Ocpp201::ResetService(*context))); - } else -#endif - { - model.setTransactionStore(std::unique_ptr( - new TransactionStore(MO_NUMCONNECTORS, filesystem))); - model.setConnectorService(std::unique_ptr( - new ConnectorService(*context, MO_NUMCONNECTORS, filesystem))); - auto connectors = makeVector>("v16.ConnectorBase.Connector"); - for (unsigned int connectorId = 0; connectorId < MO_NUMCONNECTORS; connectorId++) { - connectors.emplace_back(new Connector(*context, filesystem, connectorId)); - } - model.setConnectors(std::move(connectors)); - -#if MO_ENABLE_LOCAL_AUTH - model.setAuthorizationService(std::unique_ptr( - new AuthorizationService(*context, filesystem))); -#endif //MO_ENABLE_LOCAL_AUTH - -#if MO_ENABLE_RESERVATION - model.setReservationService(std::unique_ptr( - new ReservationService(*context, MO_NUMCONNECTORS))); -#endif + currentLimitOutput(limit.current); + }, false, /*currentSupported*/ true, false, false, reinterpret_cast(currentLimitOutput)); +} - model.setResetService(std::unique_ptr( - new ResetService(*context))); +bool mo_setSmartChargingOutput(MO_Context *ctx, unsigned int evseId, void (*chargingLimitOutput)(MO_ChargeRate, unsigned int, void*), bool powerSupported, bool currentSupported, bool phases3to1Supported, bool phaseSwitchingSupported, void *userData) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; } + auto context = mo_getContext2(ctx); - model.setHeartbeatService(std::unique_ptr( - new HeartbeatService(*context))); + MicroOcpp::SmartChargingService *scService = nullptr; -#if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS - std::unique_ptr certStore = makeCertificateStoreMbedTLS(filesystem); - if (certStore) { - model.setCertificateService(std::unique_ptr( - new CertificateService(*context))); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + scService = context->getModel16().getSmartChargingService(); } - if (certStore && model.getCertificateService()) { - model.getCertificateService()->setCertificateStore(std::move(certStore)); + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + scService = context->getModel201().getSmartChargingService(); } -#endif + #endif -#if !defined(MO_CUSTOM_UPDATER) -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS - model.setFirmwareService( - makeDefaultFirmwareService(*context)); //instantiate FW service + ESP installation routine -#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP8266) - model.setFirmwareService( - makeDefaultFirmwareService(*context)); //instantiate FW service + ESP installation routine -#endif //MO_PLATFORM -#endif //!defined(MO_CUSTOM_UPDATER) - -#if !defined(MO_CUSTOM_DIAGNOSTICS) -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS - model.setDiagnosticsService( - makeDefaultDiagnosticsService(*context, filesystem)); //instantiate Diag service + ESP hardware diagnostics -#elif MO_ENABLE_MBEDTLS - model.setDiagnosticsService( - makeDefaultDiagnosticsService(*context, filesystem)); //instantiate Diag service -#endif //MO_PLATFORM -#endif //!defined(MO_CUSTOM_DIAGNOSTICS) - -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266)) - setOnResetExecute(makeDefaultResetFn()); -#endif + if (!scService) { + MO_DBG_ERR("OOM"); + return false; + } - model.getBootService()->setChargePointCredentials(bootNotificationCredentials); + return scService->setSmartChargingOutput(evseId, chargingLimitOutput, powerSupported, currentSupported, phases3to1Supported, phaseSwitchingSupported, userData); +} - auto credsJson = model.getBootService()->getChargePointCredentials(); - if (model.getFirmwareService() && credsJson && credsJson->containsKey("firmwareVersion")) { - model.getFirmwareService()->setBuildNumber((*credsJson)["firmwareVersion"]); +//Finalize setup. After this, the library is ready to be used and mo_loop() can be run +bool mo_setup() { + if (!g_context) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; } - credsJson.reset(); - configuration_load(); + return g_context->setup(); +} -#if MO_ENABLE_V201 - if (version.major == 2) { - model.getVariableService()->load(); +//Run library routines. To be called in the main loop (e.g. place it inside `loop()`) +void mo_loop() { + if (!g_context) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; } -#endif //MO_ENABLE_V201 + g_context->loop(); +} - MO_DBG_INFO("initialized MicroOcpp v" MO_VERSION " running OCPP %i.%i.%i", version.major, version.minor, version.patch); +//Transaction management. +bool mo_beginTransaction(const char *idTag) { + return mo_beginTransaction2(mo_getApiContext(), EVSE_ID_1, idTag); } -void mocpp_deinitialize() { +bool mo_beginTransaction2(MO_Context *ctx, unsigned int evseId, const char *idTag) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; - if (context) { - //release bootstats recovery mechanism - BootStats bootstats; - BootService::loadBootStats(filesystem, bootstats); - if (bootstats.lastBootSuccess != bootstats.bootNr) { - MO_DBG_DEBUG("boot success timer override"); - bootstats.lastBootSuccess = bootstats.bootNr; - BootService::storeBootStats(filesystem, bootstats); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); + return false; } + success = txSvcEvse->beginTransaction(idTag); } - - delete context; - context = nullptr; - -#ifndef MO_CUSTOM_WS - delete connection; - connection = nullptr; - delete webSocket; - webSocket = nullptr; -#endif + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + success = mo_authorizeTransaction2(ctx, evseId, idTag, MO_IdTokenType_ISO14443); + } + #endif - filesystem.reset(); + return success; +} - configuration_deinit(); +//Same as `mo_beginTransaction()`, but skip authorization. `parentIdTag` can be NULL +bool mo_beginTransaction_authorized(const char *idTag, const char *parentIdTag) { + return mo_beginTransaction_authorized2(mo_getApiContext(), EVSE_ID_1, idTag, parentIdTag); +} -#if !MO_HEAP_PROFILER_EXTERNAL_CONTROL - MO_MEM_DEINIT(); -#endif +bool mo_beginTransaction_authorized2(MO_Context *ctx, unsigned int evseId, const char *idTag, const char *parentIdTag) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); - MO_DBG_DEBUG("deinitialized OCPP\n"); -} + bool success = false; -void mocpp_loop() { - if (!context) { - MO_DBG_WARN("need to call mocpp_initialize before"); - return; + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); + return false; + } + success = txSvcEvse->beginTransaction_authorized(idTag, parentIdTag); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + success = mo_authorizeTransaction3(ctx, evseId, idTag, MO_IdTokenType_ISO14443, false, parentIdTag); } + #endif - context->loop(); + return success; } -bool beginTransaction(const char *idTag, unsigned int connectorId) { - if (!context) { +#if MO_ENABLE_V201 +bool mo_authorizeTransaction(const char *idToken) { + return mo_authorizeTransaction2(mo_getApiContext(), EVSE_ID_1, idToken, MO_IdTokenType_ISO14443); +} + +bool mo_authorizeTransaction2(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + auto context = mo_getContext2(ctx); + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + success = mo_beginTransaction2(ctx, evseId, idToken); + } + #endif #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) { - MO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", MO_IDTOKEN_LEN_MAX); - return false; - } - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); - } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); return false; } - return evse->beginAuthorization(idTag, true); + success = txSvcEvse->beginAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), /*validateIdToken*/ true); } #endif - if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { - MO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); - return false; - } - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return false; - } - - return connector->beginTransaction(idTag) != nullptr; + return success; } -bool beginTransaction_authorized(const char *idTag, const char *parentIdTag, unsigned int connectorId) { - if (!context) { +bool mo_authorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type, bool validateIdToken, const char *groupIdToken) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + auto context = mo_getContext2(ctx); - #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) { - MO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", MO_IDTOKEN_LEN_MAX); - return false; - } - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (validateIdToken) { + success = mo_beginTransaction2(ctx, evseId, idToken); + } else { + success = mo_beginTransaction_authorized2(ctx, evseId, idToken, nullptr); } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); return false; } - return evse->beginAuthorization(idTag, false); + success = txSvcEvse->beginAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), validateIdToken, groupIdToken); } #endif - if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX || - (parentIdTag && strnlen(parentIdTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX)) { - MO_DBG_ERR("(parent)idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); - return false; - } - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return false; - } - - return connector->beginTransaction_authorized(idTag, parentIdTag) != nullptr; + return success; } -bool endTransaction(const char *idTag, const char *reason, unsigned int connectorId) { - if (!context) { +//End the transaction process if idTag is authorized +bool mo_endTransaction(const char *idTag, const char *reason) { + return mo_endTransaction2(mo_getApiContext(), EVSE_ID_1, idTag, reason); +} + +bool mo_endTransaction2(MO_Context *ctx, unsigned int evseId, const char *idTag, const char *reason) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + auto context = mo_getContext2(ctx); - #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) { - MO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", MO_IDTOKEN_LEN_MAX); - return false; - } - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); - } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); return false; } - return evse->endAuthorization(idTag, true); + success = txSvcEvse->endTransaction(idTag, reason); } #endif - - bool res = false; - if (isTransactionActive(connectorId) && getTransactionIdTag(connectorId)) { - //end transaction now if either idTag is nullptr (i.e. force stop) or the idTag matches beginTransaction - if (!idTag || !strcmp(idTag, getTransactionIdTag(connectorId))) { - res = endTransaction_authorized(idTag, reason, connectorId); + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!reason || !strcmp(reason, "Local")) { + success = mo_deauthorizeTransaction2(ctx, evseId, idTag, MO_IdTokenType_ISO14443); + } else if (!strcmp(reason, "PowerLoss")) { + success = mo_abortTransaction2(ctx, evseId, MO_TxStoppedReason_PowerLoss, MO_TxEventTriggerReason_AbnormalCondition); + } else if (!strcmp(reason, "Reboot")) { + success = mo_abortTransaction2(ctx, evseId, MO_TxStoppedReason_Reboot, MO_TxEventTriggerReason_ResetCommand); + } else if (idTag) { + success = mo_deauthorizeTransaction2(ctx, evseId, idTag, MO_IdTokenType_ISO14443); } else { - auto tx = getTransaction(connectorId); - const char *parentIdTag = tx->getParentIdTag(); - if (strlen(parentIdTag) > 0) - { - // We have a parent ID tag, so we need to check if this new card also has one - auto authorize = makeRequest(new Ocpp16::Authorize(context->getModel(), idTag)); - auto idTag_capture = makeString("MicroOcpp.cpp", idTag); - auto reason_capture = makeString("MicroOcpp.cpp", reason ? reason : ""); - authorize->setOnReceiveConfListener([idTag_capture, reason_capture, connectorId, tx] (JsonObject response) { - JsonObject idTagInfo = response["idTagInfo"]; - - if (strcmp("Accepted", idTagInfo["status"] | "UNDEFINED")) { - //Authorization rejected, do nothing - MO_DBG_DEBUG("Authorize rejected (%s), continue transaction", idTag_capture.c_str()); - auto connector = context->getModel().getConnector(connectorId); - if (connector) { - connector->updateTxNotification(TxNotification_AuthorizationRejected); - } - return; - } - if (idTagInfo.containsKey("parentIdTag") && !strcmp(idTagInfo["parenIdTag"], tx->getParentIdTag())) - { - endTransaction_authorized(idTag_capture.c_str(), reason_capture.empty() ? (const char*)nullptr : reason_capture.c_str(), connectorId); - } - }); - - authorize->setOnTimeoutListener([idTag_capture, connectorId] () { - //Authorization timed out, do nothing - MO_DBG_DEBUG("Authorization timeout (%s), continue transaction", idTag_capture.c_str()); - auto connector = context->getModel().getConnector(connectorId); - if (connector) { - connector->updateTxNotification(TxNotification_AuthorizationTimeout); - } - }); - - auto authorizationTimeoutInt = declareConfiguration(MO_CONFIG_EXT_PREFIX "AuthorizationTimeout", 20); - authorize->setTimeout(authorizationTimeoutInt && authorizationTimeoutInt->getInt() > 0 ? authorizationTimeoutInt->getInt() * 1000UL : 20UL * 1000UL); - - context->initiateRequest(std::move(authorize)); - res = true; - } else { - MO_DBG_INFO("endTransaction: idTag doesn't match"); - (void)0; - } + success = mo_abortTransaction2(ctx, evseId, MO_TxStoppedReason_Other, MO_TxEventTriggerReason_AbnormalCondition); } } - return res; + #endif + + return success; } -bool endTransaction_authorized(const char *idTag, const char *reason, unsigned int connectorId) { - if (!context) { +//Same as `endTransaction()`, but skip authorization +bool mo_endTransaction_authorized(const char *idTag, const char *reason) { + return mo_endTransaction_authorized2(mo_getApiContext(), EVSE_ID_1, idTag, reason); +} + +bool mo_endTransaction_authorized2(MO_Context *ctx, unsigned int evseId, const char *idTag, const char *reason) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + auto context = mo_getContext2(ctx); - #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) { - MO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", MO_IDTOKEN_LEN_MAX); + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); return false; } - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); - } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); - return false; + success = txSvcEvse->endTransaction_authorized(idTag, reason); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!reason || !strcmp(reason, "Local")) { + success = mo_deauthorizeTransaction3(ctx, evseId, idTag, MO_IdTokenType_ISO14443, false, NULL); + } else if (!strcmp(reason, "PowerLoss")) { + success = mo_abortTransaction2(ctx, evseId, MO_TxStoppedReason_PowerLoss, MO_TxEventTriggerReason_AbnormalCondition); + } else if (!strcmp(reason, "Reboot")) { + success = mo_abortTransaction2(ctx, evseId, MO_TxStoppedReason_Reboot, MO_TxEventTriggerReason_ResetCommand); + } else if (idTag) { + success = mo_deauthorizeTransaction3(ctx, evseId, idTag, MO_IdTokenType_ISO14443, false, NULL); + } else { + success = mo_abortTransaction2(ctx, evseId, MO_TxStoppedReason_Other, MO_TxEventTriggerReason_AbnormalCondition); } - return evse->endAuthorization(idTag, false); } #endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return false; - } - auto res = isTransactionActive(connectorId); - connector->endTransaction(idTag, reason); - return res; + return success; } -bool isTransactionActive(unsigned int connectorId) { - if (!context) { +#if MO_ENABLE_V201 +//Same as mo_endTransaction +bool mo_deauthorizeTransaction(const char *idToken) { + return mo_deauthorizeTransaction2(mo_getApiContext(), EVSE_ID_1, idToken, MO_IdTokenType_ISO14443); +} + +bool mo_deauthorizeTransaction2(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + auto context = mo_getContext2(ctx); + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + success = mo_endTransaction2(ctx, evseId, idToken, "Local"); + } + #endif #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); - } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); return false; } - return evse->getTransaction() && evse->getTransaction()->active; + success = txSvcEvse->endAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), /*validateIdToken*/ true, nullptr); } #endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return false; - } - auto& tx = connector->getTransaction(); - return tx ? tx->isActive() : false; + return success; } -bool isTransactionRunning(unsigned int connectorId) { - if (!context) { +bool mo_deauthorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type, bool validateIdToken, const char *groupIdToken) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } + auto context = mo_getContext2(ctx); - #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (validateIdToken) { + success = mo_endTransaction2(ctx, evseId, idToken, "Local"); + } else { + success = mo_endTransaction_authorized2(ctx, evseId, idToken, "Local"); } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); return false; } - return evse->getTransaction() && evse->getTransaction()->started && !evse->getTransaction()->stopped; + success = txSvcEvse->endAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), validateIdToken, groupIdToken); } #endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return false; - } - auto& tx = connector->getTransaction(); - return tx ? tx->isRunning() : false; + return success; } -const char *getTransactionIdTag(unsigned int connectorId) { - if (!context) { +//Force transaction stop +bool mo_abortTransaction(MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReason stopTrigger) { + return mo_abortTransaction2(mo_getApiContext(), EVSE_ID_1, stoppedReason, stopTrigger); +} + +bool mo_abortTransaction2(MO_Context *ctx, unsigned int evseId, MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReason stopTrigger) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return nullptr; + return false; } - - #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + const char *reason = ""; + switch (stoppedReason) { + case MO_TxStoppedReason_EmergencyStop: + reason = "EmergencyStop"; + break; + case MO_TxStoppedReason_PowerLoss: + reason = "PowerLoss"; + break; + case MO_TxStoppedReason_Reboot: + reason = "Reboot"; + break; + default: + reason = "Other"; + break; } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); - return nullptr; + success = mo_endTransaction_authorized2(ctx, evseId, NULL, reason); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("OOM"); + return false; } - return evse->getTransaction() ? evse->getTransaction()->idToken.get() : nullptr; + success = txSvcEvse->abortTransaction(stoppedReason, stopTrigger); } #endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return nullptr; - } - auto& tx = connector->getTransaction(); - return tx ? tx->getIdTag() : nullptr; + return success; } +#endif //MO_ENABLE_V201 -std::shared_ptr mocpp_undefinedTx; +//Returns if according to OCPP, the EVSE is allowed to close provide charge now +bool mo_ocppPermitsCharge() { + return mo_ocppPermitsCharge2(mo_getApiContext(), EVSE_ID_1); +} -std::shared_ptr& getTransaction(unsigned int connectorId) { - if (!context) { - MO_DBG_WARN("OCPP uninitialized"); - return mocpp_undefinedTx; +bool mo_ocppPermitsCharge2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; } - #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - MO_DBG_ERR("only supported in v16"); - return mocpp_undefinedTx; + auto context = mo_getContext2(ctx); + + bool res = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + res = txSvcEvse->ocppPermitsCharge(); } #endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return mocpp_undefinedTx; + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + res = txSvcEvse->ocppPermitsCharge(); } - return connector->getTransaction(); + #endif + + return res; } -#if MO_ENABLE_V201 -Ocpp201::Transaction *getTransactionV201(unsigned int evseId) { - if (!context) { +//Get information about the current Transaction lifecycle +bool mo_isTransactionActive() { + return mo_isTransactionActive2(mo_getApiContext(), EVSE_ID_1); +} + +bool mo_isTransactionActive2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return nullptr; + return false; } + auto context = mo_getContext2(ctx); - if (context->getVersion().major != 2) { - MO_DBG_ERR("only supported in v201"); - return nullptr; - } + bool res = false; - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(evseId); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->isActive()); } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); - return nullptr; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->active); } - return evse->getTransaction(); + #endif + + return res; } -#endif //MO_ENABLE_V201 -bool ocppPermitsCharge(unsigned int connectorId) { - if (!context) { - MO_DBG_WARN("OCPP uninitialized"); +bool mo_isTransactionRunning() { + return mo_isTransactionRunning2(mo_getApiContext(), EVSE_ID_1); +} + +bool mo_isTransactionRunning2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); - } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); + auto context = mo_getContext2(ctx); + + bool res = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); return false; } - return evse->ocppPermitsCharge(); + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->isRunning()); } -#endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return false; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->started && !tx->stopped); } - return connector->ocppPermitsCharge(); + #endif + + return res; } -ChargePointStatus getChargePointStatus(unsigned int connectorId) { - if (!context) { - MO_DBG_WARN("OCPP uninitialized"); - return ChargePointStatus_UNDEFINED; - } -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto availabilityService = context->getModel().getAvailabilityService()) { - if (auto evse = availabilityService->getEvse(connectorId)) { - return evse->getStatus(); - } - } - } -#endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return ChargePointStatus_UNDEFINED; - } - return connector->getStatus(); +/* + * Get the idTag which has been used to start the transaction. If no transaction process is + * running, this function returns nullptr + */ +const char *mo_getTransactionIdTag() { + return mo_getTransactionIdTag2(mo_getApiContext(), EVSE_ID_1); } -void setConnectorPluggedInput(std::function pluggedInput, unsigned int connectorId) { - if (!context) { +const char *mo_getTransactionIdTag2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return nullptr; } -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto availabilityService = context->getModel().getAvailabilityService()) { - if (auto evse = availabilityService->getEvse(connectorId)) { - evse->setConnectorPluggedInput(pluggedInput); - } + auto context = mo_getContext2(ctx); + + const char *res = nullptr; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return nullptr; } - if (auto txService = context->getModel().getTransactionService()) { - if (auto evse = txService->getEvse(connectorId)) { - evse->setConnectorPluggedInput(pluggedInput); - } + if (auto tx = txSvcEvse->getTransaction()) { + res = tx->getIdTag(); } - return; } -#endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return nullptr; + } + if (auto tx = txSvcEvse->getTransaction()) { + res = tx->idToken.get(); + } } - connector->setConnectorPluggedInput(pluggedInput); + #endif + + return res && *res ? res : nullptr; } -void setEnergyMeterInput(std::function energyInput, unsigned int connectorId) { - if (!context) { +#if MO_ENABLE_V16 +int mo_v16_getTransactionId() { + return mo_v16_getTransactionId2(mo_getApiContext(), EVSE_ID_1); +} + +int mo_v16_getTransactionId2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return -1; } + auto context = mo_getContext2(ctx); + + int res = -1; + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return -1; + } + if (auto tx = txSvcEvse->getTransaction()) { + res = tx->getTransactionId(); + } + } + #endif #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - addMeterValueInput([energyInput] () {return static_cast(energyInput());}, "Energy.Active.Import.Register", "Wh", nullptr, nullptr, connectorId); - return; + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_v16_getTransactionId not supported with OCPP 2.0.1"); } #endif - SampledValueProperties meterProperties; - meterProperties.setMeasurand("Energy.Active.Import.Register"); - meterProperties.setUnit("Wh"); - auto mvs = std::unique_ptr>>( - new SampledValueSamplerConcrete>( - meterProperties, - [energyInput] (ReadingContext) {return energyInput();} - )); - addMeterValueInput(std::move(mvs), connectorId); + return res; +} +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 +const char *mo_v201_getTransactionId() { + return mo_v201_getTransactionId2(mo_getApiContext(), EVSE_ID_1); } -void setPowerMeterInput(std::function powerInput, unsigned int connectorId) { - if (!context) { +const char *mo_v201_getTransactionId2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return nullptr; } + auto context = mo_getContext2(ctx); + const char *res = nullptr; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + MO_DBG_ERR("mo_v201_getTransactionId not supported with OCPP 2.0.1"); + } + #endif #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - addMeterValueInput([powerInput] () {return static_cast(powerInput());}, "Power.Active.Import", "W", nullptr, nullptr, connectorId); - return; + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return nullptr; + } + if (auto tx = txSvcEvse->getTransaction()) { + res = tx->transactionId; + } } #endif - SampledValueProperties meterProperties; - meterProperties.setMeasurand("Power.Active.Import"); - meterProperties.setUnit("W"); - auto mvs = std::unique_ptr>>( - new SampledValueSamplerConcrete>( - meterProperties, - [powerInput] (ReadingContext) {return powerInput();} - )); - addMeterValueInput(std::move(mvs), connectorId); + return res; } +#endif //MO_ENABLE_V201 -void setSmartChargingPowerOutput(std::function chargingLimitOutput, unsigned int connectorId) { - if (!context) { +MO_ChargePointStatus mo_getChargePointStatus() { + return mo_getChargePointStatus2(mo_getApiContext(), EVSE_ID_1); +} + +MO_ChargePointStatus mo_getChargePointStatus2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; - } - if (!context->getModel().getConnector(connectorId)) { - MO_DBG_ERR("could not find connector"); - return; + return MO_ChargePointStatus_UNDEFINED; } + auto context = mo_getContext2(ctx); - if (chargingLimitOutput) { - setSmartChargingOutput([chargingLimitOutput] (float power, float current, int nphases) -> void { - chargingLimitOutput(power); - }, connectorId); - } else { - setSmartChargingOutput(nullptr, connectorId); - } + MO_ChargePointStatus res = MO_ChargePointStatus_UNDEFINED; - if (auto scService = context->getModel().getSmartChargingService()) { - if (chargingLimitOutput) { - scService->updateAllowedChargingRateUnit(true, false); - } else { - scService->updateAllowedChargingRateUnit(false, false); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return MO_ChargePointStatus_UNDEFINED; + } + res = availSvcEvse->getStatus(); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto availSvc = context->getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return MO_ChargePointStatus_UNDEFINED; } + res = availSvcEvse->getStatus(); } + #endif + + return res; } -void setSmartChargingCurrentOutput(std::function chargingLimitOutput, unsigned int connectorId) { - if (!context) { +//Input if EV is ready to charge (= J1772 State C) +void mo_setEvReadyInput(bool (*evReadyInput)()) { + //convert and forward callback to `mo_setEvReadyInput2()` + mo_setEvReadyInput2(mo_getApiContext(), EVSE_ID_1, [] (unsigned int, void *userData) { + auto evReadyInput = reinterpret_cast(userData); + return evReadyInput(); + }, reinterpret_cast(evReadyInput)); +} + +void mo_setEvReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*evReadyInput2)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - if (!context->getModel().getConnector(connectorId)) { - MO_DBG_ERR("could not find connector"); - return; - } + auto context = mo_getContext2(ctx); - if (chargingLimitOutput) { - setSmartChargingOutput([chargingLimitOutput] (float power, float current, int nphases) -> void { - chargingLimitOutput(current); - }, connectorId); - } else { - setSmartChargingOutput(nullptr, connectorId); - } + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + availSvcEvse->setEvReadyInput(evReadyInput2, userData); - if (auto scService = context->getModel().getSmartChargingService()) { - if (chargingLimitOutput) { - scService->updateAllowedChargingRateUnit(false, true); - } else { - scService->updateAllowedChargingRateUnit(false, false); + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setEvReadyInput(evReadyInput2, userData); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; } + txSvcEvse->setEvReadyInput(evReadyInput2, userData); } + #endif } -void setSmartChargingOutput(std::function chargingLimitOutput, unsigned int connectorId) { - if (!context) { +//Input if EVSE allows charge (= PWM signal on) +void mo_setEvseReadyInput(bool (*evseReadyInput)()) { + //convert and forward callback to `mo_setEvseReadyInput2()` + mo_setEvseReadyInput2(mo_getApiContext(), EVSE_ID_1, [] (unsigned int, void *userData) { + auto evseReadyInput = reinterpret_cast(userData); + return evseReadyInput(); + }, reinterpret_cast(evseReadyInput)); +} + +void mo_setEvseReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*evseReadyInput2)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - if (!context->getModel().getConnector(connectorId)) { - MO_DBG_ERR("could not find connector"); - return; - } + auto context = mo_getContext2(ctx); - auto& model = context->getModel(); - if (!model.getSmartChargingService() && chargingLimitOutput) { - model.setSmartChargingService(std::unique_ptr( - new SmartChargingService(*context, filesystem, MO_NUMCONNECTORS))); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + availSvcEvse->setEvseReadyInput(evseReadyInput2, userData); } - - if (auto scService = context->getModel().getSmartChargingService()) { - scService->setSmartChargingOutput(connectorId, chargingLimitOutput); - if (chargingLimitOutput) { - scService->updateAllowedChargingRateUnit(true, true); - } else { - scService->updateAllowedChargingRateUnit(false, false); + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; } + txSvcEvse->setEvseReadyInput(evseReadyInput2, userData); } + #endif } -void setEvReadyInput(std::function evReadyInput, unsigned int connectorId) { - if (!context) { +//Input for Error codes (please refer to OCPP 1.6, Edit2, p. 71 and 72 for valid error codes) +bool mo_addErrorCodeInput(const char* (*errorCodeInput)()) { + if (!g_context) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return false; } -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto txService = context->getModel().getTransactionService()) { - if (auto evse = txService->getEvse(connectorId)) { - evse->setEvReadyInput(evReadyInput); - } + auto context = g_context; + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(EVSE_ID_1) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return false; } - return; + + MO_ErrorDataInput errorDataInput; + mo_ErrorDataInput_init(&errorDataInput); + errorDataInput.userData = reinterpret_cast(errorCodeInput); + errorDataInput.getErrorData = [] (unsigned int, void *userData) { + auto errorCodeInput = reinterpret_cast(userData); + + MO_ErrorData errorData; + mo_ErrorData_init(&errorData); + mo_ErrorData_setErrorCode(&errorData, errorCodeInput()); + return errorData; + }; + + success = availSvcEvse->addErrorDataInput(errorDataInput); } -#endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto availSvc = context->getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(EVSE_ID_1) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + + //error codes not really supported in OCPP 2.0.1, just Faulted status + MicroOcpp::Ocpp201::FaultedInput faultedInput; + faultedInput.userData = reinterpret_cast(errorCodeInput); + faultedInput.isFaulted = [] (unsigned int evseId, void *userData) { + auto errorCodeInput = reinterpret_cast(userData); + + return errorCodeInput() != nullptr; + }; + + success = availSvcEvse->addFaultedInput(faultedInput); } - connector->setEvReadyInput(evReadyInput); + #endif + + return success; } -void setEvseReadyInput(std::function evseReadyInput, unsigned int connectorId) { - if (!context) { +#if MO_ENABLE_V16 +//Input for Error codes + additional error data. See "Availability.h" for more docs +bool mo_v16_addErrorDataInput(MO_Context *ctx, unsigned int evseId, MO_ErrorData (*errorData)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return false; } -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto txService = context->getModel().getTransactionService()) { - if (auto evse = txService->getEvse(connectorId)) { - evse->setEvseReadyInput(evseReadyInput); - } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return false; } - return; + + MO_ErrorDataInput errorDataInput; + mo_ErrorDataInput_init(&errorDataInput); + errorDataInput.getErrorData = errorData; + errorDataInput.userData = userData; + + success = availSvcEvse->addErrorDataInput(errorDataInput); } -#endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_v16_addErrorDataInput not supported with OCPP 2.0.1"); } - connector->setEvseReadyInput(evseReadyInput); + #endif + + return success; } +#endif //MO_ENABLE_V16 -void addErrorCodeInput(std::function errorCodeInput, unsigned int connectorId) { - if (!context) { +#if MO_ENABLE_V201 +//Input to set EVSE into Faulted state +bool mo_v201_addFaultedInput(MO_Context *ctx, unsigned int evseId, bool (*faulted)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; - } - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; + return false; } - connector->addErrorCodeInput(errorCodeInput); -} + auto context = mo_getContext2(ctx); -void addErrorDataInput(std::function errorDataInput, unsigned int connectorId) { - if (!context) { - MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + MO_DBG_ERR("mo_v201_addFaultedInput not supported with OCPP 1.6"); } - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto availSvc = context->getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + + //error codes not really supported in OCPP 2.0.1, just Faulted status + MicroOcpp::Ocpp201::FaultedInput faultedInput; + faultedInput.isFaulted = faulted; + faultedInput.userData = userData; + + success = availSvcEvse->addFaultedInput(faultedInput); } - connector->addErrorDataInput(errorDataInput); + #endif + + return success; } +#endif //MO_ENABLE_V201 -void addMeterValueInput(std::function valueInput, const char *measurand, const char *unit, const char *location, const char *phase, unsigned int connectorId) { - if (!context) { +bool mo_addMeterValueInputInt(int32_t (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_Int); + mInput.getInt = meterInput; + mInput.measurand = measurand; + mInput.unit = unit; + mInput.location = location; + mInput.phase = phase; + return mo_addMeterValueInput(mo_getApiContext(), EVSE_ID_1, mInput); +} + +bool mo_addMeterValueInputInt2(MO_Context *ctx, unsigned int evseId, int32_t (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_IntWithArgs); + mInput.getInt2 = meterInput; + mInput.measurand = measurand; + mInput.unit = unit; + mInput.location = location; + mInput.phase = phase; + mInput.user_data = userData; + return mo_addMeterValueInput(ctx, evseId, mInput); +} + +bool mo_addMeterValueInputFloat(float (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_Float); + mInput.getFloat = meterInput; + mInput.measurand = measurand; + mInput.unit = unit; + mInput.location = location; + mInput.phase = phase; + return mo_addMeterValueInput(mo_getApiContext(), EVSE_ID_1, mInput); +} + +bool mo_addMeterValueInputFloat2(MO_Context *ctx, unsigned int evseId, float (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_FloatWithArgs); + mInput.getFloat2 = meterInput; + mInput.measurand = measurand; + mInput.unit = unit; + mInput.location = location; + mInput.phase = phase; + mInput.user_data = userData; + return mo_addMeterValueInput(ctx, evseId, mInput); +} + +bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_String); + mInput.getString = meterInput; + mInput.measurand = measurand; + mInput.unit = unit; + mInput.location = location; + mInput.phase = phase; + return mo_addMeterValueInput(mo_getApiContext(), EVSE_ID_1, mInput); +} +bool mo_addMeterValueInputString2(MO_Context *ctx, unsigned int evseId, int (*meterInput)(char *buf, size_t size, MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { + MO_MeterInput mInput; + mo_MeterInput_init(&mInput, MO_MeterInputType_StringWithArgs); + mInput.getString2 = meterInput; + mInput.measurand = measurand; + mInput.unit = unit; + mInput.location = location; + mInput.phase = phase; + mInput.user_data = userData; + return mo_addMeterValueInput(ctx, evseId, mInput); +} + +//Input for further MeterValue types with more options. See "MeterValue.h" for how to use it +bool mo_addMeterValueInput(MO_Context *ctx, unsigned int evseId, MO_MeterInput mInput) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return false; } + auto context = mo_getContext2(ctx); - if (!valueInput) { - MO_DBG_ERR("value undefined"); - return; - } + bool success = false; - if (!measurand) { - measurand = "Energy.Active.Import.Register"; - MO_DBG_WARN("measurand unspecified; assume %s", measurand); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto meterSvc = context->getModel16().getMeteringService(); + auto meterSvcEvse = meterSvc ? meterSvc->getEvse(evseId) : nullptr; + if (!meterSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + success = meterSvcEvse->addMeterInput(mInput); } - + #endif #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - auto& model = context->getModel(); - if (!model.getMeteringServiceV201()) { - model.setMeteringServiceV201(std::unique_ptr( - new Ocpp201::MeteringService(context->getModel(), MO_NUM_EVSEID))); - } - if (auto mEvse = model.getMeteringServiceV201()->getEvse(connectorId)) { - - Ocpp201::SampledValueProperties properties; - properties.setMeasurand(measurand); //mandatory for MO - - if (unit) - properties.setUnitOfMeasureUnit(unit); - if (location) - properties.setLocation(location); - if (phase) - properties.setPhase(phase); - - mEvse->addMeterValueInput([valueInput] (ReadingContext) {return static_cast(valueInput());}, properties); - } else { - MO_DBG_ERR("inalid arg"); + if (context->getOcppVersion() == MO_OCPP_V201) { + auto meterSvc = context->getModel201().getMeteringService(); + auto meterSvcEvse = meterSvc ? meterSvc->getEvse(evseId) : nullptr; + if (!meterSvcEvse) { + MO_DBG_ERR("init failure"); + return false; } - return; + success = meterSvcEvse->addMeterInput(mInput); } #endif - SampledValueProperties properties; - properties.setMeasurand(measurand); //mandatory for MO - - if (unit) - properties.setUnit(unit); - if (location) - properties.setLocation(location); - if (phase) - properties.setPhase(phase); + return success; +} - auto valueSampler = std::unique_ptr>>( - new MicroOcpp::SampledValueSamplerConcrete>( - properties, - [valueInput] (ReadingContext) {return valueInput();})); - addMeterValueInput(std::move(valueSampler), connectorId); +void mo_setOccupiedInput(bool (*occupied)()) { + //convert and forward callback to `mo_setOccupiedInput2()` + mo_setOccupiedInput2(mo_getApiContext(), EVSE_ID_1, [] (unsigned int, void *userData) { + auto occupied = reinterpret_cast(userData); + return occupied(); + }, reinterpret_cast(occupied)); } -void addMeterValueInput(std::unique_ptr valueInput, unsigned int connectorId) { - if (!context) { +void mo_setOccupiedInput2(MO_Context *ctx, unsigned int evseId, bool (*occupied2)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - MO_DBG_ERR("addMeterValueInput(std::unique_ptr...) not compatible with v201. Use addMeterValueInput(std::function...) instead"); - return; + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + availSvcEvse->setOccupiedInput(occupied2, userData); } #endif - auto& model = context->getModel(); - if (!model.getMeteringService()) { - model.setMeteringSerivce(std::unique_ptr( - new MeteringService(*context, MO_NUMCONNECTORS, filesystem))); + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto availSvc = context->getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + availSvcEvse->setOccupiedInput(occupied2, userData); } - model.getMeteringService()->addMeterValueSampler(connectorId, std::move(valueInput)); + #endif } -void setOccupiedInput(std::function occupied, unsigned int connectorId) { - if (!context) { +//Input if the charger is ready for StartTransaction +void mo_setStartTxReadyInput(bool (*startTxReady)()) { + //convert and forward callback to `mo_setStartTxReadyInput2()` + mo_setStartTxReadyInput2(mo_getApiContext(), EVSE_ID_1, [] (unsigned int, void *userData) { + auto startTxReady = reinterpret_cast(userData); + return startTxReady(); + }, reinterpret_cast(startTxReady)); +} + +void mo_setStartTxReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*startTxReady2)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto availabilityService = context->getModel().getAvailabilityService()) { - if (auto evse = availabilityService->getEvse(connectorId)) { - evse->setOccupiedInput(occupied); - } + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; } - return; + txSvcEvse->setStartTxReadyInput(startTxReady2, userData); } -#endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setStartTxReadyInput(startTxReady2, userData); } - connector->setOccupiedInput(occupied); + #endif } -void setStartTxReadyInput(std::function startTxReady, unsigned int connectorId) { - if (!context) { - MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; - } - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; - } - connector->setStartTxReadyInput(startTxReady); +//Input if the charger is ready for StopTransaction +void mo_setStopTxReadyInput(bool (*stopTxReady)()) { + //convert and forward callback to `mo_setStopTxReadyInput2()` + mo_setStopTxReadyInput2(mo_getApiContext(), EVSE_ID_1, [] (unsigned int, void *userData) { + auto stopTxReady = reinterpret_cast(userData); + return stopTxReady(); + }, reinterpret_cast(stopTxReady)); } -void setStopTxReadyInput(std::function stopTxReady, unsigned int connectorId) { - if (!context) { +void mo_setStopTxReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*stopTxReady2)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; - } - connector->setStopTxReadyInput(stopTxReady); -} + auto context = mo_getContext2(ctx); -void setTxNotificationOutput(std::function notificationOutput, unsigned int connectorId) { - if (!context) { - MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setStopTxReadyInput(stopTxReady2, userData); } + #endif #if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - MO_DBG_ERR("only supported in v16"); - return; + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setStopTxReadyInput(stopTxReady2, userData); } #endif - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; - } - connector->setTxNotificationOutput(notificationOutput); } -#if MO_ENABLE_V201 -void setTxNotificationOutputV201(std::function notificationOutput, unsigned int connectorId) { - if (!context) { +//Called when transaction state changes (see "TransactionDefs.h" for possible events). Transaction can be null +void mo_setTxNotificationOutput(void (*txNotificationOutput)(MO_TxNotification)) { + //convert and forward callback to `mo_setTxNotificationOutput2()` + mo_setTxNotificationOutput2(mo_getApiContext(), EVSE_ID_1, [] (MO_TxNotification txNotification, unsigned int, void *userData) { + auto txNotificationOutput = reinterpret_cast(userData); + txNotificationOutput(txNotification); + }, reinterpret_cast(txNotificationOutput)); +} + +void mo_setTxNotificationOutput2(MO_Context *ctx, unsigned int evseId, void (*txNotificationOutput2)(MO_TxNotification, unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } + auto context = mo_getContext2(ctx); - if (context->getVersion().major != 2) { - MO_DBG_ERR("only supported in v201"); - return; + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setTxNotificationOutput(txNotificationOutput2, userData); } - - TransactionService::Evse *evse = nullptr; - if (auto txService = context->getModel().getTransactionService()) { - evse = txService->getEvse(connectorId); - } - if (!evse) { - MO_DBG_ERR("could not find EVSE"); - return; + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + txSvcEvse->setTxNotificationOutput(txNotificationOutput2, userData); } - evse->setTxNotificationOutput(notificationOutput); + #endif } -#endif //MO_ENABLE_V201 #if MO_ENABLE_CONNECTOR_LOCK -void setOnUnlockConnectorInOut(std::function onUnlockConnectorInOut, unsigned int connectorId) { - if (!context) { +void mo_setOnUnlockConnector(MO_UnlockConnectorResult (*onUnlockConnector)()) { + //convert and forward callback to `mo_setOnUnlockConnector2()` + mo_setOnUnlockConnector2(mo_getApiContext(), EVSE_ID_1, [] (unsigned int, void *userData) { + auto onUnlockConnector = reinterpret_cast(userData); + return onUnlockConnector(); + }, reinterpret_cast(onUnlockConnector)); +} + +void mo_setOnUnlockConnector2(MO_Context *ctx, unsigned int evseId, MO_UnlockConnectorResult (*onUnlockConnector2)(unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - auto connector = context->getModel().getConnector(connectorId); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return; + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto rmtSvc = context->getModel16().getRemoteControlService(); + auto rmtSvcEvse = rmtSvc ? rmtSvc->getEvse(evseId) : nullptr; + if (!rmtSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + rmtSvcEvse->setOnUnlockConnector(onUnlockConnector2, userData); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto rmtSvc = context->getModel201().getRemoteControlService(); + auto rmtSvcEvse = rmtSvc ? rmtSvc->getEvse(evseId) : nullptr; + if (!rmtSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + rmtSvcEvse->setOnUnlockConnector(onUnlockConnector2, userData); } - connector->setOnUnlockConnector(onUnlockConnectorInOut); + #endif } #endif //MO_ENABLE_CONNECTOR_LOCK -bool isOperative(unsigned int connectorId) { - if (!context) { - MO_DBG_WARN("OCPP uninitialized"); - return true; //assume "true" as default state +bool mo_isOperative() { + return mo_isOperative2(mo_getApiContext(), EVSE_ID_1); +} + +bool mo_isOperative2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; } -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto availabilityService = context->getModel().getAvailabilityService()) { - auto chargePoint = availabilityService->getEvse(OCPP_ID_OF_CP); - auto connector = availabilityService->getEvse(connectorId); - if (!chargePoint || !connector) { - MO_DBG_ERR("could not find connector"); - return true; //assume "true" as default state - } - return chargePoint->isAvailable() && connector->isAvailable(); + auto context = mo_getContext2(ctx); + + bool result = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto availSvc = context->getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return false; } + result = availSvcEvse->isOperative(); } -#endif - auto& model = context->getModel(); - auto chargePoint = model.getConnector(OCPP_ID_OF_CP); - auto connector = model.getConnector(connectorId); - if (!chargePoint || !connector) { - MO_DBG_ERR("could not find connector"); - return true; //assume "true" as default state + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto availSvc = context->getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (!availSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + result = availSvcEvse->isAvailable(); } - return chargePoint->isOperative() && connector->isOperative(); + #endif + + return result; } -void setOnResetNotify(std::function onResetNotify) { - if (!context) { +bool mo_isConnected() { + return mo_isConnected2(mo_getApiContext()); +} +bool mo_isConnected2(MO_Context *ctx) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return false; } + auto context = mo_getContext2(ctx); -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto rService = context->getModel().getResetServiceV201()) { - rService->setNotifyReset([onResetNotify] (ResetType) {return onResetNotify(true);}); - } - return; + if (auto connection = context->getConnection()) { + return connection->isConnected(); + } else { + MO_DBG_ERR("WebSocket not set up"); + return false; } -#endif +} - if (auto rService = context->getModel().getResetService()) { - rService->setPreReset(onResetNotify); - } +bool mo_isAcceptedByCsms() { + return mo_isAcceptedByCsms2(mo_getApiContext()); } -void setOnResetExecute(std::function onResetExecute) { - if (!context) { +bool mo_isAcceptedByCsms2(MO_Context *ctx) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return false; } + auto context = mo_getContext2(ctx); -#if MO_ENABLE_V201 - if (context->getVersion().major == 2) { - if (auto rService = context->getModel().getResetServiceV201()) { - rService->setExecuteReset([onResetExecute] () {onResetExecute(true); return true;}); - } - return; + MicroOcpp::BootService *bootService = nullptr; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + bootService = context->getModel16().getBootService(); } -#endif + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + bootService = context->getModel201().getBootService(); + } + #endif - if (auto rService = context->getModel().getResetService()) { - rService->setExecuteReset(onResetExecute); + if (!bootService) { + MO_DBG_ERR("OOM"); + return false; } + + return (bootService->getRegistrationStatus() == MicroOcpp::RegistrationStatus::Accepted); } -FirmwareService *getFirmwareService() { - if (!context) { +int32_t mo_getUnixTime() { + return mo_getUnixTime2(mo_getApiContext()); +} + +int32_t mo_getUnixTime2(MO_Context *ctx) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return nullptr; + return 0; } + auto context = mo_getContext2(ctx); + auto& clock = context->getClock(); - auto& model = context->getModel(); - if (!model.getFirmwareService()) { - model.setFirmwareService(std::unique_ptr( - new FirmwareService(*context))); + int32_t unixTime = 0; + if (!clock.toUnixTime(clock.now(), unixTime)) { + unixTime = 0; } - return model.getFirmwareService(); + return unixTime; } -DiagnosticsService *getDiagnosticsService() { - if (!context) { +bool mo_setUnixTime(int32_t unixTime) { + return mo_setUnixTime2(mo_getApiContext(), unixTime); +} + +bool mo_setUnixTime2(MO_Context *ctx, int32_t unixTime) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return nullptr; + return false; } + auto context = mo_getContext2(ctx); - auto& model = context->getModel(); - if (!model.getDiagnosticsService()) { - model.setDiagnosticsService(std::unique_ptr( - new DiagnosticsService(*context))); + return context->getClock().setTime(unixTime); +} + +int32_t mo_getUptime() { + return mo_getUptime2(mo_getApiContext()); +} + +int32_t mo_getUptime2(MO_Context *ctx) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; } + auto context = mo_getContext2(ctx); - return model.getDiagnosticsService(); + return context->getClock().getUptimeInt(); } -#if MO_ENABLE_CERT_MGMT +int mo_getOcppVersion() { + return mo_getOcppVersion2(mo_getApiContext()); +} -void setCertificateStore(std::unique_ptr certStore) { - if (!context) { +int mo_getOcppVersion2(MO_Context *ctx) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return -1; + } + auto context = mo_getContext2(ctx); + + return context->getOcppVersion(); +} + +void mo_setOnResetExecute(void (*onResetExecute)()) { + if (!g_context) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } + auto context = g_context; - auto& model = context->getModel(); - if (!model.getCertificateService()) { - model.setCertificateService(std::unique_ptr( - new CertificateService(*context))); + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto rstSvc = context->getModel16().getResetService(); + if (!rstSvc) { + MO_DBG_ERR("init failure"); + return; + } + rstSvc->setExecuteReset([] (bool, void *userData) { + auto onResetExecute = reinterpret_cast(userData); + onResetExecute(); + return true; + }, reinterpret_cast(onResetExecute)); } - if (auto certService = model.getCertificateService()) { - certService->setCertificateStore(std::move(certStore)); - } else { - MO_DBG_ERR("OOM"); + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto rstSvc = context->getModel201().getResetService(); + if (!rstSvc) { + MO_DBG_ERR("init failure"); + return; + } + rstSvc->setExecuteReset(EVSE_ID_0, [] (unsigned int, void *userData) { + auto onResetExecute = reinterpret_cast(userData); + onResetExecute(); + return true; + }, reinterpret_cast(onResetExecute)); } + #endif } -#endif //MO_ENABLE_CERT_MGMT -Context *getOcppContext() { - return context; +#if MO_ENABLE_V16 +void mo_v16_setOnResetNotify(bool (*onResetNotify)(bool)) { + mo_v16_setOnResetNotify2(mo_getApiContext(), [] (bool isHard, void *userData) { + auto onResetNotify = reinterpret_cast(userData); + return onResetNotify(isHard); + }, reinterpret_cast(onResetNotify)); } -void setOnReceiveRequest(const char *operationType, OnReceiveReqListener onReceiveReq) { - if (!context) { +void mo_v16_setOnResetNotify2(MO_Context *ctx, bool (*onResetNotify2)(bool, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - if (!operationType) { - MO_DBG_ERR("invalid args"); - return; + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto rstSvc = context->getModel16().getResetService(); + if (!rstSvc) { + MO_DBG_ERR("init failure"); + return; + } + rstSvc->setNotifyReset(onResetNotify2, userData); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_v16_setOnResetNotify not supported with OCPP 2.0.1"); } - context->getOperationRegistry().setOnRequest(operationType, onReceiveReq); + #endif } -void setOnSendConf(const char *operationType, OnSendConfListener onSendConf) { - if (!context) { +void mo_v16_setOnResetExecute(void (*onResetExecute)(bool)) { + mo_v16_setOnResetExecute2(mo_getApiContext(), [] (bool isHard, void *userData) { + auto onResetExecute = reinterpret_cast(userData); + return onResetExecute(isHard); + }, reinterpret_cast(onResetExecute)); +} +void mo_v16_setOnResetExecute2(MO_Context *ctx, bool (*onResetExecute2)(bool, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - if (!operationType) { - MO_DBG_ERR("invalid args"); - return; + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto rstSvc = context->getModel16().getResetService(); + if (!rstSvc) { + MO_DBG_ERR("init failure"); + return; + } + rstSvc->setExecuteReset(onResetExecute2, userData); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_v16_setOnResetNotify not supported with OCPP 2.0.1"); } - context->getOperationRegistry().setOnResponse(operationType, onSendConf); + #endif } +#endif //MO_ENABLE_V16 -void sendRequest(const char *operationType, - std::function ()> fn_createReq, - std::function fn_processConf) { +#if MO_ENABLE_V201 +void mo_v201_setOnResetNotify(bool (*onResetNotify)(MO_ResetType)) { + //convert and forward callback to `mo_v201_setOnResetNotify2()` + mo_v201_setOnResetNotify2(mo_getApiContext(), EVSE_ID_0, [] (MO_ResetType resetType, unsigned int, void *userData) { + auto onResetNotify = reinterpret_cast(userData); + return onResetNotify(resetType); + }, reinterpret_cast(onResetNotify)); +} - if (!context) { +void mo_v201_setOnResetNotify2(MO_Context *ctx, unsigned int evseId, bool (*onResetNotify2)(MO_ResetType, unsigned int, void*), void *userData) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - if (!operationType || !fn_createReq || !fn_processConf) { - MO_DBG_ERR("invalid args"); - return; + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + MO_DBG_ERR("mo_v201_setOnResetNotify not supported with OCPP 1.6"); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto rstSvc = context->getModel201().getResetService(); + if (!rstSvc) { + MO_DBG_ERR("init failure"); + return; + } + rstSvc->setNotifyReset(evseId, onResetNotify2, userData); } + #endif +} - auto request = makeRequest(new CustomOperation(operationType, fn_createReq, fn_processConf)); - context->initiateRequest(std::move(request)); +void mo_v201_setOnResetExecute(void (*onResetExecute)()) { + //convert and forward callback to `mo_v201_setOnResetExecute2()` + mo_v201_setOnResetExecute2(mo_getApiContext(), EVSE_ID_0, [] (unsigned int, void *userData) { + auto onResetExecute = reinterpret_cast(userData); + onResetExecute(); + return false; + }, reinterpret_cast(onResetExecute)); } -void setRequestHandler(const char *operationType, - std::function fn_processReq, - std::function ()> fn_createConf) { +void mo_v201_setOnResetExecute2(MO_Context *ctx, unsigned int evseId, bool (*onResetExecute2)(unsigned int, void*), void *userData) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); - if (!context) { + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + MO_DBG_ERR("mo_v201_setOnResetExecute not supported with OCPP 1.6"); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto rstSvc = context->getModel201().getResetService(); + if (!rstSvc) { + MO_DBG_ERR("init failure"); + return; + } + rstSvc->setExecuteReset(evseId, onResetExecute2, userData); + } + #endif +} +#endif //MO_ENABLE_V201 + +#if MO_ENABLE_MBEDTLS +void mo_setFtpConfig(MO_Context *ctx, MO_FTPConfig ftpConfig) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - if (!operationType || !fn_processReq || !fn_createConf) { - MO_DBG_ERR("invalid args"); + auto context = mo_getContext2(ctx); + + context->setDefaultFtpConfig(ftpConfig); +} + +#if MO_ENABLE_DIAGNOSTICS +void mo_setDiagnosticsReader(MO_Context *ctx, size_t (*readBytes)(char*, size_t, void*), void(*onClose)(void*), void *userData) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } + auto context = mo_getContext2(ctx); + + MicroOcpp::DiagnosticsService *diagSvc = nullptr; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + diagSvc = context->getModel16().getDiagnosticsService(); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + diagSvc = context->getModel201().getDiagnosticsService(); + } + #endif - auto captureOpType = makeString("MicroOcpp.cpp", operationType); + if (!diagSvc) { + MO_DBG_ERR("init failure"); + return; + } - context->getOperationRegistry().registerOperation(operationType, [captureOpType, fn_processReq, fn_createConf] () { - return new CustomOperation(captureOpType.c_str(), fn_processReq, fn_createConf); - }); + diagSvc->setDiagnosticsReader(readBytes, onClose, userData); } -void authorize(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, unsigned int timeout) { - if (!context) { +void mo_setDiagnosticsFtpServerCert(MO_Context *ctx, const char *cert) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; } - if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { - MO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); + auto context = mo_getContext2(ctx); + + MicroOcpp::DiagnosticsService *diagSvc = nullptr; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + diagSvc = context->getModel16().getDiagnosticsService(); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + diagSvc = context->getModel201().getDiagnosticsService(); + } + #endif + + if (!diagSvc) { + MO_DBG_ERR("init failure"); return; } - auto authorize = makeRequest( - new Authorize(context->getModel(), idTag)); - if (onConf) - authorize->setOnReceiveConfListener(onConf); - if (onAbort) - authorize->setOnAbortListener(onAbort); - if (onTimeout) - authorize->setOnTimeoutListener(onTimeout); - if (onError) - authorize->setOnReceiveErrorListener(onError); - if (timeout) - authorize->setTimeout(timeout); - else - authorize->setTimeout(20000); - context->initiateRequest(std::move(authorize)); -} - -bool startTransaction(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, unsigned int timeout) { - if (!context) { + + diagSvc->setFtpServerCert(cert); +} +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT +//void mo_setFirmwareFtpServerCert(const char *cert); //zero-copy mode, i.e. cert must outlive MO +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT + +#endif //MO_ENABLE_MBEDTLS + +/* + * Transaction management (Advanced) + */ + +bool mo_isTransactionAuthorized() { + return mo_isTransactionAuthorized2(mo_getApiContext(), EVSE_ID_1); +} + +bool mo_isTransactionAuthorized2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } - if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { - MO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); - return false; + auto context = mo_getContext2(ctx); + + bool res = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->isAuthorized()); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return false; + } + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->isAuthorized); } - auto connector = context->getModel().getConnector(OCPP_ID_OF_CONNECTOR); - if (!connector) { - MO_DBG_ERR("could not find connector"); + #endif + + return res; +} + +//Returns if server has rejected transaction in StartTx or TransactionEvent response +bool mo_isTransactionDeauthorized() { + return mo_isTransactionDeauthorized2(mo_getApiContext(), EVSE_ID_1); +} + +bool mo_isTransactionDeauthorized2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return false; } - auto transaction = connector->getTransaction(); - if (transaction) { - if (transaction->getStartSync().isRequested()) { - MO_DBG_ERR("transaction already in progress. Must call stopTransaction()"); + auto context = mo_getContext2(ctx); + + bool res = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); return false; } - transaction->setIdTag(idTag); - } else { - beginTransaction_authorized(idTag); //request new transaction object - transaction = connector->getTransaction(); - if (!transaction) { - MO_DBG_WARN("transaction queue full"); + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->isIdTagDeauthorized()); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); return false; } + auto tx = txSvcEvse->getTransaction(); + res = (tx && tx->isDeauthorized); } + #endif - if (auto mService = context->getModel().getMeteringService()) { - auto meterStart = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionBegin); - if (meterStart && *meterStart) { - transaction->setMeterStart(meterStart->toInteger()); - } else { - MO_DBG_ERR("meterStart undefined"); + return res; +} + +//Returns start unix time of transaction. Returns 0 if unix time is not known +int32_t mo_getTransactionStartUnixTime() { + return mo_getTransactionStartUnixTime2(mo_getApiContext(), EVSE_ID_1); +} + +int32_t mo_getTransactionStartUnixTime2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return 0; + } + auto context = mo_getContext2(ctx); + + MicroOcpp::Timestamp startTime; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return 0; + } + if (auto tx = txSvcEvse->getTransaction()) { + startTime = tx->getStartTimestamp(); + } + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return 0; + } + if (auto tx = txSvcEvse->getTransaction()) { + startTime = tx->startTimestamp; } } + #endif - transaction->setStartTimestamp(context->getModel().getClock().now()); + int32_t res = 0; - transaction->commit(); - - auto startTransaction = makeRequest( - new StartTransaction(context->getModel(), transaction)); - if (onConf) - startTransaction->setOnReceiveConfListener(onConf); - if (onAbort) - startTransaction->setOnAbortListener(onAbort); - if (onTimeout) - startTransaction->setOnTimeoutListener(onTimeout); - if (onError) - startTransaction->setOnReceiveErrorListener(onError); - if (timeout) - startTransaction->setTimeout(timeout); - else - startTransaction->setTimeout(0); - context->initiateRequest(std::move(startTransaction)); + if (!context->getClock().toUnixTime(startTime, res)) { + MO_DBG_ERR("cannot determine transaction start unix time"); + res = 0; + } - return true; + return res; } -bool stopTransaction(OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, unsigned int timeout) { - if (!context) { +#if MO_ENABLE_V16 +//Overrides start unix time of transaction +void mo_setTransactionStartUnixTime(int32_t unixTime) { + mo_setTransactionStartUnixTime2(mo_getApiContext(), EVSE_ID_1, unixTime); +} + +void mo_setTransactionStartUnixTime2(MO_Context *ctx, unsigned int evseId, int32_t unixTime) { + if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return false; - } - auto connector = context->getModel().getConnector(OCPP_ID_OF_CONNECTOR); - if (!connector) { - MO_DBG_ERR("could not find connector"); - return false; + return; } + auto context = mo_getContext2(ctx); - auto transaction = connector->getTransaction(); - if (!transaction || !transaction->isRunning()) { - MO_DBG_ERR("no running Tx to stop"); - return false; + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + if (auto tx = txSvcEvse->getTransaction()) { + + MicroOcpp::Timestamp t; + if (!context->getClock().fromUnixTime(t, unixTime)) { + MO_DBG_ERR("invalid unix time"); + return; + } + tx->setStartTimestamp(t); + } + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_setTransactionStartUnixTime not supported with OCPP 2.0.1"); } + #endif +} - connector->endTransaction(transaction->getIdTag(), "Local"); +int32_t mo_getTransactionStopUnixTime() { + return mo_getTransactionStopUnixTime2(mo_getApiContext(), EVSE_ID_1); +} - const char *idTag = transaction->getIdTag(); - if (idTag) { - transaction->setStopIdTag(idTag); +int32_t mo_getTransactionStopUnixTime2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return 0; } + auto context = mo_getContext2(ctx); - if (auto mService = context->getModel().getMeteringService()) { - auto meterStop = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionEnd); - if (meterStop && *meterStop) { - transaction->setMeterStop(meterStop->toInteger()); - } else { - MO_DBG_ERR("meterStop undefined"); + int32_t res = 0; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return 0; + } + if (auto tx = txSvcEvse->getTransaction()) { + if (!context->getClock().toUnixTime(tx->getStopTimestamp(), res)) { + MO_DBG_ERR("cannot determine transaction stop unix time"); + res = 0; + } } } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_setTransactionStartUnixTime not supported with OCPP 2.0.1"); + } + #endif - transaction->setStopTimestamp(context->getModel().getClock().now()); + return res; +} - transaction->commit(); +//Overrides stop unix time of transaction +void mo_setTransactionStopUnixTime(int32_t unixTime) { + mo_setTransactionStopUnixTime2(mo_getApiContext(), EVSE_ID_1, unixTime); +} - auto stopTransaction = makeRequest( - new StopTransaction(context->getModel(), transaction)); - if (onConf) - stopTransaction->setOnReceiveConfListener(onConf); - if (onAbort) - stopTransaction->setOnAbortListener(onAbort); - if (onTimeout) - stopTransaction->setOnTimeoutListener(onTimeout); - if (onError) - stopTransaction->setOnReceiveErrorListener(onError); - if (timeout) - stopTransaction->setTimeout(timeout); - else - stopTransaction->setTimeout(0); - context->initiateRequest(std::move(stopTransaction)); +void mo_setTransactionStopUnixTime2(MO_Context *ctx, unsigned int evseId, int32_t unixTime) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + if (auto tx = txSvcEvse->getTransaction()) { + + MicroOcpp::Timestamp t; + if (!context->getClock().fromUnixTime(t, unixTime)) { + MO_DBG_ERR("invalid unix time"); + return; + } + tx->setStopTimestamp(t); + } + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_setTransactionStopUnixTime not supported with OCPP 2.0.1"); + } + #endif +} + +int32_t mo_getTransactionMeterStart() { + return mo_getTransactionMeterStart2(mo_getApiContext(), EVSE_ID_1); +} + +int32_t mo_getTransactionMeterStart2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return -1; + } + auto context = mo_getContext2(ctx); + + int32_t res = -1; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return -1; + } + if (auto tx = txSvcEvse->getTransaction()) { + res = tx->getMeterStart(); + } + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_getTransactionMeterStart not supported with OCPP 2.0.1"); + } + #endif + + return res; +} + +//Overrides meterStart of transaction +void mo_setTransactionMeterStart(int32_t meterStart) { + mo_setTransactionMeterStart2(mo_getApiContext(), EVSE_ID_1, meterStart); +} + +void mo_setTransactionMeterStart2(MO_Context *ctx, unsigned int evseId, int32_t meterStart) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + if (auto tx = txSvcEvse->getTransaction()) { + tx->setMeterStart(meterStart); + } + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_setTransactionMeterStart not supported with OCPP 2.0.1"); + } + #endif +} + +int32_t mo_getTransactionMeterStop() { + return mo_getTransactionMeterStop2(mo_getApiContext(), EVSE_ID_1); +} + +int32_t mo_getTransactionMeterStop2(MO_Context *ctx, unsigned int evseId) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return -1; + } + auto context = mo_getContext2(ctx); + + int32_t res = -1; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return -1; + } + if (auto tx = txSvcEvse->getTransaction()) { + res = tx->getMeterStop(); + } + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_getTransactionMeterStop not supported with OCPP 2.0.1"); + } + #endif + + return res; +} + +//Overrides meterStop of transaction +void mo_setTransactionMeterStop(int32_t meterStop) { + mo_setTransactionMeterStop2(mo_getApiContext(), EVSE_ID_1, meterStop); +} + +void mo_setTransactionMeterStop2(MO_Context *ctx, unsigned int evseId, int32_t meterStop) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + auto txSvc = context->getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return; + } + if (auto tx = txSvcEvse->getTransaction()) { + tx->setMeterStop(meterStop); + } + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_setTransactionMeterStop not supported with OCPP 2.0.1"); + } + #endif +} +#endif //MO_ENABLE_V16 + +//helper function +namespace MO_Facade { +MicroOcpp::Mutability convertMutability(MO_Mutability mutability) { + auto res = MicroOcpp::Mutability::None; + switch (mutability) { + case MO_Mutability_ReadWrite: + res = MicroOcpp::Mutability::ReadWrite; + break; + case MO_Mutability_ReadOnly: + res = MicroOcpp::Mutability::ReadOnly; + break; + case MO_Mutability_WriteOnly: + res = MicroOcpp::Mutability::WriteOnly; + break; + case MO_Mutability_None: + res = MicroOcpp::Mutability::None; + break; + } + return res; +} +} //namespace MO_Facade + +using namespace MO_Facade; + +#if MO_ENABLE_V16 +bool mo_declareConfigurationInt(const char *key, int factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired) { + return mo_declareVarConfigInt(mo_getApiContext(), NULL, NULL, key, factoryDefault, mutability, persistent, rebootRequired); +} + +bool mo_declareConfigurationBool(const char *key, bool factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired) { + return mo_declareVarConfigBool(mo_getApiContext(), NULL, NULL, key, factoryDefault, mutability, persistent, rebootRequired); +} + +bool mo_declareConfigurationString(const char *key, const char *factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired) { + return mo_declareVarConfigString(mo_getApiContext(), NULL, NULL, key, factoryDefault, mutability, persistent, rebootRequired); +} + +//Set Config value. Set after `mo_setup()` +bool mo_setConfigurationInt(const char *key, int value) { + return mo_setVarConfigInt(mo_getApiContext(), NULL, NULL, key, value); +} +bool mo_setConfigurationBool(const char *key, bool value) { + return mo_setVarConfigBool(mo_getApiContext(), NULL, NULL, key, value); +} +bool mo_setConfigurationString(const char *key, const char *value) { + return mo_setVarConfigString(mo_getApiContext(), NULL, NULL, key, value); +} + +//Get Config value. MO writes the value into `valueOut`. Call after `mo_setup()` +bool mo_getConfigurationInt(const char *key, int *valueOut) { + return mo_getVarConfigInt(mo_getApiContext(), NULL, NULL, key, valueOut); +} +bool mo_getConfigurationBool(const char *key, bool *valueOut) { + return mo_getVarConfigBool(mo_getApiContext(), NULL, NULL, key, valueOut); +} +bool mo_getConfigurationString(const char *key, const char **valueOut) { + return mo_getVarConfigString(mo_getApiContext(), NULL, NULL, key, valueOut); +} +#endif //MO_ENABLE_V16 + +bool mo_declareVarConfigInt(MO_Context *ctx, const char *component201, const char *name201, const char *key16, int factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + const char *containerId = persistent ? CONFIGS_FN_FACADE : MO_CONFIGURATION_VOLATILE; + success = configSvc->declareConfiguration(key16, factoryDefault, containerId, convertMutability(mutability), rebootRequired); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::Ocpp201::Variable::AttributeTypeSet(), rebootRequired); + } + #endif + + return success; +} +bool mo_declareVarConfigBool(MO_Context *ctx, const char *component201, const char *name201, const char *key16, bool factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + const char *containerId = persistent ? CONFIGS_FN_FACADE : MO_CONFIGURATION_VOLATILE; + success = configSvc->declareConfiguration(key16, factoryDefault, containerId, convertMutability(mutability), rebootRequired); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::Ocpp201::Variable::AttributeTypeSet(), rebootRequired); + } + #endif + + return success; +} +bool mo_declareVarConfigString(MO_Context *ctx, const char *component201, const char *name201, const char *key16, const char *factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + const char *containerId = persistent ? CONFIGS_FN_FACADE : MO_CONFIGURATION_VOLATILE; + success = configSvc->declareConfiguration(key16, factoryDefault, containerId, convertMutability(mutability), rebootRequired); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::Ocpp201::Variable::AttributeTypeSet(), rebootRequired); + } + #endif + + return success; +} + +//Set Variable or Config value. Set after `mo_setup()`. If running OCPP 2.0.1, `key16` can be NULL +bool mo_setVarConfigInt(MO_Context *ctx, const char *component201, const char *name201, const char *key16, int value) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto config = configSvc->getConfiguration(key16); + if (!config) { + MO_DBG_ERR("could not find config %s", key16); + return false; + } + config->setInt(value); + success = true; + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto variable = varSvc->getVariable(component201, name201); + if (!variable) { + MO_DBG_ERR("could not find variable %s %s", component201, name201); + return false; + } + variable->setInt(value); + success = true; + } + #endif + + return success; +} +bool mo_setVarConfigBool(MO_Context *ctx, const char *component201, const char *name201, const char *key16, bool value) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto config = configSvc->getConfiguration(key16); + if (!config) { + MO_DBG_ERR("could not find config %s", key16); + return false; + } + config->setBool(value); + success = true; + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto variable = varSvc->getVariable(component201, name201); + if (!variable) { + MO_DBG_ERR("could not find variable %s %s", component201, name201); + return false; + } + variable->setBool(value); + success = true; + } + #endif + + return success; +} + +bool mo_setVarConfigString(MO_Context *ctx, const char *component201, const char *name201, const char *key16, const char *value) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto config = configSvc->getConfiguration(key16); + if (!config) { + MO_DBG_ERR("could not find config %s", key16); + return false; + } + success = config->setString(value); + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto variable = varSvc->getVariable(component201, name201); + if (!variable) { + MO_DBG_ERR("could not find variable %s %s", component201, name201); + return false; + } + success = variable->setString(value); + } + #endif + + return success; +} + +bool mo_getVarConfigInt(MO_Context *ctx, const char *component201, const char *name201, const char *key16, int *valueOut) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto config = configSvc->getConfiguration(key16); + if (!config) { + MO_DBG_ERR("could not find config %s", key16); + return false; + } + *valueOut = config->getInt(); + success = true; + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto variable = varSvc->getVariable(component201, name201); + if (!variable) { + MO_DBG_ERR("could not find variable %s %s", component201, name201); + return false; + } + *valueOut = variable->getInt(); + success = true; + } + #endif + + return success; +} +bool mo_getVarConfigBool(MO_Context *ctx, const char *component201, const char *name201, const char *key16, bool *valueOut) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto config = configSvc->getConfiguration(key16); + if (!config) { + MO_DBG_ERR("could not find config %s", key16); + return false; + } + *valueOut = config->getBool(); + success = true; + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto variable = varSvc->getVariable(component201, name201); + if (!variable) { + MO_DBG_ERR("could not find variable %s %s", component201, name201); + return false; + } + *valueOut = variable->getBool(); + success = true; + } + #endif + + return success; +} +bool mo_getVarConfigString(MO_Context *ctx, const char *component201, const char *name201, const char *key16, const char **valueOut) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = false; + + #if MO_ENABLE_V16 + if (context->getOcppVersion() == MO_OCPP_V16) { + if (!key16) { + //disabled OCPP 1.6 compatibility + return true; + } + auto configSvc = context->getModel16().getConfigurationService(); + if (!configSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto config = configSvc->getConfiguration(key16); + if (!config) { + MO_DBG_ERR("could not find config %s", key16); + return false; + } + *valueOut = config->getString(); + success = true; + } + #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + if (!component201 || !name201) { + //disabled OCPP 2.0.1 compatibility + return true; + } + auto varSvc = context->getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + auto variable = varSvc->getVariable(component201, name201); + if (!variable) { + MO_DBG_ERR("could not find variable %s %s", component201, name201); + return false; + } + *valueOut = variable->getString(); + success = true; + } + #endif + + return success; +} + +//bool mo_addCustomVarConfigInt(const char *component201, const char *name201, const char *key16, int (*getValue)(const char*, const char*, const char*, void*), void (*setValue)(int, const char*, const char*, const char*, void*), void *userData); +//bool mo_addCustomVarConfigBool(const char *component201, const char *name201, const char *key16, bool (*getValue)(const char*, const char*, const char*, void*), void (*setValue)(bool, const char*, const char*, const char*, void*), void *userData); +//bool mo_addCustomVarConfigString(const char *component201, const char *name201, const char *key16, const char* (*getValue)(const char*, const char*, const char*, void*), bool (*setValue)(const char*, const char*, const char*, const char*, void*), void *userData); + +MicroOcpp::Context *mo_getContext() { + return g_context; +} + +MicroOcpp::Context *mo_getContext2(MO_Context *ctx) { + return reinterpret_cast(ctx); +} + +MO_Context *mo_initialize2() { + auto context = new MicroOcpp::Context(); + if (!context) { + MO_DBG_ERR("OOM"); + return nullptr; + } + return reinterpret_cast(context); +} + +bool mo_setup2(MO_Context *ctx) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + return context->setup(); +} + +void mo_deinitialize2(MO_Context *ctx) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + delete context; +} + +void mo_loop2(MO_Context *ctx) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + context->loop(); +} + +//Send operation manually and bypass MO business logic +bool mo_sendRequest(MO_Context *ctx, const char *operationType, + const char *payloadJson, + void (*onResponse)(const char *payloadJson, void *userData), + void (*onAbort)(void *userData), + void *userData) { + + MicroOcpp::Operation *operation = nullptr; + std::unique_ptr request; + + MicroOcpp::Context *context = nullptr; + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + goto fail; + } + context = mo_getContext2(ctx); + + if (auto customOperation = new MicroOcpp::CustomOperation()) { + + if (!customOperation->setupEvseInitiated(operationType, payloadJson, onResponse, userData)) { + MO_DBG_ERR("create operation failure"); + delete customOperation; + goto fail; + } + + operation = static_cast(customOperation); + } else { + MO_DBG_ERR("OOM"); + goto fail; + } + + request = MicroOcpp::makeRequest(*context, operation); + if (!request) { + MO_DBG_ERR("OOM"); + goto fail; + } + + request->setOnAbort([onAbort, userData] () { + onAbort(userData); + }); + + request->setTimeout(20); + + context->getMessageService().sendRequest(std::move(request)); + return true; +fail: + delete operation; + if (onAbort) { + //onAbort execution is guaranteed to allow client to free up resources here + onAbort(userData); + } + return false; +} + +//Set custom operation handler for incoming reqeusts and bypass MO business logic +bool mo_setRequestHandler(MO_Context *ctx, const char *operationType, + void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), + int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), + void *userData) { + + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = context->getMessageService().registerOperation(operationType, onRequest, writeResponse, userData); + if (!success) { + MO_DBG_ERR("could not register operation %s", operationType); + } + return success; +} + +//Sniff incoming requests without control over the response +bool mo_setOnReceiveRequest(MO_Context *ctx, const char *operationType, + void (*onRequest)(const char *operationType, const char *payloadJson, void *userData), + void *userData) { + + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = context->getMessageService().setOnReceiveRequest(operationType, onRequest, userData); + if (!success) { + MO_DBG_ERR("could not set onRequest %s", operationType); + } + return success; +} + +//Sniff outgoing responses without control over the response +bool mo_setOnSendConf(MO_Context *ctx, const char *operationType, + void (*onSendConf)(const char *operationType, const char *payloadJson, void *userData), + void *userData) { + + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return false; + } + auto context = mo_getContext2(ctx); + + bool success = context->getMessageService().setOnSendConf(operationType, onSendConf, userData); + if (!success) { + MO_DBG_ERR("could not set onRequest %s", operationType); + } + return success; +} + +#endif - return true; -} diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index f15c13cc..0f9d6ce5 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -5,99 +5,61 @@ #ifndef MO_MICROOCPP_H #define MO_MICROOCPP_H -#include -#include -#include - -#include +#include #include -#include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -using MicroOcpp::OnReceiveConfListener; -using MicroOcpp::OnReceiveReqListener; -using MicroOcpp::OnSendConfListener; -using MicroOcpp::OnAbortListener; -using MicroOcpp::OnTimeoutListener; -using MicroOcpp::OnReceiveErrorListener; +/* + * Basic integration + */ + +//Begin the lifecycle of MO. Now, it is possible to set up the library. After configuring MO, +//call mo_setup() to finalize the setup +bool mo_initialize(); + +//End the lifecycle of MO and free all resources +void mo_deinitialize(); -#ifndef MO_CUSTOM_WS -//use links2004/WebSockets library +//Returns if library is initialized +bool mo_isInitialized(); +//Returns Context handle to pass around API functions +MO_Context *mo_getApiContext(); + +#if MO_WS_USE == MO_WS_ARDUINO /* - * Initialize the library with the OCPP URL, EVSE voltage and filesystem configuration. + * Setup MO with links2004/WebSockets library. Only available on Arduino, for other platforms set custom + * WebSockets adapter (see examples folder). `backendUrl`, `chargeBoxId`, `authorizationKey` and + * `CA_cert` are zero-copy and must remain valid until `mo_setup()`. `CA_cert` is zero-copy and must + * outlive the MO lifecycle. * * If the connections fails, please refer to * https://github.com/matth-x/MicroOcpp/issues/36#issuecomment-989716573 for recommendations on * how to track down the issue with the connection. - * - * This is a convenience function only available for Arduino. */ -void mocpp_initialize( - const char *backendUrl, //e.g. "wss://example.com:8443/steve/websocket/CentralSystemService" - const char *chargeBoxId, //e.g. "charger001" - const char *chargePointModel = "Demo Charger", //model name of this charger - const char *chargePointVendor = "My Company Ltd.", //brand name - MicroOcpp::FilesystemOpt fsOpt = MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail, //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h - const char *password = nullptr, //password present in the websocket message header - const char *CA_cert = nullptr, //TLS certificate - bool autoRecover = false); //automatically sanitize the local data store when the lib detects recurring crashes. Not recommended during development +bool mo_setWebsocketUrl( + const char *backendUrl, //e.g. "wss://example.com:8443/steve/websocket/CentralSystemService". Must be defined + const char *chargeBoxId, //e.g. "charger001". Can be NULL + const char *authorizationKey, //authorizationKey present in the websocket message header. Can be NULL. Set this to enable OCPP Security Profile 2 + const char *CA_cert); //TLS certificate. Can be NULL. Set this to enable OCPP Security Profile 2 #endif -/* - * Convenience initialization: use this for passing the BootNotification payload JSON to the mocpp_initialize(...) below - * - * Example usage: - * - * mocpp_initialize(osock, ChargerCredentials("Demo Charger", "My Company Ltd.")); - * - * For a description of the fields, refer to OCPP 1.6 Specification - Edition 2 p. 60 - */ -struct ChargerCredentials { - ChargerCredentials( - 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); - - /* - * 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.", - 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;} - -private: - char payload [512] = {'{', '}', '\0'}; -}; - -/* - * Initialize the library with a WebSocket connection which is configured with protocol=ocpp1.6 - * (=Connection), EVSE voltage and filesystem configuration. This library requires that you handle - * establishing the connection and keeping it alive. Please refer to - * https://github.com/matth-x/MicroOcpp/tree/main/examples/ESP-TLS for an example how to use it. +#if __cplusplus +/* Set a WebSocket Client. Required if ArduinoWebsockets is not supported. This library requires + * that you handle establishing the connection and keeping it alive. MO does not take ownership + * of the passed `connection` object, i.e. it must be destroyed after `mo_deinitialize()` Please + * refer to https://github.com/matth-x/MicroOcpp/tree/main/examples/ESP-TLS for an example how to + * use it. * * This GitHub project also delivers an Connection implementation based on links2004/WebSockets. If * you need another WebSockets implementation, you can subclass the Connection class and pass it to @@ -105,23 +67,92 @@ struct ChargerCredentials { * https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/MongooseConnectionClient.cpp for * an example. */ -void mocpp_initialize( - MicroOcpp::Connection& connection, //WebSocket adapter for MicroOcpp - 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 - MicroOcpp::ProtocolVersion version = MicroOcpp::ProtocolVersion(1,6)); +void mo_setConnection(MicroOcpp::Connection *connection); +void mo_setConnection2(MO_Context *ctx, MicroOcpp::Connection *connection); +#endif + +#if MO_USE_FILEAPI != MO_CUSTOM_FS +//Set if MO can use the filesystem and if it needs to mount it. See "FilesystemAdapter.h" for all options +void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt); +void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix); +#endif //#if MO_USE_FILEAPI != MO_CUSTOM_FS + +//Set the OCPP version `MO_OCPP_V16` or `MO_OCPP_V201`. Works only if build flags `MO_ENABLE_V16` or +//`MO_ENABLE_V201` are set to 1 +void mo_setOcppVersion(int ocppVersion); +void mo_setOcppVersion2(MO_Context *ctx, int ocppVersion); + +//Set BootNotification fields (for more options, use `mo_setBootNotificationData2()`). For a description of +//the fields, refer to OCPP 1.6 or 2.0.1 specification +bool mo_setBootNotificationData( + const char *chargePointModel, //model name of this charger, e.g. "Demo Charger" + const char *chargePointVendor); //brand name, e.g. "My Company Ltd." + +bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData); //see "BootNotificationData.h" for example usage -/* - * Stop the OCPP library and release allocated resources. - */ -void mocpp_deinitialize(); /* - * To be called in the main loop (e.g. place it inside loop()) + * Define the Inputs and Outputs of this library. + * + * This library interacts with the hardware of your charger by Inputs and Outputs. Inputs and Outputs + * are callbacks which read information from the EVSE or control the behavior of the EVSE. + * + * An Input is a function which returns the current state of a variable of the EVSE. For example, if + * the energy meter stores the energy register in the global variable `e_reg`, then you can allow + * this library to read it by defining the following Input and passing it to the library. + * ``` + * setEnergyMeterInput([] () { + * return e_reg; + * }); + * ``` + * + * An Output is a callback which gets state value from the OCPP library and applies it to the EVSE. + * For example, to let Smart Charging control the PWM signal of the Control Pilot, define the + * following Output and pass it to the library. + * ``` + * setSmartChargingPowerOutput([] (float p_max) { + * pwm = p_max / PWM_FACTOR; //(simplified example) + * }); + * ``` + * + * Configure the library with Inputs and Outputs after mo_initialize() and before mo_setup(). */ -void mocpp_loop(); + +void mo_setConnectorPluggedInput(bool (*connectorPlugged)()); //Input about if an EV is plugged to this EVSE +void mo_setConnectorPluggedInput2(MO_Context *ctx, unsigned int evseId, bool (*connectorPlugged2)(unsigned int, void*), void *userData); //alternative with more args + +bool mo_setEnergyMeterInput(int32_t (*energyInput)()); //Input of the electricity meter register in Wh +bool mo_setEnergyMeterInput2(MO_Context *ctx, unsigned int evseId, int32_t (*energyInput2)(MO_ReadingContext, unsigned int, void*), void *userData); //alternative with more args + +bool mo_setPowerMeterInput(float (*powerInput)()); //Input of the power meter reading in W +bool mo_setPowerMeterInput2(MO_Context *ctx, unsigned int evseId, float (*powerInput2)(MO_ReadingContext, unsigned int, void*), void *userData); //alternative with more args + +#if MO_ENABLE_SMARTCHARGING +//Smart Charging Output, alternative for Watts only, Current only, or Watts x Current x numberPhases (x phaseToUse) +//Only one of the Smart Charging Outputs can be set at a time. +//MO will execute the callback whenever the OCPP charging limit changes and will pass the limit for now +//to the callback. If OCPP does not define a limit, then MO passes the value -1 for "undefined". +bool mo_setSmartChargingPowerOutput(void (*powerLimitOutput)(float)); //Output (in Watts) for the Smart Charging limit +bool mo_setSmartChargingCurrentOutput(void (*currentLimitOutput)(float)); //Output (in Amps) for the Smart Charging limit +bool mo_setSmartChargingOutput( + MO_Context *ctx, + unsigned int evseId, + void (*chargingLimitOutput)(MO_ChargeRate, unsigned int, void*), + bool powerSupported, //charge limit defined as power supported + bool currentSupported, //charge limit defined as current supported + bool phases3to1Supported, //charger supports switching from 3 to 1 phase during charge + bool phaseSwitchingSupported, //charger supports selecting the phase by server command. Ignored if OCPP 1.6 + void *userData); //Output (in Watts, Amps, numberPhases, phaseToUse) for the Smart Charging limit +#endif //MO_ENABLE_SMARTCHARGING + +//See section with advanced Inputs and Outputs further below + +//Finalize setup. After this, the library is ready to be used and mo_loop() can be run +bool mo_setup(); + +//Run library routines. To be called in the main loop (e.g. place it inside `loop()`) +void mo_loop(); + /* * Transaction management. @@ -148,13 +179,26 @@ void mocpp_loop(); * card to start charging, but the semantic is slightly different. This function begins the authorized * phase, but a transaction may already have started due to an earlier transaction start point. */ -bool beginTransaction(const char *idTag, unsigned int connectorId = 1); +bool mo_beginTransaction(const char *idTag); +bool mo_beginTransaction2(MO_Context *ctx, unsigned int evseId, const char *idTag); -/* - * Begin the transaction process and skip the OCPP-side authorization. See beginTransaction(...) for a - * complete description - */ -bool beginTransaction_authorized(const char *idTag, const char *parentIdTag = nullptr, unsigned int connectorId = 1); +//Same as `mo_beginTransaction()`, but skip authorization. `parentIdTag` can be NULL +bool mo_beginTransaction_authorized(const char *idTag, const char *parentIdTag); +bool mo_beginTransaction_authorized2(MO_Context *ctx, unsigned int evseId, const char *idTag, const char *parentIdTag); //alternative with more args + +#if MO_ENABLE_V201 +//Same as mo_beginTransaction, but function name fits better for OCPP 2.0.1 and more options for 2.0.1. +//Attempt to authorize a pending transaction, or create new transactino to authorize. If local +//authorization is enabled, will search the local whitelist if server response takes too long. +//Backwards-compatible: if MO is initialized with 1.6, then this is the same as `mo_beginTransaction()` +bool mo_authorizeTransaction(const char *idToken); //idTokenType ISO14443 is assumed +bool mo_authorizeTransaction2(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type); +bool mo_authorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type, bool validateIdToken, const char *groupIdToken); + +//Same as `mo_beginAuthorization()`, but skip authorization. `groupIdToken` can be NULL +bool mo_setTransactionAuthorized(const char *idToken, const char *groupIdToken); //idTokenType ISO14443 is assumed +bool mo_setTransactionAuthorized2(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type, const char *groupIdToken); +#endif //MO_ENABLE_V201 /* * OCPP 1.6 (2.0.1 see below): @@ -197,16 +241,35 @@ bool beginTransaction_authorized(const char *idTag, const char *parentIdTag = nu * * Note: the stop reason parameter is ignored when running with OCPP 2.0.1. It's always Local */ -bool endTransaction(const char *idTag = nullptr, const char *reason = nullptr, unsigned int connectorId = 1); +bool mo_endTransaction(const char *idTag, const char *reason); //idTag and reason can be NULL +bool mo_endTransaction2(MO_Context *ctx, unsigned int evseId, const char *idTag, const char *reason); -/* - * End the transaction process definitely without authorization check. See endTransaction(...) for a - * complete description. - * - * Use this function if you manage authorization on your own and want to bypass the Authorization - * management of this lib. +//Same as `endTransaction()`, but skip authorization +bool mo_endTransaction_authorized(const char *idTag, const char *reason); //idTag and reason can be NULL +bool mo_endTransaction_authorized2(MO_Context *ctx, unsigned int evseId, const char *idTag, const char *reason); + +#if MO_ENABLE_V201 +//Same as mo_endTransaction, but function name fits better for OCPP 2.0.1 and more options for 2.0.1. +//Backwards-compatible: if MO is initialized with 1.6, then this is the same as `mo_endTransaction()` +bool mo_deauthorizeTransaction(const char *idToken); //idTokenType ISO14443 is assumed +bool mo_deauthorizeTransaction2(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type); +bool mo_deauthorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type, bool validateIdToken, const char *groupIdToken); + +//Force transaction stop and set stoppedReason and stopTrigger correspondingly +//Backwards-compatible: if MO is initialized with 1.6, then this is the same as `mo_endTransaction()` +//and attempts to map 2.0.1 `stoppedReason` to 1.6 `reason` +bool mo_abortTransaction(MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReason stopTrigger); +bool mo_abortTransaction2(MO_Context *ctx, unsigned int evseId, MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReason stopTrigger); +#endif //MO_ENABLE_V201 + +/* + * Returns if according to OCPP, the EVSE is allowed to close provide charge now. + * + * If you integrate it into a J1772 charger, true means that the Control Pilot can send the PWM signal + * and false means that the Control Pilot must be at a DC voltage. */ -bool endTransaction_authorized(const char *idTag, const char *reason = nullptr, unsigned int connectorId = 1); +bool mo_ocppPermitsCharge(); +bool mo_ocppPermitsCharge2(MO_Context *ctx, unsigned int evseId); /* * Get information about the current Transaction lifecycle. A transaction can enter the following @@ -228,356 +291,341 @@ bool endTransaction_authorized(const char *idTag, const char *reason = nullptr, * Finished / Aborted | | * / Idle | false | false */ -bool isTransactionActive(unsigned int connectorId = 1); -bool isTransactionRunning(unsigned int connectorId = 1); +bool mo_isTransactionActive(); +bool mo_isTransactionActive2(MO_Context *ctx, unsigned int evseId); +bool mo_isTransactionRunning(); +bool mo_isTransactionRunning2(MO_Context *ctx, unsigned int evseId); /* * Get the idTag which has been used to start the transaction. If no transaction process is * running, this function returns nullptr */ -const char *getTransactionIdTag(unsigned int connectorId = 1); +const char *mo_getTransactionIdTag(); +const char *mo_getTransactionIdTag2(MO_Context *ctx, unsigned int evseId); -/* - * Returns the current transaction process. Returns nullptr if no transaction is running, preparing or finishing - * - * See the class definition in MicroOcpp/Model/Transactions/Transaction.h for possible uses of this object - * - * Examples: - * auto tx = getTransaction(); //fetch tx object - * if (tx) { //check if tx object exists - * bool active = tx->isActive(); //active tells if the transaction is preparing or continuing to run - * //inactive means that the transaction is about to stop, stopped or won't be started anymore - * int transactionId = tx->getTransactionId(); //the transactionId as assigned by the OCPP server - * bool deauthorized = tx->isIdTagDeauthorized(); //if StartTransaction has been rejected - * } - */ -std::shared_ptr& getTransaction(unsigned int connectorId = 1); +#if MO_ENABLE_V16 +//Get the transactionId once the StartTransaction.conf from the server has arrived. Otherwise, +//returns -1 +int mo_v16_getTransactionId(); +int mo_v16_getTransactionId2(MO_Context *ctx, unsigned int evseId); +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -/* - * OCPP 2.0.1 version of getTransaction(). Note that the return transaction object is of another type - * and unlike the 1.6 version, this function does not give ownership. - */ -MicroOcpp::Ocpp201::Transaction *getTransactionV201(unsigned int evseId = 1); +//Get the transactionId of the currently pending transaction. Returns NULL if no transaction is +//running. The returned string must be copied into own buffer, as it may be invalidated after call. +const char *mo_v201_getTransactionId(); +const char *mo_v201_getTransactionId2(MO_Context *ctx, unsigned int evseId); #endif //MO_ENABLE_V201 -/* - * Returns if the OCPP library allows the EVSE to charge at the moment. - * - * If you integrate it into a J1772 charger, true means that the Control Pilot can send the PWM signal - * and false means that the Control Pilot must be at a DC voltage. - */ -bool ocppPermitsCharge(unsigned int connectorId = 1); - /* - * Returns the latest ChargePointStatus as reported via StatusNotification (standard OCPP data type) + * Returns the latest MO_ChargePointStatus as reported via StatusNotification (standard OCPP data type) */ -ChargePointStatus getChargePointStatus(unsigned int connectorId = 1); +MO_ChargePointStatus mo_getChargePointStatus(); +MO_ChargePointStatus mo_getChargePointStatus2(MO_Context *ctx, unsigned int evseId); + /* - * Define the Inputs and Outputs of this library. - * - * This library interacts with the hardware of your charger by Inputs and Outputs. Inputs and Outputs - * are tiny function-objects which read information from the EVSE or control the behavior of the EVSE. - * - * An Input is a function which returns the current state of a variable of the EVSE. For example, if - * the energy meter stores the energy register in the global variable `e_reg`, then you can allow - * this library to read it by defining the Input - * `[] () {return e_reg;}` - * and passing it to the library. - * - * An Output is a function which gets a state value from the OCPP library and applies it to the EVSE. - * For example, to let Smart Charging control the PWM signal of the Control Pilot, define the Output - * `[] (float p_max) {pwm = p_max / PWM_FACTOR;}` (simplified example) - * and pass it to the library. + * Define the Inputs and Outputs of this library. (Advanced) * - * Configure the library with Inputs and Outputs once in the setup() function. + * These Inputs and Outputs are optional depending on the use case of your charger. Set before `mo_setup()` */ -void setConnectorPluggedInput(std::function pluggedInput, unsigned int connectorId = 1); //Input about if an EV is plugged to this EVSE +void mo_setEvReadyInput(bool (*evReadyInput)()); //Input if EV is ready to charge (= J1772 State C) +void mo_setEvReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*evReadyInput2)(unsigned int, void*), void *userData); //alternative with more args -void setEnergyMeterInput(std::function energyInput, unsigned int connectorId = 1); //Input of the electricity meter register in Wh +void mo_setEvseReadyInput(bool (*evseReadyInput)()); //Input if EVSE allows charge (= PWM signal on) +void mo_setEvseReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*evseReadyInput2)(unsigned int, void*), void *userData); //alternative with more args -void setPowerMeterInput(std::function powerInput, unsigned int connectorId = 1); //Input of the power meter reading in W +bool mo_addErrorCodeInput(const char* (*errorCodeInput)()); //Input for Error codes (please refer to OCPP 1.6, Edit2, p. 71 and 72 for valid error codes) -//Smart Charging Output, alternative for Watts only, Current only, or Watts x Current x numberPhases. -//Only one of the Smart Charging Outputs can be set at a time. -//MO will execute the callback whenever the OCPP charging limit changes and will pass the limit for now -//to the callback. If OCPP does not define a limit, then MO passes the value -1 for "undefined". -void setSmartChargingPowerOutput(std::function chargingLimitOutput, unsigned int connectorId = 1); //Output (in Watts) for the Smart Charging limit -void setSmartChargingCurrentOutput(std::function chargingLimitOutput, unsigned int connectorId = 1); //Output (in Amps) for the Smart Charging limit -void setSmartChargingOutput(std::function chargingLimitOutput, unsigned int connectorId = 1); //Output (in Watts, Amps, numberPhases) for the Smart Charging limit - -/* - * Define the Inputs and Outputs of this library. (Advanced) - * - * These Inputs and Outputs are optional depending on the use case of your charger. - */ +#if MO_ENABLE_V16 +//Input for Error codes + additional error data. See "Availability.h" for more docs +bool mo_v16_addErrorDataInput(MO_Context *ctx, unsigned int evseId, MO_ErrorData (*errorData)(unsigned int, void*), void *userData); +#endif //MO_ENABLE_V16 -void setEvReadyInput(std::function evReadyInput, unsigned int connectorId = 1); //Input if EV is ready to charge (= J1772 State C) - -void setEvseReadyInput(std::function evseReadyInput, unsigned int connectorId = 1); //Input if EVSE allows charge (= PWM signal on) +#if MO_ENABLE_V201 +//Input to set EVSE into Faulted state +bool mo_v201_addFaultedInput(MO_Context *ctx, unsigned int evseId, bool (*faulted)(unsigned int, void*), void *userData); +#endif //MO_ENABLE_V201 -void addErrorCodeInput(std::function errorCodeInput, unsigned int connectorId = 1); //Input for Error codes (please refer to OCPP 1.6, Edit2, p. 71 and 72 for valid error codes) -void addErrorDataInput(std::function errorDataInput, unsigned int connectorId = 1); +//Input for further integer MeterValue types +bool mo_addMeterValueInputInt(int32_t (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase); +bool mo_addMeterValueInputInt2(MO_Context *ctx, unsigned int evseId, int32_t (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); -void addMeterValueInput(std::function valueInput, const char *measurand = nullptr, const char *unit = nullptr, const char *location = nullptr, const char *phase = nullptr, unsigned int connectorId = 1); //integrate further metering Inputs +//Input for further float MeterValue types +bool mo_addMeterValueInputFloat(float (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); +bool mo_addMeterValueInputFloat2(MO_Context *ctx, unsigned int evseId, float (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); -void addMeterValueInput(std::unique_ptr valueInput, unsigned int connectorId = 1); //integrate further metering Inputs (more extensive alternative) +//Input for further string MeterValue types. `meterInput` shall write the value into `buf` (which is `size` large) and return the number +//of characters written (not including the terminating 0). See "MeterValue.h" for a full description +bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); +bool mo_addMeterValueInputString2(MO_Context *ctx, unsigned int evseId, int (*meterInput)(char *buf, size_t size, MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); -void setOccupiedInput(std::function occupied, unsigned int connectorId = 1); //Input if instead of Available, send StatusNotification Preparing / Finishing +//Input for further MeterValue types with more options. See "MeterValue.h" for how to use it +bool mo_addMeterValueInput(MO_Context *ctx, unsigned int evseId, MO_MeterInput meterValueInput); -void setStartTxReadyInput(std::function startTxReady, unsigned int connectorId = 1); //Input if the charger is ready for StartTransaction +//Input if instead of Available, send StatusNotification Preparing / Finishing (OCPP 1.6) or Occupied (OCPP 2.0.1) +void mo_setOccupiedInput(bool (*occupied)()); +void mo_setOccupiedInput2(MO_Context *ctx, unsigned int evseId, bool (*occupied2)(unsigned int, void*), void *userData); -void setStopTxReadyInput(std::function stopTxReady, unsigned int connectorId = 1); //Input if charger is ready for StopTransaction +void mo_setStartTxReadyInput(bool (*startTxReady)()); //Input if the charger is ready for StartTransaction +void mo_setStartTxReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*startTxReady2)(unsigned int, void*), void *userData); -void setTxNotificationOutput(std::function notificationOutput, unsigned int connectorId = 1); //called when transaction state changes (see TxNotification for possible events). Transaction can be null +void mo_setStopTxReadyInput(bool (*stopTxReady)()); //Input if charger is ready for StopTransaction +void mo_setStopTxReadyInput2(MO_Context *ctx, unsigned int evseId, bool (*stopTxReady2)(unsigned int, void*), void *userData); -#if MO_ENABLE_V201 -void setTxNotificationOutputV201(std::function notificationOutput, unsigned int connectorId = 1); -#endif //MO_ENABLE_V201 +//Called when transaction state changes (see "TransactionDefs.h" for possible events). Transaction can be null +void mo_setTxNotificationOutput(void (*txNotificationOutput)(MO_TxNotification)); +void mo_setTxNotificationOutput2(MO_Context *ctx, unsigned int evseId, void (*txNotificationOutput2)(MO_TxNotification, unsigned int, void*), void *userData); #if MO_ENABLE_CONNECTOR_LOCK /* * Set an InputOutput (reads and sets information at the same time) for forcing to unlock the * connector. Called as part of the OCPP operation "UnlockConnector" * Return values: - * - UnlockConnectorResult_Pending if action needs more time to complete (MO will call this cb again later or eventually time out) - * - UnlockConnectorResult_Unlocked if successful - * - UnlockConnectorResult_UnlockFailed if not successful (e.g. lock stuck) + * - MO_UnlockConnectorResult_Pending if action needs more time to complete (MO will call this cb again later or eventually time out) + * - MO_UnlockConnectorResult_Unlocked if successful + * - MO_UnlockConnectorResult_UnlockFailed if not successful (e.g. lock stuck) */ -void setOnUnlockConnectorInOut(std::function onUnlockConnectorInOut, unsigned int connectorId = 1); +void mo_setOnUnlockConnector(MO_UnlockConnectorResult (*onUnlockConnector)()); +void mo_setOnUnlockConnector2(MO_Context *ctx, unsigned int evseId, MO_UnlockConnectorResult (*onUnlockConnector2)(unsigned int, void*), void *userData); #endif //MO_ENABLE_CONNECTOR_LOCK + /* * Access further information about the internal state of the library */ -bool isOperative(unsigned int connectorId = 1); //if the charge point is operative (see OCPP1.6 Edit2, p. 45) and ready for transactions +bool mo_isOperative(); //if the charge point is operative (see OCPP1.6 Edit2, p. 45) and ready for transactions +bool mo_isOperative2(MO_Context *ctx, unsigned int evseId); -/* - * Configure the device management - */ +bool mo_isConnected(); //Returns WebSocket connection state +bool mo_isConnected2(MO_Context *ctx); -void setOnResetNotify(std::function onResetNotify); //call onResetNotify(isHard) before Reset. If you return false, Reset will be aborted. Optional +bool mo_isAcceptedByCsms(); //Returns if BootNotification has succeeded +bool mo_isAcceptedByCsms2(MO_Context *ctx); -void setOnResetExecute(std::function onResetExecute); //reset handler. This function should reboot this controller immediately. Already defined for the ESP32 on Arduino +int32_t mo_getUnixTime(); //Returns unix time (seconds since 1970-01-01 if known, 0 if unknown) +int32_t mo_getUnixTime2(MO_Context *ctx); +bool mo_setUnixTime(int32_t unixTime); //If the charger has an RTC, pre-init MO with the RTC time. On first connection, MO will sync with the server +bool mo_setUnixTime2(MO_Context *ctx, int32_t unixTime); +int32_t mo_getUptime(); //Returns seconds since boot +int32_t mo_getUptime2(MO_Context *ctx); -namespace MicroOcpp { -class FirmwareService; -class DiagnosticsService; -} +int mo_getOcppVersion(); //Returns either `MO_OCPP_V16` or `MO_OCPP_V201` +int mo_getOcppVersion2(MO_Context *ctx); /* - * You need to configure this object if FW updates are relevant for you. This project already - * brings a simple configuration for the ESP32 and ESP8266 for prototyping purposes, however - * for the productive system you will have to develop a configuration targeting the specific - * OCPP backend. - * See MicroOcpp/Model/FirmwareManagement/FirmwareService.h - * - * Lazy initialization: The FW Service will be created at the first call to this function - * - * To use, add `#include ` + * Configure the device management. Set before `mo_setup()` */ -MicroOcpp::FirmwareService *getFirmwareService(); -/* - * This library implements the OCPP messaging side of Diagnostics, but no logging or the - * log upload to your backend. - * To integrate Diagnostics, see MicroOcpp/Model/Diagnostics/DiagnosticsService.h - * - * Lazy initialization: The Diag Service will be created at the first call to this function - * - * To use, add `#include ` - */ -MicroOcpp::DiagnosticsService *getDiagnosticsService(); +//Reset handler. `onResetExecute` should reboot the controller. Already implemented for +//the ESP32 on Arduino +//Simpler reset handler version which supports OCPP 1.6 and 2.0.1 +void mo_setOnResetExecute(void (*onResetExecute)()); -#if MO_ENABLE_CERT_MGMT -/* - * Set a custom Certificate Store which implements certificate updates on the host system. - * MicroOcpp will forward OCPP-side update requests to the certificate store, as well as - * query the certificate store upon server request. - * - * To enable OCPP-side certificate updates (UCs M03 - M05), set the build flag - * MO_ENABLE_CERT_MGMT=1 so that this function becomes accessible. - * - * To use the built-in certificate store (depends on MbedTLS), set the build flag - * MO_ENABLE_MBEDTLS=1. To not use the built-in implementation, but still enable MbedTLS, - * additionally set MO_ENABLE_CERT_STORE_MBEDTLS=0. - */ -void setCertificateStore(std::unique_ptr certStore); -#endif //MO_ENABLE_CERT_MGMT +#if MO_ENABLE_V16 +//MO calls onResetNotify(isHard) before Reset. If you return false, Reset will be aborted. Optional for Reset feature +void mo_v16_setOnResetNotify(bool (*onResetNotify)(bool)); +void mo_v16_setOnResetNotify2(MO_Context *ctx, bool (*onResetNotify2)(bool, void*), void *userData); + +//Reset handler. This function should reboot this controller immediately. Already defined for the ESP32 on Arduino +void mo_v16_setOnResetExecute(void (*onResetExecute)(bool)); +void mo_v16_setOnResetExecute2(MO_Context *ctx, bool (*onResetExecute2)(bool, void*), void *userData); +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 +//MO calls onResetNotify(isHard) before Reset. If you return false, Reset will be aborted. Optional for Reset feature +void mo_v201_setOnResetNotify(bool (*onResetNotify)(MO_ResetType)); +void mo_v201_setOnResetNotify2(MO_Context *ctx, unsigned int evseId, bool (*onResetNotify2)(MO_ResetType, unsigned int, void*), void *userData); + +//Reset handler. This function should reboot this controller immediately. Already defined for the ESP32 on Arduino +void mo_v201_setOnResetExecute(void (*onResetExecute)()); //identical to `mo_setOnResetExecute` +void mo_v201_setOnResetExecute2(MO_Context *ctx, unsigned int evseId, bool (*onResetExecute2)(unsigned int, void*), void *userData); +#endif //MO_ENABLE_V201 + +#if MO_ENABLE_MBEDTLS +//Set FTP security parameters (e.g. client cert). See "FtpMbedTLS.h" for all options +//To use a custom FTP client, subclass `FtpClient` (see "Ftp.h") and pass to C++ `Context` object +void mo_setFtpConfig(MO_Context *ctx, MO_FTPConfig ftpConfig); + +#if MO_ENABLE_DIAGNOSTICS +//Provide MO with diagnostics data for the "GetDiagnostics" operation. Should contain human-readable text which +//is useful for troubleshooting, e.g. heap occupation, all sensor readings, any device state and config which +//isn't directly exposed via OCPP operation. +//For more documentation, see "DiagnosticsService.h" +void mo_setDiagnosticsReader(MO_Context *ctx, + size_t (*readBytes)(char *buf, size_t size, void *user_data), //reads diags into `buf`, having `size` bytes + void(*onClose)(void *user_data), //MO calls this when diags upload is terminated + void *userData); //implementer-defined pointer + +void mo_setDiagnosticsFtpServerCert(MO_Context *ctx, const char *cert); //zero-copy, i.e. cert must outlive MO +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT +//Implement custom Firmware upgrade. MO downloads the firmware binary via FTP and passes it chunk by chunk to +//the `writeBytes` callback. After the FTP download, MO terminates the firmware upgrade by calling `onClose`. +//For more documentation, see "FirmwareService.h" +//void mo_setFirmwareDownloadWriter( +// size_t (*writeBytes)(const unsigned char *buf, size_t size, void *userData), //writes `buf`, having `size` bytes to OTA partition +// void (*onClose)(MO_FtpCloseReason, void *userData), //MO calls this when the FTP was closed +// void *userData); //implementer-defined pointer +// +//void mo_setFirmwareFtpServerCert(const char *cert); //zero-copy, i.e. cert must outlive MO +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT + +#endif //MO_ENABLE_MBEDTLS /* - * Add features and customize the behavior of the OCPP client + * Transaction management (Advanced) */ -namespace MicroOcpp { -class Context; -} +bool mo_isTransactionAuthorized(); +bool mo_isTransactionAuthorized2(MO_Context *ctx, unsigned int evseId); -//Get access to internal functions and data structures. The returned Context object allows -//you to bypass the facade functions of this header and implement custom functionality. -//To use, add `#include ` -MicroOcpp::Context *getOcppContext(); +//Returns if server has rejected transaction in StartTx or TransactionEvent response +bool mo_isTransactionDeauthorized(); +bool mo_isTransactionDeauthorized2(MO_Context *ctx, unsigned int evseId); + +//Returns start unix time of transaction. Returns 0 if unix time is not known +int32_t mo_getTransactionStartUnixTime(); +int32_t mo_getTransactionStartUnixTime2(MO_Context *ctx, unsigned int evseId); + +#if MO_ENABLE_V16 +//Overrides start unix time of transaction +void mo_setTransactionStartUnixTime(int32_t unixTime); +void mo_setTransactionStartUnixTime2(MO_Context *ctx, unsigned int evseId, int32_t unixTime); + +//Returns stop unix time of transaction. Returns 0 if unix time is not known +int32_t mo_getTransactionStopUnixTime(); +int32_t mo_getTransactionStopUnixTime2(MO_Context *ctx, unsigned int evseId); + +//Overrides stop unix time of transaction +void mo_setTransactionStopUnixTime(int32_t unixTime); +void mo_setTransactionStopUnixTime2(MO_Context *ctx, unsigned int evseId, int32_t unixTime); + +//Returns meterStart of transaction. Returns -1 if meterStart is undefined +int32_t mo_getTransactionMeterStart(); +int32_t mo_getTransactionMeterStart2(MO_Context *ctx, unsigned int evseId); + +//Overrides meterStart of transaction +void mo_setTransactionMeterStart(int32_t meterStart); +void mo_setTransactionMeterStart2(MO_Context *ctx, unsigned int evseId, int32_t meterStart); + +//Returns meterStop of transaction. Returns -1 if meterStop is undefined +int32_t mo_getTransactionMeterStop(); +int32_t mo_getTransactionMeterStop2(MO_Context *ctx, unsigned int evseId); +//Overrides meterStop of transaction +void mo_setTransactionMeterStop(int32_t meterStop); +void mo_setTransactionMeterStop2(MO_Context *ctx, unsigned int evseId, int32_t meterStop); +#endif //MO_ENABLE_V16 + + +#if MO_ENABLE_V16 /* - * Set a listener which is notified when the OCPP lib processes an incoming operation of type - * operationType. After the operation has been interpreted, onReceiveReq will be called with - * the original message from the OCPP server. - * - * Example usage: - * - * setOnReceiveRequest("SetChargingProfile", [] (JsonObject payload) { - * Serial.print("[main] received charging profile for connector: "; //Arduino print function - * Serial.printf("update connector %i with chargingProfileId %i\n", - * payload["connectorId"], //ArduinoJson object access - * payload["csChargingProfiles"]["chargingProfileId"]); - * }); + * Access OCPP Configurations (OCPP 1.6) */ -void setOnReceiveRequest(const char *operationType, OnReceiveReqListener onReceiveReq); +//Add new Config. `key` is zero-copy, i.e. must outlive the MO lifecycle. Add before `mo_setup()`. +//At first boot, MO uses `factoryDefault`. At subsequent boots, it loads the values from flash +//during `mo_setup()` +bool mo_declareConfigurationInt(const char *key, int factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); +bool mo_declareConfigurationBool(const char *key, bool factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); +bool mo_declareConfigurationString(const char *key, const char *factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); + +//Set Config value. Set after `mo_setup()` +bool mo_setConfigurationInt(const char *key, int value); +bool mo_setConfigurationBool(const char *key, bool value); +bool mo_setConfigurationString(const char *key, const char *value); + +//Get Config value. MO writes the value into `valueOut`. Call after `mo_setup()` +bool mo_getConfigurationInt(const char *key, int *valueOut); +bool mo_getConfigurationBool(const char *key, bool *valueOut); +bool mo_getConfigurationString(const char *key, const char **valueOut); //need to copy `valueOut` into own buffer. `valueOut` may be invalidated after call +#endif //MO_ENABLE_V16 /* - * Set a listener which is notified when the OCPP lib sends the confirmation to an incoming - * operation of type operation type. onSendConf will be passed the original output of the - * OCPP lib. - * - * Example usage: - * - * setOnSendConf("RemoteStopTransaction", [] (JsonObject payload) -> void { - * if (!strcmp(payload["status"], "Rejected")) { - * //the OCPP lib rejected the RemoteStopTransaction command. In this example, the customer - * //wishes to stop the running transaction in any case and to log this case - * endTransaction(nullptr, "Remote"); //end transaction and send StopTransaction - * Serial.println("[main] override rejected RemoteStopTransaction"); //Arduino print function - * } - * }); - * + * Access OCPP Configurations (OCPP 1.6) or Variables (OCPP 2.0.1) + * Backwards-compatible: if MO is initialized with OCPP 1.6, then these functions access Configurations */ -void setOnSendConf(const char *operationType, OnSendConfListener onSendConf); +//Add new Variable or Config. If running OCPP 2.0.1, `key16` can be NULL. If running OCPP 1.6, +//`component201` and `name201` can be NULL. `component201`, `name201` and `key16` are zero-copy, +//i.e. must outlive the MO lifecycle. Add before `mo_setup()`. At first boot, MO uses `factoryDefault`. +//At subsequent boots, it loads the values from flash during `mo_setup()` +bool mo_declareVarConfigInt(MO_Context *ctx, const char *component201, const char *name201, const char *key16, int factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); +bool mo_declareVarConfigBool(MO_Context *ctx, const char *component201, const char *name201, const char *key16, bool factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); +bool mo_declareVarConfigString(MO_Context *ctx, const char *component201, const char *name201, const char *key16, const char *factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); + +//Set Variable or Config value. Set after `mo_setup()`. If running OCPP 2.0.1, `key16` can be NULL +bool mo_setVarConfigInt(MO_Context *ctx, const char *component201, const char *name201, const char *key16, int value); +bool mo_setVarConfigBool(MO_Context *ctx, const char *component201, const char *name201, const char *key16, bool value); +bool mo_setVarConfigString(MO_Context *ctx, const char *component201, const char *name201, const char *key16, const char *value); + +//Get Config value. MO writes the value into `valueOut`. Call after `mo_setup()`. If running +//OCPP 2.0.1, `key16` can be NULL +bool mo_getVarConfigInt(MO_Context *ctx, const char *component201, const char *name201, const char *key16, int *valueOut); +bool mo_getVarConfigBool(MO_Context *ctx, const char *component201, const char *name201, const char *key16, bool *valueOut); +bool mo_getVarConfigString(MO_Context *ctx, const char *component201, const char *name201, const char *key16, const char **valueOut); //need to copy `valueOut` into own buffer. `valueOut` may be invalidated after call /* - * Create and send an operation without using the built-in Operation class. This function bypasses - * the business logic which comes with this library. E.g. you can send unknown operations, extend - * OCPP or replace parts of the business logic with custom behavior. - * - * Use case 1, extend the library by sending additional operations. E.g. DataTransfer: - * - * sendRequest("DataTransfer", [] () -> std::unique_ptr { - * //will be called to create the request once this operation is being sent out - * size_t capacity = JSON_OBJECT_SIZE(3) + - * JSON_OBJECT_SIZE(2); //for calculating the required capacity, see https://arduinojson.org/v6/assistant/ - * auto res = std::unique_ptr(new MicroOcpp::JsonDoc(capacity)); - * JsonObject request = *res; - * request["vendorId"] = "My company Ltd."; - * request["messageId"] = "TargetValues"; - * request["data"]["battery_capacity"] = 89; - * request["data"]["battery_soc"] = 34; - * return res; - * }, [] (JsonObject response) -> void { - * //will be called with the confirmation response of the server - * if (!strcmp(response["status"], "Accepted")) { - * //DataTransfer has been accepted - * int max_energy = response["data"]["max_energy"]; - * } - * }); - * - * Use case 2, bypass the business logic of this library for custom behavior. E.g. StartTransaction: - * - * sendRequest("StartTransaction", [] () -> std::unique_ptr { - * //will be called to create the request once this operation is being sent out - * size_t capacity = JSON_OBJECT_SIZE(4); //for calculating the required capacity, see https://arduinojson.org/v6/assistant/ - * auto res = std::unique_ptr(new MicroOcpp::JsonDoc(capacity)); - * JsonObject request = res->to(); - * request["connectorId"] = 1; - * request["idTag"] = "A9C3CE1D7B71EA"; - * request["meterStart"] = 1234; - * request["timestamp"] = "2023-06-01T11:07:43Z"; //e.g. some historic transaction - * return res; - * }, [] (JsonObject response) -> void { - * //will be called with the confirmation response of the server - * const char *status = response["idTagInfo"]["status"]; - * int transactionId = response["transactionId"]; - * }); - * - * In Use case 2, the library won't send any further StatusNotification or StopTransaction on - * its own. + * Callback-based Config or Variable integration (shared API between OCPP 1.6 and 2.0.1). This + * allows to implement custom key-value stores and bypass the built-in implementation of MO. + * MO uses `getValue` to read the Config/Variable value and `setValue` to update the value after + * receiving a ChangeConfiguration or SetVariables request. */ -void sendRequest(const char *operationType, - std::function ()> fn_createReq, - std::function fn_processConf); +//bool mo_addCustomVarConfigInt(const char *component201, const char *name201, const char *key16, int (*getValue)(const char*, const char*, const char*, void*), void (*setValue)(int, const char*, const char*, const char*, void*), void *userData); +//bool mo_addCustomVarConfigBool(const char *component201, const char *name201, const char *key16, bool (*getValue)(const char*, const char*, const char*, void*), void (*setValue)(bool, const char*, const char*, const char*, void*), void *userData); +//bool mo_addCustomVarConfigString(const char *component201, const char *name201, const char *key16, const char* (*getValue)(const char*, const char*, const char*, void*), bool (*setValue)(const char*, const char*, const char*, const char*, void*), void *userData); /* - * Set a custom handler for an incoming operation type. This will update the core Operation registry - * of the library, potentially replacing the built-in Operation handler and bypassing the - * business logic of this library. - * - * Note that when replacing an operation handler, the attached listeners will be reset. - * - * Example usage: - * - * setRequestHandler("DataTransfer", [] (JsonObject request) -> void { - * //will be called with the request message from the server - * const char *vendorId = request["vendorId"]; - * const char *messageId = request["messageId"]; - * int battery_capacity = request["data"]["battery_capacity"]; - * int battery_soc = request["data"]["battery_soc"]; - * }, [] () -> std::unique_ptr { - * //will be called to create the response once this operation is being sent out - * size_t capacity = JSON_OBJECT_SIZE(2) + - * JSON_OBJECT_SIZE(1); //for calculating the required capacity, see https://arduinojson.org/v6/assistant/ - * auto res = std::unique_ptr(new MicroOcpp::JsonDoc(capacity)); - * JsonObject response = res->to(); - * response["status"] = "Accepted"; - * response["data"]["max_energy"] = 59; - * return res; - * }); + * Add features and customize the behavior of the OCPP client */ -void setRequestHandler(const char *operationType, - std::function fn_processReq, - std::function ()> fn_createConf); + +#if __cplusplus +namespace MicroOcpp { +class Context; +} + +//Get access to internal functions and data structures. The returned Context object allows +//you to bypass the facade functions of this header and implement custom functionality. +//To use, add `#include ` +MicroOcpp::Context *mo_getContext(); +MicroOcpp::Context *mo_getContext2(MO_Context *ctx); +#endif //__cplusplus /* - * Send OCPP operations manually not bypassing the internal business logic - * - * On receipt of the .conf() response the library calls the callback function - * "OnReceiveConfListener onConf" and passes the OCPP payload to it. - * - * For your first EVSE integration, the `onReceiveConfListener` is probably sufficient. For - * advanced EVSE projects, the other listeners likely become relevant: - * - `onAbortListener`: will be called whenever the engine stops trying to finish an operation - * normally which was initiated by this device. - * - `onTimeoutListener`: will be executed when the operation is not answered until the timeout - * expires. Note that timeouts also trigger the `onAbortListener`. - * - `onReceiveErrorListener`: will be called when the Central System returns a CallError. - * Again, each error also triggers the `onAbortListener`. - * - * The functions for sending OCPP operations are non-blocking. The program will resume immediately - * with the code after with the subsequent code in any case. + * Explicit context lifecycle. Avoids the internal `g_context` variable. Use if: + * - Explicit lifecycle is preferred (may fit the architecture better) + * - To run multiple instances of MO in parallel */ - -void authorize( - const char *idTag, //RFID tag (e.g. ISO 14443 UID tag with 4 or 7 bytes) - OnReceiveConfListener onConf = nullptr, //callback (confirmation received) - OnAbortListener onAbort = nullptr, //callback (confirmation not received), optional - OnTimeoutListener onTimeout = nullptr, //callback (timeout expired), optional - OnReceiveErrorListener onError = nullptr, //callback (error code received), optional - unsigned int timeout = 0); //custom timeout behavior, optional - -bool startTransaction( - const char *idTag, - OnReceiveConfListener onConf = nullptr, - OnAbortListener onAbort = nullptr, - OnTimeoutListener onTimeout = nullptr, - OnReceiveErrorListener onError = nullptr, - unsigned int timeout = 0); - -bool stopTransaction( - OnReceiveConfListener onConf = nullptr, - OnAbortListener onAbort = nullptr, - OnTimeoutListener onTimeout = nullptr, - OnReceiveErrorListener onError = nullptr, - unsigned int timeout = 0); +MO_Context *mo_initialize2(); +bool mo_setup2(MO_Context *ctx); +void mo_deinitialize2(MO_Context *ctx); +void mo_loop2(MO_Context *ctx); + +//Send operation manually and bypass MO business logic +bool mo_sendRequest(MO_Context *ctx, + const char *operationType, //zero-copy, i.e. must outlive MO + const char *payloadJson, //copied, i.e. can be invalidated + void (*onResponse)(const char *payloadJson, void *userData), + void (*onAbort)(void *userData), + void *userData); + +//Set custom operation handler for incoming reqeusts and bypass MO business logic +bool mo_setRequestHandler(MO_Context *ctx, const char *operationType, + void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), + int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), + void *userData); + +//Sniff incoming requests without control over the response +bool mo_setOnReceiveRequest(MO_Context *ctx, const char *operationType, + void (*onRequest)(const char *operationType, const char *payloadJson, void *userData), + void *userData); + +//Sniff outgoing responses generated by MO +bool mo_setOnSendConf(MO_Context *ctx, const char *operationType, + void (*onSendConf)(const char *operationType, const char *payloadJson, void *userData), + void *userData); #endif diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp new file mode 100644 index 00000000..c42cb0d2 --- /dev/null +++ b/src/MicroOcpp/Context.cpp @@ -0,0 +1,317 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include +#include +#include +#include +#include +#include + +#include + +using namespace MicroOcpp; + +Context::Context() : MemoryManaged("Context") { +#if MO_USE_FILEAPI != MO_CUSTOM_FS + memset(&filesystemConfig, 0, sizeof(filesystemConfig)); + filesystemConfig.opt = MO_FS_OPT_USE_MOUNT; + filesystemConfig.path_prefix = MO_FILENAME_PREFIX; +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + +#if MO_WS_USE != MO_WS_CUSTOM + memset(&connectionConfig, 0, sizeof(connectionConfig)); +#endif //MO_WS_USE != MO_WS_CUSTOM + +#if MO_ENABLE_MBEDTLS + memset(&ftpConfig, 0, sizeof(ftpConfig)); +#endif //MO_ENABLE_MBEDTLS +} + +Context::~Context() { + // reset resources (frees resources if owning them) + setFilesystem(nullptr); + setConnection(nullptr); + setFtpClient(nullptr); + setCertificateStore(nullptr); +} + +void Context::setDebugCb(void (*debugCb)(const char *msg)) { + debug.setDebugCb(debugCb); // `debug` globally declared in Debug.h +} + +void Context::setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)) { + debug.setDebugCb2(debugCb2); // `debug` globally declared in Debug.h +} + +void Context::setTicksCb(unsigned long (*ticksCb)()) { + this->ticksCb = ticksCb; +} + +unsigned long Context::getTicksMs() { + if (this->ticksCb) { + return this->ticksCb(); + } else { + MO_DBG_ERR("ticksCb not set yet"); + return 0; + } +} + +void Context::setRngCb(uint32_t (*rngCb)()) { + this->rngCb = rngCb; +} + +uint32_t (*Context::getRngCb())() { + return rngCb; +} + +#if MO_USE_FILEAPI != MO_CUSTOM_FS +void Context::setDefaultFilesystemConfig(MO_FilesystemConfig filesystemConfig) { + this->filesystemConfig = filesystemConfig; +} +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + +void Context::setFilesystem(MO_FilesystemAdapter *filesystem) { +#if MO_USE_FILEAPI != MO_CUSTOM_FS + if (this->filesystem && isFilesystemOwner) { + mo_freeDefaultFilesystemAdapter(this->filesystem); + this->filesystem = nullptr; + } + isFilesystemOwner = false; +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + this->filesystem = filesystem; +} + +MO_FilesystemAdapter *Context::getFilesystem() { + return filesystem; +} + +#if MO_WS_USE != MO_WS_CUSTOM +void Context::setDefaultConnectionConfig(MO_ConnectionConfig connectionConfig) { + this->connectionConfig = connectionConfig; +} +#endif //MO_WS_USE != MO_WS_CUSTOM + +void Context::setConnection(Connection *connection) { + if (this->connection) { + this->connection->setContext(nullptr); + } +#if MO_WS_USE != MO_WS_CUSTOM + if (this->connection && isConnectionOwner) { + freeDefaultConnection(this->connection); + this->connection = nullptr; + } + isConnectionOwner = false; +#endif //MO_WS_USE != MO_WS_CUSTOM + this->connection = connection; + if (this->connection) { + this->connection->setContext(this); + } +} + +Connection *Context::getConnection() { + return connection; +} + +void Context::setFtpClient(FtpClient *ftpClient) { + if (this->ftpClient && isFtpClientOwner) { + delete this->ftpClient; + this->ftpClient = nullptr; + } + isFtpClientOwner = false; + this->ftpClient = ftpClient; +} + +FtpClient *Context::getFtpClient() { + return ftpClient; +} + +void Context::setCertificateStore(CertificateStore *certStore) { + if (this->certStore && isCertStoreOwner) { + delete this->certStore; + this->certStore = nullptr; + } + isCertStoreOwner = false; + this->certStore = certStore; +} + +CertificateStore *Context::getCertificateStore() { + return certStore; +} + +Clock& Context::getClock() { + return clock; +} + +MessageService& Context::getMessageService() { + return msgService; +} + +bool Context::setOcppVersion(int ocppVersion) { + if (ocppVersion == MO_OCPP_V16 && MO_ENABLE_V16) { + this->ocppVersion = ocppVersion; + return true; + } else if (ocppVersion == MO_OCPP_V201 && MO_ENABLE_V201) { + this->ocppVersion = ocppVersion; + return true; + } else { + MO_DBG_ERR("unsupported OCPP version"); + return false; + } +} + +int Context::getOcppVersion() { + return ocppVersion; +} + +#if MO_ENABLE_V16 +Ocpp16::Model& Context::getModel16() { + return modelV16; +} +#endif + +#if MO_ENABLE_V201 +Ocpp201::Model& Context::getModel201() { + return modelV201; +} +#endif + +#if MO_ENABLE_V16 && !MO_ENABLE_V201 +Model& Context::getModel() { + return getModel16(); +} +#elif !MO_ENABLE_V16 && MO_ENABLE_V201 +Model& Context::getModel() { + return getModel201(); +} +#endif + +bool Context::setup() { + + if (!debug.setup()) { + MO_DBG_ERR("must set debugCb before"); //message won't show up on console + return false; + } + + if (!ticksCb) { + ticksCb = getDefaultTickCb(); + + if (!ticksCb) { + MO_DBG_ERR("must set TicksCb connection before setup. See the examples in this repository"); + return false; + } + } + + if (!rngCb) { + rngCb = getDefaultRngCb(); + if (!rngCb) { + MO_DBG_ERR("random number generator cannot be found"); + return false; + } + } + +#if MO_USE_FILEAPI != MO_CUSTOM_FS + if (!filesystem && filesystemConfig.opt != MO_FS_OPT_DISABLE) { + // init default FS implementaiton + filesystem = mo_makeDefaultFilesystemAdapter(filesystemConfig); + if (!filesystem) { + MO_DBG_ERR("OOM"); + return false; + } + isFilesystemOwner = true; + } +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + + if (!filesystem) { + MO_DBG_DEBUG("initialize MO without filesystem access"); + } + +#if MO_WS_USE != MO_WS_CUSTOM + if (!connection) { + // init default Connection implementation + connection = static_cast(makeDefaultConnection(connectionConfig)); + if (!connection) { + MO_DBG_ERR("OOM"); + return false; + } + isConnectionOwner = true; + } +#endif //MO_WS_USE != MO_WS_CUSTOM + + if (!connection) { + MO_DBG_ERR("must set WebSocket connection before setup. See the examples in this repository"); + return false; + } + +#if MO_ENABLE_MBEDTLS + if (!ftpClient) { + ftpClient = makeFtpClientMbedTLS(ftpConfig).release(); + if (!ftpClient) { + MO_DBG_ERR(""); + return false; + } + isFtpClientOwner = true; + } +#endif //MO_ENABLE_MBEDTLS + + if (!ftpClient) { + MO_DBG_DEBUG("initialize MO without FTP client"); + } + + if (!certStore) { + #if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS + { + certStore = makeCertificateStoreMbedTLS(filesystem); + if (!certStore) { + return false; + } + isCertStoreOwner = true; + } + #endif //MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS + + if (MO_ENABLE_CERT_MGMT && !certStore) { + MO_DBG_DEBUG("initialize MO without certificate store"); + } + } + + if (!clock.setup()) { + return false; + } + + if (!msgService.setup()) { + return false; + } + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + modelV16.setup(); + } + #endif + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + modelV201.setup(); + } + #endif + + return true; +} + +void Context::loop() { + if (connection) { + connection->loop(); + } + clock.loop(); + msgService.loop(); + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + modelV16.loop(); + } + #endif + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + modelV201.loop(); + } + #endif +} diff --git a/src/MicroOcpp/Context.h b/src/MicroOcpp/Context.h new file mode 100644 index 00000000..e5d74f4e --- /dev/null +++ b/src/MicroOcpp/Context.h @@ -0,0 +1,134 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_CONTEXT_H +#define MO_CONTEXT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//Anonymous handle to pass Context object around API functions +struct MO_Context; +typedef struct MO_Context MO_Context; + +#ifdef __cplusplus +} //extern "C" { + +namespace MicroOcpp { + +class CertificateStore; + +class Context : public MemoryManaged { +private: + unsigned long (*ticksCb)() = nullptr; + uint32_t (*rngCb)() = nullptr; + +#if MO_USE_FILEAPI != MO_CUSTOM_FS + MO_FilesystemConfig filesystemConfig; + bool isFilesystemOwner = false; +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + MO_FilesystemAdapter *filesystem = nullptr; + +#if MO_WS_USE != MO_WS_CUSTOM + MO_ConnectionConfig connectionConfig; + bool isConnectionOwner = false; +#endif //MO_WS_USE != MO_WS_CUSTOM + Connection *connection = nullptr; + +#if MO_ENABLE_MBEDTLS + MO_FTPConfig ftpConfig; + bool isFtpClientOwner = false; +#endif //MO_ENABLE_MBEDTLS + FtpClient *ftpClient = nullptr; + + CertificateStore *certStore = nullptr; + bool isCertStoreOwner = false; + + Clock clock {*this}; + MessageService msgService {*this}; + +#if MO_ENABLE_V16 + Ocpp16::Model modelV16 {*this}; +#endif +#if MO_ENABLE_V201 + Ocpp201::Model modelV201 {*this}; +#endif + int ocppVersion = MO_ENABLE_V16 ? MO_OCPP_V16 : MO_ENABLE_V201 ? MO_OCPP_V201 : -1; + +public: + Context(); + ~Context(); + + void setDebugCb(void (*debugCb)(const char *msg)); + void setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)); + + void setTicksCb(unsigned long (*ticksCb)()); + unsigned long getTicksMs(); + + void setRngCb(uint32_t (*rngCb)()); + uint32_t (*getRngCb())(); + +#if MO_USE_FILEAPI != MO_CUSTOM_FS + void setDefaultFilesystemConfig(MO_FilesystemConfig filesystemConfig); +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + void setFilesystem(MO_FilesystemAdapter *filesystem); + MO_FilesystemAdapter *getFilesystem(); + +#if MO_WS_USE != MO_WS_CUSTOM + void setDefaultConnectionConfig(MO_ConnectionConfig connectionConfig); +#endif //MO_WS_USE != MO_WS_CUSTOM + void setConnection(Connection *connection); + Connection *getConnection(); + +#if MO_ENABLE_MBEDTLS + void setDefaultFtpConfig(MO_FTPConfig ftpConfig); +#endif //MO_ENABLE_MBEDTLS + void setFtpClient(FtpClient *ftpClient); + FtpClient *getFtpClient(); + + void setCertificateStore(CertificateStore *certStore); + CertificateStore *getCertificateStore(); + + Clock& getClock(); + MessageService& getMessageService(); + + bool setOcppVersion(int ocppVersion); + int getOcppVersion(); + +#if MO_ENABLE_V16 + Ocpp16::Model& getModel16(); +#endif + +#if MO_ENABLE_V201 + Ocpp201::Model& getModel201(); +#endif + + // eliminate OCPP version specifiers if charger supports just one version anyway +#if MO_ENABLE_V16 && !MO_ENABLE_V201 + Model& getModel(); +#elif !MO_ENABLE_V16 && MO_ENABLE_V201 + Model& getModel(); +#endif + + bool setup(); + + void loop(); +}; + +} //namespace MicroOcpp + +#endif // __cplusplus +#endif diff --git a/src/MicroOcpp/Core/Configuration.cpp b/src/MicroOcpp/Core/Configuration.cpp deleted file mode 100644 index cb0a8ff5..00000000 --- a/src/MicroOcpp/Core/Configuration.cpp +++ /dev/null @@ -1,258 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include -#include - -#include -#include -#include - -namespace MicroOcpp { - -struct Validator { - const char *key = nullptr; - std::function checkValue; - Validator(const char *key, std::function checkValue) : key(key), checkValue(checkValue) { - - } -}; - -namespace ConfigurationLocal { - -std::shared_ptr filesystem; -auto configurationContainers = makeVector>("v16.Configuration.Containers"); -auto validators = makeVector("v16.Configuration.Validators"); - -} - -using namespace ConfigurationLocal; - -std::unique_ptr createConfigurationContainer(const char *filename, bool accessible) { - //create non-persistent Configuration store (i.e. lives only in RAM) if - // - Flash FS usage is switched off OR - // - Filename starts with "/volatile" - if (!filesystem || - !strncmp(filename, CONFIGURATION_VOLATILE, strlen(CONFIGURATION_VOLATILE))) { - return makeConfigurationContainerVolatile(filename, accessible); - } else { - //create persistent Configuration store. This is the normal case - return makeConfigurationContainerFlash(filesystem, filename, accessible); - } -} - - -void addConfigurationContainer(std::shared_ptr container) { - configurationContainers.push_back(container); -} - -std::shared_ptr getContainer(const char *filename) { - auto container = std::find_if(configurationContainers.begin(), configurationContainers.end(), - [filename](decltype(configurationContainers)::value_type &elem) { - return !strcmp(elem->getFilename(), filename); - }); - - if (container != configurationContainers.end()) { - return *container; - } else { - return nullptr; - } -} - -ConfigurationContainer *declareContainer(const char *filename, bool accessible) { - - auto container = getContainer(filename); - - if (!container) { - MO_DBG_DEBUG("init new configurations container: %s", filename); - - container = createConfigurationContainer(filename, accessible); - if (!container) { - MO_DBG_ERR("OOM"); - return nullptr; - } - configurationContainers.push_back(container); - } - - if (container->isAccessible() != accessible) { - MO_DBG_ERR("%s: conflicting accessibility declarations (expect %s)", filename, container->isAccessible() ? "accessible" : "inaccessible"); - } - - return container.get(); -} - -std::shared_ptr loadConfiguration(TConfig type, const char *key, bool accessible) { - for (auto& container : configurationContainers) { - if (auto config = container->getConfiguration(key)) { - if (config->getType() != type) { - MO_DBG_ERR("conflicting type for %s - remove old config", key); - container->remove(config.get()); - continue; - } - if (container->isAccessible() != accessible) { - MO_DBG_ERR("conflicting accessibility for %s", key); - } - container->loadStaticKey(*config.get(), key); - return config; - } - } - return nullptr; -} - -template -bool loadFactoryDefault(Configuration& config, T loadFactoryDefault); - -template<> -bool loadFactoryDefault(Configuration& config, int factoryDef) { - config.setInt(factoryDef); - return true; -} - -template<> -bool loadFactoryDefault(Configuration& config, bool factoryDef) { - config.setBool(factoryDef); - return true; -} - -template<> -bool loadFactoryDefault(Configuration& config, const char *factoryDef) { - return config.setString(factoryDef); -} - -void loadPermissions(Configuration& config, bool readonly, bool rebootRequired) { - if (readonly) { - config.setReadOnly(); - } - - if (rebootRequired) { - config.setRebootRequired(); - } -} - -template -std::shared_ptr declareConfiguration(const char *key, T factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible) { - - std::shared_ptr res = loadConfiguration(convertType(), key, accessible); - if (!res) { - auto container = declareContainer(filename, accessible); - if (!container) { - return nullptr; - } - - res = container->createConfiguration(convertType(), key); - if (!res) { - return nullptr; - } - - if (!loadFactoryDefault(*res.get(), factoryDef)) { - container->remove(res.get()); - return nullptr; - } - } - - loadPermissions(*res.get(), readonly, rebootRequired); - return res; -} - -template std::shared_ptr declareConfiguration(const char *key, int factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible); -template std::shared_ptr declareConfiguration(const char *key, bool factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible); -template std::shared_ptr declareConfiguration(const char *key, const char *factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible); - -std::function *getConfigurationValidator(const char *key) { - for (auto& v : validators) { - if (!strcmp(v.key, key)) { - return &v.checkValue; - } - } - return nullptr; -} - -void registerConfigurationValidator(const char *key, std::function validator) { - for (auto& v : validators) { - if (!strcmp(v.key, key)) { - v.checkValue = validator; - return; - } - } - validators.push_back(Validator{key, validator}); -} - -Configuration *getConfigurationPublic(const char *key) { - for (auto& container : configurationContainers) { - if (container->isAccessible()) { - if (auto res = container->getConfiguration(key)) { - return res.get(); - } - } - } - - return nullptr; -} - -Vector getConfigurationContainersPublic() { - auto res = makeVector("v16.Configuration.Containers"); - - for (auto& container : configurationContainers) { - if (container->isAccessible()) { - res.push_back(container.get()); - } - } - - return res; -} - -bool configuration_init(std::shared_ptr _filesystem) { - filesystem = _filesystem; - return true; -} - -void configuration_deinit() { - makeVector("v16.Configuration.Containers").swap(configurationContainers); //release allocated memory (see https://cplusplus.com/reference/vector/vector/clear/) - makeVector("v16.Configuration.Validators").swap(validators); - filesystem.reset(); -} - -bool configuration_load(const char *filename) { - bool success = true; - - for (auto& container : configurationContainers) { - if ((!filename || !strcmp(filename, container->getFilename())) && !container->load()) { - success = false; - } - } - - return success; -} - -bool configuration_save() { - bool success = true; - - for (auto& container : configurationContainers) { - if (!container->save()) { - success = false; - } - } - - return success; -} - -bool configuration_clean_unused() { - for (auto& container : configurationContainers) { - container->removeUnused(); - } - return configuration_save(); -} - -bool VALIDATE_UNSIGNED_INT(const char *value) { - for(size_t i = 0; value[i] != '\0'; i++) { - if (value[i] < '0' || value[i] > '9') { - return false; - } - } - return true; -} - -} //end namespace MicroOcpp diff --git a/src/MicroOcpp/Core/Configuration.h b/src/MicroOcpp/Core/Configuration.h deleted file mode 100644 index 486b9c69..00000000 --- a/src/MicroOcpp/Core/Configuration.h +++ /dev/null @@ -1,45 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CONFIGURATION_H -#define MO_CONFIGURATION_H - -#include -#include -#include -#include - -#include - -#define CONFIGURATION_FN (MO_FILENAME_PREFIX "ocpp-config.jsn") -#define CONFIGURATION_VOLATILE "/volatile" -#define MO_KEYVALUE_FN (MO_FILENAME_PREFIX "client-state.jsn") - -namespace MicroOcpp { - -template -std::shared_ptr declareConfiguration(const char *key, T factoryDefault, const char *filename = CONFIGURATION_FN, bool readonly = false, bool rebootRequired = false, bool accessible = true); - -std::function *getConfigurationValidator(const char *key); -void registerConfigurationValidator(const char *key, std::function validator); - -void addConfigurationContainer(std::shared_ptr container); - -Configuration *getConfigurationPublic(const char *key); -Vector getConfigurationContainersPublic(); - -bool configuration_init(std::shared_ptr filesytem); -void configuration_deinit(); - -bool configuration_load(const char *filename = nullptr); - -bool configuration_save(); - -bool configuration_clean_unused(); //remove configs which haven't been accessed - -//default implementation for common validator -bool VALIDATE_UNSIGNED_INT(const char*); - -} //end namespace MicroOcpp -#endif diff --git a/src/MicroOcpp/Core/ConfigurationContainer.cpp b/src/MicroOcpp/Core/ConfigurationContainer.cpp deleted file mode 100644 index af0cba2f..00000000 --- a/src/MicroOcpp/Core/ConfigurationContainer.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include - -#include - -using namespace MicroOcpp; - -ConfigurationContainer::~ConfigurationContainer() { - -} - -ConfigurationContainerVolatile::ConfigurationContainerVolatile(const char *filename, bool accessible) : - ConfigurationContainer(filename, accessible), MemoryManaged("v16.Configuration.ContainerVoltaile.", filename), configurations(makeVector>(getMemoryTag())) { - -} - -bool ConfigurationContainerVolatile::load() { - return true; -} - -bool ConfigurationContainerVolatile::save() { - return true; -} - -std::shared_ptr ConfigurationContainerVolatile::createConfiguration(TConfig type, const char *key) { - auto res = std::shared_ptr(makeConfiguration(type, key).release(), std::default_delete(), makeAllocator("v16.Configuration.", key)); - if (!res) { - //allocation failure - OOM - MO_DBG_ERR("OOM"); - return nullptr; - } - configurations.push_back(res); - return res; -} - -void ConfigurationContainerVolatile::remove(Configuration *config) { - for (auto entry = configurations.begin(); entry != configurations.end();) { - if (entry->get() == config) { - entry = configurations.erase(entry); - } else { - entry++; - } - } -} - -size_t ConfigurationContainerVolatile::size() { - return configurations.size(); -} - -Configuration *ConfigurationContainerVolatile::getConfiguration(size_t i) { - return configurations[i].get(); -} - -std::shared_ptr ConfigurationContainerVolatile::getConfiguration(const char *key) { - for (auto& entry : configurations) { - if (entry->getKey() && !strcmp(entry->getKey(), key)) { - return entry; - } - } - return nullptr; -} - -void ConfigurationContainerVolatile::add(std::shared_ptr c) { - configurations.push_back(std::move(c)); -} - -namespace MicroOcpp { - -std::unique_ptr makeConfigurationContainerVolatile(const char *filename, bool accessible) { - return std::unique_ptr(new ConfigurationContainerVolatile(filename, accessible)); -} - -} //end namespace MicroOcpp diff --git a/src/MicroOcpp/Core/ConfigurationContainer.h b/src/MicroOcpp/Core/ConfigurationContainer.h deleted file mode 100644 index 9a3ff3ae..00000000 --- a/src/MicroOcpp/Core/ConfigurationContainer.h +++ /dev/null @@ -1,65 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CONFIGURATIONCONTAINER_H -#define MO_CONFIGURATIONCONTAINER_H - -#include - -#include -#include - -namespace MicroOcpp { - -class ConfigurationContainer { -private: - const char *filename; - bool accessible; -public: - ConfigurationContainer(const char *filename, bool accessible) : filename(filename), accessible(accessible) { } - - virtual ~ConfigurationContainer(); - - const char *getFilename() {return filename;} - bool isAccessible() {return accessible;} - - virtual bool load() = 0; //called at the end of mocpp_intialize, to load the configurations with the stored value - virtual bool save() = 0; - - virtual std::shared_ptr createConfiguration(TConfig type, const char *key) = 0; - virtual void remove(Configuration *config) = 0; - - virtual size_t size() = 0; - virtual Configuration *getConfiguration(size_t i) = 0; - virtual std::shared_ptr getConfiguration(const char *key) = 0; - - virtual void loadStaticKey(Configuration& config, const char *key) { } //possible optimization: can replace internal key with passed static key - - virtual void removeUnused() { } //remove configs which haven't been accessed (optional and only if known) -}; - -class ConfigurationContainerVolatile : public ConfigurationContainer, public MemoryManaged { -private: - Vector> configurations; -public: - ConfigurationContainerVolatile(const char *filename, bool accessible); - - //ConfigurationContainer definitions - bool load() override; - bool save() override; - std::shared_ptr createConfiguration(TConfig type, const char *key) override; - void remove(Configuration *config) override; - size_t size() override; - Configuration *getConfiguration(size_t i) override; - std::shared_ptr getConfiguration(const char *key) override; - - //add custom Configuration object - void add(std::shared_ptr c); -}; - -std::unique_ptr makeConfigurationContainerVolatile(const char *filename, bool accessible); - -} //end namespace MicroOcpp - -#endif diff --git a/src/MicroOcpp/Core/ConfigurationContainerFlash.cpp b/src/MicroOcpp/Core/ConfigurationContainerFlash.cpp deleted file mode 100644 index b32f1af8..00000000 --- a/src/MicroOcpp/Core/ConfigurationContainerFlash.cpp +++ /dev/null @@ -1,362 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include - -#include -#include -#include -#include - -#define MAX_CONFIGURATIONS 50 - -namespace MicroOcpp { - -class ConfigurationContainerFlash : public ConfigurationContainer, public MemoryManaged { -private: - Vector> configurations; - std::shared_ptr filesystem; - uint16_t revisionSum = 0; - - bool loaded = false; - - Vector keyPool; - - void clearKeyPool(const char *key) { - auto it = keyPool.begin(); - while (it != keyPool.end()) { - if (!strcmp(*it, key)) { - MO_DBG_VERBOSE("clear key %s", key); - MO_FREE(*it); - it = keyPool.erase(it); - } else { - ++it; - } - } - } - - bool configurationsUpdated() { - auto revisionSum_old = revisionSum; - - revisionSum = 0; - for (auto& config : configurations) { - revisionSum += config->getValueRevision(); - } - - return revisionSum != revisionSum_old; - } -public: - ConfigurationContainerFlash(std::shared_ptr filesystem, const char *filename, bool accessible) : - ConfigurationContainer(filename, accessible), MemoryManaged("v16.Configuration.ContainerFlash.", filename), configurations(makeVector>(getMemoryTag())), filesystem(filesystem), keyPool(makeVector(getMemoryTag())) { } - - ~ConfigurationContainerFlash() { - auto it = keyPool.begin(); - while (it != keyPool.end()) { - MO_FREE(*it); - it = keyPool.erase(it); - } - } - - bool load() override { - - if (loaded) { - return true; - } - - if (!filesystem) { - return false; - } - - size_t file_size = 0; - if (filesystem->stat(getFilename(), &file_size) != 0 // file does not exist - || file_size == 0) { // file exists, but empty - MO_DBG_DEBUG("Populate FS: create configuration file"); - return save(); - } - - auto doc = FilesystemUtils::loadJson(filesystem, getFilename(), getMemoryTag()); - if (!doc) { - MO_DBG_ERR("failed to load %s", getFilename()); - return false; - } - - JsonObject root = doc->as(); - - JsonObject configHeader = root["head"]; - - if (strcmp(configHeader["content-type"] | "Invalid", "ocpp_config_file") && - strcmp(configHeader["content-type"] | "Invalid", "ao_configuration_file")) { //backwards-compatibility - MO_DBG_ERR("Unable to initialize: unrecognized configuration file format"); - return false; - } - - if (strcmp(configHeader["version"] | "Invalid", "2.0") && - strcmp(configHeader["version"] | "Invalid", "1.1")) { //backwards-compatibility - MO_DBG_ERR("Unable to initialize: unsupported version"); - return false; - } - - JsonArray configurationsArray = root["configurations"]; - if (configurationsArray.size() > MAX_CONFIGURATIONS) { - MO_DBG_ERR("Unable to initialize: configurations_len is too big (=%zu)", configurationsArray.size()); - return false; - } - - for (JsonObject stored : configurationsArray) { - TConfig type; - if (!deserializeTConfig(stored["type"] | "_Undefined", type)) { - MO_DBG_ERR("corrupt config"); - continue; - } - - const char *key = stored["key"] | ""; - if (!*key) { - MO_DBG_ERR("corrupt config"); - continue; - } - - if (!stored.containsKey("value")) { - MO_DBG_ERR("corrupt config"); - continue; - } - - char *key_pooled = nullptr; - - auto config = getConfiguration(key).get(); - if (config && config->getType() != type) { - MO_DBG_ERR("conflicting type for %s - remove old config", key); - remove(config); - config = nullptr; - } - if (!config) { - #if MO_ENABLE_HEAP_PROFILER - char memoryTag [64]; - snprintf(memoryTag, sizeof(memoryTag), "%s%s", "v16.Configuration.", key); - #else - const char *memoryTag = nullptr; - (void)memoryTag; - #endif - key_pooled = static_cast(MO_MALLOC(memoryTag, strlen(key) + 1)); - if (!key_pooled) { - MO_DBG_ERR("OOM: %s", key); - return false; - } - strcpy(key_pooled, key); - } - - switch (type) { - case TConfig::Int: { - if (!stored["value"].is()) { - MO_DBG_ERR("corrupt config"); - MO_FREE(key_pooled); - continue; - } - int value = stored["value"] | 0; - if (!config) { - //create new config - config = createConfiguration(TConfig::Int, key_pooled).get(); - } - if (config) { - config->setInt(value); - } - break; - } - case TConfig::Bool: { - if (!stored["value"].is()) { - MO_DBG_ERR("corrupt config"); - MO_FREE(key_pooled); - continue; - } - bool value = stored["value"] | false; - if (!config) { - //create new config - config = createConfiguration(TConfig::Bool, key_pooled).get(); - } - if (config) { - config->setBool(value); - } - break; - } - case TConfig::String: { - if (!stored["value"].is()) { - MO_DBG_ERR("corrupt config"); - MO_FREE(key_pooled); - continue; - } - const char *value = stored["value"] | ""; - if (!config) { - //create new config - config = createConfiguration(TConfig::String, key_pooled).get(); - } - if (config) { - config->setString(value); - } - break; - } - } - - if (config) { - //success - - if (key_pooled) { - //allocated key, need to store - keyPool.push_back(std::move(key_pooled)); - } - } else { - MO_DBG_ERR("OOM: %s", key); - MO_FREE(key_pooled); - } - } - - configurationsUpdated(); - - MO_DBG_DEBUG("Initialization finished"); - loaded = true; - return true; - } - - bool save() override { - - if (!filesystem) { - return false; - } - - if (!configurationsUpdated()) { - return true; //nothing to be done - } - - //during mocpp_deinitialize(), key owners are destructed. Don't store if this container is affected - for (auto& config : configurations) { - if (!config->getKey()) { - MO_DBG_DEBUG("don't write back container with destructed key(s)"); - return false; - } - } - - size_t jsonCapacity = 2 * JSON_OBJECT_SIZE(2); //head + configurations + head payload - jsonCapacity += JSON_ARRAY_SIZE(configurations.size()); //configurations array - jsonCapacity += configurations.size() * JSON_OBJECT_SIZE(3); //config entries in array - - if (jsonCapacity > MO_MAX_JSON_CAPACITY) { - MO_DBG_ERR("configs JSON exceeds maximum capacity (%s, %zu entries). Crop configs file (by FCFS)", getFilename(), configurations.size()); - jsonCapacity = MO_MAX_JSON_CAPACITY; - } - - auto doc = initJsonDoc(getMemoryTag(), jsonCapacity); - JsonObject head = doc.createNestedObject("head"); - head["content-type"] = "ocpp_config_file"; - head["version"] = "2.0"; - - JsonArray configurationsArray = doc.createNestedArray("configurations"); - - size_t trackCapacity = 0; - - for (size_t i = 0; i < configurations.size(); i++) { - auto& config = *configurations[i]; - - size_t entryCapacity = JSON_OBJECT_SIZE(3) + (JSON_ARRAY_SIZE(2) - JSON_ARRAY_SIZE(1)); - if (trackCapacity + entryCapacity > MO_MAX_JSON_CAPACITY) { - break; - } - - trackCapacity += entryCapacity; - - auto stored = configurationsArray.createNestedObject(); - - stored["type"] = serializeTConfig(config.getType()); - stored["key"] = config.getKey(); - - switch (config.getType()) { - case TConfig::Int: - stored["value"] = config.getInt(); - break; - case TConfig::Bool: - stored["value"] = config.getBool(); - break; - case TConfig::String: - stored["value"] = config.getString(); - break; - } - } - - bool success = FilesystemUtils::storeJson(filesystem, getFilename(), doc); - - if (success) { - MO_DBG_DEBUG("Saving configurations finished"); - } else { - MO_DBG_ERR("could not save configs file: %s", getFilename()); - } - - return success; - } - - std::shared_ptr createConfiguration(TConfig type, const char *key) override { - auto res = std::shared_ptr(makeConfiguration(type, key).release(), std::default_delete(), makeAllocator("v16.Configuration.", key)); - if (!res) { - //allocation failure - OOM - MO_DBG_ERR("OOM"); - return nullptr; - } - configurations.push_back(res); - return res; - } - - void remove(Configuration *config) override { - const char *key = config->getKey(); - configurations.erase(std::remove_if(configurations.begin(), configurations.end(), - [config] (std::shared_ptr& entry) { - return entry.get() == config; - }), configurations.end()); - if (key) { - clearKeyPool(key); - } - } - - size_t size() override { - return configurations.size(); - } - - Configuration *getConfiguration(size_t i) override { - return configurations[i].get(); - } - - std::shared_ptr getConfiguration(const char *key) override { - for (auto& entry : configurations) { - if (entry->getKey() && !strcmp(entry->getKey(), key)) { - return entry; - } - } - return nullptr; - } - - void loadStaticKey(Configuration& config, const char *key) override { - config.setKey(key); - clearKeyPool(key); - } - - void removeUnused() override { - //if a config's key is still in the keyPool, we know it's unused because it has never been declared in FW (originates from an older FW version) - - auto key = keyPool.begin(); - while (key != keyPool.end()) { - - for (auto config = configurations.begin(); config != configurations.end(); ++config) { - if ((*config)->getKey() == *key) { - MO_DBG_DEBUG("remove unused config %s", (*config)->getKey()); - configurations.erase(config); - break; - } - } - - MO_FREE(*key); - key = keyPool.erase(key); - } - } -}; - -std::unique_ptr makeConfigurationContainerFlash(std::shared_ptr filesystem, const char *filename, bool accessible) { - return std::unique_ptr(new ConfigurationContainerFlash(filesystem, filename, accessible)); -} - -} //end namespace MicroOcpp diff --git a/src/MicroOcpp/Core/ConfigurationContainerFlash.h b/src/MicroOcpp/Core/ConfigurationContainerFlash.h deleted file mode 100644 index 950f3cd7..00000000 --- a/src/MicroOcpp/Core/ConfigurationContainerFlash.h +++ /dev/null @@ -1,17 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CONFIGURATIONCONTAINERFLASH_H -#define MO_CONFIGURATIONCONTAINERFLASH_H - -#include -#include - -namespace MicroOcpp { - -std::unique_ptr makeConfigurationContainerFlash(std::shared_ptr filesystem, const char *filename, bool accessible); - -} //end namespace MicroOcpp - -#endif diff --git a/src/MicroOcpp/Core/ConfigurationKeyValue.cpp b/src/MicroOcpp/Core/ConfigurationKeyValue.cpp deleted file mode 100644 index 77e8ad85..00000000 --- a/src/MicroOcpp/Core/ConfigurationKeyValue.cpp +++ /dev/null @@ -1,298 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include - -#include -#include - -#define KEY_MAXLEN 60 -#define STRING_VAL_MAXLEN 512 - -namespace MicroOcpp { - -template<> TConfig convertType() {return TConfig::Int;} -template<> TConfig convertType() {return TConfig::Bool;} -template<> TConfig convertType() {return TConfig::String;} - -Configuration::~Configuration() { - -} - -void Configuration::setInt(int) { -#if MO_CONFIG_TYPECHECK - MO_DBG_ERR("type err"); -#endif -} - -void Configuration::setBool(bool) { -#if MO_CONFIG_TYPECHECK - MO_DBG_ERR("type err"); -#endif -} - -bool Configuration::setString(const char*) { -#if MO_CONFIG_TYPECHECK - MO_DBG_ERR("type err"); -#endif - return false; -} - -int Configuration::getInt() { -#if MO_CONFIG_TYPECHECK - MO_DBG_ERR("type err"); -#endif - return 0; -} - -bool Configuration::getBool() { -#if MO_CONFIG_TYPECHECK - MO_DBG_ERR("type err"); -#endif - return false; -} - -const char *Configuration::getString() { -#if MO_CONFIG_TYPECHECK - MO_DBG_ERR("type err"); -#endif - return ""; -} - -revision_t Configuration::getValueRevision() { - return value_revision; -} - -void Configuration::setRebootRequired() { - rebootRequired = true; -} - -bool Configuration::isRebootRequired() { - return rebootRequired; -} - -void Configuration::setReadOnly() { - if (mutability == Mutability::ReadWrite) { - mutability = Mutability::ReadOnly; - } else { - mutability = Mutability::None; - } -} - -bool Configuration::isReadOnly() { - return mutability == Mutability::ReadOnly; -} - -bool Configuration::isReadable() { - return mutability == Mutability::ReadWrite || mutability == Mutability::ReadOnly; -} - -void Configuration::setWriteOnly() { - if (mutability == Mutability::ReadWrite) { - mutability = Mutability::WriteOnly; - } else { - mutability = Mutability::None; - } -} - -/* - * Default implementations of the Configuration interface. - * - * How to use custom implementations: for each OCPP config, pass a config instance to the OCPP lib - * before its initialization stage. Then the library won't create new config objects but - */ - -class ConfigInt : public Configuration, public MemoryManaged { -private: - const char *key = nullptr; - int val = 0; -public: - - ~ConfigInt() = default; - - bool setKey(const char *key) override { - this->key = key; - updateMemoryTag("v16.Configuration.", key); - return true; - } - - const char *getKey() override { - return key; - } - - TConfig getType() override { - return TConfig::Int; - } - - void setInt(int val) override { - this->val = val; - value_revision++; - } - - int getInt() override { - return val; - } -}; - -class ConfigBool : public Configuration, public MemoryManaged { -private: - const char *key = nullptr; - bool val = false; -public: - - ~ConfigBool() = default; - - bool setKey(const char *key) override { - this->key = key; - updateMemoryTag("v16.Configuration.", key); - return true; - } - - const char *getKey() override { - return key; - } - - TConfig getType() override { - return TConfig::Bool; - } - - void setBool(bool val) override { - this->val = val; - value_revision++; - } - - bool getBool() override { - return val; - } -}; - -class ConfigString : public Configuration, public MemoryManaged { -private: - const char *key = nullptr; - char *val = nullptr; -public: - ConfigString() = default; - ConfigString(const ConfigString&) = delete; - ConfigString(ConfigString&&) = delete; - ConfigString& operator=(const ConfigString&) = delete; - - ~ConfigString() { - MO_FREE(val); - } - - bool setKey(const char *key) override { - this->key = key; - updateMemoryTag("v16.Configuration.", key); - if (val) { - MO_MEM_SET_TAG(val, getMemoryTag()); - } - return true; - } - - const char *getKey() override { - return key; - } - - TConfig getType() override { - return TConfig::String; - } - - bool setString(const char *src) override { - bool src_empty = !src || !*src; - - if (!val && src_empty) { - return true; - } - - if (this->val && src && !strcmp(this->val, src)) { - return true; - } - - size_t size = 0; - if (!src_empty) { - size = strlen(src) + 1; - } - - if (size > MO_CONFIG_MAX_VALSTRSIZE) { - return false; - } - - value_revision++; - - if (this->val) { - MO_FREE(this->val); - this->val = nullptr; - } - - if (!src_empty) { - this->val = (char*) MO_MALLOC(getMemoryTag(), size); - if (!this->val) { - return false; - } - strcpy(this->val, src); - } - - return true; - } - - const char *getString() override { - if (!val) { - return ""; - } - return val; - } -}; - -std::unique_ptr makeConfiguration(TConfig type, const char *key) { - std::unique_ptr res; - switch (type) { - case TConfig::Int: - res.reset(new ConfigInt()); - break; - case TConfig::Bool: - res.reset(new ConfigBool()); - break; - case TConfig::String: - res.reset(new ConfigString()); - break; - } - if (!res) { - MO_DBG_ERR("OOM"); - return nullptr; - } - res->setKey(key); - return res; -} - -bool deserializeTConfig(const char *serialized, TConfig& out) { - if (!strcmp(serialized, "int")) { - out = TConfig::Int; - return true; - } else if (!strcmp(serialized, "bool")) { - out = TConfig::Bool; - return true; - } else if (!strcmp(serialized, "string")) { - out = TConfig::String; - return true; - } else { - MO_DBG_WARN("config type error"); - return false; - } -} - -const char *serializeTConfig(TConfig type) { - switch (type) { - case TConfig::Int: - return "int"; - case TConfig::Bool: - return "bool"; - case TConfig::String: - return "string"; - } - return "_Undefined"; -} - -} //end namespace MicroOcpp diff --git a/src/MicroOcpp/Core/ConfigurationKeyValue.h b/src/MicroOcpp/Core/ConfigurationKeyValue.h deleted file mode 100644 index 3e631c1e..00000000 --- a/src/MicroOcpp/Core/ConfigurationKeyValue.h +++ /dev/null @@ -1,89 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef CONFIGURATIONKEYVALUE_H -#define CONFIGURATIONKEYVALUE_H - -#include -#include - -#define MO_CONFIG_MAX_VALSTRSIZE 128 - -#ifndef MO_CONFIG_EXT_PREFIX -#define MO_CONFIG_EXT_PREFIX "Cst_" -#endif - -#ifndef MO_CONFIG_TYPECHECK -#define MO_CONFIG_TYPECHECK 1 //enable this for debugging -#endif - -namespace MicroOcpp { - -using revision_t = uint16_t; - -enum class TConfig : uint8_t { - Int, - Bool, - String -}; - -template -TConfig convertType(); - -class Configuration { -protected: - revision_t value_revision = 0; //write access counter; used to check if this config has been changed -private: - bool rebootRequired = false; - - enum class Mutability : uint8_t { - ReadWrite, - ReadOnly, - WriteOnly, - None - }; - Mutability mutability = Mutability::ReadWrite; - -public: - virtual ~Configuration(); - - virtual bool setKey(const char *key) = 0; - virtual const char *getKey() = 0; - - 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) - - virtual TConfig getType() = 0; - - virtual revision_t getValueRevision(); - - void setRebootRequired(); - bool isRebootRequired(); - - void setReadOnly(); - bool isReadOnly(); - bool isReadable(); - - void setWriteOnly(); -}; - -/* - * Default implementations of the Configuration interface. - * - * How to use custom implementations: for each OCPP config, pass a config instance to the OCPP lib - * before its initialization stage. Then the library won't create new config objects but - */ -std::unique_ptr makeConfiguration(TConfig type, const char *key); - -const char *serializeTConfig(TConfig type); -bool deserializeTConfig(const char *serialized, TConfig& out); - -} //end namespace MicroOcpp - -#endif diff --git a/src/MicroOcpp/Core/ConfigurationOptions.h b/src/MicroOcpp/Core/ConfigurationOptions.h deleted file mode 100644 index 0e9e227f..00000000 --- a/src/MicroOcpp/Core/ConfigurationOptions.h +++ /dev/null @@ -1,64 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CONFIGURATIONOPTIONS_H -#define MO_CONFIGURATIONOPTIONS_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct OCPP_FilesystemOpt { - bool use; - bool mount; - bool formatFsOnFail; -}; - -#ifdef __cplusplus -} - -namespace MicroOcpp { - -class FilesystemOpt{ -private: - bool use = false; - bool mount = false; - bool formatFsOnFail = false; -public: - enum Mode : uint8_t {Deactivate, Use, Use_Mount, Use_Mount_FormatOnFail}; - - FilesystemOpt() = default; - FilesystemOpt(Mode mode) { - switch (mode) { - case (FilesystemOpt::Use_Mount_FormatOnFail): - formatFsOnFail = true; - //fallthrough - case (FilesystemOpt::Use_Mount): - mount = true; - //fallthrough - case (FilesystemOpt::Use): - use = true; - break; - default: - break; - } - } - FilesystemOpt(struct OCPP_FilesystemOpt fsopt) { - this->use = fsopt.use; - this->mount = fsopt.mount; - this->formatFsOnFail = fsopt.formatFsOnFail; - } - - bool accessAllowed() {return use;} - bool mustMount() {return mount;} - bool formatOnFail() {return formatFsOnFail;} -}; - -} //end namespace MicroOcpp - -#endif //__cplusplus - -#endif diff --git a/src/MicroOcpp/Core/Configuration_c.cpp b/src/MicroOcpp/Core/Configuration_c.cpp deleted file mode 100644 index 6b6998e1..00000000 --- a/src/MicroOcpp/Core/Configuration_c.cpp +++ /dev/null @@ -1,315 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include -#include - -using namespace MicroOcpp; - -class ConfigurationC : public Configuration, public MemoryManaged { -private: - ocpp_configuration *config; -public: - ConfigurationC(ocpp_configuration *config) : - config(config) { - if (config->read_only) { - setReadOnly(); - } - if (config->write_only) { - setWriteOnly(); - } - if (config->reboot_required) { - setRebootRequired(); - } - } - - bool setKey(const char *key) override { - updateMemoryTag("v16.Configuration.", key); - return config->set_key(config->user_data, key); - } - - const char *getKey() override { - return config->get_key(config->user_data); - } - - void setInt(int val) override { - #if MO_CONFIG_TYPECHECK - if (config->get_type(config->user_data) != ENUM_CDT_INT) { - MO_DBG_ERR("type err"); - return; - } - #endif - config->set_int(config->user_data, val); - } - - void setBool(bool val) override { - #if MO_CONFIG_TYPECHECK - if (config->get_type(config->user_data) != ENUM_CDT_BOOL) { - MO_DBG_ERR("type err"); - return; - } - #endif - config->set_bool(config->user_data, val); - } - - bool setString(const char *val) override { - #if MO_CONFIG_TYPECHECK - if (config->get_type(config->user_data) != ENUM_CDT_STRING) { - MO_DBG_ERR("type err"); - return false; - } - #endif - return config->set_string(config->user_data, val); - } - - int getInt() override { - #if MO_CONFIG_TYPECHECK - if (config->get_type(config->user_data) != ENUM_CDT_INT) { - MO_DBG_ERR("type err"); - return 0; - } - #endif - return config->get_int(config->user_data); - } - - bool getBool() override { - #if MO_CONFIG_TYPECHECK - if (config->get_type(config->user_data) != ENUM_CDT_BOOL) { - MO_DBG_ERR("type err"); - return false; - } - #endif - return config->get_bool(config->user_data); - } - - const char *getString() override { - #if MO_CONFIG_TYPECHECK - if (config->get_type(config->user_data) != ENUM_CDT_STRING) { - MO_DBG_ERR("type err"); - return ""; - } - #endif - return config->get_string(config->user_data); - } - - TConfig getType() override { - TConfig res = TConfig::Int; - switch (config->get_type(config->user_data)) { - case ENUM_CDT_INT: - res = TConfig::Int; - break; - case ENUM_CDT_BOOL: - res = TConfig::Bool; - break; - case ENUM_CDT_STRING: - res = TConfig::String; - break; - default: - MO_DBG_ERR("type conversion"); - break; - } - - return res; - } - - uint16_t getValueRevision() override { - return config->get_write_count(config->user_data); - } - - ocpp_configuration *getConfiguration() { - return config; - } -}; - -namespace MicroOcpp { - -ConfigurationC *getConfigurationC(ocpp_configuration *config) { - if (!config->mo_data) { - return nullptr; - } - return reinterpret_cast*>(config->mo_data)->get(); -} - -} - -using namespace MicroOcpp; - - -void ocpp_setRebootRequired(ocpp_configuration *config) { - if (auto c = getConfigurationC(config)) { - c->setRebootRequired(); - } - config->reboot_required = true; -} -bool ocpp_isRebootRequired(ocpp_configuration *config) { - if (auto c = getConfigurationC(config)) { - return c->isRebootRequired(); - } - return config->reboot_required; -} - -void ocpp_setReadOnly(ocpp_configuration *config) { - if (auto c = getConfigurationC(config)) { - c->setReadOnly(); - } - config->read_only = true; -} -bool ocpp_isReadOnly(ocpp_configuration *config) { - if (auto c = getConfigurationC(config)) { - return c->isReadOnly(); - } - return config->read_only; -} -bool ocpp_isReadable(ocpp_configuration *config) { - if (auto c = getConfigurationC(config)) { - return c->isReadable(); - } - return !config->write_only; -} - -void ocpp_setWriteOnly(ocpp_configuration *config) { - if (auto c = getConfigurationC(config)) { - c->setWriteOnly(); - } - config->write_only = true; -} - -class ConfigurationContainerC : public ConfigurationContainer, public MemoryManaged { -private: - ocpp_configuration_container *container; -public: - ConfigurationContainerC(ocpp_configuration_container *container, const char *filename, bool accessible) : - ConfigurationContainer(filename, accessible), MemoryManaged("v16.Configuration.ContainerC.", filename), container(container) { - - } - - ~ConfigurationContainerC() { - for (size_t i = 0; i < container->size(container->user_data); i++) { - if (auto config = container->get_configuration(container->user_data, i)) { - if (config->mo_data) { - delete reinterpret_cast*>(config->mo_data); - config->mo_data = nullptr; - } - } - } - } - - bool load() override { - if (container->load) { - return container->load(container->user_data); - } else { - return true; - } - } - - bool save() override { - if (container->save) { - return container->save(container->user_data); - } else { - return true; - } - } - - std::shared_ptr createConfiguration(TConfig type, const char *key) override { - - auto result = std::shared_ptr(nullptr, std::default_delete(), makeAllocator(getMemoryTag())); - - if (!container->create_configuration) { - return result; - } - - ocpp_config_datatype dt; - switch (type) { - case TConfig::Int: - dt = ENUM_CDT_INT; - break; - case TConfig::Bool: - dt = ENUM_CDT_BOOL; - break; - case TConfig::String: - dt = ENUM_CDT_STRING; - break; - default: - MO_DBG_ERR("internal error"); - return result; - } - ocpp_configuration *config = container->create_configuration(container->user_data, dt, key); - if (!config) { - return result; - } - - result.reset(new ConfigurationC(config)); - - if (result) { - auto captureConfigC = new std::shared_ptr(result); - config->mo_data = reinterpret_cast(captureConfigC); - } else { - MO_DBG_ERR("could not create config: %s", key); - if (container->remove) { - container->remove(container->user_data, key); - } - } - - return result; - } - - void remove(Configuration *config) override { - if (!container->remove) { - return; - } - - if (auto c = container->get_configuration_by_key(container->user_data, config->getKey())) { - delete reinterpret_cast*>(c->mo_data); - c->mo_data = nullptr; - } - - container->remove(container->user_data, config->getKey()); - } - - size_t size() override { - return container->size(container->user_data); - } - - Configuration *getConfiguration(size_t i) override { - auto config = container->get_configuration(container->user_data, i); - if (config) { - if (!config->mo_data) { - auto c = new ConfigurationC(config); - if (c) { - config->mo_data = reinterpret_cast(new std::shared_ptr(c, std::default_delete(), makeAllocator(getMemoryTag()))); - } - } - return static_cast(config->mo_data ? reinterpret_cast*>(config->mo_data)->get() : nullptr); - } else { - return nullptr; - } - } - - std::shared_ptr getConfiguration(const char *key) override { - auto config = container->get_configuration_by_key(container->user_data, key); - if (config) { - if (!config->mo_data) { - auto c = new ConfigurationC(config); - if (c) { - config->mo_data = reinterpret_cast(new std::shared_ptr(c, std::default_delete(), makeAllocator(getMemoryTag()))); - } - } - return config->mo_data ? *reinterpret_cast*>(config->mo_data) : nullptr; - } else { - return nullptr; - } - } - - void loadStaticKey(Configuration& config, const char *key) override { - if (container->load_static_key) { - container->load_static_key(container->user_data, key); - } - } -}; - -void ocpp_configuration_container_add(ocpp_configuration_container *container, const char *container_path, bool accessible) { - addConfigurationContainer(std::allocate_shared(makeAllocator("v16.Configuration.ContainerC.", container_path), container, container_path, accessible)); -} diff --git a/src/MicroOcpp/Core/Configuration_c.h b/src/MicroOcpp/Core/Configuration_c.h deleted file mode 100644 index 94172d07..00000000 --- a/src/MicroOcpp/Core/Configuration_c.h +++ /dev/null @@ -1,84 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CONFIGURATION_C_H -#define MO_CONFIGURATION_C_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum ocpp_config_datatype { - ENUM_CDT_INT, - ENUM_CDT_BOOL, - ENUM_CDT_STRING -} ocpp_config_datatype; - -typedef struct ocpp_configuration { - void *user_data; // Set this at your choice. MO passes it back to the functions below - - bool (*set_key) (void *user_data, const char *key); // Optional. MO may provide a static key value which you can use to replace a possibly malloc'd key buffer - const char* (*get_key) (void *user_data); // Return Configuration key - - ocpp_config_datatype (*get_type) (void *user_data); // Return internal data type of config (determines which of the following getX()/setX() pairs are valid) - - // Set value of Config - union { - void (*set_int) (void *user_data, int val); - void (*set_bool) (void *user_data, bool val); - bool (*set_string) (void *user_data, const char *val); - }; - - // Get value of Config - union { - int (*get_int) (void *user_data); - bool (*get_bool) (void *user_data); - const char* (*get_string) (void *user_data); - }; - - uint16_t (*get_write_count) (void *user_data); // Return number of changes of the value. MO uses this to detect if the firmware has updated the config - - bool read_only; - bool write_only; - bool reboot_required; - - void *mo_data; // Reserved for MO -} ocpp_configuration; - -void ocpp_setRebootRequired(ocpp_configuration *config); -bool ocpp_isRebootRequired(ocpp_configuration *config); - -void ocpp_setReadOnly(ocpp_configuration *config); -bool ocpp_isReadOnly(ocpp_configuration *config); -bool ocpp_isReadable(ocpp_configuration *config); - -void ocpp_setWriteOnly(ocpp_configuration *config); - -typedef struct ocpp_configuration_container { - void *user_data; //set this at your choice. MO passes it back to the functions below - - bool (*load) (void *user_data); // Called after declaring Configurations, to load them with their values - bool (*save) (void *user_data); // Commit all Configurations to memory - - ocpp_configuration* (*create_configuration) (void *user_data, ocpp_config_datatype dt, const char *key); // Called to get a reference to a Configuration managed by this container (create new or return existing) - void (*remove) (void *user_data, const char *key); // Remove this config from the container. Do not free the config here, the config must outlive the MO lifecycle - - size_t (*size) (void *user_data); // Number of Configurations currently managed by this container - ocpp_configuration* (*get_configuration) (void *user_data, size_t i); // Return config at container position i - ocpp_configuration* (*get_configuration_by_key) (void *user_data, const char *key); // Return config for given key - - void (*load_static_key) (void *user_data, const char *key); // Optional. MO may provide a static key value which you can use to replace a possibly malloc'd key buffer -} ocpp_configuration_container; - -// Add custom Configuration container. Add one container per container_path before mocpp_initialize(...) -void ocpp_configuration_container_add(ocpp_configuration_container *container, const char *container_path, bool accessible); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif diff --git a/src/MicroOcpp/Core/Connection.cpp b/src/MicroOcpp/Core/Connection.cpp index ae2ee2fb..f055a24e 100644 --- a/src/MicroOcpp/Core/Connection.cpp +++ b/src/MicroOcpp/Core/Connection.cpp @@ -3,10 +3,27 @@ // MIT License #include +#include #include using namespace MicroOcpp; +Connection::Connection() { + +} + +void Connection::setContext(Context *context) { + this->context = context; +} + +bool Connection::receiveTXT(const char *msg, size_t len) { + if (!context) { + MO_DBG_WARN("must assign Connection to a Context"); + return false; + } + return context->getMessageService().receiveMessage(msg, len); +} + LoopbackConnection::LoopbackConnection() : MemoryManaged("WebSocketLoopback") { } void LoopbackConnection::loop() { } @@ -15,107 +32,238 @@ bool LoopbackConnection::sendTXT(const char *msg, size_t length) { if (!connected || !online) { return false; } - if (receiveTXT) { - lastRecv = mocpp_tick_ms(); - return receiveTXT(msg, length); - } else { - return false; - } + return receiveTXT(msg, length); //`Connection::receiveTXT()` passes message back to MicroOcpp } -void LoopbackConnection::setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) { - this->receiveTXT = receiveTXT; +void LoopbackConnection::setOnline(bool online) { + this->online = online; } -unsigned long LoopbackConnection::getLastRecv() { - return lastRecv; +bool LoopbackConnection::isOnline() { + return online; } -unsigned long LoopbackConnection::getLastConnected() { - return lastConn; +void LoopbackConnection::setConnected(bool connected) { + this->connected = connected; } -void LoopbackConnection::setOnline(bool online) { - if (online) { - lastConn = mocpp_tick_ms(); - } - this->online = online; +bool LoopbackConnection::isConnected() { + return connected; } -void LoopbackConnection::setConnected(bool connected) { - if (connected) { - lastConn = mocpp_tick_ms(); +#if MO_WS_USE == MO_WS_ARDUINO + +#include + +namespace MicroOcpp { + +class ArduinoWSClient : public Connection, public MemoryManaged { +private: + WebSocketsClient *wsock = nullptr; + bool isWSockOwner = false; +public: + + ArduinoWSClient(WebSocketsClient *wsock, bool transferOwnership) : MemoryManaged("WebSocketsClient"), wsock(wsock), isWSockOwner(transferOwnership) { + wsock->onEvent([callback](WStype_t type, uint8_t * payload, size_t length) { + switch (type) { + case WStype_DISCONNECTED: + MO_DBG_INFO("Disconnected"); + break; + case WStype_CONNECTED: + MO_DBG_INFO("Connected (path: %s)", payload); + break; + case WStype_TEXT: + if (!receiveTXT((const char *) payload, length)) { //`Connection::receiveTXT()` forwards message to MicroOcpp + MO_DBG_WARN("Processing WebSocket input event failed"); + } + break; + case WStype_BIN: + MO_DBG_WARN("Binary data stream not supported"); + break; + case WStype_PING: + // pong will be send automatically + MO_DBG_VERBOSE("Recv WS ping"); + break; + case WStype_PONG: + // answer to a ping we send + MO_DBG_VERBOSE("Recv WS pong"); + break; + case WStype_FRAGMENT_TEXT_START: //fragments are not supported + default: + MO_DBG_WARN("Unsupported WebSocket event type"); + break; + } + }); } - this->connected = connected; -} -#ifndef MO_CUSTOM_WS + ~ArduinoWSClient() { + if (isWSockOwner) { + delete wsock; + wsock = nullptr; + isWSockOwner = false; + } + } -using namespace MicroOcpp::EspWiFi; + void loop() overide { + wsock->loop(); + } -WSClient::WSClient(WebSocketsClient *wsock) : MemoryManaged("WebSocketsClient"), wsock(wsock) { + bool sendTXT(const char *msg, size_t length) override { + return wsock->sendTXT(msg, length); + } -} + bool isConnected() override { + return wsock->isConnected(); + } +}; -void WSClient::loop() { - wsock->loop(); +Connection *makeArduinoWSClient(WebSocketsClient& arduinoWebsockets) { + return static_cast(new ArduinoWSClient(&arduinoWebsockets, false)); } -bool WSClient::sendTXT(const char *msg, size_t length) { - return wsock->sendTXT(msg, length); +void freeArduinoWSClient(Connection *connection) { + delete connection; } -void WSClient::setReceiveTXTcallback(ReceiveTXTcallback &callback) { - auto& captureLastRecv = lastRecv; - auto& captureLastConnected = lastConnected; - wsock->onEvent([callback, &captureLastRecv, &captureLastConnected](WStype_t type, uint8_t * payload, size_t length) { - switch (type) { - case WStype_DISCONNECTED: - MO_DBG_INFO("Disconnected"); - break; - case WStype_CONNECTED: - MO_DBG_INFO("Connected (path: %s)", payload); - captureLastRecv = mocpp_tick_ms(); - captureLastConnected = mocpp_tick_ms(); - break; - case WStype_TEXT: - if (callback((const char *) payload, length)) { //forward message to RequestQueue - captureLastRecv = mocpp_tick_ms(); - } else { - MO_DBG_WARN("Processing WebSocket input event failed"); - } - break; - case WStype_BIN: - MO_DBG_WARN("Binary data stream not supported"); - break; - case WStype_PING: - // pong will be send automatically - MO_DBG_TRAFFIC_IN(8, "WS ping"); - captureLastRecv = mocpp_tick_ms(); - break; - case WStype_PONG: - // answer to a ping we send - MO_DBG_TRAFFIC_IN(8, "WS pong"); - captureLastRecv = mocpp_tick_ms(); - break; - case WStype_FRAGMENT_TEXT_START: //fragments are not supported - default: - MO_DBG_WARN("Unsupported WebSocket event type"); - break; +Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { + + Connection *connection = nullptr; + WebSocketsClient *wsock = nullptr; + + if (!config.backendUrl) { + MO_DBG_ERR("invalid args"); + goto fail; + } + + wsock = new WebSocketsClient(); + if (!wsock) { + MO_DBG_ERR("OOM"); + goto fail; + } + + /* + * parse backendUrl so that it suits the links2004/arduinoWebSockets interface + */ + auto url = makeString("WebSocketsClient", config.backendUrl); + + //tolower protocol specifier + for (auto c = url.begin(); *c != ':' && c != url.end(); c++) { + *c = tolower(*c); + } + + bool isTLS = true; + if (!strncmp(url.c_str(),"wss://",strlen("wss://"))) { + isTLS = true; + } else if (!strncmp(url.c_str(),"ws://",strlen("ws://"))) { + isTLS = false; + } else { + MO_DBG_ERR("only ws:// and wss:// supported"); + return; + } + + //parse host, port + auto host_port_path = url.substr(url.find_first_of("://") + strlen("://")); + auto host_port = host_port_path.substr(0, host_port_path.find_first_of('/')); + auto path = host_port_path.substr(host_port.length()); + auto host = host_port.substr(0, host_port.find_first_of(':')); + if (host.empty()) { + MO_DBG_ERR("could not parse host: %s", url.c_str()); + return; + } + uint16_t port = 0; + auto port_str = host_port.substr(host.length()); + if (port_str.empty()) { + port = isTLS ? 443U : 80U; + } else { + //skip leading ':' + port_str = port_str.substr(1); + for (auto c = port_str.begin(); c != port_str.end(); c++) { + if (*c < '0' || *c > '9') { + MO_DBG_ERR("could not parse port: %s", url.c_str()); + return; + } + auto p = port * 10U + (*c - '0'); + if (p < port) { + MO_DBG_ERR("could not parse port (overflow): %s", url.c_str()); + return; + } + port = p; } - }); -} + } -unsigned long WSClient::getLastRecv() { - return lastRecv; -} + if (path.empty()) { + path = "/"; + } + + if (config.chargeBoxId) { + if (path.back() != '/') { + path += '/'; + } + + path += config.chargeBoxId; + } + + const char *ocppVersionStr = "ocpp1.6"; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + ocppVersionStr = "ocpp1.6"; + } + #endif + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + ocppVersionStr = "ocpp2.0.1"; + } + #endif + + MO_DBG_INFO("connecting to %s -- (host: %s, port: %u, path: %s)", url.c_str(), host.c_str(), port, path.c_str()); + + wsock = new WebSocketsClient(); + if (!wsock) { + MO_DBG_ERR("OOM"); + goto fail; + } + + if (config.isTLS) { + // server address, port, path and TLS certificate + webSocket->beginSslWithCA(host.c_str(), port, path.c_str(), config.CA_cert, ocppVersionStr); + } else { + // server address, port, path + webSocket->begin(host.c_str(), port, path.c_str(), ocppVersionStr); + } + + // try ever 5000 again if connection has failed + webSocket->setReconnectInterval(5000); + + // start heartbeat (optional) + // ping server every 15000 ms + // expect pong from server within 3000 ms + // consider connection disconnected if pong is not received 2 times + webSocket->enableHeartbeat(15000, 3000, 2); //comment this one out to for specific OCPP servers + + size_t chargeBoxIdLen = config.chargeBoxId ? strlen(config.chargeBoxId) : 0; + size_t authorizationKeyLen = config.authorizationKey ? strlen(config.authorizationKey) : 0; + + // add authentication data (optional) + if (config.authorizationKey && (authorizationKeyLen + chargeBoxIdLen >= 4)) { + webSocket->setAuthorization(config.chargeBoxId ? config.chargeBoxId : "", config.authorizationKey); + } + + connection = new ArduinoWSClient(webSocket, true); + if (!connection) { + MO_DBG_ERR("OOM"); + goto fail; + } -unsigned long WSClient::getLastConnected() { - return lastConnected; + //success + return static_cast(connection); +fail: + delete connection; + delete wsock; } -bool WSClient::isConnected() { - return wsock->isConnected(); +void freeDefaultConnection(Connection *connection) { + delete connection; } -#endif +} //namespace MicroOcpp +#endif //MO_WS_USE diff --git a/src/MicroOcpp/Core/Connection.h b/src/MicroOcpp/Core/Connection.h index caef2c6e..b88c5bf7 100644 --- a/src/MicroOcpp/Core/Connection.h +++ b/src/MicroOcpp/Core/Connection.h @@ -5,29 +5,37 @@ #ifndef MO_CONNECTION_H #define MO_CONNECTION_H -#include -#include - #include #include +#define MO_WS_CUSTOM 0 +#define MO_WS_ARDUINO 1 + //On all platforms other than Arduino, the integrated WS lib (links2004/arduinoWebSockets) cannot be //used. On Arduino its usage is optional. -#ifndef MO_CUSTOM_WS -#if MO_PLATFORM != MO_PLATFORM_ARDUINO -#define MO_CUSTOM_WS +#ifndef MO_WS_USE +#if MO_PLATFORM == MO_PLATFORM_ARDUINO +#define MO_WS_USE MO_WS_ARDUINO +#else +#define MO_WS_USE MO_WS_CUSTOM #endif -#endif //ndef MO_CUSTOM_WS +#endif //MO_WS_USE + +#ifdef __cplusplus namespace MicroOcpp { -using ReceiveTXTcallback = std::function; +class Context; class Connection { +protected: + Context *context = nullptr; public: - Connection() = default; + Connection(); virtual ~Connection() = default; + void setContext(Context *context); // MO will set this during `setConnection()` + /* * The OCPP library will call this function frequently. If you need to execute regular routines, like * calling the loop-function of the WebSocket library, implement them here @@ -44,19 +52,7 @@ class Connection { * The OCPP library calls this function once during initialization. It passes a callback function to * the socket. The socket should forward any incoming payload from the OCPP server to the receiveTXT callback */ - bool recvTXT(const char *msg, size_t len) = 0; - - /* - * Returns the timestamp of the last incoming message. Use mocpp_tick_ms() for creating the correct timestamp - * - * DEPRECATED: this function is superseded by isConnected(). Will be removed in MO v2.0 - */ - virtual unsigned long getLastRecv() {return 0;} - - /* - * Returns the timestamp of the last time a connection got successfully established. Use mocpp_tick_ms() for creating the correct timestamp - */ - virtual unsigned long getLastConnected() = 0; + bool receiveTXT(const char *msg, size_t len); /* * NEW IN v1.1 @@ -75,58 +71,51 @@ class Connection { class LoopbackConnection : public Connection, public MemoryManaged { private: - ReceiveTXTcallback receiveTXT; - //for simulating connection losses bool online = true; bool connected = true; - unsigned long lastRecv = 0; - unsigned long lastConn = 0; public: LoopbackConnection(); void loop() override; bool sendTXT(const char *msg, size_t length) override; - void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) override; - unsigned long getLastRecv() override; - unsigned long getLastConnected() override; void setOnline(bool online); //"online": sent messages are going through - bool isOnline() {return online;} + bool isOnline(); void setConnected(bool connected); //"connected": connection has been established, but messages may not go through (e.g. weak connection) - bool isConnected() override {return connected;} + bool isConnected() override; }; -} //end namespace MicroOcpp - -#ifndef MO_CUSTOM_WS +} //namespace MicroOcpp -#include +#endif //__cplusplus -namespace MicroOcpp { -namespace EspWiFi { +#if MO_WS_USE == MO_WS_ARDUINO -class WSClient : public Connection, public MemoryManaged { -private: - WebSocketsClient *wsock; - unsigned long lastRecv = 0, lastConnected = 0; -public: - WSClient(WebSocketsClient *wsock); - - void loop(); +#ifdef __cplusplus +extern "C" { +#endif - bool sendTXT(const char *msg, size_t length); +typedef struct { + const char *backendUrl; //e.g. "wss://example.com:8443/steve/websocket/CentralSystemService". Must be defined + const char *chargeBoxId; //e.g. "charger001". Can be NULL + const char *authorizationKey; //authorizationKey present in the websocket message header. Can be NULL. Set this to enable OCPP Security Profile 2 + const char *CA_cert; //TLS certificate. Can be NULL. Set this to enable OCPP Security Profile 2 +} MO_ConnectionConfig; - void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT); +#ifdef __cplusplus +} //extern "C" - unsigned long getLastRecv() override; //get time of last successful receive in millis +namespace MicroOcpp { - unsigned long getLastConnected() override; //get last connection creation in millis +class WebSocketsClient; +Connection *makeArduinoWSClient(WebSocketsClient& arduinoWebsockets); //does not take ownership of arduinoWebsockets +void freeArduinoWSClient(Connection *connection); - bool isConnected() override; -}; +Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion); +void freeDefaultConnection(Connection *connection); -} //end namespace EspWiFi -} //end namespace MicroOcpp -#endif //ndef MO_CUSTOM_WS +} //namespace MicroOcpp +#endif //__cplusplus +#endif //MO_WS_USE #endif diff --git a/src/MicroOcpp/Core/Context.cpp b/src/MicroOcpp/Core/Context.cpp deleted file mode 100644 index 25c2032b..00000000 --- a/src/MicroOcpp/Core/Context.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include -#include - -#include - -using namespace MicroOcpp; - -Context::Context() : MemoryManaged("Context") { - -} - -Context::~Context() { - if (filesystem && isFilesystemOwner) { - delete filesystem; - filesystem = nullptr; - isFilesystemOwner = false; - } - - if (connection && isConnectionOwner) { - delete connection; - connection = nullptr; - isConnectionOwner = false; - } - - if (ftpClient && isFtpClientOwner) { - delete ftpClient; - ftpClient = nullptr; - isFtpClientOwner = false; - } -} - -void Context::setDebugCb(void (*debugCb)(const char *msg)) { - this->debugCb = debugCb; -} - -void Context::setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)) { - this->debugCb2 = debugCb2; -} - -void Context::setTicksCb(unsigned long (*ticksCb)()) { - this->ticksCb = ticksCb; -} - -void Context::setFilesystemAdapter(FilesystemAdapter *filesystem) { - if (this->filesystem && isFilesystemOwner) { - delete this->filesystem; - this->filesystem = nullptr; - isFilesystemOwner = false; - } - this->filesystem = filesystem; -} - -FilesystemAdapter *Context::getFilesystemAdapter() { - return filesystem; -} - -void Context::setConnection(Connection *connection) { - if (connection && isConnectionOwner) { - delete connection; - connection = nullptr; - isConnectionOwner = false; - } - this->connection = connection; -} - -Connection *Context::getConnection() { - return connection; -} - -void Context::setFtpClient(FtpClient *ftpClient) { - if (ftpClient && isFtpClientOwner) { - delete ftpClient; - ftpClient = nullptr; - isFtpClientOwner = false; - } - ftpClient = ftpClient; -} - -FtpClient *Context::getFtpClient() { - return ftpClient; -} - -bool Context::setup(int protocolVersion) { - -} - - void Context::loop(); - - Model& getModel(); - - OperationRegistry& getOperationRegistry(); - - RequestQueue& getRequestQueue(); - - const ProtocolVersion& getVersion(); -}; - -} //end namespace MicroOcpp - -#endif // __cplusplus - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -struct mo_context; -typedef struct mo_context mo_context; - -mo_context *mo_context_make(); - -void mo_context_free(mo_context *ctx); - -void mo_context_dbg_cb_set(mo_context *ctx, void (*debug_cb)(const char *msg)); - -void mo_context_dbg_cb2_set(mo_context *ctx, void (*debug_cb)(int lvl, const char *fn, int line, const char *msg)); - -void mo_context_ticks_cb_set(unsigned long (*ticksCb)()); - -bool mo_context_setup(mo_context *ctx, int protocol_version); - -void mo_context_loop(mo_context *ctx); - -void Context::loop() { - connection.loop(); - reqQueue.loop(); - model.loop(); -} - -Model& Context::getModel() { - return model; -} - -OperationRegistry& Context::getOperationRegistry() { - return operationRegistry; -} - -const ProtocolVersion& Context::getVersion() { - return model.getVersion(); -} - -Connection& Context::getConnection() { - return connection; -} - -RequestQueue& Context::getRequestQueue() { - return reqQueue; -} - -void Context::setFtpClient(std::unique_ptr ftpClient) { - this->ftpClient = std::move(ftpClient); -} - -FtpClient *Context::getFtpClient() { - return ftpClient.get(); -} diff --git a/src/MicroOcpp/Core/Context.h b/src/MicroOcpp/Core/Context.h deleted file mode 100644 index e49615a5..00000000 --- a/src/MicroOcpp/Core/Context.h +++ /dev/null @@ -1,99 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2025 -// MIT License - -#ifndef MO_CONTEXT_H -#define MO_CONTEXT_H - -#include -#include -#include -#include -#include - -#ifdef __cplusplus - -namespace MicroOcpp { - -class FilesystemAdapter; -class Connection; -class FtpClient; - -class Context : public MemoryManaged { -private: - void (*debugCb)(const char *msg) = nullptr; - void (*debugCb2)(int lvl, const char *fn, int line, const char *msg) = nullptr; - - unsigned long (*ticksCb)() = nullptr; - - FilesystemAdapter *filesystem = nullptr; - Connection *connection = nullptr; - FtpClient *ftpClient = nullptr; - - Model model; - OperationRegistry operationRegistry; - RequestQueue reqQueue; - - bool isFilesystemOwner = false; - bool isConnectionOwner = false; - bool isFtpClientOwner = false; - -public: - Context(); - ~Context(); - - void setDebugCb(void (*debugCb)(const char *msg)); - void setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)); - - void setTicksCb(unsigned long (*ticksCb)()); - - void setFilesystemAdapter(FilesystemAdapter *filesystem); - FilesystemAdapter *getFilesystemAdapter(); - - void setConnection(Connection *connection); - Connection *getConnection(); - - void setFtpClient(FtpClient *ftpClient); - FtpClient *getFtpClient(); - - Model& getModel(); - - bool setup(int ocppVersion); - - void loop(); - - OperationRegistry& getOperationRegistry(); - - RequestQueue& getRequestQueue(); -}; - -} //end namespace MicroOcpp - -#endif // __cplusplus - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -struct mo_context; -typedef struct mo_context mo_context; - -mo_context *mo_context_make(); - -void mo_context_free(mo_context *ctx); - -void mo_context_dbg_cb_set(mo_context *ctx, void (*debug_cb)(const char *msg)); - -void mo_context_dbg_cb2_set(mo_context *ctx, void (*debug_cb)(int lvl, const char *fn, int line, const char *msg)); - -void mo_context_ticks_cb_set(unsigned long (*ticksCb)()); - -bool mo_context_setup(mo_context *ctx, int ocpp_version); - -void mo_context_loop(mo_context *ctx); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif diff --git a/src/MicroOcpp/Core/FilesystemAdapter.cpp b/src/MicroOcpp/Core/FilesystemAdapter.cpp index b442e583..b8b4d37b 100644 --- a/src/MicroOcpp/Core/FilesystemAdapter.cpp +++ b/src/MicroOcpp/Core/FilesystemAdapter.cpp @@ -1,13 +1,12 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include -#include //FilesystemOpt #include #include -#include +#include /* * Platform specific implementations. Currently supported: @@ -25,453 +24,650 @@ #include namespace MicroOcpp { +namespace IndexedFS { -class FilesystemAdapterIndex; +struct FilesystemAdapterIndex; -class IndexedFileAdapter : public FileAdapter, public MemoryManaged { -private: +struct IndexedFileAdapter : public MemoryManaged { FilesystemAdapterIndex& index; - char fn [MO_MAX_PATH_SIZE]; - std::unique_ptr file; - + MO_File *file = nullptr; //takes ownership + char *fname = nullptr; //no ownership size_t written = 0; -public: - IndexedFileAdapter(FilesystemAdapterIndex& index, const char *fn, std::unique_ptr file) - : MemoryManaged("FilesystemIndex"), index(index), file(std::move(file)) { - snprintf(this->fn, sizeof(this->fn), "%s", fn); - } - - ~IndexedFileAdapter(); // destructor updates file index with written size - - size_t read(char *buf, size_t len) override { - return file->read(buf, len); - } - size_t write(const char *buf, size_t len) override { - auto ret = file->write(buf, len); - written += ret; - return ret; - } - - size_t seek(size_t offset) override { - auto ret = file->seek(offset); - written = ret; - return ret; - } - - int read() override { - return file->read(); - } + IndexedFileAdapter(FilesystemAdapterIndex& index) : MemoryManaged("FilesystemIndex"), index(index) { } }; -class FilesystemAdapterIndex : public FilesystemAdapter, public MemoryManaged { -private: - std::shared_ptr filesystem; +struct FilesystemAdapterIndex : public MemoryManaged { +public: + MO_FilesystemAdapter *filesystem = nullptr; //takes ownership struct IndexEntry { - String fname; + char *fname = nullptr; //takes ownership size_t size; - - IndexEntry(const char *fname, size_t size) : fname(makeString("FilesystemIndex", fname)), size(size) { } + IndexEntry(const char *fname, size_t size) : fname(fname), size(size) { } + ~IndexEntry() { + MO_FREE(fname); + fname = nullptr; + } }; - Vector index; + Vector fileEntries; - IndexEntry *getEntryByFname(const char *fn) { - auto entry = std::find_if(index.begin(), index.end(), - [fn] (const IndexEntry& el) -> bool { - return el.fname.compare(fn) == 0; - }); + IndexEntry *getEntryByPath(const char *path) { + const char *prefix = filesystem->path_prefix ? filesystem->path_prefix : ""; + size_t prefix_len = strlen(prefix); - if (entry != index.end()) { - return &(*entry); - } else { + if (strncmp(path, prefix, prefix_len)) { return nullptr; } + + const char *fname = path + prefix_len; + + for (size_t i = 0; i < fileEntries.size(); i++) { + if (!strcmp(fileEntries[i].fname, fname)) { + return &fileEntries[i]; + } + } + return nullptr; } +}; - IndexEntry *getEntryByPath(const char *path) { - if (strlen(path) < sizeof(MO_FILENAME_PREFIX) - 1) { - MO_DBG_ERR("invalid fn"); - return nullptr; +Vector indexedFilesystems; +FilesystemAdapterIndex *getFilesystemAdapterIndexByPath(const char *path) { + if (!path) { + path = ""; + } + for (size_t i = 0; i < indexedFilesystems.size(); i++) { + auto index = &indexedFilesystems[i]; + const char *prefix = index->filesystem->path_prefix ? index->filesystem->path_prefix : ""; + size_t prefix_len = strlen(prefix); + if (!*path || !*prefix) { + //filesystem adapters are not distinguishable by a path + return index; + } + if (!strncmp(path, index->filesystem->path_prefix, prefix_len)) { + return index; } + } + return nullptr; +} - const char *fn = path + sizeof(MO_FILENAME_PREFIX) - 1; - return getEntryByFname(fn); +int stat(const char *path, size_t *size) { + auto index = getFilesystemAdapterIndexByPath(path); + if (!index) { + MO_DBG_ERR("path not recognized"); + return -1; } - - void (*onDestruct)(void*) = nullptr; -public: - FilesystemAdapterIndex(std::shared_ptr filesystem, void (*onDestruct)(void*) = nullptr) : MemoryManaged("FilesystemIndex"), filesystem(std::move(filesystem)), index(makeVector("FilesystemIndex")), onDestruct(onDestruct) { } + auto entry = index->getEntryByPath(path); + if (entry) { + *size = entry->size; + } else { + return -1; + } +} - ~FilesystemAdapterIndex() { - if (onDestruct) { - onDestruct(this); - } +bool remove(const char *path) { + auto index = getFilesystemAdapterIndexByPath(path); + if (!index) { + MO_DBG_ERR("path not recognized"); + return false; + } + auto entry = index.getEntryByPath(path); + if (!entry) { + return true; } + bool removed = index.filesystem->remove(path); + if (removed) { + index.fileEntries.erase(index.fileEntries.begin() + (entry - &index.fileEntries[0])); + } + return removed; +} - int stat(const char *path, size_t *size) override { - if (auto file = getEntryByPath(path)) { - *size = file->size; - return 0; - } else { - return -1; +int ftw(const char *path_prefix, int(*fn)(const char *fname, void *mo_user_data), void *mo_user_data) { + auto index = getFilesystemAdapterIndexByPath(path_prefix); + if (!index) { + MO_DBG_ERR("path not recognized"); + return -1; + } + for (size_t it = 0; it < index.size();) { + auto size_before = index.size(); + auto err = fn(index[it].fname); + if (err) { + return err; } + if (index.size() + 1 == size_before) { + // element removed + continue; + } + // normal execution + it++; } - std::unique_ptr open(const char *path, const char *mode) { - if (!strcmp(mode, "r")) { - return filesystem->open(path, "r"); - } else if (!strcmp(mode, "w")) { - - if (strlen(path) < sizeof(MO_FILENAME_PREFIX) - 1) { - MO_DBG_ERR("invalid fn"); - return nullptr; - } + return 0; +} - const char *fn = path + sizeof(MO_FILENAME_PREFIX) - 1; +MO_File* open(const char *path, const char *mode) { + auto index = getFilesystemAdapterIndexByPath(path); + if (!index) { + MO_DBG_ERR("path not recognized"); + return nullptr; + } - auto file = filesystem->open(path, "w"); - if (!file) { - return nullptr; - } + IndexEntry *entry = nullptr; + const char *fnCopy = nullptr; + bool created = false; + if (!(entry = getEntryByPath(path))) { - IndexEntry *entry = nullptr; - if (!(entry = getEntryByFname(fn))) { - index.emplace_back(fn, 0); - entry = &index.back(); - } + size_t capacity = index->fileEntries.size() + 1; + index->fileEntries.reserve(capacity); + if (index->fileEntries.capacity() < size) { + MO_DBG_ERR("OOM"); + return nullptr; + } - if (!entry) { - MO_DBG_ERR("internal error"); - return nullptr; - } + const char *prefix = index.path_prefix ? index.path_prefix : ""; + size_t prefix_len = strlen(prefix); + if (!strncmp(path, prefix, prefix_len)) { + MO_DBG_ERR("internal error"); + return nullptr; + } - entry->size = 0; //write always empties the file + char *fn = path + prefix_len; + size_t fn_size = strlen(path) - prefix_len + 1; - return std::unique_ptr(new IndexedFileAdapter(*this, entry->fname.c_str(), std::move(file))); - } else { - MO_DBG_ERR("only support r or w"); + char *fnCopy = static_cast(MO_MALLOC("FilesystemIndex", fn_size)); + if (!fnCopy) { + MO_DBG_ERR("OOM"); return nullptr; } + + (void)snprintf(fnCopy, fn_size, "%s", fn); + created = true; } - bool remove(const char *path) override { - if (strlen(path) >= sizeof(MO_FILENAME_PREFIX) - 1) { - //valid path - const char *fn = path + sizeof(MO_FILENAME_PREFIX) - 1; - index.erase(std::remove_if(index.begin(), index.end(), - [fn] (const IndexEntry& el) -> bool { - return el.fname.compare(fn) == 0; - }), index.end()); - } + auto indexedFile = new IndexedFileAdapter(*index); + if (!indexedFile) { + MO_DBG_ERR("OOM"); + MO_FREE(fnCopy); + fnCopy = nullptr + return nullptr + } - return filesystem->remove(path); + auto file = filesystem->open(path, mode); + if (!file) { + MO_FREE(fnCopy); + fnCopy = nullptr; + delete indexedFile; + indexedFile = nullptr; + return nullptr; } - int ftw_root(std::function fn) { - // allow fn to remove elements - for (size_t it = 0; it < index.size();) { - auto size_before = index.size(); - auto err = fn(index[it].fname.c_str()); - if (err) { - return err; - } - if (index.size() + 1 == size_before) { - // element removed - continue; - } - // normal execution - it++; - } + if (created) { + index.emplace_back(fnCopy, 0); //transfer ownership of fnCopy + entry = &index.back(); + } - return 0; + if (!strcmp(mode, "w")) { + entry->size = 0; //write always empties the file } - bool createIndex() { - if (!index.empty()) { - return false; + indexedFile->file = file; //transfer ownership + indexedFile->fname = entry->fname; //no ownership + + return reinterpret_cast(indexedFile); +} + +bool close(MO_File *fileHandle) { + auto indexedFile = reinterpret_cast(fileHandle); + auto index = indexedFile->index; + auto file = indexedFile->file; + auto filesystem = index->filesystem; + bool success = filesystem->close(file); + if (success) { + auto entry = index->getEntryByFname(indexedFile->fname); + if (entry) { + entry->size = indexedFile->written; + MO_DBG_DEBUG("update index: %s (%zuB)", entry->fname, entry->size); + } else { + MO_DBG_ERR("index consistency failure"); + //don't return false, the contents have been written and the file operation was successful } - auto ret = filesystem->ftw_root([this] (const char *fn) -> int { - int ret; - char path [MO_MAX_PATH_SIZE]; - - ret = snprintf(path, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "%s", fn); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return 0; //ignore this entry and continue ftw - } + } + delete indexedFile; + indexedFile = nullptr; + return success; +} - size_t size; - ret = filesystem->stat(path, &size); - if (ret == 0) { - //add fn and size to index - MO_DBG_DEBUG("add file to index: %s (%zuB)", fn, size); - index.emplace_back(fn, size); - return 0; //successfully added filename to index - } else { - MO_DBG_ERR("unexpected entry: %s", fn); - return 0; //ignore this entry and continue ftw - } - }); +size_t read(MO_File *file, char *buf, size_t len) { + auto indexedFile = reinterpret_cast(fileHandle); + auto file = indexedFile->file; + auto filesystem = indexedFile->index->filesystem; + return filesystem->read(file, buf, len); +} + +int getc(MO_File *file) { + auto indexedFile = reinterpret_cast(fileHandle); + auto file = indexedFile->file; + auto filesystem = indexedFile->index->filesystem; + return filesystem->getc(file); +} - MO_DBG_DEBUG("create fs index: %s, %zu entries", ret == 0 ? "success" : "failure", index.size()); +size_t write(MO_File *file, const char *buf, size_t len) { + auto indexedFile = reinterpret_cast(fileHandle); + auto file = indexedFile->file; + auto filesystem = indexedFile->index->filesystem; - return ret == 0; + auto ret = filesystem->write(file, buf, len); + indexedFile->written += ret; + return ret; +} + +int seek(MO_File *file, size_t fpos) { + auto indexedFile = reinterpret_cast(fileHandle); + auto file = indexedFile->file; + auto filesystem = indexedFile->index->filesystem; + + auto ret = filesystem->seek(file, fpos); + if (ret >= 0) { + indexedFile->written = (size_t)ret; } + return ret; +} - void updateFilesize(const char *fn, size_t size) { - if (auto entry = getEntryByFname(fn)) { - entry->size = size; - MO_DBG_DEBUG("update index: %s (%zuB)", entry->fname.c_str(), entry->size); - } +int loadIndexEntry(const char *fname, void *user_data) { + auto index = reinterpret_cast(user_data); + int ret; + char path [MO_MAX_PATH_SIZE]; + + const char *prefix = index.path_prefix ? index.path_prefix : ""; + + ret = snprintf(path, sizeof(path), "%s%s", prefix, fname); + if (ret < 0 || ret >= sizeof(path),) { + MO_DBG_ERR("snprintf: %i", ret); + return -1; } -}; -IndexedFileAdapter::~IndexedFileAdapter() { - index.updateFilesize(fn, written); + auto filesystem = index->filesystem; + + size_t size; + ret = filesystem->stat(path, &size); + if (ret == 0) { + //add fn and size to index + MO_DBG_DEBUG("add file to index: %s (%zuB)", fname, size); + + size_t fnameSize = strlen(fname) + 1; + char *fnameCopy = static_cast(MO_MALLOC("FilesystemIndex", fnameSize)); + if (!fnameCopy) { + MO_DBG_ERR("OOM"); + return -1; + } + + size_t capacity = index->fileEntries.size() + 1; + index->fileEntries.reserve(capacity); + if (index->fileEntries.capacity() < size) { + MO_DBG_ERR("OOM"); + MO_FREE(fnameCopy); + fnameCopy = nullptr + return -1; + } + + index.emplace_back(fnameCopy, size); + return 0; //successfully added filename to index + } else { + MO_DBG_ERR("unexpected entry: %s", fname); + return 0; //ignore this entry and continue ftw + } } -std::shared_ptr decorateIndex(std::shared_ptr filesystem, void (*onDestruct)(void*) = nullptr) { +MO_FilesystemAdapter *decorateIndex(MO_FilesystemAdapter *filesystem) { + if (getFilesystemAdapterIndexByPath(filesystem->path_prefix)) { + MO_DBG_ERR("Multiple filesystem indexes only supported with unique MO folder paths"); + goto fail; + } - auto fsIndex = std::allocate_shared(makeAllocator("FilesystemIndex"), std::move(filesystem), onDestruct); - if (!fsIndex) { + auto index = new FilesystemAdapterIndex(); + if (!index) { MO_DBG_ERR("OOM"); - return nullptr; + goto fail; } - if (!fsIndex->createIndex()) { - MO_DBG_ERR("createIndex err"); - return nullptr; + auto decorator = static_cast(MO_MALLOC("Filesystem", sizeof(MO_FilesystemAdapter))); + if (!decorator) { + MO_DBG_ERR("OOM"); + goto fail; + } + memset(decorator, 0, sizeof(MO_FilesystemAdapter)); + + decorator->path_prefix = filesystem->path_prefix; + decorator->stat = stat; + decorator->remove = remove; + decorator->ftw = ftw; + decorator->open = open; + decorator->close = close; + decorator->read = read; + decorator->getc = getc; + decorator->write = write; + decorator->seek = seek; + + size_t capacity = indexedFilesystems.size() + 1; + indexedFilesystems.reserve(capacity); + if (indexedFilesystems.capacity() < size) { + MO_DBG_ERR("OOM"); + goto fail; } - return fsIndex; + index->filesystem = filesystem; //transfer ownership + + //enumerate files in MO folder and load each file into the index structure + auto ret = filesystem->ftw(filesystem->path_prefix, loadIndexEntry, reinterpret_cast(index)); + if (ret == 0) { + MO_DBG_DEBUG("create fs index: found %zu entries", index->fileEntries.size()); + } else { + MO_DBG_ERR("error loading fs index"); + goto fail; + } + + indexedFilesystems.push_back(index); + return decorator; +fail: + index->filesystem = nullptr; //revoke ownership + delete index; + index = nullptr; + MO_FREE(decorator); + decorator = nullptr; + return filesystem; +} + +MO_FilesystemAdapter *resetIndex(MO_FilesystemAdapter *decorator) { + auto index = getFilesystemAdapterIndexByPath(decorator->path_prefix); + if (!index) { + MO_DBG_ERR("Index not found"); + return decorator; + } + auto filesystem = index->filesystem; + indexedFilesystems.erase(indexedFilesystems.begin() + (index - &indexedFilesystems[0])); + delete index; + index = nullptr; + MO_FREE(decorator); + decorator = nullptr; + return filesystem; } -} // namespace MicroOcpp +} //namespace IndexedFS +} //namespace MicroOcpp #endif //MO_ENABLE_FILE_INDEX -#if MO_USE_FILEAPI == ARDUINO_LITTLEFS || MO_USE_FILEAPI == ARDUINO_SPIFFS +#if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS || MO_USE_FILEAPI == MO_ARDUINO_SPIFFS -#if MO_USE_FILEAPI == ARDUINO_LITTLEFS +#if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS #include #include #define USE_FS LittleFS -#elif MO_USE_FILEAPI == ARDUINO_SPIFFS +#elif MO_USE_FILEAPI == MO_ARDUINO_SPIFFS #include #define USE_FS SPIFFS #endif namespace MicroOcpp { - -class ArduinoFileAdapter : public FileAdapter, public MemoryManaged { - File file; -public: - ArduinoFileAdapter(File&& file) : MemoryManaged("Filesystem"), file(file) {} - - ~ArduinoFileAdapter() { - if (file) { - file.close(); - } +namespace ArduinoFS { + +int stat(const char *path, size_t *size) { +#if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS + char partition_path [MO_MAX_PATH_SIZE]; + auto ret = snprintf(partition_path, MO_MAX_PATH_SIZE, "/littlefs%s", path); + if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { + MO_DBG_ERR("fn error: %i", ret); + return -1; } - - int read() override { - return file.read(); - }; - size_t read(char *buf, size_t len) override { - return file.readBytes(buf, len); + struct ::stat st; + auto status = ::stat(partition_path, &st); + if (status == 0) { + *size = st.st_size; } - size_t write(const char *buf, size_t len) override { - return file.printf("%.*s", len, buf); + return status; +#elif MO_USE_FILEAPI == MO_ARDUINO_SPIFFS + if (!USE_FS.exists(path)) { + return -1; } - size_t seek(size_t offset) override { - return file.seek(offset); + File f = USE_FS.open(path, "r"); + if (!f) { + return -1; } -}; - -class ArduinoFilesystemAdapter : public FilesystemAdapter, public MemoryManaged { -private: - bool valid = false; - FilesystemOpt config; - - void (* onDestruct)(void*) = nullptr; -public: - ArduinoFilesystemAdapter(FilesystemOpt config, void (*onDestruct)(void*) = nullptr) : MemoryManaged("Filesystem"), config(config), onDestruct(onDestruct) { - valid = true; - if (config.mustMount()) { -#if MO_USE_FILEAPI == ARDUINO_LITTLEFS - if(!USE_FS.begin(config.formatOnFail())) { - MO_DBG_ERR("Error while mounting LITTLEFS"); - valid = false; - } else { - MO_DBG_DEBUG("LittleFS mount success"); - } -#elif MO_USE_FILEAPI == ARDUINO_SPIFFS - //ESP8266 - SPIFFSConfig cfg; - cfg.setAutoFormat(config.formatOnFail()); - SPIFFS.setConfig(cfg); + int status = -1; + if (!f.isDirectory()) { + *size = f.size(); + status = 0; + } - if (!SPIFFS.begin()) { - MO_DBG_ERR("Unable to initialize: unable to mount SPIFFS"); - valid = false; - } + f.close(); + return status; #else #error #endif - } //end if mustMount() - - } +} //end stat - ~ArduinoFilesystemAdapter() { - if (config.mustMount()) { - USE_FS.end(); - } +bool remove(const char *path) { + return USE_FS.remove(fn); +} - if (onDestruct) { - onDestruct(this); - } +int ftw(const char *path_prefix, int(*fn)(const char *fname, void *mo_user_data), void *mo_user_data) { + const char *prefix = path_prefix ? path_prefix : ""; +#if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS + auto dir = USE_FS.open(prefix); + if (!dir) { + MO_DBG_ERR("cannot open root directory: " prefix); + return -1; } - operator bool() {return valid;} + int err = 0; + while (auto entry = dir.openNextFile()) { + + char fname [MO_MAX_PATH_SIZE]; + auto ret = snprintf(fname, MO_MAX_PATH_SIZE, "%s", entry.name()); + entry.close(); - int stat(const char *path, size_t *size) override { -#if MO_USE_FILEAPI == ARDUINO_LITTLEFS - char partition_path [MO_MAX_PATH_SIZE]; - auto ret = snprintf(partition_path, MO_MAX_PATH_SIZE, "/littlefs%s", path); if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { MO_DBG_ERR("fn error: %i", ret); return -1; } - struct ::stat st; - auto status = ::stat(partition_path, &st); - if (status == 0) { - *size = st.st_size; - } - return status; -#elif MO_USE_FILEAPI == ARDUINO_SPIFFS - if (!USE_FS.exists(path)) { - return -1; - } - File f = USE_FS.open(path, "r"); - if (!f) { - return -1; - } - int status = -1; - if (!f.isDirectory()) { - *size = f.size(); - status = 0; + err = fn(fname, mo_user_data); + if (err) { + break; + } + } + return err; +#elif MO_USE_FILEAPI == MO_ARDUINO_SPIFFS + size_t prefix_len = strlen(prefix); + auto dir = USE_FS.openDir(prefix); + int err = 0; + while (dir.next()) { + auto path = dir.fileName(); + if (path.c_str()) { + const char *fname = path.c_str() + prefix_len; + err = fn(fname, mo_user_data); } else { - //fetch more information for directory when MicroOcpp also uses them - //status = 0; + MO_DBG_ERR("fs error"); + err = -1; } - - f.close(); - return status; + if (err) { + break; + } + } + return err; #else #error #endif - } //end stat +} - std::unique_ptr open(const char *fn, const char *mode) override { - File file = USE_FS.open(fn, mode); - if (file && !file.isDirectory()) { - MO_DBG_DEBUG("File open successful: %s", fn); - return std::unique_ptr(new ArduinoFileAdapter(std::move(file))); - } else { - return nullptr; - } +MO_File* open(const char *path, const char *mode) { + auto *file = new File(); + if (!file) { + MO_DBG_ERR("OOM"); + return nullptr; } - bool remove(const char *fn) override { - return USE_FS.remove(fn); - }; - int ftw_root(std::function fn) override { -#if MO_USE_FILEAPI == ARDUINO_LITTLEFS - auto dir = USE_FS.open(MO_FILENAME_PREFIX); - if (!dir) { - MO_DBG_ERR("cannot open root directory: " MO_FILENAME_PREFIX); - return -1; - } + *file = USE_FS.open(fn, mode); + if (!*file) { + MO_DBG_ERR("open file failure"); + goto fail; + } + if (file->isDirectory()) { + MO_DBG_ERR("file failure"); + goto fail; + } + MO_DBG_DEBUG("File open successful: %s", fn); + return reinterpret_cast(file); +fail: + if (file) { + file->close(); + } + delete file; + file = nullptr; + return nullptr; +} - int err = 0; - while (auto entry = dir.openNextFile()) { +bool close(MO_File *fileHandle) { + auto file = reinterpret_cast(fileHandle); + bool success = file->close(); + delete file; + return success; +} - char fname [MO_MAX_PATH_SIZE]; - auto ret = snprintf(fname, MO_MAX_PATH_SIZE, "%s", entry.name()); - entry.close(); +size_t read(MO_File *fileHandle, char *buf, size_t len) { + auto file = reinterpret_cast(fileHandle); + return file->readBytes(buf, len); +} - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return -1; - } +int getc(MO_File *fileHandle) { + auto file = reinterpret_cast(fileHandle); + return file->read(); +} - err = fn(fname); - if (err) { - break; - } +size_t write(MO_File *fileHandle, const char *buf, size_t len) { + auto file = reinterpret_cast(fileHandle); + return file->printf("%.*s", len, buf); +} + +int seek(MO_File *fileHandle, size_t fpos) { + auto file = reinterpret_cast(fileHandle); + return file->seek(offset); +} + +int useCount; +MO_FilesystemOpt opt = MO_FS_OPT_DISABLE; + +} //namespace ArduinoFS +} //namespace MicroOcpp + +MO_FilesystemAdapter *mo_makeDefaultFilesystemAdapter(MO_FilesystemConfig config) { + + char *prefix_copy = nullptr; + MO_FilesystemAdapter *filesystem = nullptr; + + if (config.opt == MO_FS_OPT_DISABLE) { + MO_DBG_DEBUG("Access to filesystem not allowed by opt"); + goto fail; + } + + const char *prefix = config.path_prefix; + size_t prefix_len = prefix ? strlen(prefix) : 0; + if (prefix_len > 0) { + size_t prefix_size = prefix_len + 1; + prefix_copy = static_cast(MO_MALLOC("Filesystem", prefix_size)); + if (!prefix_copy) { + MO_DBG_ERR("OOM"); + goto fail; } - return err; -#elif MO_USE_FILEAPI == ARDUINO_SPIFFS - auto dir = USE_FS.openDir(MO_FILENAME_PREFIX); - int err = 0; - while (dir.next()) { - auto fname = dir.fileName(); - if (fname.c_str()) { - err = fn(fname.c_str() + strlen(MO_FILENAME_PREFIX)); + (void)snprintf(prefix_copy, prefix_size, "%s", prefix); + } + + auto filesystem = static_cast(MO_MALLOC("Filesystem", sizeof(MO_FilesystemAdapter))); + if (!filesystem) { + MO_DBG_ERR("OOM"); + goto fail; + } + memset(filesystem, 0, sizeof(MO_FilesystemAdapter)); + + filesystem->path_prefix = prefix_copy; + filesystem->stat = MicroOcpp::ArduinoFS::stat; + filesystem->remove = MicroOcpp::ArduinoFS::remove; + filesystem->ftw = MicroOcpp::ArduinoFS::ftw; + filesystem->open = MicroOcpp::ArduinoFS::open; + filesystem->close = MicroOcpp::ArduinoFS::close; + filesystem->read = MicroOcpp::ArduinoFS::read; + filesystem->getc = MicroOcpp::ArduinoFS::getc; + filesystem->write = MicroOcpp::ArduinoFS::write; + filesystem->seek = MicroOcpp::ArduinoFS::seek; + + if (config.opt >= MO_FS_OPT_USE_MOUNT && MicroOcpp::ArduinoFS::opt < MO_FS_OPT_USE_MOUNT) { + #if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS + { + if(!USE_FS.begin(config.opt == MO_FS_OPT_USE_MOUNT_FORMAT_ON_FAIL)) { + MO_DBG_ERR("Error while mounting LITTLEFS"); + goto fail; } else { - MO_DBG_ERR("fs error"); - err = -1; + MO_DBG_DEBUG("LittleFS mount success"); } - if (err) { - break; + } + #elif MO_USE_FILEAPI == MO_ARDUINO_SPIFFS + { + //ESP8266 + SPIFFSConfig cfg; + cfg.setAutoFormat(config.opt == MO_FS_OPT_USE_MOUNT_FORMAT_ON_FAIL); + SPIFFS.setConfig(cfg); + + if (!SPIFFS.begin()) { + MO_DBG_ERR("Unable to initialize: unable to mount SPIFFS"); + goto fail; } } - return err; -#else -#error -#endif - } -}; + #else + #error + #endif -std::weak_ptr filesystemCache; - -void resetFilesystemCache(void*) { - filesystemCache.reset(); -} + //mounted successfully -std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { + } //if MO_FS_OPT_USE_MOUNT - if (auto cached = filesystemCache.lock()) { - return cached; + if (filesystem) { + MicroOcpp::ArduinoFS::useCount++; + if (config.opt > MicroOcpp::ArduinoFS::opt) { + MicroOcpp::ArduinoFS::opt = config.opt; + } } - if (!config.accessAllowed()) { - MO_DBG_DEBUG("Access to Arduino FS not allowed by config"); - return nullptr; - } +#if MO_ENABLE_FILE_INDEX + filesystem = MicroOcpp::IndexedFS::decorateIndex(filesystem); +#endif //MO_ENABLE_FILE_INDEX - auto fs_concrete = new ArduinoFilesystemAdapter(config, resetFilesystemCache); - auto fs = std::shared_ptr(fs_concrete, std::default_delete(), makeAllocator("Filesystem")); + return filesystem; +fail: + MO_FREE(prefix_copy); + MO_FREE(filesystem); + return nullptr; +} -#if MO_ENABLE_FILE_INDEX - fs = decorateIndex(fs, resetFilesystemCache); -#endif // MO_ENABLE_FILE_INDEX +void mo_freeDefaultFilesystemAdapter(MO_FilesystemAdapter *filesystem) { + if (!filesystem) { + return; //noop + } - filesystemCache = fs; +#if MO_ENABLE_FILE_INDEX + filesystem = MicroOcpp::IndexedFS::resetIndex(filesystem); +#endif //MO_ENABLE_FILE_INDEX - if (*fs_concrete) { - return fs; - } else { - return nullptr; + MicroOcpp::ArduinoFS::useCount--; + if (MicroOcpp::ArduinoFS::useCount <= 0 && MicroOcpp::ArduinoFS::opt >= MO_FS_OPT_USE_MOUNT) { + USE_FS.end(); + MicroOcpp::ArduinoFS::opt = MO_FS_OPT_DISABLE; } + MO_FREE(filesystem->path_prefix); + MO_FREE(filesystem); } -} //end namespace MicroOcpp - -#elif MO_USE_FILEAPI == ESPIDF_SPIFFS +#elif MO_USE_FILEAPI == MO_ESPIDF_SPIFFS #include #include @@ -482,156 +678,152 @@ std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt co #endif namespace MicroOcpp { +namespace EspIdfFS { -class EspIdfFileAdapter : public FileAdapter, public MemoryManaged { - FILE *file {nullptr}; -public: - EspIdfFileAdapter(FILE *file) : MemoryManaged("Filesystem"), file(file) {} - - ~EspIdfFileAdapter() { - fclose(file); +int stat(const char *path, size_t *size) { + struct ::stat st; + auto ret = ::stat(path, &st); + if (ret == 0) { + *size = st.st_size; } + return ret; +} - size_t read(char *buf, size_t len) override { - return fread(buf, 1, len, file); - } +bool remove(const char *path) { + return unlink(path) == 0; +} - size_t write(const char *buf, size_t len) override { - return fwrite(buf, 1, len, file); +int ftw(const char *path_prefix, int(*fn)(const char *fname, void *mo_user_data), void *mo_user_data) { + //open MO root directory + const char *prefix = path_prefix ? path_prefix : ""; + char dpath [MO_MAX_PATH_SIZE]; + auto path_len = snprintf(dpath, MO_MAX_PATH_SIZE, "%s", prefix); + if (path_len < 0 || path_len >= MO_MAX_PATH_SIZE) { + MO_DBG_ERR("fn error: %i", path_len); + return -1; } - size_t seek(size_t offset) override { - return fseek(file, offset, SEEK_SET); + // trim trailing '/' if not root directory + if (path_len >= 2 && dpath[path_len - 1] == '/') { + dpath[path_len - 1] = '\0'; } - int read() override { - return fgetc(file); + auto dir = opendir(dpath); + if (!dir) { + MO_DBG_ERR("cannot open root directory: %s", dpath); + return -1; } -}; -class EspIdfFilesystemAdapter : public FilesystemAdapter, public MemoryManaged { -public: - FilesystemOpt config; - - void (* onDestruct)(void*) = nullptr; -public: - EspIdfFilesystemAdapter(FilesystemOpt config, void (* onDestruct)(void*) = nullptr) : MemoryManaged("Filesystem"), config(config), onDestruct(onDestruct) { } - - ~EspIdfFilesystemAdapter() { - if (config.mustMount()) { - esp_vfs_spiffs_unregister(MO_PARTITION_LABEL); - MO_DBG_DEBUG("SPIFFS unmounted"); - } - - if (onDestruct) { - onDestruct(this); + int err = 0; + while (auto entry = readdir(dir)) { + err = fn(entry->d_name, mo_user_data); + if (err) { + break; } } - int stat(const char *path, size_t *size) override { - struct ::stat st; - auto ret = ::stat(path, &st); - if (ret == 0) { - *size = st.st_size; - } - return ret; - } + closedir(dir); + return err; +} - std::unique_ptr open(const char *fn, const char *mode) override { - auto file = fopen(fn, mode); - if (file) { - return std::unique_ptr(new EspIdfFileAdapter(std::move(file))); - } else { - MO_DBG_DEBUG("Failed to open file path %s", fn); - return nullptr; - } - } +MO_File* open(const char *path, const char *mode) { + return reinterpret_cast(fopen(path, mode)); +} - bool remove(const char *fn) override { - return unlink(fn) == 0; - } +bool close(MO_File *file) { + return fclose(reinterpret_cast(file)); +} - int ftw_root(std::function fn) override { - //open MO root directory - char dname [MO_MAX_PATH_SIZE]; - auto dlen = snprintf(dname, MO_MAX_PATH_SIZE, "%s", MO_FILENAME_PREFIX); - if (dlen < 0 || dlen >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", dlen); - return -1; - } +size_t read(MO_File *file, char *buf, size_t len) { + return fread(buf, 1, len, reinterpret_cast(file)); +} - // trim trailing '/' if not root directory - if (dlen >= 2 && dname[dlen - 1] == '/') { - dname[dlen - 1] = '\0'; - } +int getc(MO_File *file) { + return fgetc(reinterpret_cast(file)); +} - auto dir = opendir(dname); - if (!dir) { - MO_DBG_ERR("cannot open root directory: %s", dname); - return -1; - } +size_t write(MO_File *file, const char *buf, size_t len) { + return fwrite(buf, 1, len, reinterpret_cast(file)); +} - int err = 0; - while (auto entry = readdir(dir)) { - err = fn(entry->d_name); - if (err) { - break; - } - } +int seek(MO_File *file, size_t fpos) { + return fseek(file, fpos, SEEK_SET); +} - closedir(dir); - return err; - } -}; +int useCount; +MO_FilesystemOpt opt = MO_FS_OPT_DISABLE; -std::weak_ptr filesystemCache; +} //namespace EspIdfFS +} //namespace MicroOcpp -void resetFilesystemCache(void*) { - filesystemCache.reset(); -} +using namespace MicroOcpp; -std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { +MO_FilesystemAdapter *mo_makeDefaultFilesystemAdapter(MO_FilesystemConfig config) { - if (auto cached = filesystemCache.lock()) { - return cached; - } + char *prefix_copy = nullptr; + MO_FilesystemAdapter *filesystem = nullptr; - if (!config.accessAllowed()) { - MO_DBG_DEBUG("Access to ESP-IDF SPIFFS not allowed by config"); - return nullptr; + if (config.opt == MO_FS_OPT_DISABLE) { + MO_DBG_DEBUG("Access to filesystem not allowed by opt"); + goto fail; + } + + const char *prefix = config.path_prefix; + size_t prefix_len = prefix ? strlen(prefix) : 0; + if (prefix_len > 0) { + size_t prefix_size = prefix_len + 1; + prefix_copy = static_cast(MO_MALLOC("Filesystem", prefix_size)); + if (!prefix_copy) { + MO_DBG_ERR("OOM"); + goto fail; + } + (void)snprintf(prefix_copy, prefix_size, "%s", prefix); } - bool mounted = true; - - if (config.mustMount()) { - mounted = false; - - char fnpref [MO_MAX_PATH_SIZE]; - auto fnpref_len = snprintf(fnpref, MO_MAX_PATH_SIZE, "%s", MO_FILENAME_PREFIX); - if (fnpref_len < 0 || fnpref_len >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("MO_FILENAME_PREFIX error %i", fnpref_len); - return nullptr; - } else if (fnpref_len <= 2) { //shortest possible prefix: "/p/", i.e. length = 3 - MO_DBG_ERR("MO_FILENAME_PREFIX cannot be root on ESP-IDF (working example: \"/mo_store/\")"); - return nullptr; + auto filesystem = static_cast(MO_MALLOC("Filesystem", sizeof(MO_FilesystemAdapter))); + if (!filesystem) { + MO_DBG_ERR("OOM"); + goto fail; + } + memset(filesystem, 0, sizeof(MO_FilesystemAdapter)); + + filesystem->path_prefix = prefix_copy; + filesystem->stat = EspIdfFS::stat; + filesystem->remove = EspIdfFS::remove; + filesystem->ftw = EspIdfFS::ftw; + filesystem->open = EspIdfFS::open; + filesystem->close = EspIdfFS::close; + filesystem->read = EspIdfFS::read; + filesystem->getc = EspIdfFS::getc; + filesystem->write = EspIdfFS::write; + filesystem->seek = EspIdfFS::seek; + + if (config.opt >= MO_FS_OPT_USE_MOUNT && EspIdfFS::opt < MO_FS_OPT_USE_MOUNT) { + char prefix [MO_MAX_PATH_SIZE]; + auto prefix_len = snprintf(prefix, MO_MAX_PATH_SIZE, "%s", prefix ? prefix : ""); + if (prefix_len < 0 || prefix_len >= sizeof(prefix)) { + MO_DBG_ERR("path_prefix error %i", prefix_len); + goto fail; + } else if (prefix_len <= 2) { //shortest possible prefix: "/p/", i.e. length = 3 + MO_DBG_ERR("path_prefix cannot be root on ESP-IDF (working example: \"/mo_store/\")"); + goto fail; } // trim trailing '/' - if (fnpref[fnpref_len - 1] == '/') { - fnpref[fnpref_len - 1] = '\0'; + if (prefix[prefix_len - 1] == '/') { + prefix[prefix_len - 1] = '\0'; } esp_vfs_spiffs_conf_t conf = { - .base_path = fnpref, + .base_path = prefix, .partition_label = MO_PARTITION_LABEL, .max_files = 5, - .format_if_mount_failed = config.formatOnFail() + .format_if_mount_failed = (config.opt == MO_FS_OPT_USE_MOUNT_FORMAT_ON_FAIL) }; esp_err_t ret = esp_vfs_spiffs_register(&conf); if (ret == ESP_OK) { - mounted = true; MO_DBG_DEBUG("SPIFFS mounted"); } else { if (ret == ESP_FAIL) { @@ -641,160 +833,207 @@ std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt co } else { MO_DBG_ERR("Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); } + goto fail; + } + + //mounted successfully + + } //if MO_FS_OPT_USE_MOUNT + + if (filesystem) { + EspIdfFS::useCount++; + if (config.opt > EspIdfFS::opt) { + EspIdfFS::opt = config.opt; } } - if (mounted) { - auto fs = std::shared_ptr(new EspIdfFilesystemAdapter(config, resetFilesystemCache), std::default_delete(), makeAllocator("Filesystem")); +#if MO_ENABLE_FILE_INDEX + filesystem = decorateIndex(filesystem); +#endif //MO_ENABLE_FILE_INDEX + + return filesystem; +fail: + MO_FREE(prefix_copy); + MO_FREE(filesystem); + return nullptr; +} + +void mo_freeDefaultFilesystemAdapter(MO_FilesystemAdapter *filesystem) { + if (!filesystem) { + return; //noop + } #if MO_ENABLE_FILE_INDEX - fs = decorateIndex(fs, resetFilesystemCache); -#endif // MO_ENABLE_FILE_INDEX + filesystem = resetIndex(filesystem); +#endif //MO_ENABLE_FILE_INDEX - filesystemCache = fs; - return fs; - } else { - return nullptr; + EspIdfFS::useCount--; + if (EspIdfFS::useCount <= 0 && EspIdfFS::opt >= MO_FS_OPT_USE_MOUNT) { + esp_vfs_spiffs_unregister(MO_PARTITION_LABEL); + MO_DBG_DEBUG("SPIFFS unmounted"); + EspIdfFS::opt = MO_FS_OPT_DISABLE; } + MO_FREE(filesystem->path_prefix); + MO_FREE(filesystem); } -} //end namespace MicroOcpp - -#elif MO_USE_FILEAPI == POSIX_FILEAPI +#elif MO_USE_FILEAPI == MO_POSIX_FILEAPI #include #include #include namespace MicroOcpp { +namespace PosixFS { -class PosixFileAdapter : public FileAdapter, public MemoryManaged { - FILE *file {nullptr}; -public: - PosixFileAdapter(FILE *file) : MemoryManaged("Filesystem"), file(file) {} - - ~PosixFileAdapter() { - fclose(file); - } - - size_t read(char *buf, size_t len) override { - return fread(buf, 1, len, file); - } - - size_t write(const char *buf, size_t len) override { - return fwrite(buf, 1, len, file); +int stat(const char *path, size_t *size) { + struct ::stat st; + auto ret = ::stat(path, &st); + if (ret == 0) { + *size = st.st_size; } + return ret; +} - size_t seek(size_t offset) override { - return fseek(file, offset, SEEK_SET); - } +bool remove(const char *path) { + return ::remove(path) == 0; +} - int read() override { - return fgetc(file); +int ftw(const char *path_prefix, int(*fn)(const char *fname, void *mo_user_data), void *mo_user_data) { + auto dir = opendir(path_prefix); + if (!dir) { + MO_DBG_ERR("cannot open root directory: %s", path_prefix); + return -1; } -}; - -class PosixFilesystemAdapter : public FilesystemAdapter, public MemoryManaged { -public: - FilesystemOpt config; - - void (* onDestruct)(void*) = nullptr; -public: - PosixFilesystemAdapter(FilesystemOpt config, void (* onDestruct)(void*) = nullptr) : MemoryManaged("Filesystem"), config(config), onDestruct(onDestruct) { } - ~PosixFilesystemAdapter() { - if (onDestruct) { - onDestruct(this); + int err = 0; + while (auto entry = readdir(dir)) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { + continue; //files . and .. are specific to desktop systems and rarely appear on microcontroller filesystems. Filter them } - } - - int stat(const char *path, size_t *size) override { - struct ::stat st; - auto ret = ::stat(path, &st); - if (ret == 0) { - *size = st.st_size; + err = fn(entry->d_name, mo_user_data); + if (err) { + break; } - return ret; } - std::unique_ptr open(const char *fn, const char *mode) override { - auto file = fopen(fn, mode); - if (file) { - return std::unique_ptr(new PosixFileAdapter(std::move(file))); - } else { - MO_DBG_DEBUG("Failed to open file path %s", fn); - return nullptr; - } - } + closedir(dir); + return err; +} - bool remove(const char *fn) override { - return ::remove(fn) == 0; - } +MO_File* open(const char *path, const char *mode) { + return reinterpret_cast(fopen(path, mode)); +} - int ftw_root(std::function fn) override { - auto dir = opendir(MO_FILENAME_PREFIX); // use c_str() to convert the path string to a C-style string - if (!dir) { - MO_DBG_ERR("cannot open root directory: " MO_FILENAME_PREFIX); - return -1; - } +bool close(MO_File *file) { + return fclose(reinterpret_cast(file)); +} - int err = 0; - while (auto entry = readdir(dir)) { - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { - continue; //files . and .. are specific to desktop systems and rarely appear on microcontroller filesystems. Filter them - } - err = fn(entry->d_name); - if (err) { - break; - } - } +size_t read(MO_File *file, char *buf, size_t len) { + return fread(buf, 1, len, reinterpret_cast(file)); +} - closedir(dir); - return err; - } -}; +int getc(MO_File *file) { + return fgetc(reinterpret_cast(file)); +} -std::weak_ptr filesystemCache; +size_t write(MO_File *file, const char *buf, size_t len) { + return fwrite(buf, 1, len, reinterpret_cast(file)); +} -void resetFilesystemCache(void*) { - filesystemCache.reset(); +int seek(MO_File *file, size_t fpos) { + return fseek(reinterpret_cast(file), fpos, SEEK_SET); } -std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { +} //namespace PosixFS +} //namespace MicroOcpp - if (auto cached = filesystemCache.lock()) { - return cached; - } +using namespace MicroOcpp; + +MO_FilesystemAdapter *mo_makeDefaultFilesystemAdapter(MO_FilesystemConfig config) { - if (!config.accessAllowed()) { - MO_DBG_DEBUG("Access to FS not allowed by config"); + if (config.opt == MO_FS_OPT_DISABLE) { + MO_DBG_DEBUG("Access to filesystem not allowed by opt"); return nullptr; } - if (config.mustMount()) { - MO_DBG_DEBUG("Skip mounting on UNIX host"); + char *prefix_copy = nullptr; + MO_FilesystemAdapter *filesystem = nullptr; + + const char *prefix = config.path_prefix; + size_t prefix_len = prefix ? strlen(prefix) : 0; + if (prefix_len > 0) { + size_t prefix_size = prefix_len + 1; + prefix_copy = static_cast(MO_MALLOC("Filesystem", prefix_size)); + if (!prefix_copy) { + MO_DBG_ERR("OOM"); + goto fail; + } + (void)snprintf(prefix_copy, prefix_size, "%s", prefix); } - auto fs = std::shared_ptr(new PosixFilesystemAdapter(config, resetFilesystemCache), std::default_delete(), makeAllocator("Filesystem")); + filesystem = static_cast(MO_MALLOC("Filesystem", sizeof(MO_FilesystemAdapter))); + if (!filesystem) { + MO_DBG_ERR("OOM"); + goto fail; + } + memset(filesystem, 0, sizeof(MO_FilesystemAdapter)); + + filesystem->path_prefix = prefix_copy; + filesystem->stat = PosixFS::stat; + filesystem->remove = PosixFS::remove; + filesystem->ftw = PosixFS::ftw; + filesystem->open = PosixFS::open; + filesystem->close = PosixFS::close; + filesystem->read = PosixFS::read; + filesystem->getc = PosixFS::getc; + filesystem->write = PosixFS::write; + filesystem->seek = PosixFS::seek; + + // Create MO folder if not exists + struct ::stat st; + int ret; + ret = ::stat(filesystem->path_prefix, &st); + if (ret != 0) { + mkdir(filesystem->path_prefix, 0755); + + auto ret = ::stat(filesystem->path_prefix, &st); + if (ret == 0) { + MO_DBG_INFO("created MO folder at %s", filesystem->path_prefix); + } else { + MO_DBG_ERR("MO folder not found at %s",filesystem->path_prefix); + goto fail; + } + } #if MO_ENABLE_FILE_INDEX - fs = decorateIndex(fs, resetFilesystemCache); -#endif // MO_ENABLE_FILE_INDEX + filesystem = decorateIndex(filesystem); +#endif //MO_ENABLE_FILE_INDEX - filesystemCache = fs; - return fs; + return filesystem; +fail: + MO_FREE(prefix_copy); + MO_FREE(filesystem); + return nullptr; } -} //end namespace MicroOcpp +void mo_freeDefaultFilesystemAdapter(MO_FilesystemAdapter *filesystem) { -#else //filesystem disabled + if (!filesystem) { + return; //noop + } -namespace MicroOcpp { +#if MO_ENABLE_FILE_INDEX + filesystem = resetIndex(filesystem); +#endif //MO_ENABLE_FILE_INDEX -std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { - return nullptr; + //the default FS adapter owns the path_prefix buf and needs to free it + char *path_prefix_buf = const_cast(filesystem->path_prefix); + MO_FREE(path_prefix_buf); + + MO_FREE(filesystem); } -} //end namespace MicroOcpp +#else //filesystem disabled #endif //switch-case MO_USE_FILEAPI diff --git a/src/MicroOcpp/Core/FilesystemAdapter.h b/src/MicroOcpp/Core/FilesystemAdapter.h index 7e92e7cf..0a03d5a7 100644 --- a/src/MicroOcpp/Core/FilesystemAdapter.h +++ b/src/MicroOcpp/Core/FilesystemAdapter.h @@ -1,42 +1,41 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_FILESYSTEMADAPTER_H #define MO_FILESYSTEMADAPTER_H -#include -#include - +#include #include -#include -#define DISABLE_FS 0 -#define ARDUINO_LITTLEFS 1 -#define ARDUINO_SPIFFS 2 -#define ESPIDF_SPIFFS 3 -#define POSIX_FILEAPI 4 +#define MO_CUSTOM_FS 0 +#define MO_ARDUINO_LITTLEFS 1 +#define MO_ARDUINO_SPIFFS 2 +#define MO_ESPIDF_SPIFFS 3 +#define MO_POSIX_FILEAPI 4 // choose FileAPI if not given by build flag; assume usage with Arduino if no build flags are present #ifndef MO_USE_FILEAPI #if MO_PLATFORM == MO_PLATFORM_ARDUINO #if defined(ESP32) -#define MO_USE_FILEAPI ARDUINO_LITTLEFS +#define MO_USE_FILEAPI MO_ARDUINO_LITTLEFS #else -#define MO_USE_FILEAPI ARDUINO_SPIFFS +#define MO_USE_FILEAPI MO_ARDUINO_SPIFFS #endif #elif MO_PLATFORM == MO_PLATFORM_ESPIDF -#define MO_USE_FILEAPI ESPIDF_SPIFFS +#define MO_USE_FILEAPI MO_ESPIDF_SPIFFS #elif MO_PLATFORM == MO_PLATFORM_UNIX -#define MO_USE_FILEAPI POSIX_FILEAPI +#define MO_USE_FILEAPI MO_POSIX_FILEAPI #else -#define MO_USE_FILEAPI DISABLE_FS +#define MO_USE_FILEAPI MO_CUSTOM_FS #endif //switch-case MO_PLATFORM #endif //ndef MO_USE_FILEAPI #ifndef MO_FILENAME_PREFIX -#if MO_USE_FILEAPI == ESPIDF_SPIFFS +#if MO_USE_FILEAPI == MO_ESPIDF_SPIFFS #define MO_FILENAME_PREFIX "/mo_store/" +#elif MO_PLATFORM == MO_PLATFORM_UNIX +#define MO_FILENAME_PREFIX "./mo_store/" #else #define MO_FILENAME_PREFIX "/" #endif @@ -44,7 +43,7 @@ // set default max path size parameters #ifndef MO_MAX_PATH_SIZE -#if MO_USE_FILEAPI == POSIX_FILEAPI +#if MO_USE_FILEAPI == MO_POSIX_FILEAPI #define MO_MAX_PATH_SIZE 128 #else #define MO_MAX_PATH_SIZE 30 @@ -55,26 +54,75 @@ #define MO_ENABLE_FILE_INDEX 0 #endif -namespace MicroOcpp { +#ifdef __cplusplus +extern "C" { +#endif + +struct MO_File; +typedef struct MO_File MO_File; + +typedef struct { + /* Path of the MO root folder, plus trailing '/'. MO will create the path for accessing the filesystem by appending + * a filename to the end of path_prefix. Can be empty if the filesystem implementation accepts the MO filenames as + * path directly. MO doesn't use sub-directories. */ + const char *path_prefix; + + /* Checks if a file at `path` exists and if yes, writes the size of the file content into `size` and returns 0. Returns + * a non-zero code if the file doesn't exist, is a directory or the operation was unsuccessful. + * `path`: path to the file, determined by path_prefix and the MO file name + * `size`: output param for the file content size */ + int (*stat)(const char *path, size_t *size); + + /* Removes the file at `path`. Returns true if after this operation, no file at this path exists anymore, i.e. either + * the file was removed or there was no file. Returns false if the file couldn't be removed. + * `path`: path to the file, determined by path_prefix and the MO file name */ + bool (*remove)(const char *path); + + /* Enumerates all files in the folder at `path` and executes the callback function `fn()` for each file. Passes two + * arguments to `fn()`, the name of the enumerated file (!= path) and the `mo_user_data` pointer which is provided + * to `ftw()`. `ftw()` returns 0 if all `fn()` executions were successful, or the non-zero error code of the failing + * `fn()` execution. + * `path_prefix`: path to the directory to enumerate. `path` may contain a superfluous "/" at the end + * `fn`: callback function to execute for each file. Returns 0 on success and a non-zero value to abort `ftw()` + * `mo_user_data`: pointer to pass through to `fn()` */ + int (*ftw)(const char *path_prefix, int(*fn)(const char *fname, void *mo_user_data), void *mo_user_data); + + /* Opens the file at `path`. The `mode` is either "w" (write) or "r" (read). Returns an implementation-specific file + * handle on success, or NULL on failure. + * `path`: path to the file, determined by path_prefix and the MO file name + * `mode`: "w" (write) or "r" (read) mode */ + MO_File* (*open)(const char *path, const char *mode); -class FileAdapter { -public: - virtual ~FileAdapter() = default; - virtual size_t read(char *buf, size_t len) = 0; - virtual size_t write(const char *buf, size_t len) = 0; - virtual size_t seek(size_t offset) = 0; + /* Closes a file which has been opened with `open`. Returns `true` on success. For files in the write mode the success + * code also indicates if the file writes were successful. Deallocates the resources regargless of the error code. + * `file`: file handle */ + bool (*close)(MO_File *file); - virtual int read() = 0; -}; + /* Reads `file` contents into `buf` with `len` bytes bufsize. Returns the number of read bytes, or 0 on error or if end + * of file has been reached. If the return value is equal to `len`, MO will exeucte `read` again. + * `file`: file handle + * `buf`: read buffer + * `len`: bufsize in bytes */ + size_t (*read)(MO_File *file, char *buf, size_t len); -class FilesystemAdapter { -public: - virtual ~FilesystemAdapter() = default; - virtual int stat(const char *path, size_t *size) = 0; - virtual std::unique_ptr open(const char *fn, const char *mode) = 0; - virtual bool remove(const char *fn) = 0; - virtual int ftw_root(std::function fn) = 0; //enumerate the files in the mo_store root folder -}; + int (*getc)(MO_File *file); + + /* Writes `buf` (with `len` bytes) into `file`. Returns the number of bytes actually written. If the return value is + * less than `len`, that indicates an error and MO will stop writing to this file. + * `file`: file handle + * `buf`: write buffer + * `len`: bufsize in bytes */ + size_t (*write)(MO_File *file, const char *buf, size_t len); + + /* Sets the file read- or write-position to `fpos`. For example, if `file` has been opened in read mode and `fpos` is + * 5, then the next `read` operation will continue from the 5th byte of the file. Returns 0 on success and a non-zero + * error code on failue. + * `file`: file handle + * `fpos`: file position */ + int (*seek)(MO_File *file, size_t fpos); + + void *mo_data; //reserved for MO internal usage +} MO_FilesystemAdapter; /* * Platform specific implementation. Currently supported: @@ -87,8 +135,31 @@ class FilesystemAdapter { * * Returns null if platform is not supported or Filesystem is disabled */ -std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt config); -} //end namespace MicroOcpp +#if MO_USE_FILEAPI != MO_CUSTOM_FS + +typedef enum { + MO_FS_OPT_DISABLE, + MO_FS_OPT_USE, + MO_FS_OPT_USE_MOUNT, + MO_FS_OPT_USE_MOUNT_FORMAT_ON_FAIL +} MO_FilesystemOpt; + +typedef struct { + MO_FilesystemOpt opt; //FS access level and mounting strategy + + /* Path of the MO root folder, plus trailing '/'. MO will create the path for accessing the filesystem by appending + * a filename to the end of path_prefix. Can be empty if the filesystem implementation accepts the MO filenames as + * path directly. MO doesn't use sub-directories. */ + const char *path_prefix; +} MO_FilesystemConfig; +MO_FilesystemAdapter *mo_makeDefaultFilesystemAdapter(MO_FilesystemConfig config); +void mo_freeDefaultFilesystemAdapter(MO_FilesystemAdapter *filesystem); + +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/MicroOcpp/Core/FilesystemUtils.cpp b/src/MicroOcpp/Core/FilesystemUtils.cpp index de6a034d..eb3321d7 100644 --- a/src/MicroOcpp/Core/FilesystemUtils.cpp +++ b/src/MicroOcpp/Core/FilesystemUtils.cpp @@ -2,40 +2,59 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License +#include + #include #include -#include //FilesystemOpt #include using namespace MicroOcpp; -std::unique_ptr FilesystemUtils::loadJson(std::shared_ptr filesystem, const char *fn, const char *memoryTag) { - if (!filesystem || !fn || *fn == '\0') { - MO_DBG_ERR("Format error"); - return nullptr; +bool FilesystemUtils::printPath(MO_FilesystemAdapter *filesystem, char *path, size_t size, const char *fname, ...) { + + size_t written = 0; + int ret; + bool success = true; + + // write path_prefix + ret = snprintf(path, size, "%s", filesystem->path_prefix ? filesystem->path_prefix : ""); + if (ret < 0 || written + (size_t)ret >= size) { + MO_DBG_ERR("path buffer insufficient"); + return false; } + written += (size_t)ret; - if (strnlen(fn, MO_MAX_PATH_SIZE) >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("Fn too long: %.*s", MO_MAX_PATH_SIZE, fn); - return nullptr; + // write fname + va_list args; + va_start(args, fname); + ret = vsnprintf(path + written, size - written, fname, args); + va_end(args); + if (ret < 0 || written + (size_t)ret >= size) { + MO_DBG_ERR("path buffer insufficient, fname too long or fname error"); + return false; } - - size_t fsize = 0; - if (filesystem->stat(fn, &fsize) != 0) { - MO_DBG_DEBUG("File does not exist: %s", fn); - return nullptr; + written += (size_t)ret; + + return true; +} + +FilesystemUtils::LoadStatus FilesystemUtils::loadJson(MO_FilesystemAdapter *filesystem, const char *fname, JsonDoc& doc, const char *memoryTag) { + + char path [MO_MAX_PATH_SIZE]; + if (!printPath(filesystem, path, sizeof(path), "%s", fname)) { + MO_DBG_ERR("fname too long: %s", fname); + return FilesystemUtils::LoadStatus::ErrOther; } - if (fsize < 2) { - MO_DBG_ERR("File too small for JSON, collect %s", fn); - filesystem->remove(fn); - return nullptr; + size_t fsize = 0; + if (filesystem->stat(path, &fsize) != 0) { + return FilesystemUtils::LoadStatus::FileNotFound; } - auto file = filesystem->open(fn, "r"); + auto file = filesystem->open(path, "r"); if (!file) { - MO_DBG_ERR("Could not open file %s", fn); - return nullptr; + MO_DBG_ERR("Could not open file %s", fname); + return FilesystemUtils::LoadStatus::ErrFileCorruption; } size_t capacity_init = (3 * fsize) / 2; @@ -49,92 +68,196 @@ std::unique_ptr FilesystemUtils::loadJson(std::shared_ptr MO_MAX_JSON_CAPACITY) { capacity = MO_MAX_JSON_CAPACITY; } - - std::unique_ptr doc; + DeserializationError err = DeserializationError::NoMemory; - ArduinoJsonFileAdapter fileReader {file.get()}; + ArduinoJsonFileAdapter fileReader {filesystem, file}; while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) { - doc = makeJsonDoc(memoryTag, capacity); - err = deserializeJson(*doc, fileReader); + doc = std::move(initJsonDoc(memoryTag, capacity)); + if (doc.capacity() < capacity) { + MO_DBG_ERR("OOM"); + (void)filesystem->close(file); + return FilesystemUtils::LoadStatus::ErrOOM; + } + + err = deserializeJson(doc, fileReader); capacity *= 2; - file->seek(0); //rewind file to beginning + filesystem->seek(file, 0); //rewind file to beginning } + (void)filesystem->close(file); + if (err) { - MO_DBG_ERR("Error deserializing file %s: %s", fn, err.c_str()); + MO_DBG_ERR("Error deserializing file %s: %s", fname, err.c_str()); //skip this file - return nullptr; + return FilesystemUtils::LoadStatus::ErrFileCorruption; } MO_DBG_DEBUG("Loaded JSON file: %s", fn); - return doc; + return FilesystemUtils::LoadStatus::Success;; } -bool FilesystemUtils::storeJson(std::shared_ptr filesystem, const char *fn, const JsonDoc& doc) { - if (!filesystem || !fn || *fn == '\0') { - MO_DBG_ERR("Format error"); - return false; - } +FilesystemUtils::StoreStatus FilesystemUtils::storeJson(MO_FilesystemAdapter *filesystem, const char *fname, const JsonDoc& doc) { - if (strnlen(fn, MO_MAX_PATH_SIZE) >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("Fn too long: %.*s", MO_MAX_PATH_SIZE, fn); - return false; + char path [MO_MAX_PATH_SIZE]; + if (!printPath(filesystem, path, sizeof(path), "%s", fname)) { + MO_DBG_ERR("fname too long: %s", fname); + return FilesystemUtils::StoreStatus::ErrOther; } if (doc.isNull() || doc.overflowed()) { - MO_DBG_ERR("Invalid JSON %s", fn); - return false; + MO_DBG_ERR("Invalid JSON %s", fname); + return FilesystemUtils::StoreStatus::ErrOther; } - auto file = filesystem->open(fn, "w"); + auto file = filesystem->open(path, "w"); if (!file) { - MO_DBG_ERR("Could not open file %s", fn); - return false; + MO_DBG_ERR("Could not open file %s", fname); + return FilesystemUtils::StoreStatus::ErrFileWrite; } - ArduinoJsonFileAdapter fileWriter {file.get()}; + ArduinoJsonFileAdapter fileWriter (filesystem, file); - size_t written = serializeJson(doc, fileWriter); + size_t written; + if (MO_PLATFORM == MO_PLATFORM_UNIX) { + //when debugging on Linux, add line breaks and white spaces for better readibility + written = serializeJsonPretty(doc, fileWriter); + } else { + written = serializeJson(doc, fileWriter); + } if (written < 2) { - MO_DBG_ERR("Error writing file %s", fn); - size_t file_size = 0; - if (filesystem->stat(fn, &file_size) == 0) { - MO_DBG_DEBUG("Collect invalid file %s", fn); - filesystem->remove(fn); - } - return false; + MO_DBG_ERR("Error writing file %s", fname); + (void)filesystem->close(file); + return FilesystemUtils::StoreStatus::ErrFileWrite; + } + + bool ret = filesystem->close(file); + if (!ret) { + MO_DBG_ERR("Error writing file %s", fname); + return FilesystemUtils::StoreStatus::ErrFileWrite; } MO_DBG_DEBUG("Wrote JSON file: %s", fn); - return true; + + return FilesystemUtils::StoreStatus::Success; } -bool FilesystemUtils::remove_if(std::shared_ptr filesystem, std::function pred) { - auto ret = filesystem->ftw_root([filesystem, pred] (const char *fpath) { - if (pred(fpath)) { +namespace MicroOcpp { +namespace FilesystemUtils { - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "%s", fpath); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return -1; +struct RemoveByPrefixData { + MO_FilesystemAdapter *filesystem; + const char *fnamePrefix; +}; + +} //namespace FilesystemUtils +} //namespace MicroOcpp + +bool FilesystemUtils::removeByPrefix(MO_FilesystemAdapter *filesystem, const char *fnamePrefix) { + + RemoveByPrefixData data; + data.filesystem = filesystem; + data.fnamePrefix = fnamePrefix; + + auto ret = filesystem->ftw(filesystem->path_prefix, [] (const char *fname, void* user_data) -> int { + auto data = reinterpret_cast(user_data); + auto filesystem = data->filesystem; + auto fnamePrefix = data->fnamePrefix; + + //check if fname starts with fnamePrefix + for (size_t i = 0; fnamePrefix[i] != '\0'; i++) { + if (fname[i] != fnamePrefix[i]) { + //not a prefix of fname - don't do anything on this file + return 0; } + } - filesystem->remove(fn); - //no error handling - just skip failed file + char path [MO_MAX_PATH_SIZE]; + if (!printPath(filesystem, path, sizeof(path), "%s", fname)) { + MO_DBG_ERR("fname too long: %s", fname); + return -1; } + + (void)filesystem->remove(path); //no error handling - just skip failed file return 0; - }); + }, reinterpret_cast(&data)); + + return ret == 0; +} + +namespace MicroOcpp { +namespace FilesystemUtils { + +struct RingIndexData { + const char *fnamePrefix; + unsigned int MAX_INDEX; + unsigned int *indexBegin; + unsigned int *indexEnd; + + size_t fnamePrefixLen; + unsigned int indexPivot; +}; + +int updateRingIndex(const char *fname, void* user_data) { + FilesystemUtils::RingIndexData& data = *reinterpret_cast(user_data); + const char *fnamePrefix = data.fnamePrefix; + unsigned int MAX_INDEX = data.MAX_INDEX; + unsigned int& indexBegin = *data.indexBegin; + unsigned int& indexEnd = *data.indexEnd; + size_t fnamePrefixLen = data.fnamePrefixLen; + unsigned int& indexPivot = data.indexPivot; - if (ret != 0) { - MO_DBG_ERR("ftw_root: %i", ret); + if (!strncmp(fname, fnamePrefix, fnamePrefixLen)) { + unsigned int parsedIndex = 0; + for (size_t i = fnamePrefixLen; fname[i] >= '0' && fname[i] <= '9'; i++) { + parsedIndex *= 10; + parsedIndex += fname[i] - '0'; + } + + if (indexPivot == MAX_INDEX) { + indexPivot = parsedIndex; + indexBegin = parsedIndex; + indexEnd = (parsedIndex + 1) % MAX_INDEX; + MO_DBG_DEBUG("found %s%u.jsn - Internal range from %u to %u (exclusive)", fnamePrefix, parsedIndex, indexBegin, indexEnd); + return 0; + } + + if ((parsedIndex + MAX_INDEX - indexPivot) % MAX_INDEX < MAX_INDEX / 2) { + //parsedIndex is after pivot point + if ((parsedIndex + 1 + MAX_INDEX - indexPivot) % MAX_INDEX > (indexEnd + MAX_INDEX - indexPivot) % MAX_INDEX) { + indexEnd = (parsedIndex + 1) % MAX_INDEX; + } + } else if ((indexPivot + MAX_INDEX - parsedIndex) % MAX_INDEX < MAX_INDEX / 2) { + //parsedIndex is before pivot point + if ((indexPivot + MAX_INDEX - parsedIndex) % MAX_INDEX > (indexPivot + MAX_INDEX - indexBegin) % MAX_INDEX) { + indexBegin = parsedIndex; + } + } + + MO_DBG_DEBUG("found %s%u.jsn - Internal range from %u to %u (exclusive)", fnamePrefix, parsedIndex, indexBegin, indexEnd); } + return 0; +} + +} //namespace FilesystemUtils +} //namespace MicroOcpp + +bool FilesystemUtils::loadRingIndex(MO_FilesystemAdapter *filesystem, const char *fnamePrefix, unsigned int MAX_INDEX, unsigned int *indexBegin, unsigned int *indexEnd) { + + FilesystemUtils::RingIndexData data; + data.fnamePrefix = fnamePrefix; + data.MAX_INDEX = MAX_INDEX; + data.indexBegin = indexBegin; + data.indexEnd = indexEnd; + data.fnamePrefixLen = strlen(fnamePrefix); + data.indexPivot = MAX_INDEX; + + auto ret = filesystem->ftw(filesystem->path_prefix, updateRingIndex, reinterpret_cast(&data)); return ret == 0; } diff --git a/src/MicroOcpp/Core/FilesystemUtils.h b/src/MicroOcpp/Core/FilesystemUtils.h index 31a36a73..acfab39d 100644 --- a/src/MicroOcpp/Core/FilesystemUtils.h +++ b/src/MicroOcpp/Core/FilesystemUtils.h @@ -8,42 +8,80 @@ #include #include #include -#include namespace MicroOcpp { class ArduinoJsonFileAdapter { private: - FileAdapter *file; + MO_FilesystemAdapter *filesystem = nullptr; + MO_File *file = nullptr; public: - ArduinoJsonFileAdapter(FileAdapter *file) : file(file) { } + ArduinoJsonFileAdapter(MO_FilesystemAdapter *filesystem, MO_File *file) : filesystem(filesystem), file(file) { } size_t readBytes(char *buf, size_t len) { - return file->read(buf, len); + return filesystem->read(file, buf, len); } int read() { - return file->read(); + return filesystem->getc(file); } size_t write(const uint8_t *buf, size_t len) { - return file->write((const char*) buf, len); + return filesystem->write(file, (const char*) buf, len); } size_t write(uint8_t c) { - return file->write((const char*) &c, 1); + return filesystem->write(file, (const char*) &c, 1); } }; namespace FilesystemUtils { -std::unique_ptr loadJson(std::shared_ptr filesystem, const char *fn, const char *memoryTag = nullptr); -bool storeJson(std::shared_ptr filesystem, const char *fn, const JsonDoc& doc); +/* Determines path of a file given the filename and the path_prefix defined in the filesystem adapter. + * `path`: array which should have at least MO_MAX_PATH_SIZE bytes (including the terminating zero) + * `size`: number of bytes of `path` */ +bool printPath(MO_FilesystemAdapter *filesystem, char *path, size_t size, const char *fname, ...); -bool remove_if(std::shared_ptr filesystem, std::function pred); +enum class LoadStatus { + Success, //file operation successful + FileNotFound, + ErrOOM, + ErrFileCorruption, + ErrOther, //file operation is not possible with the given parameters +}; +LoadStatus loadJson(MO_FilesystemAdapter *filesystem, const char *fname, JsonDoc& doc, const char *memoryTag); + +enum class StoreStatus { + Success, //file operation successful + ErrFileWrite, + ErrJsonCorruption, + ErrOther, //file operation is not possible with the given parameters +}; +StoreStatus storeJson(MO_FilesystemAdapter *filesystem, const char *fname, const JsonDoc& doc); + +/* + * Removes files in the MO folder whose file names start with `fnamePrefix`. + */ +bool removeByPrefix(MO_FilesystemAdapter *filesystem, const char *fnamePrefix); -} +/* + * Loads index for data queue on flash which is organized as a ring buffer. For example, Security + * events are stored on flash like this: + * sloc-4.jsn + * sloc-5.jsn + * sloc-6.jsn + * Then the `indexBegin` is 4 and `indexEnd` is 7 (one after last element). The size of the queue is + * `indexEnd - indexBegin = 7 - 4 = 3`. If the queue is empty, then `indexEnd - indexBegin = 0`. + * Parameters (and example values): + * `filesystem`: filesystem adapter + * `fnamePrefix`: common prefix of the filenames (e.g. "sloc-") + * `MAX_INDEX`: range end of the index (e.g. if the index ranges from 0 - 999, then `MAX_INDEX = 1000`) + * `*indexBegin`: output parameter for the first file in the queue (e.g. 4) + * `*indexEnd`: output parameter for the end of the queue (e.g. 7) + */ +bool loadRingIndex(MO_FilesystemAdapter *filesystem, const char *fnamePrefix, unsigned int MAX_INDEX, unsigned int *indexBegin, unsigned int *indexEnd); -} +} //namespace FilesystemUtils +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Core/Ftp.h b/src/MicroOcpp/Core/Ftp.h index 053f9e01..31243e14 100644 --- a/src/MicroOcpp/Core/Ftp.h +++ b/src/MicroOcpp/Core/Ftp.h @@ -17,43 +17,43 @@ typedef enum { MO_FtpCloseReason_Failure } MO_FtpCloseReason; -typedef struct ocpp_ftp_download { +typedef struct mo_ftp_download { void *user_data; //set this at your choice. MO passes it back to the functions below void (*loop)(void *user_data); void (*is_active)(void *user_data); -} ocpp_ftp_download; +} mo_ftp_download; -typedef struct ocpp_ftp_upload { +typedef struct mo_ftp_upload { void *user_data; //set this at your choice. MO passes it back to the functions below void (*loop)(void *user_data); void (*is_active)(void *user_data); -} ocpp_ftp_upload; +} mo_ftp_upload; -typedef struct ocpp_ftp_client { +typedef struct mo_ftp_client { void *user_data; //set this at your choice. MO passes it back to the functions below void (*loop)(void *user_data); - ocpp_ftp_download* (*get_file)(void *user_data, + mo_ftp_download* (*get_file)(void *user_data, const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename size_t (*file_writer)(void *mo_data, unsigned char *data, size_t len), void (*on_close)(void *mo_data, MO_FtpCloseReason reason), void *mo_data, const char *ca_cert); // nullptr to disable cert check; will be ignored for non-TLS connections - void (*get_file_free)(void *user_data, ocpp_ftp_download*); + void (*get_file_free)(void *user_data, mo_ftp_download*); - ocpp_ftp_upload* (*post_file)(void *user_data, + mo_ftp_upload* (*post_file)(void *user_data, const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename size_t (*file_reader)(void *mo_data, unsigned char *buf, size_t bufsize), void (*on_close)(void *mo_data, MO_FtpCloseReason reason), void *mo_data, const char *ca_cert); // nullptr to disable cert check; will be ignored for non-TLS connections - void (*post_file_free)(void *user_data, ocpp_ftp_upload*); -} ocpp_ftp_client; + void (*post_file_free)(void *user_data, mo_ftp_upload*); +} mo_ftp_client; #ifdef __cplusplus } //extern "C" @@ -94,6 +94,6 @@ class FtpClient { const char *ca_cert = nullptr) = 0; // nullptr to disable cert check; will be ignored for non-TLS connections }; -} // namespace MicroOcpp +} //namespace MicroOcpp #endif //def __cplusplus #endif diff --git a/src/MicroOcpp/Core/FtpMbedTLS.cpp b/src/MicroOcpp/Core/FtpMbedTLS.cpp index b40015b9..e5ae0e36 100644 --- a/src/MicroOcpp/Core/FtpMbedTLS.cpp +++ b/src/MicroOcpp/Core/FtpMbedTLS.cpp @@ -131,8 +131,8 @@ class FtpClientMbedTLS : public FtpClient, public MemoryManaged { const char *ca_cert = nullptr) override; // nullptr to disable cert check; will be ignored for non-TLS connections }; -std::unique_ptr makeFtpClientMbedTLS(bool tls_only, const char *client_cert, const char *client_key) { - return std::unique_ptr(new FtpClientMbedTLS(tls_only, client_cert, client_key)); +std::unique_ptr makeFtpClientMbedTLS(MO_FTPConfig config) { + return std::unique_ptr(new FtpClientMbedTLS(config.tls_only, config.client_cert, config.client_key)); } void mo_mbedtls_log(void *user, int level, const char *file, int line, const char *str) { @@ -147,16 +147,22 @@ void mo_mbedtls_log(void *user, int level, const char *file, int line, const cha * * To change the debug level, use the build flag MO_DBG_LEVEL_MBEDTLS accordingly */ - const char *lstr = ""; - if (level <= 1) { - lstr = "ERROR"; - } else if (level <= 3) { - lstr = "debug"; - } else { - lstr = "verbose"; - } - MO_CONSOLE_PRINTF("[MO] %s (%s:%i): %s\n", lstr, file, line, str); + int mo_level = MO_DL_VERBOSE; + switch (level) { + case 1: + mo_level = MO_DL_ERROR; + break; + case 2: + case 3: + mo_level = MO_DL_DEBUG; + break; + case 4: + mo_level = MO_DL_VERBOSE; + break; + } + + MicroOcpp::debug(mo_level, file, line, "%s", str); } /* diff --git a/src/MicroOcpp/Core/FtpMbedTLS.h b/src/MicroOcpp/Core/FtpMbedTLS.h index 799b3905..6dfc6512 100644 --- a/src/MicroOcpp/Core/FtpMbedTLS.h +++ b/src/MicroOcpp/Core/FtpMbedTLS.h @@ -29,9 +29,19 @@ #include +extern "C" { + +typedef struct { + bool tls_only; + const char *client_cert; //zero-copy. client_cert must outlive MO lifecycle. Can be NULL + const char *client_key; //zero-copy. client_key must outlive MO lifecycle. Can be NULL +} MO_FTPConfig; + +} //extern "C" + namespace MicroOcpp { -std::unique_ptr makeFtpClientMbedTLS(bool tls_only = false, const char *client_cert = nullptr, const char *client_key = nullptr); +std::unique_ptr makeFtpClientMbedTLS(MO_FTPConfig config); } //namespace MicroOcpp diff --git a/src/MicroOcpp/Core/Memory.cpp b/src/MicroOcpp/Core/Memory.cpp index a352e9ac..abd95bad 100644 --- a/src/MicroOcpp/Core/Memory.cpp +++ b/src/MicroOcpp/Core/Memory.cpp @@ -199,7 +199,7 @@ void mo_mem_set_tag(void *ptr, const char *tag) { void mo_mem_print_stats() { - MO_CONSOLE_PRINTF("\n *** Heap usage statistics ***\n"); + MO_DBG_INFO("\n *** Heap usage statistics ***\n"); size_t size = 0; @@ -209,7 +209,7 @@ void mo_mem_print_stats() { size += heapEntry.second.size; #if MO_DBG_LEVEL >= MO_DL_VERBOSE { - MO_CONSOLE_PRINTF("@%p - %zu B (%s)\n", heapEntry.first, heapEntry.second.size, heapEntry.second.tag.c_str()); + MO_DBG_INFO("@%p - %zu B (%s)\n", heapEntry.first, heapEntry.second.size, heapEntry.second.tag.c_str()); } #endif @@ -235,7 +235,7 @@ void mo_mem_print_stats() { size_control += tag.second; #if MO_DBG_LEVEL >= MO_DL_VERBOSE { - MO_CONSOLE_PRINTF("%s - %zu B\n", tag.first.c_str(), tag.second); + MO_DBG_INFO("%s - %zu B\n", tag.first.c_str(), tag.second); } #endif } @@ -243,13 +243,13 @@ void mo_mem_print_stats() { size_t size_control2 = 0; for (const auto& tag : memTags) { size_control2 += tag.second.current_size; - MO_CONSOLE_PRINTF("%s - %zu B (max. %zu B)\n", tag.first.c_str(), tag.second.current_size, tag.second.max_size); + MO_DBG_INFO("%s - %zu B (max. %zu B)\n", tag.first.c_str(), tag.second.current_size, tag.second.max_size); } - MO_CONSOLE_PRINTF(" *** Summary ***\nBlocks: %zu\nTags: %zu\nCurrent usage: %zu B\nMaximum usage: %zu B\n", memBlocks.size(), memTags.size(), memTotal, memTotalMax); + MO_DBG_INFO(" *** Summary ***\nBlocks: %zu\nTags: %zu\nCurrent usage: %zu B\nMaximum usage: %zu B\n", memBlocks.size(), memTags.size(), memTotal, memTotalMax); #if MO_DBG_LEVEL >= MO_DL_DEBUG { - MO_CONSOLE_PRINTF(" *** Debug information ***\nTotal blocks (control value 1): %zu B\nTags (control value): %zu\nTotal tagged (control value 2): %zu B\nTotal tagged (control value 3): %zu B\nUntagged: %zu\nTotal untagged: %zu B\n", size, tags.size(), size_control, size_control2, untagged, untagged_size); + MO_DBG_INFO(" *** Debug information ***\nTotal blocks (control value 1): %zu B\nTags (control value): %zu\nTotal tagged (control value 2): %zu B\nTotal tagged (control value 3): %zu B\nUntagged: %zu\nTotal untagged: %zu B\n", size, tags.size(), size_control, size_control2, untagged, untagged_size); } #endif } @@ -319,7 +319,12 @@ JsonDoc initJsonDoc(const char *tag, size_t capacity) { std::unique_ptr makeJsonDoc(const char *tag, size_t capacity) { #if MO_OVERRIDE_ALLOCATION - return std::unique_ptr(new JsonDoc(capacity, ArduinoJsonAllocator(tag))); + auto doc = std::unique_ptr(new JsonDoc(capacity, ArduinoJsonAllocator(tag))); + if (!doc || doc->capacity() < capacity) { + MO_DBG_ERR("OOM"); + return nullptr; + } + return doc; #else return std::unique_ptr(new JsonDoc(capacity)); #endif diff --git a/src/MicroOcpp/Core/MessageService.cpp b/src/MicroOcpp/Core/MessageService.cpp new file mode 100644 index 00000000..e3a69e14 --- /dev/null +++ b/src/MicroOcpp/Core/MessageService.cpp @@ -0,0 +1,490 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace MicroOcpp { +size_t removePayload(const char *src, size_t src_size, char *dst, size_t dst_size); +} + +using namespace MicroOcpp; + +MessageService::MessageService(Context& context) : + MemoryManaged("MessageService"), + context(context), + operationRegistry(makeVector(getMemoryTag())), + operationRegistry2(makeVector(getMemoryTag())), + receiveRequestListeners(makeVector(getMemoryTag())), + sendConfListeners(makeVector(getMemoryTag())) { + + addSendQueue(&defaultSendQueue); +} + +bool MessageService::setup() { + connection = context.getConnection(); + if (!connection) { + MO_DBG_ERR("need to set Connection before MessageService setup"); + return false; + } + return true; +} + +void MessageService::loop() { + + /* + * Check if front request timed out + */ + if (sendReqFront && sendReqFront->isTimeoutExceeded()) { + MO_DBG_INFO("operation timeout: %s", sendReqFront->getOperationType()); + sendReqFront->executeTimeout(); + sendReqFront.reset(); + } + + if (recvReqFront && recvReqFront->isTimeoutExceeded()) { + MO_DBG_INFO("operation timeout: %s", recvReqFront->getOperationType()); + recvReqFront->executeTimeout(); + recvReqFront.reset(); + } + + defaultSendQueue.loop(); + + if (!connection->isConnected()) { + return; + } + + /** + * Send and dequeue a pending confirmation message, if existing + * + * If a message has been sent, terminate this loop() function. + */ + + if (!recvReqFront) { + recvReqFront = recvQueue.fetchFrontRequest(); + } + + if (recvReqFront) { + + auto response = initJsonDoc(getMemoryTag()); + auto ret = recvReqFront->createResponse(response); + + if (ret == Request::CreateResponseResult::Success) { + auto out = makeString(getMemoryTag()); + serializeJson(response, out); + + bool success = connection->sendTXT(out.c_str(), out.length()); + + if (success) { + MO_DBG_DEBUG("Send %s", out.c_str()); + recvReqFront.reset(); + } + + return; + } //else: There will be another attempt to send this conf message in a future loop call + } + + /** + * Send pending req message + */ + + if (!sendReqFront) { + + unsigned int minOpNr = RequestQueue::NoOperation; + size_t index = MO_NUM_REQUEST_QUEUES; + for (size_t i = 0; i < MO_NUM_REQUEST_QUEUES && sendQueues[i]; i++) { + auto opNr = sendQueues[i]->getFrontRequestOpNr(); + if (opNr < minOpNr) { + minOpNr = opNr; + index = i; + } + } + + if (index < MO_NUM_REQUEST_QUEUES) { + sendReqFront = sendQueues[index]->fetchFrontRequest(); + } + } + + if (sendReqFront && !sendReqFront->isRequestSent()) { + + auto request = initJsonDoc(getMemoryTag()); + auto ret = sendReqFront->createRequest(request); + + if (ret == Request::CreateRequestResult::Success) { + + //send request + auto out = makeString(getMemoryTag()); + serializeJson(request, out); + + bool success = connection->sendTXT(out.c_str(), out.length()); + + if (success) { + MO_DBG_DEBUG("Send %s", out.c_str()); + sendReqFront->setRequestSent(); //mask as sent and wait for response / timeout + } + + return; + } + } +} + +bool MessageService::sendRequest(std::unique_ptr op){ + return defaultSendQueue.pushRequestBack(std::move(op)); +} + +bool MessageService::sendRequestPreBoot(std::unique_ptr op){ + if (!preBootSendQueue) { + MO_DBG_ERR("did not set PreBoot queue"); + return false; + } + return preBootSendQueue->pushRequestBack(std::move(op)); +} + +void MessageService::addSendQueue(RequestQueue* sendQueue) { + for (size_t i = 0; i < MO_NUM_REQUEST_QUEUES; i++) { + if (!sendQueues[i]) { + sendQueues[i] = sendQueue; + return; + } + } + MO_DBG_ERR("exceeded sendQueue capacity"); +} + +void MessageService::setPreBootSendQueue(VolatileRequestQueue *preBootQueue) { + this->preBootSendQueue = preBootQueue; + addSendQueue(preBootQueue); +} + +unsigned int MessageService::getNextOpNr() { + return nextOpNr++; +} + +namespace MicroOcpp { + +template +T *getEntry(Vector& listeners, const char *operationType) { + for (size_t i = 0; i < listeners.size(); i++) { + if (!strcmp(listeners[i].operationType, operationType)) { + return &listeners[i]; + } + } + return nullptr; +} + +template +void removeEntry(Vector& listeners, const char *operationType) { + for (auto it = listeners.begin(); it != listeners.end();) { + if (!strcmp(operationType, it->operationType)) { + it = listeners.erase(it); + } else { + it++; + } + } +} + +template +bool appendEntry(Vector& listeners, const T& el) { + bool capacity = listeners.size() + 1; + listeners.reserve(capacity); + if (listeners.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return false; + } + + listeners.push_back(el); + return true; +} + +} //namespace MicroOcpp + +bool MessageService::registerOperation(const char *operationType, Operation* (*createOperationCb)(Context& context)) { + + bool exists = false; + + exists |= (getEntry(operationRegistry, operationType) != nullptr); + exists |= (getEntry(operationRegistry2, operationType) != nullptr); + + if (exists) { + MO_DBG_DEBUG("operation creator %s already exists - do not replace", operationType); + return true; + } + + OperationCreator entry; + entry.operationType = operationType; + entry.create = createOperationCb; + + MO_DBG_DEBUG("registered operation %s", operationType); + return appendEntry(operationRegistry, entry); +} + +bool MessageService::registerOperation(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), void *userData) { + + bool exists = false; + + exists |= (getEntry(operationRegistry, operationType) != nullptr); + exists |= (getEntry(operationRegistry2, operationType) != nullptr); + + if (exists) { + MO_DBG_DEBUG("operation creator %s already exists - do not replace", operationType); + return true; + } + + CustomOperationCreator entry; + entry.operationType = operationType; + entry.userData = userData; + entry.onRequest = onRequest; + entry.writeResponse = writeResponse; + + MO_DBG_DEBUG("registered operation %s", operationType); + return appendEntry(operationRegistry2, entry); +} + +bool MessageService::setOnReceiveRequest(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, void *userData), void *userData) { + bool exists = (getEntry(receiveRequestListeners, operationType)) != nullptr; + if (exists) { + MO_DBG_DEBUG("operation onRequest %s already exists - do not replace", operationType); + return true; + } + MO_DBG_DEBUG("set onRequest %s", operationType); + OperationListener listener; + listener.operationType = operationType; + listener.onEvent = onRequest; + listener.userData = userData; + return appendEntry(receiveRequestListeners, listener); +} + +bool MessageService::setOnSendConf(const char *operationType, void (*onConfirmation)(const char *operationType, const char *payloadJson, void *userData), void *userData) { + bool exists = (getEntry(sendConfListeners, operationType)) != nullptr; + if (exists) { + MO_DBG_DEBUG("operation onConfirmation %s already exists - do not replace", operationType); + return true; + } + MO_DBG_DEBUG("set onConfirmation %s", operationType); + OperationListener listener; + listener.operationType = operationType; + listener.onEvent = onConfirmation; + listener.userData = userData; + return appendEntry(sendConfListeners, listener); +} + +std::unique_ptr MessageService::createRequest(const char *operationType) { + + Operation *operation = nullptr; + if (auto entry = getEntry(operationRegistry, operationType)) { + operation = entry->create(context); + if (!operation) { + MO_DBG_ERR("create operation failure"); + return nullptr; + } + } + + if (!operation) { + if (auto entry = getEntry(operationRegistry2, operationType)) { + auto customOperation = new CustomOperation(); + if (!customOperation) { + MO_DBG_ERR("OOM"); + return nullptr; + } + + if (!customOperation->setupCsmsInitiated( + entry->operationType, + entry->onRequest, + entry->writeResponse, + entry->userData)) { + MO_DBG_ERR("create operation failure"); + delete customOperation; + return nullptr; + } + + operation = static_cast(customOperation); + } + } + + if (!operation) { + auto notImplemented = new NotImplemented(); + if (!notImplemented) { + MO_DBG_ERR("OOM"); + return nullptr; + } + auto request = makeRequest(context, notImplemented); + if (!request) { + MO_DBG_ERR("OOM"); + return nullptr; + } + return request; + } + + auto request = makeRequest(context, operation); + if (!request) { + MO_DBG_ERR("OOM"); + return nullptr; + } + + if (auto entry = getEntry(receiveRequestListeners, operationType)) { + request->setOnReceiveRequest(entry->onEvent, entry->userData); + } + + if (auto entry = getEntry(sendConfListeners, operationType)) { + request->setOnSendConf(entry->onEvent, entry->userData); + } + + return request; +} + +bool MessageService::receiveMessage(const char* payload, size_t length) { + + MO_DBG_DEBUG("Recv %.*s", (int)length, payload); + + size_t capacity_init = (3 * length) / 2; + + //capacity = ceil capacity_init to the next power of two; should be at least 128 + + size_t capacity = 128; + while (capacity < capacity_init && capacity < MO_MAX_JSON_CAPACITY) { + capacity *= 2; + } + if (capacity > MO_MAX_JSON_CAPACITY) { + capacity = MO_MAX_JSON_CAPACITY; + } + + auto doc = initJsonDoc(getMemoryTag()); + DeserializationError err = DeserializationError::NoMemory; + + while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) { + + doc = initJsonDoc(getMemoryTag(), capacity); + err = deserializeJson(doc, payload, length); + + capacity *= 2; + } + + bool success = false; + + switch (err.code()) { + case DeserializationError::Ok: { + int messageTypeId = doc[0] | -1; + + if (messageTypeId == MESSAGE_TYPE_CALL) { + receiveRequest(doc.as()); + success = true; + } else if (messageTypeId == MESSAGE_TYPE_CALLRESULT || + messageTypeId == MESSAGE_TYPE_CALLERROR) { + receiveResponse(doc.as()); + success = true; + } else { + MO_DBG_WARN("Invalid OCPP message! (though JSON has successfully been deserialized)"); + } + break; + } + case DeserializationError::InvalidInput: + MO_DBG_WARN("Invalid input! Not a JSON"); + break; + case DeserializationError::NoMemory: { + MO_DBG_WARN("incoming operation exceeds buffer capacity. Input length = %zu, max capacity = %d", length, MO_MAX_JSON_CAPACITY); + + /* + * If websocket input is of message type MESSAGE_TYPE_CALL, send back a message of type MESSAGE_TYPE_CALLERROR. + * Then the communication counterpart knows that this operation failed. + * If the input type is MESSAGE_TYPE_CALLRESULT, then abort the operation to avoid getting stalled. + */ + + doc = initJsonDoc(getMemoryTag(), 200); + char onlyRpcHeader[200]; + size_t onlyRpcHeader_len = removePayload(payload, length, onlyRpcHeader, sizeof(onlyRpcHeader)); + DeserializationError err2 = deserializeJson(doc, onlyRpcHeader, onlyRpcHeader_len); + if (err2.code() == DeserializationError::Ok) { + int messageTypeId = doc[0] | -1; + if (messageTypeId == MESSAGE_TYPE_CALL) { + success = true; + auto op = makeRequest(context, new MsgBufferExceeded(MO_MAX_JSON_CAPACITY, length)); + receiveRequest(doc.as(), std::move(op)); + } else if (messageTypeId == MESSAGE_TYPE_CALLRESULT || + messageTypeId == MESSAGE_TYPE_CALLERROR) { + success = true; + MO_DBG_WARN("crop incoming response"); + receiveResponse(doc.as()); + } + } + break; + } + default: + MO_DBG_WARN("Deserialization failed: %s", err.c_str()); + break; + } + + return success; +} + +/** + * call conf() on each element of the queue. Start with first element. On successful message delivery, + * delete the element from the list. Try all the pending OCPP Operations until the right one is found. + * + * This function could result in improper behavior in Charging Stations, because messages are not + * guaranteed to be received and therefore processed in the right order. + */ +void MessageService::receiveResponse(JsonArray json) { + + if (!sendReqFront || !sendReqFront->receiveResponse(json)) { + MO_DBG_WARN("Received response doesn't match pending operation"); + } + + sendReqFront.reset(); +} + +void MessageService::receiveRequest(JsonArray json) { + if (json.size() <= 2 || !json[2].is()) { + MO_DBG_ERR("malformatted request"); + return; + } + auto request = createRequest(json[2]); + if (request == nullptr) { + MO_DBG_WARN("OOM"); + return; + } + receiveRequest(json, std::move(request)); +} + +void MessageService::receiveRequest(JsonArray json, std::unique_ptr request) { + request->receiveRequest(json); //execute the operation + recvQueue.pushRequestBack(std::move(request)); //enqueue so loop() plans conf sending +} + +/* + * Tries to recover the Ocpp-Operation header from a broken message. + * + * Example input: + * [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {"key":"now the message breaks... + * + * The Json library returns an error code when trying to deserialize that broken message. This + * function searches for the first occurence of the character '{' and writes "}]" after it. + * + * Example output: + * [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {}] + * + */ +size_t MicroOcpp::removePayload(const char *src, size_t src_size, char *dst, size_t dst_size) { + size_t res_len = 0; + for (size_t i = 0; i < src_size && i < dst_size-3; i++) { + if (src[i] == '\0'){ + //no payload found within specified range. Cancel execution + break; + } + dst[i] = src[i]; + if (src[i] == '{') { + dst[i+1] = '}'; + dst[i+2] = ']'; + res_len = i+3; + break; + } + } + dst[res_len] = '\0'; + res_len++; + return res_len; +} diff --git a/src/MicroOcpp/Core/MessageService.h b/src/MicroOcpp/Core/MessageService.h new file mode 100644 index 00000000..c4ec4c2e --- /dev/null +++ b/src/MicroOcpp/Core/MessageService.h @@ -0,0 +1,101 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_MESSAGESERVICE_H +#define MO_MESSAGESERVICE_H + +#include +#include +#include + +#include + +#ifndef MO_REQUEST_CACHE_MAXSIZE +#define MO_REQUEST_CACHE_MAXSIZE 10 +#endif + +#ifndef MO_NUM_REQUEST_QUEUES +#define MO_NUM_REQUEST_QUEUES 10 +#endif + +namespace MicroOcpp { + +class Context; +class Operation; + +struct OperationCreator { + const char *operationType = nullptr; + Operation* (*create)(Context& context) = nullptr; +}; + +struct CustomOperationCreator { + const char *operationType = nullptr; + void *userData = nullptr; + void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData) = nullptr; + int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData) = nullptr; +}; + +struct OperationListener { + const char *operationType = nullptr; + void *userData = nullptr; + void (*onEvent)(const char *operationType, const char *payloadJson, void *userData) = nullptr; +}; + +class Connection; + +class MessageService : public MemoryManaged { +private: + Context& context; + Connection *connection = nullptr; + + RequestQueue* sendQueues [MO_NUM_REQUEST_QUEUES] {nullptr}; + VolatileRequestQueue defaultSendQueue; + VolatileRequestQueue *preBootSendQueue = nullptr; + std::unique_ptr sendReqFront; + + VolatileRequestQueue recvQueue; + std::unique_ptr recvReqFront; + + Vector operationRegistry; + Vector operationRegistry2; + Vector receiveRequestListeners; + Vector sendConfListeners; + + void receiveRequest(JsonArray json); + void receiveRequest(JsonArray json, std::unique_ptr op); + void receiveResponse(JsonArray json); + std::unique_ptr createRequest(const char *operationType); + + unsigned long sockTrackLastConnected = 0; + + unsigned int nextOpNr = 10; //Nr 0 - 9 reservered for internal purposes +public: + MessageService(Context& context); + + bool setup(); + + void loop(); //polls all reqQueues and decides which request to send (if any) + + // send a Request to the OCPP server + bool sendRequest(std::unique_ptr request); //send an OCPP operation request to the server; adds request to default queue + bool sendRequestPreBoot(std::unique_ptr request); //send an OCPP operation request to the server; adds request to preBootQueue + + // handle Requests from the OCPP Server + bool registerOperation(const char *operationType, Operation* (*createOperationCb)(Context& context)); + bool registerOperation(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), void *userData = nullptr); + bool setOnReceiveRequest(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, void *userData), void *userData = nullptr); + bool setOnSendConf(const char *operationType, void (*onConfirmation)(const char *operationType, const char *payloadJson, void *userData), void *userData = nullptr); + + // process message from server ("message" = serialized Request or Confirmation) + bool receiveMessage(const char* payload, size_t length); + + // Outgoing Requests are handled in separate queues. + void addSendQueue(RequestQueue* sendQueue); + void setPreBootSendQueue(VolatileRequestQueue *preBootQueue); + + unsigned int getNextOpNr(); +}; + +} //namespace MicroOcpp +#endif diff --git a/src/MicroOcpp/Core/OcppError.h b/src/MicroOcpp/Core/OcppError.h index 826c3215..06ec6a9a 100644 --- a/src/MicroOcpp/Core/OcppError.h +++ b/src/MicroOcpp/Core/OcppError.h @@ -40,5 +40,5 @@ class MsgBufferExceeded : public Operation, public MemoryManaged { } }; -} //end namespace MicroOcpp +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Core/Operation.h b/src/MicroOcpp/Core/Operation.h index 9c1756a1..7ffbb7a3 100644 --- a/src/MicroOcpp/Core/Operation.h +++ b/src/MicroOcpp/Core/Operation.h @@ -47,10 +47,7 @@ class Operation { virtual void processConf(JsonObject payload); - /* - * returns if the operation must be aborted - */ - virtual bool processErr(const char *code, const char *description, JsonObject details) { return true;} + virtual void processErr(const char *code, const char *description, JsonObject details) {} /** * Processes the request in the JSON document. @@ -68,5 +65,5 @@ class Operation { virtual std::unique_ptr getErrorDetails() {return createEmptyDocument();} }; -} //end namespace MicroOcpp +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Core/OperationRegistry.cpp b/src/MicroOcpp/Core/OperationRegistry.cpp deleted file mode 100644 index 0bd17d45..00000000 --- a/src/MicroOcpp/Core/OperationRegistry.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include -#include -#include -#include - -using namespace MicroOcpp; - -OperationRegistry::OperationRegistry() : registry(makeVector("OperationRegistry")) { - -} - -OperationCreator *OperationRegistry::findCreator(const char *operationType) { - for (auto it = registry.begin(); it != registry.end(); ++it) { - if (!strcmp(it->operationType, operationType)) { - return &*it; - } - } - return nullptr; -} - -void OperationRegistry::registerOperation(const char *operationType, std::function creator) { - registry.erase(std::remove_if(registry.begin(), registry.end(), - [operationType] (const OperationCreator& el) { - return !strcmp(operationType, el.operationType); - }), - registry.end()); - - OperationCreator entry; - entry.operationType = operationType; - entry.creator = creator; - - registry.push_back(entry); - - MO_DBG_DEBUG("registered operation %s", operationType); -} - -void OperationRegistry::setOnRequest(const char *operationType, OnReceiveReqListener onRequest) { - if (auto entry = findCreator(operationType)) { - entry->onRequest = onRequest; - } else { - MO_DBG_ERR("%s not registered", operationType); - } -} - -void OperationRegistry::setOnResponse(const char *operationType, OnSendConfListener onResponse) { - if (auto entry = findCreator(operationType)) { - entry->onResponse = onResponse; - } else { - MO_DBG_ERR("%s not registered", operationType); - } -} - -std::unique_ptr OperationRegistry::deserializeOperation(const char *operationType) { - - if (auto entry = findCreator(operationType)) { - auto payload = entry->creator(); - if (payload) { - auto result = std::unique_ptr(new Request( - std::unique_ptr(payload))); - result->setOnReceiveReqListener(entry->onRequest); - result->setOnSendConfListener(entry->onResponse); - return result; - } - } - - return std::unique_ptr(new Request( - std::unique_ptr(new NotImplemented()))); -} - -void OperationRegistry::debugPrint() { - for (auto& creator : registry) { - MO_CONSOLE_PRINTF("[OCPP] > %s\n", creator.operationType); - } -} diff --git a/src/MicroOcpp/Core/OperationRegistry.h b/src/MicroOcpp/Core/OperationRegistry.h deleted file mode 100644 index 0d0d44b5..00000000 --- a/src/MicroOcpp/Core/OperationRegistry.h +++ /dev/null @@ -1,44 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_OPERATIONREGISTRY_H -#define MO_OPERATIONREGISTRY_H - -#include -#include -#include -#include - -namespace MicroOcpp { - -class Operation; -class Request; - -struct OperationCreator { - const char *operationType {nullptr}; - std::function creator {nullptr}; - OnReceiveReqListener onRequest {nullptr}; - OnSendConfListener onResponse {nullptr}; -}; - -class OperationRegistry { -private: - Vector registry; - OperationCreator *findCreator(const char *operationType); - -public: - OperationRegistry(); - - void registerOperation(const char *operationType, std::function creator); - void setOnRequest(const char *operationType, OnReceiveReqListener onRequest); - void setOnResponse(const char *operationType, OnSendConfListener onResponse); - - std::unique_ptr deserializeOperation(const char *operationType); - - void debugPrint(); -}; - -} - -#endif diff --git a/src/MicroOcpp/Core/PersistencyUtils.cpp b/src/MicroOcpp/Core/PersistencyUtils.cpp new file mode 100644 index 00000000..b1f31883 --- /dev/null +++ b/src/MicroOcpp/Core/PersistencyUtils.cpp @@ -0,0 +1,152 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#include + +#include +#include +#include + +using namespace MicroOcpp; + +bool PersistencyUtils::loadBootStats(MO_FilesystemAdapter *filesystem, BootStats& bstats) { + + JsonDoc json {0}; + bool success = false; + + auto ret = FilesystemUtils::loadJson(filesystem, "bootstats.jsn", json, "PersistencyUtils"); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: { + bool success = true; + + int bootNrIn = json["bootNr"] | 0; + if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits::max()) { + bstats.bootNr = (uint16_t)bootNrIn; + } else { + success = false; + } + + int attemptsIn = json["attempts"] | -1; + if (attemptsIn >= 0 && attemptsIn <= std::numeric_limits::max()) { + bstats.attempts = (uint16_t) attemptsIn; + } //else: attempts counter can be missing after upgrade from pre 2.0.0 version + + const char *microOcppVersionIn = json["MicroOcppVersion"] | (const char*)nullptr; + if (microOcppVersionIn) { + auto ret = snprintf(bstats.microOcppVersion, sizeof(bstats.microOcppVersion), "%s", microOcppVersionIn); + if (ret < 0 || (size_t)ret >= sizeof(bstats.microOcppVersion)) { + success = false; + } + } //else: version specifier can be missing after upgrade from pre 1.2.0 version + + if (!success) { + MO_DBG_ERR("bootstats invalid data"); + bstats = BootStats(); + } + break; } + case FilesystemUtils::LoadStatus::FileNotFound: + MO_DBG_DEBUG("populate bootstats"); + success = true; + break; + case FilesystemUtils::LoadStatus::ErrOOM: + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("error loading bootstats %i", (int)ret); + success = false; + break; + } + + return success; +} + +bool PersistencyUtils::storeBootStats(MO_FilesystemAdapter *filesystem, BootStats& bstats) { + + auto json = initJsonDoc("PersistencyUtils", JSON_OBJECT_SIZE(3)); + + json["bootNr"] = bstats.bootNr; + json["attempts"] = bstats.attempts; + json["MicroOcppVersion"] = (const char*)bstats.microOcppVersion; + + auto ret = FilesystemUtils::storeJson(filesystem, "bootstats.jsn", json); + bool success = (ret == FilesystemUtils::StoreStatus::Success); + + if (!success) { + MO_DBG_ERR("error storing bootstats %i", (int)ret); + } + + return success; +} + +bool PersistencyUtils::migrate(MO_FilesystemAdapter *filesystem) { + + BootStats bstats; + if (!loadBootStats(filesystem, bstats)) { + return false; + } + + bool success = true; + + if (strcmp(bstats.microOcppVersion, MO_VERSION)) { + MO_DBG_INFO("migrate persistent storage to MO v" MO_VERSION); + success &= FilesystemUtils::removeByPrefix(filesystem, "sd"); + success &= FilesystemUtils::removeByPrefix(filesystem, "tx"); + success &= FilesystemUtils::removeByPrefix(filesystem, "op"); + success &= FilesystemUtils::removeByPrefix(filesystem, "sc-"); + success &= FilesystemUtils::removeByPrefix(filesystem, "client-state.cnf"); + success &= FilesystemUtils::removeByPrefix(filesystem, "arduino-ocpp.cnf"); + success &= FilesystemUtils::removeByPrefix(filesystem, "ocpp-creds.jsn"); + + snprintf(bstats.microOcppVersion, sizeof(bstats.microOcppVersion), "%s", MO_VERSION); + MO_DBG_DEBUG("clear local state files (migration): %s", success ? "success" : "not completed"); + + success &= storeBootStats(filesystem, bstats); + } + return success; +} + +bool PersistencyUtils::autoRecovery(MO_FilesystemAdapter *filesystem) { + + BootStats bstats; + if (!loadBootStats(filesystem, bstats)) { + return false; + } + + bstats.attempts++; + + bool success = storeBootStats(filesystem, bstats); + + if (bstats.attempts <= 3) { + //no boot loop - just increment attempts and we're good + return success; + } + + bool ret = true; + ret &= FilesystemUtils::removeByPrefix(filesystem, "sd"); + ret &= FilesystemUtils::removeByPrefix(filesystem, "tx"); + ret &= FilesystemUtils::removeByPrefix(filesystem, "sc-"); + ret &= FilesystemUtils::removeByPrefix(filesystem, "reservation"); + ret &= FilesystemUtils::removeByPrefix(filesystem, "client-state"); + + MO_DBG_ERR("clear local state files (recovery): %s", ret ? "success" : "not completed"); + success &= ret; + + return success; +} + +bool PersistencyUtils::setBootSuccess(MO_FilesystemAdapter *filesystem) { + + BootStats bstats; + if (!loadBootStats(filesystem, bstats)) { + return false; + } + + if (bstats.attempts == 0) { + // attempts already 0 - no need to write back file + return true; + } + + bstats.attempts = 0; + + return storeBootStats(filesystem, bstats); +} diff --git a/src/MicroOcpp/Core/PersistencyUtils.h b/src/MicroOcpp/Core/PersistencyUtils.h new file mode 100644 index 00000000..a1e1c47c --- /dev/null +++ b/src/MicroOcpp/Core/PersistencyUtils.h @@ -0,0 +1,36 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_PERSISTENCY_UTILS_H +#define MO_PERSISTENCY_UTILS_H + +#include +#include + +#define MO_BOOTSTATS_VERSION_SIZE 10 + +namespace MicroOcpp { +namespace PersistencyUtils { + +struct BootStats { + uint16_t bootNr = 0; + uint16_t attempts = 0; //incremented by 1 for each attempt to initialize the library, reset to 0 after successful initialization + + char microOcppVersion [MO_BOOTSTATS_VERSION_SIZE] = {'\0'}; +}; + +bool loadBootStats(MO_FilesystemAdapter *filesystem, BootStats& bstats); +bool storeBootStats(MO_FilesystemAdapter *filesystem, BootStats& bstats); + +bool migrate(MO_FilesystemAdapter *filesystem); //migrate persistent storage if running on a new MO version + +//check for multiple boot failures in a row and if detected, delete all persistent files which could cause a crash. Call +//before MO is initialized / set up / as early as possible +bool autoRecovery(MO_FilesystemAdapter *filesystem); + +bool setBootSuccess(MO_FilesystemAdapter *filesystem); //reset boot failure counter after successful boot + +} //namespace PersistencyUtils +} //namespace MicroOcpp +#endif diff --git a/src/MicroOcpp/Core/Request.cpp b/src/MicroOcpp/Core/Request.cpp index af6e2885..097b92a0 100644 --- a/src/MicroOcpp/Core/Request.cpp +++ b/src/MicroOcpp/Core/Request.cpp @@ -1,8 +1,9 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include +#include #include #include #include @@ -14,64 +15,84 @@ #include #include -namespace MicroOcpp { - unsigned int g_randSeed = 1394827383; - - void writeRandomNonsecure(unsigned char *buf, size_t len) { - g_randSeed += mocpp_tick_ms(); - const unsigned int a = 16807; - const unsigned int m = 2147483647; - for (size_t i = 0; i < len; i++) { - g_randSeed = (a * g_randSeed) % m; - buf[i] = g_randSeed; - } - } -} - using namespace MicroOcpp; -Request::Request(std::unique_ptr msg) : MemoryManaged("Request.", msg->getOperationType()), messageID(makeString(getMemoryTag())), operation(std::move(msg)) { - timeout_start = mocpp_tick_ms(); - debugRequest_start = mocpp_tick_ms(); +Request::Request(Context& context, std::unique_ptr operation) : MemoryManaged("Request.", operation->getOperationType()), context(context), operation(std::move(operation)) { + timeoutStart = context.getClock().getUptime(); + debugRequestTime = context.getClock().getUptime(); } Request::~Request(){ - + if (onAbortListener) { + onAbortListener(); + } + setMessageID(nullptr); } Operation *Request::getOperation(){ return operation.get(); } -void Request::setTimeout(unsigned long timeout) { - this->timeout_period = timeout; +void Request::setTimeout(int32_t timeout) { + this->timeoutPeriod = timeout; } bool Request::isTimeoutExceeded() { - return timed_out || (timeout_period && mocpp_tick_ms() - timeout_start >= timeout_period); + auto& clock = context.getClock(); + int32_t dt; + clock.delta(clock.getUptime(), timeoutStart, dt); + return timedOut || (timeoutPeriod > 0 && dt >= timeoutPeriod); } void Request::executeTimeout() { - if (!timed_out) { - onTimeoutListener(); - onAbortListener(); + if (!timedOut) { + if (onTimeoutListener) { + onTimeoutListener(); + onTimeoutListener = nullptr; + } + if (onAbortListener) { + onAbortListener(); + onAbortListener = nullptr; + } } - timed_out = true; + timedOut = true; } -void Request::setMessageID(const char *id){ - if (!messageID.empty()){ - MO_DBG_ERR("messageID already defined"); +bool Request::setMessageID(const char *id){ + MO_FREE(messageID); + messageID = nullptr; + + if (id) { + size_t size = strlen(id); + messageID = static_cast(MO_MALLOC(getMemoryTag(), size)); + if (!messageID) { + MO_DBG_ERR("OOM"); + return false; + } + memset(messageID, 0, size); + auto ret = snprintf(messageID, size, "%s", id); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("snprintf: %i", ret); + MO_FREE(messageID); + messageID = nullptr; + return false; + } } - messageID = id; + + //success + return true; } Request::CreateRequestResult Request::createRequest(JsonDoc& requestJson) { - if (messageID.empty()) { - char uuid [37] = {'\0'}; - generateUUID(uuid, 37); - messageID = uuid; + if (!messageID) { + char uuid [MO_UUID_STR_SIZE] = {'\0'}; + UuidUtils::generateUUID(context.getRngCb(), uuid, sizeof(uuid)); + setMessageID(uuid); + } + + if (!messageID) { + return CreateRequestResult::Failure; } /* @@ -85,40 +106,44 @@ Request::CreateRequestResult Request::createRequest(JsonDoc& requestJson) { /* * Create OCPP-J Remote Procedure Call header */ - size_t json_buffsize = JSON_ARRAY_SIZE(4) + (messageID.length() + 1) + requestPayload->capacity(); + size_t json_buffsize = JSON_ARRAY_SIZE(4) + requestPayload->capacity(); requestJson = initJsonDoc(getMemoryTag(), json_buffsize); - requestJson.add(MESSAGE_TYPE_CALL); //MessageType - requestJson.add(messageID); //Unique message ID + requestJson.add(MESSAGE_TYPE_CALL); //MessageType + requestJson.add((const char*)messageID); //Unique message ID (force zero-copy) requestJson.add(operation->getOperationType()); //Action - requestJson.add(*requestPayload); //Payload - - if (MO_DBG_LEVEL >= MO_DL_DEBUG && mocpp_tick_ms() - debugRequest_start >= 10000) { //print contents on the console - debugRequest_start = mocpp_tick_ms(); - - char *buf = new char[1024]; - size_t len = 0; - if (buf) { - len = serializeJson(requestJson, buf, 1024); + requestJson.add(*requestPayload); //Payload + + if (MO_DBG_LEVEL >= MO_DL_DEBUG) { + auto& clock = context.getClock(); + int32_t dt; + clock.delta(clock.now(), debugRequestTime, dt); + if (dt >= 10) { + debugRequestTime = clock.now(); + + char *buf = static_cast(MO_MALLOC(getMemoryTag(), 1024)); + size_t len = 0; + if (buf) { + len = serializeJson(requestJson, buf, 1024); + } + + if (!buf || len < 1) { + MO_DBG_DEBUG("Try to send request: %s", operation->getOperationType()); + } else { + MO_DBG_DEBUG("Try to send request: %.*s (...)", 128, buf); + } + + MO_FREE(buf); } - - if (!buf || len < 1) { - MO_DBG_DEBUG("Try to send request: %s", operation->getOperationType()); - } else { - MO_DBG_DEBUG("Try to send request: %.*s (...)", 128, buf); - } - - delete[] buf; } return CreateRequestResult::Success; } bool Request::receiveResponse(JsonArray response){ - /* - * check if messageIDs match. If yes, continue with this function. If not, return false for message not consumed - */ - if (messageID.compare(response[1].as())){ + + //check if messageIDs match + if (!messageID || !strcmp(messageID, response[1].as())) { return false; } @@ -135,12 +160,12 @@ bool Request::receiveResponse(JsonArray response){ /* * Hand the payload over to the onReceiveConf Callback */ - onReceiveConfListener(payload); + if (onReceiveConfListener) { + onReceiveConfListener(payload); + } + + onAbortListener = nullptr; //ensure onAbort is not called during destruction - /* - * return true as this message has been consumed - */ - return true; } else if (messageTypeId == MESSAGE_TYPE_CALLERROR) { /* @@ -149,19 +174,22 @@ bool Request::receiveResponse(JsonArray response){ const char *errorCode = response[2]; const char *errorDescription = response[3]; JsonObject errorDetails = response[4]; - bool abortOperation = operation->processErr(errorCode, errorDescription, errorDetails); + operation->processErr(errorCode, errorDescription, errorDetails); - if (abortOperation) { + if (onReceiveErrorListener) { onReceiveErrorListener(errorCode, errorDescription, errorDetails); + } + if (onAbortListener) { onAbortListener(); + onAbortListener = nullptr; //ensure onAbort is called only once } - return abortOperation; } else { MO_DBG_WARN("invalid response"); return false; //don't discard this message but retry sending it } + return true; } bool Request::receiveRequest(JsonArray request) { @@ -182,13 +210,32 @@ bool Request::receiveRequest(JsonArray request) { /* * Hand the payload over to the first Callback. It is a callback that notifies the client that request has been processed in the OCPP-library */ - onReceiveReqListener(payload); + if (onReceiveReqListener) { + size_t bufsize = 1000; //fixed for now, may change to direct pass-through of the input message + char *buf = static_cast(MO_MALLOC(getMemoryTag(), bufsize)); + if (buf) { + auto written = serializeJson(payload, buf, bufsize); + if (written >= 2 && written < bufsize) { + //success + onReceiveReqListener(operation->getOperationType(), buf, onReceiveReqListenerUserData); + } else { + MO_DBG_ERR("onReceiveReqListener supports only %zu charactes", bufsize); + } + } else { + MO_DBG_ERR("OOM"); + } + MO_FREE(buf); + } return true; //success } Request::CreateResponseResult Request::createResponse(JsonDoc& response) { + if (!messageID) { + return CreateResponseResult::Failure; + } + bool operationFailure = operation->getErrorCode() != nullptr; if (!operationFailure) { @@ -206,11 +253,24 @@ Request::CreateResponseResult Request::createResponse(JsonDoc& response) { response = initJsonDoc(getMemoryTag(), json_buffsize); response.add(MESSAGE_TYPE_CALLRESULT); //MessageType - response.add(messageID.c_str()); //Unique message ID - response.add(*payload); //Payload + response.add((const char*)messageID); //Unique message ID (force zero-copy) + response.add(*payload); //Payload if (onSendConfListener) { - onSendConfListener(payload->as()); + size_t bufsize = 1000; //fixed for now, may change to direct pass-through of the input message + char *buf = static_cast(MO_MALLOC(getMemoryTag(), bufsize)); + if (buf) { + auto written = serializeJson(payload->as(), buf, bufsize); + if (written >= 2 && written < bufsize) { + //success + onSendConfListener(operation->getOperationType(), buf, onSendConfListenerUserData); + } else { + MO_DBG_ERR("onSendConfListener supports only %zu charactes", bufsize); + } + } else { + MO_DBG_ERR("OOM"); + } + MO_FREE(buf); } } else { //operation failure. Send error message instead @@ -227,46 +287,44 @@ Request::CreateResponseResult Request::createResponse(JsonDoc& response) { response = initJsonDoc(getMemoryTag(), json_buffsize); response.add(MESSAGE_TYPE_CALLERROR); //MessageType - response.add(messageID.c_str()); //Unique message ID + response.add((const char*)messageID); //Unique message ID (force zero-copy) response.add(errorCode); response.add(errorDescription); - response.add(*errorDetails); //Error description + response.add(*errorDetails); //Error description } + onAbortListener = nullptr; //ensure onAbort is not called during destruction + return CreateResponseResult::Success; } -void Request::setOnReceiveConfListener(OnReceiveConfListener onReceiveConf){ - if (onReceiveConf) - onReceiveConfListener = onReceiveConf; +void Request::setOnReceiveConf(ReceiveConfListener onReceiveConf){ + onReceiveConfListener = onReceiveConf; } /** * Sets a Listener that is called after this machine processed a request by the communication counterpart */ -void Request::setOnReceiveReqListener(OnReceiveReqListener onReceiveReq){ - if (onReceiveReq) - onReceiveReqListener = onReceiveReq; +void Request::setOnReceiveRequest(void (*onReceiveReq)(const char *operationType, const char *payloadJson, void *userData), void *userData){ + this->onReceiveReqListener = onReceiveReq; + this->onReceiveReqListenerUserData = userData; } -void Request::setOnSendConfListener(OnSendConfListener onSendConf){ - if (onSendConf) - onSendConfListener = onSendConf; +void Request::setOnSendConf(void (*onSendConf)(const char *operationType, const char *payloadJson, void *userData), void *userData){ + this->onSendConfListener = onSendConf; + this->onSendConfListenerUserData = userData; } -void Request::setOnTimeoutListener(OnTimeoutListener onTimeout) { - if (onTimeout) - onTimeoutListener = onTimeout; +void Request::setOnTimeout(TimeoutListener onTimeout) { + onTimeoutListener = onTimeout; } -void Request::setOnReceiveErrorListener(OnReceiveErrorListener onReceiveError) { - if (onReceiveError) - onReceiveErrorListener = onReceiveError; +void Request::setReceiveErrorListener(ReceiveErrorListener onReceiveError) { + onReceiveErrorListener = onReceiveError; } -void Request::setOnAbortListener(OnAbortListener onAbort) { - if (onAbort) - onAbortListener = onAbort; +void Request::setOnAbort(AbortListener onAbort) { + onAbortListener = onAbort; } const char *Request::getOperationType() { @@ -283,15 +341,15 @@ bool Request::isRequestSent() { namespace MicroOcpp { -std::unique_ptr makeRequest(std::unique_ptr operation){ +std::unique_ptr makeRequest(Context& context, std::unique_ptr operation){ if (operation == nullptr) { return nullptr; } - return std::unique_ptr(new Request(std::move(operation))); + return std::unique_ptr(new Request(context, std::move(operation))); } -std::unique_ptr makeRequest(Operation *operation) { - return makeRequest(std::unique_ptr(operation)); +std::unique_ptr makeRequest(Context& context, Operation *operation) { + return makeRequest(context, std::unique_ptr(operation)); } -} //end namespace MicroOcpp +} //namespace MicroOcpp diff --git a/src/MicroOcpp/Core/Request.h b/src/MicroOcpp/Core/Request.h index 62e42967..59bfb4fb 100644 --- a/src/MicroOcpp/Core/Request.h +++ b/src/MicroOcpp/Core/Request.h @@ -12,45 +12,49 @@ #include #include - +#include #include namespace MicroOcpp { +class Context; class Operation; class Model; class Request : public MemoryManaged { private: - String messageID; + Context& context; + char *messageID = nullptr; std::unique_ptr operation; - void setMessageID(const char *id); - OnReceiveConfListener onReceiveConfListener = [] (JsonObject payload) {}; - OnReceiveReqListener onReceiveReqListener = [] (JsonObject payload) {}; - OnSendConfListener onSendConfListener = [] (JsonObject payload) {}; - OnTimeoutListener onTimeoutListener = [] () {}; - OnReceiveErrorListener onReceiveErrorListener = [] (const char *code, const char *description, JsonObject details) {}; - OnAbortListener onAbortListener = [] () {}; - - unsigned long timeout_start = 0; - unsigned long timeout_period = 40000; - bool timed_out = false; + bool setMessageID(const char *id); + ReceiveConfListener onReceiveConfListener; + void (*onReceiveReqListener)(const char *operationType, const char *payloadJson, void *userData) = nullptr; + void *onReceiveReqListenerUserData = nullptr; + void (*onSendConfListener)(const char *operationType, const char *payloadJson, void *userData) = nullptr; + void *onSendConfListenerUserData = nullptr; + TimeoutListener onTimeoutListener; + ReceiveErrorListener onReceiveErrorListener; + AbortListener onAbortListener; + + Timestamp timeoutStart; + int32_t timeoutPeriod = 40; //in seconds + bool timedOut = false; - unsigned long debugRequest_start = 0; + Timestamp debugRequestTime; bool requestSent = false; public: - Request(std::unique_ptr msg); + Request(Context& context, std::unique_ptr msg); ~Request(); Operation *getOperation(); - void setTimeout(unsigned long timeout); //0 = disable timeout + void setTimeout(int32_t timeout); //if positive, sets timeout period in secs. If 0 or negative value, disables timeout bool isTimeoutExceeded(); void executeTimeout(); //call Timeout Listener - void setOnTimeoutListener(OnTimeoutListener onTimeout); + void setOnTimeout(TimeoutListener onTimeout); /** * Sends the message(s) that belong to the OCPP Operation. This function puts a JSON message on the lower protocol layer. @@ -67,9 +71,7 @@ class Request : public MemoryManaged { CreateRequestResult createRequest(JsonDoc& out); /** - * Decides if message belongs to this operation instance and if yes, proccesses it. Receives both Confirmations and Errors - * - * Returns true if JSON object has been consumed, false otherwise. + * Processes Server response (=Confirmations and Errors). Returns true on success, false otherwise */ bool receiveResponse(JsonArray json); @@ -93,11 +95,11 @@ class Request : public MemoryManaged { CreateResponseResult createResponse(JsonDoc& out); - void setOnReceiveConfListener(OnReceiveConfListener onReceiveConf); //listener executed when we received the .conf() to a .req() we sent - void setOnReceiveReqListener(OnReceiveReqListener onReceiveReq); //listener executed when we receive a .req() - void setOnSendConfListener(OnSendConfListener onSendConf); //listener executed when we send a .conf() to a .req() we received + void setOnReceiveConf(ReceiveConfListener onReceiveConf); //listener executed when we received the .conf() to a .req() we sent + void setOnReceiveRequest(void (*onReceiveReq)(const char *operationType, const char *payloadJson, void *userData), void *userData); //listener executed when we receive a .req() + void setOnSendConf(void (*onSendConf)(const char *operationType, const char *payloadJson, void *userData), void *userData); //listener executed when we send a .conf() to a .req() we received - void setOnReceiveErrorListener(OnReceiveErrorListener onReceiveError); + void setReceiveErrorListener(ReceiveErrorListener onReceiveError); /** * The listener onAbort will be called whenever the engine stops trying to execute an operation normally which were initiated @@ -110,7 +112,7 @@ class Request : public MemoryManaged { * * The engine uses this listener in both modes: EVSE mode and Central system mode */ - void setOnAbortListener(OnAbortListener onAbort); + void setOnAbort(AbortListener onAbort); const char *getOperationType(); @@ -121,9 +123,9 @@ class Request : public MemoryManaged { /* * Simple factory functions */ -std::unique_ptr makeRequest(std::unique_ptr op); -std::unique_ptr makeRequest(Operation *op); //takes ownership of op +std::unique_ptr makeRequest(Context& context, std::unique_ptr op); +std::unique_ptr makeRequest(Context& context, Operation *op); //takes ownership of op -} //end namespace MicroOcpp +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Core/RequestCallbacks.h b/src/MicroOcpp/Core/RequestCallbacks.h index 4959a3bc..859d60e5 100644 --- a/src/MicroOcpp/Core/RequestCallbacks.h +++ b/src/MicroOcpp/Core/RequestCallbacks.h @@ -10,12 +10,10 @@ namespace MicroOcpp { -using OnReceiveConfListener = std::function; -using OnReceiveReqListener = std::function; -using OnSendConfListener = std::function; -using OnTimeoutListener = std::function; -using OnReceiveErrorListener = std::function; //will be called if OCPP communication partner returns error code -using OnAbortListener = std::function; //will be called whenever the engine will stop trying to execute the operation normallythere is a timeout or error (onAbort = onTimeout || onReceiveError) +using ReceiveConfListener = std::function; +using TimeoutListener = std::function; +using ReceiveErrorListener = std::function; //will be called if OCPP communication partner returns error code +using AbortListener = std::function; //will be called whenever the engine will stop trying to execute the operation normallythere is a timeout or error (onAbort = onTimeout || onReceiveError) -} //end namespace MicroOcpp +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Core/RequestQueue.cpp b/src/MicroOcpp/Core/RequestQueue.cpp index 104a2e97..fe7f70b8 100644 --- a/src/MicroOcpp/Core/RequestQueue.cpp +++ b/src/MicroOcpp/Core/RequestQueue.cpp @@ -1,20 +1,11 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License -#include - #include #include -#include -#include -#include -#include - #include -size_t removePayload(const char *src, size_t src_size, char *dst, size_t dst_size); - using namespace MicroOcpp; VolatileRequestQueue::VolatileRequestQueue() : MemoryManaged("VolatileRequestQueue") { @@ -118,291 +109,3 @@ bool VolatileRequestQueue::pushRequestBack(std::unique_ptr request) { len++; return true; } - -RequestQueue::RequestQueue(Connection& connection, OperationRegistry& operationRegistry) - : MemoryManaged("RequestQueue"), connection(connection), operationRegistry(operationRegistry) { - - ReceiveTXTcallback callback = [this] (const char *payload, size_t length) { - return this->receiveMessage(payload, length); - }; - - connection.setReceiveTXTcallback(callback); - - memset(sendQueues, 0, sizeof(sendQueues)); - addSendQueue(&defaultSendQueue); -} - -void RequestQueue::loop() { - - /* - * Check if front request timed out - */ - if (sendReqFront && sendReqFront->isTimeoutExceeded()) { - MO_DBG_INFO("operation timeout: %s", sendReqFront->getOperationType()); - sendReqFront->executeTimeout(); - sendReqFront.reset(); - } - - if (recvReqFront && recvReqFront->isTimeoutExceeded()) { - MO_DBG_INFO("operation timeout: %s", recvReqFront->getOperationType()); - recvReqFront->executeTimeout(); - recvReqFront.reset(); - } - - defaultSendQueue.loop(); - - if (!connection.isConnected()) { - return; - } - - /** - * Send and dequeue a pending confirmation message, if existing - * - * If a message has been sent, terminate this loop() function. - */ - - if (!recvReqFront) { - recvReqFront = recvQueue.fetchFrontRequest(); - } - - if (recvReqFront) { - - auto response = initJsonDoc(getMemoryTag()); - auto ret = recvReqFront->createResponse(response); - - if (ret == Request::CreateResponseResult::Success) { - auto out = makeString(getMemoryTag()); - serializeJson(response, out); - - bool success = connection.sendTXT(out.c_str(), out.length()); - - if (success) { - MO_DBG_TRAFFIC_OUT(out.c_str()); - recvReqFront.reset(); - } - - return; - } //else: There will be another attempt to send this conf message in a future loop call - } - - /** - * Send pending req message - */ - - if (!sendReqFront) { - - unsigned int minOpNr = RequestEmitter::NoOperation; - size_t index = MO_NUM_REQUEST_QUEUES; - for (size_t i = 0; i < MO_NUM_REQUEST_QUEUES && sendQueues[i]; i++) { - auto opNr = sendQueues[i]->getFrontRequestOpNr(); - if (opNr < minOpNr) { - minOpNr = opNr; - index = i; - } - } - - if (index < MO_NUM_REQUEST_QUEUES) { - sendReqFront = sendQueues[index]->fetchFrontRequest(); - } - } - - if (sendReqFront && !sendReqFront->isRequestSent()) { - - auto request = initJsonDoc(getMemoryTag()); - auto ret = sendReqFront->createRequest(request); - - if (ret == Request::CreateRequestResult::Success) { - - //send request - auto out = makeString(getMemoryTag()); - serializeJson(request, out); - - bool success = connection.sendTXT(out.c_str(), out.length()); - - if (success) { - MO_DBG_TRAFFIC_OUT(out.c_str()); - sendReqFront->setRequestSent(); //mask as sent and wait for response / timeout - } - - return; - } - } -} - -void RequestQueue::sendRequest(std::unique_ptr op){ - defaultSendQueue.pushRequestBack(std::move(op)); -} - -void RequestQueue::sendRequestPreBoot(std::unique_ptr op){ - if (!preBootSendQueue) { - MO_DBG_ERR("did not set PreBoot queue"); - return; - } - preBootSendQueue->pushRequestBack(std::move(op)); -} - -void RequestQueue::addSendQueue(RequestEmitter* sendQueue) { - for (size_t i = 0; i < MO_NUM_REQUEST_QUEUES; i++) { - if (!sendQueues[i]) { - sendQueues[i] = sendQueue; - return; - } - } - MO_DBG_ERR("exceeded sendQueue capacity"); -} - -void RequestQueue::setPreBootSendQueue(VolatileRequestQueue *preBootQueue) { - this->preBootSendQueue = preBootQueue; - addSendQueue(preBootQueue); -} - -unsigned int RequestQueue::getNextOpNr() { - return nextOpNr++; -} - -bool RequestQueue::receiveMessage(const char* payload, size_t length) { - - MO_DBG_TRAFFIC_IN((int) length, payload); - - size_t capacity_init = (3 * length) / 2; - - //capacity = ceil capacity_init to the next power of two; should be at least 128 - - size_t capacity = 128; - while (capacity < capacity_init && capacity < MO_MAX_JSON_CAPACITY) { - capacity *= 2; - } - if (capacity > MO_MAX_JSON_CAPACITY) { - capacity = MO_MAX_JSON_CAPACITY; - } - - auto doc = initJsonDoc(getMemoryTag()); - DeserializationError err = DeserializationError::NoMemory; - - while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) { - - doc = initJsonDoc(getMemoryTag(), capacity); - err = deserializeJson(doc, payload, length); - - capacity *= 2; - } - - bool success = false; - - switch (err.code()) { - case DeserializationError::Ok: { - int messageTypeId = doc[0] | -1; - - if (messageTypeId == MESSAGE_TYPE_CALL) { - receiveRequest(doc.as()); - success = true; - } else if (messageTypeId == MESSAGE_TYPE_CALLRESULT || - messageTypeId == MESSAGE_TYPE_CALLERROR) { - receiveResponse(doc.as()); - success = true; - } else { - MO_DBG_WARN("Invalid OCPP message! (though JSON has successfully been deserialized)"); - } - break; - } - case DeserializationError::InvalidInput: - MO_DBG_WARN("Invalid input! Not a JSON"); - break; - case DeserializationError::NoMemory: { - MO_DBG_WARN("incoming operation exceeds buffer capacity. Input length = %zu, max capacity = %d", length, MO_MAX_JSON_CAPACITY); - - /* - * If websocket input is of message type MESSAGE_TYPE_CALL, send back a message of type MESSAGE_TYPE_CALLERROR. - * Then the communication counterpart knows that this operation failed. - * If the input type is MESSAGE_TYPE_CALLRESULT, then abort the operation to avoid getting stalled. - */ - - doc = initJsonDoc(getMemoryTag(), 200); - char onlyRpcHeader[200]; - size_t onlyRpcHeader_len = removePayload(payload, length, onlyRpcHeader, sizeof(onlyRpcHeader)); - DeserializationError err2 = deserializeJson(doc, onlyRpcHeader, onlyRpcHeader_len); - if (err2.code() == DeserializationError::Ok) { - int messageTypeId = doc[0] | -1; - if (messageTypeId == MESSAGE_TYPE_CALL) { - success = true; - auto op = makeRequest(new MsgBufferExceeded(MO_MAX_JSON_CAPACITY, length)); - receiveRequest(doc.as(), std::move(op)); - } else if (messageTypeId == MESSAGE_TYPE_CALLRESULT || - messageTypeId == MESSAGE_TYPE_CALLERROR) { - success = true; - MO_DBG_WARN("crop incoming response"); - receiveResponse(doc.as()); - } - } - break; - } - default: - MO_DBG_WARN("Deserialization failed: %s", err.c_str()); - break; - } - - return success; -} - -/** - * call conf() on each element of the queue. Start with first element. On successful message delivery, - * delete the element from the list. Try all the pending OCPP Operations until the right one is found. - * - * This function could result in improper behavior in Charging Stations, because messages are not - * guaranteed to be received and therefore processed in the right order. - */ -void RequestQueue::receiveResponse(JsonArray json) { - - if (!sendReqFront || !sendReqFront->receiveResponse(json)) { - MO_DBG_WARN("Received response doesn't match pending operation"); - } - - sendReqFront.reset(); -} - -void RequestQueue::receiveRequest(JsonArray json) { - auto op = operationRegistry.deserializeOperation(json[2] | "UNDEFINED"); - if (op == nullptr) { - MO_DBG_WARN("OOM"); - return; - } - receiveRequest(json, std::move(op)); -} - -void RequestQueue::receiveRequest(JsonArray json, std::unique_ptr op) { - op->receiveRequest(json); //execute the operation - recvQueue.pushRequestBack(std::move(op)); //enqueue so loop() plans conf sending -} - -/* - * Tries to recover the Ocpp-Operation header from a broken message. - * - * Example input: - * [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {"key":"now the message breaks... - * - * The Json library returns an error code when trying to deserialize that broken message. This - * function searches for the first occurence of the character '{' and writes "}]" after it. - * - * Example output: - * [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {}] - * - */ -size_t removePayload(const char *src, size_t src_size, char *dst, size_t dst_size) { - size_t res_len = 0; - for (size_t i = 0; i < src_size && i < dst_size-3; i++) { - if (src[i] == '\0'){ - //no payload found within specified range. Cancel execution - break; - } - dst[i] = src[i]; - if (src[i] == '{') { - dst[i+1] = '}'; - dst[i+2] = ']'; - res_len = i+3; - break; - } - } - dst[res_len] = '\0'; - res_len++; - return res_len; -} diff --git a/src/MicroOcpp/Core/RequestQueue.h b/src/MicroOcpp/Core/RequestQueue.h index 1dcb1b9e..5145a0cd 100644 --- a/src/MicroOcpp/Core/RequestQueue.h +++ b/src/MicroOcpp/Core/RequestQueue.h @@ -1,16 +1,15 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_REQUESTQUEUE_H #define MO_REQUESTQUEUE_H #include +#include -#include #include -#include #include #ifndef MO_REQUEST_CACHE_MAXSIZE @@ -27,7 +26,7 @@ class Connection; class OperationRegistry; class Request; -class RequestEmitter { +class RequestQueue { public: static const unsigned int NoOperation = std::numeric_limits::max(); @@ -35,7 +34,7 @@ class RequestEmitter { virtual std::unique_ptr fetchFrontRequest() = 0; }; -class VolatileRequestQueue : public RequestEmitter, public MemoryManaged { +class VolatileRequestQueue : public RequestQueue, public MemoryManaged { private: std::unique_ptr requests [MO_REQUEST_CACHE_MAXSIZE]; size_t front = 0, len = 0; @@ -50,48 +49,5 @@ class VolatileRequestQueue : public RequestEmitter, public MemoryManaged { bool pushRequestBack(std::unique_ptr request); }; -class RequestQueue : public MemoryManaged { -private: - Connection& connection; - OperationRegistry& operationRegistry; - - RequestEmitter* sendQueues [MO_NUM_REQUEST_QUEUES]; - VolatileRequestQueue defaultSendQueue; - VolatileRequestQueue *preBootSendQueue = nullptr; - std::unique_ptr sendReqFront; - - VolatileRequestQueue recvQueue; - std::unique_ptr recvReqFront; - - bool receiveMessage(const char* payload, size_t length); //receive from server: either a request or response - void receiveRequest(JsonArray json); - void receiveRequest(JsonArray json, std::unique_ptr op); - void receiveResponse(JsonArray json); - - unsigned long sockTrackLastConnected = 0; - - unsigned int nextOpNr = 10; //Nr 0 - 9 reservered for internal purposes -public: - RequestQueue() = delete; - RequestQueue(const RequestQueue&) = delete; - RequestQueue(const RequestQueue&&) = delete; - - RequestQueue(Connection& connection, OperationRegistry& operationRegistry); - - void loop(); //polls all reqQueues and decides which request to send (if any) - - void sendRequest(std::unique_ptr request); //send an OCPP operation request to the server; adds request to default queue - void sendRequestPreBoot(std::unique_ptr request); //send an OCPP operation request to the server; adds request to preBootQueue - - void handleRequest(const char *operationType, Operation* (*createOperationCb)(const char *operationType, void* user_data), void *user_data = nullptr); - void setOnRequest(const char *operationType, void (*onRequest)(const char *payloadJson, size_t len)); - void setOnConfirmation(const char *operationType, void (*onConfirmation)(const char *payloadJson, size_t len)); - - void addSendQueue(RequestEmitter* sendQueue); - void setPreBootSendQueue(VolatileRequestQueue *preBootQueue); - - unsigned int getNextOpNr(); -}; - -} //end namespace MicroOcpp +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index 1bcd0caa..cf9f9319 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -1,15 +1,36 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include + #include -#include +#include + +#include +#include +#include + +#define MO_MAX_UPTIME (MO_MIN_TIME - 1) + +// It's a common mistake to roll-over the mocpp_tick_ms callback too early which breaks +// the unsigned integer arithmetics in this library. The ms counter should use the full +// value range up to (32^2 - 1) ms before resetting to 0. Clock checks for this issue +// and skips one time increment if it is detected. This can be disabled by defining +// MO_CLOCK_ROLLOVER_PROTECTION=0 in the build system +#ifndef MO_CLOCK_ROLLOVER_PROTECTION +#define MO_CLOCK_ROLLOVER_PROTECTION 1 +#endif namespace MicroOcpp { +int noDays(int month, int year) { + return (month == 0 || month == 2 || month == 4 || month == 6 || month == 7 || month == 9 || month == 11) ? 31 : + ((month == 3 || month == 5 || month == 8 || month == 10) ? 30 : + ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28)); +} +} //namespace MicroOcpp -const Timestamp MIN_TIME = Timestamp(2010, 0, 0, 0, 0, 0); -const Timestamp MAX_TIME = Timestamp(2037, 0, 0, 0, 0, 0); +using namespace MicroOcpp; Timestamp::Timestamp() : MemoryManaged("Timestamp") { @@ -19,364 +40,499 @@ Timestamp::Timestamp(const Timestamp& other) : MemoryManaged("Timestamp") { *this = other; } -#if MO_ENABLE_TIMESTAMP_MILLISECONDS - Timestamp::Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second, int32_t ms) : - MemoryManaged("Timestamp"), year(year), month(month), day(day), hour(hour), minute(minute), second(second), ms(ms) { } -#else - Timestamp::Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second) : - MemoryManaged("Timestamp"), year(year), month(month), day(day), hour(hour), minute(minute), second(second) { } -#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS +bool Timestamp::isUnixTime() const { + return time >= MO_MIN_TIME && time <= MO_MAX_TIME; +} -int noDays(int month, int year) { - return (month == 0 || month == 2 || month == 4 || month == 6 || month == 7 || month == 9 || month == 11) ? 31 : - ((month == 3 || month == 5 || month == 8 || month == 10) ? 30 : - ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28)); +bool Timestamp::isUptime() const { + return time <= MO_MAX_UPTIME; } -bool Timestamp::setTime(const char *jsonDateString) { +bool Timestamp::isDefined() const { + return bootNr != 0; +} - const int JSONDATE_MINLENGTH = 19; +Clock::Clock(Context& context) : context(context) { - if (strlen(jsonDateString) < JSONDATE_MINLENGTH){ - return false; - } +} - if (!isdigit(jsonDateString[0]) || //2 - !isdigit(jsonDateString[1]) || //0 - !isdigit(jsonDateString[2]) || //1 - !isdigit(jsonDateString[3]) || //3 - jsonDateString[4] != '-' || //- - !isdigit(jsonDateString[5]) || //0 - !isdigit(jsonDateString[6]) || //2 - jsonDateString[7] != '-' || //- - !isdigit(jsonDateString[8]) || //0 - !isdigit(jsonDateString[9]) || //1 - jsonDateString[10] != 'T' || //T - !isdigit(jsonDateString[11]) || //2 - !isdigit(jsonDateString[12]) || //0 - jsonDateString[13] != ':' || //: - !isdigit(jsonDateString[14]) || //5 - !isdigit(jsonDateString[15]) || //3 - jsonDateString[16] != ':' || //: - !isdigit(jsonDateString[17]) || //3 - !isdigit(jsonDateString[18])) { //2 - //ignore subsequent characters - return false; - } - - int year = (jsonDateString[0] - '0') * 1000 + - (jsonDateString[1] - '0') * 100 + - (jsonDateString[2] - '0') * 10 + - (jsonDateString[3] - '0'); - int month = (jsonDateString[5] - '0') * 10 + - (jsonDateString[6] - '0') - 1; - int day = (jsonDateString[8] - '0') * 10 + - (jsonDateString[9] - '0') - 1; - int hour = (jsonDateString[11] - '0') * 10 + - (jsonDateString[12] - '0'); - int minute = (jsonDateString[14] - '0') * 10 + - (jsonDateString[15] - '0'); - int second = (jsonDateString[17] - '0') * 10 + - (jsonDateString[18] - '0'); - - //optional fractals - int ms = 0; - if (jsonDateString[19] == '.') { - if (isdigit(jsonDateString[20]) || //1 - isdigit(jsonDateString[21]) || //2 - isdigit(jsonDateString[22])) { - - ms = (jsonDateString[20] - '0') * 100 + - (jsonDateString[21] - '0') * 10 + - (jsonDateString[22] - '0'); - } else { +bool Clock::setup() { + + uint16_t bootNr = 1; //0 is reserved for undefined + + if (auto filesystem = context.getFilesystem()) { + // restore bootNr from bootstats + + PersistencyUtils::BootStats bootstats; + if (!PersistencyUtils::loadBootStats(filesystem, bootstats)) { + MO_DBG_ERR("cannot load bootstats"); return false; } - } - if (year < 1970 || year >= 2038 || - month < 0 || month >= 12 || - day < 0 || day >= noDays(month, year) || - hour < 0 || hour >= 24 || - minute < 0 || minute >= 60 || - second < 0 || second > 60 || //tolerate leap seconds -- (23:59:60) can be a valid time - ms < 0 || ms >= 1000) { - return false; + bootstats.bootNr++; //assign new boot number to this run + if (bootstats.bootNr == 0) { //handle roll-over + bootstats.bootNr = 1; + } + + bootNr = bootstats.bootNr; + + if (!PersistencyUtils::storeBootStats(filesystem, bootstats)) { + MO_DBG_ERR("cannot update bootstats"); + return false; + } } - this->year = year; - this->month = month; - this->day = day; - this->hour = hour; - this->minute = minute; - this->second = second; -#if MO_ENABLE_TIMESTAMP_MILLISECONDS - this->ms = ms; -#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - + uptime.bootNr = bootNr; + unixTime.bootNr = bootNr; + return true; } -bool Timestamp::toJsonString(char *jsonDateString, size_t buffsize) const { - if (buffsize < JSONDATE_LENGTH + 1) return false; - - jsonDateString[0] = ((char) ((year / 1000) % 10)) + '0'; - jsonDateString[1] = ((char) ((year / 100) % 10)) + '0'; - jsonDateString[2] = ((char) ((year / 10) % 10)) + '0'; - jsonDateString[3] = ((char) ((year / 1) % 10)) + '0'; - jsonDateString[4] = '-'; - jsonDateString[5] = ((char) (((month + 1) / 10) % 10)) + '0'; - jsonDateString[6] = ((char) (((month + 1) / 1) % 10)) + '0'; - jsonDateString[7] = '-'; - jsonDateString[8] = ((char) (((day + 1) / 10) % 10)) + '0'; - jsonDateString[9] = ((char) (((day + 1) / 1) % 10)) + '0'; - jsonDateString[10] = 'T'; - jsonDateString[11] = ((char) ((hour / 10) % 10)) + '0'; - jsonDateString[12] = ((char) ((hour / 1) % 10)) + '0'; - jsonDateString[13] = ':'; - jsonDateString[14] = ((char) ((minute / 10) % 10)) + '0'; - jsonDateString[15] = ((char) ((minute / 1) % 10)) + '0'; - jsonDateString[16] = ':'; - jsonDateString[17] = ((char) ((second / 10) % 10)) + '0'; - jsonDateString[18] = ((char) ((second / 1) % 10)) + '0'; +void Clock::loop() { + + auto platformTimeMs = context.getTicksMs(); + auto platformDeltaMs = platformTimeMs - lastIncrement; + + /* Roll-over protection: if the platform implementation of mocpp_tick_ms does not + * fill up the full 32 bits before rolling over, the unsigned integer arithmetics + * would break. Handle this case here, because it is a common mistake */ + if (MO_CLOCK_ROLLOVER_PROTECTION && platformTimeMs < lastIncrement) { + // rolled over + if (platformDeltaMs > 3600 * 1000) { + // timer rolled over and delta is above 1 hour - that's suspicious + MO_DBG_ERR("mocpp_tick_ms roll-over error. Must increment milliseconds up to (32^2 - 1) before resetting to 0"); + platformDeltaMs = 0; + } + } + #if MO_ENABLE_TIMESTAMP_MILLISECONDS - jsonDateString[19] = '.'; - jsonDateString[20] = ((char) ((ms / 100) % 10)) + '0'; - jsonDateString[21] = ((char) ((ms / 10) % 10)) + '0'; - jsonDateString[22] = ((char) ((ms / 1) % 10)) + '0'; - jsonDateString[23] = 'Z'; - jsonDateString[24] = '\0'; + addMs(unixTime, platformDeltaMs); + addMs(uptime, platformDeltaMs); + lastIncrement = platformTimeMs; #else - jsonDateString[19] = 'Z'; - jsonDateString[20] = '\0'; + auto platformDeltaS = platformDeltaMs / 1000; + add(unixTime, platformDeltaS); + add(uptime, platformDeltaS) + lastIncrement += platformDeltaS * 1000; #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS +} - return true; +const Timestamp &Clock::now() { + if (unixTime.isUnixTime()) { + return unixTime; + } else { + return uptime; + } } -Timestamp &Timestamp::operator+=(int secs) { +const Timestamp &Clock::getUptime() { + return uptime; +} - second += secs; +int32_t Clock::getUptimeInt() { + return uptime.time; +} - if (second >= 0 && second < 60) return *this; +bool Clock::setTime(const char* jsonDateString) { - minute += second / 60; - second %= 60; - if (second < 0) { - minute--; - second += 60; + Timestamp t; + if (!parseString(jsonDateString, t)) { + return false; } - if (minute >= 0 && minute < 60) return *this; + unixTime = t; + return true; +} + +bool Clock::setTime(int32_t unixTimeInt) { - hour += minute / 60; - minute %= 60; - if (minute < 0) { - hour--; - minute += 60; + Timestamp t; + if (!fromUnixTime(t, unixTimeInt)) { + return false; } - if (hour >= 0 && hour < 24) return *this; + unixTime = t; + return true; +} - day += hour / 24; - hour %= 24; - if (hour < 0) { - day--; - hour += 24; +bool Clock::delta(const Timestamp& t2, const Timestamp& t1, int32_t& dt) const { + //dt = t2 - t1 + if (!t1.isDefined() || !t2.isDefined()) { + return false; } - while (day >= noDays(month, year)) { - day -= noDays(month, year); - month++; - - if (month >= 12) { - month -= 12; - year++; + if (t1.isUnixTime() && t2.isUnixTime()) { + dt = t2.time - t1.time; + return true; + } else if (t1.isUptime() && t2.isUptime() && t1.bootNr == t2.bootNr) { + + if ((t1.time > 0 && t2.time - t1.time > t2.time) || + (t1.time < 0 && t2.time - t1.time < t2.time)) { + MO_DBG_ERR("integer overflow"); + return false; } - } - while (day < 0) { - month--; - if (month < 0) { - month += 12; - year--; + dt = t2.time - t1.time; + return true; + } else { + + + Timestamp t1Unix, t2Unix; + if (toUnixTime(t1, t1Unix) && toUnixTime(t2, t2Unix)) { + dt = t2Unix.time - t1Unix.time; + return true; + } else { + MO_DBG_ERR("cannot get delta: time captured before initial clock sync"); + return false; } - day += noDays(month, year); } - - return *this; } -#if MO_ENABLE_TIMESTAMP_MILLISECONDS -Timestamp &Timestamp::addMilliseconds(int val) { +bool Clock::add(Timestamp& t, int32_t secs) const { - ms += val; + if (!t.isDefined()) { + return false; + } - if (ms >= 0 && ms < 1000) return *this; - - auto dsecond = ms / 1000; - ms %= 1000; - if (ms < 0) { - dsecond--; - ms += 1000; + if ((secs > 0 && t.time + secs < t.time) || + (secs < 0 && t.time + secs > t.time)) { + MO_DBG_ERR("integer overflow"); + return false; + } + + if (t.isUnixTime() && (t.time + secs < MO_MIN_TIME || t.time + secs > MO_MAX_TIME)) { + MO_DBG_ERR("exceed valid unix time range"); + return false; + } + + if (t.isUptime() && (t.time + secs > MO_MAX_UPTIME)) { + MO_DBG_ERR("exceed valid uptime time range"); + return false; } - return this->operator+=(dsecond); -} -#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS -Timestamp &Timestamp::operator-=(int secs) { - return operator+=(-secs); + t.time += secs; + + return true; } -int Timestamp::operator-(const Timestamp &rhs) const { - //dt = rhs - mocpp_base - - int16_t year_base, year_end; - if (year <= rhs.year) { - year_base = year; - year_end = rhs.year; - } else { - year_base = rhs.year; - year_end = year; +bool Clock::toJsonString(const Timestamp& src, char *dst, size_t size) const { + + if (!src.isDefined()) { + return false; } - int16_t lhsDays = day; - int16_t rhsDays = rhs.day; + if (size < MO_JSONDATE_SIZE) { + return false; + } - for (int16_t iy = year_base; iy <= year_end; iy++) { - for (int16_t im = 0; im < 12; im++) { - if (year > iy || (year == iy && month > im)) { - lhsDays += noDays(im, iy); - } - if (rhs.year > iy || (rhs.year == iy && rhs.month > im)) { - rhsDays += noDays(im, iy); - } + Timestamp t; + if (!toUnixTime(src, t)) { + MO_DBG_ERR("timestamp not a unix time"); + return false; + } + + int32_t time = t.time; + + int year = 1970; + int month = 0; + while (time - (noDays(year, month) * 24 * 3600) >= 0) { + time -= noDays(year, month) * 24 * 3600; + month++; + if (month >= 12) { + year++; + month = 0; } } - int dt = (lhsDays - rhsDays) * (24 * 3600) + (hour - rhs.hour) * 3600 + (minute - rhs.minute) * 60 + second - rhs.second; + int day = time / (24 * 3600); + time %= 24 * 3600; + int hour = time / 3600; + time %= 3600; + int minute = time / 60; + time %= 60; + int second = time; + + // first month of the year is '1' and first day of the month is '1' + month++; + day++; + int ret; #if MO_ENABLE_TIMESTAMP_MILLISECONDS - // Make it so that we round the difference to the nearest second, instead of being up to almost a whole second off - if ((ms - rhs.ms) > 500) dt++; - if ((ms - rhs.ms) < -500) dt--; + ret = snprintf(dst, size, "%04i-%02i-%02iT%02i:%02i:%02i.%03uZ", + year, month, day, hour, minute, second, (unsigned int)(t.ms > 999 ? 999 : t.ms)); +#else + ret = snprintf(dst, size, "%04i-%02i-%02iT%02i:%02i:%02iZ", + year, month, day, hour, minute, second); #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - return dt; + if (ret < 0 || (size_t)ret >= size) { + return false; + } + + //success + return true; } -Timestamp &Timestamp::operator=(const Timestamp &rhs) { - year = rhs.year; - month = rhs.month; - day = rhs.day; - hour = rhs.hour; - minute = rhs.minute; - second = rhs.second; +bool Clock::toInternalString(const Timestamp& t, char *dst, size_t size) const { + + if (!t.isDefined()) { + return false; + } + + if (size < MO_INTERNALTIME_SIZE) { + return false; + } + + int ret; #if MO_ENABLE_TIMESTAMP_MILLISECONDS - ms = rhs.ms; + ret = snprintf(dst, size, "i%ut%li", (unsigned int)t.bootNr, (long int)t.time); +#else + ret = snprintf(dst, size, "i%ut%lim%u", (unsigned int)t.bootNr, (long int)t.time, (unsigned int)(dst.ms > 999 ? 999 : dst.ms)); #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - return *this; -} + if (ret < 0 || (size_t)ret >= size) { + return false; + } -Timestamp operator+(const Timestamp &lhs, int secs) { - Timestamp res = lhs; - res += secs; - return res; + return true; } -Timestamp operator-(const Timestamp &lhs, int secs) { - return operator+(lhs, -secs); -} +bool Clock::parseString(const char *src, Timestamp& dst) const { + + if (src[0] == 'i') { //this is the internal representation, consisting of an optional bootNr and a time value + size_t i = 1; + uint16_t bootNr = 0, bootNr2 = 0; + for (; isdigit(src[i]); i++) { + bootNr *= 10; + bootNr += src[i] - '0'; + if (bootNr < bootNr2) { + //overflow + MO_DBG_ERR("invalid bootNr"); + return false; + } + bootNr2 = bootNr; + } -bool operator==(const Timestamp &lhs, const Timestamp &rhs) { - return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day && lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.second == rhs.second -#if MO_ENABLE_TIMESTAMP_MILLISECONDS - && lhs.ms == rhs.ms -#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - ; -} + if (src[i++] != 't') { + MO_DBG_ERR("invalid time string"); + return false; + } -bool operator!=(const Timestamp &lhs, const Timestamp &rhs) { - return !(lhs == rhs); -} + bool positive = true; + if (src[i] == '-') { + positive = false; + i++; + } + + int32_t time = 0, time2 = 0; + for (; isdigit(src[i]); i++) { + time *= 10; + if (positive) { + time += src[i] - '0'; + } else { + time -= src[i] - '0'; + } + if ((positive && time < time2) || (!positive && time > time2)) { + //overflow + MO_DBG_ERR("invalid time"); + return false; + } + if (time >= MO_MAX_TIME) { + //exceed numeric limit + MO_DBG_ERR("invalid time"); + return false; + } + if (time < MO_MIN_TIME && time > MO_MAX_UPTIME) { + MO_DBG_ERR("invalid time"); + return false; + } + time2 = time; + } + + // Optional ms + uint16_t ms = 0; + if (src[i] == 'm') { + i++; + for (; isdigit(src[i]); i++) { + ms *= 10; + ms += src[i] - '0'; + if (ms >= 1000) { + //overflow + MO_DBG_ERR("invalid ms"); + return false; + } + } + } -bool operator<(const Timestamp &lhs, const Timestamp &rhs) { - if (lhs.year != rhs.year) - return lhs.year < rhs.year; - if (lhs.month != rhs.month) - return lhs.month < rhs.month; - if (lhs.day != rhs.day) - return lhs.day < rhs.day; - if (lhs.hour != rhs.hour) - return lhs.hour < rhs.hour; - if (lhs.minute != rhs.minute) - return lhs.minute < rhs.minute; - if (lhs.second != rhs.second) - return lhs.second < rhs.second; + if (src[i] != '\0') { + MO_DBG_ERR("invalid time"); + return false; + } + + //success + dst.time = time; + dst.bootNr = bootNr; #if MO_ENABLE_TIMESTAMP_MILLISECONDS - if (lhs.ms != rhs.ms) - return lhs.ms < rhs.ms; + dst.ms = ms; #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - return false; -} + return true; + } else { // this is a JSON time string -bool operator<=(const Timestamp &lhs, const Timestamp &rhs) { - return lhs < rhs || lhs == rhs; -} + const int JSONDATE_MINLENGTH = 19; -bool operator>(const Timestamp &lhs, const Timestamp &rhs) { - return rhs < lhs; -} + if (strlen(src) < JSONDATE_MINLENGTH){ + return false; + } -bool operator>=(const Timestamp &lhs, const Timestamp &rhs) { - return rhs <= lhs; -} + if (!isdigit(src[0]) || //2 + !isdigit(src[1]) || //0 + !isdigit(src[2]) || //1 + !isdigit(src[3]) || //3 + src[4] != '-' || //- + !isdigit(src[5]) || //0 + !isdigit(src[6]) || //2 + src[7] != '-' || //- + !isdigit(src[8]) || //0 + !isdigit(src[9]) || //1 + src[10] != 'T' || //T + !isdigit(src[11]) || //2 + !isdigit(src[12]) || //0 + src[13] != ':' || //: + !isdigit(src[14]) || //5 + !isdigit(src[15]) || //3 + src[16] != ':' || //: + !isdigit(src[17]) || //3 + !isdigit(src[18])) { //2 + //ignore subsequent characters + return false; + } + + int year = (src[0] - '0') * 1000 + + (src[1] - '0') * 100 + + (src[2] - '0') * 10 + + (src[3] - '0'); + int month = (src[5] - '0') * 10 + + (src[6] - '0') - 1; + int day = (src[8] - '0') * 10 + + (src[9] - '0') - 1; + int hour = (src[11] - '0') * 10 + + (src[12] - '0'); + int minute = (src[14] - '0') * 10 + + (src[15] - '0'); + int second = (src[17] - '0') * 10 + + (src[18] - '0'); + + //optional fractals + int ms = 0; + if (src[19] == '.') { + if (isdigit(src[20]) || //1 + isdigit(src[21]) || //2 + isdigit(src[22])) { + + ms = (src[20] - '0') * 100 + + (src[21] - '0') * 10 + + (src[22] - '0'); + } else { + return false; + } + } + + if (year < 1970 || year >= 2038 || + month < 0 || month >= 12 || + day < 0 || day >= noDays(month, year) || + hour < 0 || hour >= 24 || + minute < 0 || minute >= 60 || + second < 0 || second > 60 || //tolerate leap seconds -- (23:59:60) can be a valid time + ms < 0 || ms >= 1000) { + return false; + } + + int32_t time = 0; + for (int y = 1970; y < year; y++) { + for (int m = 0; m < 12; m++) { + time += noDays(m, y) * 24 * 3600; + } + } + + for (int m = 0; m < month; m++) { + time += noDays(m, year) * 24 * 3600; + } + + time += day * 24 * 3600; + time += hour * 3600; + time += second; -Clock::Clock() { + if (time < MO_MIN_TIME || time > MO_MAX_TIME) { + MO_DBG_ERR("only accept time range from year 2010 to 2037"); + return false; + } + //success + dst.time = time; + dst.bootNr = unixTime.bootNr; //set bootNr to a defined value +#if MO_ENABLE_TIMESTAMP_MILLISECONDS + dst.ms = (uint16_t)ms; +#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS + return true; + } } -bool Clock::setTime(const char* jsonDateString) { +bool Clock::toUnixTime(const Timestamp& src, Timestamp& dst) const { + if (src.isUnixTime()) { + dst = src; + return true; + } + if (!src.isUptime() || !src.isDefined()) { + return false; + } - Timestamp timestamp = Timestamp(); - - if (!timestamp.setTime(jsonDateString)) { + if (!unixTime.isUnixTime()) { + //clock doesn't know unix time yet - no conversion is possible + return false; + } + + if (src.bootNr != uptime.bootNr) { + //src is from a previous power cycle - don't have a record of previous boot-time-unix-time offsets return false; } - system_basetime = mocpp_tick_ms(); - mocpp_basetime = timestamp; + int32_t deltaUptime; + if (!delta(src, uptime, deltaUptime)) { + return false; + } - currentTime = mocpp_basetime; - lastUpdate = system_basetime; + dst = unixTime; + if (!add(dst, deltaUptime)) { + return false; + } + //success return true; } -const Timestamp &Clock::now() { - auto tReading = mocpp_tick_ms(); - auto delta = tReading - lastUpdate; - -#if MO_ENABLE_TIMESTAMP_MILLISECONDS - currentTime.addMilliseconds(delta); - lastUpdate = tReading; -#else - auto deltaSecs = delta / 1000; - currentTime += deltaSecs; - lastUpdate += deltaSecs * 1000; -#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - - return currentTime; +bool Clock::toUnixTime(const Timestamp& src, int32_t& dst) const { + Timestamp t; + if (!toUnixTime(src, t)) { + return false; + } + dst = t.time; + return true; } -Timestamp Clock::adjustPrebootTimestamp(const Timestamp& t) { - auto systemtime_in = t - Timestamp(); - if (systemtime_in > (int) system_basetime / 1000) { - return mocpp_basetime; +bool Clock::fromUnixTime(Timestamp& dst, int32_t unixTimeInt) const { + + Timestamp t = unixTime; + t.time = unixTimeInt; + + if (!t.isUnixTime()) { + return false; } - return mocpp_basetime - ((int) (system_basetime / 1000) - systemtime_in); + + dst = t; + return true; } +uint16_t Clock::getBootNr() const { + return uptime.bootNr; } diff --git a/src/MicroOcpp/Core/Time.h b/src/MicroOcpp/Core/Time.h index 56dc9a8f..9e4363d4 100644 --- a/src/MicroOcpp/Core/Time.h +++ b/src/MicroOcpp/Core/Time.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_TIME_H @@ -22,93 +22,60 @@ #define JSONDATE_LENGTH 20 //ISO 8601 date length, excluding the terminating zero #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS +#define MO_JSONDATE_SIZE (JSONDATE_LENGTH + 1) +#define MO_INTERNALTIME_SIZE sizeof("i65535t-2147483648m999") //longest possible string: max bootNr, max negative time and milliseconds + +#define MO_MIN_TIME (((2010 - 1970) * 365 + 10) * 24 * 3600) // unix time for 2010-01-01Z00:00:00T (= 40 years + 10 leap days, in seconds) +#define MO_MAX_TIME (((2037 - 1970) * 365 + 17) * 24 * 3600) // unix time for 2037-01-01Z00:00:00T (= 67 years + 17 leap days, in seconds) + namespace MicroOcpp { +class Context; +class Clock; + class Timestamp : public MemoryManaged { private: - int32_t time; //Unix time (number of seconds since Jan 1, 1970 UTC, not counting leap seconds) + int32_t time = 0; //Unix time (number of seconds since Jan 1, 1970 UTC, not counting leap seconds) + uint16_t bootNr = 0; //bootNr when timestamp was taken #if MO_ENABLE_TIMESTAMP_MILLISECONDS - int16_t ms = 0; //fractional ms of timestamp. Compound timestamp = time + ms. Range should be [0, 999] + uint16_t ms = 0; //fractional ms of timestamp. Compound timestamp = time + ms. Range should be 0...999 #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS public: Timestamp(); - Timestamp(const Timestamp& other); -#if MO_ENABLE_TIMESTAMP_MILLISECONDS - Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second, int32_t ms = 0); -#else - Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second); -#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - - /** - * Expects a date string like - * 2020-10-01T20:53:32.486Z - * - * as generated in JavaScript by calling toJSON() on a Date object - * - * Only processes the first 19 characters. The subsequent are ignored until terminating 0. - * - * Has a semi-sophisticated type check included. Will return true on successful time set and false if - * the given string is not a JSON Date string. - * - * jsonDateString: 0-terminated string - */ - bool setTime(const char* jsonDateString); - - bool toJsonString(char *out, size_t buffsize) const; - - Timestamp &operator=(const Timestamp &rhs); - -#if MO_ENABLE_TIMESTAMP_MILLISECONDS - Timestamp &addMilliseconds(int ms); -#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS - - /* - * Time periods are given in seconds for all of the following arithmetic operations - */ - Timestamp &operator+=(int secs); - Timestamp &operator-=(int secs); - - int operator-(const Timestamp &rhs) const; - - operator int32_t() const; + bool isUnixTime() const; + bool isUptime() const; + bool isDefined() const; - friend Timestamp operator+(const Timestamp &lhs, int secs); - friend Timestamp operator-(const Timestamp &lhs, int secs); - - friend bool operator==(const Timestamp &lhs, const Timestamp &rhs); - friend bool operator!=(const Timestamp &lhs, const Timestamp &rhs); - friend bool operator<(const Timestamp &lhs, const Timestamp &rhs); - friend bool operator<=(const Timestamp &lhs, const Timestamp &rhs); - friend bool operator>(const Timestamp &lhs, const Timestamp &rhs); - friend bool operator>=(const Timestamp &lhs, const Timestamp &rhs); +friend class Clock; }; -extern const Timestamp MIN_TIME; -extern const Timestamp MAX_TIME; - class Clock { private: + Context& context; - Timestamp mocpp_basetime = Timestamp(); - decltype(mocpp_tick_ms()) system_basetime = 0; //the value of mocpp_tick_ms() when OCPP server's time was taken - decltype(mocpp_tick_ms()) lastUpdate = 0; + Timestamp unixTime; + Timestamp uptime; - Timestamp currentTime = Timestamp(); + unsigned long lastIncrement = 0; public: - Clock(); - Clock(const Clock&) = delete; - Clock(const Clock&&) = delete; - Clock& operator=(const Clock&) = delete; + Clock(Context& context); + + bool setup(); + + void loop(); const Timestamp &now(); + const Timestamp &getUptime(); + int32_t getUptimeInt(); + /** * Expects a date string like * 2020-10-01T20:53:32.486Z @@ -125,13 +92,60 @@ class Clock { bool setTime(const char* jsonDateString); /* - * Timestamps which were taken before the Clock was initially set can be adjusted retrospectively. Two - * conditions must be true: the Clock was set in the meantime and the Timestamp was taken at the same - * run of this library. The caller must check this + * Same as `setTime(const char* jsonDateString)`, but expects Unix time as seconds since 1970-01-01 + */ + bool setTime(int32_t unixTimeInt); + + /* + * Time delta between two timestamps. Calculates t2 - t1, writes the result in seconds into dt and + * returns true, if successful and false otherwise. The delta between two timestamps is defined if + * t1 and t2 have a unix time, or if the clock can determine their unix time after having connected + * to the OCPP server, or if both have a processor uptime. + */ + bool delta(const Timestamp& t2, const Timestamp& t1, int32_t& dt) const; + + /* + * Add secs seconds to `t`. After this operation, t will be later timestamp if secs is positive, or + * t will be an earlier timestamp if secs is negative. This operation is only valid if t remains in + * its numerical limits. I.e. if t is a processor uptime timestamp, then secs must not move it outside + * the uptime range and vice versa for unix timestamps. Returns true if successful, or false if + * operation is invalid or would overflow + */ + bool add(Timestamp& t, int32_t secs) const; + +#if MO_ENABLE_TIMESTAMP_MILLISECONDS + bool deltaMs(const Timestamp& t2, const Timestamp& t1, int32_t& dtMs) const; + bool addMs(Timestamp& t, int32_t ms) const; +#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS + + /* + * Print the ISO8601 represenation of t into dst, having `size` bytes. Fails if the unix time is + * not defined and cannot be determined by the clock. `size` must be at least MO_JSONDATE_SIZE + */ + bool toJsonString(const Timestamp& t, char *dst, size_t size) const; + + /* + * Print an internal representation of t into dst, having `size` bytes. `size` must be at least + * MO_INTERNALTIME_SIZE + */ + bool toInternalString(const Timestamp& t, char *dst, size_t size) const; + + /* + * Parses the time either from the ISO8601 representation, or the internal representation. Returns + * true on success and false on failure. */ - Timestamp adjustPrebootTimestamp(const Timestamp& t); + bool parseString(const char *src, Timestamp& dst) const; + + /* Converts src into a unix time and writes result to dst. dst will equal to src if src is already + * a unix time. Returns true if successful and false if the unix time cannot be determined */ + bool toUnixTime(const Timestamp& src, Timestamp& dst) const; + bool toUnixTime(const Timestamp& src, int32_t& dstInt) const; + + bool fromUnixTime(Timestamp& dst, int32_t unixTimeInt) const; + + uint16_t getBootNr() const; }; -} +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Core/UuidUtils.cpp b/src/MicroOcpp/Core/UuidUtils.cpp index 7d3ddb15..ca76aa1f 100644 --- a/src/MicroOcpp/Core/UuidUtils.cpp +++ b/src/MicroOcpp/Core/UuidUtils.cpp @@ -4,18 +4,21 @@ #include namespace MicroOcpp { +namespace UuidUtils { -#define UUID_STR_LEN 36 - -bool generateUUID(char *uuidBuffer, size_t len) { - if (len < UUID_STR_LEN + 1) +bool generateUUID(uint32_t (*rng)(), char *uuidBuffer, size_t size) { + if (!rng) { + return false; + } + + if (size < MO_UUID_STR_SIZE) { return false; } uint32_t ar[4]; for (uint8_t i = 0; i < 4; i++) { - ar[i] = mocpp_rng(); + ar[i] = rng(); } // Conforming to RFC 4122 Specification @@ -49,8 +52,9 @@ bool generateUUID(char *uuidBuffer, size_t len) { uuidBuffer[j++] = (ch < 10)? '0' + ch : ('a' - 10) + ch; } - uuidBuffer[UUID_STR_LEN] = 0; + uuidBuffer[MO_UUID_STR_SIZE - 1] = 0; return true; } -} +} //namespace UuidUtils +} //namespace MicroOcpp diff --git a/src/MicroOcpp/Core/UuidUtils.h b/src/MicroOcpp/Core/UuidUtils.h index 3516effe..4fb0d79b 100644 --- a/src/MicroOcpp/Core/UuidUtils.h +++ b/src/MicroOcpp/Core/UuidUtils.h @@ -1,14 +1,19 @@ #ifndef MO_UUIDUTILS_H #define MO_UUIDUTILS_H +#include #include + +#define MO_UUID_STR_SIZE (36 + 1) + namespace MicroOcpp { +namespace UuidUtils { // Generates a UUID (Universally Unique Identifier) and writes it into a given buffer // Returns false if the generation failed // The buffer must be at least 37 bytes long (36 characters + zero termination) -bool generateUUID(char *uuidBuffer, size_t len); - -} +bool generateUUID(uint32_t (*rng)(), char *uuidBuffer, size_t size); +} //namespace UuidUtils +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Debug.cpp b/src/MicroOcpp/Debug.cpp index 71f42343..5e9cdcd1 100644 --- a/src/MicroOcpp/Debug.cpp +++ b/src/MicroOcpp/Debug.cpp @@ -1,12 +1,15 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include +#include +#include #include -const char *level_label [] = { +namespace MicroOcpp { +const char *dbgLevelLabel [] = { "", //MO_DL_NONE 0x00 "ERROR", //MO_DL_ERROR 0x01 "warning", //MO_DL_WARN 0x02 @@ -15,40 +18,68 @@ const char *level_label [] = { "verbose" //MO_DL_VERBOSE 0x05 }; -#if MO_DBG_FORMAT == MO_DF_MINIMAL -void mo_dbg_print_prefix(int level, const char *fn, int line) { - (void)0; +Debug debug; +} //namespace MicroOcpp + +using namespace MicroOcpp; + +void Debug::setDebugCb(void (*debugCb)(const char *msg)) { + this->debugCb = debugCb; } -#elif MO_DBG_FORMAT == MO_DF_COMPACT -void mo_dbg_print_prefix(int level, const char *fn, int line) { - size_t l = strlen(fn); - size_t r = l; - while (l > 0 && fn[l-1] != '/' && fn[l-1] != '\\') { - l--; - if (fn[l] == '.') r = l; - } - MO_CONSOLE_PRINTF("%.*s:%i ", (int) (r - l), fn + l, line); +void Debug::setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)) { + this->debugCb2 = debugCb2; } -#elif MO_DBG_FORMAT == MO_DF_FILE_LINE -void mo_dbg_print_prefix(int level, const char *fn, int line) { - size_t l = strlen(fn); - while (l > 0 && fn[l-1] != '/' && fn[l-1] != '\\') { - l--; +void Debug::setDebugLevel(int dbgLevel) { + if (dbgLevel > MO_DBG_LEVEL) { + MO_DBG_ERR("Debug level limited to %s by build config", dbgLevelLabel[MO_DBG_LEVEL]); + dbgLevel = MO_DBG_LEVEL; } - MO_CONSOLE_PRINTF("[MO] %s (%s:%i): ", level_label[level], fn + l, line); + this->dbgLevel = dbgLevel; } -#elif MO_DBG_FORMAT == MO_DF_FULL -void mo_dbg_print_prefix(int level, const char *fn, int line) { - MO_CONSOLE_PRINTF("[MO] %s (%s:%i): ", level_label[level], fn, line); +bool Debug::setup() { + if (!debugCb && !debugCb2) { + // default-initialize console + debugCb = getDefaultDebugCb(); //defaultDebugCb is null on unsupported platforms. Just inconvenient, not a failure + } + return true; } -#else -#error invalid MO_DBG_FORMAT definition -#endif +void Debug::operator()(int lvl, const char *fn, int line, const char *format, ...) { + if (debugCb2) { + va_list args; + va_start(args, format); + auto ret = vsnprintf(buf, sizeof(buf), format, args); + if (ret < 0 || (size_t)ret >= sizeof(buf)) { + snprintf(buf + sizeof(buf) - sizeof(" [...]"), sizeof(buf), " [...]"); + } + va_end(args); + + debugCb2(lvl, fn, line, buf); + } else if (debugCb) { + size_t l = strlen(fn); + while (l > 0 && fn[l-1] != '/' && fn[l-1] != '\\') { + l--; + } + + auto ret = snprintf(buf, sizeof(buf), "[MO] %s (%s:%i): ", dbgLevelLabel[lvl], fn + l, line); + if (ret < 0 || (size_t)ret >= sizeof(buf)) { + snprintf(buf + sizeof(buf) - sizeof(" : "), sizeof(buf), " : "); + } -void mo_dbg_print_suffix() { - MO_CONSOLE_PRINTF("\n"); + debugCb(buf); + + va_list args; + va_start(args, format); + ret = vsnprintf(buf, sizeof(buf), format, args); + if (ret < 0 || (size_t)ret >= sizeof(buf)) { + snprintf(buf + sizeof(buf) - sizeof(" [...]"), sizeof(buf), " [...]"); + } + va_end(args); + + debugCb(buf); + debugCb(MO_DBG_ENDL); + } } diff --git a/src/MicroOcpp/Debug.h b/src/MicroOcpp/Debug.h index 51e71216..10d32e23 100644 --- a/src/MicroOcpp/Debug.h +++ b/src/MicroOcpp/Debug.h @@ -20,83 +20,76 @@ //MbedTLS debug level documented in mbedtls/debug.h: #ifndef MO_DBG_LEVEL_MBEDTLS -#define MO_DBG_LEVEL_MBEDTLS 1 +#define MO_DBG_LEVEL_MBEDTLS 1 //Error #endif -#define MO_DF_MINIMAL 0x00 //don't reveal origin of a debug message -#define MO_DF_COMPACT 0x01 //print module by file name and line number -#define MO_DF_FILE_LINE 0x02 //print file and line number -#define MO_DF_FULL 0x03 //print path and file and line numbr +#ifndef MO_DBG_ENDL +#define MO_DBG_ENDL "\n" +#endif -#ifndef MO_DBG_FORMAT -#define MO_DBG_FORMAT MO_DF_FILE_LINE //default +#ifndef MO_DBG_MAXMSGSIZE +#ifdef MO_CUSTOM_CONSOLE_MAXMSGSIZE +#define MO_DBG_MAXMSGSIZE MO_CUSTOM_CONSOLE_MAXMSGSIZE +#else +#define MO_DBG_MAXMSGSIZE 256 #endif +#endif //MO_DBG_MAXMSGSIZE #ifdef __cplusplus -extern "C" { -#endif -void mo_dbg_print_prefix(int level, const char *fn, int line); -void mo_dbg_print_suffix(); +namespace MicroOcpp { +class Debug { +private: + void (*debugCb)(const char *msg) = nullptr; + void (*debugCb2)(int lvl, const char *fn, int line, const char *msg) = nullptr; -#ifdef __cplusplus -} -#endif + char buf [MO_DBG_MAXMSGSIZE] = {'\0'}; + int dbgLevel = MO_DBG_LEVEL; +public: + Debug() = default; + + void setDebugCb(void (*debugCb)(const char *msg)); + void setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)); + + void setDebugLevel(int dbgLevel); + + bool setup(); -#define MO_DBG(level, X) \ - do { \ - mo_dbg_print_prefix(level, __FILE__, __LINE__); \ - MO_CONSOLE_PRINTF X; \ - mo_dbg_print_suffix(); \ - } while (0) + void operator()(int lvl, const char *fn, int line, const char *format, ...); +}; + +extern Debug debug; +} //namespace MicroOcpp +#endif //__cplusplus #if MO_DBG_LEVEL >= MO_DL_ERROR -#define MO_DBG_ERR(...) MO_DBG(MO_DL_ERROR,(__VA_ARGS__)) +#define MO_DBG_ERR(...) MicroOcpp::debug(MO_DL_ERROR, __FILE__, __LINE__, __VA_ARGS__) #else -#define MO_DBG_ERR(...) ((void)0) +#define MO_DBG_ERR(...) (void)0 #endif #if MO_DBG_LEVEL >= MO_DL_WARN -#define MO_DBG_WARN(...) MO_DBG(MO_DL_WARN,(__VA_ARGS__)) +#define MO_DBG_WARN(...) MicroOcpp::debug(MO_DL_WARN, __FILE__, __LINE__, __VA_ARGS__) #else -#define MO_DBG_WARN(...) ((void)0) +#define MO_DBG_WARN(...) (void)0 #endif #if MO_DBG_LEVEL >= MO_DL_INFO -#define MO_DBG_INFO(...) MO_DBG(MO_DL_INFO,(__VA_ARGS__)) +#define MO_DBG_INFO(...) MicroOcpp::debug(MO_DL_INFO, __FILE__, __LINE__, __VA_ARGS__) #else -#define MO_DBG_INFO(...) ((void)0) +#define MO_DBG_INFO(...) (void)0 #endif #if MO_DBG_LEVEL >= MO_DL_DEBUG -#define MO_DBG_DEBUG(...) MO_DBG(MO_DL_DEBUG,(__VA_ARGS__)) +#define MO_DBG_DEBUG(...) MicroOcpp::debug(MO_DL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) #else -#define MO_DBG_DEBUG(...) ((void)0) +#define MO_DBG_DEBUG(...) (void)0 #endif #if MO_DBG_LEVEL >= MO_DL_VERBOSE -#define MO_DBG_VERBOSE(...) MO_DBG(MO_DL_VERBOSE,(__VA_ARGS__)) -#else -#define MO_DBG_VERBOSE(...) ((void)0) -#endif - -#ifdef MO_TRAFFIC_OUT - -#define MO_DBG_TRAFFIC_OUT(...) \ - do { \ - MO_CONSOLE_PRINTF("[MO] Send: %s",__VA_ARGS__); \ - MO_CONSOLE_PRINTF("\n"); \ - } while (0) - -#define MO_DBG_TRAFFIC_IN(...) \ - do { \ - MO_CONSOLE_PRINTF("[MO] Recv: %.*s",__VA_ARGS__); \ - MO_CONSOLE_PRINTF("\n"); \ - } while (0) - +#define MO_DBG_VERBOSE(...) MicroOcpp::debug(MO_DL_VERBOSE, __FILE__, __LINE__, __VA_ARGS__) #else -#define MO_DBG_TRAFFIC_OUT(...) ((void)0) -#define MO_DBG_TRAFFIC_IN(...) ((void)0) +#define MO_DBG_VERBOSE(...) (void)0 #endif #endif diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp index 08cbfbca..bcf64bbd 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp @@ -2,14 +2,13 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_LOCAL_AUTH - #include #include +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH + using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; AuthorizationData::AuthorizationData() : MemoryManaged("v16.Authorization.AuthorizationData") { @@ -20,109 +19,130 @@ AuthorizationData::AuthorizationData(AuthorizationData&& other) : MemoryManaged( } AuthorizationData::~AuthorizationData() { - MO_FREE(parentIdTag); - parentIdTag = nullptr; + reset(); } AuthorizationData& AuthorizationData::operator=(AuthorizationData&& other) { parentIdTag = other.parentIdTag; other.parentIdTag = nullptr; - expiryDate = std::move(other.expiryDate); - strncpy(idTag, other.idTag, IDTAG_LEN_MAX + 1); - idTag[IDTAG_LEN_MAX] = '\0'; + expiryDate = other.expiryDate; + other.expiryDate = nullptr; + snprintf(idTag, sizeof(idTag), "%s", other.idTag); + memset(other.idTag, 0, sizeof(other.idTag)); status = other.status; return *this; } -void AuthorizationData::readJson(JsonObject entry, bool compact) { - if (entry.containsKey(AUTHDATA_KEY_IDTAG(compact))) { - strncpy(idTag, entry[AUTHDATA_KEY_IDTAG(compact)], IDTAG_LEN_MAX + 1); - idTag[IDTAG_LEN_MAX] = '\0'; +bool AuthorizationData::readJson(Clock& clock, JsonObject entry, bool internalFormat) { + if (entry.containsKey(AUTHDATA_KEY_IDTAG(internalFormat))) { + snprintf(idTag, sizeof(idTag), "%s", entry[AUTHDATA_KEY_IDTAG(internalFormat)].as()); } else { - idTag[0] = '\0'; + MO_DBG_ERR("format error"); + return false; } JsonObject idTagInfo; - if (compact){ + if (internalFormat){ idTagInfo = entry; } else { idTagInfo = entry[AUTHDATA_KEY_IDTAGINFO]; } - if (idTagInfo.containsKey(AUTHDATA_KEY_EXPIRYDATE(compact))) { - expiryDate = std::unique_ptr(new Timestamp()); - if (!expiryDate->setTime(idTagInfo[AUTHDATA_KEY_EXPIRYDATE(compact)])) { - expiryDate.reset(); + delete expiryDate; + expiryDate = nullptr; + + if (idTagInfo.containsKey(AUTHDATA_KEY_EXPIRYDATE(internalFormat))) { + expiryDate = new Timestamp(); + if (!expiryDate) { + MO_DBG_ERR("OOM"); + return false; + } + if (!clock.parseString(idTagInfo[AUTHDATA_KEY_EXPIRYDATE(internalFormat)], *expiryDate)) { + MO_DBG_ERR("format error"); + return false; } - } else { - expiryDate.reset(); } - if (idTagInfo.containsKey(AUTHDATA_KEY_PARENTIDTAG(compact))) { - MO_FREE(parentIdTag); - parentIdTag = nullptr; + MO_FREE(parentIdTag); + parentIdTag = nullptr; + + if (idTagInfo.containsKey(AUTHDATA_KEY_PARENTIDTAG(internalFormat))) { parentIdTag = static_cast(MO_MALLOC(getMemoryTag(), IDTAG_LEN_MAX + 1)); if (parentIdTag) { - strncpy(parentIdTag, idTagInfo[AUTHDATA_KEY_PARENTIDTAG(compact)], IDTAG_LEN_MAX + 1); - parentIdTag[IDTAG_LEN_MAX] = '\0'; + snprintf(parentIdTag, sizeof(parentIdTag), "%s", idTagInfo[AUTHDATA_KEY_PARENTIDTAG(internalFormat)].as()); } else { MO_DBG_ERR("OOM"); + return false; } - } else { - MO_FREE(parentIdTag); - parentIdTag = nullptr; } - if (idTagInfo.containsKey(AUTHDATA_KEY_STATUS(compact))) { - status = deserializeAuthorizationStatus(idTagInfo[AUTHDATA_KEY_STATUS(compact)]); + if (idTagInfo.containsKey(AUTHDATA_KEY_STATUS(internalFormat))) { + status = deserializeAuthorizationStatus(idTagInfo[AUTHDATA_KEY_STATUS(internalFormat)]); } else { - if (compact) { + if (internalFormat) { status = AuthorizationStatus::Accepted; } else { status = AuthorizationStatus::UNDEFINED; } } + + if (status == AuthorizationStatus::UNDEFINED) { + MO_DBG_ERR("format error"); + return false; + } + + return true; } -size_t AuthorizationData::getJsonCapacity() const { +size_t AuthorizationData::getJsonCapacity(bool internalFormat) const { return JSON_OBJECT_SIZE(2) + (idTag[0] != '\0' ? JSON_OBJECT_SIZE(1) : 0) + (expiryDate ? - JSON_OBJECT_SIZE(1) + JSONDATE_LENGTH + 1 : 0) + + JSON_OBJECT_SIZE(1) + + (internalFormat ? + MO_INTERNALTIME_SIZE : MO_JSONDATE_SIZE) + : 0) + (parentIdTag ? JSON_OBJECT_SIZE(1) : 0) + (status != AuthorizationStatus::UNDEFINED ? JSON_OBJECT_SIZE(1) : 0); } -void AuthorizationData::writeJson(JsonObject& entry, bool compact) { +void AuthorizationData::writeJson(Clock& clock, JsonObject& entry, bool internalFormat) { if (idTag[0] != '\0') { - entry[AUTHDATA_KEY_IDTAG(compact)] = (const char*) idTag; + entry[AUTHDATA_KEY_IDTAG(internalFormat)] = (const char*) idTag; } JsonObject idTagInfo; - if (compact) { + if (internalFormat) { idTagInfo = entry; } else { idTagInfo = entry.createNestedObject(AUTHDATA_KEY_IDTAGINFO); } if (expiryDate) { - char buf [JSONDATE_LENGTH + 1]; - if (expiryDate->toJsonString(buf, JSONDATE_LENGTH + 1)) { - idTagInfo[AUTHDATA_KEY_EXPIRYDATE(compact)] = buf; + if (internalFormat) { + char buf [MO_INTERNALTIME_SIZE]; + if (clock.toInternalString(*expiryDate, buf, sizeof(buf))) { + idTagInfo[AUTHDATA_KEY_EXPIRYDATE(internalFormat)] = buf; + } + } else { + char buf [MO_JSONDATE_SIZE]; + if (clock.toJsonString(*expiryDate, buf, sizeof(buf))) { + idTagInfo[AUTHDATA_KEY_EXPIRYDATE(internalFormat)] = buf; + } } } if (parentIdTag) { - idTagInfo[AUTHDATA_KEY_PARENTIDTAG(compact)] = (const char *) parentIdTag; + idTagInfo[AUTHDATA_KEY_PARENTIDTAG(internalFormat)] = (const char *) parentIdTag; } if (status != AuthorizationStatus::Accepted) { - idTagInfo[AUTHDATA_KEY_STATUS(compact)] = serializeAuthorizationStatus(status); - } else if (!compact) { - idTagInfo[AUTHDATA_KEY_STATUS(compact)] = serializeAuthorizationStatus(AuthorizationStatus::Invalid); + idTagInfo[AUTHDATA_KEY_STATUS(internalFormat)] = serializeAuthorizationStatus(status); + } else if (!internalFormat) { + idTagInfo[AUTHDATA_KEY_STATUS(internalFormat)] = serializeAuthorizationStatus(AuthorizationStatus::Invalid); } } @@ -130,7 +150,7 @@ const char *AuthorizationData::getIdTag() const { return idTag; } Timestamp *AuthorizationData::getExpiryDate() const { - return expiryDate.get(); + return expiryDate; } const char *AuthorizationData::getParentIdTag() const { return parentIdTag; @@ -140,10 +160,17 @@ AuthorizationStatus AuthorizationData::getAuthorizationStatus() const { } void AuthorizationData::reset() { + delete expiryDate; + expiryDate = nullptr; + MO_FREE(parentIdTag); + parentIdTag = nullptr; idTag[0] = '\0'; } -const char *MicroOcpp::serializeAuthorizationStatus(AuthorizationStatus status) { +namespace MicroOcpp { +namespace Ocpp16 { + +const char *serializeAuthorizationStatus(AuthorizationStatus status) { switch (status) { case (AuthorizationStatus::Accepted): return "Accepted"; @@ -160,7 +187,7 @@ const char *MicroOcpp::serializeAuthorizationStatus(AuthorizationStatus status) } } -MicroOcpp::AuthorizationStatus MicroOcpp::deserializeAuthorizationStatus(const char *cstr) { +AuthorizationStatus deserializeAuthorizationStatus(const char *cstr) { if (!cstr) { return AuthorizationStatus::UNDEFINED; } @@ -180,4 +207,6 @@ MicroOcpp::AuthorizationStatus MicroOcpp::deserializeAuthorizationStatus(const c } } -#endif //MO_ENABLE_LOCAL_AUTH +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.h b/src/MicroOcpp/Model/Authorization/AuthorizationData.h index 7bcdccd7..0174d91b 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.h +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.h @@ -5,17 +5,18 @@ #ifndef MO_AUTHORIZATIONDATA_H #define MO_AUTHORIZATIONDATA_H -#include - -#if MO_ENABLE_LOCAL_AUTH - #include #include #include +#include + #include #include +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH + namespace MicroOcpp { +namespace Ocpp16 { enum class AuthorizationStatus : uint8_t { Accepted, @@ -26,23 +27,19 @@ enum class AuthorizationStatus : uint8_t { UNDEFINED //not part of OCPP 1.6 }; -#define AUTHDATA_KEY_IDTAG(COMPACT) (COMPACT ? "it" : "idTag") -#define AUTHDATA_KEY_IDTAGINFO "idTagInfo" -#define AUTHDATA_KEY_EXPIRYDATE(COMPACT) (COMPACT ? "ed" : "expiryDate") -#define AUTHDATA_KEY_PARENTIDTAG(COMPACT) (COMPACT ? "pi" : "parentIdTag") -#define AUTHDATA_KEY_STATUS(COMPACT) (COMPACT ? "st" : "status") - -#define AUTHORIZATIONSTATUS_LEN_MAX (sizeof("ConcurrentTx") - 1) //max length of serialized AuthStatus +#define AUTHDATA_KEY_IDTAG(INTERNAL_FORMAT) (INTERNAL_FORMAT ? "it" : "idTag") +#define AUTHDATA_KEY_IDTAGINFO "idTagInfo" +#define AUTHDATA_KEY_EXPIRYDATE(INTERNAL_FORMAT) (INTERNAL_FORMAT ? "ed" : "expiryDate") +#define AUTHDATA_KEY_PARENTIDTAG(INTERNAL_FORMAT) (INTERNAL_FORMAT ? "pi" : "parentIdTag") +#define AUTHDATA_KEY_STATUS(INTERNAL_FORMAT) (INTERNAL_FORMAT ? "st" : "status") const char *serializeAuthorizationStatus(AuthorizationStatus status); AuthorizationStatus deserializeAuthorizationStatus(const char *cstr); class AuthorizationData : public MemoryManaged { private: - //data structure optimized for memory consumption - char *parentIdTag = nullptr; - std::unique_ptr expiryDate; + Timestamp *expiryDate = nullptr; //has ownership char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; @@ -54,10 +51,10 @@ class AuthorizationData : public MemoryManaged { AuthorizationData& operator=(AuthorizationData&& other); - void readJson(JsonObject entry, bool compact = false); //compact: compressed representation for flash storage + bool readJson(Clock& clock, JsonObject entry, bool internalFormat); //internalFormat: compressed representation for flash storage - size_t getJsonCapacity() const; - void writeJson(JsonObject& entry, bool compact = false); //compact: compressed representation for flash storage + size_t getJsonCapacity(bool internalFormat) const; + void writeJson(Clock& clock, JsonObject& entry, bool internalFormat); //internalFormat: compressed representation for flash storage const char *getIdTag() const; Timestamp *getExpiryDate() const; @@ -67,7 +64,7 @@ class AuthorizationData : public MemoryManaged { void reset(); }; -} - -#endif //MO_ENABLE_LOCAL_AUTH +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp index dd438ff8..1e7326d3 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp @@ -1,202 +1,301 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License -#include - -#if MO_ENABLE_LOCAL_AUTH - #include #include #include #include +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH + using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -AuthorizationList::AuthorizationList() : MemoryManaged("v16.Authorization.AuthorizationList"), localAuthorizationList(makeVector(getMemoryTag())) { +AuthorizationList::AuthorizationList() : MemoryManaged("v16.Authorization.AuthorizationList") { } AuthorizationList::~AuthorizationList() { - + clear(); } -MicroOcpp::AuthorizationData *AuthorizationList::get(const char *idTag) { - //binary search - +MicroOcpp::Ocpp16::AuthorizationData *AuthorizationList::get(const char *idTag) { + if (!idTag) { return nullptr; } - + + //binary search int l = 0; - int r = ((int) localAuthorizationList.size()) - 1; + int r = ((int) localAuthorizationListSize) - 1; while (l <= r) { auto m = (r + l) / 2; - auto diff = strcmp(localAuthorizationList[m].getIdTag(), idTag); + auto diff = strcmp(localAuthorizationList[m]->getIdTag(), idTag); if (diff < 0) { l = m + 1; } else if (diff > 0) { r = m - 1; } else { - return &localAuthorizationList[m]; + return localAuthorizationList[m]; } } return nullptr; } -bool AuthorizationList::readJson(JsonArray authlistJson, int listVersion, bool differential, bool compact) { +bool AuthorizationList::readJson(Clock& clock, JsonArray authlistJson, int listVersion, bool differential, bool internalFormat) { - if (compact) { + if (internalFormat) { //compact representations don't contain remove commands differential = false; } - for (size_t i = 0; i < authlistJson.size(); i++) { - - //check if JSON object is valid - if (!authlistJson[i].as().containsKey(AUTHDATA_KEY_IDTAG(compact))) { - return false; - } - } - - auto authlist_index = makeVector(getMemoryTag()); - auto remove_list = makeVector(getMemoryTag()); - - unsigned int resultingListLength = 0; + size_t resListSize = 0; + size_t updateSize = 0; if (!differential) { //every entry will insert an idTag - resultingListLength = authlistJson.size(); + resListSize = authlistJson.size(); + updateSize = authlistJson.size(); } else { //update type is differential; only unkown entries will insert an idTag - resultingListLength = localAuthorizationList.size(); - - //also, build index here - authlist_index.resize(authlistJson.size(), -1); + resListSize = localAuthorizationListSize; for (size_t i = 0; i < authlistJson.size(); i++) { //check if locally stored auth info is present; if yes, apply it to the index - AuthorizationData *found = get(authlistJson[i][AUTHDATA_KEY_IDTAG(compact)]); + AuthorizationData *found = get(authlistJson[i][AUTHDATA_KEY_IDTAG(internalFormat)]); if (found) { - authlist_index[i] = (int) (found - localAuthorizationList.data()); - //remove or update? if (!authlistJson[i].as().containsKey(AUTHDATA_KEY_IDTAGINFO)) { //this entry should be removed - found->reset(); //mark for deletion - remove_list.push_back((int) (found - localAuthorizationList.data())); - resultingListLength--; - } //else: this entry should be updated + resListSize--; + } else { + //this entry should be updated + updateSize++; + } } else { //insert or ignore? if (authlistJson[i].as().containsKey(AUTHDATA_KEY_IDTAGINFO)) { //add - resultingListLength++; - } //else: ignore + resListSize++; + updateSize++; + } else { + // invalid record + return false; + } } } } - if (resultingListLength > MO_LocalAuthListMaxLength) { + if (resListSize > MO_LocalAuthListMaxLength) { MO_DBG_WARN("localAuthList capacity exceeded"); return false; } - //apply new list + + AuthorizationData **updateList = nullptr; //list of newly allocated entries + AuthorizationData **resList = nullptr; //resulting list after list update. Contains pointers to old auth list and updateList - if (compact) { - localAuthorizationList.clear(); + size_t updateWritten = 0; + size_t resWritten = 0; - for (size_t i = 0; i < authlistJson.size(); i++) { - localAuthorizationList.emplace_back(); - localAuthorizationList.back().readJson(authlistJson[i], compact); + if (updateSize > 0) { + updateList = static_cast(MO_MALLOC(getMemoryTag(), sizeof(AuthorizationData*) * updateSize)); + if (!updateList) { + MO_DBG_ERR("OOM"); + goto fail; } - } else if (differential) { - - for (size_t i = 0; i < authlistJson.size(); i++) { + memset(updateList, 0, sizeof(AuthorizationData*) * updateSize); + } - //is entry a remove command? - if (!authlistJson[i].as().containsKey(AUTHDATA_KEY_IDTAGINFO)) { - continue; //yes, remove command, will be deleted afterwards - } + if (resListSize > 0) { + resList = static_cast(MO_MALLOC(getMemoryTag(), sizeof(AuthorizationData*) * resListSize)); + if (!resList) { + MO_DBG_ERR("OOM"); + goto fail; + } + memset(resList, 0, sizeof(AuthorizationData*) * resListSize); + } - //update, or insert + // Keep, update, or remove old entries + if (differential) { + for (size_t i = 0; i < localAuthorizationListSize; i++) { - if (authlist_index[i] < 0) { - //auth list does not contain idTag yet -> insert new entry + bool remove = false; + bool update = false; - //reuse removed AuthData object? - if (!remove_list.empty()) { - //yes, reuse - authlist_index[i] = remove_list.back(); - remove_list.pop_back(); + for (size_t j = 0; j < authlistJson.size(); j++) { + //remove or update? + const char *key_i = localAuthorizationList[i]->getIdTag(); + const char *key_j = authlistJson[j][AUTHDATA_KEY_IDTAG(false)] | ""; + if (!*key_i || !*key_j || strcmp(key_i, key_j)) { + // Keys don't match + continue; + } + if (authlistJson[j].containsKey(AUTHDATA_KEY_IDTAGINFO)) { + //this entry should be updated + update = true; + break; } else { - //no, create new - authlist_index[i] = localAuthorizationList.size(); - localAuthorizationList.emplace_back(); + //this entry should be removed + remove = true; + break; } } - localAuthorizationList[authlist_index[i]].readJson(authlistJson[i], compact); + if (remove) { + // resList won't include this entry + (void)0; + } else if (update) { + updateList[updateWritten] = new AuthorizationData(); + if (!updateList[updateWritten]) { + MO_DBG_ERR("OOM"); + goto fail; + } + if (!updateList[updateWritten]->readJson(clock, authlistJson[i], internalFormat)) { + MO_DBG_ERR("format error"); + goto fail; + } + resList[resWritten] = updateList[updateWritten]; + updateWritten++; + resWritten++; + } else { + resList[resWritten] = localAuthorizationList[i]; + resWritten++; + } } + } - } else { - localAuthorizationList.clear(); + // Insert new entries + for (size_t i = 0; i < authlistJson.size(); i++) { - for (size_t i = 0; i < authlistJson.size(); i++) { - if (authlistJson[i].as().containsKey(AUTHDATA_KEY_IDTAGINFO)) { - localAuthorizationList.emplace_back(); - localAuthorizationList.back().readJson(authlistJson[i], compact); - } + if (!authlistJson[i].containsKey(AUTHDATA_KEY_IDTAGINFO)) { + // remove already handled above + continue; } - } - localAuthorizationList.erase(std::remove_if(localAuthorizationList.begin(), localAuthorizationList.end(), - [] (const AuthorizationData& elem) { - return elem.getIdTag()[0] == '\0'; //"" means no idTag --> marked for removal - }), localAuthorizationList.end()); + bool insert = true; + + const char *key_i = authlistJson[i][AUTHDATA_KEY_IDTAG(false)] | ""; + + for (size_t j = 0; j < resWritten; j++) { + //insert? + const char *key_j = resList[j]->getIdTag(); + if (*key_i && key_j && !strcmp(key_i, key_j)) { + // Keys match, this entry was updated above, and should not be inserted + insert = false; + break; + } + } - std::sort(localAuthorizationList.begin(), localAuthorizationList.end(), - [] (const AuthorizationData& lhs, const AuthorizationData& rhs) { - return strcmp(lhs.getIdTag(), rhs.getIdTag()) < 0; - }); + if (insert) { + updateList[updateWritten] = new AuthorizationData(); + if (!updateList[updateWritten]) { + MO_DBG_ERR("OOM"); + goto fail; + } + if (!updateList[updateWritten]->readJson(clock, authlistJson[i], internalFormat)) { + MO_DBG_ERR("format error"); + goto fail; + } + resList[resWritten] = updateList[updateWritten]; + updateWritten++; + resWritten++; + } + } + qsort(resList, resListSize, sizeof(resList[0]), + [] (const void* a,const void* b) -> int { + return strcmp( + reinterpret_cast(a)->getIdTag(), + reinterpret_cast(b)->getIdTag()); + }); + + // success + this->listVersion = listVersion; - if (localAuthorizationList.empty()) { + if (resListSize == 0) { this->listVersion = 0; } + for (size_t i = 0; i < localAuthorizationListSize; i++) { + bool found = false; + for (size_t j = 0; j < resListSize; j++) { + if (localAuthorizationList[i] == resList[j]) { + found = true; + break; + } + } + + if (!found) { + //entry not used anymore + delete localAuthorizationList[i]; + localAuthorizationList[i] = nullptr; + } + } + MO_FREE(localAuthorizationList); + localAuthorizationList = nullptr; + localAuthorizationListSize = 0; + + localAuthorizationList = resList; + localAuthorizationListSize = resListSize; + + MO_FREE(updateList); + updateList = nullptr; + updateSize = 0; + return true; +fail: + if (updateList) { + for (size_t i = 0; i < updateSize; i++) { + delete updateList[i]; + updateList[i] = nullptr; + } + } + MO_FREE(updateList); + updateList = nullptr; + + MO_FREE(resList); + resList = nullptr; + resListSize = 0; + + return false; } void AuthorizationList::clear() { - localAuthorizationList.clear(); + for (size_t i = 0; i < localAuthorizationListSize; i++) { + delete localAuthorizationList[i]; + localAuthorizationList[i] = nullptr; + } + MO_FREE(localAuthorizationList); + localAuthorizationList = nullptr; + localAuthorizationListSize = 0; listVersion = 0; } -size_t AuthorizationList::getJsonCapacity() { - size_t res = JSON_ARRAY_SIZE(localAuthorizationList.size()); - for (auto& entry : localAuthorizationList) { - res += entry.getJsonCapacity(); +size_t AuthorizationList::getJsonCapacity(bool internalFormat) { + size_t res = JSON_ARRAY_SIZE(localAuthorizationListSize); + for (size_t i = 0; i < localAuthorizationListSize; i++) { + res += localAuthorizationList[i]->getJsonCapacity(internalFormat); } return res; } -void AuthorizationList::writeJson(JsonArray authListOut, bool compact) { - for (auto& entry : localAuthorizationList) { +void AuthorizationList::writeJson(Clock& clock, JsonArray authListOut, bool internalFormat) { + for (size_t i = 0; i < localAuthorizationListSize; i++) { JsonObject entryJson = authListOut.createNestedObject(); - entry.writeJson(entryJson, compact); + localAuthorizationList[i]->writeJson(clock, entryJson, internalFormat); } } size_t AuthorizationList::size() { - return localAuthorizationList.size(); + return localAuthorizationListSize; } -#endif //MO_ENABLE_LOCAL_AUTH +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationList.h b/src/MicroOcpp/Model/Authorization/AuthorizationList.h index 0a083777..762f8221 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationList.h +++ b/src/MicroOcpp/Model/Authorization/AuthorizationList.h @@ -1,16 +1,15 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_AUTHORIZATIONLIST_H #define MO_AUTHORIZATIONLIST_H -#include - -#if MO_ENABLE_LOCAL_AUTH - #include #include +#include + +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #ifndef MO_LocalAuthListMaxLength #define MO_LocalAuthListMaxLength 48 @@ -22,28 +21,33 @@ namespace MicroOcpp { +class Clock; + +namespace Ocpp16 { + class AuthorizationList : public MemoryManaged { private: int listVersion = 0; - Vector localAuthorizationList; //sorted list + AuthorizationData **localAuthorizationList = nullptr; //sorted array + size_t localAuthorizationListSize = 0; public: AuthorizationList(); ~AuthorizationList(); AuthorizationData *get(const char *idTag); - bool readJson(JsonArray localAuthorizationList, int listVersion, bool differential = false, bool compact = false); //compact: if true, then use compact non-ocpp representation + bool readJson(Clock& clock, JsonArray localAuthorizationList, int listVersion, bool differential, bool internalFormat); //internalFormat: if true, then use compact non-ocpp representation void clear(); - size_t getJsonCapacity(); - void writeJson(JsonArray authListOut, bool compact = false); + size_t getJsonCapacity(bool internalFormat); + void writeJson(Clock& clock, JsonArray authListOut, bool internalFormat); int getListVersion() {return listVersion;} size_t size(); //used in unit tests }; -} - -#endif //MO_ENABLE_LOCAL_AUTH +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp index e9dc39d2..38c63306 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp @@ -2,46 +2,54 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_LOCAL_AUTH - #include -#include +#include +#include #include -#include +#include #include -#include #include #include #include #include #include -#define MO_LOCALAUTHORIZATIONLIST_FN (MO_FILENAME_PREFIX "localauth.jsn") +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH + +#define MO_LOCALAUTHORIZATIONLIST_FN "localauth.jsn" using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -AuthorizationService::AuthorizationService(Context& context, std::shared_ptr filesystem) : MemoryManaged("v16.Authorization.AuthorizationService"), context(context), filesystem(filesystem) { +AuthorizationService::AuthorizationService(Context& context) : MemoryManaged("v16.Authorization.AuthorizationService"), context(context) { - localAuthListEnabledBool = declareConfiguration("LocalAuthListEnabled", true); - declareConfiguration("LocalAuthListMaxLength", MO_LocalAuthListMaxLength, CONFIGURATION_VOLATILE, true); - declareConfiguration("SendLocalListMaxLength", MO_SendLocalListMaxLength, CONFIGURATION_VOLATILE, true); +} +bool AuthorizationService::setup() { + + filesystem = context.getFilesystem(); + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("initialization error"); + return false; + } + + localAuthListEnabledBool = configService->declareConfiguration("LocalAuthListEnabled", true); if (!localAuthListEnabledBool) { MO_DBG_ERR("initialization error"); + return false; } - - context.getOperationRegistry().registerOperation("GetLocalListVersion", [&context] () { - return new Ocpp16::GetLocalListVersion(context.getModel());}); - context.getOperationRegistry().registerOperation("SendLocalList", [this] () { - return new Ocpp16::SendLocalList(*this);}); - loadLists(); -} + configService->declareConfiguration("LocalAuthListMaxLength", MO_LocalAuthListMaxLength, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + configService->declareConfiguration("SendLocalListMaxLength", MO_SendLocalListMaxLength, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); -AuthorizationService::~AuthorizationService() { - + context.getMessageService().registerOperation("GetLocalListVersion", [] (Context& context) -> Operation* { + return new GetLocalListVersion(context.getModel16());}); + context.getMessageService().registerOperation("SendLocalList", [] (Context& context) -> Operation* { + return new SendLocalList(*context.getModel16().getAuthorizationService());}); + + return loadLists(); } bool AuthorizationService::loadLists() { @@ -50,23 +58,26 @@ bool AuthorizationService::loadLists() { return true; } - size_t msize = 0; - if (filesystem->stat(MO_LOCALAUTHORIZATIONLIST_FN, &msize) != 0) { - MO_DBG_DEBUG("no local authorization list stored already"); - return true; - } - - auto doc = FilesystemUtils::loadJson(filesystem, MO_LOCALAUTHORIZATIONLIST_FN, getMemoryTag()); - if (!doc) { - MO_DBG_ERR("failed to load %s", MO_LOCALAUTHORIZATIONLIST_FN); - return false; - } - - JsonObject root = doc->as(); - - int listVersion = root["listVersion"] | 0; + JsonDoc doc (0); + auto ret = FilesystemUtils::loadJson(filesystem, MO_LOCALAUTHORIZATIONLIST_FN, doc, getMemoryTag()); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + MO_DBG_DEBUG("no local authorization list stored already"); + return true; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", MO_LOCALAUTHORIZATIONLIST_FN); + return false; + } + + int listVersion = doc["listVersion"] | 0; - if (!localAuthorizationList.readJson(root["localAuthorizationList"].as(), listVersion, false, true)) { + if (!localAuthorizationList.readJson(context.getClock(), doc["localAuthorizationList"].as(), listVersion, /*differential*/ false, /*internalFormat*/ true)) { MO_DBG_ERR("list read failure"); return false; } @@ -102,19 +113,19 @@ size_t AuthorizationService::getLocalListSize() { } bool AuthorizationService::updateLocalList(JsonArray localAuthorizationListJson, int listVersion, bool differential) { - bool success = localAuthorizationList.readJson(localAuthorizationListJson, listVersion, differential, false); + bool success = localAuthorizationList.readJson(context.getClock(), localAuthorizationListJson, listVersion, differential, /*internalFormat*/ false); + + if (success && filesystem) { - if (success) { - auto doc = initJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(3) + - localAuthorizationList.getJsonCapacity()); + localAuthorizationList.getJsonCapacity(/*internalFormat*/ true)); JsonObject root = doc.to(); root["listVersion"] = listVersion; JsonArray authListCompact = root.createNestedArray("localAuthorizationList"); - localAuthorizationList.writeJson(authListCompact, true); - success = FilesystemUtils::storeJson(filesystem, MO_LOCALAUTHORIZATIONLIST_FN, doc); + localAuthorizationList.writeJson(context.getClock(), authListCompact, /*internalFormat*/ true); + success = (FilesystemUtils::storeJson(filesystem, MO_LOCALAUTHORIZATIONLIST_FN, doc) == FilesystemUtils::StoreStatus::Success); if (!success) { loadLists(); @@ -154,10 +165,15 @@ void AuthorizationService::notifyAuthorization(const char *idTag, JsonObject idT } if (localStatus == AuthorizationStatus::Accepted && localInfo->getExpiryDate()) { //check for expiry - auto& t_now = context.getModel().getClock().now(); - if (t_now > *localInfo->getExpiryDate()) { - MO_DBG_DEBUG("local auth expired"); - localStatus = AuthorizationStatus::Expired; + int32_t dtExpiryDate; + if (context.getClock().delta(context.getClock().now(), *localInfo->getExpiryDate(), dtExpiryDate)) { + if (dtExpiryDate > 0) { + //now is past expiryDate + MO_DBG_DEBUG("local auth expired"); + localStatus = AuthorizationStatus::Expired; + } + } else { + MO_DBG_ERR("cannot determine local auth expiry"); } } @@ -180,21 +196,25 @@ void AuthorizationService::notifyAuthorization(const char *idTag, JsonObject idT if (!equivalent) { //send error code "LocalListConflict" to server - ChargePointStatus cpStatus = ChargePointStatus_UNDEFINED; - if (context.getModel().getNumConnectors() > 0) { - cpStatus = context.getModel().getConnector(0)->getStatus(); - } + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcCp = availSvc ? availSvc->getEvse(0) : nullptr; + auto cpStatus = availSvcCp ? availSvcCp->getStatus() : MO_ChargePointStatus_UNDEFINED; + + MO_ErrorData errorCode; + mo_ErrorData_init(&errorCode); + mo_ErrorData_setErrorCode(&errorCode, "LocalListConflict"); - auto statusNotification = makeRequest(new Ocpp16::StatusNotification( + auto statusNotification = makeRequest(context, new StatusNotification( + context, 0, - cpStatus, //will be determined in StatusNotification::initiate - context.getModel().getClock().now(), - "LocalListConflict")); + cpStatus, + context.getClock().now(), + errorCode)); - statusNotification->setTimeout(60000); + statusNotification->setTimeout(60); - context.initiateRequest(std::move(statusNotification)); + context.getMessageService().sendRequest(std::move(statusNotification)); } } -#endif //MO_ENABLE_LOCAL_AUTH +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationService.h b/src/MicroOcpp/Model/Authorization/AuthorizationService.h index 88ba7936..d97b754d 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationService.h +++ b/src/MicroOcpp/Model/Authorization/AuthorizationService.h @@ -5,30 +5,33 @@ #ifndef MO_AUTHORIZATIONSERVICE_H #define MO_AUTHORIZATIONSERVICE_H -#include - -#if MO_ENABLE_LOCAL_AUTH - #include #include -#include #include +#include + +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH namespace MicroOcpp { class Context; +namespace Ocpp16 { + +class Configuration; + class AuthorizationService : public MemoryManaged { private: Context& context; - std::shared_ptr filesystem; + MO_FilesystemAdapter *filesystem = nullptr; AuthorizationList localAuthorizationList; - std::shared_ptr localAuthListEnabledBool; + Configuration *localAuthListEnabledBool = nullptr; public: - AuthorizationService(Context& context, std::shared_ptr filesystem); - ~AuthorizationService(); + AuthorizationService(Context& context); + + bool setup(); bool loadLists(); @@ -42,7 +45,7 @@ class AuthorizationService : public MemoryManaged { void notifyAuthorization(const char *idTag, JsonObject idTagInfo); }; -} - -#endif //MO_ENABLE_LOCAL_AUTH +} //namespace MicroOcpp +} //namespace Ocpp16 +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp index e1637ef8..512f2220 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.cpp +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -1,21 +1,18 @@ // matth-x/MicroOcpp // Copyright Matthias Akstaller 2019 - 2024 // MIT License - -#include - -#if MO_ENABLE_V201 - #include +#include #include #include -#include +#if MO_ENABLE_V201 using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; -IdToken::IdToken(const char *token, Type type, const char *memoryTag) : MemoryManaged(memoryTag ? memoryTag : "v201.Authorization.IdToken"), type(type) { +IdToken::IdToken(const char *token, MO_IdTokenType type, const char *memoryTag) : MemoryManaged(memoryTag ? memoryTag : "v201.Authorization.IdToken"), type(type) { if (token) { auto ret = snprintf(idToken, MO_IDTOKEN_LEN_MAX + 1, "%s", token); if (ret < 0 || ret >= MO_IDTOKEN_LEN_MAX + 1) { @@ -37,21 +34,21 @@ bool IdToken::parseCstr(const char *token, const char *typeCstr) { } if (!strcmp(typeCstr, "Central")) { - type = Type::Central; + type = MO_IdTokenType_Central; } else if (!strcmp(typeCstr, "eMAID")) { - type = Type::eMAID; + type = MO_IdTokenType_eMAID; } else if (!strcmp(typeCstr, "ISO14443")) { - type = Type::ISO14443; + type = MO_IdTokenType_ISO14443; } else if (!strcmp(typeCstr, "ISO15693")) { - type = Type::ISO15693; + type = MO_IdTokenType_ISO15693; } else if (!strcmp(typeCstr, "KeyCode")) { - type = Type::KeyCode; + type = MO_IdTokenType_KeyCode; } else if (!strcmp(typeCstr, "Local")) { - type = Type::Local; + type = MO_IdTokenType_Local; } else if (!strcmp(typeCstr, "MacAddress")) { - type = Type::MacAddress; + type = MO_IdTokenType_MacAddress; } else if (!strcmp(typeCstr, "NoAuthorization")) { - type = Type::NoAuthorization; + type = MO_IdTokenType_NoAuthorization; } else { return false; } @@ -71,31 +68,31 @@ const char *IdToken::get() const { const char *IdToken::getTypeCstr() const { const char *res = ""; switch (type) { - case Type::UNDEFINED: + case MO_IdTokenType_UNDEFINED: MO_DBG_ERR("internal error"); break; - case Type::Central: + case MO_IdTokenType_Central: res = "Central"; break; - case Type::eMAID: + case MO_IdTokenType_eMAID: res = "eMAID"; break; - case Type::ISO14443: + case MO_IdTokenType_ISO14443: res = "ISO14443"; break; - case Type::ISO15693: + case MO_IdTokenType_ISO15693: res = "ISO15693"; break; - case Type::KeyCode: + case MO_IdTokenType_KeyCode: res = "KeyCode"; break; - case Type::Local: + case MO_IdTokenType_Local: res = "Local"; break; - case Type::MacAddress: + case MO_IdTokenType_MacAddress: res = "MacAddress"; break; - case Type::NoAuthorization: + case MO_IdTokenType_NoAuthorization: res = "NoAuthorization"; break; } @@ -107,4 +104,4 @@ bool IdToken::equals(const IdToken& other) { return type == other.type && !strcmp(idToken, other.idToken); } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h index cb209872..907f5606 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.h +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -5,40 +5,45 @@ #ifndef MO_IDTOKEN_H #define MO_IDTOKEN_H +#include + +#include #include #if MO_ENABLE_V201 -#include +#ifdef __cplusplus +extern "C" { +#endif -#include +// IdTokenEnumType (3.43) +typedef enum { + MO_IdTokenType_UNDEFINED, + MO_IdTokenType_Central, + MO_IdTokenType_eMAID, + MO_IdTokenType_ISO14443, + MO_IdTokenType_ISO15693, + MO_IdTokenType_KeyCode, + MO_IdTokenType_Local, + MO_IdTokenType_MacAddress, + MO_IdTokenType_NoAuthorization, +} MO_IdTokenType; + +#ifdef __cplusplus +} //extern "C" #define MO_IDTOKEN_LEN_MAX 36 namespace MicroOcpp { +namespace Ocpp201 { // IdTokenType (2.28) class IdToken : public MemoryManaged { -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; + MO_IdTokenType type = MO_IdTokenType_UNDEFINED; public: - IdToken(const char *token = nullptr, Type type = Type::ISO14443, const char *memoryTag = nullptr); + IdToken(const char *token = nullptr, MO_IdTokenType type = MO_IdTokenType_ISO14443, const char *memoryTag = nullptr); IdToken(const IdToken& other, const char *memoryTag = nullptr); @@ -50,7 +55,8 @@ class IdToken : public MemoryManaged { bool equals(const IdToken& other); }; -} // namespace MicroOcpp - -#endif // MO_ENABLE_V201 +} //namespace MicroOcpp +} //namespace Ocpp201 +#endif //__cplusplus +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp b/src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp new file mode 100644 index 00000000..17bcf6ae --- /dev/null +++ b/src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp @@ -0,0 +1,74 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#include + +#if MO_ENABLE_V16 + +void mo_ErrorData_init(MO_ErrorData *errorData) { + memset(errorData, 0, sizeof(*errorData)); + errorData->severity = 1; +} + +void mo_ErrorData_setErrorCode(MO_ErrorData *errorData, const char *errorCode) { + errorData->errorCode = errorCode; + if (errorCode) { + errorData->isError = true; + errorData->isFaulted = true; + } +} + +void mo_ErrorDataInput_init(MO_ErrorDataInput *errorDataInput) { + memset(errorDataInput, 0, sizeof(*errorDataInput)); +} + +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V16 || MO_ENABLE_V201 + +const char *mo_serializeChargePointStatus(MO_ChargePointStatus v) { + const char *res = ""; + switch (v) { + case (MO_ChargePointStatus_UNDEFINED): + res = ""; + break; + case (MO_ChargePointStatus_Available): + res = "Available"; + break; + case (MO_ChargePointStatus_Preparing): + res = "Preparing"; + break; + case (MO_ChargePointStatus_Charging): + res = "Charging"; + break; + case (MO_ChargePointStatus_SuspendedEVSE): + res = "SuspendedEVSE"; + break; + case (MO_ChargePointStatus_SuspendedEV): + res = "SuspendedEV"; + break; + case (MO_ChargePointStatus_Finishing): + res = "Finishing"; + break; + case (MO_ChargePointStatus_Reserved): + res = "Reserved"; + break; + case (MO_ChargePointStatus_Unavailable): + res = "Unavailable"; + break; + case (MO_ChargePointStatus_Faulted): + res = "Faulted"; + break; +#if MO_ENABLE_V201 + case (MO_ChargePointStatus_Occupied): + res = "Occupied"; + break; +#endif + } + return res; +} + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Availability/AvailabilityDefs.h b/src/MicroOcpp/Model/Availability/AvailabilityDefs.h new file mode 100644 index 00000000..179c26f5 --- /dev/null +++ b/src/MicroOcpp/Model/Availability/AvailabilityDefs.h @@ -0,0 +1,81 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_AVAILABILITYDEFS_H +#define MO_AVAILABILITYDEFS_H + +#include + +#include + +#if MO_ENABLE_V16 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool isError; //if any error information is set + bool isFaulted; //if this is a severe error and the EVSE should go into the faulted state + uint8_t severity; //severity: don't send less severe errors during highly severe error condition + const char *errorCode; //see ChargePointErrorCode (p. 76/77) for possible values + const char *info; //Additional free format information related to the error + const char *vendorId; //vendor-specific implementation identifier + const char *vendorErrorCode; //vendor-specific error code +} MO_ErrorData; + +void mo_ErrorData_init(MO_ErrorData *errorData); +void mo_ErrorData_setErrorCode(MO_ErrorData *errorData, const char *errorCode); + +typedef struct { + MO_ErrorData (*getErrorData)(unsigned int evseId, void *userData); + void *userData; +} MO_ErrorDataInput; + +void mo_ErrorDataInput_init(MO_ErrorDataInput *errorDataInput); + +#ifdef __cplusplus +} //extern "C" +#endif + +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V16 || MO_ENABLE_V201 +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +typedef enum MO_ChargePointStatus { + MO_ChargePointStatus_UNDEFINED, //internal use only - no OCPP standard value + MO_ChargePointStatus_Available, + MO_ChargePointStatus_Preparing, + MO_ChargePointStatus_Charging, + MO_ChargePointStatus_SuspendedEVSE, + MO_ChargePointStatus_SuspendedEV, + MO_ChargePointStatus_Finishing, + MO_ChargePointStatus_Reserved, + MO_ChargePointStatus_Unavailable, + MO_ChargePointStatus_Faulted, +#if MO_ENABLE_V201 + MO_ChargePointStatus_Occupied, +#endif +} MO_ChargePointStatus; + +const char *mo_serializeChargePointStatus(MO_ChargePointStatus v); + +#ifdef __cplusplus +} //extern "C" + +namespace MicroOcpp { + +enum class ChangeAvailabilityStatus : uint8_t { + Accepted, + Rejected, + Scheduled +}; + +} //namespace MicroOcpp +#endif //__cplusplus +#endif //MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index b1e59419..01430ca3 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -2,74 +2,479 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include -#include +#include #include #include +#include +#include #include -#include +#include +#include #include #include +#if MO_ENABLE_V16 + using namespace MicroOcpp; -AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availabilityService, unsigned int evseId) : MemoryManaged("v201.Availability.AvailabilityServiceEvse"), context(context), availabilityService(availabilityService), evseId(evseId) { +Ocpp16::AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId) : MemoryManaged("v16.Availability.AvailabilityServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), availService(availService), evseId(evseId) { + +} + +Ocpp16::AvailabilityServiceEvse::~AvailabilityServiceEvse() { + if (availabilityBool->getKey() == availabilityBoolKey) { + availabilityBool->setKey(nullptr); + } +} + + +bool Ocpp16::AvailabilityServiceEvse::setup() { + + connection = context.getConnection(); + if (!connection) { + MO_DBG_ERR("setup failure"); + return false; + } + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + snprintf(availabilityBoolKey, sizeof(availabilityBoolKey), MO_CONFIG_EXT_PREFIX "AVAIL_CONN_%d", evseId); + availabilityBool = configService->declareConfiguration(availabilityBoolKey, true, MO_KEYVALUE_FN, Mutability::None); + if (!availabilityBool) { + MO_DBG_ERR("setup failure"); + return false; + } + + availabilityContainer = configService->findContainerOfConfiguration(availabilityBool); + if (!availabilityContainer) { + MO_DBG_ERR("setup failure"); + return false; + } + + return true; +} + +void Ocpp16::AvailabilityServiceEvse::loop() { + + if (!trackLoopExecute) { + trackLoopExecute = true; + } + + MO_ErrorData errorData; + mo_ErrorData_init(&errorData); + errorData.severity = 0; + int errorDataIndex = -1; + + if (clock.now().isUnixTime()) { + + if (reportedErrorIndex >= 0) { + auto error = errorDataInputs[reportedErrorIndex].getErrorData(evseId, errorDataInputs[reportedErrorIndex].userData); + if (error.isError) { + errorData = error; + errorDataIndex = reportedErrorIndex; + } + } + + for (auto i = std::min(errorDataInputs.size(), trackErrorDataInputs.size()); i >= 1; i--) { + auto index = i - 1; + MO_ErrorData error; + if ((int)index != errorDataIndex) { + error = errorDataInputs[index].getErrorData(evseId, errorDataInputs[index].userData); + } else { + error = errorData; + } + if (error.isError && !trackErrorDataInputs[index] && error.severity >= errorData.severity) { + //new error + errorData = error; + errorDataIndex = index; + } else if (error.isError && error.severity > errorData.severity) { + errorData = error; + errorDataIndex = index; + } else if (!error.isError && trackErrorDataInputs[index]) { + //reset error + trackErrorDataInputs[index] = false; + } + } + + if (errorDataIndex != reportedErrorIndex) { + if (errorDataIndex >= 0 || MO_REPORT_NOERROR) { + reportedStatus = MO_ChargePointStatus_UNDEFINED; //trigger sending currentStatus again with code NoError + } else { + reportedErrorIndex = -1; + } + } + } + + auto status = getStatus(); + + if (status != currentStatus) { + MO_DBG_DEBUG("Status changed %s -> %s %s", + currentStatus == MO_ChargePointStatus_UNDEFINED ? "" : mo_serializeChargePointStatus(currentStatus), + mo_serializeChargePointStatus(status), + availService.minimumStatusDurationInt->getInt() ? " (will report delayed)" : ""); + currentStatus = status; + t_statusTransition = clock.now(); + } + + int32_t dtStatusTransition; + if (!clock.delta(clock.now(), t_statusTransition, dtStatusTransition)) { + dtStatusTransition = 0; + } + + if (reportedStatus != currentStatus && + connection->isConnected() && + clock.now().isUnixTime() && + (availService.minimumStatusDurationInt->getInt() <= 0 || //MinimumStatusDuration disabled + dtStatusTransition >= availService.minimumStatusDurationInt->getInt())) { + reportedStatus = currentStatus; + reportedErrorIndex = errorDataIndex; + if (errorDataIndex >= 0) { + trackErrorDataInputs[errorDataIndex] = true; + } + + auto statusNotificationOp = new StatusNotification(context, evseId, reportedStatus, t_statusTransition, errorData); + if (!statusNotificationOp) { + MO_DBG_ERR("OOM"); + return; + } + auto statusNotification = makeRequest(context, statusNotificationOp); + if (!statusNotification) { + MO_DBG_ERR("OOM"); + return; + } + statusNotification->setTimeout(0); + context.getMessageService().sendRequest(std::move(statusNotification)); + return; + } +} + +void Ocpp16::AvailabilityServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { + this->connectorPluggedInput = connectorPlugged; + this->connectorPluggedInputUserData = userData; +} + +void Ocpp16::AvailabilityServiceEvse::setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData) { + this->evReadyInput = evReady; + this->evReadyInputUserData = userData; +} + +void Ocpp16::AvailabilityServiceEvse::setEvseReadyInput(bool (*evseReady)(unsigned int, void*), void *userData) { + this->evseReadyInput = evseReady; + this->evseReadyInputUserData = userData; +} + +void Ocpp16::AvailabilityServiceEvse::setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData) { + this->occupiedInput = occupied; + this->occupiedInputUserData = userData; +} + +bool Ocpp16::AvailabilityServiceEvse::addErrorDataInput(MO_ErrorDataInput errorDataInput) { + size_t capacity = errorDataInputs.size() + 1; + errorDataInputs.reserve(capacity); + trackErrorDataInputs.reserve(capacity); + if (errorDataInputs.capacity() != capacity || trackErrorDataInputs.capacity() != capacity) { + MO_DBG_ERR("OOM"); + return false; + } + + errorDataInputs.push_back(errorDataInput); + trackErrorDataInputs.push_back(false); + return true; +} + +void Ocpp16::AvailabilityServiceEvse::setAvailability(bool available) { + availabilityBool->setBool(available); + availabilityContainer->commit(); +} +void Ocpp16::AvailabilityServiceEvse::setAvailabilityVolatile(bool available) { + availabilityVolatile = available; } -void AvailabilityServiceEvse::loop() { +MO_ChargePointStatus Ocpp16::AvailabilityServiceEvse::getStatus() { + + MO_ChargePointStatus res = MO_ChargePointStatus_UNDEFINED; + + /* + * Handle special case: This is the Connector for the whole CP (i.e. evseId=0) --> only states Available, Unavailable, Faulted are possible + */ + if (evseId == 0) { + if (isFaulted()) { + res = MO_ChargePointStatus_Faulted; + } else if (!isOperative()) { + res = MO_ChargePointStatus_Unavailable; + } else { + res = MO_ChargePointStatus_Available; + } + return res; + } + + auto transaction = txServiceEvse->getTransaction(); + + if (isFaulted()) { + res = MO_ChargePointStatus_Faulted; + } else if (!isOperative()) { + res = MO_ChargePointStatus_Unavailable; + } else if (transaction && transaction->isRunning()) { + //Transaction is currently running + if (connectorPluggedInput && !connectorPluggedInput(evseId, connectorPluggedInputUserData)) { //special case when StopTransactionOnEVSideDisconnect is false + res = MO_ChargePointStatus_SuspendedEV; + } else if (!txServiceEvse->ocppPermitsCharge() || + (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData))) { + res = MO_ChargePointStatus_SuspendedEVSE; + } else if (evReadyInput && !evReadyInput(evseId, evReadyInputUserData)) { + res = MO_ChargePointStatus_SuspendedEV; + } else { + res = MO_ChargePointStatus_Charging; + } + } + #if MO_ENABLE_RESERVATION + else if (model.getReservationService() && model.getReservationService()->getReservation(evseId)) { + res = MO_ChargePointStatus_Reserved; + } + #endif + else if ((!transaction) && //no transaction process occupying the connector + (!connectorPluggedInput || !connectorPluggedInput(evseId, connectorPluggedInputUserData)) && //no vehicle plugged + (!occupiedInput || !occupiedInput(evseId, occupiedInputUserData))) { //occupied override clear + res = MO_ChargePointStatus_Available; + } else { + /* + * Either in Preparing or Finishing state. Only way to know is from previous state + */ + const auto previous = currentStatus; + if (previous == MO_ChargePointStatus_Finishing || + previous == MO_ChargePointStatus_Charging || + previous == MO_ChargePointStatus_SuspendedEV || + previous == MO_ChargePointStatus_SuspendedEVSE || + (transaction && transaction->getStartSync().isRequested())) { //transaction process still occupying the connector + res = MO_ChargePointStatus_Finishing; + } else { + res = MO_ChargePointStatus_Preparing; + } + } + + if (res == MO_ChargePointStatus_UNDEFINED) { + MO_DBG_DEBUG("status undefined"); + return MO_ChargePointStatus_Faulted; //internal error + } + + return res; +} + +bool Ocpp16::AvailabilityServiceEvse::isFaulted() { + //for (auto i = errorDataInputs.begin(); i != errorDataInputs.end(); ++i) { + for (size_t i = 0; i < errorDataInputs.size(); i++) { + if (errorDataInputs[i].getErrorData(evseId, errorDataInputs[i].userData).isFaulted) { + return true; + } + } + return false; +} + +const char *Ocpp16::AvailabilityServiceEvse::getErrorCode() { + if (reportedErrorIndex >= 0) { + auto error = errorDataInputs[reportedErrorIndex].getErrorData(evseId, errorDataInputs[reportedErrorIndex].userData); + if (error.isError && error.errorCode) { + return error.errorCode; + } + } + return nullptr; +} + +bool Ocpp16::AvailabilityServiceEvse::isOperative() { + if (isFaulted()) { + return false; + } + + if (!trackLoopExecute) { + return false; + } + + //check for running transaction(s) - if yes then the connector is always operative + if (evseId == 0) { + for (unsigned int cId = 1; cId < model.getNumEvseId(); cId++) { + auto txService = model.getTransactionService(); + auto txServiceEvse = txService ? txService->getEvse(cId) : nullptr; + auto transaction = txServiceEvse ? txServiceEvse->getTransaction() : nullptr; + if (transaction && transaction->isRunning()) { + return true; + } + } + } else { + auto transaction = txServiceEvse->getTransaction(); + if (transaction && transaction->isRunning()) { + return true; + } + } + + return availabilityVolatile && availabilityBool->getBool(); +} + +Operation *Ocpp16::AvailabilityServiceEvse::createTriggeredStatusNotification() { + + MO_ErrorData errorData; + mo_ErrorData_init(&errorData); + errorData.severity = 0; + + if (reportedErrorIndex >= 0) { + errorData = errorDataInputs[reportedErrorIndex].getErrorData(evseId, errorDataInputs[reportedErrorIndex].userData); + } else { + //find errorData with maximum severity + for (auto i = errorDataInputs.size(); i >= 1; i--) { + auto index = i - 1; + MO_ErrorData error = errorDataInputs[index].getErrorData(evseId, errorDataInputs[index].userData); + if (error.isError && error.severity >= errorData.severity) { + errorData = error; + } + } + } + + return new StatusNotification( + context, + evseId, + getStatus(), + clock.now(), + errorData); +} + +Ocpp16::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v16.Availability.AvailabilityService"), context(context) { + +} + +Ocpp16::AvailabilityService::~AvailabilityService() { + for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { + delete evses[i]; + evses[i] = nullptr; + } +} + +bool Ocpp16::AvailabilityService::setup() { + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + minimumStatusDurationInt = configService->declareConfiguration("MinimumStatusDuration", 0); + if (!minimumStatusDurationInt) { + MO_DBG_ERR("setup failure"); + return false; + } + + context.getMessageService().registerOperation("ChangeAvailability", [] (Context& context) -> Operation* { + return new ChangeAvailability(context.getModel16());}); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("StatusNotification", nullptr, nullptr); + #endif //MO_ENABLE_MOCK_SERVER + + auto rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("StatusNotification", [] (Context& context, unsigned int evseId) { + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + return availSvcEvse ? availSvcEvse->createTriggeredStatusNotification() : nullptr; + }); + + numEvseId = context.getModel16().getNumEvseId(); + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i)) { + MO_DBG_ERR("connector init failure"); + return false; + } + } + + return true; +} + +void Ocpp16::AvailabilityService::loop() { + for (size_t i = 0; i < numEvseId && evses[i]; i++) { + evses[i]->loop(); + } +} + +Ocpp16::AvailabilityServiceEvse *Ocpp16::AvailabilityService::getEvse(unsigned int evseId) { + if (evseId == 0 || evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); + return nullptr; + } + + if (!evses[evseId]) { + evses[evseId] = new AvailabilityServiceEvse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + + return evses[evseId]; +} + +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 + +using namespace MicroOcpp; + +Ocpp201::AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId) : MemoryManaged("v201.Availability.AvailabilityServiceEvse"), context(context), availService(availService), evseId(evseId), faultedInputs(makeVector(getMemoryTag())) { + +} + +void Ocpp201::AvailabilityServiceEvse::loop() { if (evseId >= 1) { auto status = getStatus(); if (status != reportedStatus && - context.getModel().getClock().now() >= MIN_TIME) { + context.getClock().now().isUnixTime()) { - auto statusNotification = makeRequest(new Ocpp201::StatusNotification(evseId, status, context.getModel().getClock().now())); + auto statusNotification = makeRequest(context, new StatusNotification(context, evseId, status, context.getClock().now())); statusNotification->setTimeout(0); - context.initiateRequest(std::move(statusNotification)); + context.getMessageService().sendRequest(std::move(statusNotification)); reportedStatus = status; return; } } } -void AvailabilityServiceEvse::setConnectorPluggedInput(std::function connectorPluggedInput) { - this->connectorPluggedInput = connectorPluggedInput; +void Ocpp201::AvailabilityServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { + this->connectorPluggedInput = connectorPlugged; + this->connectorPluggedInputUserData = userData; } -void AvailabilityServiceEvse::setOccupiedInput(std::function occupiedInput) { - this->occupiedInput = occupiedInput; +void Ocpp201::AvailabilityServiceEvse::setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData) { + this->occupiedInput = occupied; + this->occupiedInputUserData = userData; } -ChargePointStatus AvailabilityServiceEvse::getStatus() { - ChargePointStatus res = ChargePointStatus_UNDEFINED; +MO_ChargePointStatus Ocpp201::AvailabilityServiceEvse::getStatus() { + MO_ChargePointStatus res = MO_ChargePointStatus_UNDEFINED; if (isFaulted()) { - res = ChargePointStatus_Faulted; + res = MO_ChargePointStatus_Faulted; } else if (!isAvailable()) { - res = ChargePointStatus_Unavailable; - } - #if MO_ENABLE_RESERVATION - else if (context.getModel().getReservationService() && context.getModel().getReservationService()->getReservation(evseId)) { - res = ChargePointStatus_Reserved; - } - #endif - else if ((!connectorPluggedInput || !connectorPluggedInput()) && //no vehicle plugged - (!occupiedInput || !occupiedInput())) { //occupied override clear - res = ChargePointStatus_Available; + res = MO_ChargePointStatus_Unavailable; + } else if ((!connectorPluggedInput || !connectorPluggedInput(evseId, connectorPluggedInputUserData)) && //no vehicle plugged + (!occupiedInput || !occupiedInput(evseId, occupiedInputUserData))) { //occupied override clear + res = MO_ChargePointStatus_Available; } else { - res = ChargePointStatus_Occupied; + res = MO_ChargePointStatus_Occupied; } return res; } -void AvailabilityServiceEvse::setUnavailable(void *requesterId) { +void Ocpp201::AvailabilityServiceEvse::setUnavailable(void *requesterId) { for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { if (!unavailableRequesters[i]) { unavailableRequesters[i] = requesterId; @@ -79,7 +484,7 @@ void AvailabilityServiceEvse::setUnavailable(void *requesterId) { MO_DBG_ERR("exceeded max. unavailable requesters"); } -void AvailabilityServiceEvse::setAvailable(void *requesterId) { +void Ocpp201::AvailabilityServiceEvse::setAvailable(void *requesterId) { for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { if (unavailableRequesters[i] == requesterId) { unavailableRequesters[i] = nullptr; @@ -89,7 +494,7 @@ void AvailabilityServiceEvse::setAvailable(void *requesterId) { MO_DBG_ERR("could not find unavailable requester"); } -ChangeAvailabilityStatus AvailabilityServiceEvse::changeAvailability(bool operative) { +ChangeAvailabilityStatus Ocpp201::AvailabilityServiceEvse::changeAvailability(bool operative) { if (operative) { setAvailable(this); } else { @@ -103,7 +508,7 @@ ChangeAvailabilityStatus AvailabilityServiceEvse::changeAvailability(bool operat if (evseId == 0) { for (unsigned int id = 1; id < MO_NUM_EVSEID; id++) { - if (availabilityService.getEvse(id) && availabilityService.getEvse(id)->isAvailable()) { + if (availService.getEvse(id) && availService.getEvse(id)->isAvailable()) { return ChangeAvailabilityStatus::Scheduled; } } @@ -113,29 +518,17 @@ ChangeAvailabilityStatus AvailabilityServiceEvse::changeAvailability(bool operat return ChangeAvailabilityStatus::Accepted; } -void AvailabilityServiceEvse::setFaulted(void *requesterId) { - for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) { - if (!faultedRequesters[i]) { - faultedRequesters[i] = requesterId; - return; - } - } - MO_DBG_ERR("exceeded max. faulted requesters"); -} - -void AvailabilityServiceEvse::resetFaulted(void *requesterId) { - for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) { - if (faultedRequesters[i] == requesterId) { - faultedRequesters[i] = nullptr; - return; - } - } - MO_DBG_ERR("could not find faulted requester"); +Operation *Ocpp201::AvailabilityServiceEvse::createTriggeredStatusNotification() { + return new StatusNotification( + context, + evseId, + getStatus(), + context.getClock().now()); } -bool AvailabilityServiceEvse::isAvailable() { +bool Ocpp201::AvailabilityServiceEvse::isAvailable() { - auto txService = context.getModel().getTransactionService(); + auto txService = context.getModel201().getTransactionService(); auto txEvse = txService ? txService->getEvse(evseId) : nullptr; if (txEvse) { if (txEvse->getTransaction() && @@ -146,7 +539,7 @@ bool AvailabilityServiceEvse::isAvailable() { } if (evseId > 0) { - if (availabilityService.getEvse(0) && !availabilityService.getEvse(0)->isAvailable()) { + if (availService.getEvse(0) && !availService.getEvse(0)->isAvailable()) { return false; } } @@ -159,45 +552,81 @@ bool AvailabilityServiceEvse::isAvailable() { return true; } -bool AvailabilityServiceEvse::isFaulted() { - for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) { - if (faultedRequesters[i]) { - return true; - } +bool Ocpp201::AvailabilityServiceEvse::addFaultedInput(FaultedInput faultedInput) { + size_t capacity = faultedInputs.size() + 1; + faultedInputs.reserve(capacity); + if (faultedInputs.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return false; } - return false; + faultedInputs.push_back(faultedInput); + return true; } -AvailabilityService::AvailabilityService(Context& context, size_t numEvses) : MemoryManaged("v201.Availability.AvailabilityService"), context(context) { - - for (size_t i = 0; i < numEvses && i < MO_NUM_EVSEID; i++) { - evses[i] = new AvailabilityServiceEvse(context, *this, (unsigned int)i); - } - - context.getOperationRegistry().registerOperation("StatusNotification", [&context] () { - return new Ocpp16::StatusNotification(-1, ChargePointStatus_UNDEFINED, Timestamp());}); - context.getOperationRegistry().registerOperation("ChangeAvailability", [this] () { - return new Ocpp201::ChangeAvailability(*this);}); +Ocpp201::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v201.Availability.AvailabilityService"), context(context) { + } -AvailabilityService::~AvailabilityService() { +Ocpp201::AvailabilityService::~AvailabilityService() { for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { delete evses[i]; + evses[i] = nullptr; } } -void AvailabilityService::loop() { - for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { +bool Ocpp201::AvailabilityService::setup() { + + context.getMessageService().registerOperation("ChangeAvailability", [] (Context& context) -> Operation* { + return new ChangeAvailability(*context.getModel201().getAvailabilityService());}); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("StatusNotification", nullptr, nullptr); + #endif //MO_ENABLE_MOCK_SERVER + + auto rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("StatusNotification", [] (Context& context, unsigned int evseId) { + auto availSvc = context.getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + return availSvcEvse ? availSvcEvse->createTriggeredStatusNotification() : nullptr; + }); + + numEvseId = context.getModel201().getNumEvseId(); + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i)) { + MO_DBG_ERR("connector init failure"); + return false; + } + } + + return true; +} + +void Ocpp201::AvailabilityService::loop() { + for (size_t i = 0; i < numEvseId && evses[i]; i++) { evses[i]->loop(); } } -AvailabilityServiceEvse *AvailabilityService::getEvse(unsigned int evseId) { - if (evseId >= MO_NUM_EVSEID) { - MO_DBG_ERR("invalid arg"); +Ocpp201::AvailabilityServiceEvse *Ocpp201::AvailabilityService::getEvse(unsigned int evseId) { + if (evseId == 0 || evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); return nullptr; } + + if (!evses[evseId]) { + evses[evseId] = new AvailabilityServiceEvse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + return evses[evseId]; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h index d8b74d61..f103c159 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -11,16 +11,122 @@ #ifndef MO_AVAILABILITYSERVICE_H #define MO_AVAILABILITYSERVICE_H +#include +#include +#include +#include +#include +#include #include -#if MO_ENABLE_V201 +#if MO_ENABLE_V16 -#include +namespace MicroOcpp { -#include -#include -#include -#include +class Context; +class Clock; +class Connection; + +namespace Ocpp16 { + +class Model; +class Configuration; +class ConfigurationContainer; +class AvailabilityService; +class TransactionServiceEvse; + +class AvailabilityServiceEvse : public MemoryManaged { +private: + Context& context; + Clock& clock; + Model& model; + AvailabilityService& availService; + const unsigned int evseId; + + Connection *connection = nullptr; + TransactionServiceEvse *txServiceEvse = nullptr; + + bool (*connectorPluggedInput)(unsigned int evseId, void *userData) = nullptr; + void *connectorPluggedInputUserData = nullptr; + bool (*evReadyInput)(unsigned int evseId, void *userData) = nullptr; + void *evReadyInputUserData = nullptr; + bool (*evseReadyInput)(unsigned int evseId, void *userData) = nullptr; + void *evseReadyInputUserData = nullptr; + bool (*occupiedInput)(unsigned int evseId, void *userData) = nullptr; //instead of Available, go into Preparing / Finishing state + void *occupiedInputUserData = nullptr; + + Configuration *availabilityBool = nullptr; + char availabilityBoolKey [sizeof(MO_CONFIG_EXT_PREFIX "AVAIL_CONN_xxxx") + 1]; + bool availabilityVolatile = true; + + ConfigurationContainer *availabilityContainer = nullptr; + + Vector errorDataInputs; + Vector trackErrorDataInputs; + int reportedErrorIndex = -1; //last reported error + const char *getErrorCode(); + + MO_ChargePointStatus currentStatus = MO_ChargePointStatus_UNDEFINED; + MO_ChargePointStatus reportedStatus = MO_ChargePointStatus_UNDEFINED; + Timestamp t_statusTransition; + + bool trackLoopExecute = false; //if loop has been executed once + +public: + AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId); + ~AvailabilityServiceEvse(); + + void setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData); + void setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData); + void setEvseReadyInput(bool (*evseReady)(unsigned int, void*), void *userData); + void setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData); + + bool addErrorDataInput(MO_ErrorDataInput errorDataInput); + + void loop(); + + bool setup(); + + MO_ChargePointStatus getStatus(); + + void setAvailability(bool available); + void setAvailabilityVolatile(bool available); //set inoperative state but keep only until reboot at most + + ChangeAvailabilityStatus changeAvailability(bool operative); + + Operation *createTriggeredStatusNotification(); + + bool isOperative(); + bool isFaulted(); +}; + +class AvailabilityService : public MemoryManaged { +private: + Context& context; + + AvailabilityServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; + + Configuration *minimumStatusDurationInt = nullptr; //in seconds + +public: + AvailabilityService(Context& context); + ~AvailabilityService(); + + bool setup(); + + void loop(); + + AvailabilityServiceEvse *getEvse(unsigned int evseId); + +friend class AvailabilityServiceEvse; +}; + +} //namespace MicroOcpp +} //namespace Ocpp16 +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 #ifndef MO_INOPERATIVE_REQUESTERS_MAX #define MO_INOPERATIVE_REQUESTERS_MAX 3 @@ -33,38 +139,49 @@ namespace MicroOcpp { class Context; + +namespace Ocpp201 { + class AvailabilityService; +struct FaultedInput { + bool (*isFaulted)(unsigned int connectorId, void *userData) = nullptr; + void *userData = nullptr; +}; + class AvailabilityServiceEvse : public MemoryManaged { private: Context& context; - AvailabilityService& availabilityService; + AvailabilityService& availService; const unsigned int evseId; - std::function connectorPluggedInput; - std::function occupiedInput; //instead of Available, go into Occupied + bool (*connectorPluggedInput)(unsigned int evseId, void *userData) = nullptr; + void *connectorPluggedInputUserData = nullptr; + bool (*occupiedInput)(unsigned int evseId, void *userData) = nullptr; //instead of Available, go into Occupied + void *occupiedInputUserData = nullptr; void *unavailableRequesters [MO_INOPERATIVE_REQUESTERS_MAX] = {nullptr}; - void *faultedRequesters [MO_FAULTED_REQUESTERS_MAX] = {nullptr}; + Vector faultedInputs; - ChargePointStatus reportedStatus = ChargePointStatus_UNDEFINED; + MO_ChargePointStatus reportedStatus = MO_ChargePointStatus_UNDEFINED; public: - AvailabilityServiceEvse(Context& context, AvailabilityService& availabilityService, unsigned int evseId); + AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId); - void loop(); + void setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData); + void setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData); + + bool addFaultedInput(FaultedInput faultedInput); - void setConnectorPluggedInput(std::function connectorPluggedInput); - void setOccupiedInput(std::function occupiedInput); + void loop(); - ChargePointStatus getStatus(); + MO_ChargePointStatus getStatus(); void setUnavailable(void *requesterId); void setAvailable(void *requesterId); ChangeAvailabilityStatus changeAvailability(bool operative); - void setFaulted(void *requesterId); - void resetFaulted(void *requesterId); + Operation *createTriggeredStatusNotification(); bool isAvailable(); bool isFaulted(); @@ -75,18 +192,20 @@ class AvailabilityService : public MemoryManaged { Context& context; AvailabilityServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; public: - AvailabilityService(Context& context, size_t numEvses); + AvailabilityService(Context& context); ~AvailabilityService(); + bool setup(); + void loop(); AvailabilityServiceEvse *getEvse(unsigned int evseId); }; -} // namespace MicroOcpp - -#endif // MO_ENABLE_V201 - +} //namespace MicroOcpp +} //namespace Ocpp201 +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h b/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h deleted file mode 100644 index 113a3768..00000000 --- a/src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h +++ /dev/null @@ -1,25 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CHANGEAVAILABILITYSTATUS_H -#define MO_CHANGEAVAILABILITYSTATUS_H - -#include - -#if MO_ENABLE_V201 - -#include - -namespace MicroOcpp { - -enum class ChangeAvailabilityStatus : uint8_t { - Accepted, - Rejected, - Scheduled -}; - -} //namespace MicroOcpp - -#endif //MO_ENABLE_V201 -#endif diff --git a/src/MicroOcpp/Model/Boot/BootNotificationData.h b/src/MicroOcpp/Model/Boot/BootNotificationData.h new file mode 100644 index 00000000..543e18c9 --- /dev/null +++ b/src/MicroOcpp/Model/Boot/BootNotificationData.h @@ -0,0 +1,52 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_BOOTNOTIFICATIONDATA_H +#define MO_BOOTNOTIFICATIONDATA_H + +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Data to be sent in BootNotification. + * + * Usage: + * ``` + * // init + * MO_BootNotificationData bnData; + * mo_BootNotificationData_init(&bnData); + * + * // set payload + * bnData.chargePointVendor = "My Company Ltd."; + * bnData.chargePointModel = "Demo Charger"; + * + * // pass data to MO + * mo_setBootNotificationData2(bnData); //copies data, i.e. bnData can be invalidated now + */ + +typedef struct { + const char *chargePointModel; + const char *chargePointVendor; + const char *firmwareVersion; + const char *chargePointSerialNumber; + const char *meterSerialNumber; //OCPP 1.6 only + const char *meterType; //OCPP 1.6 only + const char *chargeBoxSerialNumber; //OCPP 1.6 only + const char *iccid; + const char *imsi; +} MO_BootNotificationData; + +void mo_BootNotificationData_init(MO_BootNotificationData *bnData); + +#ifdef __cplusplus +} //extern "C" +#endif + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index 1aabb200..ef3a83ac 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -1,21 +1,31 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License +#include + #include -#include -#include +#include #include -#include +#include +#include +#include #include #include +#include #include #include #include +#if MO_ENABLE_V16 || MO_ENABLE_V201 + using namespace MicroOcpp; +void mo_BootNotificationData_init(MO_BootNotificationData *bnData) { + memset(bnData, 0, sizeof(*bnData)); +} + unsigned int PreBootQueue::getFrontRequestOpNr() { if (!activatedPostBootCommunication) { return 0; @@ -41,39 +51,135 @@ RegistrationStatus MicroOcpp::deserializeRegistrationStatus(const char *serializ } } -BootService::BootService(Context& context, std::shared_ptr filesystem) : MemoryManaged("v16.Boot.BootService"), context(context), filesystem(filesystem), cpCredentials{makeString(getMemoryTag())} { - - context.getRequestQueue().setPreBootSendQueue(&preBootQueue); //register PreBootQueue in RequestQueue module - - //if transactions can start before the BootNotification succeeds - preBootTransactionsBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "PreBootTransactions", false); - - if (!preBootTransactionsBool) { - MO_DBG_ERR("initialization error"); +BootService::BootService(Context& context) : MemoryManaged("v16/v201.Boot.BootService"), context(context) { + +} + +BootService::~BootService() { + MO_FREE(bnDataBuf); + bnDataBuf = nullptr; +} + +bool BootService::setup() { + + context.getMessageService().setPreBootSendQueue(&preBootQueue); //register PreBootQueue in RequestQueue module + + filesystem = context.getFilesystem(); + + ocppVersion = context.getOcppVersion(); + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("initialization error"); + return false; + } + + //if transactions can start before the BootNotification succeeds + preBootTransactionsBoolV16 = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "PreBootTransactions", false); + if (!preBootTransactionsBoolV16) { + MO_DBG_ERR("initialization error"); + return false; + } + + heartbeatService = context.getModel16().getHeartbeatService(); //optional + + RemoteControlService *rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("BootNotification", [] (Context& context) -> Operation* { + auto& model = context.getModel16(); + auto& bootSvc = *model.getBootService(); + return new BootNotification(context, bootSvc, model.getHeartbeatService(), bootSvc.getBootNotificationData()); + }); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + + auto varService = context.getModel201().getVariableService(); + if (!varService) { + MO_DBG_ERR("initialization error"); + return false; + } + + //if transactions can start before the BootNotification succeeds + preBootTransactionsBoolV201 = varService->declareVariable("CustomizationCtrlr", "PreBootTransactions", false); + if (!preBootTransactionsBoolV201) { + MO_DBG_ERR("initialization error"); + return false; + } + + heartbeatService = context.getModel201().getHeartbeatService(); //optional + + RemoteControlService *rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("BootNotification", [] (Context& context, int evseId) -> TriggerMessageStatus { + (void)evseId; + auto& model = context.getModel201(); + auto& bootSvc = *model.getBootService(); + + if (bootSvc.getRegistrationStatus() == RegistrationStatus::Accepted) { + //F06.FR.17: if previous BootNotification was accepted, don't allow triggering a new one + return TriggerMessageStatus::Rejected; + } else { + auto operation = new BootNotification(context, bootSvc, model.getHeartbeatService(), bootSvc.getBootNotificationData()); + if (!operation) { + MO_DBG_ERR("OOM"); + return TriggerMessageStatus::ERR_INTERNAL; + } + auto request = makeRequest(context, operation); + if (!request) { + MO_DBG_ERR("OOM"); + return TriggerMessageStatus::ERR_INTERNAL; + } + if (!context.getMessageService().sendRequestPreBoot(std::move(request))) { + MO_DBG_ERR("OOM"); + return TriggerMessageStatus::ERR_INTERNAL; + } + return TriggerMessageStatus::Accepted; + } + }); } + #endif //MO_ENABLE_V201 - //Register message handler for TriggerMessage operation - context.getOperationRegistry().registerOperation("BootNotification", [this] () { - return new Ocpp16::BootNotification(this->context.getModel(), getChargePointCredentials());}); + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("BootNotification", nullptr, BootNotification::writeMockConf, reinterpret_cast(&context)); + #endif //MO_ENABLE_MOCK_SERVER + + return true; } void BootService::loop() { + auto& clock = context.getClock(); + if (!executedFirstTime) { executedFirstTime = true; - firstExecutionTimestamp = mocpp_tick_ms(); + firstExecutionTimestamp = clock.getUptime(); + } + + int32_t dtFirstExecution; + if (!clock.delta(clock.getUptime(), firstExecutionTimestamp, dtFirstExecution)) { + dtFirstExecution = 0; } - if (!executedLongTime && mocpp_tick_ms() - firstExecutionTimestamp >= MO_BOOTSTATS_LONGTIME_MS) { + if (!executedLongTime && dtFirstExecution >= MO_BOOTSTATS_LONGTIME_MS) { executedLongTime = true; MO_DBG_DEBUG("boot success timer reached"); - configuration_clean_unused(); - - BootStats bootstats; - loadBootStats(filesystem, bootstats); - bootstats.lastBootSuccess = bootstats.bootNr; - storeBootStats(filesystem, bootstats); + if (filesystem) { + PersistencyUtils::setBootSuccess(filesystem); + } } preBootQueue.loop(); @@ -83,16 +189,44 @@ void BootService::loop() { activatedPostBootCommunication = true; } - if (!activatedModel && (status == RegistrationStatus::Accepted || preBootTransactionsBool->getBool())) { - context.getModel().activateTasks(); + bool preBootTransactions = false; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + preBootTransactions = preBootTransactionsBoolV16->getBool(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + preBootTransactions = preBootTransactionsBoolV201->getBool(); + } + #endif //MO_ENABLE_V201 + + if (!activatedModel && (status == RegistrationStatus::Accepted || preBootTransactions)) { + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + context.getModel16().activateTasks(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + context.getModel201().activateTasks(); + } + #endif //MO_ENABLE_V201 + activatedModel = true; } if (status == RegistrationStatus::Accepted) { return; } + + int32_t dtLastBootNotification; + if (!clock.delta(clock.getUptime(), lastBootNotification, dtLastBootNotification)) { + dtLastBootNotification = 0; + } - if (mocpp_tick_ms() - lastBootNotification < (interval_s * 1000UL)) { + if (lastBootNotification.isDefined() && dtLastBootNotification < interval_s) { return; } @@ -100,54 +234,93 @@ void BootService::loop() { * Create BootNotification. The BootNotifaction object will fetch its paremeters from * this class and notify this class about the response */ - auto bootNotification = makeRequest(new Ocpp16::BootNotification(context.getModel(), getChargePointCredentials())); - bootNotification->setTimeout(interval_s * 1000UL); - context.getRequestQueue().sendRequestPreBoot(std::move(bootNotification)); + auto bootNotification = makeRequest(context, new BootNotification(context, *this, heartbeatService, getBootNotificationData())); + bootNotification->setTimeout(interval_s); + context.getMessageService().sendRequestPreBoot(std::move(bootNotification)); - lastBootNotification = mocpp_tick_ms(); + lastBootNotification = clock.getUptime(); } -void BootService::setChargePointCredentials(JsonObject credentials) { - auto written = serializeJson(credentials, cpCredentials); - if (written < 2) { - MO_DBG_ERR("serialization error"); - cpCredentials = "{}"; - } +namespace MicroOcpp { +size_t measureDataEntry(const char *bnDataEntry) { + return bnDataEntry ? strlen(bnDataEntry) + 1 : 0; } -void BootService::setChargePointCredentials(const char *credentials) { - cpCredentials = credentials; - if (cpCredentials.size() < 2) { - cpCredentials = "{}"; +bool setDataEntry(char *bnDataBuf, size_t bufsize, size_t& written, const char **bnDataEntryDest, const char *bnDataEntrySrc) { + if (!bnDataEntrySrc) { + bnDataEntryDest = nullptr; + return true; + } + *bnDataEntryDest = bnDataBuf + written; + int ret = snprintf(bnDataBuf + written, bufsize - written, "%s", bnDataEntrySrc); + if (ret < 0 || (size_t)ret >= bufsize - written) { + return false; } + written += (size_t)ret + 1; + return true; +} } -std::unique_ptr BootService::getChargePointCredentials() { - if (cpCredentials.size() <= 2) { - return createEmptyDocument(); +bool BootService::setBootNotificationData(MO_BootNotificationData bnData) { + size_t bufsize = 0; + bufsize += measureDataEntry(bnData.chargePointModel); + bufsize += measureDataEntry(bnData.chargePointVendor); + bufsize += measureDataEntry(bnData.firmwareVersion); + bufsize += measureDataEntry(bnData.chargePointSerialNumber); + bufsize += measureDataEntry(bnData.meterSerialNumber); + bufsize += measureDataEntry(bnData.meterType); + bufsize += measureDataEntry(bnData.chargeBoxSerialNumber); + bufsize += measureDataEntry(bnData.iccid); + bufsize += measureDataEntry(bnData.imsi); + + bnDataBuf = static_cast(MO_MALLOC(getMemoryTag(), bufsize)); + if (!bnDataBuf) { + MO_DBG_ERR("OOM"); + return false; } - std::unique_ptr doc; - size_t capacity = JSON_OBJECT_SIZE(9) + cpCredentials.size(); - DeserializationError err = DeserializationError::NoMemory; - while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) { - doc = makeJsonDoc(getMemoryTag(), capacity); - err = deserializeJson(*doc, cpCredentials); + memset(&this->bnData, 0, sizeof(this->bnData)); - capacity *= 2; + size_t written = 0; + bool success = true; + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.chargePointModel, bnData.chargePointModel);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.chargePointVendor, bnData.chargePointVendor);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.firmwareVersion, bnData.firmwareVersion);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.chargePointSerialNumber, bnData.chargePointSerialNumber);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.meterSerialNumber, bnData.meterSerialNumber);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.meterType, bnData.meterType);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.chargeBoxSerialNumber, bnData.chargeBoxSerialNumber);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.iccid, bnData.iccid);} + if (success) {success &= setDataEntry(bnDataBuf, bufsize, written, &this->bnData.imsi, bnData.imsi);} + + if (!success) { + MO_DBG_ERR("failure to set BootNotification data"); + goto fail; } - if (!err) { - return doc; - } else { - MO_DBG_ERR("could not parse stored credentials: %s", err.c_str()); - return nullptr; + if (written != bufsize) { + MO_DBG_ERR("internal error"); + goto fail; } + + return true; +fail: + MO_FREE(bnDataBuf); + memset(&this->bnData, 0, sizeof(this->bnData)); + return false; +} + +const MO_BootNotificationData& BootService::getBootNotificationData() { + return bnData; } void BootService::notifyRegistrationStatus(RegistrationStatus status) { this->status = status; - lastBootNotification = mocpp_tick_ms(); + lastBootNotification = context.getClock().getUptime(); +} + +RegistrationStatus BootService::getRegistrationStatus() { + return status; } void BootService::setRetryInterval(unsigned long interval_s) { @@ -156,110 +329,7 @@ void BootService::setRetryInterval(unsigned long interval_s) { } else { this->interval_s = interval_s; } - lastBootNotification = mocpp_tick_ms(); + lastBootNotification = context.getClock().getUptime(); } -bool BootService::loadBootStats(std::shared_ptr filesystem, BootStats& bstats) { - if (!filesystem) { - return false; - } - - size_t msize = 0; - if (filesystem->stat(MO_FILENAME_PREFIX "bootstats.jsn", &msize) == 0) { - - bool success = true; - - auto json = FilesystemUtils::loadJson(filesystem, MO_FILENAME_PREFIX "bootstats.jsn", "v16.Boot.BootService"); - if (json) { - int bootNrIn = (*json)["bootNr"] | -1; - if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits::max()) { - bstats.bootNr = (uint16_t) bootNrIn; - } else { - success = false; - } - - int lastSuccessIn = (*json)["lastSuccess"] | -1; - if (lastSuccessIn >= 0 && lastSuccessIn <= std::numeric_limits::max()) { - bstats.lastBootSuccess = (uint16_t) lastSuccessIn; - } else { - success = false; - } - - const char *microOcppVersionIn = (*json)["MicroOcppVersion"] | (const char*)nullptr; - if (microOcppVersionIn) { - auto ret = snprintf(bstats.microOcppVersion, sizeof(bstats.microOcppVersion), "%s", microOcppVersionIn); - if (ret < 0 || (size_t)ret >= sizeof(bstats.microOcppVersion)) { - success = false; - } - } //else: version specifier can be missing after upgrade from pre 1.2.0 version - } else { - success = false; - } - - if (!success) { - MO_DBG_ERR("bootstats corrupted"); - filesystem->remove(MO_FILENAME_PREFIX "bootstats.jsn"); - bstats = BootStats(); - } - - return success; - } else { - return false; - } -} - -bool BootService::storeBootStats(std::shared_ptr filesystem, BootStats& bstats) { - if (!filesystem) { - return false; - } - - auto json = initJsonDoc("v16.Boot.BootService", JSON_OBJECT_SIZE(3)); - - json["bootNr"] = bstats.bootNr; - json["lastSuccess"] = bstats.lastBootSuccess; - json["MicroOcppVersion"] = (const char*)bstats.microOcppVersion; - - return FilesystemUtils::storeJson(filesystem, MO_FILENAME_PREFIX "bootstats.jsn", json); -} - -bool BootService::recover(std::shared_ptr filesystem, BootStats& bstats) { - if (!filesystem) { - return false; - } - - bool success = FilesystemUtils::remove_if(filesystem, [] (const char *fname) -> bool { - return !strncmp(fname, "sd", strlen("sd")) || - !strncmp(fname, "tx", strlen("tx")) || - !strncmp(fname, "sc-", strlen("sc-")) || - !strncmp(fname, "reservation", strlen("reservation")) || - !strncmp(fname, "client-state", strlen("client-state")); - }); - MO_DBG_ERR("clear local state files (recovery): %s", success ? "success" : "not completed"); - - return success; -} - -bool BootService::migrate(std::shared_ptr filesystem, BootStats& bstats) { - if (!filesystem) { - return false; - } - - bool success = true; - - if (strcmp(bstats.microOcppVersion, MO_VERSION)) { - MO_DBG_INFO("migrate persistent storage to MO v" MO_VERSION); - success = FilesystemUtils::remove_if(filesystem, [] (const char *fname) -> bool { - return !strncmp(fname, "sd", strlen("sd")) || - !strncmp(fname, "tx", strlen("tx")) || - !strncmp(fname, "op", strlen("op")) || - !strncmp(fname, "sc-", strlen("sc-")) || - !strcmp(fname, "client-state.cnf") || - !strcmp(fname, "arduino-ocpp.cnf") || - !strcmp(fname, "ocpp-creds.jsn"); - }); - - snprintf(bstats.microOcppVersion, sizeof(bstats.microOcppVersion), "%s", MO_VERSION); - MO_DBG_DEBUG("clear local state files (migration): %s", success ? "success" : "not completed"); - } - return success; -} +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Boot/BootService.h b/src/MicroOcpp/Model/Boot/BootService.h index 6091dc2c..6069a440 100644 --- a/src/MicroOcpp/Model/Boot/BootService.h +++ b/src/MicroOcpp/Model/Boot/BootService.h @@ -1,16 +1,20 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_BOOTSERVICE_H #define MO_BOOTSERVICE_H -#include +#include #include #include #include +#include +#include #include +#if MO_ENABLE_V16 || MO_ENABLE_V201 + #define MO_BOOT_INTERVAL_DEFAULT 60 #ifndef MO_BOOTSTATS_LONGTIME_MS @@ -19,19 +23,6 @@ namespace MicroOcpp { -#define MO_BOOTSTATS_VERSION_SIZE 10 - -struct BootStats { - uint16_t bootNr = 0; - uint16_t lastBootSuccess = 0; - - uint16_t getBootFailureCount() { - return bootNr - lastBootSuccess; - } - - char microOcppVersion [MO_BOOTSTATS_VERSION_SIZE] = {'\0'}; -}; - enum class RegistrationStatus { Accepted, Pending, @@ -45,56 +36,76 @@ class PreBootQueue : public VolatileRequestQueue { private: bool activatedPostBootCommunication = false; public: - unsigned int getFrontRequestOpNr() override; //override FrontRequestOpNr behavior: in PreBoot mode, always return 0 to avoid other RequestEmitters from sending msgs + unsigned int getFrontRequestOpNr() override; //override FrontRequestOpNr behavior: in PreBoot mode, always return 0 to avoid other RequestQueues from sending msgs void activatePostBootCommunication(); //end PreBoot mode, now send Requests normally }; class Context; +class HeartbeatService; + +#if MO_ENABLE_V16 +namespace Ocpp16 { +class Configuration; +} +#endif +#if MO_ENABLE_V201 +namespace Ocpp201 { +class Variable; +} +#endif class BootService : public MemoryManaged { private: Context& context; - std::shared_ptr filesystem; + MO_FilesystemAdapter *filesystem = nullptr;; PreBootQueue preBootQueue; - unsigned long interval_s = MO_BOOT_INTERVAL_DEFAULT; - unsigned long lastBootNotification = -1UL / 2; + HeartbeatService *heartbeatService = nullptr; + + int32_t interval_s = MO_BOOT_INTERVAL_DEFAULT; + Timestamp lastBootNotification; RegistrationStatus status = RegistrationStatus::Pending; - - String cpCredentials; - std::shared_ptr preBootTransactionsBool; + MO_BootNotificationData bnData {0}; + char *bnDataBuf = nullptr; + + int ocppVersion = -1; + + #if MO_ENABLE_V16 + Ocpp16::Configuration *preBootTransactionsBoolV16 = nullptr; + #endif + #if MO_ENABLE_V201 + Ocpp201::Variable *preBootTransactionsBoolV201 = nullptr; + #endif bool activatedModel = false; bool activatedPostBootCommunication = false; - unsigned long firstExecutionTimestamp = 0; + Timestamp firstExecutionTimestamp; bool executedFirstTime = false; bool executedLongTime = false; public: - BootService(Context& context, std::shared_ptr filesystem); + BootService(Context& context); + ~BootService(); + + bool setup(); void loop(); - void setChargePointCredentials(JsonObject credentials); - void setChargePointCredentials(const char *credentials); //credentials: serialized BootNotification payload - std::unique_ptr getChargePointCredentials(); + bool setBootNotificationData(MO_BootNotificationData bnData); + const MO_BootNotificationData& getBootNotificationData(); void notifyRegistrationStatus(RegistrationStatus status); - void setRetryInterval(unsigned long interval); - - static bool loadBootStats(std::shared_ptr filesystem, BootStats& bstats); - static bool storeBootStats(std::shared_ptr filesystem, BootStats& bstats); + RegistrationStatus getRegistrationStatus(); - static bool recover(std::shared_ptr filesystem, BootStats& bstats); //delete all persistent files which could lead to a crash - - static bool migrate(std::shared_ptr filesystem, BootStats& bstats); //migrate persistent storage if running on a new MO version + void setRetryInterval(unsigned long interval); }; } +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Certificates/Certificate.cpp b/src/MicroOcpp/Model/Certificates/Certificate.cpp index 1de32094..160f646b 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate.cpp +++ b/src/MicroOcpp/Model/Certificates/Certificate.cpp @@ -4,19 +4,19 @@ #include -#if MO_ENABLE_CERT_MGMT +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #include #include -bool ocpp_cert_equals(const ocpp_cert_hash *h1, const ocpp_cert_hash *h2) { +bool mo_cert_equals(const mo_cert_hash *h1, const mo_cert_hash *h2) { return h1->hashAlgorithm == h2->hashAlgorithm && h1->serialNumberLen == h2->serialNumberLen && !memcmp(h1->serialNumber, h2->serialNumber, h1->serialNumberLen) && !memcmp(h1->issuerNameHash, h2->issuerNameHash, HashAlgorithmSize(h1->hashAlgorithm)) && !memcmp(h1->issuerKeyHash, h2->issuerKeyHash, HashAlgorithmSize(h1->hashAlgorithm)); } -int ocpp_cert_bytes_to_hex(char *dst, size_t dst_size, const unsigned char *src, size_t src_len) { +int mo_cert_bytes_to_hex(char *dst, size_t dst_size, const unsigned char *src, size_t src_len) { if (!dst || !dst_size || !src) { return -1; } @@ -37,15 +37,15 @@ int ocpp_cert_bytes_to_hex(char *dst, size_t dst_size, const unsigned char *src, return (int)hexLen; } -int ocpp_cert_print_issuerNameHash(const ocpp_cert_hash *src, char *buf, size_t size) { - return ocpp_cert_bytes_to_hex(buf, size, src->issuerNameHash, HashAlgorithmSize(src->hashAlgorithm)); +int mo_cert_print_issuerNameHash(const mo_cert_hash *src, char *buf, size_t size) { + return mo_cert_bytes_to_hex(buf, size, src->issuerNameHash, HashAlgorithmSize(src->hashAlgorithm)); } -int ocpp_cert_print_issuerKeyHash(const ocpp_cert_hash *src, char *buf, size_t size) { - return ocpp_cert_bytes_to_hex(buf, size, src->issuerKeyHash, HashAlgorithmSize(src->hashAlgorithm)); +int mo_cert_print_issuerKeyHash(const mo_cert_hash *src, char *buf, size_t size) { + return mo_cert_bytes_to_hex(buf, size, src->issuerKeyHash, HashAlgorithmSize(src->hashAlgorithm)); } -int ocpp_cert_print_serialNumber(const ocpp_cert_hash *src, char *buf, size_t size) { +int mo_cert_print_serialNumber(const mo_cert_hash *src, char *buf, size_t size) { if (!buf || !size) { return -1; @@ -63,7 +63,7 @@ int ocpp_cert_print_serialNumber(const ocpp_cert_hash *src, char *buf, size_t si } if (src->serialNumberLen > 1) { - auto ret = ocpp_cert_bytes_to_hex(buf + (size_t)hexLen, size - (size_t)hexLen, src->serialNumber + 1, src->serialNumberLen - 1); + auto ret = mo_cert_bytes_to_hex(buf + (size_t)hexLen, size - (size_t)hexLen, src->serialNumber + 1, src->serialNumberLen - 1); if (ret < 0) { return -1; } @@ -73,7 +73,7 @@ int ocpp_cert_print_serialNumber(const ocpp_cert_hash *src, char *buf, size_t si return hexLen; } -int ocpp_cert_hex_to_bytes(unsigned char *dst, size_t dst_size, const char *hex_src) { +int mo_cert_hex_to_bytes(unsigned char *dst, size_t dst_size, const char *hex_src) { if (!dst || !dst_size || !hex_src) { return -1; } @@ -124,8 +124,8 @@ int ocpp_cert_hex_to_bytes(unsigned char *dst, size_t dst_size, const char *hex_ return (int)write_len; } -int ocpp_cert_set_issuerNameHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm) { - auto ret = ocpp_cert_hex_to_bytes(dst->issuerNameHash, sizeof(dst->issuerNameHash), hex_src); +int mo_cert_set_issuerNameHash(mo_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm) { + auto ret = mo_cert_hex_to_bytes(dst->issuerNameHash, sizeof(dst->issuerNameHash), hex_src); if (ret < 0) { return ret; @@ -138,8 +138,8 @@ int ocpp_cert_set_issuerNameHash(ocpp_cert_hash *dst, const char *hex_src, HashA return ret; } -int ocpp_cert_set_issuerKeyHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm) { - auto ret = ocpp_cert_hex_to_bytes(dst->issuerKeyHash, sizeof(dst->issuerNameHash), hex_src); +int mo_cert_set_issuerKeyHash(mo_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm) { + auto ret = mo_cert_hex_to_bytes(dst->issuerKeyHash, sizeof(dst->issuerNameHash), hex_src); if (ret < 0) { return ret; @@ -152,8 +152,8 @@ int ocpp_cert_set_issuerKeyHash(ocpp_cert_hash *dst, const char *hex_src, HashAl return ret; } -int ocpp_cert_set_serialNumber(ocpp_cert_hash *dst, const char *hex_src) { - auto ret = ocpp_cert_hex_to_bytes(dst->serialNumber, sizeof(dst->serialNumber), hex_src); +int mo_cert_set_serialNumber(mo_cert_hash *dst, const char *hex_src) { + auto ret = mo_cert_hex_to_bytes(dst->serialNumber, sizeof(dst->serialNumber), hex_src); if (ret < 0) { return ret; @@ -164,4 +164,4 @@ int ocpp_cert_set_serialNumber(ocpp_cert_hash *dst, const char *hex_src) { return ret; } -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT diff --git a/src/MicroOcpp/Model/Certificates/Certificate.h b/src/MicroOcpp/Model/Certificates/Certificate.h index 33574351..d39596dc 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate.h +++ b/src/MicroOcpp/Model/Certificates/Certificate.h @@ -7,7 +7,7 @@ #include -#if MO_ENABLE_CERT_MGMT +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #include @@ -83,47 +83,47 @@ typedef enum HashAlgorithmType { alg == HashAlgorithmType_SHA384 ? 48 : \ alg == HashAlgorithmType_SHA512 ? 64 : 0) -typedef struct ocpp_cert_hash { +typedef struct mo_cert_hash { enum HashAlgorithmType hashAlgorithm; unsigned char issuerNameHash [64]; // hash buf can hold 64 bytes (SHA512). Actual hash size is determined by hash algorithm unsigned char issuerKeyHash [64]; unsigned char serialNumber [20]; size_t serialNumberLen; // length of serial number in bytes -} ocpp_cert_hash; +} mo_cert_hash; -bool ocpp_cert_equals(const ocpp_cert_hash *h1, const ocpp_cert_hash *h2); +bool mo_cert_equals(const mo_cert_hash *h1, const mo_cert_hash *h2); // Max size of hex-encoded cert hash components #define MO_CERT_HASH_ISSUER_NAME_KEY_SIZE (128 + 1) // hex-encoding needs two characters per byte + terminating null-byte #define MO_CERT_HASH_SERIAL_NUMBER_SIZE (40 + 1) /* - * Print the issuerNameHash of ocpp_cert_hash as hex-encoded string (e.g. "0123AB") into buf. Bufsize MO_CERT_HASH_ISSUER_NAME_KEY_SIZE is always enough + * Print the issuerNameHash of mo_cert_hash as hex-encoded string (e.g. "0123AB") into buf. Bufsize MO_CERT_HASH_ISSUER_NAME_KEY_SIZE is always enough * * Returns the length not counting the terminating 0 on success, -1 on failure */ -int ocpp_cert_print_issuerNameHash(const ocpp_cert_hash *src, char *buf, size_t size); +int mo_cert_print_issuerNameHash(const mo_cert_hash *src, char *buf, size_t size); /* - * Print the issuerKeyHash of ocpp_cert_hash as hex-encoded string (e.g. "0123AB") into buf. Bufsize MO_CERT_HASH_ISSUER_NAME_KEY_SIZE is always enough + * Print the issuerKeyHash of mo_cert_hash as hex-encoded string (e.g. "0123AB") into buf. Bufsize MO_CERT_HASH_ISSUER_NAME_KEY_SIZE is always enough * * Returns the length not counting the terminating 0 on success, -1 on failure */ -int ocpp_cert_print_issuerKeyHash(const ocpp_cert_hash *src, char *buf, size_t size); +int mo_cert_print_issuerKeyHash(const mo_cert_hash *src, char *buf, size_t size); /* - * Print the serialNumber of ocpp_cert_hash as hex-encoded string without leading 0s (e.g. "123AB") into buf. Bufsize MO_CERT_HASH_SERIAL_NUMBER_SIZE is always enough + * Print the serialNumber of mo_cert_hash as hex-encoded string without leading 0s (e.g. "123AB") into buf. Bufsize MO_CERT_HASH_SERIAL_NUMBER_SIZE is always enough * * Returns the length not counting the terminating 0 on success, -1 on failure */ -int ocpp_cert_print_serialNumber(const ocpp_cert_hash *src, char *buf, size_t size); +int mo_cert_print_serialNumber(const mo_cert_hash *src, char *buf, size_t size); -int ocpp_cert_set_issuerNameHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm); +int mo_cert_set_issuerNameHash(mo_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm); -int ocpp_cert_set_issuerKeyHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm); +int mo_cert_set_issuerKeyHash(mo_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm); -int ocpp_cert_set_serialNumber(ocpp_cert_hash *dst, const char *hex_src); +int mo_cert_set_serialNumber(mo_cert_hash *dst, const char *hex_src); #ifdef __cplusplus } //extern "C" @@ -132,7 +132,7 @@ int ocpp_cert_set_serialNumber(ocpp_cert_hash *dst, const char *hex_src); namespace MicroOcpp { -using CertificateHash = ocpp_cert_hash; +using CertificateHash = mo_cert_hash; /* * See OCPP 2.0.1 part 2 Data Type 2.5 @@ -160,5 +160,5 @@ class CertificateStore { } //namespace MicroOcpp #endif //__cplusplus -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #endif diff --git a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp index 73314158..f55993e7 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp +++ b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp @@ -4,7 +4,7 @@ #include -#if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS #include @@ -13,9 +13,10 @@ #include #include +#include #include -bool ocpp_get_cert_hash(mbedtls_x509_crt& cacert, HashAlgorithmType hashAlg, ocpp_cert_hash *out) { +bool mo_get_cert_hash(mbedtls_x509_crt& cacert, HashAlgorithmType hashAlg, mo_cert_hash *out) { if (cacert.next) { MO_DBG_ERR("only sole root certs supported"); @@ -112,7 +113,7 @@ bool ocpp_get_cert_hash(mbedtls_x509_crt& cacert, HashAlgorithmType hashAlg, ocp return true; } -bool ocpp_get_cert_hash(const unsigned char *buf, size_t len, HashAlgorithmType hashAlg, ocpp_cert_hash *out) { +bool mo_get_cert_hash(const unsigned char *buf, size_t len, HashAlgorithmType hashAlg, mo_cert_hash *out) { mbedtls_x509_crt cacert; mbedtls_x509_crt_init(&cacert); @@ -121,7 +122,7 @@ bool ocpp_get_cert_hash(const unsigned char *buf, size_t len, HashAlgorithmType int ret; if((ret = mbedtls_x509_crt_parse(&cacert, buf, len + 1)) >= 0) { - success = ocpp_get_cert_hash(cacert, hashAlg, out); + success = mo_get_cert_hash(cacert, hashAlg, out); } else { char err [100]; mbedtls_strerror(ret, err, 100); @@ -136,55 +137,62 @@ namespace MicroOcpp { class CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged { private: - std::shared_ptr filesystem; + MO_FilesystemAdapter *filesystem = nullptr; + + bool getCertHash(const char *path, HashAlgorithmType hashAlg, CertificateHash& out) { + + MO_File *file = nullptr; + unsigned char *buf = nullptr; + bool success = false; - bool getCertHash(const char *fn, HashAlgorithmType hashAlg, CertificateHash& out) { size_t fsize; - if (filesystem->stat(fn, &fsize) != 0) { - MO_DBG_ERR("certificate does not exist: %s", fn); - return false; + if (filesystem->stat(path, &fsize) != 0) { + MO_DBG_ERR("certificate does not exist: %s", path); + goto exit; } if (fsize >= MO_MAX_CERT_SIZE) { - MO_DBG_ERR("cert file exceeds limit: %s, %zuB", fn, fsize); - return false; + MO_DBG_ERR("cert file exceeds limit: %s, %zuB", path, fsize); + goto exit; } - auto file = filesystem->open(fn, "r"); + file = filesystem->open(path, "r"); if (!file) { - MO_DBG_ERR("could not open file: %s", fn); - return false; + MO_DBG_ERR("could not open file: %s", path); + goto exit; } - unsigned char *buf = static_cast(MO_MALLOC(getMemoryTag(), fsize + 1)); + buf = static_cast(MO_MALLOC(getMemoryTag(), fsize + 1)); if (!buf) { MO_DBG_ERR("OOM"); - return false; + goto exit; } - bool success = true; - size_t ret; - if ((ret = file->read((char*) buf, fsize)) != fsize) { + if ((ret = filesystem->read(file, (char*) buf, fsize)) != fsize) { MO_DBG_ERR("read error: %zu (expect %zu)", ret, fsize); - success = false; + goto exit; } buf[fsize] = '\0'; - if (success) { - success &= ocpp_get_cert_hash(buf, fsize, hashAlg, &out); + if (!mo_get_cert_hash(buf, fsize, hashAlg, &out)) { + MO_DBG_ERR("could not read cert: %s", path); + goto exit; } - if (!success) { - MO_DBG_ERR("could not read cert: %s", fn); - } + //success + success = true; + exit: + if (file) { + (void)filesystem->close(file); //read-only + } MO_FREE(buf); return success; } public: - CertificateStoreMbedTLS(std::shared_ptr filesystem) + CertificateStoreMbedTLS(MO_FilesystemAdapter *filesystem) : MemoryManaged("v201.Certificates.CertificateStoreMbedTLS"), filesystem(filesystem) { } @@ -210,16 +218,16 @@ class CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged { continue; } - for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) { - char fn [MO_MAX_PATH_SIZE]; - if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) { + for (unsigned int i = 0; i < MO_CERT_STORE_SIZE; i++) { + char path [MO_MAX_PATH_SIZE]; + if (!printCertPath(filesystem, path, sizeof(path), certTypeFnStr, i)) { MO_DBG_ERR("internal error"); out.clear(); break; } size_t msize; - if (filesystem->stat(fn, &msize) != 0) { + if (filesystem->stat(path, &msize) != 0) { continue; //no cert installed at this slot } @@ -228,8 +236,8 @@ class CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged { rootCert.certificateType = certType; - if (!getCertHash(fn, HashAlgorithmType_SHA256, rootCert.certificateHashData)) { - MO_DBG_ERR("could not create hash: %s", fn); + if (!getCertHash(path, HashAlgorithmType_SHA256, rootCert.certificateHashData)) { + MO_DBG_ERR("could not create hash: %s", path); out.pop_back(); continue; } @@ -246,31 +254,31 @@ class CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged { //enumerate all certs possibly installed by this CertStore implementation for (const char *certTypeFnStr : {MO_CERT_FN_CSMS_ROOT, MO_CERT_FN_MANUFACTURER_ROOT}) { - for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) { + for (unsigned int i = 0; i < MO_CERT_STORE_SIZE; i++) { - char fn [MO_MAX_PATH_SIZE] = {'\0'}; //cert fn on flash storage + char path [MO_MAX_PATH_SIZE]; //cert path on flash storage - if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) { + if (!printCertPath(filesystem, path, sizeof(path), certTypeFnStr, i)) { MO_DBG_ERR("internal error"); return DeleteCertificateStatus_Failed; } size_t msize; - if (filesystem->stat(fn, &msize) != 0) { + if (filesystem->stat(path, &msize) != 0) { continue; //no cert installed at this slot } CertificateHash probe; - if (!getCertHash(fn, hash.hashAlgorithm, probe)) { - MO_DBG_ERR("could not create hash: %s", fn); + if (!getCertHash(path, hash.hashAlgorithm, probe)) { + MO_DBG_ERR("could not create hash: %s", path); err = true; continue; } - if (ocpp_cert_equals(&probe, &hash)) { + if (mo_cert_equals(&probe, &hash)) { //found, delete - bool success = filesystem->remove(fn); + bool success = filesystem->remove(path); return success ? DeleteCertificateStatus_Accepted : DeleteCertificateStatus_Failed; @@ -302,7 +310,7 @@ class CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged { //check if this implementation is able to parse incoming cert CertificateHash certId; - if (!ocpp_get_cert_hash((const unsigned char*)certificate, strlen(certificate), HashAlgorithmType_SHA256, &certId)) { + if (!mo_get_cert_hash((const unsigned char*)certificate, strlen(certificate), HashAlgorithmType_SHA256, &certId)) { MO_DBG_ERR("unable to parse cert"); return InstallCertificateStatus_Rejected; } @@ -313,28 +321,28 @@ class CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged { MO_DBG_DEBUG("hashAlgorithm: %s", HashAlgorithmLabel(certId.hashAlgorithm)); char buf [MO_CERT_HASH_ISSUER_NAME_KEY_SIZE]; - ocpp_cert_print_issuerNameHash(&certId, buf, sizeof(buf)); + mo_cert_print_issuerNameHash(&certId, buf, sizeof(buf)); MO_DBG_DEBUG("issuerNameHash: %s", buf); - ocpp_cert_print_issuerKeyHash(&certId, buf, sizeof(buf)); + mo_cert_print_issuerKeyHash(&certId, buf, sizeof(buf)); MO_DBG_DEBUG("issuerKeyHash: %s", buf); - ocpp_cert_print_serialNumber(&certId, buf, sizeof(buf)); + mo_cert_print_serialNumber(&certId, buf, sizeof(buf)); MO_DBG_DEBUG("serialNumber: %s", buf); } -#endif // MO_DBG_LEVEL >= MO_DL_DEBUG +#endif //MO_DBG_LEVEL >= MO_DL_DEBUG //check if cert is already stored on flash auto installedCerts = makeVector(getMemoryTag()); auto ret = getCertificateIds({certTypeGetType}, installedCerts); if (ret == GetInstalledCertificateStatus_Accepted) { for (auto &installedCert : installedCerts) { - if (ocpp_cert_equals(&installedCert.certificateHashData, &certId)) { + if (mo_cert_equals(&installedCert.certificateHashData, &certId)) { MO_DBG_INFO("certificate already installed"); return InstallCertificateStatus_Accepted; } for (auto& installedChild : installedCert.childCertificateHashData) { - if (ocpp_cert_equals(&installedChild, &certId)) { + if (mo_cert_equals(&installedChild, &certId)) { MO_DBG_INFO("certificate already installed"); return InstallCertificateStatus_Accepted; } @@ -342,51 +350,58 @@ class CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged { } } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; //cert fn on flash storage + char path [MO_MAX_PATH_SIZE] = {'\0'}; //cert path on flash storage //check for free cert slot - for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) { - if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) { - MO_DBG_ERR("invalid cert fn"); + for (unsigned int i = 0; i < MO_CERT_STORE_SIZE; i++) { + if (!printCertPath(filesystem, path, sizeof(path), certTypeFnStr, i)) { + MO_DBG_ERR("invalid cert path"); return InstallCertificateStatus_Failed; } size_t msize; - if (filesystem->stat(fn, &msize) != 0) { - //found free slot; fn contains result + if (filesystem->stat(path, &msize) != 0) { + //found free slot; path contains result break; } else { - //this slot is already occupied; invalidate fn and try next - fn[0] = '\0'; + //this slot is already occupied; invalidate path and try next + path[0] = '\0'; } } - if (fn[0] == '\0') { + if (path[0] == '\0') { MO_DBG_ERR("exceed maximum number of certs; must delete before"); return InstallCertificateStatus_Rejected; } - auto file = filesystem->open(fn, "w"); + auto file = filesystem->open(path, "w"); if (!file) { MO_DBG_ERR("could not open file"); return InstallCertificateStatus_Failed; } size_t cert_len = strlen(certificate); - auto written = file->write(certificate, cert_len); + auto written = filesystem->write(file, certificate, cert_len); if (written < cert_len) { MO_DBG_ERR("file write error"); - file.reset(); - filesystem->remove(fn); + (void)filesystem->close(file); + filesystem->remove(path); return InstallCertificateStatus_Failed; } - MO_DBG_INFO("installed certificate: %s", fn); - return InstallCertificateStatus_Accepted; + bool closeSuccess = filesystem->close(file); + if (closeSuccess) { + MO_DBG_INFO("installed certificate: %s", path); + return InstallCertificateStatus_Accepted; + } else { + MO_DBG_ERR("Error writing file %s", path); + filesystem->remove(path); + return InstallCertificateStatus_Failed; + } } }; -std::unique_ptr makeCertificateStoreMbedTLS(std::shared_ptr filesystem) { +std::unique_ptr makeCertificateStoreMbedTLS(MO_FilesystemAdapter *filesystem) { if (!filesystem) { MO_DBG_WARN("default Certificate Store requires FS"); return nullptr; @@ -394,21 +409,26 @@ std::unique_ptr makeCertificateStoreMbedTLS(std::shared_ptr(new CertificateStoreMbedTLS(filesystem)); } -bool printCertFn(const char *certType, size_t index, char *buf, size_t bufsize) { - if (!certType || !*certType || index >= MO_CERT_STORE_SIZE || !buf) { +bool printCertPath(MO_FilesystemAdapter *filesystem, char *path, size_t size, const char *certType, unsigned int index) { + if (!certType || !*certType || index >= MO_CERT_STORE_SIZE || !path) { MO_DBG_ERR("invalid args"); return false; } - auto ret = snprintf(buf, bufsize, MO_FILENAME_PREFIX MO_CERT_FN_PREFIX "%s" "-%zu" MO_CERT_FN_SUFFIX, - certType, index); - if (ret < 0 || ret >= (int)bufsize) { - MO_DBG_ERR("fn error: %i", ret); + char fname [MO_MAX_PATH_SIZE]; + auto ret = snprintf(fname, sizeof(fname), "%s-%.*u" MO_CERT_FN_SUFFIX, certType, MO_CERT_STORE_DIGITS, index); + if (ret < 0 || (size_t)ret >= sizeof(fname)) { + MO_DBG_ERR("fname error: %i", ret); return false; } - return true; + + auto ret2 = FilesystemUtils::printPath(filesystem, path, size, fname); + if (!ret2) { + MO_DBG_ERR("path error: %s", fname); + } + return ret2; } } //namespace MicroOcpp -#endif //MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS diff --git a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h index e60a2734..80c30cbe 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h +++ b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h @@ -16,7 +16,7 @@ #define MO_ENABLE_CERT_STORE_MBEDTLS MO_ENABLE_MBEDTLS #endif -#if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS /* * Provide certificate interpreter to facilitate cert store in C. A full implementation is only available for C++ @@ -27,7 +27,7 @@ extern "C" { #endif -bool ocpp_get_cert_hash(const unsigned char *cert, size_t len, enum HashAlgorithmType hashAlg, ocpp_cert_hash *out); +bool mo_get_cert_hash(const unsigned char *cert, size_t len, enum HashAlgorithmType hashAlg, mo_cert_hash *out); #ifdef __cplusplus } //extern "C" @@ -49,22 +49,26 @@ bool ocpp_get_cert_hash(const unsigned char *cert, size_t len, enum HashAlgorith #endif #ifndef MO_CERT_FN_MANUFACTURER_ROOT -#define MO_CERT_FN_MANUFACTURER_ROOT "mfact" +#define MO_CERT_FN_MANUFACTURER_ROOT "mfct" #endif #ifndef MO_CERT_STORE_SIZE #define MO_CERT_STORE_SIZE 3 //max number of certs per certificate type (e.g. CSMS root CA, Manufacturer root CA) #endif +#ifndef MO_CERT_STORE_DIGITS +#define MO_CERT_STORE_DIGITS 1 //digits needed to print MO_CERT_STORE_SIZE-1 (="3", i.e. 1 digit) +#endif + namespace MicroOcpp { -std::unique_ptr makeCertificateStoreMbedTLS(std::shared_ptr filesystem); +std::unique_ptr makeCertificateStoreMbedTLS(MO_FilesystemAdapter *filesystem); -bool printCertFn(const char *certType, size_t index, char *buf, size_t bufsize); +bool printCertPath(MO_FilesystemAdapter *filesystem, char *path, size_t size, const char *certType, unsigned int index); } //namespace MicroOcpp #endif //def __cplusplus -#endif //MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS #endif diff --git a/src/MicroOcpp/Model/Certificates/CertificateService.cpp b/src/MicroOcpp/Model/Certificates/CertificateService.cpp index 22e5ab6d..1deda36d 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateService.cpp +++ b/src/MicroOcpp/Model/Certificates/CertificateService.cpp @@ -4,32 +4,54 @@ #include -#if MO_ENABLE_CERT_MGMT - -#include +#include #include #include #include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT using namespace MicroOcpp; CertificateService::CertificateService(Context& context) - : MemoryManaged("v201.Certificates.CertificateService"), context(context) { - - context.getOperationRegistry().registerOperation("DeleteCertificate", [this] () { - return new Ocpp201::DeleteCertificate(*this);}); - context.getOperationRegistry().registerOperation("GetInstalledCertificateIds", [this] () { - return new Ocpp201::GetInstalledCertificateIds(*this);}); - context.getOperationRegistry().registerOperation("InstallCertificate", [this] () { - return new Ocpp201::InstallCertificate(*this);}); + : MemoryManaged("v16/v201.Certificates.CertificateService"), context(context) { + } -void CertificateService::setCertificateStore(std::unique_ptr certStore) { - this->certStore = std::move(certStore); +bool CertificateService::setup() { + certStore = context.getCertificateStore(); + if (!certStore) { + MO_DBG_WARN("CertMgmt disabled, because CertificateStore implementation is missing"); + return true; // not a critical failure + } + + #if MO_ENABLE_V16 + if (context.getOcppVersion() == MO_OCPP_V16) { + context.getMessageService().registerOperation("DeleteCertificate", [] (Context& context) -> Operation* { + return new DeleteCertificate(*context.getModel16().getCertificateService());}); + context.getMessageService().registerOperation("GetInstalledCertificateIds", [] (Context& context) -> Operation* { + return new GetInstalledCertificateIds(*context.getModel16().getCertificateService());}); + context.getMessageService().registerOperation("InstallCertificate", [] (Context& context) -> Operation* { + return new InstallCertificate(*context.getModel16().getCertificateService());}); + } + #endif + #if MO_ENABLE_V201 + if (context.getOcppVersion() == MO_OCPP_V201) { + context.getMessageService().registerOperation("DeleteCertificate", [] (Context& context) -> Operation* { + return new DeleteCertificate(*context.getModel201().getCertificateService());}); + context.getMessageService().registerOperation("GetInstalledCertificateIds", [] (Context& context) -> Operation* { + return new GetInstalledCertificateIds(*context.getModel201().getCertificateService());}); + context.getMessageService().registerOperation("InstallCertificate", [] (Context& context) -> Operation* { + return new InstallCertificate(*context.getModel201().getCertificateService());}); + } + #endif + + return true; } CertificateStore *CertificateService::getCertificateStore() { - return certStore.get(); + return certStore; } -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT diff --git a/src/MicroOcpp/Model/Certificates/CertificateService.h b/src/MicroOcpp/Model/Certificates/CertificateService.h index 31d10048..ab09c120 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateService.h +++ b/src/MicroOcpp/Model/Certificates/CertificateService.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License /* @@ -16,7 +16,7 @@ #include -#if MO_ENABLE_CERT_MGMT +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #include #include @@ -31,15 +31,16 @@ class Context; class CertificateService : public MemoryManaged { private: Context& context; - std::unique_ptr certStore; + CertificateStore *certStore = nullptr; public: CertificateService(Context& context); - void setCertificateStore(std::unique_ptr certStore); + bool setup(); + CertificateStore *getCertificateStore(); }; } -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #endif diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp index d316140c..2f12da5f 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp @@ -4,7 +4,7 @@ #include -#if MO_ENABLE_CERT_MGMT +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #include #include @@ -18,9 +18,9 @@ namespace MicroOcpp { */ class CertificateStoreC : public CertificateStore, public MemoryManaged { private: - ocpp_cert_store *certstore = nullptr; + mo_cert_store *certstore = nullptr; public: - CertificateStoreC(ocpp_cert_store *certstore) : MemoryManaged("v201.Certificates.CertificateStoreC"), certstore(certstore) { + CertificateStoreC(mo_cert_store *certstore) : MemoryManaged("v201.Certificates.CertificateStoreC"), certstore(certstore) { } @@ -29,7 +29,7 @@ class CertificateStoreC : public CertificateStore, public MemoryManaged { GetInstalledCertificateStatus getCertificateIds(const Vector& certificateType, Vector& out) override { out.clear(); - ocpp_cert_chain_hash *cch; + mo_cert_chain_hash *cch; auto ret = certstore->getCertificateIds(certstore->user_data, &certificateType[0], certificateType.size(), &cch); if (ret == GetInstalledCertificateStatus_NotFound || !cch) { @@ -38,15 +38,15 @@ class CertificateStoreC : public CertificateStore, public MemoryManaged { bool err = false; - for (ocpp_cert_chain_hash *it = cch; it && !err; it = it->next) { + for (mo_cert_chain_hash *it = cch; it && !err; it = it->next) { out.emplace_back(); auto &chd_el = out.back(); chd_el.certificateType = it->certType; - memcpy(&chd_el.certificateHashData, &it->certHashData, sizeof(ocpp_cert_hash)); + memcpy(&chd_el.certificateHashData, &it->certHashData, sizeof(mo_cert_hash)); } while (cch) { - ocpp_cert_chain_hash *el = cch; + mo_cert_chain_hash *el = cch; cch = cch->next; el->invalidate(el); } @@ -69,9 +69,9 @@ class CertificateStoreC : public CertificateStore, public MemoryManaged { } }; -std::unique_ptr makeCertificateStoreCwrapper(ocpp_cert_store *certstore) { +std::unique_ptr makeCertificateStoreCwrapper(mo_cert_store *certstore) { return std::unique_ptr(new CertificateStoreC(certstore)); } } //namespace MicroOcpp -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.h b/src/MicroOcpp/Model/Certificates/Certificate_c.h index 3b0bc858..0dbee5ca 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.h +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.h @@ -7,7 +7,7 @@ #include -#if MO_ENABLE_CERT_MGMT +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #include @@ -17,25 +17,25 @@ extern "C" { #endif -typedef struct ocpp_cert_chain_hash { +typedef struct mo_cert_chain_hash { void *user_data; //set this at your choice. MO passes it back to the functions below enum GetCertificateIdType certType; - ocpp_cert_hash certHashData; - //ocpp_cert_hash *childCertificateHashData; + mo_cert_hash certHashData; + //mo_cert_hash *childCertificateHashData; - struct ocpp_cert_chain_hash *next; //link to next list element if result of getCertificateIds + struct mo_cert_chain_hash *next; //link to next list element if result of getCertificateIds void (*invalidate)(void *user_data); //free resources here. Guaranteed to be called -} ocpp_cert_chain_hash; +} mo_cert_chain_hash; -typedef struct ocpp_cert_store { +typedef struct mo_cert_store { void *user_data; //set this at your choice. MO passes it back to the functions below - enum GetInstalledCertificateStatus (*getCertificateIds)(void *user_data, const enum GetCertificateIdType certType [], size_t certTypeLen, ocpp_cert_chain_hash **out); - enum DeleteCertificateStatus (*deleteCertificate)(void *user_data, const ocpp_cert_hash *hash); + enum GetInstalledCertificateStatus (*getCertificateIds)(void *user_data, const enum GetCertificateIdType certType [], size_t certTypeLen, mo_cert_chain_hash **out); + enum DeleteCertificateStatus (*deleteCertificate)(void *user_data, const mo_cert_hash *hash); enum InstallCertificateStatus (*installCertificate)(void *user_data, enum InstallCertificateType certType, const char *cert); -} ocpp_cert_store; +} mo_cert_store; #ifdef __cplusplus } //extern "C" @@ -44,11 +44,11 @@ typedef struct ocpp_cert_store { namespace MicroOcpp { -std::unique_ptr makeCertificateStoreCwrapper(ocpp_cert_store *certstore); +std::unique_ptr makeCertificateStoreCwrapper(mo_cert_store *certstore); } //namespace MicroOcpp #endif //__cplusplus -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #endif diff --git a/src/MicroOcpp/Model/Common/EvseId.h b/src/MicroOcpp/Model/Common/EvseId.h new file mode 100644 index 00000000..900d987f --- /dev/null +++ b/src/MicroOcpp/Model/Common/EvseId.h @@ -0,0 +1,42 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_EVSEID_H +#define MO_EVSEID_H + +#include + +// number of EVSE IDs (including 0). On a charger with one physical connector, NUM_EVSEID is 2 +#ifndef MO_NUM_EVSEID +// Use MO_NUMCONNECTORS if defined, for backwards compatibility +#if defined(MO_NUMCONNECTORS) +#define MO_NUM_EVSEID MO_NUMCONNECTORS +#else +#define MO_NUM_EVSEID 2 +#endif +#endif //MO_NUM_EVSEID + +#ifndef MO_NUM_EVSEID_DIGITS +#define MO_NUM_EVSEID_DIGITS 1 //digits needed to print MO_NUM_EVSEID-1 (="1", i.e. 1 digit) +#endif + +#if MO_ENABLE_V201 +#ifdef __cplusplus + +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 //__cplusplus +#endif //MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Model/Common/Mutability.h b/src/MicroOcpp/Model/Common/Mutability.h new file mode 100644 index 00000000..e563faf6 --- /dev/null +++ b/src/MicroOcpp/Model/Common/Mutability.h @@ -0,0 +1,41 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_MUTABILITY_H +#define MO_MUTABILITY_H + +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 + +#ifdef __cplusplus +extern "C" { +#endif + +//MutabilityEnumType (3.58), shared between Configuration and Variable +typedef enum { + MO_Mutability_ReadWrite, + MO_Mutability_ReadOnly, + MO_Mutability_WriteOnly, + MO_Mutability_None +} MO_Mutability; + +#ifdef __cplusplus +} //extern "C" + +namespace MicroOcpp { + +//MutabilityEnumType (3.58), shared between Configuration and Variable +enum class Mutability : uint8_t { + ReadWrite, + ReadOnly, + WriteOnly, + None +}; + +} //namespace MicroOcpp + +#endif //__cplusplus +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 +#endif //MO_MUTABILITY_H diff --git a/src/MicroOcpp/Model/Configuration/Configuration.cpp b/src/MicroOcpp/Model/Configuration/Configuration.cpp new file mode 100644 index 00000000..443f9363 --- /dev/null +++ b/src/MicroOcpp/Model/Configuration/Configuration.cpp @@ -0,0 +1,192 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#include + +#include +#include + +#if MO_ENABLE_V16 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +Configuration::~Configuration() { + +} + +void Configuration::setKey(const char *name) { + this->key = name; + updateMemoryTag("v16.Configuration.", name); +} +const char *Configuration::getKey() const { + return key; +} + +void Configuration::setInt(int val) { + MO_DBG_ERR("type err"); +} +void Configuration::setBool(bool val) { + MO_DBG_ERR("type err"); +} +bool Configuration::setString(const char *val) { + MO_DBG_ERR("type err"); + return false; +} + +int Configuration::getInt() { + MO_DBG_ERR("type err"); + return 0; +} +bool Configuration::getBool() { + MO_DBG_ERR("type err"); + return false; +} +const char *Configuration::getString() { + MO_DBG_ERR("type err"); + return ""; +} + +uint16_t Configuration::getWriteCount() { + return 0; +} + +bool Configuration::isRebootRequired() { + return rebootRequired; +} +void Configuration::setRebootRequired() { + rebootRequired = true; +} + +void Configuration::setMutability(Mutability mutability) { + this->mutability = mutability; +} + +Mutability Configuration::getMutability() { + return mutability; +} + +class ConfigurationConcrete : public Configuration { +private: + uint16_t writeCount = 0; + + union { + int valueInt = 0; + bool valueBool; + char *valueString; + }; + Type type = Type::Int; + + bool resetString(const char *val) { + if (type != Type::String) { + return false; + } + + char *nextString = nullptr; + + if (val) { + size_t size = strlen(val) + 1; + nextString = static_cast(MO_MALLOC(getMemoryTag(), size)); + if (!nextString) { + MO_DBG_ERR("OOM"); + return false; + } + memset(nextString, 0, size); + auto ret = snprintf(nextString, size, "%s", val); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("snprintf: %i", ret); + MO_FREE(nextString); + nextString = nullptr; + return false; + } + } + + MO_FREE(valueString); + valueString = nextString; + return true; + } + +public: + ConfigurationConcrete(Type type) : type(type) { } + ~ConfigurationConcrete() { + (void)resetString(nullptr); + } + + void setInt(int val) override { + #if MO_CONFIG_TYPECHECK + if (type != Type::Int) { + MO_DBG_ERR("type err"); + return; + } + #endif //MO_CONFIG_TYPECHECK + valueInt = val; + writeCount++; + } + void setBool(bool val) override { + #if MO_CONFIG_TYPECHECK + if (type != Type::Bool) { + MO_DBG_ERR("type err"); + return; + } + #endif //MO_CONFIG_TYPECHECK + valueBool = val; + writeCount++; + } + bool setString(const char *val) override { + #if MO_CONFIG_TYPECHECK + if (type != Type::String) { + MO_DBG_ERR("type err"); + return false; + } + #endif //MO_CONFIG_TYPECHECK + bool success = resetString(val); + if (success) { + writeCount++; + } + return success; + } + + // get Value of Configuration + int getInt() override { + #if MO_CONFIG_TYPECHECK + if (type != Type::Int) { + MO_DBG_ERR("type err"); + return 0; + } + #endif //MO_CONFIG_TYPECHECK + return valueInt; + } + bool getBool() override { + #if MO_CONFIG_TYPECHECK + if (type != Type::Bool) { + MO_DBG_ERR("type err"); + return false; + } + #endif //MO_CONFIG_TYPECHECK + return valueBool; + } + const char *getString() override { + #if MO_CONFIG_TYPECHECK + if (type != Type::String) { + MO_DBG_ERR("type err"); + return ""; + } + #endif //MO_CONFIG_TYPECHECK + return valueString ? valueString : ""; + } + + Type getType() override { + return type; + } + + uint16_t getWriteCount() override { + return writeCount; + } +}; + +std::unique_ptr MicroOcpp::Ocpp16::makeConfiguration(Configuration::Type type) { + return std::unique_ptr(new ConfigurationConcrete(type)); +} + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Configuration/Configuration.h b/src/MicroOcpp/Model/Configuration/Configuration.h new file mode 100644 index 00000000..a5fc6703 --- /dev/null +++ b/src/MicroOcpp/Model/Configuration/Configuration.h @@ -0,0 +1,82 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_CONFIGURATION_H +#define MO_CONFIGURATION_H + +#include +#include + +#include +#include +#include + +#if MO_ENABLE_V16 + +#ifndef MO_CONFIG_TYPECHECK +#define MO_CONFIG_TYPECHECK 1 +#endif + +namespace MicroOcpp { + +namespace Ocpp16 { + +// ConfigurationStatus (7.22) +enum class ConfigurationStatus : uint8_t { + Accepted, + Rejected, + RebootRequired, + NotSupported +}; + +/* + * Single Configuration Key-value pair + * + * Template method pattern: this is a super-class which has hook-methods for storing and fetching + * the value of the configuration. 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 Configuration : public MemoryManaged { +private: + const char *key = nullptr; + bool rebootRequired = false; + Mutability mutability = Mutability::ReadWrite; +public: + virtual ~Configuration(); + + void setKey(const char *key); //zero-copy + const char *getKey() const; + + // set Value of Configuration + virtual void setInt(int val); + virtual void setBool(bool val); + virtual bool setString(const char *val); + + // get Value of Configuration + virtual int getInt(); + virtual bool getBool(); + virtual const char *getString(); //always returns c-string (empty if undefined) + + enum class Type : uint8_t { + Int, + Bool, + String + }; + virtual Type getType() = 0; + + virtual uint16_t getWriteCount(); //get write count (implement this if MO should persist values on flash) + + void setRebootRequired(); + bool isRebootRequired(); + + void setMutability(Mutability mutability); //server-side mutability, i.e. how Get-/ChangeConfiguration can access this config + Mutability getMutability(); //server-side mutability, i.e. how Get-/ChangeConfiguration can access this config +}; + +std::unique_ptr makeConfiguration(Configuration::Type dtype); + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 +#endif diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp new file mode 100644 index 00000000..51880c2d --- /dev/null +++ b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp @@ -0,0 +1,258 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#include + +#include +#include +#include + +#if MO_ENABLE_V16 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +ConfigurationContainer::~ConfigurationContainer() { + +} + +bool ConfigurationContainer::commit() { + return true; +} + +ConfigurationContainerNonOwning::ConfigurationContainerNonOwning() : + ConfigurationContainer(), MemoryManaged("v16.Configuration.ConfigurationContainerNonOwning"), configurations(makeVector(getMemoryTag())) { + +} + +size_t ConfigurationContainerNonOwning::size() { + return configurations.size(); +} + +Configuration *ConfigurationContainerNonOwning::getConfiguration(size_t i) { + return configurations[i]; +} + +Configuration *ConfigurationContainerNonOwning::getConfiguration(const char *key) { + for (size_t i = 0; i < configurations.size(); i++) { + auto& configuration = configurations[i]; + if (!strcmp(configuration->getKey(), key)) { + return configuration; + } + } + return nullptr; +} + +bool ConfigurationContainerNonOwning::add(Configuration *configuration) { + configurations.push_back(configuration); + return true; +} + +bool ConfigurationContainerOwning::checkWriteCountUpdated() { + + decltype(trackWriteCount) writeCount = 0; + + for (size_t i = 0; i < configurations.size(); i++) { + writeCount += configurations[i]->getWriteCount(); + } + + bool updated = writeCount != trackWriteCount; + + trackWriteCount = writeCount; + + return updated; +} + +ConfigurationContainerOwning::ConfigurationContainerOwning() : + ConfigurationContainer(), MemoryManaged("v16.Configuration.ConfigurationContainerOwning"), configurations(makeVector(getMemoryTag())) { + +} + +ConfigurationContainerOwning::~ConfigurationContainerOwning() { + MO_FREE(filename); + filename = nullptr; + + for (size_t i = 0; i < configurations.size(); i++) { + delete configurations[i]; + } +} + +size_t ConfigurationContainerOwning::size() { + return configurations.size(); +} + +Configuration *ConfigurationContainerOwning::getConfiguration(size_t i) { + return configurations[i]; +} + +Configuration *ConfigurationContainerOwning::getConfiguration(const char *key) { + for (size_t i = 0; i < configurations.size(); i++) { + auto& configuration = configurations[i]; + if (!strcmp(configuration->getKey(), key)) { + return configuration; + } + } + return nullptr; +} + +bool ConfigurationContainerOwning::add(std::unique_ptr configuration) { + configurations.push_back(configuration.release()); + return true; +} + +void ConfigurationContainerOwning::setFilesystem(MO_FilesystemAdapter *filesystem) { + this->filesystem = filesystem; +} + +bool ConfigurationContainerOwning::setFilename(const char *filename) { + MO_FREE(this->filename); + this->filename = nullptr; + + if (filename && *filename) { + size_t fnsize = strlen(filename) + 1; + + this->filename = static_cast(MO_MALLOC(getMemoryTag(), fnsize)); + if (!this->filename) { + MO_DBG_ERR("OOM"); + return false; + } + + snprintf(this->filename, fnsize, "%s", filename); + } + + return true; +} + +const char *ConfigurationContainerOwning::getFilename() { + return filename; +} + +bool ConfigurationContainerOwning::load() { + if (loaded) { + return true; + } + + if (!filesystem || !filename || !strncmp(filename, MO_CONFIGURATION_VOLATILE, sizeof(MO_CONFIGURATION_VOLATILE) - 1)) { + return true; //persistency disabled - nothing to do + } + + JsonDoc doc (0); + + auto ret = FilesystemUtils::loadJson(filesystem, filename, doc, getMemoryTag()); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + MO_DBG_DEBUG("Populate FS: create configurations file"); + return commit(); + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", filename); + return false; + } + + JsonArray configurationsJson = doc["configurations"]; + + for (JsonObject stored : configurationsJson) { + + const char *key = stored["key"] | ""; + if (!*key) { + MO_DBG_ERR("corrupt config"); + continue; + } + + if (!stored.containsKey("value")) { + MO_DBG_ERR("corrupt config"); + continue; + } + + auto configurationPtr = getConfiguration(key); + if (!configurationPtr) { + MO_DBG_ERR("loaded configuration does not exist: %s, %s", filename, key); + continue; + } + + auto& configuration = *configurationPtr; + + switch (configuration.getType()) { + case Configuration::Type::Int: + configuration.setInt(stored["value"] | 0); + break; + case Configuration::Type::Bool: + configuration.setBool(stored["value"] | false); + break; + case Configuration::Type::String: + if (!configuration.setString(stored["value"] | "")) { + MO_DBG_ERR("value error: %s, %s", filename, key); + continue; + } + break; + } + } + + checkWriteCountUpdated(); // update trackWriteCount after load is completed + + MO_DBG_DEBUG("Initialization finished"); + loaded = true; + return true; +} + +bool ConfigurationContainerOwning::commit() { + if (!filesystem || !filename || !strncmp(filename, MO_CONFIGURATION_VOLATILE, sizeof(MO_CONFIGURATION_VOLATILE) - 1)) { + //persistency disabled - nothing to do + return true; + } + + if (!checkWriteCountUpdated()) { + return true; //nothing to be done + } + + size_t jsonCapacity = JSON_ARRAY_SIZE(configurations.size()); //configurations array + jsonCapacity += configurations.size() * JSON_OBJECT_SIZE(3); //config entries in array + + if (jsonCapacity > MO_MAX_JSON_CAPACITY) { + MO_DBG_ERR("configs JSON exceeds maximum capacity (%s, %zu entries). Crop configs file (by FCFS)", getFilename(), configurations.size()); + jsonCapacity = MO_MAX_JSON_CAPACITY; + } + + auto doc = initJsonDoc(getMemoryTag(), jsonCapacity); + + JsonArray configurationsJson = doc.createNestedArray("configurations"); + + for (size_t i = 0; i < configurations.size(); i++) { + auto& configuration = *configurations[i]; + + auto stored = configurationsJson.createNestedObject(); + + stored["key"] = configuration.getKey(); + + switch (configuration.getType()) { + case Configuration::Type::Int: + stored["value"] = configuration.getInt(); + break; + case Configuration::Type::Bool: + stored["value"] = configuration.getBool(); + break; + case Configuration::Type::String: + stored["value"] = configuration.getString(); + break; + } + } + + auto ret = FilesystemUtils::storeJson(filesystem, filename, doc); + bool success = (ret == FilesystemUtils::StoreStatus::Success); + + if (success) { + MO_DBG_DEBUG("Saving configurations finished"); + } else { + MO_DBG_ERR("could not save configurations file: %s %i", filename, (int)ret); + } + + return success; +} + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.h b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.h new file mode 100644 index 00000000..65ec383c --- /dev/null +++ b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.h @@ -0,0 +1,78 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_CONFIGURATIONCONTAINER_H +#define MO_CONFIGURATIONCONTAINER_H + +#include + +#include +#include +#include + +#if MO_ENABLE_V16 + +#define MO_CONFIGURATION_FN "ocpp-config.jsn" +#define MO_CONFIGURATION_VOLATILE "/volatile" +#define MO_KEYVALUE_FN "client-state.jsn" + +namespace MicroOcpp { +namespace Ocpp16 { + +class ConfigurationContainer { +public: + virtual ~ConfigurationContainer(); + virtual size_t size() = 0; + virtual Configuration *getConfiguration(size_t i) = 0; + virtual Configuration *getConfiguration(const char *key) = 0; + + virtual bool commit(); +}; + +class ConfigurationContainerNonOwning : public ConfigurationContainer, public MemoryManaged { +private: + Vector configurations; +public: + ConfigurationContainerNonOwning(); + + size_t size() override; + Configuration *getConfiguration(size_t i) override; + Configuration *getConfiguration(const char *key) override; + + bool add(Configuration *configuration); +}; + +class ConfigurationContainerOwning : public ConfigurationContainer, public MemoryManaged { +private: + MO_FilesystemAdapter *filesystem = nullptr; + char *filename = nullptr; + + Vector configurations; + + uint16_t trackWriteCount = 0; + bool checkWriteCountUpdated(); + + bool loaded = false; + +public: + ConfigurationContainerOwning(); + ~ConfigurationContainerOwning(); + + size_t size() override; + Configuration *getConfiguration(size_t i) override; + Configuration *getConfiguration(const char *key) override; + + bool add(std::unique_ptr configuration); + + void setFilesystem(MO_FilesystemAdapter *filesystem); + bool setFilename(const char *filename); + const char *getFilename(); + bool load(); //load configurations from flash + bool commit() override; +}; + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 +#endif diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationDefs.h b/src/MicroOcpp/Model/Configuration/ConfigurationDefs.h new file mode 100644 index 00000000..e0e65950 --- /dev/null +++ b/src/MicroOcpp/Model/Configuration/ConfigurationDefs.h @@ -0,0 +1,17 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_CONFIGURATIONDEFS_H +#define MO_CONFIGURATIONDEFS_H + +#include + +#if MO_ENABLE_V16 + +#ifndef MO_CONFIG_EXT_PREFIX +#define MO_CONFIG_EXT_PREFIX "Cst_" +#endif + +#endif //MO_ENABLE_V16 +#endif diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp b/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp new file mode 100644 index 00000000..b985ec55 --- /dev/null +++ b/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp @@ -0,0 +1,497 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +/* + * Implementation of the UCs B05 - B06 + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if MO_ENABLE_V16 + +#ifndef MO_GETBASEREPORT_CHUNKSIZE +#define MO_GETBASEREPORT_CHUNKSIZE 4 +#endif + +namespace MicroOcpp { +namespace Ocpp16 { + +template +ConfigurationValidator::ConfigurationValidator(const char *key, bool (*validateFn)(T, void*), void *user_data) : + key(key), user_data(user_data), validateFn(validateFn) { + +} + +template +bool ConfigurationValidator::validate(T v) { + return validateFn(v, user_data); +} + +bool VALIDATE_UNSIGNED_INT(int v, void*) { + return v >= 0; +} + +template +ConfigurationValidator *getConfigurationValidator(Vector>& collection, const char *key) { + for (size_t i = 0; i < collection.size(); i++) { + auto& validator = collection[i]; + if (!strcmp(key, validator.key)) { + return &validator; + } + } + return nullptr; +} + +ConfigurationValidator *ConfigurationService::getValidatorInt(const char *key) { + return getConfigurationValidator(validatorInt, key); +} + +ConfigurationValidator *ConfigurationService::getValidatorBool(const char *key) { + return getConfigurationValidator(validatorBool, key); +} + +ConfigurationValidator *ConfigurationService::getValidatorString(const char *key) { + return getConfigurationValidator(validatorString, key); +} + +ConfigurationContainerOwning *ConfigurationService::getContainerInternal(const char *filename) { + for (size_t i = 0; i < containersInternal.size(); i++) { + auto container = containersInternal[i]; + if (container->getFilename() && !strcmp(filename, container->getFilename())) { + return container; + } + } + return nullptr; +} + +ConfigurationContainerOwning *ConfigurationService::declareContainer(const char *filename) { + + auto container = getContainerInternal(filename); + + if (!container) { + MO_DBG_DEBUG("init new configurations container: %s", filename); + + container = new ConfigurationContainerOwning(); + if (!container) { + MO_DBG_ERR("OOM"); + return nullptr; + } + if (!container->setFilename(filename)) { + MO_DBG_ERR("OOM"); + delete container; + container = nullptr; + return nullptr; + } + containersInternal.push_back(container); //transfer ownership + containers.push_back(container); //does not take ownership + } + + return container; +} + +bool ConfigurationService::addContainer(ConfigurationContainer *container) { + containers.push_back(container); //does not take ownership + return true; +} + +template +bool registerConfigurationValidator(Vector>& collection, const char *key, bool (*validate)(T, void*), void *user_data) { + for (auto it = collection.begin(); it != collection.end(); it++) { + if (!strcmp(key, it->key)) { + collection.erase(it); + break; + } + } + collection.emplace_back(key, validate, user_data); + return true; +} + +template <> +bool ConfigurationService::registerValidator(const char *key, bool (*validate)(int, void*), void *user_data) { + return registerConfigurationValidator(validatorInt, key, validate, user_data); +} + +template <> +bool ConfigurationService::registerValidator(const char *key, bool (*validate)(bool, void*), void *user_data) { + return registerConfigurationValidator(validatorBool, key, validate, user_data); +} + +template <> +bool ConfigurationService::registerValidator(const char *key, bool (*validate)(const char*, void*), void *user_data) { + return registerConfigurationValidator(validatorString, key, validate, user_data); +} + +Configuration *ConfigurationService::getConfiguration(const char *key) { + + for (size_t i = 0; i < containers.size(); i++) { + auto container = containers[i]; + if (auto config = container->getConfiguration(key)) { + return config; + } + } + + return nullptr; +} + +bool ConfigurationService::getConfiguration(const char **keys, size_t keysSize, Vector& configurations, Vector& unknownKeys) { + if (keysSize == 0) { + // query empty - dump all configs + + for (size_t i = 0; i < containers.size(); i++) { + auto container = containers[i]; + for (size_t j = 0; j < container->size(); j++) { + auto config = container->getConfiguration(j); + if (config->getMutability() == Mutability::ReadWrite || config->getMutability() == Mutability::ReadOnly) { + configurations.push_back(config); //does not take ownership + } + } + } + } else { + // query configs by keys + + for (size_t i = 0; i < keysSize; i++) { + const char *key = keys[i]; + + auto config = getConfiguration(key); + if (config && (config->getMutability() == Mutability::ReadWrite || config->getMutability() == Mutability::ReadOnly)) { + //found key + configurations.push_back(config); //does not take ownership + } else { + //unknown key + + size_t size = strlen(key) + 1; + char *unknownKey = static_cast(MO_MALLOC(getMemoryTag(), size)); + if (!unknownKey) { + MO_DBG_ERR("OOM"); + return false; + } + memset(unknownKey, 0, size); + auto ret = snprintf(unknownKey, size, "%s", key); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("snprintf: %i", ret); + MO_FREE(unknownKey); + unknownKey = nullptr; + return false; + } + + unknownKeys.push_back(unknownKey); //transfer ownership + } + } + } + + return true; +} + +ConfigurationStatus ConfigurationService::changeConfiguration(const char *key, const char *value) { + + ConfigurationContainer *container = nullptr; + Configuration *config = nullptr; + + for (size_t i = 0; i < containers.size(); i++) { + auto container_i = containers[i]; + for (size_t j = 0; j < container_i->size(); j++) { + auto config_ij = container_i->getConfiguration(j); + if (!strcmp(key, config_ij->getKey())) { + container = container_i; + config = config_ij; + } + } + } + + if (!config || config->getMutability() == Mutability::None) { + //config not found or hidden config + return ConfigurationStatus::NotSupported; + } + + if (config->getMutability() == Mutability::ReadOnly) { + MO_DBG_WARN("Trying to override readonly value"); + return ConfigurationStatus::Rejected; + } + + //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", key, 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 { + convertibleBool = false; + } + + //validate and store (parsed) value to Config + + if (config->getType() == Configuration::Type::Int && convertibleInt) { + auto validator = getValidatorInt(key); + if (validator && !validator->validate(numInt)) { + MO_DBG_WARN("validation failed for key=%s value=%i", key, numInt); + return ConfigurationStatus::Rejected; + } + config->setInt(numInt); + } else if (config->getType() == Configuration::Type::Bool && convertibleBool) { + auto validator = getValidatorBool(key); + if (validator && !validator->validate(numBool)) { + MO_DBG_WARN("validation failed for key=%s value=%s", key, numBool ? "true" : "false"); + return ConfigurationStatus::Rejected; + } + config->setBool(numBool); + } else if (config->getType() == Configuration::Type::String) { + auto validator = getValidatorString(key); + if (validator && !validator->validate(value)) { + MO_DBG_WARN("validation failed for key=%s value=%i", key, value); + return ConfigurationStatus::Rejected; + } + if (!config->setString(value)) { + MO_DBG_WARN("OOM"); + return ConfigurationStatus::Rejected; + } + } else { + MO_DBG_WARN("Value has incompatible type"); + return ConfigurationStatus::Rejected; + } + + if (!container->commit()) { + MO_DBG_ERR("could not write changes to flash"); + return ConfigurationStatus::Rejected; + } + + //success + + if (config->isRebootRequired()) { + return ConfigurationStatus::RebootRequired; + } else { + return ConfigurationStatus::Accepted; + } +} + +ConfigurationContainer *ConfigurationService::findContainerOfConfiguration(Configuration *whichConfig) { + for (size_t i = 0; i < containers.size(); i++) { + auto container = containers[i]; + for (size_t j = 0; j < container->size(); j++) { + if (whichConfig == container->getConfiguration(j)) { + return container; + } + } + } + return nullptr; +} + +ConfigurationService::ConfigurationService(Context& context) : + MemoryManaged("v16.Configuration.ConfigurationService"), + context(context), + containers(makeVector(getMemoryTag())), + validatorInt(makeVector>(getMemoryTag())), + validatorBool(makeVector>(getMemoryTag())), + validatorString(makeVector>(getMemoryTag())) { + +} + +ConfigurationService::~ConfigurationService() { + for (unsigned int i = 0; i < containersInternal.size(); i++) { + delete containersInternal[i]; + } +} + +bool ConfigurationService::init() { + containers.reserve(1); + if (containers.capacity() < 1) { + MO_DBG_ERR("OOM"); + return false; + } + + containers.push_back(&containerExternal); + return true; +} + +bool ConfigurationService::setup() { + + declareConfiguration("GetConfigurationMaxKeys", 30, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + + filesystem = context.getFilesystem(); + if (!filesystem) { + MO_DBG_DEBUG("no fs access"); + } + + for (unsigned int i = 0; i < containersInternal.size(); i++) { + containersInternal[i]->setFilesystem(filesystem); + if (!containersInternal[i]->load()) { + MO_DBG_ERR("failure to load %s", containersInternal[i]->getFilename()); + return false; + } + } + + context.getMessageService().registerOperation("ChangeConfiguration", [] (Context& context) -> Operation* { + return new ChangeConfiguration(*context.getModel16().getConfigurationService());}); + context.getMessageService().registerOperation("GetConfiguration", [] (Context& context) -> Operation* { + return new GetConfiguration(*context.getModel16().getConfigurationService());}); + + return true; +} + +template +bool loadConfigurationFactoryDefault(Configuration& config, T factoryDef); + +template<> +bool loadConfigurationFactoryDefault(Configuration& config, int factoryDef) { + config.setInt(factoryDef); + return true; +} + +template<> +bool loadConfigurationFactoryDefault(Configuration& config, bool factoryDef) { + config.setBool(factoryDef); + return true; +} + +template<> +bool loadConfigurationFactoryDefault(Configuration& config, const char *factoryDef) { + return config.setString(factoryDef); +} + +void loadConfigurationCharacteristics(Configuration& config, Mutability mutability, bool rebootRequired) { + if (config.getMutability() == Mutability::ReadWrite) { + config.setMutability(mutability); + } else if (mutability != config.getMutability() && mutability != Mutability::ReadWrite) { + config.setMutability(Mutability::None); + } + + if (rebootRequired) { + config.setRebootRequired(); + } +} + +template +Configuration::Type getType(); + +template<> Configuration::Type getType() {return Configuration::Type::Int;} +template<> Configuration::Type getType() {return Configuration::Type::Bool;} +template<> Configuration::Type getType() {return Configuration::Type::String;} + +template +Configuration *ConfigurationService::declareConfiguration(const char *key, T factoryDefault, const char *filename, Mutability mutability, bool rebootRequired) { + + auto res = getConfiguration(key); + if (!res) { + auto container = declareContainer(filename); + if (!container) { + MO_DBG_ERR("OOM"); + return nullptr; + } + + auto config = makeConfiguration(getType()); + if (!config) { + MO_DBG_ERR("OOM"); + return nullptr; + } + + config->setKey(key); + + if (!loadConfigurationFactoryDefault(*config, factoryDefault)) { + return nullptr; + } + + res = config.get(); + + if (!container->add(std::move(config))) { + return nullptr; + } + } + + loadConfigurationCharacteristics(*res, mutability, rebootRequired); + return res; +} + +template Configuration *ConfigurationService::declareConfiguration( const char*, int, const char*, Mutability, bool); +template Configuration *ConfigurationService::declareConfiguration( const char*, bool, const char*, Mutability, bool); +template Configuration *ConfigurationService::declareConfiguration(const char*, const char*, const char*, Mutability, bool); + +bool ConfigurationService::addConfiguration(Configuration *config) { + return containerExternal.add(config); +} + +bool ConfigurationService::addConfiguration(std::unique_ptr config, const char *filename) { + if (getConfiguration(config->getKey())) { + MO_DBG_ERR("config already exists"); + return false; + } + auto container = declareContainer(filename); + if (!container) { + MO_DBG_ERR("OOM"); + return false; + } + return container->add(std::move(config)); +} + +bool ConfigurationService::commit() { + bool success = true; + + for (size_t i = 0; i < containers.size(); i++) { + if (!containers[i]->commit()) { + success = false; + } + } + + return success; +} + +} //namespace Ocpp16 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationService.h b/src/MicroOcpp/Model/Configuration/ConfigurationService.h new file mode 100644 index 00000000..edffe85c --- /dev/null +++ b/src/MicroOcpp/Model/Configuration/ConfigurationService.h @@ -0,0 +1,87 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_CONFIGURATIONSERVICE_H +#define MO_CONFIGURATIONSERVICE_H + +#include +#include +#include +#include +#include + +#if MO_ENABLE_V16 + +namespace MicroOcpp { + +class Context; + +namespace Ocpp16 { + +template +struct ConfigurationValidator { + const char *key; + void *user_data; + bool (*validateFn)(T, void*); + ConfigurationValidator(const char *key, bool (*validate)(T, void*), void *user_data); + bool validate(T); +}; + +bool VALIDATE_UNSIGNED_INT(int v, void*); //default implementation for common validator. Returns true if v >= 0 + +class ConfigurationService : public MemoryManaged { +private: + Context& context; + + MO_FilesystemAdapter *filesystem = nullptr; + + Vector containers; //does not take ownership + ConfigurationContainerNonOwning containerExternal; + Vector containersInternal; //takes ownership + ConfigurationContainerOwning *getContainerInternal(const char *filename); + ConfigurationContainerOwning *declareContainer(const char *filename); + + Vector> validatorInt; + Vector> validatorBool; + Vector> validatorString; + + ConfigurationValidator *getValidatorInt(const char *key); + ConfigurationValidator *getValidatorBool(const char *key); + ConfigurationValidator *getValidatorString(const char *key); + +public: + ConfigurationService(Context& context); + ~ConfigurationService(); + + bool init(); + + //Get Configuration. If not existent, create Configuration owned by MO and return + template + Configuration *declareConfiguration(const char *key, T factoryDefault, const char *filename = MO_CONFIGURATION_FN, Mutability mutability = Mutability::ReadWrite, bool rebootRequired = false); + + bool addConfiguration(Configuration *configuration); //Add Configuration without transferring ownership + bool addConfiguration(std::unique_ptr configuration, const char *filename); //Add Configuration and transfer ownership + + bool addContainer(ConfigurationContainer *container); //Add Container without transferring ownership + + template + bool registerValidator(const char *key, bool (*validate)(T, void*), void *userPtr = nullptr); + + bool setup(); + + //Get Configuration. If not existent, return nullptr + Configuration *getConfiguration(const char *key); + + bool getConfiguration(const char **keys, size_t keysSize, Vector& configurations, Vector& unknownKeys); //unknownKeys entries are allocated on heap - need to MO_FREE each entry, even after failure + ConfigurationStatus changeConfiguration(const char *key, const char *value); + + ConfigurationContainer *findContainerOfConfiguration(Configuration *whichConfig); + + bool commit(); +}; + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 +#endif diff --git a/src/MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h b/src/MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h deleted file mode 100644 index 6c7b04f2..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h +++ /dev/null @@ -1,33 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CHARGEPOINTERRORCODE_H -#define MO_CHARGEPOINTERRORCODE_H - -#include - -namespace MicroOcpp { - -struct ErrorData { - bool isError = false; //if any error information is set - bool isFaulted = false; //if this is a severe error and the EVSE should go into the faulted state - uint8_t severity = 1; //severity: don't send less severe errors during highly severe error condition - const char *errorCode = nullptr; //see ChargePointErrorCode (p. 76/77) for possible values - const char *info = nullptr; //Additional free format information related to the error - const char *vendorId = nullptr; //vendor-specific implementation identifier - const char *vendorErrorCode = nullptr; //vendor-specific error code - - ErrorData() = default; - - ErrorData(const char *errorCode = nullptr) : errorCode(errorCode) { - if (errorCode) { - isError = true; - isFaulted = true; - } - } -}; - -} - -#endif diff --git a/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h b/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h deleted file mode 100644 index 019fce27..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h +++ /dev/null @@ -1,36 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CHARGEPOINTSTATUS_H -#define MO_CHARGEPOINTSTATUS_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum ChargePointStatus { - ChargePointStatus_UNDEFINED, //internal use only - no OCPP standard value - ChargePointStatus_Available, - ChargePointStatus_Preparing, - ChargePointStatus_Charging, - ChargePointStatus_SuspendedEVSE, - ChargePointStatus_SuspendedEV, - ChargePointStatus_Finishing, - ChargePointStatus_Reserved, - ChargePointStatus_Unavailable, - ChargePointStatus_Faulted - -#if MO_ENABLE_V201 - ,ChargePointStatus_Occupied -#endif - -} ChargePointStatus; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp deleted file mode 100644 index 11142dc9..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp +++ /dev/null @@ -1,1322 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#ifndef MO_TX_CLEAN_ABORTED -#define MO_TX_CLEAN_ABORTED 1 -#endif - -using namespace MicroOcpp; - -Connector::Connector(Context& context, std::shared_ptr filesystem, unsigned int connectorId) - : MemoryManaged("v16.ConnectorBase.Connector"), context(context), model(context.getModel()), filesystem(filesystem), connectorId(connectorId), - errorDataInputs(makeVector>(getMemoryTag())), trackErrorDataInputs(makeVector(getMemoryTag())) { - - context.getRequestQueue().addSendQueue(this); //register at RequestQueue as Request emitter - - snprintf(availabilityBoolKey, sizeof(availabilityBoolKey), MO_CONFIG_EXT_PREFIX "AVAIL_CONN_%d", connectorId); - availabilityBool = declareConfiguration(availabilityBoolKey, true, MO_KEYVALUE_FN, false, false, false); - -#if MO_ENABLE_CONNECTOR_LOCK - declareConfiguration("UnlockConnectorOnEVSideDisconnect", true); //read-write -#else - declareConfiguration("UnlockConnectorOnEVSideDisconnect", false, CONFIGURATION_VOLATILE, true); //read-only because there is no connector lock -#endif //MO_ENABLE_CONNECTOR_LOCK - - connectionTimeOutInt = declareConfiguration("ConnectionTimeOut", 30); - registerConfigurationValidator("ConnectionTimeOut", VALIDATE_UNSIGNED_INT); - minimumStatusDurationInt = declareConfiguration("MinimumStatusDuration", 0); - registerConfigurationValidator("MinimumStatusDuration", VALIDATE_UNSIGNED_INT); - stopTransactionOnInvalidIdBool = declareConfiguration("StopTransactionOnInvalidId", true); - stopTransactionOnEVSideDisconnectBool = declareConfiguration("StopTransactionOnEVSideDisconnect", true); - localPreAuthorizeBool = declareConfiguration("LocalPreAuthorize", false); - localAuthorizeOfflineBool = declareConfiguration("LocalAuthorizeOffline", true); - allowOfflineTxForUnknownIdBool = MicroOcpp::declareConfiguration("AllowOfflineTxForUnknownId", false); - - //if the EVSE goes offline, can it continue to charge without sending StartTx / StopTx to the server when going online again? - silentOfflineTransactionsBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "SilentOfflineTransactions", false); - - //how long the EVSE tries the Authorize request before it enters offline mode - authorizationTimeoutInt = MicroOcpp::declareConfiguration(MO_CONFIG_EXT_PREFIX "AuthorizationTimeout", 20); - registerConfigurationValidator(MO_CONFIG_EXT_PREFIX "AuthorizationTimeout", VALIDATE_UNSIGNED_INT); - - //FreeVend mode - freeVendActiveBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "FreeVendActive", false); - freeVendIdTagString = declareConfiguration(MO_CONFIG_EXT_PREFIX "FreeVendIdTag", ""); - - txStartOnPowerPathClosedBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "TxStartOnPowerPathClosed", false); - - transactionMessageAttemptsInt = declareConfiguration("TransactionMessageAttempts", 3); - registerConfigurationValidator("TransactionMessageAttempts", VALIDATE_UNSIGNED_INT); - transactionMessageRetryIntervalInt = declareConfiguration("TransactionMessageRetryInterval", 60); - registerConfigurationValidator("TransactionMessageRetryInterval", VALIDATE_UNSIGNED_INT); - - if (!availabilityBool) { - MO_DBG_ERR("Cannot declare availabilityBool"); - } - - char txFnamePrefix [30]; - snprintf(txFnamePrefix, sizeof(txFnamePrefix), "tx-%u-", connectorId); - size_t txFnamePrefixLen = strlen(txFnamePrefix); - - unsigned int txNrPivot = std::numeric_limits::max(); - - if (filesystem) { - filesystem->ftw_root([this, txFnamePrefix, txFnamePrefixLen, &txNrPivot] (const char *fname) { - if (!strncmp(fname, txFnamePrefix, txFnamePrefixLen)) { - unsigned int parsedTxNr = 0; - for (size_t i = txFnamePrefixLen; fname[i] >= '0' && fname[i] <= '9'; i++) { - parsedTxNr *= 10; - parsedTxNr += fname[i] - '0'; - } - - if (txNrPivot == std::numeric_limits::max()) { - txNrPivot = parsedTxNr; - txNrBegin = parsedTxNr; - txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT; - return 0; - } - - if ((parsedTxNr + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT < MAX_TX_CNT / 2) { - //parsedTxNr is after pivot point - if ((parsedTxNr + 1 + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT > (txNrEnd + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT) { - txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT; - } - } else if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT < MAX_TX_CNT / 2) { - //parsedTxNr is before pivot point - if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT > (txNrPivot + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT) { - txNrBegin = parsedTxNr; - } - } - - MO_DBG_DEBUG("found %s%u.jsn - Internal range from %u to %u (exclusive)", txFnamePrefix, parsedTxNr, txNrBegin, txNrEnd); - } - return 0; - }); - } - - MO_DBG_DEBUG("found %u transactions for connector %u. Internal range from %u to %u (exclusive)", (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT, connectorId, txNrBegin, txNrEnd); - txNrFront = txNrBegin; - - if (model.getTransactionStore()) { - unsigned int txNrLatest = (txNrEnd + MAX_TX_CNT - 1) % MAX_TX_CNT; //txNr of the most recent tx on flash - transaction = model.getTransactionStore()->getTransaction(connectorId, txNrLatest); //returns nullptr if txNrLatest does not exist on flash - } else { - MO_DBG_ERR("must initialize TxStore before Connector"); - } -} - -Connector::~Connector() { - if (availabilityBool->getKey() == availabilityBoolKey) { - availabilityBool->setKey(nullptr); - } -} - -ChargePointStatus Connector::getStatus() { - - ChargePointStatus res = ChargePointStatus_UNDEFINED; - - /* - * Handle special case: This is the Connector for the whole CP (i.e. connectorId=0) --> only states Available, Unavailable, Faulted are possible - */ - if (connectorId == 0) { - if (isFaulted()) { - res = ChargePointStatus_Faulted; - } else if (!isOperative()) { - res = ChargePointStatus_Unavailable; - } else { - res = ChargePointStatus_Available; - } - return res; - } - - if (isFaulted()) { - res = ChargePointStatus_Faulted; - } else if (!isOperative()) { - res = ChargePointStatus_Unavailable; - } else if (transaction && transaction->isRunning()) { - //Transaction is currently running - if (connectorPluggedInput && !connectorPluggedInput()) { //special case when StopTransactionOnEVSideDisconnect is false - res = ChargePointStatus_SuspendedEV; - } else if (!ocppPermitsCharge() || - (evseReadyInput && !evseReadyInput())) { - res = ChargePointStatus_SuspendedEVSE; - } else if (evReadyInput && !evReadyInput()) { - res = ChargePointStatus_SuspendedEV; - } else { - res = ChargePointStatus_Charging; - } - } - #if MO_ENABLE_RESERVATION - else if (model.getReservationService() && model.getReservationService()->getReservation(connectorId)) { - res = ChargePointStatus_Reserved; - } - #endif - else if ((!transaction) && //no transaction process occupying the connector - (!connectorPluggedInput || !connectorPluggedInput()) && //no vehicle plugged - (!occupiedInput || !occupiedInput())) { //occupied override clear - res = ChargePointStatus_Available; - } else { - /* - * Either in Preparing or Finishing state. Only way to know is from previous state - */ - const auto previous = currentStatus; - if (previous == ChargePointStatus_Finishing || - previous == ChargePointStatus_Charging || - previous == ChargePointStatus_SuspendedEV || - previous == ChargePointStatus_SuspendedEVSE || - (transaction && transaction->getStartSync().isRequested())) { //transaction process still occupying the connector - res = ChargePointStatus_Finishing; - } else { - res = ChargePointStatus_Preparing; - } - } - -#if MO_ENABLE_V201 - if (model.getVersion().major == 2) { - //OCPP 2.0.1: map v1.6 status onto v2.0.1 - if (res == ChargePointStatus_Preparing || - res == ChargePointStatus_Charging || - res == ChargePointStatus_SuspendedEV || - res == ChargePointStatus_SuspendedEVSE || - res == ChargePointStatus_Finishing) { - res = ChargePointStatus_Occupied; - } - } -#endif - - if (res == ChargePointStatus_UNDEFINED) { - MO_DBG_DEBUG("status undefined"); - return ChargePointStatus_Faulted; //internal error - } - - return res; -} - -bool Connector::ocppPermitsCharge() { - if (connectorId == 0) { - MO_DBG_WARN("not supported for connectorId == 0"); - return false; - } - - bool suspendDeAuthorizedIdTag = transaction && transaction->isIdTagDeauthorized(); //if idTag status is "DeAuthorized" and if charging should stop - - //check special case for DeAuthorized idTags: FreeVend mode - if (suspendDeAuthorizedIdTag && freeVendActiveBool && freeVendActiveBool->getBool()) { - suspendDeAuthorizedIdTag = false; - } - - // check charge permission depending on TxStartPoint - if (txStartOnPowerPathClosedBool && txStartOnPowerPathClosedBool->getBool()) { - // tx starts when the power path is closed. Advertise charging before transaction - return transaction && - transaction->isActive() && - transaction->isAuthorized() && - !suspendDeAuthorizedIdTag; - } else { - // tx must be started before the power path can be closed - return transaction && - transaction->isRunning() && - transaction->isActive() && - !suspendDeAuthorizedIdTag; - } -} - -void Connector::loop() { - - if (!trackLoopExecute) { - trackLoopExecute = true; - if (connectorPluggedInput) { - freeVendTrackPlugged = connectorPluggedInput(); - } - } - - if (transaction && ((transaction->isAborted() && MO_TX_CLEAN_ABORTED) || (transaction->isSilent() && transaction->getStopSync().isRequested()))) { - //If the transaction is aborted (invalidated before started) or is silent and has stopped. Delete all artifacts from flash - //This is an optimization. The memory management will attempt to remove those files again later - bool removed = true; - if (auto mService = model.getMeteringService()) { - mService->abortTxMeterData(connectorId); - removed &= mService->removeTxMeterData(connectorId, transaction->getTxNr()); - } - - if (removed) { - removed &= model.getTransactionStore()->remove(connectorId, transaction->getTxNr()); - } - - if (removed) { - if (txNrFront == txNrEnd) { - txNrFront = transaction->getTxNr(); - } - txNrEnd = transaction->getTxNr(); //roll back creation of last tx entry - } - - MO_DBG_DEBUG("collect aborted or silent transaction %u-%u %s", connectorId, transaction->getTxNr(), removed ? "" : "failure"); - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - transaction = nullptr; - } - - if (transaction && transaction->isAborted()) { - MO_DBG_DEBUG("collect aborted transaction %u-%u", connectorId, transaction->getTxNr()); - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - transaction = nullptr; - } - - if (transaction && transaction->getStopSync().isRequested()) { - MO_DBG_DEBUG("collect obsolete transaction %u-%u", connectorId, transaction->getTxNr()); - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - transaction = nullptr; - } - - if (transaction) { //begin exclusively transaction-related operations - - if (connectorPluggedInput) { - if (transaction->isRunning() && transaction->isActive() && !connectorPluggedInput()) { - if (!stopTransactionOnEVSideDisconnectBool || stopTransactionOnEVSideDisconnectBool->getBool()) { - MO_DBG_DEBUG("Stop Tx due to EV disconnect"); - transaction->setStopReason("EVDisconnected"); - transaction->setInactive(); - transaction->commit(); - } - } - - if (transaction->isActive() && - !transaction->getStartSync().isRequested() && - transaction->getBeginTimestamp() > MIN_TIME && - connectionTimeOutInt && connectionTimeOutInt->getInt() > 0 && - !connectorPluggedInput() && - model.getClock().now() - transaction->getBeginTimestamp() > connectionTimeOutInt->getInt()) { - - MO_DBG_INFO("Session mngt: timeout"); - transaction->setInactive(); - transaction->commit(); - - updateTxNotification(TxNotification_ConnectionTimeout); - } - } - - if (transaction->isActive() && - transaction->isIdTagDeauthorized() && ( //transaction has been deAuthorized - !transaction->isRunning() || //if transaction hasn't started yet, always end - !stopTransactionOnInvalidIdBool || stopTransactionOnInvalidIdBool->getBool())) { //if transaction is running, behavior depends on StopTransactionOnInvalidId - - MO_DBG_DEBUG("DeAuthorize session"); - transaction->setStopReason("DeAuthorized"); - transaction->setInactive(); - transaction->commit(); - } - - /* - * Check conditions for start or stop transaction - */ - - if (!transaction->isRunning()) { - //start tx? - - if (transaction->isActive() && transaction->isAuthorized() && //tx must be authorized - (!connectorPluggedInput || connectorPluggedInput()) && //if applicable, connector must be plugged - isOperative() && //only start tx if charger is free of error conditions - (!txStartOnPowerPathClosedBool || !txStartOnPowerPathClosedBool->getBool() || !evReadyInput || evReadyInput()) && //if applicable, postpone tx start point to PowerPathClosed - (!startTxReadyInput || startTxReadyInput())) { //if defined, user Input for allowing StartTx must be true - //start Transaction - - MO_DBG_INFO("Session mngt: trigger StartTransaction"); - - auto meteringService = model.getMeteringService(); - if (transaction->getMeterStart() < 0 && meteringService) { - auto meterStart = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionBegin); - if (meterStart && *meterStart) { - transaction->setMeterStart(meterStart->toInteger()); - } else { - MO_DBG_ERR("MeterStart undefined"); - } - } - - if (transaction->getStartTimestamp() <= MIN_TIME) { - transaction->setStartTimestamp(model.getClock().now()); - transaction->setStartBootNr(model.getBootNr()); - } - - transaction->getStartSync().setRequested(); - transaction->getStartSync().setOpNr(context.getRequestQueue().getNextOpNr()); - - if (transaction->isSilent()) { - MO_DBG_INFO("silent Transaction: omit StartTx"); - transaction->getStartSync().confirm(); - } else { - //normal transaction, record txMeterData - if (model.getMeteringService()) { - model.getMeteringService()->beginTxMeterData(transaction.get()); - } - } - - transaction->commit(); - - updateTxNotification(TxNotification_StartTx); - - //fetchFrontRequest will create the StartTransaction and pass it to the message sender - return; - } - } else { - //stop tx? - - if (!transaction->isActive() && - (!stopTxReadyInput || stopTxReadyInput())) { - //stop transaction - - MO_DBG_INFO("Session mngt: trigger StopTransaction"); - - auto meteringService = model.getMeteringService(); - if (transaction->getMeterStop() < 0 && meteringService) { - auto meterStop = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionEnd); - if (meterStop && *meterStop) { - transaction->setMeterStop(meterStop->toInteger()); - } else { - MO_DBG_ERR("MeterStop undefined"); - } - } - - if (transaction->getStopTimestamp() <= MIN_TIME) { - transaction->setStopTimestamp(model.getClock().now()); - transaction->setStopBootNr(model.getBootNr()); - } - - transaction->getStopSync().setRequested(); - transaction->getStopSync().setOpNr(context.getRequestQueue().getNextOpNr()); - - if (transaction->isSilent()) { - MO_DBG_INFO("silent Transaction: omit StopTx"); - transaction->getStopSync().confirm(); - } else { - //normal transaction, record txMeterData - if (model.getMeteringService()) { - model.getMeteringService()->endTxMeterData(transaction.get()); - } - } - - transaction->commit(); - - updateTxNotification(TxNotification_StopTx); - - //fetchFrontRequest will create the StopTransaction and pass it to the message sender - return; - } - } - } //end transaction-related operations - - //handle FreeVend mode - if (freeVendActiveBool && freeVendActiveBool->getBool() && connectorPluggedInput) { - if (!freeVendTrackPlugged && connectorPluggedInput() && !transaction) { - const char *idTag = freeVendIdTagString ? freeVendIdTagString->getString() : ""; - if (!idTag || *idTag == '\0') { - idTag = "A0000000"; - } - MO_DBG_INFO("begin FreeVend Tx using idTag %s", idTag); - beginTransaction_authorized(idTag); - - if (!transaction) { - MO_DBG_ERR("could not begin FreeVend Tx"); - } - } - - freeVendTrackPlugged = connectorPluggedInput(); - } - - ErrorData errorData {nullptr}; - errorData.severity = 0; - int errorDataIndex = -1; - - if (model.getVersion().major == 1 && model.getClock().now() >= MIN_TIME) { - //OCPP 1.6: use StatusNotification to send error codes - - if (reportedErrorIndex >= 0) { - auto error = errorDataInputs[reportedErrorIndex].operator()(); - if (error.isError) { - errorData = error; - errorDataIndex = reportedErrorIndex; - } - } - - for (auto i = std::min(errorDataInputs.size(), trackErrorDataInputs.size()); i >= 1; i--) { - auto index = i - 1; - ErrorData error {nullptr}; - if ((int)index != errorDataIndex) { - error = errorDataInputs[index].operator()(); - } else { - error = errorData; - } - if (error.isError && !trackErrorDataInputs[index] && error.severity >= errorData.severity) { - //new error - errorData = error; - errorDataIndex = index; - } else if (error.isError && error.severity > errorData.severity) { - errorData = error; - errorDataIndex = index; - } else if (!error.isError && trackErrorDataInputs[index]) { - //reset error - trackErrorDataInputs[index] = false; - } - } - - if (errorDataIndex != reportedErrorIndex) { - if (errorDataIndex >= 0 || MO_REPORT_NOERROR) { - reportedStatus = ChargePointStatus_UNDEFINED; //trigger sending currentStatus again with code NoError - } else { - reportedErrorIndex = -1; - } - } - } //if (model.getVersion().major == 1) - - auto status = getStatus(); - - if (status != currentStatus) { - MO_DBG_DEBUG("Status changed %s -> %s %s", - currentStatus == ChargePointStatus_UNDEFINED ? "" : cstrFromOcppEveState(currentStatus), - cstrFromOcppEveState(status), - minimumStatusDurationInt->getInt() ? " (will report delayed)" : ""); - currentStatus = status; - t_statusTransition = mocpp_tick_ms(); - } - - if (reportedStatus != currentStatus && - model.getClock().now() >= MIN_TIME && - (minimumStatusDurationInt->getInt() <= 0 || //MinimumStatusDuration disabled - mocpp_tick_ms() - t_statusTransition >= ((unsigned long) minimumStatusDurationInt->getInt()) * 1000UL)) { - reportedStatus = currentStatus; - reportedErrorIndex = errorDataIndex; - if (errorDataIndex >= 0) { - trackErrorDataInputs[errorDataIndex] = true; - } - Timestamp reportedTimestamp = model.getClock().now(); - reportedTimestamp -= (mocpp_tick_ms() - t_statusTransition) / 1000UL; - - auto statusNotification = - #if MO_ENABLE_V201 - model.getVersion().major == 2 ? - makeRequest( - new Ocpp201::StatusNotification(connectorId, reportedStatus, reportedTimestamp)) : - #endif //MO_ENABLE_V201 - makeRequest( - new Ocpp16::StatusNotification(connectorId, reportedStatus, reportedTimestamp, errorData)); - - statusNotification->setTimeout(0); - context.initiateRequest(std::move(statusNotification)); - return; - } - - return; -} - -bool Connector::isFaulted() { - //for (auto i = errorDataInputs.begin(); i != errorDataInputs.end(); ++i) { - for (size_t i = 0; i < errorDataInputs.size(); i++) { - if (errorDataInputs[i].operator()().isFaulted) { - return true; - } - } - return false; -} - -const char *Connector::getErrorCode() { - if (reportedErrorIndex >= 0) { - auto error = errorDataInputs[reportedErrorIndex].operator()(); - if (error.isError && error.errorCode) { - return error.errorCode; - } - } - return nullptr; -} - -std::shared_ptr Connector::allocateTransaction() { - - std::shared_ptr tx; - - //clean possible aborted tx - unsigned int txr = txNrEnd; - unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; - for (unsigned int i = 0; i < txSize; i++) { - txr = (txr + MAX_TX_CNT - 1) % MAX_TX_CNT; //decrement by 1 - - auto tx = model.getTransactionStore()->getTransaction(connectorId, txr); - //check if dangling silent tx, aborted tx, or corrupted entry (tx == null) - if (!tx || tx->isSilent() || (tx->isAborted() && MO_TX_CLEAN_ABORTED)) { - //yes, remove - bool removed = true; - if (auto mService = model.getMeteringService()) { - removed &= mService->removeTxMeterData(connectorId, txr); - } - if (removed) { - removed &= model.getTransactionStore()->remove(connectorId, txr); - } - if (removed) { - if (txNrFront == txNrEnd) { - txNrFront = txr; - } - txNrEnd = txr; - MO_DBG_WARN("deleted dangling silent or aborted tx for new transaction"); - } else { - MO_DBG_ERR("memory corruption"); - break; - } - } else { - //no, tx record trimmed, end - break; - } - } - - txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; //refresh after cleaning txs - - //try to create new transaction - if (txSize < MO_TXRECORD_SIZE) { - tx = model.getTransactionStore()->createTransaction(connectorId, txNrEnd); - } - - if (!tx) { - //could not create transaction - now, try to replace tx history entry - - unsigned int txl = txNrBegin; - txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; - - for (unsigned int i = 0; i < txSize; i++) { - - if (tx) { - //success, finished here - break; - } - - //no transaction allocated, delete history entry to make space - - auto txhist = model.getTransactionStore()->getTransaction(connectorId, txl); - //oldest entry, now check if it's history and can be removed or corrupted entry - if (!txhist || txhist->isCompleted() || txhist->isAborted() || (txhist->isSilent() && txhist->getStopSync().isRequested())) { - //yes, remove - bool removed = true; - if (auto mService = model.getMeteringService()) { - removed &= mService->removeTxMeterData(connectorId, txl); - } - if (removed) { - removed &= model.getTransactionStore()->remove(connectorId, txl); - } - if (removed) { - txNrBegin = (txl + 1) % MAX_TX_CNT; - if (txNrFront == txl) { - txNrFront = txNrBegin; - } - MO_DBG_DEBUG("deleted tx history entry for new transaction"); - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - - tx = model.getTransactionStore()->createTransaction(connectorId, txNrEnd); - } else { - MO_DBG_ERR("memory corruption"); - break; - } - } else { - //no, end of history reached, don't delete further tx - MO_DBG_DEBUG("cannot delete more tx"); - break; - } - - txl++; - txl %= MAX_TX_CNT; - } - } - - if (!tx) { - //couldn't create normal transaction -> check if to start charging without real transaction - if (silentOfflineTransactionsBool && silentOfflineTransactionsBool->getBool()) { - //try to handle charging session without sending StartTx or StopTx to the server - tx = model.getTransactionStore()->createTransaction(connectorId, txNrEnd, true); - - if (tx) { - MO_DBG_DEBUG("created silent transaction"); - } - } - } - - if (tx) { - //clean meter data which could still be here from a rolled-back transaction - if (auto mService = model.getMeteringService()) { - if (!mService->removeTxMeterData(connectorId, tx->getTxNr())) { - MO_DBG_ERR("memory corruption"); - } - } - } - - if (tx) { - txNrEnd = (txNrEnd + 1) % MAX_TX_CNT; - MO_DBG_DEBUG("advance txNrEnd %u-%u", connectorId, txNrEnd); - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - } - - return tx; -} - -std::shared_ptr Connector::beginTransaction(const char *idTag) { - - if (transaction) { - MO_DBG_WARN("tx process still running. Please call endTransaction(...) before"); - return nullptr; - } - - MO_DBG_DEBUG("Begin transaction process (%s), prepare", idTag != nullptr ? idTag : ""); - - bool localAuthFound = false; - const char *parentIdTag = nullptr; //locally stored parentIdTag - bool offlineBlockedAuth = false; //if offline authorization will be blocked by local auth list entry - - //check local OCPP whitelist - #if MO_ENABLE_LOCAL_AUTH - if (auto authService = model.getAuthorizationService()) { - auto localAuth = authService->getLocalAuthorization(idTag); - - //check authorization status - if (localAuth && localAuth->getAuthorizationStatus() != AuthorizationStatus::Accepted) { - MO_DBG_DEBUG("local auth denied (%s)", idTag); - offlineBlockedAuth = true; - localAuth = nullptr; - } - - //check expiry - if (localAuth && localAuth->getExpiryDate() && *localAuth->getExpiryDate() < model.getClock().now()) { - MO_DBG_DEBUG("idTag %s local auth entry expired", idTag); - offlineBlockedAuth = true; - localAuth = nullptr; - } - - if (localAuth) { - localAuthFound = true; - parentIdTag = localAuth->getParentIdTag(); - } - } - #endif //MO_ENABLE_LOCAL_AUTH - - int reservationId = -1; - bool offlineBlockedResv = false; //if offline authorization will be blocked by reservation - - //check if blocked by reservation - #if MO_ENABLE_RESERVATION - if (model.getReservationService()) { - - auto reservation = model.getReservationService()->getReservation( - connectorId, - idTag, - parentIdTag); - - if (reservation) { - reservationId = reservation->getReservationId(); - } - - if (reservation && !reservation->matches( - idTag, - parentIdTag)) { - //reservation blocks connector - offlineBlockedResv = true; //when offline, tx is always blocked - - //if parentIdTag is known, abort this tx immediately, otherwise wait for Authorize.conf to decide - if (parentIdTag) { - //parentIdTag known - MO_DBG_INFO("connector %u reserved - abort transaction", connectorId); - updateTxNotification(TxNotification_ReservationConflict); - return nullptr; - } else { - //parentIdTag unkown but local authorization failed in any case - MO_DBG_INFO("connector %u reserved - no local auth", connectorId); - localAuthFound = false; - } - } - } - #endif //MO_ENABLE_RESERVATION - - transaction = allocateTransaction(); - - if (!transaction) { - MO_DBG_ERR("could not allocate Tx"); - return nullptr; - } - - if (!idTag || *idTag == '\0') { - //input string is empty - transaction->setIdTag(""); - } else { - transaction->setIdTag(idTag); - } - - if (parentIdTag) { - transaction->setParentIdTag(parentIdTag); - } - - transaction->setBeginTimestamp(model.getClock().now()); - - //check for local preauthorization - if (localAuthFound && localPreAuthorizeBool && localPreAuthorizeBool->getBool()) { - MO_DBG_DEBUG("Begin transaction process (%s), preauthorized locally", idTag != nullptr ? idTag : ""); - - if (reservationId >= 0) { - transaction->setReservationId(reservationId); - } - transaction->setAuthorized(); - - updateTxNotification(TxNotification_Authorized); - } - - transaction->commit(); - - auto authorize = makeRequest(new Ocpp16::Authorize(context.getModel(), idTag)); - authorize->setTimeout(authorizationTimeoutInt && authorizationTimeoutInt->getInt() > 0 ? authorizationTimeoutInt->getInt() * 1000UL : 20UL * 1000UL); - - if (!context.getConnection().isConnected()) { - //WebSockt unconnected. Enter offline mode immediately - authorize->setTimeout(1); - } - - auto tx = transaction; - authorize->setOnReceiveConfListener([this, tx] (JsonObject response) { - JsonObject idTagInfo = response["idTagInfo"]; - - if (strcmp("Accepted", idTagInfo["status"] | "UNDEFINED")) { - //Authorization rejected, abort transaction - MO_DBG_DEBUG("Authorize rejected (%s), abort tx process", tx->getIdTag()); - tx->setIdTagDeauthorized(); - tx->commit(); - updateTxNotification(TxNotification_AuthorizationRejected); - return; - } - - #if MO_ENABLE_RESERVATION - if (model.getReservationService()) { - auto reservation = model.getReservationService()->getReservation( - connectorId, - tx->getIdTag(), - idTagInfo["parentIdTag"] | (const char*) nullptr); - if (reservation) { - //reservation found for connector - if (reservation->matches( - tx->getIdTag(), - idTagInfo["parentIdTag"] | (const char*) nullptr)) { - MO_DBG_INFO("connector %u matches reservationId %i", connectorId, reservation->getReservationId()); - tx->setReservationId(reservation->getReservationId()); - } else { - //reservation found for connector but does not match idTag or parentIdTag - MO_DBG_INFO("connector %u reserved - abort transaction", connectorId); - tx->setInactive(); - tx->commit(); - updateTxNotification(TxNotification_ReservationConflict); - return; - } - } - } - #endif //MO_ENABLE_RESERVATION - - if (idTagInfo.containsKey("parentIdTag")) { - tx->setParentIdTag(idTagInfo["parentIdTag"] | ""); - } - - MO_DBG_DEBUG("Authorized transaction process (%s)", tx->getIdTag()); - tx->setAuthorized(); - tx->commit(); - - updateTxNotification(TxNotification_Authorized); - }); - - //capture local auth and reservation check in for timeout handler - authorize->setOnTimeoutListener([this, tx, - offlineBlockedAuth, - offlineBlockedResv, - localAuthFound, - reservationId] () { - - if (offlineBlockedAuth) { - //local auth entry exists, but is expired -> avoid offline tx - MO_DBG_DEBUG("Abort transaction process (%s), timeout, expired local auth", tx->getIdTag()); - tx->setInactive(); - tx->commit(); - updateTxNotification(TxNotification_AuthorizationTimeout); - return; - } - - if (offlineBlockedResv) { - //reservation found for connector but does not match idTag or parentIdTag - MO_DBG_INFO("connector %u reserved (offline) - abort transaction", connectorId); - tx->setInactive(); - tx->commit(); - updateTxNotification(TxNotification_ReservationConflict); - return; - } - - if (localAuthFound && localAuthorizeOfflineBool && localAuthorizeOfflineBool->getBool()) { - MO_DBG_DEBUG("Offline transaction process (%s), locally authorized", tx->getIdTag()); - if (reservationId >= 0) { - tx->setReservationId(reservationId); - } - tx->setAuthorized(); - tx->commit(); - - updateTxNotification(TxNotification_Authorized); - return; - } - - if (allowOfflineTxForUnknownIdBool && allowOfflineTxForUnknownIdBool->getBool()) { - MO_DBG_DEBUG("Offline transaction process (%s), allow unknown ID", tx->getIdTag()); - if (reservationId >= 0) { - tx->setReservationId(reservationId); - } - tx->setAuthorized(); - tx->commit(); - updateTxNotification(TxNotification_Authorized); - return; - } - - MO_DBG_DEBUG("Abort transaction process (%s): timeout", tx->getIdTag()); - tx->setInactive(); - tx->commit(); - updateTxNotification(TxNotification_AuthorizationTimeout); - return; //offline tx disabled - }); - context.initiateRequest(std::move(authorize)); - - return transaction; -} - -std::shared_ptr Connector::beginTransaction_authorized(const char *idTag, const char *parentIdTag) { - - if (transaction) { - MO_DBG_WARN("tx process still running. Please call endTransaction(...) before"); - return nullptr; - } - - transaction = allocateTransaction(); - - if (!transaction) { - MO_DBG_ERR("could not allocate Tx"); - return nullptr; - } - - if (!idTag || *idTag == '\0') { - //input string is empty - transaction->setIdTag(""); - } else { - transaction->setIdTag(idTag); - } - - if (parentIdTag) { - transaction->setParentIdTag(parentIdTag); - } - - transaction->setBeginTimestamp(model.getClock().now()); - - MO_DBG_DEBUG("Begin transaction process (%s), already authorized", idTag != nullptr ? idTag : ""); - - transaction->setAuthorized(); - - #if MO_ENABLE_RESERVATION - if (model.getReservationService()) { - if (auto reservation = model.getReservationService()->getReservation(connectorId, idTag, parentIdTag)) { - if (reservation->matches(idTag, parentIdTag)) { - transaction->setReservationId(reservation->getReservationId()); - } - } - } - #endif //MO_ENABLE_RESERVATION - - transaction->commit(); - - return transaction; -} - -void Connector::endTransaction(const char *idTag, const char *reason) { - - if (!transaction || !transaction->isActive()) { - //transaction already ended / not active anymore - return; - } - - MO_DBG_DEBUG("End session started by idTag %s", - transaction->getIdTag()); - - if (idTag && *idTag != '\0') { - transaction->setStopIdTag(idTag); - } - - if (reason) { - transaction->setStopReason(reason); - } - transaction->setInactive(); - transaction->commit(); -} - -std::shared_ptr& Connector::getTransaction() { - return transaction; -} - -bool Connector::isOperative() { - if (isFaulted()) { - return false; - } - - if (!trackLoopExecute) { - return false; - } - - //check for running transaction(s) - if yes then the connector is always operative - if (connectorId == 0) { - for (unsigned int cId = 1; cId < model.getNumConnectors(); cId++) { - if (model.getConnector(cId)->getTransaction() && model.getConnector(cId)->getTransaction()->isRunning()) { - return true; - } - } - } else { - if (transaction && transaction->isRunning()) { - return true; - } - } - - #if MO_ENABLE_V201 - if (model.getVersion().major == 2 && model.getTransactionService()) { - auto txService = model.getTransactionService(); - - if (connectorId == 0) { - for (unsigned int cId = 1; cId < model.getNumConnectors(); cId++) { - if (txService->getEvse(cId)->getTransaction() && - txService->getEvse(cId)->getTransaction()->started && - !txService->getEvse(cId)->getTransaction()->stopped) { - return true; - } - } - } else { - if (txService->getEvse(connectorId)->getTransaction() && - txService->getEvse(connectorId)->getTransaction()->started && - !txService->getEvse(connectorId)->getTransaction()->stopped) { - return true; - } - } - } - #endif //MO_ENABLE_V201 - - return availabilityVolatile && availabilityBool->getBool(); -} - -void Connector::setAvailability(bool available) { - availabilityBool->setBool(available); - configuration_save(); -} - -void Connector::setAvailabilityVolatile(bool available) { - availabilityVolatile = available; -} - -void Connector::setConnectorPluggedInput(std::function connectorPlugged) { - this->connectorPluggedInput = connectorPlugged; -} - -void Connector::setEvReadyInput(std::function evRequestsEnergy) { - this->evReadyInput = evRequestsEnergy; -} - -void Connector::setEvseReadyInput(std::function connectorEnergized) { - this->evseReadyInput = connectorEnergized; -} - -void Connector::addErrorCodeInput(std::function connectorErrorCode) { - addErrorDataInput([connectorErrorCode] () -> ErrorData { - return ErrorData(connectorErrorCode()); - }); -} - -void Connector::addErrorDataInput(std::function errorDataInput) { - this->errorDataInputs.push_back(errorDataInput); - this->trackErrorDataInputs.push_back(false); -} - -#if MO_ENABLE_CONNECTOR_LOCK -void Connector::setOnUnlockConnector(std::function unlockConnector) { - this->onUnlockConnector = unlockConnector; -} - -std::function Connector::getOnUnlockConnector() { - return this->onUnlockConnector; -} -#endif //MO_ENABLE_CONNECTOR_LOCK - -void Connector::setStartTxReadyInput(std::function startTxReady) { - this->startTxReadyInput = startTxReady; -} - -void Connector::setStopTxReadyInput(std::function stopTxReady) { - this->stopTxReadyInput = stopTxReady; -} - -void Connector::setOccupiedInput(std::function occupied) { - this->occupiedInput = occupied; -} - -void Connector::setTxNotificationOutput(std::function txNotificationOutput) { - this->txNotificationOutput = txNotificationOutput; -} - -void Connector::updateTxNotification(TxNotification event) { - if (txNotificationOutput) { - txNotificationOutput(transaction.get(), event); - } -} - -unsigned int Connector::getFrontRequestOpNr() { - - /* - * Advance front transaction? - */ - - unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrFront) % MAX_TX_CNT; - - if (transactionFront && txSize == 0) { - //catch edge case where txBack has been rolled back and txFront was equal to txBack - MO_DBG_DEBUG("collect front transaction %u-%u after tx rollback", connectorId, transactionFront->getTxNr()); - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - transactionFront = nullptr; - } - - for (unsigned int i = 0; i < txSize; i++) { - - if (!transactionFront) { - transactionFront = model.getTransactionStore()->getTransaction(connectorId, txNrFront); - - #if MO_DBG_LEVEL >= MO_DL_VERBOSE - if (transactionFront) - { - MO_DBG_VERBOSE("load front transaction %u-%u", connectorId, transactionFront->getTxNr()); - } - #endif - } - - if (transactionFront && (transactionFront->isAborted() || transactionFront->isCompleted() || transactionFront->isSilent())) { - //advance front - MO_DBG_DEBUG("collect front transaction %u-%u", connectorId, transactionFront->getTxNr()); - transactionFront = nullptr; - txNrFront = (txNrFront + 1) % MAX_TX_CNT; - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - } else { - //front is accurate. Done here - break; - } - } - - if (transactionFront) { - if (transactionFront->getStartSync().isRequested() && !transactionFront->getStartSync().isConfirmed()) { - return transactionFront->getStartSync().getOpNr(); - } - - if (transactionFront->getStopSync().isRequested() && !transactionFront->getStopSync().isConfirmed()) { - return transactionFront->getStopSync().getOpNr(); - } - } - - return NoOperation; -} - -std::unique_ptr Connector::fetchFrontRequest() { - - if (transactionFront && !transactionFront->isSilent()) { - if (transactionFront->getStartSync().isRequested() && !transactionFront->getStartSync().isConfirmed()) { - //send StartTx? - - bool cancelStartTx = false; - - if (transactionFront->getStartTimestamp() < MIN_TIME && - transactionFront->getStartBootNr() != model.getBootNr()) { - //time not set, cannot be restored anymore -> invalid tx - MO_DBG_ERR("cannot recover tx from previus run"); - - cancelStartTx = true; - } - - if ((int)transactionFront->getStartSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) { - MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); - - cancelStartTx = true; - } - - if (cancelStartTx) { - transactionFront->setSilent(); - transactionFront->setInactive(); - transactionFront->commit(); - - //clean up possible tx records - if (auto mSerivce = model.getMeteringService()) { - mSerivce->removeTxMeterData(connectorId, transactionFront->getTxNr()); - } - //next getFrontRequestOpNr() call will collect transactionFront - return nullptr; - } - - Timestamp nextAttempt = transactionFront->getStartSync().getAttemptTime() + - transactionFront->getStartSync().getAttemptNr() * std::max(0, transactionMessageRetryIntervalInt->getInt()); - - if (nextAttempt > model.getClock().now()) { - return nullptr; - } - - transactionFront->getStartSync().advanceAttemptNr(); - transactionFront->getStartSync().setAttemptTime(model.getClock().now()); - transactionFront->commit(); - - auto startTx = makeRequest(new Ocpp16::StartTransaction(model, transactionFront)); - startTx->setOnReceiveConfListener([this] (JsonObject response) { - //fetch authorization status from StartTransaction.conf() for user notification - - const char* idTagInfoStatus = response["idTagInfo"]["status"] | "_Undefined"; - if (strcmp(idTagInfoStatus, "Accepted")) { - updateTxNotification(TxNotification_DeAuthorized); - } - }); - auto transactionFront_capture = transactionFront; - startTx->setOnAbortListener([this, transactionFront_capture] () { - //shortcut to the attemptNr check above. Relevant if other operations block the queue while this StartTx is timing out - if (transactionFront_capture && (int)transactionFront_capture->getStartSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) { - MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); - - transactionFront_capture->setSilent(); - transactionFront_capture->setInactive(); - transactionFront_capture->commit(); - - //clean up possible tx records - if (auto mSerivce = model.getMeteringService()) { - mSerivce->removeTxMeterData(connectorId, transactionFront_capture->getTxNr()); - } - //next getFrontRequestOpNr() call will collect transactionFront - } - }); - - return startTx; - } - - if (transactionFront->getStopSync().isRequested() && !transactionFront->getStopSync().isConfirmed()) { - //send StopTx? - - if ((int)transactionFront->getStopSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) { - MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); - - transactionFront->setSilent(); - - //clean up possible tx records - if (auto mSerivce = model.getMeteringService()) { - mSerivce->removeTxMeterData(connectorId, transactionFront->getTxNr()); - } - //next getFrontRequestOpNr() call will collect transactionFront - return nullptr; - } - - Timestamp nextAttempt = transactionFront->getStopSync().getAttemptTime() + - transactionFront->getStopSync().getAttemptNr() * std::max(0, transactionMessageRetryIntervalInt->getInt()); - - if (nextAttempt > model.getClock().now()) { - return nullptr; - } - - transactionFront->getStopSync().advanceAttemptNr(); - transactionFront->getStopSync().setAttemptTime(model.getClock().now()); - transactionFront->commit(); - - std::shared_ptr stopTxData; - - if (auto meteringService = model.getMeteringService()) { - stopTxData = meteringService->getStopTxMeterData(transactionFront.get()); - } - - std::unique_ptr stopTx; - - if (stopTxData) { - stopTx = makeRequest(new Ocpp16::StopTransaction(model, transactionFront, stopTxData->retrieveStopTxData())); - } else { - stopTx = makeRequest(new Ocpp16::StopTransaction(model, transactionFront)); - } - auto transactionFront_capture = transactionFront; - stopTx->setOnAbortListener([this, transactionFront_capture] () { - //shortcut to the attemptNr check above. Relevant if other operations block the queue while this StopTx is timing out - if ((int)transactionFront_capture->getStopSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) { - MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); - - transactionFront_capture->setSilent(); - transactionFront_capture->setInactive(); - transactionFront_capture->commit(); - - //clean up possible tx records - if (auto mSerivce = model.getMeteringService()) { - mSerivce->removeTxMeterData(connectorId, transactionFront_capture->getTxNr()); - } - //next getFrontRequestOpNr() call will collect transactionFront - } - }); - - return stopTx; - } - } - - return nullptr; -} - -bool Connector::triggerStatusNotification() { - - ErrorData errorData {nullptr}; - errorData.severity = 0; - - if (reportedErrorIndex >= 0) { - errorData = errorDataInputs[reportedErrorIndex].operator()(); - } else { - //find errorData with maximum severity - for (auto i = errorDataInputs.size(); i >= 1; i--) { - auto index = i - 1; - ErrorData error = errorDataInputs[index].operator()(); - if (error.isError && error.severity >= errorData.severity) { - errorData = error; - } - } - } - - auto statusNotification = makeRequest(new Ocpp16::StatusNotification( - connectorId, - getStatus(), - context.getModel().getClock().now(), - errorData)); - - statusNotification->setTimeout(60000); - - context.getRequestQueue().sendRequestPreBoot(std::move(statusNotification)); - - return true; -} - -unsigned int Connector::getTxNrBeginHistory() { - return txNrBegin; -} - -unsigned int Connector::getTxNrFront() { - return txNrFront; -} - -unsigned int Connector::getTxNrEnd() { - return txNrEnd; -} diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.h b/src/MicroOcpp/Model/ConnectorBase/Connector.h deleted file mode 100644 index d8d61639..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.h +++ /dev/null @@ -1,166 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CONNECTOR_H -#define MO_CONNECTOR_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifndef MO_TXRECORD_SIZE -#define MO_TXRECORD_SIZE 4 //no. of tx to hold on flash storage -#endif - -#ifndef MO_REPORT_NOERROR -#define MO_REPORT_NOERROR 0 -#endif - -namespace MicroOcpp { - -class Context; -class Model; -class Operation; - -class Connector : public RequestEmitter, public MemoryManaged { -private: - Context& context; - Model& model; - std::shared_ptr filesystem; - - const unsigned int connectorId; - - std::shared_ptr transaction; - - std::shared_ptr availabilityBool; - char availabilityBoolKey [sizeof(MO_CONFIG_EXT_PREFIX "AVAIL_CONN_xxxx") + 1]; - bool availabilityVolatile = true; - - std::function connectorPluggedInput; - std::function evReadyInput; - std::function evseReadyInput; - Vector> errorDataInputs; - Vector trackErrorDataInputs; - int reportedErrorIndex = -1; //last reported error - bool isFaulted(); - const char *getErrorCode(); - - ChargePointStatus currentStatus = ChargePointStatus_UNDEFINED; - std::shared_ptr minimumStatusDurationInt; //in seconds - ChargePointStatus reportedStatus = ChargePointStatus_UNDEFINED; - unsigned long t_statusTransition = 0; - -#if MO_ENABLE_CONNECTOR_LOCK - std::function onUnlockConnector; -#endif //MO_ENABLE_CONNECTOR_LOCK - - std::function startTxReadyInput; //the StartTx request will be delayed while this Input is false - std::function stopTxReadyInput; //the StopTx request will be delayed while this Input is false - std::function occupiedInput; //instead of Available, go into Preparing / Finishing state - - std::function txNotificationOutput; - - std::shared_ptr connectionTimeOutInt; //in seconds - std::shared_ptr stopTransactionOnInvalidIdBool; - std::shared_ptr stopTransactionOnEVSideDisconnectBool; - std::shared_ptr localPreAuthorizeBool; - std::shared_ptr localAuthorizeOfflineBool; - std::shared_ptr allowOfflineTxForUnknownIdBool; - - std::shared_ptr silentOfflineTransactionsBool; - std::shared_ptr authorizationTimeoutInt; //in seconds - std::shared_ptr freeVendActiveBool; - std::shared_ptr freeVendIdTagString; - bool freeVendTrackPlugged = false; - - std::shared_ptr txStartOnPowerPathClosedBool; // this postpones the tx start point to when evReadyInput becomes true - - std::shared_ptr transactionMessageAttemptsInt; - std::shared_ptr transactionMessageRetryIntervalInt; - - bool trackLoopExecute = false; //if loop has been executed once - - unsigned int txNrBegin = 0; //oldest (historical) transaction on flash. Has no function, but is useful for error diagnosis - unsigned int txNrFront = 0; //oldest transaction which is still queued to be sent to the server - unsigned int txNrEnd = 0; //one position behind newest transaction - - std::shared_ptr transactionFront; -public: - Connector(Context& context, std::shared_ptr filesystem, unsigned int connectorId); - Connector(const Connector&) = delete; - Connector(Connector&&) = delete; - Connector& operator=(const Connector&) = delete; - - ~Connector(); - - /* - * beginTransaction begins the transaction process which eventually leads to a StartTransaction - * request in the normal case. - * - * If the transaction process begins successfully, a Transaction object is returned - * If no transaction process begins due to this call, nullptr is returned (e.g. memory allocation failed) - */ - std::shared_ptr beginTransaction(const char *idTag); - std::shared_ptr beginTransaction_authorized(const char *idTag, const char *parentIdTag = nullptr); - - /* - * End the current transaction process, if existing and not ended yet. This eventually leads to - * a StopTransaction request, if the transaction process has actually ended due to this call. It - * is safe to call this function at any time even if no transaction is running - */ - void endTransaction(const char *idTag = nullptr, const char *reason = nullptr); - - std::shared_ptr& getTransaction(); - - //create detached transaction - won't have any side-effects with the transaction handling of this lib - std::shared_ptr allocateTransaction(); - - bool isOperative(); - void setAvailability(bool available); - void setAvailabilityVolatile(bool available); //set inoperative state but keep only until reboot at most - void setConnectorPluggedInput(std::function connectorPlugged); - void setEvReadyInput(std::function evRequestsEnergy); - void setEvseReadyInput(std::function connectorEnergized); - void addErrorCodeInput(std::function connectorErrorCode); - void addErrorDataInput(std::function errorCodeInput); - - void loop(); - - ChargePointStatus getStatus(); - - bool ocppPermitsCharge(); - -#if MO_ENABLE_CONNECTOR_LOCK - void setOnUnlockConnector(std::function unlockConnector); - std::function getOnUnlockConnector(); -#endif //MO_ENABLE_CONNECTOR_LOCK - - void setStartTxReadyInput(std::function startTxReady); - void setStopTxReadyInput(std::function stopTxReady); - void setOccupiedInput(std::function occupied); - - void setTxNotificationOutput(std::function txNotificationOutput); - void updateTxNotification(TxNotification event); - - unsigned int getFrontRequestOpNr() override; - std::unique_ptr fetchFrontRequest() override; - - bool triggerStatusNotification(); - - unsigned int getTxNrBeginHistory(); //if getTxNrBeginHistory() != getTxNrFront(), then return value is the txNr of the oldest tx history entry. If equal to getTxNrFront(), then the history is empty - unsigned int getTxNrFront(); //if getTxNrEnd() != getTxNrFront(), then return value is the txNr of the oldest transaction queued to be sent to the server. If equal to getTxNrEnd(), then there is no tx to be sent to the server - unsigned int getTxNrEnd(); //upper limit for the range of valid txNrs -}; - -} //end namespace MicroOcpp -#endif diff --git a/src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp b/src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp deleted file mode 100644 index d7767015..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/ConnectorService.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -using namespace MicroOcpp; - -ConnectorService::ConnectorService(Context& context, unsigned int numConn, std::shared_ptr filesystem) : - MemoryManaged("v16.ConnectorBase.ConnectorService"), context(context) { - - declareConfiguration("NumberOfConnectors", numConn >= 1 ? numConn - 1 : 0, CONFIGURATION_VOLATILE, true); - - /* - * Further configuration keys which correspond to the Core profile - */ - declareConfiguration("AuthorizeRemoteTxRequests", false); - declareConfiguration("GetConfigurationMaxKeys", 30, CONFIGURATION_VOLATILE, true); - - context.getOperationRegistry().registerOperation("ChangeAvailability", [&context] () { - return new Ocpp16::ChangeAvailability(context.getModel());}); - context.getOperationRegistry().registerOperation("ChangeConfiguration", [] () { - return new Ocpp16::ChangeConfiguration();}); - context.getOperationRegistry().registerOperation("ClearCache", [filesystem] () { - return new Ocpp16::ClearCache(filesystem);}); - context.getOperationRegistry().registerOperation("DataTransfer", [] () { - return new Ocpp16::DataTransfer();}); - context.getOperationRegistry().registerOperation("GetConfiguration", [] () { - return new Ocpp16::GetConfiguration();}); - context.getOperationRegistry().registerOperation("RemoteStartTransaction", [&context] () { - return new Ocpp16::RemoteStartTransaction(context.getModel());}); - context.getOperationRegistry().registerOperation("RemoteStopTransaction", [&context] () { - return new Ocpp16::RemoteStopTransaction(context.getModel());}); - context.getOperationRegistry().registerOperation("Reset", [&context] () { - return new Ocpp16::Reset(context.getModel());}); - context.getOperationRegistry().registerOperation("TriggerMessage", [&context] () { - return new Ocpp16::TriggerMessage(context);}); - context.getOperationRegistry().registerOperation("UnlockConnector", [&context] () { - return new Ocpp16::UnlockConnector(context.getModel());}); - - /* - * Register further message handlers to support echo mode: when this library - * is connected with a WebSocket echo server, let it reply to its own requests. - * Mocking an OCPP Server on the same device makes running (unit) tests easier. - */ -#if MO_ENABLE_V201 - if (context.getVersion().major == 2) { - // OCPP 2.0.1 compliant echo messages - context.getOperationRegistry().registerOperation("Authorize", [&context] () { - return new Ocpp201::Authorize(context.getModel(), "");}); - context.getOperationRegistry().registerOperation("TransactionEvent", [&context] () { - return new Ocpp201::TransactionEvent(context.getModel(), nullptr);}); - } else -#endif //MO_ENABLE_V201 - { - // OCPP 1.6 compliant echo messages - context.getOperationRegistry().registerOperation("Authorize", [&context] () { - return new Ocpp16::Authorize(context.getModel(), "");}); - context.getOperationRegistry().registerOperation("StartTransaction", [&context] () { - return new Ocpp16::StartTransaction(context.getModel(), nullptr);}); - context.getOperationRegistry().registerOperation("StopTransaction", [&context] () { - return new Ocpp16::StopTransaction(context.getModel(), nullptr);}); - } - // OCPP 1.6 + 2.0.1 compliant echo messages - context.getOperationRegistry().registerOperation("StatusNotification", [&context] () { - return new Ocpp16::StatusNotification(-1, ChargePointStatus_UNDEFINED, Timestamp());}); -} - -void ConnectorService::loop() { - //do nothing -} diff --git a/src/MicroOcpp/Model/ConnectorBase/ConnectorService.h b/src/MicroOcpp/Model/ConnectorBase/ConnectorService.h deleted file mode 100644 index c63a67db..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/ConnectorService.h +++ /dev/null @@ -1,26 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CHARGECONTROLCOMMON_H -#define MO_CHARGECONTROLCOMMON_H - -#include -#include - -namespace MicroOcpp { - -class Context; - -class ConnectorService : public MemoryManaged { -private: - Context& context; -public: - ConnectorService(Context& context, unsigned int numConnectors, std::shared_ptr filesystem); - - void loop(); -}; - -} //end namespace MicroOcpp - -#endif diff --git a/src/MicroOcpp/Model/ConnectorBase/EvseId.h b/src/MicroOcpp/Model/ConnectorBase/EvseId.h deleted file mode 100644 index d2a45f03..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/EvseId.h +++ /dev/null @@ -1,26 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_EVSEID_H -#define MO_EVSEID_H - -#include - -#if MO_ENABLE_V201 - -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/ConnectorBase/UnlockConnectorResult.h b/src/MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h deleted file mode 100644 index 0d863f2d..00000000 --- a/src/MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h +++ /dev/null @@ -1,36 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_UNLOCKCONNECTORRESULT_H -#define MO_UNLOCKCONNECTORRESULT_H - -#include - -// Connector-lock related behavior (i.e. if UnlockConnectorOnEVSideDisconnect is RW; enable HW binding for UnlockConnector) -#ifndef MO_ENABLE_CONNECTOR_LOCK -#define MO_ENABLE_CONNECTOR_LOCK 0 -#endif - -#if MO_ENABLE_CONNECTOR_LOCK - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#ifndef MO_UNLOCK_TIMEOUT -#define MO_UNLOCK_TIMEOUT 10000 // if Result is Pending, wait at most this period (in ms) until sending UnlockFailed -#endif - -typedef enum { - UnlockConnectorResult_UnlockFailed, - UnlockConnectorResult_Unlocked, - UnlockConnectorResult_Pending // unlock action not finished yet, result still unknown (MO will check again later) -} UnlockConnectorResult; - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // MO_ENABLE_CONNECTOR_LOCK -#endif // MO_UNLOCKCONNECTORRESULT_H diff --git a/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp b/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp new file mode 100644 index 00000000..824c2831 --- /dev/null +++ b/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp @@ -0,0 +1,101 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +const char *mo_serializeGetLogStatus(MO_GetLogStatus status) { + const char *statusCstr = ""; + switch (status) { + case (MO_GetLogStatus_Accepted): + statusCstr = "Accepted"; + break; + case (MO_GetLogStatus_Rejected): + statusCstr = "Rejected"; + break; + case (MO_GetLogStatus_AcceptedCanceled): + statusCstr = "AcceptedCanceled"; + break; + } + return statusCstr; +} + +const char *mo_serializeUploadLogStatus(MO_UploadLogStatus status) { + const char *statusCstr = ""; + switch (status) { + case (MO_UploadLogStatus_BadMessage): + statusCstr = "BadMessage"; + break; + case (MO_UploadLogStatus_Idle): + statusCstr = "Idle"; + break; + case (MO_UploadLogStatus_NotSupportedOperation): + statusCstr = "NotSupportedOperation"; + break; + case (MO_UploadLogStatus_PermissionDenied): + statusCstr = "PermissionDenied"; + break; + case (MO_UploadLogStatus_Uploaded): + statusCstr = "Uploaded"; + break; + case (MO_UploadLogStatus_UploadFailure): + statusCstr = "UploadFailure"; + break; + case (MO_UploadLogStatus_Uploading): + statusCstr = "Uploading"; + break; + case (MO_UploadLogStatus_AcceptedCanceled): + statusCstr = "AcceptedCanceled"; + break; + } + return statusCstr; +} + +MO_LogType mo_deserializeLogType(const char *v) { + if (!v) { + return MO_LogType_UNDEFINED; + } + + MO_LogType res = MO_LogType_UNDEFINED; + + if (!strcmp(v, "DiagnosticsLog")) { + res = MO_LogType_DiagnosticsLog; + } else if (!strcmp(v, "SecurityLog")) { + res = MO_LogType_SecurityLog; + } + + return res; +} + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS +namespace MicroOcpp { +namespace Ocpp16 { + +const char *serializeDiagnosticsStatus(DiagnosticsStatus status) { + const char *statusCstr = ""; + switch (status) { + case (DiagnosticsStatus::Idle): + statusCstr = "Idle"; + break; + case (DiagnosticsStatus::Uploaded): + statusCstr = "Uploaded"; + break; + case (DiagnosticsStatus::UploadFailed): + statusCstr = "UploadFailed"; + break; + case (DiagnosticsStatus::Uploading): + statusCstr = "Uploading"; + break; + } + return statusCstr; +} + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp/Model/Diagnostics/Diagnostics.h b/src/MicroOcpp/Model/Diagnostics/Diagnostics.h new file mode 100644 index 00000000..b5fa040c --- /dev/null +++ b/src/MicroOcpp/Model/Diagnostics/Diagnostics.h @@ -0,0 +1,78 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_DIAGNOSTICS_H +#define MO_DIAGNOSTICS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +#ifndef MO_GETLOG_FNAME_SIZE +#define MO_GETLOG_FNAME_SIZE 30 +#endif + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +typedef enum { + MO_GetLogStatus_UNDEFINED, //MO-internal + MO_GetLogStatus_Accepted, + MO_GetLogStatus_Rejected, + MO_GetLogStatus_AcceptedCanceled, +} MO_GetLogStatus; +const char *mo_serializeGetLogStatus(MO_GetLogStatus status); + +// UploadLogStatusEnumType (6.17 in 1.6 Security whitepaper, 3.87 in 2.0.1 spec) +typedef enum { + MO_UploadLogStatus_BadMessage, + MO_UploadLogStatus_Idle, + MO_UploadLogStatus_NotSupportedOperation, + MO_UploadLogStatus_PermissionDenied, + MO_UploadLogStatus_Uploaded, + MO_UploadLogStatus_UploadFailure, + MO_UploadLogStatus_Uploading, + MO_UploadLogStatus_AcceptedCanceled, + +} MO_UploadLogStatus; +const char *mo_serializeUploadLogStatus(MO_UploadLogStatus status); + +typedef enum { + MO_UploadStatus_NotUploaded, + MO_UploadStatus_Uploaded, + MO_UploadStatus_UploadFailed +} MO_UploadStatus; + +typedef enum { + MO_LogType_UNDEFINED, + MO_LogType_DiagnosticsLog, + MO_LogType_SecurityLog, +} MO_LogType; +MO_LogType mo_deserializeLogType(const char *v); + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +#ifdef __cplusplus +} //extern "C" + +#if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS + +namespace MicroOcpp { +namespace Ocpp16 { + +enum class DiagnosticsStatus { + Idle, + Uploaded, + UploadFailed, + Uploading +}; +const char *serializeDiagnosticsStatus(DiagnosticsStatus status); + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS + +#endif //__cplusplus +#endif diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 6257ad57..b46ea22f 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -3,46 +3,193 @@ // MIT License #include -#include +#include #include +#include +#include +#include +#include +#include #include #include #include #include +#include +#include //Fetch relevant data from other modules for diagnostics -#include -#include //for serializing ChargePointStatus +#include #include -#include +#include +#include #include //for MO_ENABLE_V201 -#include //for MO_ENABLE_CONNECTOR_LOCK +#include //for MO_ENABLE_CONNECTOR_LOCK + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS -using MicroOcpp::DiagnosticsService; -using MicroOcpp::Ocpp16::DiagnosticsStatus; -using MicroOcpp::Request; +#define MO_DIAG_PREAMBLE_SIZE 192U +#define MO_DIAG_POSTAMBLE_SIZE 1024U + +#if MO_USE_DIAGNOSTICS == MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 +size_t defaultDiagnosticsReader(char *buf, size_t size, void *user_data); +void defaultDiagnosticsOnClose(void *user_data); +#endif -DiagnosticsService::DiagnosticsService(Context& context) : MemoryManaged("v16.Diagnostics.DiagnosticsService"), context(context), location(makeString(getMemoryTag())), diagFileList(makeVector(getMemoryTag())) { +using namespace MicroOcpp; - context.getOperationRegistry().registerOperation("GetDiagnostics", [this] () { - return new Ocpp16::GetDiagnostics(*this);}); +DiagnosticsService::DiagnosticsService(Context& context) : MemoryManaged("v16.Diagnostics.DiagnosticsService"), context(context), diagFileList(makeVector(getMemoryTag())) { - //Register message handler for TriggerMessage operation - context.getOperationRegistry().registerOperation("DiagnosticsStatusNotification", [this] () { - return new Ocpp16::DiagnosticsStatusNotification(getDiagnosticsStatus());}); } DiagnosticsService::~DiagnosticsService() { + MO_FREE(customProtocols); + customProtocols = nullptr; + MO_FREE(location); + location = nullptr; + MO_FREE(filename); + filename = nullptr; MO_FREE(diagPreamble); + diagPreamble = nullptr; MO_FREE(diagPostamble); + diagPostamble = nullptr; } -void DiagnosticsService::loop() { +bool DiagnosticsService::setup() { + + filesystem = context.getFilesystem(); + ftpClient = context.getFtpClient(); + +#if MO_USE_FW_UPDATER == MO_FW_UPDATER_CUSTOM + if (!onUpload || !onUploadStatusInput) { + MO_DBG_ERR("need to set onUpload cb and onUploadStatusInput cb"); + return false; + } +#else + if ((!onUpload || !onUploadStatusInput) && ftpClient) { + if (!filesystem) { + MO_DBG_WARN("Security Log upload disabled (volatile mode)"); + } + } else { + MO_DBG_ERR("depends on FTP client"); + return false; + } +#endif + +#if MO_USE_DIAGNOSTICS == MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 + if (!diagnosticsReader) { + diagnosticsReader = defaultDiagnosticsReader; + diagnosticsOnClose = defaultDiagnosticsOnClose; + } +#endif //MO_USE_DIAGNOSTICS == MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 + + char fileTransferProtocolsBuf [sizeof("FTP,FTPS,HTTP,HTTPS,SFTP") * 2]; //dimension to fit all allowed protocols (plus some safety space) + if (customProtocols) { + auto ret = snprintf(fileTransferProtocolsBuf, sizeof(fileTransferProtocolsBuf), "%s", customProtocols); + if (ret < 0 || (size_t)ret >= sizeof(fileTransferProtocolsBuf)) { + MO_DBG_ERR("custom protocols too long"); + return false; + } + MO_FREE(customProtocols); + customProtocols = nullptr; + } + + + ocppVersion = context.getOcppVersion(); + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("initialization error"); + return false; + } + + auto fileTransferProtocols = configService->declareConfiguration("SupportedFileTransferProtocols", "", MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + if (!fileTransferProtocols) { + MO_DBG_ERR("initialization error"); + return false; + } + + if (customProtocols) { + fileTransferProtocols->setString(customProtocols); + } else if (ftpClient) { + fileTransferProtocols->setString("FTP,FTPS"); + } + + context.getMessageService().registerOperation("GetDiagnostics", [] (Context& context) -> Operation* { + return new Ocpp16::GetDiagnostics(context, *context.getModel16().getDiagnosticsService());}); + + context.getMessageService().registerOperation("GetLog", [] (Context& context) -> Operation* { + return new GetLog(context, *context.getModel16().getDiagnosticsService());}); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("DiagnosticsStatusNotification", nullptr, nullptr); + context.getMessageService().registerOperation("LogStatusNotification", nullptr, nullptr); + #endif //MO_ENABLE_MOCK_SERVER + + auto rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("DiagnosticsStatusNotification", [] (Context& context) -> Operation* { + auto diagSvc = context.getModel16().getDiagnosticsService(); + return new Ocpp16::DiagnosticsStatusNotification(diagSvc->getUploadStatus16()); + }); - if (ftpUpload && ftpUpload->isActive()) { - ftpUpload->loop(); + rcService->addTriggerMessageHandler("LogStatusNotification", [] (Context& context) -> Operation* { + auto diagService = context.getModel16().getDiagnosticsService(); + return new LogStatusNotification(diagService->getUploadStatus(), diagService->getRequestId());}); } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + auto varService = context.getModel201().getVariableService(); + if (!varService) { + MO_DBG_ERR("initialization error"); + return false; + } + + //if transactions can start before the BootNotification succeeds + auto fileTransferProtocols = varService->declareVariable("OCPPCommCtrlr", "FileTransferProtocols", "", Mutability::ReadOnly, false); + if (!fileTransferProtocols) { + MO_DBG_ERR("initialization error"); + return false; + } + + if (customProtocols) { + fileTransferProtocols->setString(customProtocols); + } else if (ftpClient) { + fileTransferProtocols->setString("FTP,FTPS"); + } + + context.getMessageService().registerOperation("GetLog", [] (Context& context) -> Operation* { + return new GetLog(context, *context.getModel201().getDiagnosticsService());}); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("LogStatusNotification", nullptr, nullptr); + #endif //MO_ENABLE_MOCK_SERVER + + auto rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("LogStatusNotification", [] (Context& context) -> Operation* { + auto diagService = context.getModel201().getDiagnosticsService(); + return new LogStatusNotification(diagService->getUploadStatus(), diagService->getRequestId());}); + } + #endif //MO_ENABLE_V201 + + MO_FREE(customProtocols); //not needed anymore + customProtocols = nullptr; + + return true; +} + +void DiagnosticsService::loop() { if (ftpUpload) { if (ftpUpload->isActive()) { @@ -53,120 +200,274 @@ void DiagnosticsService::loop() { } } - auto notification = getDiagnosticsStatusNotification(); - if (notification) { - context.initiateRequest(std::move(notification)); + Operation *uploadStatusNotification = nullptr; + +#if MO_ENABLE_V16 + if (use16impl) { + if (getUploadStatus16() != lastReportedUploadStatus16) { + lastReportedUploadStatus16 = getUploadStatus16(); + if (lastReportedUploadStatus16 != Ocpp16::DiagnosticsStatus::Idle) { + uploadStatusNotification = new Ocpp16::DiagnosticsStatusNotification(lastReportedUploadStatus16); + if (!uploadStatusNotification) { + MO_DBG_ERR("OOM"); + } + } + } + } else +#endif + { + if (getUploadStatus() != lastReportedUploadStatus) { + lastReportedUploadStatus = getUploadStatus(); + if (lastReportedUploadStatus != MO_UploadLogStatus_Idle) { + uploadStatusNotification = new LogStatusNotification(lastReportedUploadStatus, requestId); + if (!uploadStatusNotification) { + MO_DBG_ERR("OOM"); + } + } + } + } + + if (uploadStatusNotification) { + auto request = makeRequest(context, uploadStatusNotification); + if (!request) { + MO_DBG_ERR("OOM"); + delete uploadStatusNotification; + } else { + context.getMessageService().sendRequest(std::move(request)); + } + uploadStatusNotification = nullptr; } - const auto& timestampNow = context.getModel().getClock().now(); - if (retries > 0 && timestampNow >= nextTry) { + if (runCustomUpload) { + if (getUploadStatus() != MO_UploadLogStatus_Uploading) { + MO_DBG_INFO("custom upload finished"); + runCustomUpload = false; + } + return; + } + + auto& clock = context.getClock(); + + int32_t dtNextTry; + clock.delta(clock.getUptime(), nextTry, dtNextTry); + + if (retries > 0 && dtNextTry >= 0) { if (!uploadIssued) { - if (onUpload != nullptr) { - MO_DBG_DEBUG("Call onUpload"); - onUpload(location.c_str(), startTime, stopTime); + + bool success = false; + switch (logType) { + case MO_LogType_DiagnosticsLog: + success = uploadDiagnostics(); + break; + case MO_LogType_SecurityLog: + success = uploadSecurityLog(); + break; + } + + if (success) { uploadIssued = true; uploadFailure = false; } else { - MO_DBG_ERR("onUpload must be set! (see setOnUpload). Will abort"); + MO_DBG_ERR("cannot upload via FTP. Abort"); retries = 0; uploadIssued = false; uploadFailure = true; + cleanUploadData(); + cleanGetLogData(); } } if (uploadIssued) { - if (uploadStatusInput != nullptr && uploadStatusInput() == UploadStatus::Uploaded) { + if (getUploadStatus() == MO_UploadLogStatus_Uploaded) { //success! MO_DBG_DEBUG("end upload routine (by status)"); uploadIssued = false; retries = 0; + cleanUploadData(); + cleanGetLogData(); } //check if maximum time elapsed or failed + bool isUploadFailure = false; + switch(getUploadStatus()) { + case MO_UploadLogStatus_BadMessage: + case MO_UploadLogStatus_NotSupportedOperation: + case MO_UploadLogStatus_PermissionDenied: + case MO_UploadLogStatus_UploadFailure: + isUploadFailure = true; + break; + case MO_UploadLogStatus_Uploading: + case MO_UploadLogStatus_Idle: + case MO_UploadLogStatus_Uploaded: + case MO_UploadLogStatus_AcceptedCanceled: + isUploadFailure = false; + break; + } + const int UPLOAD_TIMEOUT = 60; - if (timestampNow - nextTry >= UPLOAD_TIMEOUT - || (uploadStatusInput != nullptr && uploadStatusInput() == UploadStatus::UploadFailed)) { + if (isUploadFailure || dtNextTry >= UPLOAD_TIMEOUT) { //maximum upload time elapsed or failed - if (uploadStatusInput == nullptr) { - //No way to find out if failed. But maximum time has elapsed. Assume success - MO_DBG_DEBUG("end upload routine (by timer)"); - uploadIssued = false; - retries = 0; + //either we have UploadFailed status or (NotUploaded + timeout) here + MO_DBG_WARN("Upload timeout or failed"); + cleanUploadData(); + + const int TRANSITION_DELAY = 10; + if (retryInterval <= UPLOAD_TIMEOUT + TRANSITION_DELAY) { + nextTry = clock.getUptime(); + clock.add(nextTry, TRANSITION_DELAY); //wait for another 10 seconds } else { - //either we have UploadFailed status or (NotUploaded + timeout) here - MO_DBG_WARN("Upload timeout or failed"); - ftpUpload.reset(); - - const int TRANSITION_DELAY = 10; - if (retryInterval <= UPLOAD_TIMEOUT + TRANSITION_DELAY) { - nextTry = timestampNow; - nextTry += TRANSITION_DELAY; //wait for another 10 seconds - } else { - nextTry += retryInterval; - } - retries--; + clock.add(nextTry, retryInterval); + } + retries--; - if (retries == 0) { - MO_DBG_DEBUG("end upload routine (no more retry)"); - uploadFailure = true; - } + if (retries == 0) { + MO_DBG_DEBUG("end upload routine (no more retry)"); + uploadFailure = true; + cleanGetLogData(); } } } //end if (uploadIssued) } //end try upload } -//timestamps before year 2021 will be treated as "undefined" -MicroOcpp::String DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned int retries, unsigned int retryInterval, Timestamp startTime, Timestamp stopTime) { - if (onUpload == nullptr) { - return makeString(getMemoryTag()); +#if MO_ENABLE_V16 +bool DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned int retries, unsigned int retryInterval, Timestamp startTime, Timestamp stopTime, char filenameOut[MO_GETLOG_FNAME_SIZE]) { + + bool success = false; + + auto ret = getLog(MO_LogType_DiagnosticsLog, -1, retries, retryInterval, location, startTime, stopTime, filenameOut); + switch (ret) { + case MO_GetLogStatus_Accepted: + case MO_GetLogStatus_AcceptedCanceled: + this->use16impl = true; + success = true; + break; + case MO_GetLogStatus_Rejected: + filenameOut[0] = '\0'; //clear filename - that means that no upload will follow + success = true; + break; + case MO_GetLogStatus_UNDEFINED: + success = false; + break; } + return success; +} +#endif //MO_ENABLE_V16 - String fileName; - if (refreshFilename) { - fileName = refreshFilename().c_str(); - } else { - fileName = "diagnostics.log"; +MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int retries, unsigned int retryInterval, const char *remoteLocation, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]) { + + if (runCustomUpload || this->retries > 0) { + MO_DBG_INFO("upload still running"); + return MO_GetLogStatus_Rejected; } - this->location.reserve(strlen(location) + 1 + fileName.size()); + //clean data which outlives last GetLog + ftpUploadStatus = MO_UploadLogStatus_Idle; - this->location = location; + #if MO_ENABLE_V16 + use16impl = false; //may be re-assigned in requestDiagnosticsUpload + #endif - if (!this->location.empty() && this->location.back() != '/') { - this->location.append("/"); + //set data which is needed for custom upload and built-in upload + this->requestId = requestId; + + auto& clock = context.getClock(); + + if (onUpload && onUploadStatusInput) { + //initiate custom upload + + char oldestTimestampStr [MO_JSONDATE_SIZE]; + if (oldestTimestamp.isDefined()) { + if (!clock.toJsonString(oldestTimestamp, oldestTimestampStr, sizeof(oldestTimestampStr))) { + MO_DBG_ERR("toJsonString"); + return MO_GetLogStatus_UNDEFINED; + } + } + + char latestTimestampStr [MO_JSONDATE_SIZE]; + if (latestTimestamp.isDefined()) { + if (!clock.toJsonString(latestTimestamp, latestTimestampStr, sizeof(latestTimestampStr))) { + MO_DBG_ERR("toJsonString"); + return MO_GetLogStatus_UNDEFINED; + } + } + + auto ret = onUpload( + type, + location, + oldestTimestamp.isDefined() ? oldestTimestampStr : nullptr, + latestTimestamp.isDefined() ? latestTimestampStr : nullptr, + filenameOut, + onUploadUserData); + if (ret == MO_GetLogStatus_Accepted || ret == MO_GetLogStatus_AcceptedCanceled) { + runCustomUpload = true; + } + return ret; } - this->location.append(fileName.c_str()); - this->retries = retries; - this->retryInterval = retryInterval; - this->startTime = startTime; - - Timestamp stopMin = Timestamp(2021,0,0,0,0,0); - if (stopTime >= stopMin) { - this->stopTime = stopTime; - } else { - auto newStop = context.getModel().getClock().now(); - newStop += 3600 * 24 * 365; //set new stop time one year in future - this->stopTime = newStop; + //initiate built-in upload + + bool hasFilename = false; + size_t filenameSize = 0; + + MO_FREE(this->location); + this->location = nullptr; + size_t locationSize = strlen(location); + this->location = static_cast(MO_MALLOC(getMemoryTag, locationSize)); + if (!this->location) { + MO_DBG_ERR("OOM"); + goto fail; } - + + MO_FREE(this->filename); + this->filename = nullptr; + + if (refreshFilename) { + hasFilename = refreshFilename(type, filenameOut, refreshFilenameUserData); + } + + if (!hasFilename) { + auto ret = snprintf(filenameOut, MO_GETLOG_FNAME_SIZE, "diagnostics.log"); + if (ret < 0 || (size_t)ret >= MO_GETLOG_FNAME_SIZE) { + MO_DBG_ERR("snprintf: %i", ret); + goto fail; + } + } + + filenameSize = strlen(filenameOut) + 1; + this->filename = static_cast(MO_MALLOC(getMemoryTag(), filenameSize)); + if (!this->filename) { + MO_DBG_ERR("OOM"); + goto fail; + } + snprintf(this->filename, filenameSize, "%s", filenameOut); + + this->logType = type; + this->retries = retries > 0 ? retries : 1; + this->retryInterval = retryInterval > 30 ? retryInterval : 30; + this->oldestTimestamp = oldestTimestamp; + this->latestTimestamp = latestTimestamp; + #if MO_DBG_LEVEL >= MO_DL_INFO { - char dbuf [JSONDATE_LENGTH + 1] = {'\0'}; - char dbuf2 [JSONDATE_LENGTH + 1] = {'\0'}; - this->startTime.toJsonString(dbuf, JSONDATE_LENGTH + 1); - this->stopTime.toJsonString(dbuf2, JSONDATE_LENGTH + 1); + char dbuf [MO_JSONDATE_SIZE] = {'\0'}; + char dbuf2 [MO_JSONDATE_SIZE] = {'\0'}; + if (oldestTimestamp.isDefined()) { + clock.toJsonString(oldestTimestamp, dbuf, sizeof(dbuf)); + } + if (latestTimestamp.isDefined()) { + clock.toJsonString(latestTimestamp, dbuf2, sizeof(dbuf2)); + } MO_DBG_INFO("Scheduled Diagnostics upload!\n" \ " location = %s\n" \ " retries = %i" \ ", retryInterval = %u" \ - " startTime = %s\n" \ - " stopTime = %s", - this->location.c_str(), + " oldestTimestamp = %s\n" \ + " latestTimestamp = %s", + this->location, this->retries, this->retryInterval, dbuf, @@ -174,146 +475,208 @@ MicroOcpp::String DiagnosticsService::requestDiagnosticsUpload(const char *locat } #endif - nextTry = context.getModel().getClock().now(); - nextTry += 5; //wait for 5s before upload + nextTry = clock.now(); + clock.add(nextTry, 5); //wait for 5s before upload uploadIssued = false; + uploadFailure = false; #if MO_DBG_LEVEL >= MO_DL_DEBUG { - char dbuf [JSONDATE_LENGTH + 1] = {'\0'}; - nextTry.toJsonString(dbuf, JSONDATE_LENGTH + 1); + char dbuf [MO_JSONDATE_SIZE] = {'\0'}; + if (nextTry.isDefined()) { + clock.toJsonString(nextTry, dbuf, sizeof(dbuf)); + } MO_DBG_DEBUG("Initial try at %s", dbuf); } #endif - return fileName; + return MO_GetLogStatus_Accepted; +fail: + cleanGetLogData(); + return MO_GetLogStatus_UNDEFINED; } -DiagnosticsStatus DiagnosticsService::getDiagnosticsStatus() { - if (uploadFailure) { - return DiagnosticsStatus::UploadFailed; - } - - if (uploadIssued) { - if (uploadStatusInput != nullptr) { - switch (uploadStatusInput()) { - case UploadStatus::NotUploaded: - return DiagnosticsStatus::Uploading; - case UploadStatus::Uploaded: - return DiagnosticsStatus::Uploaded; - case UploadStatus::UploadFailed: - return DiagnosticsStatus::UploadFailed; - } - } - return DiagnosticsStatus::Uploading; +int DiagnosticsService::getRequestId() { + if (runCustomUpload || this->retries > 0) { + return requestId; + } else { + return -1; } - return DiagnosticsStatus::Idle; } -std::unique_ptr DiagnosticsService::getDiagnosticsStatusNotification() { +MO_UploadLogStatus DiagnosticsService::getUploadStatus() { - if (getDiagnosticsStatus() != lastReportedStatus) { - lastReportedStatus = getDiagnosticsStatus(); - if (lastReportedStatus != DiagnosticsStatus::Idle) { - Operation *diagNotificationMsg = new Ocpp16::DiagnosticsStatusNotification(lastReportedStatus); - auto diagNotification = makeRequest(diagNotificationMsg); - return diagNotification; - } + MO_UploadLogStatus status = MO_UploadLogStatus_Idle; + + if (runCustomUpload) { + status = onUploadStatusInput(onUploadUserData); + } else if (uploadFailure) { + status = MO_UploadLogStatus_UploadFailure; + } else { + status = ftpUploadStatus; } + return status; +} - return nullptr; +Ocpp16::DiagnosticsStatus DiagnosticsService::getUploadStatus16() { + + MO_UploadLogStatus status = getUploadStatus(); + + auto res = Ocpp16::DiagnosticsStatus::Idle; + + switch(status) { + case MO_UploadLogStatus_Idle: + res = Ocpp16::DiagnosticsStatus::Idle; + break; + case MO_UploadLogStatus_Uploaded: + case MO_UploadLogStatus_AcceptedCanceled: + res = Ocpp16::DiagnosticsStatus::Uploaded; + break; + case MO_UploadLogStatus_BadMessage: + case MO_UploadLogStatus_NotSupportedOperation: + case MO_UploadLogStatus_PermissionDenied: + case MO_UploadLogStatus_UploadFailure: + res = Ocpp16::DiagnosticsStatus::UploadFailed; + break; + case MO_UploadLogStatus_Uploading: + res = Ocpp16::DiagnosticsStatus::Uploading; + break; + } + return res; } -void DiagnosticsService::setRefreshFilename(std::function refreshFn) { - this->refreshFilename = refreshFn; +void DiagnosticsService::setRefreshFilename(bool (*refreshFilename)(MO_LogType type, char filenameOut[MO_GETLOG_FNAME_SIZE], void *user_data), void *user_data) { + this->refreshFilename = refreshFilename; + this->refreshFilenameUserData = user_data; } -void DiagnosticsService::setOnUpload(std::function onUpload) { +bool DiagnosticsService::setOnUpload( + MO_GetLogStatus (*onUpload)(MO_LogType type, const char *location, const char *oldestTimestamp, const char *latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE], void *user_data), + MO_UploadLogStatus (*onUploadStatusInput)(void *user_data), + const char *customProtocols, + void *user_data) { this->onUpload = onUpload; -} + this->onUploadStatusInput = onUploadStatusInput; + + MO_FREE(this->customProtocols); + this->customProtocols = nullptr; + if (customProtocols) { + size_t customProtocolsSize = strlen(customProtocols) + 1; + this->customProtocols = static_cast(MO_MALLOC(getMemoryTag(), customProtocolsSize)); + if (!this->customProtocols) { + MO_DBG_ERR("OOM"); + return false; + } + snprintf(this->customProtocols, customProtocolsSize, "%s", customProtocols); + } -void DiagnosticsService::setOnUploadStatusInput(std::function uploadStatusInput) { - this->uploadStatusInput = uploadStatusInput; + this->onUploadUserData = user_data; + return true; } -void DiagnosticsService::setDiagnosticsReader(std::function diagnosticsReader, std::function onClose, std::shared_ptr filesystem) { +namespace MicroOcpp { +struct DiagnosticsReaderFtwData { + DiagnosticsService *diagService; + int ret; +}; +} //namespace MicroOcpp - this->onUpload = [this, diagnosticsReader, onClose, filesystem] (const char *location, Timestamp &startTime, Timestamp &stopTime) -> bool { +using namespace MicroOcpp; - auto ftpClient = context.getFtpClient(); - if (!ftpClient) { - MO_DBG_ERR("FTP client not set"); - this->ftpUploadStatus = UploadStatus::UploadFailed; - return false; - } +bool DiagnosticsService::uploadDiagnostics() { - const size_t diagPreambleSize = 128; - diagPreamble = static_cast(MO_MALLOC(getMemoryTag(), diagPreambleSize)); - if (!diagPreamble) { - MO_DBG_ERR("OOM"); - this->ftpUploadStatus = UploadStatus::UploadFailed; - return false; - } - diagPreambleLen = 0; - diagPreambleTransferred = 0; + MO_FREE(diagPreamble); + diagPreamble = nullptr; - diagReaderHasData = diagnosticsReader ? true : false; + diagPreamble = static_cast(MO_MALLOC(getMemoryTag(), MO_DIAG_PREAMBLE_SIZE)); + if (!diagPreamble) { + MO_DBG_ERR("OOM"); + cleanUploadData(); + return false; + } + diagPreambleLen = 0; + diagPreambleTransferred = 0; - const size_t diagPostambleSize = 1024; - diagPostamble = static_cast(MO_MALLOC(getMemoryTag(), diagPostambleSize)); - if (!diagPostamble) { - MO_DBG_ERR("OOM"); - this->ftpUploadStatus = UploadStatus::UploadFailed; - MO_FREE(diagPreamble); - return false; - } - diagPostambleLen = 0; - diagPostambleTransferred = 0; + diagReaderHasData = diagnosticsReader ? true : false; - auto& model = context.getModel(); + MO_FREE(diagPostamble); + diagPostamble = nullptr; - auto cpModel = makeString(getMemoryTag()); - auto fwVersion = makeString(getMemoryTag()); + diagPostamble = static_cast(MO_MALLOC(getMemoryTag(), MO_DIAG_POSTAMBLE_SIZE)); + if (!diagPostamble) { + MO_DBG_ERR("OOM"); + cleanUploadData(); + return false; + } + diagPostambleLen = 0; + diagPostambleTransferred = 0; - if (auto bootService = model.getBootService()) { - if (auto cpCreds = bootService->getChargePointCredentials()) { - cpModel = (*cpCreds)["chargePointModel"] | "Charger"; - fwVersion = (*cpCreds)["firmwareVersion"] | ""; - } - } + BootService *bootService = nullptr; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + bootService = context.getModel16().getBootService(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + bootService = context.getModel201().getBootService(); + } + #endif //MO_ENABLE_V201 - char jsonDate [JSONDATE_LENGTH + 1]; - model.getClock().now().toJsonString(jsonDate, sizeof(jsonDate)); + const char *cpModel = nullptr; + const char *fwVersion = nullptr; - int ret; + if (bootService) { + auto bnData = bootService->getBootNotificationData(); + cpModel = bnData.chargePointModel; + fwVersion = bnData.firmwareVersion; + } - ret = snprintf(diagPreamble, diagPreambleSize, - "### %s Hardware Diagnostics%s%s\n%s\n", - cpModel.c_str(), - fwVersion.empty() ? "" : " - v. ", fwVersion.c_str(), - jsonDate); + char jsonDate [MO_JSONDATE_SIZE]; + if (!context.getClock().toJsonString(context.getClock().now(), jsonDate, sizeof(jsonDate))) { + MO_DBG_ERR("internal error"); + jsonDate[0] = '\0'; + } - if (ret < 0 || (size_t)ret >= diagPreambleSize) { - MO_DBG_ERR("snprintf: %i", ret); - this->ftpUploadStatus = UploadStatus::UploadFailed; - MO_FREE(diagPreamble); - MO_FREE(diagPostamble); - return false; - } + int ret; + + ret = snprintf(diagPreamble, MO_DIAG_PREAMBLE_SIZE, + "### %s Security Log%s%s\n%s\n", + cpModel ? cpModel : "Charger", + fwVersion ? " - v. " : "", fwVersion ? fwVersion : "", + jsonDate); + + if (ret < 0 || (size_t)ret >= MO_DIAG_PREAMBLE_SIZE) { + MO_DBG_ERR("snprintf: %i", ret); + cleanUploadData(); + return false; + } - diagPreambleLen += (size_t)ret; + diagPreambleLen += (size_t)ret; - Connector *connector0 = model.getConnector(0); - Connector *connector1 = model.getConnector(1); - Transaction *connector1Tx = connector1 ? connector1->getTransaction().get() : nullptr; - Connector *connector2 = model.getNumConnectors() > 2 ? model.getConnector(2) : nullptr; - Transaction *connector2Tx = connector2 ? connector2->getTransaction().get() : nullptr; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + //OCPP 1.6 specific diagnostics + + auto& model = context.getModel16(); + + auto txSvc = model.getTransactionService(); + auto txSvcEvse0 = txSvc ? txSvc->getEvse(0) : nullptr; + auto txSvcEvse1 = txSvc ? txSvc->getEvse(1) : nullptr; + auto txSvcEvse2 = txSvc && model.getNumEvseId() > 2 ? txSvc->getEvse(2) : nullptr; + auto txSvcEvse1Tx = txSvcEvse1 ? txSvcEvse1->getTransaction() : nullptr; + auto txSvcEvse2Tx = txSvcEvse2 ? txSvcEvse2->getTransaction() : nullptr; + + auto availSvc = model.getAvailabilityService(); + auto availSvcEvse0 = availSvc ? availSvc->getEvse(0) : nullptr; + auto availSvcEvse1 = availSvc ? availSvc->getEvse(1) : nullptr; + auto availSvcEvse2 = availSvc ? availSvc->getEvse(2) : nullptr; ret = 0; - if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) { + if (ret >= 0 && (size_t)ret + diagPostambleLen < MO_DIAG_POSTAMBLE_SIZE) { diagPostambleLen += (size_t)ret; - ret = snprintf(diagPostamble + diagPostambleLen, diagPostambleSize - diagPostambleLen, + ret = snprintf(diagPostamble + diagPostambleLen, MO_DIAG_POSTAMBLE_SIZE - diagPostambleLen, "\n# OCPP" "\nclient_version=%s" "\nuptime=%lus" @@ -321,8 +684,6 @@ void DiagnosticsService::setDiagnosticsReader(std::functiongetStatus()) : "", - connector1 ? "\nocpp_status_cId1=" : "", connector1 ? cstrFromOcppEveState(connector1->getStatus()) : "", - connector2 ? "\nocpp_status_cId2=" : "", connector2 ? cstrFromOcppEveState(connector2->getStatus()) : "", - context.getConnection().isConnected() ? "connected" : "unconnected", - context.getConnection().getLastConnected() / 1000UL, - context.getConnection().getLastRecv() / 1000UL, - connector1 ? "\ncId1_hasTx=" : "", connector1 ? (connector1Tx ? "1" : "0") : "", - connector1Tx ? "\ncId1_txActive=" : "", connector1Tx ? (connector1Tx->isActive() ? "1" : "0") : "", - connector1Tx ? "\ncId1_txHasStarted=" : "", connector1Tx ? (connector1Tx->getStartSync().isRequested() ? "1" : "0") : "", - connector1Tx ? "\ncId1_txHasStopped=" : "", connector1Tx ? (connector1Tx->getStopSync().isRequested() ? "1" : "0") : "", - connector2 ? "\ncId2_hasTx=" : "", connector2 ? (connector2Tx ? "1" : "0") : "", - connector2Tx ? "\ncId2_txActive=" : "", connector2Tx ? (connector2Tx->isActive() ? "1" : "0") : "", - connector2Tx ? "\ncId2_txHasStarted=" : "", connector2Tx ? (connector2Tx->getStartSync().isRequested() ? "1" : "0") : "", - connector2Tx ? "\ncId2_txHasStopped=" : "", connector2Tx ? (connector2Tx->getStopSync().isRequested() ? "1" : "0") : "", + context.getTicksMs() / 1000UL, + availSvcEvse0 ? "\nocpp_status_cId0=" : "", availSvcEvse0 ? mo_serializeChargePointStatus(availSvcEvse0->getStatus()) : "", + availSvcEvse1 ? "\nocpp_status_cId1=" : "", availSvcEvse1 ? mo_serializeChargePointStatus(availSvcEvse1->getStatus()) : "", + availSvcEvse2 ? "\nocpp_status_cId2=" : "", availSvcEvse2 ? mo_serializeChargePointStatus(availSvcEvse2->getStatus()) : "", + context.getConnection() && context.getConnection()->isConnected() ? "connected" : "unconnected", + txSvcEvse1 ? "\ncId1_hasTx=" : "", txSvcEvse1 ? (txSvcEvse1Tx ? "1" : "0") : "", + txSvcEvse1Tx ? "\ncId1_txActive=" : "", txSvcEvse1Tx ? (txSvcEvse1Tx->isActive() ? "1" : "0") : "", + txSvcEvse1Tx ? "\ncId1_txHasStarted=" : "", txSvcEvse1Tx ? (txSvcEvse1Tx->getStartSync().isRequested() ? "1" : "0") : "", + txSvcEvse1Tx ? "\ncId1_txHasStopped=" : "", txSvcEvse1Tx ? (txSvcEvse1Tx->getStopSync().isRequested() ? "1" : "0") : "", + txSvcEvse2 ? "\ncId2_hasTx=" : "", txSvcEvse2 ? (txSvcEvse2Tx ? "1" : "0") : "", + txSvcEvse2Tx ? "\ncId2_txActive=" : "", txSvcEvse2Tx ? (txSvcEvse2Tx->isActive() ? "1" : "0") : "", + txSvcEvse2Tx ? "\ncId2_txHasStarted=" : "", txSvcEvse2Tx ? (txSvcEvse2Tx->getStartSync().isRequested() ? "1" : "0") : "", + txSvcEvse2Tx ? "\ncId2_txHasStopped=" : "", txSvcEvse2Tx ? (txSvcEvse2Tx->getStopSync().isRequested() ? "1" : "0") : "", MO_ENABLE_CONNECTOR_LOCK, MO_ENABLE_FILE_INDEX, MO_ENABLE_V201 ); } + } + #endif //MO_ENABLE_V16 + + if (filesystem) { + + if (ret >= 0 && (size_t)ret + diagPostambleLen < MO_DIAG_POSTAMBLE_SIZE) { + diagPostambleLen += (size_t)ret; + ret = snprintf(diagPostamble + diagPostambleLen, MO_DIAG_POSTAMBLE_SIZE - diagPostambleLen, "\n# Filesystem\n"); + } + + DiagnosticsReaderFtwData data; + data.diagService = this; + data.ret = ret; - if (filesystem) { + diagFileList.clear(); - if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) { + filesystem->ftw(filesystem->path_prefix, [] (const char *fname, void *user_data) -> int { + auto& data = *reinterpret_cast(user_data); + auto& diagPostambleLen = data.diagService->diagPostambleLen; + auto& diagPostamble = data.diagService->diagPostamble; + auto& diagFileList = data.diagService->diagFileList; + auto& ret = data.ret; + + if (ret >= 0 && (size_t)ret + diagPostambleLen < MO_DIAG_POSTAMBLE_SIZE) { diagPostambleLen += (size_t)ret; - ret = snprintf(diagPostamble + diagPostambleLen, diagPostambleSize - diagPostambleLen, "\n# Filesystem\n"); + ret = snprintf(diagPostamble + diagPostambleLen, MO_DIAG_POSTAMBLE_SIZE - diagPostambleLen, "%s\n", fname); } + diagFileList.emplace_back(fname); + return 0; + }, reinterpret_cast(&data)); - filesystem->ftw_root([this, &ret] (const char *fname) -> int { - if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) { - diagPostambleLen += (size_t)ret; - ret = snprintf(diagPostamble + diagPostambleLen, diagPostambleSize - diagPostambleLen, "%s\n", fname); - } - diagFileList.emplace_back(fname); - return 0; - }); + ret = data.ret; - MO_DBG_DEBUG("discovered %zu files", diagFileList.size()); - } + MO_DBG_DEBUG("discovered %zu files", diagFileList.size()); + } - if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) { - diagPostambleLen += (size_t)ret; - } else { - char errMsg [64]; - auto errLen = snprintf(errMsg, sizeof(errMsg), "\n[Diagnostics cut]\n"); - size_t ellipseStart = std::min(diagPostambleSize - (size_t)errLen - 1, diagPostambleLen); - auto ret2 = snprintf(diagPostamble + ellipseStart, diagPostambleSize - ellipseStart, "%s", errMsg); - diagPostambleLen += (size_t)ret2; + if (ret >= 0 && (size_t)ret + diagPostambleLen < MO_DIAG_POSTAMBLE_SIZE) { + diagPostambleLen += (size_t)ret; + } else { + char errMsg [64]; + auto errLen = snprintf(errMsg, sizeof(errMsg), "\n[Diagnostics cut]\n"); + size_t ellipseStart = std::min(MO_DIAG_POSTAMBLE_SIZE - (size_t)errLen - 1, diagPostambleLen); + auto ret2 = snprintf(diagPostamble + ellipseStart, MO_DIAG_POSTAMBLE_SIZE - ellipseStart, "%s", errMsg); + diagPostambleLen += (size_t)ret2; + } + + if (!startFtpUpload()) { + cleanUploadData(); + return false; + } + + return true; +} + +bool DiagnosticsService::uploadSecurityLog() { + + if (!filesystem) { + MO_DBG_ERR("depends on filesystem"); + cleanUploadData(); + return false; + } + + MO_FREE(diagPreamble); + diagPreamble = nullptr; + + diagPreamble = static_cast(MO_MALLOC(getMemoryTag(), MO_DIAG_PREAMBLE_SIZE)); + if (!diagPreamble) { + MO_DBG_ERR("OOM"); + cleanUploadData(); + return false; + } + diagPreambleLen = 0; + diagPreambleTransferred = 0; + + diagReaderHasData = false; + + BootService *bootService = nullptr; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + bootService = context.getModel16().getBootService(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + bootService = context.getModel201().getBootService(); + } + #endif //MO_ENABLE_V201 + + auto cpModel = makeString(getMemoryTag()); + auto fwVersion = makeString(getMemoryTag()); + + if (bootService) { + auto bnData = bootService->getBootNotificationData(); + cpModel = bnData.chargePointModel; + fwVersion = bnData.firmwareVersion; + } + + char jsonDate [MO_JSONDATE_SIZE]; + if (!context.getClock().toJsonString(context.getClock().now(), jsonDate, sizeof(jsonDate))) { + MO_DBG_ERR("internal error"); + jsonDate[0] = '\0'; + } + + int ret; + + ret = snprintf(diagPreamble, MO_DIAG_PREAMBLE_SIZE, + "### %s Hardware Diagnostics%s%s\n%s\n", + cpModel.c_str(), + fwVersion.empty() ? "" : " - v. ", fwVersion.c_str(), + jsonDate); + + if (ret < 0 || (size_t)ret >= MO_DIAG_PREAMBLE_SIZE) { + MO_DBG_ERR("snprintf: %i", ret); + cleanUploadData(); + return false; + } + + diagPreambleLen += (size_t)ret; + + //load Security Log index structure from flash (fileNrBegin denotes first file, fileNrEnd denots index after last file) + + unsigned int fileNrBegin = 0, fileNrEnd = 0; + + if (!FilesystemUtils::loadRingIndex(filesystem, MO_SECLOG_FN_PREFIX, MO_SECLOG_INDEX_MAX, &fileNrBegin, &fileNrEnd)) { + MO_DBG_ERR("failed to load security event log"); + cleanUploadData(); + return false; + } + + DiagnosticsReaderFtwData data; + data.diagService = this; + + diagFileList.clear(); + + int ret_ftw = filesystem->ftw(filesystem->path_prefix, [] (const char *fname, void *user_data) -> int { + auto& data = *reinterpret_cast(user_data); + auto& diagFileList = data.diagService->diagFileList; + + if (!strncmp(fname, MO_SECLOG_FN_PREFIX, sizeof(MO_SECLOG_FN_PREFIX) - 1)) { + //fname starts with "slog-" + diagFileList.emplace_back(fname); } - this->ftpUpload = ftpClient->postFile(location, - [this, diagnosticsReader, filesystem] (unsigned char *buf, size_t size) -> size_t { - size_t written = 0; - if (written < size && diagPreambleTransferred < diagPreambleLen) { - size_t writeLen = std::min(size - written, diagPreambleLen - diagPreambleTransferred); - memcpy(buf + written, diagPreamble + diagPreambleTransferred, writeLen); - diagPreambleTransferred += writeLen; - written += writeLen; - } + return 0; + }, reinterpret_cast(&data)); - while (written < size && diagReaderHasData && diagnosticsReader) { - size_t writeLen = diagnosticsReader((char*)buf + written, size - written); - if (writeLen == 0) { - diagReaderHasData = false; - } - written += writeLen; - } + if (ret_ftw != 0) { + MO_DBG_ERR("ftw: %i", ret_ftw); + cleanUploadData(); + return false; + } - if (written < size && diagPostambleTransferred < diagPostambleLen) { - size_t writeLen = std::min(size - written, diagPostambleLen - diagPostambleTransferred); - memcpy(buf + written, diagPostamble + diagPostambleTransferred, writeLen); - diagPostambleTransferred += writeLen; - written += writeLen; + MO_DBG_DEBUG("discovered %zu files", diagFileList.size()); + + if (!startFtpUpload()) { + cleanUploadData(); + return false; + } + + return true; +} + +bool DiagnosticsService::startFtpUpload() { + + size_t locationLen = strlen(location); + size_t filenameLen = strlen(filename); + size_t urlSize = locationLen + + 1 + //separating '/' between location and filename + filenameLen + + 1; //terminating 0 + + char *url = static_cast(MO_MALLOC(getMemoryTag(), urlSize)); + if (!url) { + MO_DBG_ERR("OOM"); + return false; + } + + if (locationLen > 0 && filenameLen > 0 && location[locationLen - 1] != '/') { + snprintf(url, urlSize, "%s/%s", location, filename); + } else { + snprintf(url, urlSize, "%s%s", location, filename); + } + + this->ftpUpload = ftpClient->postFile(url, + [this] (unsigned char *buf, size_t size) -> size_t { + size_t written = 0; + if (written < size && diagPreambleTransferred < diagPreambleLen) { + size_t writeLen = std::min(size - written, diagPreambleLen - diagPreambleTransferred); + memcpy(buf + written, diagPreamble + diagPreambleTransferred, writeLen); + diagPreambleTransferred += writeLen; + written += writeLen; + } + + while (written < size && diagReaderHasData && diagnosticsReader) { + size_t writeLen = diagnosticsReader((char*)buf + written, size - written, diagnosticsUserData); + if (writeLen == 0) { + diagReaderHasData = false; } + written += writeLen; + } - while (written < size && !diagFileList.empty() && filesystem) { + if (written < size && diagPostambleTransferred < diagPostambleLen) { + size_t writeLen = std::min(size - written, diagPostambleLen - diagPostambleTransferred); + memcpy(buf + written, diagPostamble + diagPostambleTransferred, writeLen); + diagPostambleTransferred += writeLen; + written += writeLen; + } - char fpath [MO_MAX_PATH_SIZE]; - auto ret = snprintf(fpath, sizeof(fpath), "%s%s", MO_FILENAME_PREFIX, diagFileList.back().c_str()); - if (ret < 0 || (size_t)ret >= sizeof(fpath)) { - MO_DBG_ERR("fn error: %i", ret); - diagFileList.pop_back(); - continue; - } + while (written < size && !diagFileList.empty() && filesystem) { - if (auto file = filesystem->open(fpath, "r")) { - - if (diagFilesBackTransferred == 0) { - char fileHeading [30 + MO_MAX_PATH_SIZE]; - auto writeLen = snprintf(fileHeading, sizeof(fileHeading), "\n\n# File %s:\n", diagFileList.back().c_str()); - if (writeLen < 0 || (size_t)writeLen >= sizeof(fileHeading)) { - MO_DBG_ERR("fn error: %i", ret); - diagFileList.pop_back(); - continue; - } - if (writeLen + written > size || //heading doesn't fit anymore, return with a bit unused buffer space and print heading the next time - writeLen + written == size) { //filling the buffer up exactly would mean that no file payload is written and this head gets printed again - - MO_DBG_DEBUG("upload diag chunk (%zuB)", written); - return written; - } - - memcpy(buf + written, fileHeading, (size_t)writeLen); - written += (size_t)writeLen; - } + char fpath [MO_MAX_PATH_SIZE]; + if (!FilesystemUtils::printPath(filesystem, fpath, sizeof(fpath), diagFileList.back().c_str())) { + MO_DBG_ERR("path error: %s", diagFileList.back().c_str()); + diagFileList.pop_back(); + continue; + } - file->seek(diagFilesBackTransferred); - size_t writeLen = file->read((char*)buf + written, size - written); + if (auto file = filesystem->open(fpath, "r")) { - if (writeLen < size - written) { + if (diagFilesBackTransferred == 0) { + char fileHeading [30 + MO_MAX_PATH_SIZE]; + auto writeLen = snprintf(fileHeading, sizeof(fileHeading), "\n\n# File %s:\n", diagFileList.back().c_str()); + if (writeLen < 0 || (size_t)writeLen >= sizeof(fileHeading)) { + MO_DBG_ERR("fn error: %i", writeLen); diagFileList.pop_back(); + filesystem->close(file); + continue; } - written += writeLen; - } else { - MO_DBG_ERR("could not open file: %s", fpath); - diagFileList.pop_back(); + if (writeLen + written > size || //heading doesn't fit anymore, return with a bit unused buffer space and print heading the next time + writeLen + written == size) { //filling the buffer up exactly would mean that no file payload is written and this head gets printed again + + MO_DBG_DEBUG("upload diag chunk (%zuB)", written); + filesystem->close(file); + return written; + } + + memcpy(buf + written, fileHeading, (size_t)writeLen); + written += (size_t)writeLen; + filesystem->close(file); } - } - MO_DBG_DEBUG("upload diag chunk (%zuB)", written); - return written; - }, - [this, onClose] (MO_FtpCloseReason reason) -> void { - if (reason == MO_FtpCloseReason_Success) { - MO_DBG_INFO("FTP upload success"); - this->ftpUploadStatus = UploadStatus::Uploaded; + filesystem->seek(file, diagFilesBackTransferred); + size_t writeLen = filesystem->read(file, (char*)buf + written, size - written); + + if (writeLen < size - written) { + diagFileList.pop_back(); + } + written += writeLen; } else { - MO_DBG_INFO("FTP upload failure (%i)", reason); - this->ftpUploadStatus = UploadStatus::UploadFailed; + MO_DBG_ERR("could not open file: %s", fpath); + diagFileList.pop_back(); } + } - MO_FREE(diagPreamble); - MO_FREE(diagPostamble); - diagFileList.clear(); + MO_DBG_DEBUG("upload diag chunk (%zuB)", written); + return written; + }, + [this] (MO_FtpCloseReason reason) -> void { + if (reason == MO_FtpCloseReason_Success) { + MO_DBG_INFO("FTP upload success"); + this->ftpUploadStatus = MO_UploadLogStatus_Uploaded; + } else { + MO_DBG_INFO("FTP upload failure (%i)", reason); + this->ftpUploadStatus = MO_UploadLogStatus_UploadFailure; + } - if (onClose) { - onClose(); - } - }); + if (diagnosticsOnClose) { + diagnosticsOnClose(diagnosticsUserData); + } + }); - if (this->ftpUpload) { - this->ftpUploadStatus = UploadStatus::NotUploaded; - return true; - } else { - this->ftpUploadStatus = UploadStatus::UploadFailed; - return false; - } - }; + MO_FREE(url); + url = nullptr; + + if (!this->ftpUpload) { + MO_DBG_ERR("OOM"); + return false; + } + + this->ftpUploadStatus = MO_UploadLogStatus_Uploading; + return true; +} + +void DiagnosticsService::cleanUploadData() { + ftpUpload.reset(); + + MO_FREE(diagPreamble); + diagPreamble = nullptr; + diagPreambleLen = 0; + diagPreambleTransferred = 0; + + diagReaderHasData = false; - this->uploadStatusInput = [this] () { - return this->ftpUploadStatus; - }; + MO_FREE(diagPostamble); + diagPostamble = nullptr; + diagPostambleLen = 0; + diagPostambleTransferred = 0; + + diagFileList.clear(); + diagFilesBackTransferred = 0; +} + +void DiagnosticsService::cleanGetLogData() { + logType = MO_LogType_UNDEFINED; + requestId = -1; + MO_FREE(location); + location = nullptr; + MO_FREE(filename); + filename = nullptr; + retries = 0; + retryInterval = 0; + oldestTimestamp = Timestamp(); + latestTimestamp = Timestamp(); } void DiagnosticsService::setFtpServerCert(const char *cert) { this->ftpServerCert = cert; } -#if !defined(MO_CUSTOM_DIAGNOSTICS) +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS +#if MO_USE_DIAGNOSTICS == MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 #include "esp_heap_caps.h" #include +namespace MicroOcpp { bool g_diagsSent = false; +} -std::unique_ptr MicroOcpp::makeDefaultDiagnosticsService(Context& context, std::shared_ptr filesystem) { - std::unique_ptr diagService = std::unique_ptr(new DiagnosticsService(context)); - - diagService->setDiagnosticsReader( - [] (char *buf, size_t size) -> size_t { - if (!g_diagsSent) { - g_diagsSent = true; - int ret = snprintf(buf, size, - "\n# Memory\n" - "freeHeap=%zu\n" - "minHeap=%zu\n" - "maxAllocHeap=%zu\n" - "LittleFS_used=%zu\n" - "LittleFS_total=%zu\n", - heap_caps_get_free_size(MALLOC_CAP_DEFAULT), - heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT), - heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT), - LittleFS.usedBytes(), - LittleFS.totalBytes() - ); - if (ret < 0 || (size_t)ret >= size) { - MO_DBG_ERR("snprintf: %i", ret); - return 0; - } - return (size_t)ret; - } +size_t defaultDiagnosticsReader(char *buf, size_t size, void*) { + if (!g_diagsSent) { + g_diagsSent = true; + int ret = snprintf(buf, size, + "\n# Memory\n" + "freeHeap=%zu\n" + "minHeap=%zu\n" + "maxAllocHeap=%zu\n" + "LittleFS_used=%zu\n" + "LittleFS_total=%zu\n", + heap_caps_get_free_size(MALLOC_CAP_DEFAULT), + heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT), + heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT), + LittleFS.usedBytes(), + LittleFS.totalBytes() + ); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("snprintf: %i", ret); return 0; - }, [] () { - g_diagsSent = false; - }, - filesystem); - - return diagService; + } + return (size_t)ret; + } + return 0; } -#elif MO_ENABLE_MBEDTLS - -std::unique_ptr MicroOcpp::makeDefaultDiagnosticsService(Context& context, std::shared_ptr filesystem) { - std::unique_ptr diagService = std::unique_ptr(new DiagnosticsService(context)); - - diagService->setDiagnosticsReader(nullptr, nullptr, filesystem); //report the built-in MO defaults - - return diagService; -} +void defaultDiagnosticsOnClose(void*) { + g_diagsSent = false; +}; -#endif //MO_PLATFORM -#endif //!defined(MO_CUSTOM_DIAGNOSTICS) +#endif //MO_USE_DIAGNOSTICS diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h index ed3ae3c0..3d3a6677 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h @@ -9,41 +9,81 @@ #include #include #include +#include #include -#include +#include +#include -namespace MicroOcpp { +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS -enum class UploadStatus { - NotUploaded, - Uploaded, - UploadFailed -}; +#define MO_DIAGNOSTICS_CUSTOM 0 +#define MO_DIAGNOSTICS_BUILTIN_MBEDTLS 1 +#define MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 2 + +#ifndef MO_USE_DIAGNOSTICS +#ifdef MO_CUSTOM_DIAGNOSTICS +#define MO_USE_DIAGNOSTICS MO_DIAGNOSTICS_CUSTOM +#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS +#define MO_USE_DIAGNOSTICS MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 +#elif MO_ENABLE_MBEDTLS +#define MO_USE_DIAGNOSTICS MO_DIAGNOSTICS_BUILTIN_MBEDTLS +#else +#define MO_USE_DIAGNOSTICS MO_DIAGNOSTICS_CUSTOM +#endif +#endif //MO_USE_DIAGNOSTICS + +namespace MicroOcpp { class Context; class Request; -class FilesystemAdapter; +class FtpClient; class DiagnosticsService : public MemoryManaged { private: Context& context; - - String location; - unsigned int retries = 0; - unsigned int retryInterval = 0; - Timestamp startTime; - Timestamp stopTime; + //platform requirements for built-in logs upload + MO_FilesystemAdapter *filesystem = nullptr; + FtpClient *ftpClient = nullptr; + + //custom logs upload (bypasses built-in implementation) + MO_GetLogStatus (*onUpload)(MO_LogType type, const char *location, const char *oldestTimestamp, const char *latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE], void *onUploadUserData) = nullptr; + MO_UploadLogStatus (*onUploadStatusInput)(void *onUploadUserData) = nullptr; + void *onUploadUserData = nullptr; + char *customProtocols = nullptr; + + int ocppVersion = -1; + + size_t (*diagnosticsReader)(char *buf, size_t size, void *diagnosticsReaderUserData) = nullptr; + void(*diagnosticsOnClose)(void *diagnosticsReaderUserData) = nullptr; + void *diagnosticsUserData = nullptr; + + //override filename for built-in logs upload + bool (*refreshFilename)(MO_LogType type, char filenameOut[MO_GETLOG_FNAME_SIZE], void *refreshFilenameUserData) = nullptr; + void *refreshFilenameUserData = nullptr; + + //server request data + MO_LogType logType = MO_LogType_UNDEFINED; + int requestId = -1; + char *location = nullptr; + char *filename = nullptr; + int retries = 0; + int retryInterval = 0; + Timestamp oldestTimestamp; + Timestamp latestTimestamp; + + //internal status Timestamp nextTry; - - std::function refreshFilename; - std::function onUpload; - std::function uploadStatusInput; bool uploadIssued = false; bool uploadFailure = false; +#if MO_ENABLE_V16 + bool use16impl = false; //if to use original 1.6 implementation instead of Security Whitepaper +#endif //MO_ENABLE_V16 + bool runCustomUpload = false; + //built-in logs upload data std::unique_ptr ftpUpload; - UploadStatus ftpUploadStatus = UploadStatus::NotUploaded; + MO_UploadLogStatus ftpUploadStatus = MO_UploadLogStatus_Idle; const char *ftpServerCert = nullptr; char *diagPreamble = nullptr; size_t diagPreambleLen = 0; @@ -55,25 +95,21 @@ class DiagnosticsService : public MemoryManaged { Vector diagFileList; size_t diagFilesBackTransferred = 0; - std::unique_ptr getDiagnosticsStatusNotification(); + MO_UploadLogStatus lastReportedUploadStatus = MO_UploadLogStatus_Idle; + #if MO_ENABLE_V16 + Ocpp16::DiagnosticsStatus lastReportedUploadStatus16 = Ocpp16::DiagnosticsStatus::Idle; + #endif //MO_ENABLE_V16 - Ocpp16::DiagnosticsStatus lastReportedStatus = Ocpp16::DiagnosticsStatus::Idle; + bool uploadDiagnostics(); //prepare diags and start FTP upload + bool uploadSecurityLog(); //prepare sec logs and start FTP upload + bool startFtpUpload(); //do FTP upload + void cleanUploadData(); //clean data which is set per FTP upload attempt + void cleanGetLogData(); //clean data which is set per GetLog request public: DiagnosticsService(Context& context); ~DiagnosticsService(); - void loop(); - - //timestamps before year 2021 will be treated as "undefined" - //returns empty std::string if onUpload is missing or upload cannot be scheduled for another reason - //returns fileName of diagnostics file to be uploaded if upload has been scheduled - String requestDiagnosticsUpload(const char *location, unsigned int retries = 1, unsigned int retryInterval = 0, Timestamp startTime = Timestamp(), Timestamp stopTime = Timestamp()); - - Ocpp16::DiagnosticsStatus getDiagnosticsStatus(); - - void setRefreshFilename(std::function refreshFn); //refresh a new filename which will be used for the subsequent upload tries - /* * Sets the diagnostics data reader. When the server sends a GetDiagnostics operation, then MO will open an FTP * connection to the FTP server and upload a diagnostics file. MO automatically creates a small report about @@ -92,32 +128,45 @@ class DiagnosticsService : public MemoryManaged { * * Note that this function only works if MO_ENABLE_MBEDTLS=1, or MO has been configured with a custom FTP client */ - void setDiagnosticsReader(std::function diagnosticsReader, std::function onClose, std::shared_ptr filesystem); + void setDiagnosticsReader(size_t (*diagnosticsReader)(char *buf, size_t size, void *user_data), void(*onClose)(void *user_data), void *user_data); void setFtpServerCert(const char *cert); //zero-copy mode, i.e. cert must outlive MO - void setOnUpload(std::function onUpload); - - void setOnUploadStatusInput(std::function uploadStatusInput); -}; + //override default logs file name ("diagnostics.log"). Only applies to built-in diagnostics upload + void setRefreshFilename(bool (*refreshFilename)(MO_LogType type, char filenameOut[MO_GETLOG_FNAME_SIZE], void *user_data), void *user_data); -} //end namespace MicroOcpp + /* Bypass built-in upload + * `onUpload`: hook function for diagnostics upload + * `onUploadStatusInput`: status getter cb which MO will execute to control custom onUpload and notify server + * `protocols`: CSV list for supported protocols. Possible values: FTP, FTPS, HTTP, HTTPS, SFTP + * `user_data`: implementer-defined pointer */ + bool setOnUpload( + MO_GetLogStatus (*onUpload)(MO_LogType type, const char *location, const char *oldestTimestamp, const char *latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE], void *user_data), + MO_UploadLogStatus (*onUploadStatusInput)(void *user_data), + const char *protocols, + void *user_data); -#if !defined(MO_CUSTOM_DIAGNOSTICS) + bool setup(); -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS - -namespace MicroOcpp { -std::unique_ptr makeDefaultDiagnosticsService(Context& context, std::shared_ptr filesystem); -} + void loop(); -#elif MO_ENABLE_MBEDTLS +#if MO_ENABLE_V16 + //timestamps before year 2021 will be treated as "undefined" + //returns empty std::string if onUpload is missing or upload cannot be scheduled for another reason + //returns fileName of diagnostics file to be uploaded if upload has been scheduled + bool requestDiagnosticsUpload(const char *location, unsigned int retries, unsigned int retryInterval, Timestamp startTime, Timestamp stopTime, char filenameOut[MO_GETLOG_FNAME_SIZE]); +#endif //MO_ENABLE_V16 -namespace MicroOcpp { -std::unique_ptr makeDefaultDiagnosticsService(Context& context, std::shared_ptr filesystem); -} + MO_GetLogStatus getLog(MO_LogType type, int requestId, int retries, unsigned int retryInterval, const char *remoteLocation, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]); -#endif //MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS -#endif //!defined(MO_CUSTOM_DIAGNOSTICS) + //for TriggerMessage + int getRequestId(); + MO_UploadLogStatus getUploadStatus(); + #if MO_ENABLE_V16 + Ocpp16::DiagnosticsStatus getUploadStatus16(); + #endif //MO_ENABLE_V16 +}; +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS #endif diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsStatus.h b/src/MicroOcpp/Model/Diagnostics/DiagnosticsStatus.h deleted file mode 100644 index 632708a4..00000000 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsStatus.h +++ /dev/null @@ -1,20 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_DIAGNOSTICS_STATUS -#define MO_DIAGNOSTICS_STATUS - -namespace MicroOcpp { -namespace Ocpp16 { - -enum class DiagnosticsStatus { - Idle, - Uploaded, - UploadFailed, - Uploading -}; - -} -} //end namespace MicroOcpp -#endif diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp index 7eb006e1..24c62f4a 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp @@ -3,12 +3,13 @@ // MIT License #include -#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -17,31 +18,111 @@ #include #include +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT + //debug option: update immediately and don't wait for the retreive date #ifndef MO_IGNORE_FW_RETR_DATE #define MO_IGNORE_FW_RETR_DATE 0 #endif -using MicroOcpp::FirmwareService; -using MicroOcpp::Ocpp16::FirmwareStatus; -using MicroOcpp::Request; +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -FirmwareService::FirmwareService(Context& context) : MemoryManaged("v16.Firmware.FirmwareService"), context(context), buildNumber(makeString(getMemoryTag())), location(makeString(getMemoryTag())) { - - context.getOperationRegistry().registerOperation("UpdateFirmware", [this] () { - return new Ocpp16::UpdateFirmware(*this);}); +#if MO_USE_FW_UPDATER != MO_FW_UPDATER_CUSTOM +namespace MicroOcpp { +bool setupDefaultFwUpdater(FirmwareService *fwService); +} +#endif + +FirmwareService::FirmwareService(Context& context) : MemoryManaged("v16.Firmware.FirmwareService"), context(context), clock(context.getClock()), timestampTransition(clock.getUptime()) { - //Register message handler for TriggerMessage operation - context.getOperationRegistry().registerOperation("FirmwareStatusNotification", [this] () { - return new Ocpp16::FirmwareStatusNotification(getFirmwareStatus());}); } -void FirmwareService::setBuildNumber(const char *buildNumber) { - if (buildNumber == nullptr) - return; - this->buildNumber = buildNumber; - previousBuildNumberString = declareConfiguration("BUILD_NUMBER", this->buildNumber.c_str(), MO_KEYVALUE_FN, false, false, false); - checkedSuccessfulFwUpdate = false; //--> CS will be notified +FirmwareService::~FirmwareService() { + MO_FREE(buildNumber); + buildNumber = nullptr; + MO_FREE(location); + location = nullptr; +} + +bool FirmwareService::setup() { + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + auto previousBuildNumberString = configService->declareConfiguration("BUILD_NUMBER", "", MO_KEYVALUE_FN, Mutability::None); + if (!previousBuildNumberString) { + MO_DBG_ERR("declareConfiguration"); + return false; + } + + auto bootSvc = context.getModel16().getBootService(); + if (!bootSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + MO_BootNotificationData bnData = bootSvc->getBootNotificationData(); + + const char *fwIdentifier = buildNumber ? buildNumber : bnData.firmwareVersion; + if (fwIdentifier && strcmp(fwIdentifier, previousBuildNumberString->getString())) { + + MO_DBG_INFO("Firmware updated. Previous build number: %s, new build number: %s", previousBuildNumberString->getString(), fwIdentifier); + notifyFirmwareUpdate = true; //will send this during `loop()` + } + MO_FREE(buildNumber); + buildNumber = nullptr; + + context.getMessageService().registerOperation("UpdateFirmware", [] (Context& context) -> Operation* { + return new Ocpp16::UpdateFirmware(context, *context.getModel16().getFirmwareService());}); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("FirmwareStatusNotification", nullptr, nullptr); + #endif //MO_ENABLE_MOCK_SERVER + + auto rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("FirmwareStatusNotification", [] (Context& context) -> Operation* { + return new Ocpp16::FirmwareStatusNotification(context.getModel16().getFirmwareService()->getFirmwareStatus());}); + +#if MO_USE_FW_UPDATER != MO_FW_UPDATER_CUSTOM + if (!setupDefaultFwUpdater(this)) { + return false; + } +#endif //MO_CUSTOM_UPDATER + + return true; +} + +bool FirmwareService::setBuildNumber(const char *buildNumber) { + if (!buildNumber) { + MO_DBG_ERR("invalid arg"); + return false; + } + MO_FREE(this->buildNumber); + this->buildNumber = nullptr; + size_t size = strlen(buildNumber) + 1; + this->buildNumber = static_cast(MO_MALLOC(getMemoryTag(), size)); + if (!this->buildNumber) { + MO_DBG_ERR("OOM"); + return false; + } + auto ret = snprintf(this->buildNumber, size, "%s", buildNumber); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("snprintf: %i", ret); + MO_FREE(this->buildNumber); + this->buildNumber = nullptr; + return false; + } + + return true; //CS will be notified } void FirmwareService::loop() { @@ -61,30 +142,40 @@ void FirmwareService::loop() { auto notification = getFirmwareStatusNotification(); if (notification) { - context.initiateRequest(std::move(notification)); + context.getMessageService().sendRequest(std::move(notification)); } - if (mocpp_tick_ms() - timestampTransition < delayTransition) { + int32_t dtTransition; + if (!clock.delta(clock.getUptime(), timestampTransition, dtTransition)) { + dtTransition = 0; + } + + if (dtTransition < delayTransition) { return; } - auto& timestampNow = context.getModel().getClock().now(); - if (retries > 0 && timestampNow >= retreiveDate) { + int32_t dtRetreiveDate; + if (!clock.delta(clock.now(), retreiveDate, dtRetreiveDate)) { + dtRetreiveDate = 0; + } + + if (retries > 0 && dtRetreiveDate >= 0) { if (stage == UpdateStage::Idle) { MO_DBG_INFO("Start update"); - if (context.getModel().getNumConnectors() > 0) { - auto cp = context.getModel().getConnector(0); - cp->setAvailabilityVolatile(false); + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcCp = availSvc ? availSvc->getEvse(0) : nullptr; + if (availSvcCp) { + availSvcCp->setAvailabilityVolatile(false); } if (onDownload == nullptr) { stage = UpdateStage::AfterDownload; } else { downloadIssued = true; stage = UpdateStage::AwaitDownload; - timestampTransition = mocpp_tick_ms(); - delayTransition = 2000; //delay between state "Downloading" and actually starting the download + timestampTransition = clock.getUptime(); + delayTransition = 2; //delay between state "Downloading" and actually starting the download return; } } @@ -93,9 +184,9 @@ void FirmwareService::loop() { MO_DBG_INFO("Start download"); stage = UpdateStage::Downloading; if (onDownload != nullptr) { - onDownload(location.c_str()); - timestampTransition = mocpp_tick_ms(); - delayTransition = downloadStatusInput ? 1000 : 30000; //give the download at least 30s + onDownload(location ? location : ""); + timestampTransition = clock.getUptime(); + delayTransition = downloadStatusInput ? 1 : 30; //give the download at least 30s return; } } @@ -110,13 +201,13 @@ void FirmwareService::loop() { stage = UpdateStage::AfterDownload; } else if (downloadStatusInput() == DownloadStatus::DownloadFailed) { MO_DBG_INFO("Download timeout or failed"); - retreiveDate = timestampNow; - retreiveDate += retryInterval; + retreiveDate = clock.now(); + clock.add(retreiveDate, retryInterval); retries--; resetStage(); - timestampTransition = mocpp_tick_ms(); - delayTransition = 10000; + timestampTransition = clock.getUptime(); + delayTransition = 10; } return; } else { @@ -127,9 +218,10 @@ void FirmwareService::loop() { if (stage == UpdateStage::AfterDownload) { bool ongoingTx = false; - for (unsigned int cId = 0; cId < context.getModel().getNumConnectors(); cId++) { - auto connector = context.getModel().getConnector(cId); - if (connector && connector->getTransaction() && connector->getTransaction()->isRunning()) { + for (unsigned int evseId = 0; evseId < context.getModel16().getNumEvseId(); evseId++) { + auto txSvc = context.getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (txSvcEvse && txSvcEvse->getTransaction() && txSvcEvse->getTransaction()->isRunning()) { ongoingTx = true; break; } @@ -141,8 +233,8 @@ void FirmwareService::loop() { } else { stage = UpdateStage::AwaitInstallation; } - timestampTransition = mocpp_tick_ms(); - delayTransition = 2000; + timestampTransition = clock.getUptime(); + delayTransition = 2; installationIssued = true; } @@ -154,10 +246,10 @@ void FirmwareService::loop() { stage = UpdateStage::Installing; if (onInstall) { - onInstall(location.c_str()); //may restart the device on success + onInstall(location ? location : ""); //may restart the device on success - timestampTransition = mocpp_tick_ms(); - delayTransition = installationStatusInput ? 1000 : 120 * 1000; + timestampTransition = clock.getUptime(); + delayTransition = installationStatusInput ? 1 : 120; } return; } @@ -171,16 +263,17 @@ void FirmwareService::loop() { resetStage(); retries = 0; //End of update routine stage = UpdateStage::Installed; - location.clear(); + MO_FREE(location); + location = nullptr; } else if (installationStatusInput() == InstallationStatus::InstallationFailed) { MO_DBG_INFO("Installation timeout or failed! Retry"); - retreiveDate = timestampNow; - retreiveDate += retryInterval; + retreiveDate = clock.now(); + clock.add(retreiveDate, retryInterval); retries--; resetStage(); - timestampTransition = mocpp_tick_ms(); - delayTransition = 10000; + timestampTransition = clock.getUptime(); + delayTransition = 10; } return; } else { @@ -189,7 +282,8 @@ void FirmwareService::loop() { resetStage(); stage = UpdateStage::Installed; retries = 0; //End of update routine - location.clear(); + MO_FREE(location); + location = nullptr; return; } } @@ -199,45 +293,71 @@ void FirmwareService::loop() { retries = 0; resetStage(); stage = UpdateStage::InternalError; - location.clear(); + MO_FREE(location); + location = nullptr; } } -void FirmwareService::scheduleFirmwareUpdate(const char *location, Timestamp retreiveDate, unsigned int retries, unsigned int retryInterval) { +bool FirmwareService::scheduleFirmwareUpdate(const char *location, Timestamp retreiveDate, unsigned int retries, unsigned int retryInterval) { + + if (!location) { + MO_DBG_ERR("invalid arg"); + return false; + } if (!onDownload && !onInstall) { MO_DBG_ERR("FW service not configured"); stage = UpdateStage::InternalError; //will send "InstallationFailed" and not proceed with update - return; + return false; + } + + MO_FREE(this->location); + this->location = nullptr; + size_t len = strlen(location); + this->location = static_cast(MO_MALLOC(getMemoryTag(), len)); + if (!this->location) { + MO_DBG_ERR("OOM"); + return false; + } + auto ret = snprintf(this->location, len, "%s", location); + if (ret < 0 || ret >= len) { + MO_DBG_ERR("snprintf: %i", ret); + MO_FREE(this->location); + this->location = nullptr; + return false; } - this->location = location; this->retreiveDate = retreiveDate; this->retries = retries; this->retryInterval = retryInterval; if (MO_IGNORE_FW_RETR_DATE) { MO_DBG_DEBUG("ignore FW update retreive date"); - this->retreiveDate = context.getModel().getClock().now(); + this->retreiveDate = clock.now(); } - char dbuf [JSONDATE_LENGTH + 1] = {'\0'}; - this->retreiveDate.toJsonString(dbuf, JSONDATE_LENGTH + 1); + char dbuf [MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(this->retreiveDate, dbuf, sizeof(dbuf))) { + MO_DBG_ERR("internal error"); + dbuf[0] = '\0'; + } MO_DBG_INFO("Scheduled FW update!\n" \ " location = %s\n" \ " retrieveDate = %s\n" \ " retries = %u" \ ", retryInterval = %u", - this->location.c_str(), + location ? location : "", dbuf, this->retries, this->retryInterval); - timestampTransition = mocpp_tick_ms(); - delayTransition = 1000; + timestampTransition = clock.getUptime(); + delayTransition = 1; resetStage(); + + return true; } FirmwareStatus FirmwareService::getFirmwareStatus() { @@ -279,30 +399,20 @@ std::unique_ptr FirmwareService::getFirmwareStatusNotification() { /* * Check if FW has been updated previously, but only once */ - if (!checkedSuccessfulFwUpdate && !buildNumber.empty() && previousBuildNumberString != nullptr) { - checkedSuccessfulFwUpdate = true; - - MO_DBG_DEBUG("Previous build number: %s, new build number: %s", previousBuildNumberString->getString(), buildNumber.c_str()); - - if (buildNumber.compare(previousBuildNumberString->getString())) { - //new FW - previousBuildNumberString->setString(buildNumber.c_str()); - configuration_save(); + if (notifyFirmwareUpdate) { + notifyFirmwareUpdate = false; - buildNumber.clear(); - - lastReportedStatus = FirmwareStatus::Installed; - auto fwNotificationMsg = new Ocpp16::FirmwareStatusNotification(lastReportedStatus); - auto fwNotification = makeRequest(fwNotificationMsg); - return fwNotification; - } + lastReportedStatus = FirmwareStatus::Installed; + auto fwNotificationMsg = new Ocpp16::FirmwareStatusNotification(lastReportedStatus); + auto fwNotification = makeRequest(context, fwNotificationMsg); + return fwNotification; } if (getFirmwareStatus() != lastReportedStatus) { lastReportedStatus = getFirmwareStatus(); if (lastReportedStatus != FirmwareStatus::Idle) { auto fwNotificationMsg = new Ocpp16::FirmwareStatusNotification(lastReportedStatus); - auto fwNotification = makeRequest(fwNotificationMsg); + auto fwNotification = makeRequest(context, fwNotificationMsg); return fwNotification; } } @@ -374,21 +484,17 @@ void FirmwareService::setFtpServerCert(const char *cert) { this->ftpServerCert = cert; } -#if !defined(MO_CUSTOM_UPDATER) -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS +#if MO_USE_FW_UPDATER == MO_FW_UPDATER_BUILTIN_ESP32 #include -std::unique_ptr MicroOcpp::makeDefaultFirmwareService(Context& context) { - std::unique_ptr fwService = std::unique_ptr(new FirmwareService(context)); - auto ftServicePtr = fwService.get(); - +bool MicroOcpp::setupDefaultFwUpdater(FirmwareService *fwService) { fwService->setDownloadFileWriter( - [ftServicePtr] (const unsigned char *data, size_t size) -> size_t { + [fwService] (const unsigned char *data, size_t size) -> size_t { if (!Update.isRunning()) { MO_DBG_DEBUG("start writing FW"); MO_DBG_WARN("Built-in updater for ESP32 is only intended for demonstration purposes"); - ftServicePtr->setInstallationStatusInput([](){return InstallationStatus::NotInstalled;}); + fwService->setInstallationStatusInput([](){return InstallationStatus::NotInstalled;}); auto ret = Update.begin(); if (!ret) { @@ -429,16 +535,16 @@ std::unique_ptr MicroOcpp::makeDefaultFirmwareService(Context& } }); - fwService->setOnInstall([ftServicePtr] (const char *location) { + fwService->setOnInstall([fwService] (const char *location) { if (Update.isRunning() && Update.end(true)) { MO_DBG_DEBUG("update success"); - ftServicePtr->setInstallationStatusInput([](){return InstallationStatus::Installed;}); + fwService->setInstallationStatusInput([](){return InstallationStatus::Installed;}); ESP.restart(); } else { MO_DBG_ERR("update failed"); - ftServicePtr->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;}); + fwService->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;}); } return true; @@ -448,18 +554,16 @@ std::unique_ptr MicroOcpp::makeDefaultFirmwareService(Context& return InstallationStatus::NotInstalled; }); - return fwService; + return true; } -#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP8266) +#elif MO_USE_FW_UPDATER == MO_FW_UPDATER_BUILTIN_ESP32 #include -std::unique_ptr MicroOcpp::makeDefaultFirmwareService(Context& context) { - std::unique_ptr fwService = std::unique_ptr(new FirmwareService(context)); - auto fwServicePtr = fwService.get(); +bool MicroOcpp::setupDefaultFwUpdater(FirmwareService *fwService) { - fwService->setOnInstall([fwServicePtr] (const char *location) { + fwService->setOnInstall([fwService] (const char *location) { MO_DBG_WARN("Built-in updater for ESP8266 is only intended for demonstration purposes. HTTP support only"); @@ -474,15 +578,15 @@ std::unique_ptr MicroOcpp::makeDefaultFirmwareService(Context& switch (ret) { case HTTP_UPDATE_FAILED: - fwServicePtr->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;}); + fwService->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;}); MO_DBG_WARN("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); break; case HTTP_UPDATE_NO_UPDATES: - fwServicePtr->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;}); + fwService->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;}); MO_DBG_WARN("HTTP_UPDATE_NO_UPDATES"); break; case HTTP_UPDATE_OK: - fwServicePtr->setInstallationStatusInput([](){return InstallationStatus::Installed;}); + fwService->setInstallationStatusInput([](){return InstallationStatus::Installed;}); MO_DBG_INFO("HTTP_UPDATE_OK"); ESP.restart(); break; @@ -495,8 +599,9 @@ std::unique_ptr MicroOcpp::makeDefaultFirmwareService(Context& return InstallationStatus::NotInstalled; }); - return fwService; + return true; } -#endif //MO_PLATFORM -#endif //!defined(MO_CUSTOM_UPDATER) +#endif //MO_USE_FW_UPDATER + +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h index 5f4671f2..54d78dc7 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h @@ -8,14 +8,35 @@ #include #include -#include +#include #include #include #include #include +#include + +#define MO_FW_UPDATER_CUSTOM 0 +#define MO_FW_UPDATER_BUILTIN_ESP32 1 +#define MO_FW_UPDATER_BUILTIN_ESP8266 2 + +#ifndef MO_USE_FW_UPDATER +#ifdef MO_CUSTOM_UPDATER +#define MO_USE_FW_UPDATER MO_FW_UPDATER_CUSTOM +#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS +#define MO_USE_FW_UPDATER MO_FW_UPDATER_BUILTIN_ESP32 +#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP8266) +#define MO_USE_FW_UPDATER MO_FW_UPDATER_BUILTIN_ESP8266 +#endif +#endif //MO_USE_FW_UPDATER + +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT namespace MicroOcpp { +class Context; +class Clock; +class Request; + enum class DownloadStatus { NotDownloaded, // == before download or during download Downloaded, @@ -28,15 +49,15 @@ enum class InstallationStatus { InstallationFailed }; -class Context; -class Request; +namespace Ocpp16 { class FirmwareService : public MemoryManaged { private: Context& context; - - std::shared_ptr previousBuildNumberString; - String buildNumber; + Clock& clock; + + char *buildNumber = nullptr; + bool notifyFirmwareUpdate = false; std::function downloadStatusInput; bool downloadIssued = false; @@ -48,10 +69,9 @@ class FirmwareService : public MemoryManaged { std::function installationStatusInput; bool installationIssued = false; - Ocpp16::FirmwareStatus lastReportedStatus = Ocpp16::FirmwareStatus::Idle; - bool checkedSuccessfulFwUpdate = false; + FirmwareStatus lastReportedStatus = FirmwareStatus::Idle; - String location; + char *location = nullptr; Timestamp retreiveDate; unsigned int retries = 0; unsigned int retryInterval = 0; @@ -59,8 +79,8 @@ class FirmwareService : public MemoryManaged { std::function onDownload; std::function onInstall; - unsigned long delayTransition = 0; - unsigned long timestampTransition = 0; + int32_t delayTransition = 0; + Timestamp timestampTransition; enum class UpdateStage { Idle, @@ -80,13 +100,9 @@ class FirmwareService : public MemoryManaged { public: FirmwareService(Context& context); - void setBuildNumber(const char *buildNumber); + ~FirmwareService(); - void loop(); - - void scheduleFirmwareUpdate(const char *location, Timestamp retreiveDate, unsigned int retries = 1, unsigned int retryInterval = 0); - - Ocpp16::FirmwareStatus getFirmwareStatus(); + bool setBuildNumber(const char *buildNumber); /* * Sets the firmware writer. During the UpdateFirmware process, MO will use an FTP client to download the firmware and forward @@ -111,25 +127,17 @@ class FirmwareService : public MemoryManaged { void setOnInstall(std::function onInstall); void setInstallationStatusInput(std::function installationStatusInput); -}; -} //endif namespace MicroOcpp + bool setup(); -#if !defined(MO_CUSTOM_UPDATER) - -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS - -namespace MicroOcpp { -std::unique_ptr makeDefaultFirmwareService(Context& context); -} + void loop(); -#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP8266) + bool scheduleFirmwareUpdate(const char *location, Timestamp retreiveDate, unsigned int retries = 1, unsigned int retryInterval = 0); -namespace MicroOcpp { -std::unique_ptr makeDefaultFirmwareService(Context& context); -} - -#endif //MO_PLATFORM -#endif //!defined(MO_CUSTOM_UPDATER) + FirmwareStatus getFirmwareStatus(); +}; +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h b/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h index 3c8c7c2c..9894ddde 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h @@ -5,6 +5,10 @@ #ifndef MO_FIRMWARE_STATUS #define MO_FIRMWARE_STATUS +#include + +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT + namespace MicroOcpp { namespace Ocpp16 { @@ -18,6 +22,7 @@ enum class FirmwareStatus { Installed }; -} -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp index 4ebab502..d1eb7e80 100644 --- a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp +++ b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp @@ -1,37 +1,146 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include -#include +#include #include -#include +#include +#include +#include #include #include +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 using namespace MicroOcpp; -HeartbeatService::HeartbeatService(Context& context) : MemoryManaged("v16.Heartbeat.HeartbeatService"), context(context) { - heartbeatIntervalInt = declareConfiguration("HeartbeatInterval", 86400); - registerConfigurationValidator("HeartbeatInterval", VALIDATE_UNSIGNED_INT); - lastHeartbeat = mocpp_tick_ms(); +HeartbeatService::HeartbeatService(Context& context) : MemoryManaged("v16/v201.Heartbeat.HeartbeatService"), context(context) { - //Register message handler for TriggerMessage operation - context.getOperationRegistry().registerOperation("Heartbeat", [&context] () { - return new Ocpp16::Heartbeat(context.getModel());}); +} + +bool HeartbeatService::setup() { + + ocppVersion = context.getOcppVersion(); + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + + configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("initialization error"); + return false; + } + + //if transactions can start before the BootNotification succeeds + heartbeatIntervalIntV16 = configService->declareConfiguration("HeartbeatInterval", false); + if (!heartbeatIntervalIntV16) { + MO_DBG_ERR("initialization error"); + return false; + } + + if (!configService->registerValidator("HeartbeatInterval", validateHeartbeatInterval)) { + MO_DBG_ERR("initialization error"); + return false; + } + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + + varService = context.getModel201().getVariableService(); + if (!varService) { + MO_DBG_ERR("initialization error"); + return false; + } + + //if transactions can start before the BootNotification succeeds + heartbeatIntervalIntV201 = varService->declareVariable("OCPPCommCtrlr", "HeartbeatInterval", false); + if (!heartbeatIntervalIntV201) { + MO_DBG_ERR("initialization error"); + return false; + } + + if (!varService->registerValidator("OCPPCommCtrlr", "HeartbeatInterval", validateHeartbeatInterval)) { + MO_DBG_ERR("initialization error"); + return false; + } + } + #endif //MO_ENABLE_V201 + + lastHeartbeat = context.getClock().getUptime(); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("Heartbeat", nullptr, Heartbeat::writeMockConf, reinterpret_cast(&context)); + #endif //MO_ENABLE_MOCK_SERVER + + auto rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("Heartbeat", [] (Context& context) -> Operation* { + return new Heartbeat(context);}); + + return true; } void HeartbeatService::loop() { - unsigned long hbInterval = heartbeatIntervalInt->getInt(); - hbInterval *= 1000UL; //conversion s -> ms - unsigned long now = mocpp_tick_ms(); - if (now - lastHeartbeat >= hbInterval) { - lastHeartbeat = now; + int hbInterval = 86400; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + hbInterval = heartbeatIntervalIntV16->getInt(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + hbInterval = heartbeatIntervalIntV201->getInt(); + } + #endif //MO_ENABLE_V201 + + int32_t dtLastHeartbeat; + if (!context.getClock().delta(context.getClock().getUptime(), lastHeartbeat, dtLastHeartbeat)) { + dtLastHeartbeat = hbInterval; //force send Heartbeat + } - auto heartbeat = makeRequest(new Ocpp16::Heartbeat(context.getModel())); + if (dtLastHeartbeat >= hbInterval && hbInterval > 0) { + lastHeartbeat = context.getClock().getUptime(); + + auto heartbeat = makeRequest(context, new Heartbeat(context)); // Heartbeats can not deviate more than 4s from the configured interval - heartbeat->setTimeout(std::min(4000UL, hbInterval)); - context.initiateRequest(std::move(heartbeat)); + heartbeat->setTimeout(std::min(4, hbInterval)); + context.getMessageService().sendRequest(std::move(heartbeat)); } } + +bool HeartbeatService::setHeartbeatInterval(int interval) { + if (!validateHeartbeatInterval(interval, nullptr)) { + return false; + } + + bool success = false; + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + heartbeatIntervalIntV16->setInt(interval); + success = configService->commit(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + heartbeatIntervalIntV201->setInt(interval); + success = varService->commit(); + } + #endif //MO_ENABLE_V201 + + return success; +} + +bool MicroOcpp::validateHeartbeatInterval(int v, void*) { + return v >= 1; +} + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h index ea632cc8..7418b6e3 100644 --- a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h +++ b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h @@ -1,32 +1,63 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_HEARTBEATSERVICE_H #define MO_HEARTBEATSERVICE_H -#include - -#include +#include #include +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 namespace MicroOcpp { class Context; +#if MO_ENABLE_V16 +namespace Ocpp16 { +class ConfigurationService; +class Configuration; +} +#endif +#if MO_ENABLE_V201 +namespace Ocpp201 { +class VariableService; +class Variable; +} +#endif + class HeartbeatService : public MemoryManaged { private: Context& context; - unsigned long lastHeartbeat; - std::shared_ptr heartbeatIntervalInt; + int ocppVersion = -1; + + #if MO_ENABLE_V16 + Ocpp16::ConfigurationService *configService = nullptr; + Ocpp16::Configuration *heartbeatIntervalIntV16 = nullptr; + #endif + #if MO_ENABLE_V201 + Ocpp201::VariableService *varService = nullptr; + Ocpp201::Variable *heartbeatIntervalIntV201 = nullptr; + #endif + + Timestamp lastHeartbeat; public: HeartbeatService(Context& context); + bool setup(); + void loop(); + + bool setHeartbeatInterval(int interval); }; +bool validateHeartbeatInterval(int v, void*); + } +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Metering/MeterStore.cpp b/src/MicroOcpp/Model/Metering/MeterStore.cpp index e4bc9e85..55bf6eac 100644 --- a/src/MicroOcpp/Model/Metering/MeterStore.cpp +++ b/src/MicroOcpp/Model/Metering/MeterStore.cpp @@ -3,297 +3,117 @@ // MIT License #include -#include - -#include #include -#ifndef MO_MAX_STOPTXDATA_LEN -#define MO_MAX_STOPTXDATA_LEN 4 -#endif +#include +#include +#include -using namespace MicroOcpp; -TransactionMeterData::TransactionMeterData(unsigned int connectorId, unsigned int txNr, std::shared_ptr filesystem) - : MemoryManaged("v16.Metering.TransactionMeterData"), connectorId(connectorId), txNr(txNr), filesystem{filesystem}, txData{makeVector>(getMemoryTag())} { - - if (!filesystem) { - MO_DBG_DEBUG("volatile mode"); - } -} +#if MO_ENABLE_V16 -bool TransactionMeterData::addTxData(std::unique_ptr mv) { - if (isFinalized()) { - MO_DBG_ERR("immutable"); - return false; - } +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; - if (!mv) { - MO_DBG_ERR("null"); +bool MeterStore::printTxMeterValueFName(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int mvIndex) { + auto ret = snprintf(fname, MO_MAX_PATH_SIZE, "sd-%.*u-%.*u-%.*u.jsn", + MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr, MO_STOPTXDATA_DIGITS, mvIndex); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("fname error: %i", ret); return false; } - - if (MO_MAX_STOPTXDATA_LEN <= 0) { - //txData off - return true; - } - - bool replaceLast = mvCount >= MO_MAX_STOPTXDATA_LEN; //txData size exceeded? overwrite last entry instead of appending - - if (filesystem) { - - unsigned int mvIndex = 0; - if (replaceLast) { - mvIndex = mvCount - 1; - } else { - mvIndex = mvCount ; - } - - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "sd" "-%u-%u-%u.jsn", connectorId, txNr, mvIndex); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return false; - } - - auto mvDoc = mv->toJson(); - if (!mvDoc) { - MO_DBG_ERR("MV not ready yet"); - return false; - } - - if (!FilesystemUtils::storeJson(filesystem, fn, *mvDoc)) { - MO_DBG_ERR("FS error"); - return false; - } - - if (!replaceLast) { - mvCount++; - } - } - - if (replaceLast) { - txData.back() = std::move(mv); - MO_DBG_DEBUG("updated latest sd"); - } else { - txData.push_back(std::move(mv)); - MO_DBG_DEBUG("added sd"); - } return true; } -Vector> TransactionMeterData::retrieveStopTxData() { - if (isFinalized()) { - MO_DBG_ERR("Can only retrieve once"); - return makeVector>(getMemoryTag()); - } - finalize(); - MO_DBG_DEBUG("creating sd"); - return std::move(txData); -} +FilesystemUtils::LoadStatus MeterStore::load(MO_FilesystemAdapter *filesystem, Context& context, Vector& meterInputs, unsigned int evseId, unsigned int txNr, Vector& txMeterValues) { -bool TransactionMeterData::restore(MeterValueBuilder& mvBuilder) { - if (!filesystem) { - MO_DBG_DEBUG("No FS - nothing to restore"); - return true; - } + auto& clock = context.getClock(); - const unsigned int MISSES_LIMIT = 3; - unsigned int i = 0; - unsigned int misses = 0; + for (unsigned int i = 0; i < MO_STOPTXDATA_MAX_SIZE; i++) { //search until region without mvs found - while (misses < MISSES_LIMIT) { //search until region without mvs found - - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "sd" "-%u-%u-%u.jsn", connectorId, txNr, i); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return false; //all files have same length + char fname [MO_MAX_PATH_SIZE] = {'\0'}; + if (!printTxMeterValueFName(fname, sizeof(fname), evseId, txNr, i)) { + MO_DBG_ERR("fname error %u %u", evseId, txNr); + return FilesystemUtils::LoadStatus::ErrOther; } - auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag()); + JsonDoc doc (0); - if (!doc) { - misses++; - i++; - continue; + auto ret = FilesystemUtils::loadJson(filesystem, fname, doc, "v16/v201.Metering.MeterStore"); + if (ret == FilesystemUtils::LoadStatus::FileNotFound) { + break; //done loading meterValues } - - JsonObject mvJson = doc->as(); - std::unique_ptr mv = mvBuilder.deserializeSample(mvJson); - - if (!mv) { - MO_DBG_ERR("Deserialization error"); - misses++; - i++; - continue; + if (ret != FilesystemUtils::LoadStatus::Success) { + return ret; } - if (txData.size() >= MO_MAX_STOPTXDATA_LEN) { - MO_DBG_ERR("corrupted memory"); - return false; + auto capacity = txMeterValues.size() + 1; + txMeterValues.reserve(capacity); + if (txMeterValues.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return FilesystemUtils::LoadStatus::ErrOOM; } - txData.push_back(std::move(mv)); - - i++; - mvCount = i; - misses = 0; - } - - MO_DBG_DEBUG("Restored %zu meter values from sd-%u-%u-0 to %u (exclusive)", txData.size(), connectorId, txNr, mvCount); - return true; -} - -MeterStore::MeterStore(std::shared_ptr filesystem) : MemoryManaged("v16.Metering.MeterStore"), filesystem {filesystem}, txMeterData{makeVector>(getMemoryTag())} { - - if (!filesystem) { - MO_DBG_DEBUG("volatile mode"); - } -} + auto mv = new MeterValue(); + if (!mv) { + MO_DBG_ERR("OOM"); + return FilesystemUtils::LoadStatus::ErrOOM; + } + txMeterValues.push_back(mv); -std::shared_ptr MeterStore::getTxMeterData(MeterValueBuilder& mvBuilder, Transaction *transaction) { - if (!transaction || transaction->isSilent()) { - //no tx assignment -> don't store txData - //tx is silent -> no StopTx will be sent and don't store txData - return nullptr; - } - auto connectorId = transaction->getConnectorId(); - auto txNr = transaction->getTxNr(); - - auto cached = std::find_if(txMeterData.begin(), txMeterData.end(), - [connectorId, txNr] (std::weak_ptr& txm) { - if (auto txml = txm.lock()) { - return txml->getConnectorId() == connectorId && txml->getTxNr() == txNr; - } else { - return false; - } - }); - - if (cached != txMeterData.end()) { - if (auto cachedl = cached->lock()) { - return cachedl; + if (!mv->parseJson(clock, meterInputs, doc.as())) { + MO_DBG_ERR("deserialization error %s", fname); + return FilesystemUtils::LoadStatus::ErrFileCorruption; } } - //clean outdated pointers before creating new object - - txMeterData.erase(std::remove_if(txMeterData.begin(), txMeterData.end(), - [] (std::weak_ptr& txm) { - return txm.expired(); - }), - txMeterData.end()); - - //create new object and cache weak pointer - - auto tx = std::allocate_shared(makeAllocator(getMemoryTag()), connectorId, txNr, filesystem); - - if (filesystem) { - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "sd" "-%u-%u-%u.jsn", connectorId, txNr, 0); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return nullptr; //cannot store - } + //success - size_t size = 0; - bool exists = filesystem->stat(fn, &size) == 0; + MO_DBG_DEBUG("Restored tx %u-%u with %zu meter values", evseId, txNr, txData.size(), txMeterValues.size()); - if (exists) { - if (!tx->restore(mvBuilder)) { - remove(connectorId, txNr); - MO_DBG_ERR("removed corrupted tx entries"); - } - } + if (txMeterValues.empty()) { + return FilesystemUtils::LoadStatus::FileNotFound; } - txMeterData.push_back(tx); - - MO_DBG_DEBUG("Added txNr %u, now holding %zu sds", txNr, txMeterData.size()); - - return tx; + return FilesystemUtils::LoadStatus::Success; } -bool MeterStore::remove(unsigned int connectorId, unsigned int txNr) { - - unsigned int mvCount = 0; +FilesystemUtils::StoreStatus MeterStore::store(MO_FilesystemAdapter *filesystem, Context& context, unsigned int evseId, unsigned int txNr, unsigned int mvIndex, MeterValue& mv) { - auto cached = std::find_if(txMeterData.begin(), txMeterData.end(), - [connectorId, txNr] (std::weak_ptr& txm) { - if (auto txml = txm.lock()) { - return txml->getConnectorId() == connectorId && txml->getTxNr() == txNr; - } else { - return false; - } - }); - - if (cached != txMeterData.end()) { - if (auto cachedl = cached->lock()) { - mvCount = cachedl->getPathsCount(); - cachedl->finalize(); - } + char fname [MO_MAX_PATH_SIZE]; + if (!printTxMeterValueFName(fname, sizeof(fname), evseId, txNr, mvIndex)) { + MO_DBG_ERR("fname error %u %u %u", evseId, txNr, mvIndex); + return FilesystemUtils::StoreStatus::ErrOther; } - bool success = true; - - if (filesystem) { - if (mvCount == 0) { - const unsigned int MISSES_LIMIT = 3; - unsigned int misses = 0; - unsigned int i = 0; - - while (misses < MISSES_LIMIT) { //search until region without mvs found - - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "sd" "-%u-%u-%u.jsn", connectorId, txNr, i); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return false; //all files have same length - } - - size_t nsize = 0; - if (filesystem->stat(fn, &nsize) != 0) { - misses++; - i++; - continue; - } + auto& clock = context.getClock(); - i++; - mvCount = i; - misses = 0; - } - } - - MO_DBG_DEBUG("remove %u mvs for txNr %u", mvCount, txNr); - - for (unsigned int i = 0; i < mvCount; i++) { - unsigned int sd = mvCount - 1U - i; - - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "sd" "-%u-%u-%u.jsn", connectorId, txNr, sd); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return false; - } + JsonDoc doc = initJsonDoc("v16/v201.MeterStore", 1024); + if (!mv.toJson(clock, context.getOcppVersion(), /*internal format*/ true, doc.as())) { + MO_DBG_ERR("serialization error %s", fname); + return FilesystemUtils::StoreStatus::ErrJsonCorruption; + } - success &= filesystem->remove(fn); - } + auto ret = FilesystemUtils::storeJson(filesystem, fname, doc); + if (ret != FilesystemUtils::StoreStatus::Success) { + MO_DBG_ERR("fs error %s %i", fname, (int)ret); } - //clean outdated pointers + return ret; +} - txMeterData.erase(std::remove_if(txMeterData.begin(), txMeterData.end(), - [] (std::weak_ptr& txm) { - return txm.expired(); - }), - txMeterData.end()); +bool MeterStore::remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr) { - if (success) { - MO_DBG_DEBUG("Removed meter values for cId %u, txNr %u", connectorId, txNr); - } else { - MO_DBG_DEBUG("corrupted fs"); + char fname [MO_MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fname, MO_MAX_PATH_SIZE, "sd-%.*u-%.*u-", + MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr); + if (ret < 0 || (size_t)ret >= sizeof(fname)) { + MO_DBG_ERR("fname error: %i", ret); + return false; } - return success; + return FilesystemUtils::removeByPrefix(filesystem, fname); } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Metering/MeterStore.h b/src/MicroOcpp/Model/Metering/MeterStore.h index bdb67441..5499a1c6 100644 --- a/src/MicroOcpp/Model/Metering/MeterStore.h +++ b/src/MicroOcpp/Model/Metering/MeterStore.h @@ -8,54 +8,35 @@ #include #include #include +#include #include +#include -namespace MicroOcpp { - -class TransactionMeterData : public MemoryManaged { -private: - const unsigned int connectorId; //assignment to Transaction object - const unsigned int txNr; //assignment to Transaction object - - unsigned int mvCount = 0; //nr of saved meter values, including gaps - bool finalized = false; //if true, this is read-only - - std::shared_ptr filesystem; - - Vector> txData; - -public: - TransactionMeterData(unsigned int connectorId, unsigned int txNr, std::shared_ptr filesystem); +#if MO_ENABLE_V16 - bool addTxData(std::unique_ptr mv); - - Vector> retrieveStopTxData(); //will invalidate internal cache - - bool restore(MeterValueBuilder& mvBuilder); //load record from memory; true if record found, false if nothing loaded +#ifndef MO_STOPTXDATA_MAX_SIZE +#define MO_STOPTXDATA_MAX_SIZE 4 +#endif - unsigned int getConnectorId() {return connectorId;} - unsigned int getTxNr() {return txNr;} - unsigned int getPathsCount() {return mvCount;} //size of spanned path indexes - void finalize() {finalized = true;} - bool isFinalized() {return finalized;} -}; +#ifndef MO_STOPTXDATA_DIGITS +#define MO_STOPTXDATA_DIGITS 1 //digits needed to print MO_STOPTXDATA_MAX_SIZE-1 (="3", i.e. 1 digit) +#endif -class MeterStore : public MemoryManaged { -private: - std::shared_ptr filesystem; - - Vector> txMeterData; +namespace MicroOcpp { -public: - MeterStore() = delete; - MeterStore(MeterStore&) = delete; - MeterStore(std::shared_ptr filesystem); +class Context; - std::shared_ptr getTxMeterData(MeterValueBuilder& mvBuilder, Transaction *transaction); +namespace Ocpp16 { +namespace MeterStore { - bool remove(unsigned int connectorId, unsigned int txNr); -}; +bool printTxMeterValueFName(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int mvIndex); -} +FilesystemUtils::LoadStatus load(MO_FilesystemAdapter *filesystem, Context& context, Vector& meterInputs, unsigned int evseId, unsigned int txNr, Vector& txMeterValue); +FilesystemUtils::StoreStatus store(MO_FilesystemAdapter *filesystem, Context& context, unsigned int evseId, unsigned int txNr, unsigned int mvIndex, MeterValue& mv); +bool remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr); //removes tx meter values only +} //namespace MeterStore +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Model/Metering/MeterValue.cpp b/src/MicroOcpp/Model/Metering/MeterValue.cpp index 740905da..2b177283 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValue.cpp @@ -2,211 +2,582 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - #include -#include +#include #include -using namespace MicroOcpp; - -MeterValue::MeterValue(const Timestamp& timestamp) : - MemoryManaged("v16.Metering.MeterValue"), - timestamp(timestamp), - sampledValue(makeVector>(getMemoryTag())) { +#if MO_ENABLE_V16 || MO_ENABLE_V201 +void mo_MeterInput_init(MO_MeterInput *mInput, MO_MeterInputType type) { + memset(mInput, 0, sizeof(*mInput)); + mo_MeterInput_setType(mInput, type); } -void MeterValue::addSampledValue(std::unique_ptr sample) { - sampledValue.push_back(std::move(sample)); +MO_MeterInputType mo_MeterInput_getType(MO_MeterInput *mInput) { + return static_cast(mInput->type); +} +void mo_MeterInput_setType(MO_MeterInput *mInput, MO_MeterInputType type) { + mInput->type = static_casttype)>(type); } -std::unique_ptr MeterValue::toJson() { - size_t capacity = 0; - auto entries = makeVector>(getMemoryTag()); - for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) { - auto json = (*sample)->toJson(); - if (!json) { - return nullptr; - } - capacity += json->capacity(); - entries.push_back(std::move(json)); - } +using namespace MicroOcpp; - capacity += JSON_ARRAY_SIZE(entries.size()); - capacity += JSONDATE_LENGTH + 1; - capacity += JSON_OBJECT_SIZE(2); - - auto result = makeJsonDoc(getMemoryTag(), capacity); - auto jsonPayload = result->to(); +SampledValue::SampledValue(MO_MeterInput& meterInput) : MemoryManaged("v16.Metering.MeterValue"), meterInput(meterInput) { + +} - char timestampStr [JSONDATE_LENGTH + 1] = {'\0'}; - if (timestamp.toJsonString(timestampStr, JSONDATE_LENGTH + 1)) { - jsonPayload["timestamp"] = timestampStr; +SampledValue::~SampledValue() { + switch (getType()) { + case Type::Int: + case Type::Float: + case Type::UNDEFINED: + break; + case Type::String: + MO_FREE(valueString); + valueString = nullptr; + #if MO_ENABLE_V201 + case Type::SignedValue: + valueSigned->onDestroy(valueSigned->user_data); + MO_FREE(valueSigned); + valueSigned = nullptr; + break; + #endif //MO_ENABLE_V201 } - auto jsonMeterValue = jsonPayload.createNestedArray("sampledValue"); - for (auto entry = entries.begin(); entry != entries.end(); entry++) { - jsonMeterValue.add(**entry); +} + +SampledValue::Type SampledValue::getType() { + Type res = Type::UNDEFINED; + switch (meterInput.type) { + case MO_MeterInputType_Int: + case MO_MeterInputType_IntWithArgs: + res = Type::Int; + break; + case MO_MeterInputType_Float: + case MO_MeterInputType_FloatWithArgs: + res = Type::Float; + break; + case MO_MeterInputType_String: + case MO_MeterInputType_StringWithArgs: + res = Type::Float; + break; + #if MO_ENABLE_V201 + case MO_MeterInputType_SignedValueWithArgs: + res = Type::SignedValue; + break; + #endif //MO_ENABLE_V201 + case MO_MeterInputType_UNDEFINED: + res = Type::UNDEFINED; + break; } - return result; + return res; } -const Timestamp& MeterValue::getTimestamp() { - return timestamp; +MeterValue::MeterValue() : + MemoryManaged("v16.Metering.MeterValue"), + sampledValue(makeVector(getMemoryTag())) { + } -void MeterValue::setTimestamp(Timestamp timestamp) { - this->timestamp = timestamp; +MeterValue::~MeterValue() { + for (size_t i = 0; i < sampledValue.size(); i++) { + delete sampledValue[i]; + } + sampledValue.clear(); } -ReadingContext MeterValue::getReadingContext() { - //all sampledValues have the same ReadingContext. Just get the first result - for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) { - if ((*sample)->getReadingContext() != ReadingContext_UNDEFINED) { - return (*sample)->getReadingContext(); +#if MO_ENABLE_V201 + +namespace MicroOcpp { +bool loadSignedValue(MO_SignedMeterValue201* signedValue, const char *signedMeterData, const char *signingMethod, const char *encodingMethod, const char *publicKey) { + + size_t signedMeterDataSize = signedMeterData ? strlen(signedMeterData) + 1 : 0; + size_t signingMethodSize = signingMethod ? strlen(signingMethod) + 1 : 0; + size_t encodingMethodSize = encodingMethod ? strlen(encodingMethod) + 1 : 0; + size_t publicKeySize = publicKey ? strlen(publicKey) + 1 : 0; + + size_t bufsize = + signedMeterDataSize + + signingMethodSize + + encodingMethodSize + + publicKeySize; + + char *buf = static_cast(MO_MALLOC(getMemoryTag(), bufsize)); + if (!buf) { + MO_DBG_ERR("OOM"); + return false; + } + memset(buf, 0, bufsize); + size_t written = 0; + + if (signedMeterDataSize > 0) { + signedValue->signedMeterData = buf + written; + auto ret = snprintf(const_cast(signedValue->signedMeterData), signedMeterDataSize, "%s", signedMeterData); + if (ret < 0 || (size_t)ret >= signedMeterDataSize) { + MO_DBG_ERR("snprintf: %i", ret); + goto fail; } + + written += (size_t)ret + 1; } - return ReadingContext_UNDEFINED; -} -void MeterValue::setTxNr(unsigned int txNr) { - if (txNr > (unsigned int)std::numeric_limits::max()) { - MO_DBG_ERR("invalid arg"); - return; + if (signingMethodSize > 0) { + signedValue->signingMethod = buf + written; + auto ret = snprintf(const_cast(signedValue->signingMethod), signingMethodSize, "%s", signingMethod); + if (ret < 0 || (size_t)ret >= signingMethodSize) { + MO_DBG_ERR("snprintf: %i", ret); + goto fail; + } + written += (size_t)ret + 1; } - this->txNr = (int)txNr; -} -int MeterValue::getTxNr() { - return txNr; -} + if (encodingMethodSize > 0) { + signedValue->encodingMethod = buf + written; + auto ret = snprintf(const_cast(signedValue->encodingMethod), encodingMethodSize, "%s", encodingMethod); + if (ret < 0 || (size_t)ret >= encodingMethodSize) { + MO_DBG_ERR("snprintf: %i", ret); + goto fail; + } + written += (size_t)ret + 1; + } -void MeterValue::setOpNr(unsigned int opNr) { - this->opNr = opNr; -} + if (publicKeySize > 0) { + signedValue->publicKey = buf + written; + auto ret = snprintf(const_cast(signedValue->publicKey), publicKeySize, "%s", publicKey); + if (ret < 0 || (size_t)ret >= publicKeySize) { + MO_DBG_ERR("snprintf: %i", ret); + goto fail; + } + written += (size_t)ret + 1; + } -unsigned int MeterValue::getOpNr() { - return opNr; -} + if (written != bufsize) { + MO_DBG_ERR("internal error"); + goto fail; + } -void MeterValue::advanceAttemptNr() { - attemptNr++; -} + signedValue->user_data = reinterpret_cast(buf); -unsigned int MeterValue::getAttemptNr() { - return attemptNr; -} + signedValue->onDestroy = [] (void *user_data) { + auto buf = reinterpret_cast(user_data); + MO_FREE(buf); + }; -unsigned long MeterValue::getAttemptTime() { - return attemptTime; -} + return true; -void MeterValue::setAttemptTime(unsigned long timestamp) { - this->attemptTime = timestamp; +fail: + MO_FREE(buf); + return false; } +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 -MeterValueBuilder::MeterValueBuilder(const Vector> &samplers, - std::shared_ptr samplersSelectStr) : - MemoryManaged("v16.Metering.MeterValueBuilder"), - samplers(samplers), - selectString(samplersSelectStr), - select_mask(makeVector(getMemoryTag())) { +bool MeterValue::parseJson(Clock& clock, Vector& meterInputs, JsonObject in) { - updateObservedSamplers(); - select_observe = selectString->getValueRevision(); -} - -void MeterValueBuilder::updateObservedSamplers() { + if (!clock.parseString(in["timestamp"] | "_Invalid", timestamp)) { + return false; + } - if (select_mask.size() != samplers.size()) { - select_mask.resize(samplers.size(), false); - select_n = 0; + //MO stores the ReadingContext only once in the MeterValue object. Currently, there are no use cases in MO where + //different ReadingContexts could occur in the same MeterValue + if (in.containsKey("context")) { + readingContext = deserializeReadingContext(in["context"] | "_Invalid"); + if (readingContext == MO_ReadingContext_UNDEFINED) { + MO_DBG_ERR("deserialization error"); + return false; + } + } else { + readingContext = MO_ReadingContext_SamplePeriodic; } - for (size_t i = 0; i < select_mask.size(); i++) { - select_mask[i] = false; + JsonArray sampledValueJson = in["sampledValue"]; + sampledValue.resize(sampledValueJson.size()); + if (sampledValue.capacity() < sampledValueJson.size()) { + MO_DBG_ERR("OOM"); + return false; } - - auto sstring = selectString->getString(); - auto ssize = strlen(sstring) + 1; - size_t sl = 0, sr = 0; - while (sstring && sl < ssize) { - while (sr < ssize) { - if (sstring[sr] == ',') { + for (size_t i = 0; i < sampledValueJson.size(); i++) { //for each sampled value, search sampler with matching measurand type + JsonObject svJson = sampledValueJson[i]; + + auto format = svJson["format"] | (const char*)nullptr; + auto measurand = svJson["measurand"] | (const char*)nullptr; + auto phase = svJson["phase"] | (const char*)nullptr; + auto location = svJson["location"] | (const char*)nullptr; + + const char *unit = svJson["unit"] | (const char*)nullptr; + int8_t multiplier = 0; + + #if MO_ENABLE_V201 + { + int multiplierRaw = svJson["multiplier"] | 0; + multiplier = multiplierRaw; + if ((int)multiplier != multiplierRaw) { + MO_DBG_ERR("int overflow"); + return false; + } + } + #else + { + (void)multiplier; + } + #endif //MO_ENABLE_V201 + + MO_MeterInput *meterDevice = nullptr; + for (size_t i = 0; i < meterInputs.size(); i++) { + auto& mInput = meterInputs[i]; + if (!strcmp(mInput.measurand ? mInput.measurand : "Energy.Active.Import.Register", measurand ? measurand : "Energy.Active.Import.Register") && + !strcmp(mInput.unit ? mInput.unit : "Wh", unit ? unit : "Wh") && + #if MO_ENABLE_V201 + mInput.unitOfMeasureMultiplier == multiplier && + #endif //MO_ENABLE_V201 + !strcmp(mInput.location ? mInput.location : "Outlet", location ? location : "Outlet") && + !strcmp(mInput.phase ? mInput.phase : "", phase ? phase : "") && + !strcmp(mInput.format ? mInput.format : "Raw", format ? format : "Raw")) { + + //found + meterDevice = &mInput; break; } - sr++; } - if (sr != sl + 1) { - for (size_t i = 0; i < samplers.size(); i++) { - if (!strncmp(samplers[i]->getProperties().getMeasurand(), sstring + sl, sr - sl)) { - select_mask[i] = true; - select_n++; + if (!meterDevice) { + MO_DBG_ERR("need to add MeterInputs before setup()"); + return false; + } + + auto sv = new SampledValue(*meterDevice); + if (!sv) { + return false; + } + sampledValue.push_back(sv); + + switch (sv->getType()) { //type defined via meterDevice + case SampledValue::Type::Int: + if (!svJson["value"].is()) { + MO_DBG_ERR("deserialization error"); + return false; + } + sv->valueInt = svJson["value"]; + break; + case SampledValue::Type::Float: + if (!svJson["value"].is()) { + MO_DBG_ERR("deserialization error"); + return false; + } + sv->valueFloat = svJson["value"]; + break; + case SampledValue::Type::String: { + if (!svJson["value"].is()) { + MO_DBG_ERR("deserialization error"); + return false; + } + const char *value = svJson["value"]; + size_t valueLen = strlen(value); + char *valueCopy = nullptr; + if (valueLen > 0) { + size_t size = valueLen + 1; + valueCopy = static_cast(MO_MALLOC("v16.Transactions.TransactionStore", size)); + if (!valueCopy) { + MO_DBG_ERR("OOM"); + return false; + } + snprintf(valueCopy, size, "%s", value); } + sv->valueString = valueCopy; + break; } - } + #if MO_ENABLE_V201 + case SampledValue::Type::SignedValue: + if (!svJson["value"].isvalueSigned->value)>() || + !svJson["signedMeterData"].is() || + !svJson["signingMethod"].is() || + !svJson["encodingMethod"].is() || + !svJson["publicKey"].is()) { + MO_DBG_ERR("deserialization error"); + return false; + } + sv->valueSigned->value = svJson["value"]; - sr++; - sl = sr; + sv->valueSigned = static_cast(MO_MALLOC(getMemoryTag(), sizeof(MO_SignedMeterValue201))); + if (!sv->valueSigned) { + MO_DBG_ERR("OOM"); + return false; + } + if (!loadSignedValue(sv->valueSigned, + svJson["signedMeterData"] | (const char*)nullptr, + svJson["signingMethod"] | (const char*)nullptr, + svJson["encodingMethod"] | (const char*)nullptr, + svJson["publicKey"] | (const char*)nullptr)) { + MO_DBG_ERR("OOM"); + MO_FREE(sv->valueSigned); + return false; + } + break; + #endif //MO_ENABLE_V201 + case SampledValue::Type::UNDEFINED: + MO_DBG_ERR("deserialization error"); + return false; + } } + + MO_DBG_VERBOSE("deserialized MV"); + return true; } -std::unique_ptr MeterValueBuilder::takeSample(const Timestamp& timestamp, const ReadingContext& context) { - if (select_observe != selectString->getValueRevision() || //OCPP server has changed configuration about which measurands to take - samplers.size() != select_mask.size()) { //Client has added another Measurand; synchronize lists - MO_DBG_DEBUG("Updating observed samplers due to config change or samplers added"); - updateObservedSamplers(); - select_observe = selectString->getValueRevision(); - } +int MeterValue::getJsonCapacity(int ocppVersion, bool internalFormat) { + + size_t capacity = 0; + + capacity += JSON_OBJECT_SIZE(2); //timestamp, sampledValue - if (select_n == 0) { - return nullptr; + if (internalFormat) { + capacity += MO_INTERNALTIME_SIZE; + if (readingContext != MO_ReadingContext_SamplePeriodic) { + capacity += JSON_OBJECT_SIZE(1); //readingContext + } + } else { + capacity += MO_JSONDATE_SIZE; } - auto sample = std::unique_ptr(new MeterValue(timestamp)); + capacity += JSON_ARRAY_SIZE(sampledValue.size()); + for (size_t j = 0; j < sampledValue.size(); j++) { + auto sv = sampledValue[j]; - for (size_t i = 0; i < select_mask.size(); i++) { - if (select_mask[i]) { - sample->addSampledValue(samplers[i]->takeValue(context)); + int valueLen = -1; + switch (sv->getType()) { + case SampledValue::Type::Int: + valueLen = snprintf(nullptr, 0, "%i", sv->valueInt); + break; + case SampledValue::Type::Float: + valueLen = snprintf(nullptr, 0, MO_SAMPLEDVALUE_FLOAT_FORMAT, sv->valueFloat); + break; + case SampledValue::Type::String: + valueLen = (int)strlen(sv->valueString ? sv->valueString : ""); + break; + #if MO_ENABLE_V201 + case SampledValue::Type::SignedValue: + valueLen = snprintf(nullptr, 0, MO_SAMPLEDVALUE_FLOAT_FORMAT, sv->valueSigned->value); + break; + #endif //MO_ENABLE_V201 + case SampledValue::Type::UNDEFINED: + MO_DBG_ERR("serialization error"); + break; + } + if (valueLen >= 0) { + capacity += (size_t)valueLen + 1; + } else { + MO_DBG_ERR("serialization error"); + return -1; + } + + capacity += JSON_OBJECT_SIZE(1) + valueLen + 1; //value + + if (!internalFormat && readingContext != MO_ReadingContext_SamplePeriodic) { + capacity += JSON_OBJECT_SIZE(1); //readingContext + } + + auto& meterDevice = sv->meterInput; + + if (meterDevice.format && strcmp(meterDevice.format, "Raw")) { + capacity += JSON_OBJECT_SIZE(1); //format + } + if (meterDevice.measurand && strcmp(meterDevice.measurand, "Energy.Active.Import.Register")) { + capacity += JSON_OBJECT_SIZE(1); //measurand + } + if (meterDevice.phase && *meterDevice.phase) { + capacity += JSON_OBJECT_SIZE(1); //phase + } + if (meterDevice.location && strcmp(meterDevice.location, "Outlet")) { + capacity += JSON_OBJECT_SIZE(1); //location + } + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + //no signed meter value and simple unitOfMeasure + if (meterDevice.unit && strcmp(meterDevice.unit, "Wh")) { + capacity += JSON_OBJECT_SIZE(1); //unit + } + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + //complex unitOfMeasure and signedMeterValue + if (sv->getType() == SampledValue::Type::SignedValue) { + if (!internalFormat) { + capacity += JSON_OBJECT_SIZE(1); //stored as complex type + } + capacity += sv->valueSigned->signedMeterData ? JSON_OBJECT_SIZE(1) : 0; + capacity += sv->valueSigned->signingMethod ? JSON_OBJECT_SIZE(1): 0; + capacity += sv->valueSigned->encodingMethod ? JSON_OBJECT_SIZE(1) : 0; + capacity += sv->valueSigned->publicKey ? JSON_OBJECT_SIZE(1) : 0; + } + + bool hasUnitOfMeasure = false; + + if (meterDevice.unit && strcmp(meterDevice.unit, "Wh")) { + capacity += JSON_OBJECT_SIZE(1); //unit + hasUnitOfMeasure = true; + } + + if (meterDevice.unitOfMeasureMultiplier != 0) { + capacity += JSON_OBJECT_SIZE(1); //multiplier + hasUnitOfMeasure = true; + } + + if (hasUnitOfMeasure && !internalFormat) { + capacity += JSON_OBJECT_SIZE(1); + } } + #endif //MO_ENABLE_V201 } - return sample; + return (size_t)capacity; } -std::unique_ptr MeterValueBuilder::deserializeSample(const JsonObject mvJson) { - - Timestamp timestamp; - bool ret = timestamp.setTime(mvJson["timestamp"] | "Invalid"); - if (!ret) { - MO_DBG_ERR("invalid timestamp"); - return nullptr; - } - - auto sample = std::unique_ptr(new MeterValue(timestamp)); - - JsonArray sampledValue = mvJson["sampledValue"]; - for (JsonObject svJson : sampledValue) { //for each sampled value, search sampler with matching measurand type - for (auto& sampler : samplers) { - auto& properties = sampler->getProperties(); - if (!strcmp(properties.getMeasurand(), svJson["measurand"] | "") && - !strcmp(properties.getFormat(), svJson["format"] | "") && - !strcmp(properties.getPhase(), svJson["phase"] | "") && - !strcmp(properties.getLocation(), svJson["location"] | "") && - !strcmp(properties.getUnit(), svJson["unit"] | "")) { - //found correct sampler - auto dVal = sampler->deserializeValue(svJson); - if (dVal) { - sample->addSampledValue(std::move(dVal)); +bool MeterValue::toJson(Clock& clock, int ocppVersion, bool internalFormat, JsonObject out) { + + if (internalFormat) { + char timeStr [MO_INTERNALTIME_SIZE]; + if (!clock.toInternalString(timestamp, timeStr, sizeof(timeStr))) { + return false; + } + out["timestamp"] = timeStr; + } else { + char timeStr [MO_JSONDATE_SIZE]; + if (!clock.toJsonString(timestamp, timeStr, sizeof(timeStr))) { + return false; + } + out["timestamp"] = timeStr; + } + + if (readingContext == MO_ReadingContext_UNDEFINED) { + MO_DBG_ERR("serialization error"); + return false; + } + + if (internalFormat && readingContext != MO_ReadingContext_SamplePeriodic) { + out["context"] = serializeReadingContext(readingContext); + } + + JsonArray sampledValueJson = out.createNestedArray("sampledValue"); + + for (size_t i = 0; i < sampledValue.size(); i++) { + auto sv = sampledValue[i]; + + JsonObject svJson = sampledValueJson.createNestedObject(); + + char valueBuf [30]; //for int and float, serialized as string + + switch (sv->getType()) { + case SampledValue::Type::Int: + if (internalFormat) { + svJson["value"] = sv->valueInt; } else { - MO_DBG_ERR("deserialization error"); + int ret = snprintf(valueBuf, sizeof(valueBuf), "%i", sv->valueInt); + if (ret < 0 || ret >= sizeof(valueBuf)) { + MO_DBG_ERR("serialization error"); + return false; + } + svJson["value"] = valueBuf; + } + break; + case SampledValue::Type::Float: + if (internalFormat) { + svJson["value"] = sv->valueFloat; + } else { + int ret = snprintf(valueBuf, sizeof(valueBuf), MO_SAMPLEDVALUE_FLOAT_FORMAT, sv->valueFloat); + if (ret < 0 || ret >= sizeof(valueBuf)) { + MO_DBG_ERR("serialization error"); + return false; + } + svJson["value"] = valueBuf; + } + break; + case SampledValue::Type::String: + svJson["value"] = sv->valueString ? (const char*)sv->valueString : ""; //force zero-copy + break; + #if MO_ENABLE_V201 + case SampledValue::Type::SignedValue: { + JsonObject signedMeterValue; + if (internalFormat) { + svJson["value"] = sv->valueSigned->value; + signedMeterValue = svJson; //write signature data into same sv JSON object + } else { + int ret = snprintf(valueBuf, sizeof(valueBuf), MO_SAMPLEDVALUE_FLOAT_FORMAT, sv->valueFloat); + if (ret < 0 || ret >= sizeof(valueBuf)) { + MO_DBG_ERR("serialization error"); + return false; + } + svJson["value"] = valueBuf; + signedMeterValue = svJson.createNestedObject("signedMeterValue"); + } + if (sv->valueSigned->signedMeterData) { + signedMeterValue["signedMeterData"] = (const char*)sv->valueSigned->signedMeterData; //force zero-copy + } + if (sv->valueSigned->signingMethod) { + signedMeterValue["signingMethod"] = (const char*)sv->valueSigned->signingMethod; //force zero-copy + } + if (sv->valueSigned->encodingMethod) { + signedMeterValue["encodingMethod"] = (const char*)sv->valueSigned->encodingMethod; //force zero-copy + } + if (sv->valueSigned->publicKey) { + signedMeterValue["publicKey"] = (const char*)sv->valueSigned->publicKey; //force zero-copy } break; } + #endif //MO_ENABLE_V201 + case SampledValue::Type::UNDEFINED: + MO_DBG_ERR("serialization error"); + return false; + } + + if (!internalFormat && readingContext != MO_ReadingContext_SamplePeriodic) { + svJson["context"] = serializeReadingContext(readingContext); } + + auto& meterDevice = sv->meterInput; + + if (meterDevice.format && strcmp(meterDevice.format, "Raw")) { + svJson["format"] = meterDevice.format; + } + if (meterDevice.measurand && strcmp(meterDevice.measurand, "Energy.Active.Import.Register")) { + svJson["measurand"] = meterDevice.measurand; + } + if (meterDevice.phase && *meterDevice.phase) { + svJson["phase"] = meterDevice.phase; //phase + } + if (meterDevice.location && strcmp(meterDevice.location, "Outlet")) { + svJson["location"] = meterDevice.location; //location + } + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + if (meterDevice.unit && strcmp(meterDevice.unit, "Wh")) { + svJson["unit"] = meterDevice.unit; //unit + } + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (meterDevice.unit && strcmp(meterDevice.unit, "Wh") || meterDevice.unitOfMeasureMultiplier != 0) { + JsonObject unitJson; + if (internalFormat) { + unitJson = svJson; + } else { + unitJson = svJson.createNestedObject("unitOfMeasure"); + } + + if (meterDevice.unit && strcmp(meterDevice.unit, "Wh")) { + unitJson["unit"] = meterDevice.unit; + } + if (meterDevice.unitOfMeasureMultiplier != 0) { + unitJson["multiplier"] = meterDevice.unitOfMeasureMultiplier; + } + } + } + #endif //MO_ENABLE_V201 } - MO_DBG_VERBOSE("deserialized MV"); - return sample; + return true; } + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Metering/MeterValue.h b/src/MicroOcpp/Model/Metering/MeterValue.h index 0637ad33..f9e8bd2b 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.h +++ b/src/MicroOcpp/Model/Metering/MeterValue.h @@ -5,68 +5,161 @@ #ifndef MO_METERVALUE_H #define MO_METERVALUE_H +#include +#include +#include +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 + +#ifndef MO_SAMPLEDVALUE_FLOAT_FORMAT +#define MO_SAMPLEDVALUE_FLOAT_FORMAT "%.2f" +#endif + +#ifndef MO_SAMPLEDVALUE_STRING_MAX_LEN +#define MO_SAMPLEDVALUE_STRING_MAX_LEN 1000 //maximum acceptable sampled value string length +#endif + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +#if MO_ENABLE_V201 + +/* Compound type for transmitting SignedMeterValues via OCPP. The firmware should generate signature + * data and keep it ready until MO queries it using the `getSignedValue` callback. Then, the firmware + * can fill this struct provided by MO. Probably the signature data needs to be duplicated into the + * heap per malloc. The firmware can set the `onDestroy` callback to free the memory again. */ +struct MO_SignedMeterValue201 { + double value; + + /* Signed data. If not null, MO will dereference these strings and send them to the server. When + * MO no longer needs these strings, it calls onDestroy (guaranteed execution). It's possible to + * malloc string buffers for these fields and to free them again in onDestroy */ + const char *signedMeterData; + const char *signingMethod; + const char *encodingMethod; + const char *publicKey; + + //set this as needed + void* user_data; //MO will forward this pointer to onDestroy + void (*onDestroy)(void *user_data); //MO calls it shortly before destruction of this struct +}; + +#endif //MO_ENABLE_V201 + +typedef enum { + MO_MeterInputType_UNDEFINED = 0, + MO_MeterInputType_Int, + MO_MeterInputType_Float, + MO_MeterInputType_String, + MO_MeterInputType_IntWithArgs, + MO_MeterInputType_FloatWithArgs, + MO_MeterInputType_StringWithArgs, + + #if MO_ENABLE_V201 + MO_MeterInputType_SignedValueWithArgs, + #endif //MO_ENABLE_V201 +} MO_MeterInputType; + +typedef struct { + + void* user_data; + union { + int32_t (*getInt)(); + float (*getFloat)(); + //if `size` is large enough, writes string to `buf` and returns written string length (not + //including terminating zero). If `size` is too short, returns string length which would have + //been written if `buf` was large enough. MO may retry with a larger buf. Adds a terminating 0. + //Note: `buf` will never be larger than (MO_SAMPLEDVALUE_STRING_MAX_LEN + 1) + int (*getString)(char *buf, size_t size); + + int32_t (*getInt2)(MO_ReadingContext readingContext, unsigned int evseId, void *user_data); + float (*getFloat2)(MO_ReadingContext readingContext, unsigned int evseId, void *user_data); + int (*getString2)(char *buf, size_t size, MO_ReadingContext readingContext, unsigned int evseId, void *user_data); //see `getString()` + + #if MO_ENABLE_V201 + //if signed value exists, fills `val` with the signature data and returns true. If no signed + //data exists, returns false. + bool (*getSignedValue2)(MO_SignedMeterValue201* val, MO_ReadingContext readingContext, unsigned int evseId, void *user_data); + #endif //MO_ENABLE_V201 + }; + + uint8_t type; //MO_MeterInputType in dense representation. Use `mo_MeterInput_setType()` to define + uint8_t mo_flags; //flags which MO uses internally + + #if MO_ENABLE_V201 + int8_t unitOfMeasureMultiplier; + #endif //MO_ENABLE_V201 + + //OCPP-side attributes. Zero-copy strings + const char *format; + const char *measurand; + const char *phase; + const char *location; + const char *unit; +} MO_MeterInput; + +void mo_MeterInput_init(MO_MeterInput *mInput, MO_MeterInputType type); +MO_MeterInputType mo_MeterInput_getType(MO_MeterInput *mInput); +void mo_MeterInput_setType(MO_MeterInput *mInput, MO_MeterInputType type); + +#ifdef __cplusplus +} //extern "C" + #include -#include -#include +#include #include -#include -#include namespace MicroOcpp { -class MeterValue : public MemoryManaged { -private: - Timestamp timestamp; - Vector> sampledValue; +class Clock; - int txNr = -1; - unsigned int opNr = 1; - unsigned int attemptNr = 0; - unsigned long attemptTime = 0; -public: - MeterValue(const Timestamp& timestamp); - MeterValue(const MeterValue& other) = delete; - - void addSampledValue(std::unique_ptr sample); +struct SampledValue : public MemoryManaged { + MO_MeterInput& meterInput; - std::unique_ptr toJson(); + union { + int32_t valueInt; + float valueFloat; + char *valueString = nullptr; - const Timestamp& getTimestamp(); - void setTimestamp(Timestamp timestamp); + #if MO_ENABLE_V201 + MO_SignedMeterValue201 *valueSigned; + #endif //MO_ENABLE_V201 + }; - ReadingContext getReadingContext(); + SampledValue(MO_MeterInput& meterInput); + ~SampledValue(); - void setTxNr(unsigned int txNr); - int getTxNr(); + enum class Type : uint8_t { + UNDEFINED, + Int, + Float, + String, + SignedValue, + }; + Type getType(); +}; - void setOpNr(unsigned int opNr); - unsigned int getOpNr(); +struct MeterValue : public MemoryManaged { + Timestamp timestamp; + Vector sampledValue; + MO_ReadingContext readingContext = MO_ReadingContext_UNDEFINED; //all SampledValues in a MeterValue have the same MO_ReadingContext - void advanceAttemptNr(); - unsigned int getAttemptNr(); + int txNr = -1; + unsigned int opNr = 1; + unsigned int attemptNr = 0; + Timestamp attemptTime; - unsigned long getAttemptTime(); - void setAttemptTime(unsigned long timestamp); -}; + MeterValue(); + ~MeterValue(); -class MeterValueBuilder : public MemoryManaged { -private: - const Vector> &samplers; - std::shared_ptr selectString; - Vector select_mask; - unsigned int select_n {0}; - decltype(selectString->getValueRevision()) select_observe; - - void updateObservedSamplers(); -public: - MeterValueBuilder(const Vector> &samplers, - std::shared_ptr samplersSelectStr); - - std::unique_ptr takeSample(const Timestamp& timestamp, const ReadingContext& context); - - std::unique_ptr deserializeSample(const JsonObject mvJson); + bool parseJson(Clock& clock, Vector& meterInputs, JsonObject in); //only parses internal format + int getJsonCapacity(int ocppVersion, bool internalFormat); + bool toJson(Clock& clock, int ocppVersion, bool internalFormat, JsonObject out); }; -} - +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 +#endif //__cplusplus #endif diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index 65b78fc3..db47e6c7 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -5,372 +5,979 @@ #include #include +#include +#include +#include #include -#include +#include +#include +#include #include -#include -#include -#include -#include +#include #include #include #include -using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +#if MO_ENABLE_V16 || MO_ENABLE_V201 -MeteringServiceEvse::MeteringServiceEvse(Context& context, int connectorId, MeterStore& meterStore) - : MemoryManaged("v16.Metering.MeteringServiceEvse"), context(context), model(context.getModel()), connectorId{connectorId}, meterStore(meterStore), meterData(makeVector>(getMemoryTag())), samplers(makeVector>(getMemoryTag())) { +namespace MicroOcpp { - context.getRequestQueue().addSendQueue(this); +MeterValue *takeMeterValue(Clock& clock, Vector& meterInputs, unsigned int evseId, MO_ReadingContext readingContext, uint8_t inputSelectFlag, const char *memoryTag) { - auto meterValuesSampledDataString = declareConfiguration("MeterValuesSampledData", ""); - declareConfiguration("MeterValuesSampledDataMaxLength", 8, CONFIGURATION_VOLATILE, true); - meterValueSampleIntervalInt = declareConfiguration("MeterValueSampleInterval", 60); - registerConfigurationValidator("MeterValueSampleInterval", VALIDATE_UNSIGNED_INT); + size_t samplesSize = 0; + for (size_t i = 0; i < meterInputs.size(); i++) { + if (meterInputs[i].mo_flags & inputSelectFlag) { + samplesSize++; + } + } - auto stopTxnSampledDataString = declareConfiguration("StopTxnSampledData", ""); - declareConfiguration("StopTxnSampledDataMaxLength", 8, CONFIGURATION_VOLATILE, true); + if (samplesSize == 0) { + MO_DBG_DEBUG("no meter inputs selected"); + return nullptr; + } - auto meterValuesAlignedDataString = declareConfiguration("MeterValuesAlignedData", ""); - declareConfiguration("MeterValuesAlignedDataMaxLength", 8, CONFIGURATION_VOLATILE, true); - clockAlignedDataIntervalInt = declareConfiguration("ClockAlignedDataInterval", 0); - registerConfigurationValidator("ClockAlignedDataInterval", VALIDATE_UNSIGNED_INT); + auto meterValue = new MeterValue(); + if (!meterValue) { + MO_DBG_ERR("OOM"); + goto fail; + } - auto stopTxnAlignedDataString = declareConfiguration("StopTxnAlignedData", ""); + meterValue->sampledValue.resize(samplesSize); + if (meterValue->sampledValue.capacity() < samplesSize) { + MO_DBG_ERR("OOM"); + goto fail; + } - meterValuesInTxOnlyBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "MeterValuesInTxOnly", true); - stopTxnDataCapturePeriodicBool = declareConfiguration(MO_CONFIG_EXT_PREFIX "StopTxnDataCapturePeriodic", false); + meterValue->timestamp = clock.now(); + meterValue->readingContext = readingContext; - transactionMessageAttemptsInt = declareConfiguration("TransactionMessageAttempts", 3); - transactionMessageRetryIntervalInt = declareConfiguration("TransactionMessageRetryInterval", 60); + for (size_t i = 0; i < meterInputs.size(); i++) { + auto& mInput = meterInputs[i]; + if (mInput.mo_flags & inputSelectFlag) { + auto sv = new SampledValue(mInput); + if (!sv) { + MO_DBG_ERR("OOM"); + goto fail; + } - sampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, meterValuesSampledDataString)); - alignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, meterValuesAlignedDataString)); - stopTxnSampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, stopTxnSampledDataString)); - stopTxnAlignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, stopTxnAlignedDataString)); -} + switch (mInput.type) { + case MO_MeterInputType_Int: + sv->valueInt = mInput.getInt(); + break; + case MO_MeterInputType_IntWithArgs: + sv->valueInt = mInput.getInt2(readingContext, evseId, mInput.user_data); + break; + case MO_MeterInputType_Float: + sv->valueFloat = mInput.getFloat(); + break; + case MO_MeterInputType_FloatWithArgs: + sv->valueFloat = mInput.getFloat2(readingContext, evseId, mInput.user_data); + break; + case MO_MeterInputType_String: + case MO_MeterInputType_StringWithArgs: { + char bufStack [100]; + int ret = -1; + if (mInput.type == MO_MeterInputType_String) { + ret = mInput.getString(bufStack, sizeof(bufStack)); + } else { //MO_MeterInputType_StringWithArgs + ret = mInput.getString2(bufStack, sizeof(bufStack), readingContext, evseId, mInput.user_data); + } + if (ret < 0 || ret > MO_SAMPLEDVALUE_STRING_MAX_LEN) { + MO_DBG_ERR("meterInput getString cb: %i", ret); + delete sv; + sv = nullptr; + break; //failure, skip this measurand + } -void MeteringServiceEvse::loop() { + if (ret > 0) { + size_t size = ret + 1; + sv->valueString = static_cast(MO_MALLOC(memoryTag, size)); + if (!sv->valueString) { + MO_DBG_ERR("OOM"); + delete sv; + sv = nullptr; + break; //failure, skip this measurand + } + memset(sv->valueString, 0, size); + + if ((size_t)ret < sizeof(bufStack)) { + //bufStack contains value + snprintf(sv->valueString, size, "%s", bufStack); + } else { + //need to re-run getString with properly sized buffer + if (mInput.type == MO_MeterInputType_String) { + ret = mInput.getString(sv->valueString, size); + } else { //MO_MeterInputType_StringWithArgs + ret = mInput.getString2(sv->valueString, size, readingContext, evseId, mInput.user_data); + } + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("meterInput getString cb: %i", ret); + MO_FREE(sv->valueString); + sv->valueString = nullptr; + delete sv; + sv = nullptr; + break; //failure, skip this measurand + } + } + } - bool txBreak = false; - if (model.getConnector(connectorId)) { - auto &curTx = model.getConnector(connectorId)->getTransaction(); - txBreak = (curTx && curTx->isRunning()) != trackTxRunning; - trackTxRunning = (curTx && curTx->isRunning()); + break; //success + } + #if MO_ENABLE_V201 + case MO_MeterInputType_SignedValueWithArgs: + sv->valueSigned = static_cast(MO_MALLOC(memoryTag, sizeof(MO_SignedMeterValue201))); + if (!sv->valueSigned) { + MO_DBG_ERR("OOM"); + delete sv; + sv = nullptr; + break; //failure, skip this measurand + } + memset(sv->valueSigned, 0, sizeof(*sv->valueSigned)); + + if (!mInput.getSignedValue2(sv->valueSigned, readingContext, evseId, mInput.user_data)) { + if (sv->valueSigned->onDestroy) { + sv->valueSigned->onDestroy(sv->valueSigned->user_data); + } + MO_FREE(sv->valueSigned); + delete sv; + sv = nullptr; + break; //undefined value skip this measurand + } + break; + #endif //MO_ENABLE_V201 + case MO_MeterInputType_UNDEFINED: + MO_DBG_ERR("need to set type of meter input"); + delete sv; + sv = nullptr; + break; //failure, skip this measurand + } + + if (sv) { + meterValue->sampledValue.push_back(sv); + } + } } - if (txBreak) { - lastSampleTime = mocpp_tick_ms(); + return meterValue; +fail: + delete meterValue; + meterValue = nullptr; + return nullptr; +} + +void updateInputSelectFlag(const char *selectString, uint8_t flag, Vector& meterInputs) { + auto size = strlen(selectString) + 1; + size_t sl = 0, sr = 0; + while (selectString && sl < size) { + while (sr < size) { + if (selectString[sr] == ',') { + break; + } + sr++; + } + + if (sr != sl + 1) { + const char *csvMeasurand = selectString + sl; + size_t csvMeasurandLen = sr - sl; + + for (size_t i = 0; i < meterInputs.size(); i++) { + const char *measurand = meterInputs[i].measurand ? meterInputs[i].measurand : "Energy.Active.Import.Register"; + if (!strncmp(measurand, csvMeasurand, csvMeasurandLen) && strlen(measurand) == csvMeasurandLen) { + meterInputs[i].mo_flags |= flag; + } else { + meterInputs[i].mo_flags &= ~flag; + } + } + } + + sr++; + sl = sr; } +} + +bool validateSelectString(const char *selectString, void *user_data) { + auto& context = *reinterpret_cast(user_data); - if (model.getConnector(connectorId)) { - if (transaction != model.getConnector(connectorId)->getTransaction()) { - transaction = model.getConnector(connectorId)->getTransaction(); + unsigned int numEvseId = 0; + + #if MO_ENABLE_V16 + if (context.getOcppVersion() == MO_OCPP_V16) { + numEvseId = context.getModel16().getNumEvseId(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (context.getOcppVersion() == MO_OCPP_V201) { + numEvseId = context.getModel201().getNumEvseId(); + } + #endif //MO_ENABLE_V201 + + bool isValid = true; + const char *l = selectString; //the beginning of an entry of the comma-separated list + const char *r = l; //one place after the last character of the entry beginning with l + while (*l) { + if (*l == ',') { + l++; + continue; + } + r = l + 1; + while (*r != '\0' && *r != ',') { + r++; } - if (transaction && transaction->isRunning() && !transaction->isSilent()) { - //check during transaction + const char *csvMeasurand = l; + size_t csvMeasurandLen = (size_t)(r - l); + + //check if measurand exists in MeterInputs. Search through all EVSEs + bool found = false; + for (unsigned int evseId = 0; evseId < numEvseId; evseId++) { - if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { - MO_DBG_WARN("reload stopTxnData, %s, for tx-%u-%u", stopTxnData ? "replace" : "first time", connectorId, transaction->getTxNr()); - //reload (e.g. after power cut during transaction) - stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction.get()); + Vector *meterInputsPtr = nullptr; + + #if MO_ENABLE_V16 + if (context.getOcppVersion() == MO_OCPP_V16) { + auto mService = context.getModel16().getMeteringService(); + auto evse = mService ? mService->getEvse(evseId) : nullptr; + meterInputsPtr = evse ? &evse->getMeterInputs() : nullptr; } - } else { - //check outside of transaction + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (context.getOcppVersion() == MO_OCPP_V201) { + auto mService = context.getModel201().getMeteringService(); + auto evse = mService ? mService->getEvse(evseId) : nullptr; + meterInputsPtr = evse ? &evse->getMeterInputs() : nullptr; + } + #endif //MO_ENABLE_V201 - if (connectorId != 0 && meterValuesInTxOnlyBool->getBool()) { - //don't take any MeterValues outside of transactions on connectorIds other than 0 - return; + if (!meterInputsPtr) { + MO_DBG_ERR("internal error"); + continue; } + + auto& meterInputs = *meterInputsPtr; + + for (size_t i = 0; i < meterInputs.size(); i++) { + const char *measurand = meterInputs[i].measurand ? meterInputs[i].measurand : "Energy.Active.Import.Register"; + if (!strncmp(measurand, csvMeasurand, csvMeasurandLen) && strlen(measurand) == csvMeasurandLen) { + found = true; + break; + } + } + + if (found) { + break; + } + } + if (!found) { + isValid = false; + MO_DBG_WARN("could not find metering device for %.*s", (int)csvMeasurandLen, csvMeasurand); + break; } + l = r; } + return isValid; +} - if (clockAlignedDataIntervalInt->getInt() >= 1 && model.getClock().now() >= MIN_TIME) { +} //namespace MicroOcpp - auto& timestampNow = model.getClock().now(); - auto dt = nextAlignedTime - timestampNow; - if (dt < 0 || //normal case: interval elapsed - dt > clockAlignedDataIntervalInt->getInt()) { //special case: clock has been adjusted or first run +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 - MO_DBG_DEBUG("Clock aligned measurement %ds: %s", dt, - abs(dt) <= 60 ? - "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); - if (abs(dt) <= 60) { //is measurement still "clock-aligned"? +#if MO_ENABLE_V16 - if (auto alignedMeterValue = alignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock)) { - if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { - MO_DBG_INFO("MeterValue cache full. Drop old MV"); - meterData.erase(meterData.begin()); - } - alignedMeterValue->setOpNr(context.getRequestQueue().getNextOpNr()); - if (transaction) { - alignedMeterValue->setTxNr(transaction->getTxNr()); - } - meterData.push_back(std::move(alignedMeterValue)); +#define MO_FLAG_MeterValuesSampledData (1 << 0) +#define MO_FLAG_MeterValuesAlignedData (1 << 1) +#define MO_FLAG_StopTxnSampledData (1 << 2) +#define MO_FLAG_StopTxnAlignedData (1 << 3) + +using namespace MicroOcpp; + +Ocpp16::MeteringServiceEvse::MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int connectorId) + : MemoryManaged("v16.Metering.MeteringServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), mService(mService), connectorId(connectorId), meterData(makeVector(getMemoryTag())), meterInputs(makeVector(getMemoryTag())) { + Timestamp lastSampleTime = context.getClock().getUptime(); + Timestamp lastAlignedTime = context.getClock().now(); +} + +Ocpp16::MeteringServiceEvse::~MeteringServiceEvse() { + for (size_t i = 0; i < meterData.size(); i++) { + delete meterData[i]; + } + meterData.clear(); + delete meterDataFront; + meterDataFront = nullptr; +} + +bool Ocpp16::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { + auto capacity = meterInputs.size() + 1; + meterInputs.resize(capacity); + if (meterInputs.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return false; + } + + meterInput.mo_flags = 0; + meterInputs.push_back(meterInput); + return true; +} + +Vector& Ocpp16::MeteringServiceEvse::getMeterInputs() { + return meterInputs; +} + +bool Ocpp16::MeteringServiceEvse::setTxEnergyMeterInput(int32_t (*getInt)()) { + txEnergyMeterInput = getInt; + return true; +} + +bool Ocpp16::MeteringServiceEvse::setTxEnergyMeterInput2(int32_t (*getInt2)(MO_ReadingContext readingContext, unsigned int evseId, void *user_data), void *user_data) { + txEnergyMeterInput2 = getInt2; + txEnergyMeterInput2_user_data = user_data; + return true; +} + +bool Ocpp16::MeteringServiceEvse::setup() { + + context.getMessageService().addSendQueue(this); + + filesystem = context.getFilesystem(); + if (!filesystem) { + MO_DBG_DEBUG("volatile mode"); + } + + auto txSvc = context.getModel16().getTransactionService(); + txSvcEvse = txSvc ? txSvc->getEvse(connectorId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("setup failure"); + return false; + } + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + if (!txEnergyMeterInput && !txEnergyMeterInput2) { + //search default energy meter + bool found = false; + for (size_t i = 0; i < meterInputs.size(); i++) { + auto& mInput = meterInputs[i]; + if (mInput.measurand && !strcmp(mInput.measurand, "Energy.Active.Import.Register") && + (!mInput.phase || !*mInput.phase) && + (!mInput.location || !strcmp(mInput.location, "Outlet")) && + (!mInput.unit || !strcmp(mInput.unit, "Wh")) && + (mInput.type == MO_MeterInputType_Int || mInput.type == MO_MeterInputType_IntWithArgs)) { + + if (mInput.type == MO_MeterInputType_Int) { + txEnergyMeterInput = mInput.getInt; + } else { + txEnergyMeterInput2 = mInput.getInt2; + txEnergyMeterInput2_user_data = mInput.user_data; + } + found = true; + } + } + + if (found) { + MO_DBG_DEBUG("found energy input for Start- and StopTx values"); + } else { + MO_DBG_WARN("no energy meter set up. Start- and StopTx will report 0 Wh"); + } + } + + return true; +} + +MicroOcpp::MeterValue *Ocpp16::MeteringServiceEvse::takeMeterValue(MO_ReadingContext readingContext, uint8_t inputSelectFlag) { + return MicroOcpp::takeMeterValue(context.getClock(), meterInputs, connectorId, readingContext, inputSelectFlag, getMemoryTag()); +} + +void Ocpp16::MeteringServiceEvse::loop() { + + bool txBreak = false; + auto transaction = txSvcEvse->getTransaction(); + txBreak = (transaction && transaction->isRunning()) != trackTxRunning; + trackTxRunning = (transaction && transaction->isRunning()); + + if (txBreak) { + lastSampleTime = clock.getUptime(); + } + + if (!transaction && connectorId != 0 && mService.meterValuesInTxOnlyBool->getBool()) { + //don't take any MeterValues outside of transactions on connectorIds other than 0 + return; + } + + updateInputSelectFlags(); + + if (mService.clockAlignedDataIntervalInt->getInt() >= 1 && clock.now().isUnixTime()) { + + bool shouldTakeMeasurement = false; + bool shouldUpdateLastTime = false; + + auto& timestampNow = clock.now(); + int32_t dt; + bool dt_defined = clock.delta(timestampNow, lastAlignedTime, dt); + if (!dt_defined) { + //lastAlignedTime not set yet. Do that now + shouldUpdateLastTime = true; + MO_DBG_DEBUG("Clock aligned measurement: initial setting"); + } else if (dt < 0) { + //timestampNow before lastAligned time - probably the clock has been adjusted + shouldUpdateLastTime = true; + MO_DBG_DEBUG("Clock aligned measurement: correct time after clock adjustment (%i)", (int)dt); + } else if (dt < mService.clockAlignedDataIntervalInt->getInt()) { + //dt is number of seconds elapsed since last measurements and seconds < interval, so do nothing + (void)0; + } else if (dt <= mService.clockAlignedDataIntervalInt->getInt() + 60) { + //elapsed seconds >= interval and still within tolerance band of 60 seconds, take measurement! + shouldTakeMeasurement = true; + shouldUpdateLastTime = true; + MO_DBG_DEBUG("Clock aligned measurement: take measurement (%i)", (int)dt); + } else { + //elapsed seconds is past the tolerance band. Measurement wouldn't be clock-aligned anymore, so skip + shouldUpdateLastTime = true; + MO_DBG_DEBUG("Clock aligned measurement: tolerance exceeded, skip measurement (%i)", (int)dt); + } + + if (shouldTakeMeasurement) { + + if (auto alignedMeterValue = takeMeterValue(MO_ReadingContext_SampleClock, MO_FLAG_MeterValuesAlignedData)) { + if (meterData.size() >= MO_MVRECORD_SIZE) { + MO_DBG_INFO("MeterValue cache full. Drop old MV"); + auto mv = meterData.begin(); + delete *mv; + meterData.erase(mv); } + alignedMeterValue->opNr = context.getMessageService().getNextOpNr(); + if (transaction) { + alignedMeterValue->txNr = transaction->getTxNr(); + } + meterData.push_back(alignedMeterValue); + } - if (stopTxnData) { - auto alignedStopTx = stopTxnAlignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock); - if (alignedStopTx) { - stopTxnData->addTxData(std::move(alignedStopTx)); - } + if (transaction) { + if (auto alignedStopTx = takeMeterValue(MO_ReadingContext_SampleClock, MO_FLAG_StopTxnAlignedData)) { + addTxMeterData(*transaction, alignedStopTx); } } + } - Timestamp midnightBase = Timestamp(2010,0,0,0,0,0); - auto intervall = timestampNow - midnightBase; - intervall %= 3600 * 24; - Timestamp midnight = timestampNow - intervall; - intervall += clockAlignedDataIntervalInt->getInt(); - if (intervall >= 3600 * 24) { - //next measurement is tomorrow; set to precisely 00:00 - nextAlignedTime = midnight; - nextAlignedTime += 3600 * 24; + if (shouldUpdateLastTime) { + Timestamp midnightBase; + clock.parseString("2010-01-01T00:00:00Z", midnightBase); + int32_t dt; + clock.delta(timestampNow, midnightBase, dt); //dt is the number of seconds since Jan 1 2010 + dt %= (int32_t)(3600 * 24); //dt is the number of seconds since midnight + Timestamp midnight = timestampNow; + clock.add(midnight, -dt); //midnight is last midnight (i.e. today at 00:00) + if (dt < mService.clockAlignedDataIntervalInt->getInt()) { + //close to midnight, so align to midnight + lastAlignedTime = midnight; } else { - intervall /= clockAlignedDataIntervalInt->getInt(); - nextAlignedTime = midnight + (intervall * clockAlignedDataIntervalInt->getInt()); + //at least one period since midnight has elapsed, so align to last period + dt /= mService.clockAlignedDataIntervalInt->getInt(); //floor dt to the last full period + dt *= mService.clockAlignedDataIntervalInt->getInt(); + lastAlignedTime = midnight; + clock.add(lastAlignedTime, dt); } } } - if (meterValueSampleIntervalInt->getInt() >= 1) { + if (mService.meterValueSampleIntervalInt->getInt() >= 1) { //record periodic tx data - if (mocpp_tick_ms() - lastSampleTime >= (unsigned long) (meterValueSampleIntervalInt->getInt() * 1000)) { - if (auto sampledMeterValue = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic)) { - if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) { - MO_DBG_INFO("MeterValue cache full. Drop old MV"); - meterData.erase(meterData.begin()); + auto uptime = clock.getUptime(); + int32_t dt; + clock.delta(uptime, lastSampleTime, dt); + + if (dt >= mService.meterValueSampleIntervalInt->getInt()) { + if (auto sampledMeterValue = takeMeterValue(MO_ReadingContext_SamplePeriodic, MO_FLAG_MeterValuesSampledData)) { + if (meterData.size() >= MO_MVRECORD_SIZE) { + auto mv = meterData.begin(); + delete *mv; + meterData.erase(mv); } - sampledMeterValue->setOpNr(context.getRequestQueue().getNextOpNr()); + sampledMeterValue->opNr = context.getMessageService().getNextOpNr(); if (transaction) { - sampledMeterValue->setTxNr(transaction->getTxNr()); + sampledMeterValue->txNr = transaction->getTxNr(); } - meterData.push_back(std::move(sampledMeterValue)); + meterData.push_back(sampledMeterValue); } - if (stopTxnData && stopTxnDataCapturePeriodicBool->getBool()) { - auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic); - if (sampleStopTx) { - stopTxnData->addTxData(std::move(sampleStopTx)); + if (transaction && mService.stopTxnDataCapturePeriodicBool->getBool()) { + if (auto sampleStopTx = takeMeterValue(MO_ReadingContext_SamplePeriodic, MO_FLAG_StopTxnSampledData)) { + addTxMeterData(*transaction, sampleStopTx); } } - lastSampleTime = mocpp_tick_ms(); + + lastSampleTime = uptime; } } } -std::unique_ptr MeteringServiceEvse::takeTriggeredMeterValues() { - - auto sample = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_Trigger); +Operation *Ocpp16::MeteringServiceEvse::createTriggeredMeterValues() { - if (!sample) { + auto meterValue = takeMeterValue(MO_ReadingContext_Trigger, MO_FLAG_MeterValuesSampledData); + if (!meterValue) { + MO_DBG_ERR("OOM"); return nullptr; } - std::shared_ptr transaction = nullptr; - if (model.getConnector(connectorId)) { - transaction = model.getConnector(connectorId)->getTransaction(); + int transactionId = -1; + if (auto tx = txSvcEvse->getTransaction()) { + transactionId = tx->getTransactionId(); } - auto msg = std::unique_ptr(new MeterValues(model, std::move(sample), connectorId, transaction)); - auto meterValues = makeRequest(std::move(msg)); - meterValues->setTimeout(120000); - return meterValues; + auto operation = new MeterValues(context, connectorId, transactionId, meterValue, /*transferOwnership*/ true); + if (!operation) { + MO_DBG_ERR("OOM"); + delete meterValue; + return nullptr; + } + + return operation; } -void MeteringServiceEvse::addMeterValueSampler(std::unique_ptr meterValueSampler) { - if (!strcmp(meterValueSampler->getProperties().getMeasurand(), "Energy.Active.Import.Register")) { - energySamplerIndex = samplers.size(); +int32_t Ocpp16::MeteringServiceEvse::readTxEnergyMeter(MO_ReadingContext readingContext) { + if (txEnergyMeterInput) { + return txEnergyMeterInput(); + } else if (txEnergyMeterInput2) { + return txEnergyMeterInput2(readingContext, connectorId, txEnergyMeterInput2_user_data); + } else { + return 0; } - samplers.push_back(std::move(meterValueSampler)); } -std::unique_ptr MeteringServiceEvse::readTxEnergyMeter(ReadingContext model) { - if (energySamplerIndex >= 0 && (size_t) energySamplerIndex < samplers.size()) { - return samplers[energySamplerIndex]->takeValue(model); +bool Ocpp16::MeteringServiceEvse::addTxMeterData(Transaction& transaction, MeterValue *mv) { + + auto& txMeterValues = transaction.getTxMeterValues(); + + unsigned int mvIndex; + bool replaceEntry = false; + if (txMeterValues.size() >= MO_STOPTXDATA_MAX_SIZE) { + //replace last entry + mvIndex = txMeterValues.size() - 1; + replaceEntry = true; } else { - MO_DBG_DEBUG("Called readTxEnergyMeter(), but no energySampler or handling strategy set"); - return nullptr; + //append + mvIndex = txMeterValues.size(); } -} -void MeteringServiceEvse::beginTxMeterData(Transaction *transaction) { - if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { - stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); + size_t capacity = mvIndex + 1; + txMeterValues.resize(capacity); + if (txMeterValues.size() < capacity) { + MO_DBG_ERR("OOM"); + goto fail; } - if (stopTxnData) { - auto sampleTxBegin = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionBegin); - if (sampleTxBegin) { - stopTxnData->addTxData(std::move(sampleTxBegin)); + if (filesystem) { + auto ret = MeterStore::store(filesystem, context, connectorId, transaction.getTxNr(), mvIndex, *mv); + if (ret != FilesystemUtils::StoreStatus::Success) { + MO_DBG_ERR("fs failure"); + goto fail; } } + + if (replaceEntry) { + delete txMeterValues.back(); + txMeterValues.back() = mv; + MO_DBG_DEBUG("updated latest sd"); + } else { + txMeterValues.emplace_back(mv); + MO_DBG_DEBUG("added sd"); + } + return true; +fail: + delete mv; + return false; } -std::shared_ptr MeteringServiceEvse::endTxMeterData(Transaction *transaction) { - if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) { - stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); +bool Ocpp16::MeteringServiceEvse::beginTxMeterData(Transaction *transaction) { + + auto& txMeterValues = transaction->getTxMeterValues(); + + if (!txMeterValues.empty()) { + //first MV already taken + return true; } - if (stopTxnData) { - auto sampleTxEnd = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionEnd); - if (sampleTxEnd) { - stopTxnData->addTxData(std::move(sampleTxEnd)); - } + auto sampleTxBegin = takeMeterValue(MO_ReadingContext_TransactionBegin, MO_FLAG_StopTxnSampledData); + if (!sampleTxBegin) { + return true; } - return std::move(stopTxnData); + return addTxMeterData(*transaction, sampleTxBegin); } -void MeteringServiceEvse::abortTxMeterData() { - stopTxnData.reset(); -} +bool Ocpp16::MeteringServiceEvse::endTxMeterData(Transaction *transaction) { -std::shared_ptr MeteringServiceEvse::getStopTxMeterData(Transaction *transaction) { - auto txData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction); + auto& txMeterValues = transaction->getTxMeterValues(); - if (!txData) { - MO_DBG_ERR("could not create TxData"); - return nullptr; + if (!txMeterValues.empty() && txMeterValues.back()->readingContext == MO_ReadingContext_TransactionEnd) { + //final MV already taken + return true; } - return txData; -} + auto sampleTxEnd = takeMeterValue(MO_ReadingContext_TransactionEnd, MO_FLAG_StopTxnSampledData); + if (!sampleTxEnd) { + return true; + } -bool MeteringServiceEvse::removeTxMeterData(unsigned int txNr) { - return meterStore.remove(connectorId, txNr); + return addTxMeterData(*transaction, sampleTxEnd); } -bool MeteringServiceEvse::existsSampler(const char *measurand, size_t len) { - for (size_t i = 0; i < samplers.size(); i++) { - if (strlen(samplers[i]->getProperties().getMeasurand()) == len && - !strncmp(measurand, samplers[i]->getProperties().getMeasurand(), len)) { - return true; - } +void Ocpp16::MeteringServiceEvse::updateInputSelectFlags() { + uint16_t selectInputsWriteCount = 0; + selectInputsWriteCount += mService.meterValuesSampledDataString->getWriteCount(); + selectInputsWriteCount += mService.stopTxnSampledDataString->getWriteCount(); + selectInputsWriteCount += mService.meterValuesAlignedDataString->getWriteCount(); + selectInputsWriteCount += mService.stopTxnAlignedDataString->getWriteCount(); + selectInputsWriteCount += (uint16_t)meterInputs.size(); //also invalidate if new meterInputs were added + if (selectInputsWriteCount != trackSelectInputs) { + MO_DBG_DEBUG("Updating observed samplers after config change or meterInputs added"); + updateInputSelectFlag(mService.meterValuesSampledDataString->getString(), MO_FLAG_MeterValuesSampledData, meterInputs); + updateInputSelectFlag(mService.stopTxnSampledDataString->getString(), MO_FLAG_StopTxnSampledData, meterInputs); + updateInputSelectFlag(mService.meterValuesAlignedDataString->getString(), MO_FLAG_MeterValuesAlignedData, meterInputs); + updateInputSelectFlag(mService.stopTxnAlignedDataString->getString(), MO_FLAG_StopTxnAlignedData, meterInputs); + trackSelectInputs = selectInputsWriteCount; } - - return false; } -unsigned int MeteringServiceEvse::getFrontRequestOpNr() { +unsigned int Ocpp16::MeteringServiceEvse::getFrontRequestOpNr() { if (!meterDataFront && !meterData.empty()) { MO_DBG_DEBUG("advance MV front"); - meterDataFront = std::move(meterData.front()); + meterDataFront = meterData.front(); //transfer ownership meterData.erase(meterData.begin()); } if (meterDataFront) { - return meterDataFront->getOpNr(); + return meterDataFront->opNr; } return NoOperation; } -std::unique_ptr MeteringServiceEvse::fetchFrontRequest() { +std::unique_ptr Ocpp16::MeteringServiceEvse::fetchFrontRequest() { + + if (!mService.connection->isConnected()) { + //offline behavior: pause sending messages and do not increment attempt counters + return nullptr; + } if (!meterDataFront) { return nullptr; } - if ((int)meterDataFront->getAttemptNr() >= transactionMessageAttemptsInt->getInt()) { + if (meterValuesInProgress) { + //ensure that only one MeterValues request is being executed at the same time + return nullptr; + } + + if ((int)meterDataFront->attemptNr >= mService.transactionMessageAttemptsInt->getInt()) { MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard MeterValue"); - meterDataFront.reset(); + delete meterDataFront; + meterDataFront = nullptr; return nullptr; } - if (mocpp_tick_ms() - meterDataFront->getAttemptTime() < meterDataFront->getAttemptNr() * (unsigned long)(std::max(0, transactionMessageRetryIntervalInt->getInt())) * 1000UL) { + int32_t dtLastAttempt; + if (!clock.delta(clock.getUptime(), meterDataFront->attemptTime, dtLastAttempt)) { + MO_DBG_ERR("internal error"); + dtLastAttempt = 0; + } + + if (dtLastAttempt < meterDataFront->attemptNr * std::max(0, mService.transactionMessageRetryIntervalInt->getInt())) { return nullptr; } - meterDataFront->advanceAttemptNr(); - meterDataFront->setAttemptTime(mocpp_tick_ms()); + meterDataFront->attemptNr++; + meterDataFront->attemptTime = clock.getUptime(); - //fetch tx for meterValue - std::shared_ptr tx; - if (meterDataFront->getTxNr() >= 0) { - tx = model.getTransactionStore()->getTransaction(connectorId, meterDataFront->getTxNr()); + auto transaction = txSvcEvse->getTransactionFront(); + if (transaction && transaction->getTxNr() != meterDataFront->txNr) { + MO_DBG_ERR("txNr mismatch"); //this could happen if tx-related MeterValues are sent shortly before StartTx or shortly after StopTx, or if txNr is mis-assigned during creation + transaction = nullptr; } //discard MV if it belongs to silent tx - if (tx && tx->isSilent()) { + if (transaction && transaction->isSilent()) { MO_DBG_DEBUG("Drop MeterValue belonging to silent tx"); - meterDataFront.reset(); + delete meterDataFront; + meterDataFront = nullptr; return nullptr; } - auto meterValues = makeRequest(new MeterValues(model, meterDataFront.get(), connectorId, tx)); - meterValues->setOnReceiveConfListener([this] (JsonObject) { + int transactionId = -1; + if (transaction) { + transactionId = transaction->getTransactionId(); + } + + auto meterValues = makeRequest(context, new MeterValues(context, connectorId, transactionId, meterDataFront, /*transferOwnership*/ false)); + meterValues->setOnReceiveConf([this] (JsonObject) { + meterValuesInProgress = false; + //operation success MO_DBG_DEBUG("drop MV front"); - meterDataFront.reset(); + delete meterDataFront; + meterDataFront = nullptr; + }); + meterValues->setOnAbort([this] () { + meterValuesInProgress = false; }); + meterValuesInProgress = true; + return meterValues; } -MeteringService::MeteringService(Context& context, int numConn, std::shared_ptr filesystem) - : MemoryManaged("v16.Metering.MeteringService"), context(context), meterStore(filesystem), connectors(makeVector>(getMemoryTag())) { +Ocpp16::MeteringService::MeteringService(Context& context) + : MemoryManaged("v16.Metering.MeteringService"), context(context) { - //set factory defaults for Metering-related config keys - declareConfiguration("MeterValuesSampledData", "Energy.Active.Import.Register,Power.Active.Import"); - declareConfiguration("StopTxnSampledData", ""); - declareConfiguration("MeterValuesAlignedData", "Energy.Active.Import.Register,Power.Active.Import"); - declareConfiguration("StopTxnAlignedData", ""); - - connectors.reserve(numConn); - for (int i = 0; i < numConn; i++) { - connectors.emplace_back(new MeteringServiceEvse(context, i, meterStore)); +} + +Ocpp16::MeteringService::~MeteringService() { + for (unsigned int i = 0; i < MO_NUM_EVSEID; i++) { + delete evses[i]; + evses[i] = nullptr; } +} - std::function validateSelectString = [this] (const char *csl) { - bool isValid = true; - const char *l = csl; //the beginning of an entry of the comma-separated list - const char *r = l; //one place after the last character of the entry beginning with l - while (*l) { - if (*l == ',') { - l++; - continue; - } - r = l + 1; - while (*r != '\0' && *r != ',') { - r++; - } - bool found = false; - for (size_t cId = 0; cId < connectors.size(); cId++) { - if (connectors[cId]->existsSampler(l, (size_t) (r - l))) { - found = true; - break; - } - } - if (!found) { - isValid = false; - MO_DBG_WARN("could not find metering device for %.*s", (int) (r - l), l); - break; - } - l = r; +bool Ocpp16::MeteringService::setup() { + + connection = context.getConnection(); + if (!connection) { + MO_DBG_ERR("setup failure"); + return false; + } + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + meterValuesSampledDataString = configService->declareConfiguration("MeterValuesSampledData", "Energy.Active.Import.Register,Power.Active.Import"); + stopTxnSampledDataString = configService->declareConfiguration("StopTxnSampledData", ""); + meterValueSampleIntervalInt = configService->declareConfiguration("MeterValueSampleInterval", 60); + + meterValuesAlignedDataString = configService->declareConfiguration("MeterValuesAlignedData", ""); + stopTxnAlignedDataString = configService->declareConfiguration("StopTxnAlignedData", ""); + clockAlignedDataIntervalInt = configService->declareConfiguration("ClockAlignedDataInterval", 0); + + meterValuesInTxOnlyBool = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "MeterValuesInTxOnly", true); + stopTxnDataCapturePeriodicBool = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "StopTxnDataCapturePeriodic", false); + + transactionMessageAttemptsInt = configService->declareConfiguration("TransactionMessageAttempts", 3); + transactionMessageRetryIntervalInt = configService->declareConfiguration("TransactionMessageRetryInterval", 60); + + if (!meterValuesSampledDataString || + !stopTxnSampledDataString || + !meterValueSampleIntervalInt || + !meterValuesAlignedDataString || + !stopTxnAlignedDataString || + !clockAlignedDataIntervalInt || + !meterValuesInTxOnlyBool || + !stopTxnDataCapturePeriodicBool || + !transactionMessageAttemptsInt || + !transactionMessageRetryIntervalInt) { + MO_DBG_ERR("declareConfiguration failed"); + return false; + } + + configService->registerValidator("MeterValuesSampledData", validateSelectString, reinterpret_cast(&context)); + configService->registerValidator("StopTxnSampledData", validateSelectString, reinterpret_cast(&context)); + configService->registerValidator("MeterValueSampleInterval", VALIDATE_UNSIGNED_INT); + + configService->registerValidator("MeterValuesAlignedData", validateSelectString, reinterpret_cast(&context)); + configService->registerValidator("StopTxnAlignedData", validateSelectString, reinterpret_cast(&context)); + configService->registerValidator("ClockAlignedDataInterval", VALIDATE_UNSIGNED_INT); + + auto rcService = context.getModel16().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("MeterValues", [] (Context& context, unsigned int evseId) { + auto mvSvc = context.getModel16().getMeteringService(); + auto mvSvcEvse = mvSvc ? mvSvc->getEvse(evseId) : nullptr; + return mvSvcEvse ? mvSvcEvse->createTriggeredMeterValues() : nullptr; + }); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("MeterValues", nullptr, nullptr); + #endif //MO_ENABLE_MOCK_SERVER + + numEvseId = context.getModel16().getNumEvseId(); + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i) || !getEvse(i)->setup()) { + MO_DBG_ERR("connector init failure"); + return false; } - return isValid; - }; + } + + return true; +} + +void Ocpp16::MeteringService::loop(){ + for (unsigned int i = 0; i < numEvseId; i++){ + evses[i]->loop(); + } +} + +Ocpp16::MeteringServiceEvse *Ocpp16::MeteringService::getEvse(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); + return nullptr; + } + + if (!evses[evseId]) { + evses[evseId] = new MeteringServiceEvse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + + return evses[evseId]; +} + +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 + +#define MO_FLAG_SampledDataTxStarted (1 << 0) +#define MO_FLAG_SampledDataTxUpdated (1 << 1) +#define MO_FLAG_SampledDataTxEnded (1 << 2) +#define MO_FLAG_AlignedData (1 << 3) + +using namespace MicroOcpp; + +Ocpp201::MeteringServiceEvse::MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int evseId) + : MemoryManaged("v201.MeterValues.MeteringServiceEvse"), context(context), mService(mService), evseId(evseId), meterInputs(makeVector(getMemoryTag())) { + +} + +bool Ocpp201::MeteringServiceEvse::setup() { + return true; //nothing to be done here +} + +bool Ocpp201::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { + auto capacity = meterInputs.size() + 1; + meterInputs.resize(capacity); + if (meterInputs.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return false; + } + + meterInput.mo_flags = 0; + meterInputs.push_back(meterInput); + return true; +} + +Vector& Ocpp201::MeteringServiceEvse::getMeterInputs() { + return meterInputs; +} + +void Ocpp201::MeteringServiceEvse::updateInputSelectFlags() { + uint16_t selectInputsWriteCount = 0; + selectInputsWriteCount += mService.sampledDataTxStartedMeasurands->getWriteCount(); + selectInputsWriteCount += mService.sampledDataTxUpdatedMeasurands->getWriteCount(); + selectInputsWriteCount += mService.sampledDataTxEndedMeasurands->getWriteCount(); + selectInputsWriteCount += mService.alignedDataMeasurands->getWriteCount(); + selectInputsWriteCount += (uint16_t)meterInputs.size(); //also invalidate if new meterInputs were added + if (selectInputsWriteCount != trackSelectInputs) { + MO_DBG_DEBUG("Updating observed samplers after variables change or meterInputs added"); + updateInputSelectFlag(mService.sampledDataTxStartedMeasurands->getString(), MO_FLAG_SampledDataTxStarted, meterInputs); + updateInputSelectFlag(mService.sampledDataTxUpdatedMeasurands->getString(), MO_FLAG_SampledDataTxUpdated, meterInputs); + updateInputSelectFlag(mService.sampledDataTxEndedMeasurands->getString(), MO_FLAG_SampledDataTxEnded, meterInputs); + updateInputSelectFlag(mService.alignedDataMeasurands->getString(), MO_FLAG_AlignedData, meterInputs); + trackSelectInputs = selectInputsWriteCount; + } +} + +std::unique_ptr Ocpp201::MeteringServiceEvse::takeTxStartedMeterValue(MO_ReadingContext readingContext) { + updateInputSelectFlags(); + return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, readingContext, MO_FLAG_SampledDataTxStarted, getMemoryTag())); +} +std::unique_ptr Ocpp201::MeteringServiceEvse::takeTxUpdatedMeterValue(MO_ReadingContext readingContext) { + updateInputSelectFlags(); + return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, readingContext, MO_FLAG_SampledDataTxUpdated, getMemoryTag())); +} +std::unique_ptr Ocpp201::MeteringServiceEvse::takeTxEndedMeterValue(MO_ReadingContext readingContext) { + updateInputSelectFlags(); + return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, readingContext, MO_FLAG_SampledDataTxEnded, getMemoryTag())); +} +std::unique_ptr Ocpp201::MeteringServiceEvse::takeTriggeredMeterValues() { + updateInputSelectFlags(); + return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, MO_ReadingContext_Trigger, MO_FLAG_AlignedData, getMemoryTag())); +} + +Ocpp201::MeteringService::MeteringService(Context& context) : MemoryManaged("v16.Metering.MeteringService"), context(context) { + +} + +Ocpp201::MeteringService::~MeteringService() { + for (size_t i = 0; i < MO_NUM_EVSEID; i++) { + delete evses[i]; + evses[i] = nullptr; + } +} + +Ocpp201::MeteringServiceEvse *Ocpp201::MeteringService::getEvse(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); + return nullptr; + } - registerConfigurationValidator("MeterValuesSampledData", validateSelectString); - registerConfigurationValidator("StopTxnSampledData", validateSelectString); - registerConfigurationValidator("MeterValuesAlignedData", validateSelectString); - registerConfigurationValidator("StopTxnAlignedData", validateSelectString); - registerConfigurationValidator("MeterValueSampleInterval", VALIDATE_UNSIGNED_INT); - registerConfigurationValidator("ClockAlignedDataInterval", VALIDATE_UNSIGNED_INT); + if (!evses[evseId]) { + evses[evseId] = new MeteringServiceEvse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } - /* - * Register further message handlers to support echo mode: when this library - * is connected with a WebSocket echo server, let it reply to its own requests. - * Mocking an OCPP Server on the same device makes running (unit) tests easier. - */ - context.getOperationRegistry().registerOperation("MeterValues", [this] () { - return new Ocpp16::MeterValues(this->context.getModel());}); + return evses[evseId]; } -void MeteringService::loop(){ - for (unsigned int i = 0; i < connectors.size(); i++){ - connectors[i]->loop(); +bool Ocpp201::MeteringService::setup() { + + auto varService = context.getModel201().getVariableService(); + if (!varService) { + return false; } + + sampledDataTxStartedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxStartedMeasurands", ""); + sampledDataTxUpdatedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxUpdatedMeasurands", ""); + sampledDataTxEndedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", ""); + alignedDataMeasurands = varService->declareVariable("AlignedDataCtrlr", "AlignedDataMeasurands", ""); + + if (!sampledDataTxStartedMeasurands || + !sampledDataTxUpdatedMeasurands || + !sampledDataTxEndedMeasurands || + !alignedDataMeasurands) { + MO_DBG_ERR("failure to declare variable"); + return false; + } + + //define factory defaults + varService->declareVariable("SampledDataCtrlr", "TxStartedMeasurands", ""); + varService->declareVariable("SampledDataCtrlr", "TxUpdatedMeasurands", ""); + varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", ""); + varService->declareVariable("AlignedDataCtrlr", "AlignedDataMeasurands", ""); + + varService->registerValidator("SampledDataCtrlr", "TxStartedMeasurands", validateSelectString, reinterpret_cast(&context)); + varService->registerValidator("SampledDataCtrlr", "TxUpdatedMeasurands", validateSelectString, reinterpret_cast(&context)); + varService->registerValidator("SampledDataCtrlr", "TxEndedMeasurands", validateSelectString, reinterpret_cast(&context)); + varService->registerValidator("AlignedDataCtrlr", "AlignedDataMeasurands", validateSelectString, reinterpret_cast(&context)); + + numEvseId = context.getModel201().getNumEvseId(); + for (size_t i = 0; i < numEvseId; i++) { + if (!getEvse(i) || !getEvse(i)->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } + } + + return true; } + +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Metering/MeteringService.h b/src/MicroOcpp/Model/Metering/MeteringService.h index 50ddc8ef..8a970b91 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.h +++ b/src/MicroOcpp/Model/Metering/MeteringService.h @@ -8,100 +8,192 @@ #include #include -#include -#include #include -#include -#include +#include #include +#include #include +#include +#include + +#if MO_ENABLE_V16 + +#ifndef MO_MVRECORD_SIZE +#define MO_MVRECORD_SIZE 10 +#endif namespace MicroOcpp { class Context; -class Model; +class Clock; class Operation; class Request; -class FilesystemAdapter; -class MeteringServiceEvse : public MemoryManaged, public RequestEmitter { +namespace Ocpp16 { + +class Model; +class MeteringService; +class Configuration; +class TransactionServiceEvse; +class Transaction; + +class MeteringServiceEvse : public MemoryManaged, public RequestQueue { private: Context& context; + Clock& clock; Model& model; - const int connectorId; - MeterStore& meterStore; - - Vector> meterData; - std::unique_ptr meterDataFront; - std::shared_ptr stopTxnData; - - std::unique_ptr sampledDataBuilder; - std::unique_ptr alignedDataBuilder; - std::unique_ptr stopTxnSampledDataBuilder; - std::unique_ptr stopTxnAlignedDataBuilder; - - std::shared_ptr sampledDataSelectString; - std::shared_ptr alignedDataSelectString; - std::shared_ptr stopTxnSampledDataSelectString; - std::shared_ptr stopTxnAlignedDataSelectString; - - unsigned long lastSampleTime = 0; //0 means not charging right now - Timestamp nextAlignedTime; - std::shared_ptr transaction; + MeteringService& mService; + const unsigned int connectorId; + MO_FilesystemAdapter *filesystem = nullptr; + TransactionServiceEvse *txSvcEvse = nullptr; + + Vector meterData; //has ownership + MeterValue *meterDataFront = nullptr; //has ownership + bool meterValuesInProgress = false; + + Vector meterInputs; + int32_t (*txEnergyMeterInput)() = nullptr; + int32_t (*txEnergyMeterInput2)(MO_ReadingContext readingContext, unsigned int evseId, void *user_data) = nullptr; + void *txEnergyMeterInput2_user_data = nullptr; + + MeterValue *takeMeterValue(MO_ReadingContext readingContext, uint8_t inputSelectFlag); + + Timestamp lastSampleTime; + Timestamp lastAlignedTime; bool trackTxRunning = false; - - Vector> samplers; - int energySamplerIndex {-1}; - std::shared_ptr meterValueSampleIntervalInt; + bool addTxMeterData(Transaction& transaction, MeterValue *mv); + + uint16_t trackSelectInputs = -1; + void updateInputSelectFlags(); - std::shared_ptr clockAlignedDataIntervalInt; +public: + MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int connectorId); + ~MeteringServiceEvse(); - std::shared_ptr meterValuesInTxOnlyBool; - std::shared_ptr stopTxnDataCapturePeriodicBool; + bool addMeterInput(MO_MeterInput meterInput); + Vector& getMeterInputs(); + + /* Specify energy meter for Start- and StopTx (if not used, MO will select general-purpose energy meter) + * `setTxEnergyMeterInput(cb)`: set simple callback without args + * `setTxEnergyMeterInput2(cb)`: set complex callback. When MO executes the callback, it passes the ReadingContext (i.e. if measurement is + * taken at the start, middle or stop of a transactinon), the evseId and the user_data pointer which the caller can freely select */ + bool setTxEnergyMeterInput(int32_t (*getInt)()); + bool setTxEnergyMeterInput2(int32_t (*getInt2)(MO_ReadingContext readingContext, unsigned int evseId, void *user_data), void *user_data = nullptr); + + bool setup(); + + void loop(); + + int32_t readTxEnergyMeter(MO_ReadingContext readingContext); + + Operation *createTriggeredMeterValues(); + + bool beginTxMeterData(Transaction *transaction); + + bool endTxMeterData(Transaction *transaction); + + //RequestQueue implementation + unsigned int getFrontRequestOpNr() override; + std::unique_ptr fetchFrontRequest() override; +}; - std::shared_ptr transactionMessageAttemptsInt; - std::shared_ptr transactionMessageRetryIntervalInt; +class MeteringService : public MemoryManaged { +private: + Context& context; + Connection *connection = nullptr; + + MeteringServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; + + Configuration *meterValuesSampledDataString = nullptr; + Configuration *stopTxnSampledDataString = nullptr; + Configuration *meterValueSampleIntervalInt = nullptr; + + Configuration *meterValuesAlignedDataString = nullptr; + Configuration *stopTxnAlignedDataString = nullptr; + Configuration *clockAlignedDataIntervalInt = nullptr; + + Configuration *meterValuesInTxOnlyBool = nullptr; + Configuration *stopTxnDataCapturePeriodicBool = nullptr; + + Configuration *transactionMessageAttemptsInt = nullptr; + Configuration *transactionMessageRetryIntervalInt = nullptr; public: - MeteringServiceEvse(Context& context, int connectorId, MeterStore& meterStore); + MeteringService(Context& context); + ~MeteringService(); + + bool setup(); void loop(); - void addMeterValueSampler(std::unique_ptr meterValueSampler); + MeteringServiceEvse *getEvse(unsigned int evseId); + +friend class MeteringServiceEvse; +}; + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 - std::unique_ptr readTxEnergyMeter(ReadingContext model); +#if MO_ENABLE_V201 - std::unique_ptr takeTriggeredMeterValues(); +namespace MicroOcpp { - void beginTxMeterData(Transaction *transaction); +class Context; - std::shared_ptr endTxMeterData(Transaction *transaction); +namespace Ocpp201 { - void abortTxMeterData(); +class MeteringService; +class Variable; - std::shared_ptr getStopTxMeterData(Transaction *transaction); +class MeteringServiceEvse : public MemoryManaged { +private: + Context& context; + MeteringService& mService; + const unsigned int evseId; + + Vector meterInputs; - bool removeTxMeterData(unsigned int txNr); + uint16_t trackSelectInputs = -1; + void updateInputSelectFlags(); +public: + MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int evseId); - bool existsSampler(const char *measurand, size_t len); + bool addMeterInput(MO_MeterInput meterInput); + Vector& getMeterInputs(); - //RequestEmitter implementation - unsigned int getFrontRequestOpNr() override; - std::unique_ptr fetchFrontRequest() override; + bool setup(); + std::unique_ptr takeTxStartedMeterValue(MO_ReadingContext context = MO_ReadingContext_TransactionBegin); + std::unique_ptr takeTxUpdatedMeterValue(MO_ReadingContext context = MO_ReadingContext_SamplePeriodic); + std::unique_ptr takeTxEndedMeterValue(MO_ReadingContext context); + std::unique_ptr takeTriggeredMeterValues(); }; class MeteringService : public MemoryManaged { private: Context& context; - MeterStore meterStore; + MeteringServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; + + Variable *sampledDataTxStartedMeasurands = nullptr; + Variable *sampledDataTxUpdatedMeasurands = nullptr; + Variable *sampledDataTxEndedMeasurands = nullptr; + Variable *alignedDataMeasurands = nullptr; public: - MeteringService(Context& context, int numConnectors, std::shared_ptr filesystem); + MeteringService(Context& context); + ~MeteringService(); + + MeteringServiceEvse *getEvse(unsigned int evseId); bool setup(); - void loop(); +friend class MeteringServiceEvse; }; -} //end namespace MicroOcpp +} +} + +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Metering/MeterValuesV201.cpp b/src/MicroOcpp/Model/Metering/MeteringServiceV201.cpp similarity index 100% rename from src/MicroOcpp/Model/Metering/MeterValuesV201.cpp rename to src/MicroOcpp/Model/Metering/MeteringServiceV201.cpp diff --git a/src/MicroOcpp/Model/Metering/MeterValuesV201.h b/src/MicroOcpp/Model/Metering/MeteringServiceV201.h similarity index 100% rename from src/MicroOcpp/Model/Metering/MeterValuesV201.h rename to src/MicroOcpp/Model/Metering/MeteringServiceV201.h diff --git a/src/MicroOcpp/Model/Metering/ReadingContext.cpp b/src/MicroOcpp/Model/Metering/ReadingContext.cpp index 2ea24886..639f878d 100644 --- a/src/MicroOcpp/Model/Metering/ReadingContext.cpp +++ b/src/MicroOcpp/Model/Metering/ReadingContext.cpp @@ -7,59 +7,62 @@ #include #include +#if MO_ENABLE_V16 || MO_ENABLE_V201 + namespace MicroOcpp { -const char *serializeReadingContext(ReadingContext context) { +const char *serializeReadingContext(MO_ReadingContext context) { switch (context) { - case (ReadingContext_InterruptionBegin): + case (MO_ReadingContext_InterruptionBegin): return "Interruption.Begin"; - case (ReadingContext_InterruptionEnd): + case (MO_ReadingContext_InterruptionEnd): return "Interruption.End"; - case (ReadingContext_Other): + case (MO_ReadingContext_Other): return "Other"; - case (ReadingContext_SampleClock): + case (MO_ReadingContext_SampleClock): return "Sample.Clock"; - case (ReadingContext_SamplePeriodic): + case (MO_ReadingContext_SamplePeriodic): return "Sample.Periodic"; - case (ReadingContext_TransactionBegin): + case (MO_ReadingContext_TransactionBegin): return "Transaction.Begin"; - case (ReadingContext_TransactionEnd): + case (MO_ReadingContext_TransactionEnd): return "Transaction.End"; - case (ReadingContext_Trigger): + case (MO_ReadingContext_Trigger): return "Trigger"; default: - MO_DBG_ERR("ReadingContext not specified"); + MO_DBG_ERR("MO_ReadingContext not specified"); /* fall through */ - case (ReadingContext_UNDEFINED): + case (MO_ReadingContext_UNDEFINED): return ""; } } -ReadingContext deserializeReadingContext(const char *context) { +MO_ReadingContext deserializeReadingContext(const char *context) { if (!context) { MO_DBG_ERR("Invalid argument"); - return ReadingContext_UNDEFINED; + return MO_ReadingContext_UNDEFINED; } if (!strcmp(context, "Sample.Periodic")) { - return ReadingContext_SamplePeriodic; + return MO_ReadingContext_SamplePeriodic; } else if (!strcmp(context, "Sample.Clock")) { - return ReadingContext_SampleClock; + return MO_ReadingContext_SampleClock; } else if (!strcmp(context, "Transaction.Begin")) { - return ReadingContext_TransactionBegin; + return MO_ReadingContext_TransactionBegin; } else if (!strcmp(context, "Transaction.End")) { - return ReadingContext_TransactionEnd; + return MO_ReadingContext_TransactionEnd; } else if (!strcmp(context, "Other")) { - return ReadingContext_Other; + return MO_ReadingContext_Other; } else if (!strcmp(context, "Interruption.Begin")) { - return ReadingContext_InterruptionBegin; + return MO_ReadingContext_InterruptionBegin; } else if (!strcmp(context, "Interruption.End")) { - return ReadingContext_InterruptionEnd; + return MO_ReadingContext_InterruptionEnd; } else if (!strcmp(context, "Trigger")) { - return ReadingContext_Trigger; + return MO_ReadingContext_Trigger; } MO_DBG_ERR("ReadingContext not specified %.10s", context); - return ReadingContext_UNDEFINED; + return MO_ReadingContext_UNDEFINED; } } //namespace MicroOcpp +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Metering/ReadingContext.h b/src/MicroOcpp/Model/Metering/ReadingContext.h index 592914e1..be38a4fe 100644 --- a/src/MicroOcpp/Model/Metering/ReadingContext.h +++ b/src/MicroOcpp/Model/Metering/ReadingContext.h @@ -5,24 +5,30 @@ #ifndef MO_READINGCONTEXT_H #define MO_READINGCONTEXT_H +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 + typedef enum { - ReadingContext_UNDEFINED, - ReadingContext_InterruptionBegin, - ReadingContext_InterruptionEnd, - ReadingContext_Other, - ReadingContext_SampleClock, - ReadingContext_SamplePeriodic, - ReadingContext_TransactionBegin, - ReadingContext_TransactionEnd, - ReadingContext_Trigger -} ReadingContext; + MO_ReadingContext_UNDEFINED, + MO_ReadingContext_InterruptionBegin, + MO_ReadingContext_InterruptionEnd, + MO_ReadingContext_Other, + MO_ReadingContext_SampleClock, + MO_ReadingContext_SamplePeriodic, + MO_ReadingContext_TransactionBegin, + MO_ReadingContext_TransactionEnd, + MO_ReadingContext_Trigger +} MO_ReadingContext; #ifdef __cplusplus namespace MicroOcpp { -const char *serializeReadingContext(ReadingContext context); -ReadingContext deserializeReadingContext(const char *serialized); +const char *serializeReadingContext(MO_ReadingContext context); +MO_ReadingContext deserializeReadingContext(const char *serialized); } -#endif +#endif //__cplusplus + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Metering/SampledValue.cpp b/src/MicroOcpp/Model/Metering/SampledValue.cpp deleted file mode 100644 index 4168a048..00000000 --- a/src/MicroOcpp/Model/Metering/SampledValue.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include -#include -#include - -#ifndef MO_SAMPLEDVALUE_FLOAT_FORMAT -#define MO_SAMPLEDVALUE_FLOAT_FORMAT "%.2f" -#endif - -using namespace MicroOcpp; - -int32_t SampledValueDeSerializer::deserialize(const char *str) { - return strtol(str, nullptr, 10); -} - -MicroOcpp::String SampledValueDeSerializer::serialize(int32_t& val) { - char str [12] = {'\0'}; - snprintf(str, 12, "%" PRId32, val); - return makeString("v16.Metering.SampledValueDeSerializer", str); -} - -MicroOcpp::String SampledValueDeSerializer::serialize(float& val) { - char str [20]; - str[0] = '\0'; - snprintf(str, 20, MO_SAMPLEDVALUE_FLOAT_FORMAT, val); - return makeString("v16.Metering.SampledValueDeSerializer", str); -} - -std::unique_ptr SampledValue::toJson() { - auto value = serializeValue(); - if (value.empty()) { - return nullptr; - } - size_t capacity = 0; - capacity += JSON_OBJECT_SIZE(8); - capacity += value.length() + 1; - auto result = makeJsonDoc("v16.Metering.SampledValue", capacity); - auto payload = result->to(); - payload["value"] = value; - auto context_cstr = serializeReadingContext(context); - if (context_cstr) - payload["context"] = context_cstr; - if (*properties.getFormat()) - payload["format"] = properties.getFormat(); - if (*properties.getMeasurand()) - payload["measurand"] = properties.getMeasurand(); - if (*properties.getPhase()) - payload["phase"] = properties.getPhase(); - if (*properties.getLocation()) - payload["location"] = properties.getLocation(); - if (*properties.getUnit()) - payload["unit"] = properties.getUnit(); - return result; -} - -ReadingContext SampledValue::getReadingContext() { - return context; -} diff --git a/src/MicroOcpp/Model/Metering/SampledValue.h b/src/MicroOcpp/Model/Metering/SampledValue.h deleted file mode 100644 index 350990ae..00000000 --- a/src/MicroOcpp/Model/Metering/SampledValue.h +++ /dev/null @@ -1,147 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef SAMPLEDVALUE_H -#define SAMPLEDVALUE_H - -#include -#include -#include - -#include -#include -#include - -namespace MicroOcpp { - -template -class SampledValueDeSerializer { -public: - static T deserialize(const char *str); - static bool ready(T& val); - static String serialize(T& val); - static int32_t toInteger(T& val); -}; - -template <> -class SampledValueDeSerializer { // example class -public: - static int32_t deserialize(const char *str); - static bool ready(int32_t& val) {return true;} //int32_t is always valid - static String serialize(int32_t& val); - static int32_t toInteger(int32_t& val) {return val;} //no conversion required -}; - -template <> -class SampledValueDeSerializer { // Used in meterValues -public: - static float deserialize(const char *str) {return atof(str);} - static bool ready(float& val) {return true;} //float is always valid - static String serialize(float& val); - static int32_t toInteger(float& val) {return (int32_t) val;} -}; - -class SampledValueProperties { -private: - String format; - String measurand; - String phase; - String location; - String unit; - -public: - SampledValueProperties() : - format(makeString("v16.Metering.SampledValueProperties")), - measurand(makeString("v16.Metering.SampledValueProperties")), - phase(makeString("v16.Metering.SampledValueProperties")), - location(makeString("v16.Metering.SampledValueProperties")), - unit(makeString("v16.Metering.SampledValueProperties")) { } - SampledValueProperties(const SampledValueProperties& other) : - format(other.format), - measurand(other.measurand), - phase(other.phase), - location(other.location), - unit(other.unit) { } - ~SampledValueProperties() = default; - - void setFormat(const char *format) {this->format = format;} - const char *getFormat() const {return format.c_str();} - void setMeasurand(const char *measurand) {this->measurand = measurand;} - const char *getMeasurand() const {return measurand.c_str();} - void setPhase(const char *phase) {this->phase = phase;} - const char *getPhase() const {return phase.c_str();} - void setLocation(const char *location) {this->location = location;} - const char *getLocation() const {return location.c_str();} - void setUnit(const char *unit) {this->unit = unit;} - const char *getUnit() const {return unit.c_str();} -}; - -class SampledValue { -protected: - const SampledValueProperties& properties; - const ReadingContext context; - virtual String serializeValue() = 0; -public: - SampledValue(const SampledValueProperties& properties, ReadingContext context) : properties(properties), context(context) { } - SampledValue(const SampledValue& other) : properties(other.properties), context(other.context) { } - virtual ~SampledValue() = default; - - std::unique_ptr toJson(); - - virtual operator bool() = 0; - virtual int32_t toInteger() = 0; - - ReadingContext getReadingContext(); -}; - -template -class SampledValueConcrete : public SampledValue, public MemoryManaged { -private: - T value; -public: - SampledValueConcrete(const SampledValueProperties& properties, ReadingContext context, const T&& value) : SampledValue(properties, context), MemoryManaged("v16.Metering.SampledValueConcrete"), value(value) { } - SampledValueConcrete(const SampledValueConcrete& other) : SampledValue(other), MemoryManaged(other), value(other.value) { } - ~SampledValueConcrete() = default; - - operator bool() override {return DeSerializer::ready(value);} - - String serializeValue() override {return DeSerializer::serialize(value);} - - int32_t toInteger() override { return DeSerializer::toInteger(value);} -}; - -class SampledValueSampler { -protected: - SampledValueProperties properties; -public: - SampledValueSampler(SampledValueProperties properties) : properties(properties) { } - virtual ~SampledValueSampler() = default; - virtual std::unique_ptr takeValue(ReadingContext context) = 0; - virtual std::unique_ptr deserializeValue(JsonObject svJson) = 0; - const SampledValueProperties& getProperties() {return properties;}; -}; - -template -class SampledValueSamplerConcrete : public SampledValueSampler, public MemoryManaged { -private: - std::function sampler; -public: - SampledValueSamplerConcrete(SampledValueProperties properties, std::function sampler) : SampledValueSampler(properties), MemoryManaged("v16.Metering.SampledValueSamplerConcrete"), sampler(sampler) { } - std::unique_ptr takeValue(ReadingContext context) override { - return std::unique_ptr>(new SampledValueConcrete( - properties, - context, - sampler(context))); - } - std::unique_ptr deserializeValue(JsonObject svJson) override { - return std::unique_ptr>(new SampledValueConcrete( - properties, - deserializeReadingContext(svJson["context"] | "NOT_SET"), - DeSerializer::deserialize(svJson["value"] | ""))); - } -}; - -} //end namespace MicroOcpp - -#endif diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index e5b1ae3a..9c0c3725 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -1,14 +1,15 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include +#include +#include #include #include -#include #include -#include +#include #include #include #include @@ -17,24 +18,143 @@ #include #include #include -#include +#include +#include +#include #include #include #include - -#include - +#include +#include +#include #include +#if MO_ENABLE_V16 + using namespace MicroOcpp; -Model::Model(ProtocolVersion version, uint16_t bootNr) : MemoryManaged("Model"), connectors(makeVector>(getMemoryTag())), version(version), bootNr(bootNr) { +Ocpp16::Model::Model(Context& context) : MemoryManaged("v16.Model"), context(context) { + +} + +Ocpp16::Model::~Model() { + delete bootService; + bootService = nullptr; + delete heartbeatService; + heartbeatService = nullptr; + delete configurationService; + configurationService = nullptr; + delete transactionService; + transactionService = nullptr; + delete meteringService; + meteringService = nullptr; + delete resetService; + resetService = nullptr; + delete availabilityService; + availabilityService = nullptr; + delete remoteControlService; + remoteControlService = nullptr; + +#if MO_ENABLE_FIRMWAREMANAGEMENT + delete firmwareService; + firmwareService = nullptr; + delete diagnosticsService; + diagnosticsService = nullptr; +#endif //MO_ENABLE_FIRMWAREMANAGEMENT + +#if MO_ENABLE_LOCAL_AUTH + delete authorizationService; + authorizationService = nullptr; +#endif //MO_ENABLE_LOCAL_AUTH + +#if MO_ENABLE_RESERVATION + delete reservationService; + reservationService = nullptr; +#endif //MO_ENABLE_RESERVATION + +#if MO_ENABLE_SMARTCHARGING + delete smartChargingService; + smartChargingService = nullptr; +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT + delete certService; + certService = nullptr; +#endif //MO_ENABLE_CERT_MGMT +#if MO_ENABLE_SECURITY_EVENT + delete secEventService; + secEventService = nullptr; +#endif //MO_ENABLE_SECURITY_EVENT } -Model::~Model() = default; +void Ocpp16::Model::updateSupportedStandardProfiles() { + + auto supportedFeatureProfilesString = + configurationService->declareConfiguration("SupportedFeatureProfiles", "", MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + + if (!supportedFeatureProfilesString) { + MO_DBG_ERR("OOM"); + return; + } + + auto buf = makeString(getMemoryTag(), supportedFeatureProfilesString->getString()); + + if (transactionService && + availabilityService && + remoteControlService && + heartbeatService && + bootService) { + if (!strstr(supportedFeatureProfilesString->getString(), "Core")) { + if (!buf.empty()) buf += ','; + buf += "Core"; + } + } + + if (firmwareService || + diagnosticsService) { + if (!strstr(supportedFeatureProfilesString->getString(), "FirmwareManagement")) { + if (!buf.empty()) buf += ','; + buf += "FirmwareManagement"; + } + } + +#if MO_ENABLE_LOCAL_AUTH + if (authorizationService) { + if (!strstr(supportedFeatureProfilesString->getString(), "LocalAuthListManagement")) { + if (!buf.empty()) buf += ','; + buf += "LocalAuthListManagement"; + } + } +#endif //MO_ENABLE_LOCAL_AUTH + +#if MO_ENABLE_RESERVATION + if (reservationService) { + if (!strstr(supportedFeatureProfilesString->getString(), "Reservation")) { + if (!buf.empty()) buf += ','; + buf += "Reservation"; + } + } +#endif //MO_ENABLE_RESERVATION + + if (smartChargingService) { + if (!strstr(supportedFeatureProfilesString->getString(), "SmartCharging")) { + if (!buf.empty()) buf += ','; + buf += "SmartCharging"; + } + } -void Model::setNumEvseId(unsigned int numEvseId) { + if (!strstr(supportedFeatureProfilesString->getString(), "RemoteTrigger")) { + if (!buf.empty()) buf += ','; + buf += "RemoteTrigger"; + } + + supportedFeatureProfilesString->setString(buf.c_str()); + + MO_DBG_DEBUG("supported feature profiles: %s", buf.c_str()); +} + +void Ocpp16::Model::setNumEvseId(unsigned int numEvseId) { if (numEvseId >= MO_NUM_EVSEID) { MO_DBG_ERR("invalid arg"); return; @@ -42,126 +162,93 @@ void Model::setNumEvseId(unsigned int numEvseId) { this->numEvseId = numEvseId; } -unsigned int Model::getNumEvseId() { +unsigned int Ocpp16::Model::getNumEvseId() { return numEvseId; } -BootService *Model::getBootService() { +BootService *Ocpp16::Model::getBootService() { if (!bootService) { bootService = new BootService(context); } return bootService; } -HeartbeatService *Model::getHeartbeatService() { +HeartbeatService *Ocpp16::Model::getHeartbeatService() { if (!heartbeatService) { heartbeatService = new HeartbeatService(context); } return heartbeatService; } -ResetService *Model::getResetService() { - if (!resetService) { - resetService = new ResetService(context); - } - return resetService; -} +Ocpp16::ConfigurationService *Ocpp16::Model::getConfigurationService() { + if (!configurationService) { + configurationService = new Ocpp16::ConfigurationService(context); -ConnectorService *Model::getConnectorService() { - if (!connectorService) { - connectorService = new ConnectorService(context); - } - return connectorService; -} - -Connector *Model::getConnector(unsigned int evseId) { - if (evseId >= numEvseId) { - MO_DBG_ERR("connector with evseId %u does not exist", evseId); - return nullptr; - } - - if (!connectors) { - connectors = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(Connector*)); - if (connectors) { - memset(connectors, 0, sizeof(*connectors)); - } else { - return nullptr; //OOM + // need extra init step so that other modules can declare configs before setup() + if (configurationService && !configurationService->init()) { + // configurationService cannot be inited + delete configurationService; + configurationService = nullptr; } } - - if (!connectors[evseId]) { - connectors[evseId] = new Connector(context); - } - return connectors[evseId]; + return configurationService; } -TransactionStoreEvse *Model::getTransactionStoreEvse(unsigned int evseId) { - if (evseId >= numEvseId) { - MO_DBG_ERR("connector with evseId %u does not exist", evseId); - return nullptr; +Ocpp16::TransactionService *Ocpp16::Model::getTransactionService() { + if (!transactionService) { + transactionService = new TransactionService(context); } - - if (!transactionStoreEvse) { - transactionStoreEvse = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(TransactionStoreEvse*)); - if (transactionStoreEvse) { - memset(transactionStoreEvse, 0, sizeof(*transactionStoreEvse)); - } else { - return nullptr; //OOM - } - } - - if (!transactionStoreEvse[evseId]) { - transactionStoreEvse[evseId] = new TransactionStoreEvse(context); - } - return transactionStoreEvse[evseId]; + return transactionService; } -MeteringService* Model::getMeteringService() { +Ocpp16::MeteringService* Ocpp16::Model::getMeteringService() { if (!meteringService) { meteringService = new MeteringService(context); } return meteringService; } -MeteringServiceEvse* Model::getMeteringServiceEvse(unsigned int evseId) { - if (evseId >= numEvseId) { - MO_DBG_ERR("connector with evseId %u does not exist", evseId); - return nullptr; +Ocpp16::ResetService *Ocpp16::Model::getResetService() { + if (!resetService) { + resetService = new ResetService(context); } + return resetService; +} - if (!meteringServiceEvse) { - meteringServiceEvse = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(MeteringServiceEvse*)); - if (meteringServiceEvse) { - memset(meteringServiceEvse, 0, sizeof(*meteringServiceEvse)); - } else { - return nullptr; //OOM - } +Ocpp16::AvailabilityService *Ocpp16::Model::getAvailabilityService() { + if (!availabilityService) { + availabilityService = new Ocpp16::AvailabilityService(context); } + return availabilityService; +} - if (!meteringServiceEvse[evseId]) { - meteringServiceEvse[evseId] = new MeteringServiceEvse(context); +RemoteControlService *Ocpp16::Model::getRemoteControlService() { + if (!remoteControlService) { + remoteControlService = new RemoteControlService(context); } - return meteringServiceEvse[evseId]; + return remoteControlService; } #if MO_ENABLE_FIRMWAREMANAGEMENT -FirmwareService *Model::getFirmwareService() { +Ocpp16::FirmwareService *Ocpp16::Model::getFirmwareService() { if (!firmwareService) { firmwareService = new FirmwareService(context); } return firmwareService; } +#endif //MO_ENABLE_FIRMWAREMANAGEMENT -DiagnosticsService *Model::getDiagnosticsService() { +#if MO_ENABLE_DIAGNOSTICS +DiagnosticsService *Ocpp16::Model::getDiagnosticsService() { if (!diagnosticsService) { diagnosticsService = new DiagnosticsService(context); } return diagnosticsService; } -#endif //MO_ENABLE_FIRMWAREMANAGEMENT +#endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_LOCAL_AUTH -AuthorizationService *Model::getAuthorizationService() { +Ocpp16::AuthorizationService *Ocpp16::Model::getAuthorizationService() { if (!authorizationService) { authorizationService = new AuthorizationService(context); } @@ -170,7 +257,7 @@ AuthorizationService *Model::getAuthorizationService() { #endif //MO_ENABLE_LOCAL_AUTH #if MO_ENABLE_RESERVATION -ReservationService *Model::getReservationService() { +Ocpp16::ReservationService *Ocpp16::Model::getReservationService() { if (!reservationService) { reservationService = new ReservationService(context); } @@ -179,37 +266,16 @@ ReservationService *Model::getReservationService() { #endif //MO_ENABLE_RESERVATION #if MO_ENABLE_SMARTCHARGING -SmartChargingService* Model::getSmartChargingService() { +SmartChargingService* Ocpp16::Model::getSmartChargingService() { if (!smartChargingService) { smartChargingService = new SmartChargingService(context); } return smartChargingService; } - -SmartChargingServiceEvse *Model::getSmartChargingServiceEvse(unsigned int evseId) { - if (evseId < 1 || evseId >= numEvseId) { - MO_DBG_ERR("connector with evseId %u does not exist", evseId); - return nullptr; - } - - if (!smartChargingServiceEvse) { - smartChargingServiceEvse = MO_MALLOC(getMemoryTag(), numEvseId * sizeof(SmartChargingServiceEvse*)); - if (smartChargingServiceEvse) { - memset(smartChargingServiceEvse, 0, sizeof(*smartChargingServiceEvse)); - } else { - return nullptr; //OOM - } - } - - if (!smartChargingServiceEvse[evseId]) { - smartChargingServiceEvse[evseId] = new SmartChargingServiceEvse(context); - } - return smartChargingServiceEvse[evseId]; -} #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT -CertificateService *Model::getCertificateService() { +CertificateService *Ocpp16::Model::getCertificateService() { if (!certService) { certService = new CertificateService(context); } @@ -217,202 +283,403 @@ CertificateService *Model::getCertificateService() { } #endif //MO_ENABLE_CERT_MGMT -bool Model::setup() { - if (!getBootService()) { +#if MO_ENABLE_SECURITY_EVENT +SecurityEventService *Ocpp16::Model::getSecurityEventService() { + if (!secEventService) { + secEventService = new SecurityEventService(context); + } + return secEventService; +} +#endif //MO_ENABLE_SECURITY_EVENT + +bool Ocpp16::Model::setup() { + if (!getBootService() || !getBootService()->setup()) { return false; //OOM } - getBootService()->setup(); - if (!getHeartbeatService()) { + + if (!getHeartbeatService() || !getHeartbeatService()->setup()) { return false; //OOM } - getHeartbeatService()->setup(); -} -void Model::loop() { + if (!getTransactionService() || !getTransactionService()->setup()) { + return false; //OOM + } - if (bootService) { - bootService->loop(); + if (!getMeteringService() || !getMeteringService()->setup()) { + return false; //OOM + } + + if (!getResetService() || !getResetService()->setup()) { + return false; //OOM } - if (capabilitiesUpdated) { - updateSupportedStandardProfiles(); - capabilitiesUpdated = false; + if (!getAvailabilityService() || !getAvailabilityService()->setup()) { + return false; } - if (!runTasks) { - return; + if (!getRemoteControlService() || !getRemoteControlService()->setup()) { + return false; } - for (auto& connector : connectors) { - connector->loop(); +#if MO_ENABLE_FIRMWAREMANAGEMENT + if (!getFirmwareService() || !getFirmwareService()->setup()) { + return false; //OOM } +#endif //MO_ENABLE_FIRMWAREMANAGEMENT - if (connectorService) - connectorService->loop(); +#if MO_ENABLE_DIAGNOSTICS + if (!getDiagnosticsService() || !getDiagnosticsService()->setup()) { + return false; + } +#endif //MO_ENABLE_DIAGNOSTICS - if (smartChargingService) - smartChargingService->loop(); +#if MO_ENABLE_LOCAL_AUTH + if (!getAuthorizationService() || !getAuthorizationService()->setup()) { + return false; //OOM or memory corruption + } +#endif //MO_ENABLE_LOCAL_AUTH + +#if MO_ENABLE_RESERVATION + if (!getReservationService() || !getReservationService()->setup()) { + return false; + } +#endif //MO_ENABLE_RESERVATION + +#if MO_ENABLE_SMARTCHARGING + if (!getSmartChargingService() || !getSmartChargingService()->setup()) { + return false; + } +#endif //MO_ENABLE_SMARTCHARGING - if (heartbeatService) +#if MO_ENABLE_CERT_MGMT + if (!getCertificateService() || !getCertificateService()->setup()) { + return false; + } +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT + if (!getSecurityEventService() || !getSecurityEventService()->setup()) { + return false; + } +#endif //MO_ENABLE_SECURITY_EVENT + + // Ensure this is set up last. ConfigurationService::setup() loads the persistent config values from flash + if (!getConfigurationService() || !getConfigurationService()->setup()) { + return false; + } + + updateSupportedStandardProfiles(); + + // Register remainder of operations which don't have dedicated service + context.getMessageService().registerOperation("DataTransfer", [] (Context&) -> Operation* { + return new Ocpp16::DataTransfer();}); + + return true; +} + +void Ocpp16::Model::loop() { + + if (bootService) { + bootService->loop(); + } + + if (!runTasks) { + return; + } + + if (heartbeatService) { heartbeatService->loop(); + } + + if (transactionService) { + transactionService->loop(); + } - if (meteringService) + if (meteringService) { meteringService->loop(); + } - if (diagnosticsService) - diagnosticsService->loop(); + if (resetService) { + resetService->loop(); + } - if (firmwareService) + if (availabilityService) { + availabilityService->loop(); + } + +#if MO_ENABLE_FIRMWAREMANAGEMENT + if (firmwareService) { firmwareService->loop(); + } + + if (diagnosticsService) { + diagnosticsService->loop(); + } +#endif //MO_ENABLE_FIRMWAREMANAGEMENT #if MO_ENABLE_RESERVATION - if (reservationService) + if (reservationService) { reservationService->loop(); + } #endif //MO_ENABLE_RESERVATION - if (resetService) - resetService->loop(); +#if MO_ENABLE_SMARTCHARGING + if (smartChargingService) { + smartChargingService->loop(); + } +#endif //MO_ENABLE_SMARTCHARGING +} + +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 - if (availabilityService) - availabilityService->loop(); - if (transactionService) - transactionService->loop(); - - if (resetServiceV201) - resetServiceV201->loop(); -#endif +using namespace MicroOcpp; + +Ocpp201::Model::Model(Context& context) : MemoryManaged("v201.Model"), context(context) { + } -#if MO_ENABLE_V201 -void Model::setAvailabilityService(std::unique_ptr as) { - this->availabilityService = std::move(as); - capabilitiesUpdated = true; +Ocpp201::Model::~Model() { + delete bootService; + bootService = nullptr; + delete heartbeatService; + heartbeatService = nullptr; + delete variableService; + variableService = nullptr; + delete transactionService; + transactionService = nullptr; + delete meteringService; + meteringService = nullptr; + delete resetService; + resetService = nullptr; + delete availabilityService; + availabilityService = nullptr; + delete remoteControlService; + remoteControlService = nullptr; + +#if MO_ENABLE_SMARTCHARGING + delete smartChargingService; + smartChargingService = nullptr; +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT + delete certService; + certService = nullptr; +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT + delete secEventService; + secEventService = nullptr; +#endif //MO_ENABLE_SECURITY_EVENT } -AvailabilityService *Model::getAvailabilityService() const { - return availabilityService.get(); +void Ocpp201::Model::setNumEvseId(unsigned int numEvseId) { + if (numEvseId >= MO_NUM_EVSEID) { + MO_DBG_ERR("invalid arg"); + return; + } + this->numEvseId = numEvseId; } -void Model::setVariableService(std::unique_ptr vs) { - this->variableService = std::move(vs); - capabilitiesUpdated = true; +unsigned int Ocpp201::Model::getNumEvseId() { + return numEvseId; } -VariableService *Model::getVariableService() const { - return variableService.get(); +BootService *Ocpp201::Model::getBootService() { + if (!bootService) { + bootService = new BootService(context); + } + return bootService; } -void Model::setTransactionService(std::unique_ptr ts) { - this->transactionService = std::move(ts); - capabilitiesUpdated = true; +HeartbeatService *Ocpp201::Model::getHeartbeatService() { + if (!heartbeatService) { + heartbeatService = new HeartbeatService(context); + } + return heartbeatService; } -TransactionService *Model::getTransactionService() const { - return transactionService.get(); +Ocpp201::VariableService *Ocpp201::Model::getVariableService() { + if (!variableService) { + variableService = new Ocpp201::VariableService(context); + + // need extra init step so that other modules can declare variables before setup() + if (variableService && !variableService->init()) { + // variableService cannot be inited + delete variableService; + variableService = nullptr; + } + } + return variableService; } -void Model::setResetServiceV201(std::unique_ptr rs) { - this->resetServiceV201 = std::move(rs); - capabilitiesUpdated = true; +Ocpp201::TransactionService *Ocpp201::Model::getTransactionService() { + if (!transactionService) { + transactionService = new TransactionService(context); + } + return transactionService; } -Ocpp201::ResetService *Model::getResetServiceV201() const { - return resetServiceV201.get(); +Ocpp201::MeteringService *Ocpp201::Model::getMeteringService() { + if (!meteringService) { + meteringService = new Ocpp201::MeteringService(context); + } + return meteringService; } -void Model::setMeteringServiceV201(std::unique_ptr rs) { - this->meteringServiceV201 = std::move(rs); - capabilitiesUpdated = true; +Ocpp201::ResetService *Ocpp201::Model::getResetService() { + if (!resetService) { + resetService = new Ocpp201::ResetService(context); + } + return resetService; } -Ocpp201::MeteringService *Model::getMeteringServiceV201() const { - return meteringServiceV201.get(); +Ocpp201::AvailabilityService *Ocpp201::Model::getAvailabilityService() { + if (!availabilityService) { + availabilityService = new Ocpp201::AvailabilityService(context); + } + return availabilityService; } -void Model::setRemoteControlService(std::unique_ptr rs) { - remoteControlService = std::move(rs); - capabilitiesUpdated = true; +RemoteControlService *Ocpp201::Model::getRemoteControlService() { + if (!remoteControlService) { + remoteControlService = new RemoteControlService(context); + } + return remoteControlService; } -RemoteControlService *Model::getRemoteControlService() const { - return remoteControlService.get(); +#if MO_ENABLE_DIAGNOSTICS +DiagnosticsService *Ocpp201::Model::getDiagnosticsService() { + if (!diagnosticsService) { + diagnosticsService = new DiagnosticsService(context); + } + return diagnosticsService; } -#endif +#endif //MO_ENABLE_DIAGNOSTICS -Clock& Model::getClock() { - return clock; +#if MO_ENABLE_SMARTCHARGING +SmartChargingService* Ocpp201::Model::getSmartChargingService() { + if (!smartChargingService) { + smartChargingService = new SmartChargingService(context); + } + return smartChargingService; } +#endif //MO_ENABLE_SMARTCHARGING -const ProtocolVersion& Model::getVersion() const { - return version; +#if MO_ENABLE_CERT_MGMT +CertificateService *Ocpp201::Model::getCertificateService() { + if (!certService) { + certService = new CertificateService(context); + } + return certService; } +#endif //MO_ENABLE_CERT_MGMT -uint16_t Model::getBootNr() { - return bootNr; +#if MO_ENABLE_SECURITY_EVENT +SecurityEventService *Ocpp201::Model::getSecurityEventService() { + if (!secEventService) { + secEventService = new SecurityEventService(context); + } + return secEventService; } +#endif //MO_ENABLE_SECURITY_EVENT -void Model::updateSupportedStandardProfiles() { +bool Ocpp201::Model::setup() { + if (!getBootService() || !getBootService()->setup()) { + return false; //OOM + } - auto supportedFeatureProfilesString = - declareConfiguration("SupportedFeatureProfiles", "", CONFIGURATION_VOLATILE, true); - - if (!supportedFeatureProfilesString) { - MO_DBG_ERR("OOM"); - return; + if (!getHeartbeatService() || !getHeartbeatService()->setup()) { + return false; //OOM } - auto buf = makeString(getMemoryTag(), supportedFeatureProfilesString->getString()); + if (!getTransactionService() || !getTransactionService()->setup()) { + return false; + } - if (connectorService && - heartbeatService && - bootService) { - if (!strstr(supportedFeatureProfilesString->getString(), "Core")) { - if (!buf.empty()) buf += ','; - buf += "Core"; - } + if (!getMeteringService() || !getMeteringService()->setup()) { + return false; + } + if (!getAvailabilityService() || !getAvailabilityService()->setup()) { + return false; } - if (firmwareService || - diagnosticsService) { - if (!strstr(supportedFeatureProfilesString->getString(), "FirmwareManagement")) { - if (!buf.empty()) buf += ','; - buf += "FirmwareManagement"; - } + if (!getRemoteControlService() || !getRemoteControlService()->setup()) { + return false; } -#if MO_ENABLE_LOCAL_AUTH - if (authorizationService) { - if (!strstr(supportedFeatureProfilesString->getString(), "LocalAuthListManagement")) { - if (!buf.empty()) buf += ','; - buf += "LocalAuthListManagement"; - } + if (!getResetService() || !getResetService()->setup()) { + return false; } -#endif //MO_ENABLE_LOCAL_AUTH -#if MO_ENABLE_RESERVATION - if (reservationService) { - if (!strstr(supportedFeatureProfilesString->getString(), "Reservation")) { - if (!buf.empty()) buf += ','; - buf += "Reservation"; - } +#if MO_ENABLE_DIAGNOSTICS + if (!getDiagnosticsService() || !getDiagnosticsService()->setup()) { + return false; } -#endif //MO_ENABLE_RESERVATION +#endif //MO_ENABLE_DIAGNOSTICS - if (smartChargingService) { - if (!strstr(supportedFeatureProfilesString->getString(), "SmartCharging")) { - if (!buf.empty()) buf += ','; - buf += "SmartCharging"; - } +#if MO_ENABLE_SMARTCHARGING + if (!getSmartChargingService() || !getSmartChargingService()->setup()) { + return false; } +#endif //MO_ENABLE_SMARTCHARGING - if (!strstr(supportedFeatureProfilesString->getString(), "RemoteTrigger")) { - if (!buf.empty()) buf += ','; - buf += "RemoteTrigger"; +#if MO_ENABLE_CERT_MGMT + if (!getCertificateService() || !getCertificateService()->setup()) { + return false; } +#endif //MO_ENABLE_CERT_MGMT - supportedFeatureProfilesString->setString(buf.c_str()); +#if MO_ENABLE_SECURITY_EVENT + if (!getSecurityEventService() || !getSecurityEventService()->setup()) { + return false; + } +#endif //MO_ENABLE_SECURITY_EVENT - MO_DBG_DEBUG("supported feature profiles: %s", buf.c_str()); + // Ensure this is set up last. VariableService::setup() loads the persistent variable values from flash + if (!getVariableService() || !getVariableService()->setup()) { + return false; + } + + return true; } + +void Ocpp201::Model::loop() { + + if (bootService) { + bootService->loop(); + } + + if (variableService) { + variableService->loop(); + } + + if (!runTasks) { + return; + } + + if (heartbeatService) { + heartbeatService->loop(); + } + + if (transactionService) { + transactionService->loop(); + } + + if (resetService) { + resetService->loop(); + } + + if (availabilityService) { + availabilityService->loop(); + } + + #if MO_ENABLE_SMARTCHARGING + if (smartChargingService) { + smartChargingService->loop(); + } + #endif //MO_ENABLE_SMARTCHARGING +} + +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index ce53512d..60baab40 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -5,66 +5,57 @@ #ifndef MO_MODEL_H #define MO_MODEL_H -#include #include +#include #include -// number of EVSE IDs (including 0). On a charger with one physical connector, NUM_EVSEID is 2 -#ifndef MO_NUM_EVSEID -// Use MO_NUMCONNECTORS if defined, for backwards compatibility -#if defined(MO_NUMCONNECTORS) -#define MO_NUM_EVSEID MO_NUMCONNECTORS -#else -#define MO_NUM_EVSEID 2 -#endif -#endif // MO_NUM_EVSEID - namespace MicroOcpp { class Context; - class BootService; class HeartbeatService; -class ResetService; -class ConnectorService; -class Connector; -class TransactionStoreEvse; -class MeteringService; -class MeteringServiceEvse; +class RemoteControlService; -#if MO_ENABLE_FIRMWAREMANAGEMENT -class FirmwareService; +#if MO_ENABLE_DIAGNOSTICS class DiagnosticsService; -#endif //MO_ENABLE_FIRMWAREMANAGEMENT - -#if MO_ENABLE_LOCAL_AUTH -class AuthorizationService; -#endif //MO_ENABLE_LOCAL_AUTH - -#if MO_ENABLE_RESERVATION -class ReservationService; -#endif //MO_ENABLE_RESERVATION +#endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_SMARTCHARGING class SmartChargingService; -class SmartChargingServiceEvse; #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT class CertificateService; #endif //MO_ENABLE_CERT_MGMT -#if MO_ENABLE_V201 -class AvailabilityService; -class VariableService; -class TransactionService; -class RemoteControlService; +#if MO_ENABLE_SECURITY_EVENT +class SecurityEventService; +#endif //MO_ENABLE_SECURITY_EVENT -namespace Ocpp201 { -class ResetService; +} //namespace MicroOcpp + +#if MO_ENABLE_V16 + +namespace MicroOcpp { +namespace Ocpp16 { + +class ConfigurationService; +class TransactionService; class MeteringService; -} -#endif //MO_ENABLE_V201 +class ResetService; +class AvailabilityService; + +#if MO_ENABLE_FIRMWAREMANAGEMENT +class FirmwareService; +#endif //MO_ENABLE_FIRMWAREMANAGEMENT + +#if MO_ENABLE_LOCAL_AUTH +class AuthorizationService; +#endif //MO_ENABLE_LOCAL_AUTH + +#if MO_ENABLE_RESERVATION +class ReservationService; +#endif //MO_ENABLE_RESERVATION class Model : public MemoryManaged { private: @@ -72,18 +63,21 @@ class Model : public MemoryManaged { BootService *bootService = nullptr; HeartbeatService *heartbeatService = nullptr; - ResetService *resetService = nullptr; - ConnectorService *connectorService = nullptr; - Connector **connectors = nullptr; - TransactionStoreEvse **transactionStoreEvse = nullptr; + ConfigurationService *configurationService = nullptr; + TransactionService *transactionService = nullptr; MeteringService *meteringService = nullptr; - MeteringServiceEvse **meteringServiceEvse = nullptr; + ResetService *resetService = nullptr; + AvailabilityService *availabilityService = nullptr; + RemoteControlService *remoteControlService = nullptr; #if MO_ENABLE_FIRMWAREMANAGEMENT FirmwareService *firmwareService = nullptr; - DiagnosticsService *diagnosticsService = nullptr; #endif //MO_ENABLE_FIRMWAREMANAGEMENT +#if MO_ENABLE_DIAGNOSTICS + DiagnosticsService *diagnosticsService = nullptr; +#endif //MO_ENABLE_DIAGNOSTICS + #if MO_ENABLE_LOCAL_AUTH AuthorizationService *authorizationService = nullptr; #endif //MO_ENABLE_LOCAL_AUTH @@ -94,38 +88,23 @@ class Model : public MemoryManaged { #if MO_ENABLE_SMARTCHARGING SmartChargingService *smartChargingService = nullptr; - SmartChargingServiceEvse **smartChargingServiceEvse = nullptr; #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT CertificateService *certService = nullptr; #endif //MO_ENABLE_CERT_MGMT -#if MO_ENABLE_V201 - AvailabilityService *availabilityService = nullptr; - VariableService *variableService = nullptr; - TransactionService *transactionService = nullptr; - Ocpp201::ResetService *resetServiceV201 = nullptr; - Ocpp201::MeteringService *meteringServiceV201 = nullptr; - RemoteControlService *remoteControlService = nullptr; -#endif - - Clock clock; +#if MO_ENABLE_SECURITY_EVENT + SecurityEventService *secEventService = nullptr; +#endif //MO_ENABLE_SECURITY_EVENT - int ocppVersion = MO_OCPP_V16; unsigned int numEvseId = MO_NUM_EVSEID; - - bool capabilitiesUpdated = true; - void updateSupportedStandardProfiles(); - bool runTasks = false; - bool isSetup = false; - const uint16_t bootNr = 0; //each boot of this lib has a unique number + void updateSupportedStandardProfiles(); public: - Model(ProtocolVersion version = ProtocolVersion(1,6), uint16_t bootNr = 0); - Model(const Model& rhs) = delete; + Model(Context& context); ~Model(); // Set number of EVSE IDs (including 0). On a charger with one physical connector, numEvseId is 2. Default value is MO_NUM_EVSEID @@ -134,18 +113,21 @@ class Model : public MemoryManaged { BootService *getBootService(); HeartbeatService *getHeartbeatService(); - ResetService *getResetService(); - ConnectorService *getConnectorService(); - Connector *getConnector(unsigned int evseId); - TransactionStoreEvse *getTransactionStoreEvse(); + ConfigurationService *getConfigurationService(); + TransactionService *getTransactionService(); MeteringService *getMeteringService(); - MeteringServiceEvse *getMeteringServiceEvse(unsigned int evseId); + ResetService *getResetService(); + AvailabilityService *getAvailabilityService(); + RemoteControlService *getRemoteControlService(); #if MO_ENABLE_FIRMWAREMANAGEMENT FirmwareService *getFirmwareService(); - DiagnosticsService *getDiagnosticsService(); #endif //MO_ENABLE_FIRMWAREMANAGEMENT +#if MO_ENABLE_DIAGNOSTICS + DiagnosticsService *getDiagnosticsService(); +#endif //MO_ENABLE_DIAGNOSTICS + #if MO_ENABLE_LOCAL_AUTH AuthorizationService *getAuthorizationService(); #endif //MO_ENABLE_LOCAL_AUTH @@ -156,46 +138,118 @@ class Model : public MemoryManaged { #if MO_ENABLE_SMARTCHARGING SmartChargingService *getSmartChargingService(); - SmartChargingServiceEvse *getSmartChargingServiceEvse(unsigned int evseId); #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT - CertificateService *getCertificateService() const; + CertificateService *getCertificateService(); #endif //MO_ENABLE_CERT_MGMT +#if MO_ENABLE_SECURITY_EVENT + SecurityEventService *getSecurityEventService(); +#endif //MO_ENABLE_SECURITY_EVENT + + bool setup(); + void loop(); + void activateTasks() {runTasks = true;} +}; + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 - void setAvailabilityService(std::unique_ptr as); - AvailabilityService *getAvailabilityService() const; - void setVariableService(std::unique_ptr vs); - VariableService *getVariableService() const; +namespace MicroOcpp { +namespace Ocpp201 { - void setTransactionService(std::unique_ptr ts); - TransactionService *getTransactionService() const; +class VariableService; +class TransactionService; +class MeteringService; +class ResetService; +class AvailabilityService; - void setResetServiceV201(std::unique_ptr rs); - Ocpp201::ResetService *getResetServiceV201() const; +class Model : public MemoryManaged { +private: + Context& context; - void setMeteringServiceV201(std::unique_ptr ms); - Ocpp201::MeteringService *getMeteringServiceV201() const; + BootService *bootService = nullptr; + HeartbeatService *heartbeatService = nullptr; + VariableService *variableService = nullptr; + TransactionService *transactionService = nullptr; + MeteringService *meteringService = nullptr; + ResetService *resetService = nullptr; + AvailabilityService *availabilityService = nullptr; + RemoteControlService *remoteControlService = nullptr; - void setRemoteControlService(std::unique_ptr rs); - RemoteControlService *getRemoteControlService() const; -#endif +#if MO_ENABLE_DIAGNOSTICS + DiagnosticsService *diagnosticsService = nullptr; +#endif //MO_ENABLE_DIAGNOSTICS - bool setup(int ocppVersion); +#if MO_ENABLE_SMARTCHARGING + SmartChargingService *smartChargingService = nullptr; +#endif //MO_ENABLE_SMARTCHARGING - void loop(); +#if MO_ENABLE_CERT_MGMT + CertificateService *certService = nullptr; +#endif //MO_ENABLE_CERT_MGMT - void activateTasks() {runTasks = true;} +#if MO_ENABLE_SECURITY_EVENT + SecurityEventService *secEventService = nullptr; +#endif //MO_ENABLE_SECURITY_EVENT - Clock &getClock(); + unsigned int numEvseId = MO_NUM_EVSEID; + bool runTasks = false; - const ProtocolVersion& getVersion() const; +public: + Model(Context& context); + ~Model(); + + // Set number of EVSE IDs (including 0). On a charger with one physical connector, numEvseId is 2. Default value is MO_NUM_EVSEID + void setNumEvseId(unsigned int numEvseId); + unsigned int getNumEvseId(); - uint16_t getBootNr(); + BootService *getBootService(); + HeartbeatService *getHeartbeatService(); + VariableService *getVariableService(); + TransactionService *getTransactionService(); + MeteringService *getMeteringService(); + ResetService *getResetService(); + AvailabilityService *getAvailabilityService(); + RemoteControlService *getRemoteControlService(); + +#if MO_ENABLE_DIAGNOSTICS + DiagnosticsService *getDiagnosticsService(); +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING + SmartChargingService *getSmartChargingService(); +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT + CertificateService *getCertificateService(); +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT + SecurityEventService *getSecurityEventService(); +#endif //MO_ENABLE_SECURITY_EVENT + + bool setup(); + void loop(); + void activateTasks() {runTasks = true;} }; -} //end namespace MicroOcpp +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 + +#if MO_ENABLE_V16 && !MO_ENABLE_V201 +namespace MicroOcpp { +using Model = Ocpp16::Model; +} +#elif !MO_ENABLE_V16 && MO_ENABLE_V201 +namespace MicroOcpp { +using Model = Ocpp201::Model; +} +#endif #endif diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h index 2edc83e3..f3ec8152 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h @@ -5,30 +5,94 @@ #ifndef MO_UNLOCKCONNECTOR_H #define MO_UNLOCKCONNECTOR_H +#include #include -#if MO_ENABLE_V201 +// Connector-lock related behavior (i.e. if UnlockConnectorOnEVSideDisconnect is RW; enable HW binding for UnlockConnector) +#ifndef MO_ENABLE_CONNECTOR_LOCK +#define MO_ENABLE_CONNECTOR_LOCK 1 +#endif -#include +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus -#include +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CONNECTOR_LOCK + +#ifndef MO_UNLOCK_TIMEOUT +#define MO_UNLOCK_TIMEOUT 10 // if Result is Pending, wait at most this period (in ms) until sending UnlockFailed +#endif typedef enum { - RequestStartStopStatus_Accepted, - RequestStartStopStatus_Rejected -} RequestStartStopStatus; + MO_UnlockConnectorResult_UnlockFailed, + MO_UnlockConnectorResult_Unlocked, + MO_UnlockConnectorResult_Pending // unlock action not finished yet, result still unknown (MO will check again later) +} MO_UnlockConnectorResult; -#if MO_ENABLE_CONNECTOR_LOCK +#ifdef __cplusplus +} +#endif // __cplusplus -typedef enum { - UnlockStatus_Unlocked, - UnlockStatus_UnlockFailed, - UnlockStatus_OngoingAuthorizedTransaction, - UnlockStatus_UnknownConnector, - UnlockStatus_PENDING // unlock action not finished yet, result still unknown (MO will check again later) -} UnlockStatus; +namespace MicroOcpp { + +enum class TriggerMessageStatus : uint8_t { + ERR_INTERNAL, + Accepted, + Rejected, + NotImplemented, +}; + +} //namespace MicroOcpp + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CONNECTOR_LOCK + +#if MO_ENABLE_V16 +#ifdef __cplusplus + +namespace MicroOcpp { +namespace Ocpp16 { + +enum class RemoteStartStopStatus : uint8_t { + ERR_INTERNAL, + Accepted, + Rejected +}; + +enum class UnlockStatus : uint8_t { + Unlocked, + UnlockFailed, + NotSupported, + PENDING //MO-internal: unlock action not finished yet, result still unknown. Check later +}; + +} //namespace Ocpp16 +} //namespace MicroOcpp + +#endif //__cplusplus +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 +#ifdef __cplusplus + +namespace MicroOcpp { +namespace Ocpp201 { + +enum class RequestStartStopStatus : uint8_t { + Accepted, + Rejected +}; + +enum class UnlockStatus : uint8_t { + Unlocked, + UnlockFailed, + OngoingAuthorizedTransaction, + UnknownConnector, + PENDING //MO-internal: unlock action not finished yet, result still unknown. Check later +}; -#endif // MO_ENABLE_CONNECTOR_LOCK +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //__cplusplus +#endif //MO_ENABLE_V201 -#endif // MO_ENABLE_V201 -#endif // MO_UNLOCKCONNECTOR_H +#endif //MO_UNLOCKCONNECTOR_H diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index 74aefe0f..d96bb1aa 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -2,118 +2,425 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include -#include +#include #include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include +#if MO_ENABLE_V16 || MO_ENABLE_V201 + using namespace MicroOcpp; -RemoteControlServiceEvse::RemoteControlServiceEvse(Context& context, unsigned int evseId) : MemoryManaged("v201.RemoteControl.RemoteControlServiceEvse"), context(context), evseId(evseId) { +RemoteControlServiceEvse::RemoteControlServiceEvse(Context& context, RemoteControlService& rcService, unsigned int evseId) : + MemoryManaged("v16/v201.RemoteControl.RemoteControlServiceEvse"), + context(context), + rcService(rcService), + evseId(evseId) { } +bool RemoteControlServiceEvse::setup() { + + #if MO_ENABLE_V16 + if (context.getOcppVersion() == MO_OCPP_V16) { + auto txServce = context.getModel16().getTransactionService(); + txServiceEvse16 = txServce ? txServce->getEvse(evseId) : nullptr; + if (!txServiceEvse16) { + MO_DBG_ERR("setup failure"); + return false; + } + } + #endif + #if MO_ENABLE_V201 + if (context.getOcppVersion() == MO_OCPP_V16) { + auto txServce = context.getModel201().getTransactionService(); + txServiceEvse201 = txServce ? txServce->getEvse(evseId) : nullptr; + if (!txServiceEvse201) { + MO_DBG_ERR("setup failure"); + return false; + } + } + #endif + + #if MO_ENABLE_CONNECTOR_LOCK + if (!onUnlockConnector) { + MO_DBG_WARN("need to set onUnlockConnector"); + } + #endif + + return true; +} + #if MO_ENABLE_CONNECTOR_LOCK -void RemoteControlServiceEvse::setOnUnlockConnector(UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData) { +void RemoteControlServiceEvse::setOnUnlockConnector(MO_UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData) { this->onUnlockConnector = onUnlockConnector; this->onUnlockConnectorUserData = userData; } -UnlockStatus RemoteControlServiceEvse::unlockConnector() { +#if MO_ENABLE_V16 +Ocpp16::UnlockStatus RemoteControlServiceEvse::unlockConnector16() { if (!onUnlockConnector) { - return UnlockStatus_UnlockFailed; + return Ocpp16::UnlockStatus::UnlockFailed; } - if (auto txService = context.getModel().getTransactionService()) { - if (auto evse = txService->getEvse(evseId)) { - if (auto tx = evse->getTransaction()) { - if (tx->started && !tx->stopped && tx->isAuthorized) { - return UnlockStatus_OngoingAuthorizedTransaction; - } else { - evse->abortTransaction(Ocpp201::Transaction::StoppedReason::Other,Ocpp201::TransactionEventTriggerReason::UnlockCommand); - } - } - } + auto tx = txServiceEvse16->getTransaction(); + if (tx && tx->isActive()) { + txServiceEvse16->endTransaction(nullptr, "UnlockCommand"); + txServiceEvse16->updateTxNotification(MO_TxNotification_RemoteStop); } auto status = onUnlockConnector(evseId, onUnlockConnectorUserData); switch (status) { - case UnlockConnectorResult_Pending: - return UnlockStatus_PENDING; - case UnlockConnectorResult_Unlocked: - return UnlockStatus_Unlocked; - case UnlockConnectorResult_UnlockFailed: - return UnlockStatus_UnlockFailed; + case MO_UnlockConnectorResult_Pending: + return Ocpp16::UnlockStatus::PENDING; + case MO_UnlockConnectorResult_Unlocked: + return Ocpp16::UnlockStatus::Unlocked; + case MO_UnlockConnectorResult_UnlockFailed: + return Ocpp16::UnlockStatus::UnlockFailed; } MO_DBG_ERR("invalid onUnlockConnector result code"); - return UnlockStatus_UnlockFailed; + return Ocpp16::UnlockStatus::UnlockFailed; } -#endif +#endif //MO_ENABLE_V16 -RemoteControlService::RemoteControlService(Context& context, size_t numEvses) : MemoryManaged("v201.RemoteControl.RemoteControlService"), context(context) { +#if MO_ENABLE_V201 +Ocpp201::UnlockStatus RemoteControlServiceEvse::unlockConnector201() { - for (size_t i = 0; i < numEvses && i < MO_NUM_EVSEID; i++) { - evses[i] = new RemoteControlServiceEvse(context, (unsigned int)i); + if (!onUnlockConnector) { + return Ocpp201::UnlockStatus::UnlockFailed; } - auto varService = context.getModel().getVariableService(); - authorizeRemoteStart = varService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false); - - context.getOperationRegistry().registerOperation("RequestStartTransaction", [this] () -> Operation* { - if (!this->context.getModel().getTransactionService()) { - return nullptr; //-> NotSupported + if (auto tx = txServiceEvse201->getTransaction()) { + if (tx->started && !tx->stopped && tx->isAuthorized) { + return Ocpp201::UnlockStatus::OngoingAuthorizedTransaction; + } else { + txServiceEvse201->abortTransaction(MO_TxStoppedReason_Other,MO_TxEventTriggerReason_UnlockCommand); } - return new Ocpp201::RequestStartTransaction(*this);}); - context.getOperationRegistry().registerOperation("RequestStopTransaction", [this] () -> Operation* { - if (!this->context.getModel().getTransactionService()) { - return nullptr; //-> NotSupported - } - return new Ocpp201::RequestStopTransaction(*this);}); -#if MO_ENABLE_CONNECTOR_LOCK - context.getOperationRegistry().registerOperation("UnlockConnector", [this] () { - return new Ocpp201::UnlockConnector(*this);}); -#endif - context.getOperationRegistry().registerOperation("TriggerMessage", [&context] () { - return new Ocpp16::TriggerMessage(context);}); + } + + auto status = onUnlockConnector(evseId, onUnlockConnectorUserData); + switch (status) { + case MO_UnlockConnectorResult_Pending: + return Ocpp201::UnlockStatus::PENDING; + case MO_UnlockConnectorResult_Unlocked: + return Ocpp201::UnlockStatus::Unlocked; + case MO_UnlockConnectorResult_UnlockFailed: + return Ocpp201::UnlockStatus::UnlockFailed; + } + + MO_DBG_ERR("invalid onUnlockConnector result code"); + return Ocpp201::UnlockStatus::UnlockFailed; +} +#endif //MO_ENABLE_V201 + +#endif //MO_ENABLE_CONNECTOR_LOCK + +RemoteControlService::RemoteControlService(Context& context) : MemoryManaged("v16.RemoteControl.RemoteControlService"), context(context) { + } RemoteControlService::~RemoteControlService() { - for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { + for (unsigned int i = 0; i < MO_NUM_EVSEID; i++) { delete evses[i]; + evses[i] = nullptr; } } RemoteControlServiceEvse *RemoteControlService::getEvse(unsigned int evseId) { - if (evseId >= MO_NUM_EVSEID) { - MO_DBG_ERR("invalid arg"); + if (evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); return nullptr; } + + if (!evses[evseId]) { + evses[evseId] = new RemoteControlServiceEvse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + return evses[evseId]; } -RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize) { +bool RemoteControlService::setup() { + + unsigned int numEvseId = MO_NUM_EVSEID; + + #if MO_ENABLE_V16 + if (context.getOcppVersion() == MO_OCPP_V16) { + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } - TransactionService *txService = context.getModel().getTransactionService(); - if (!txService) { - MO_DBG_ERR("TxService uninitialized"); - return RequestStartStopStatus_Rejected; + configService->declareConfiguration("AuthorizeRemoteTxRequests", false); + + #if MO_ENABLE_CONNECTOR_LOCK + configService->declareConfiguration("UnlockConnectorOnEVSideDisconnect", true); //read-write + #else + configService->declareConfiguration("UnlockConnectorOnEVSideDisconnect", false, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); //read-only because there is no connector lock + #endif //MO_ENABLE_CONNECTOR_LOCK + + txService16 = context.getModel16().getTransactionService(); + if (!txService16) { + MO_DBG_ERR("setup failure"); + return false; + } + + numEvseId = context.getModel16().getNumEvseId(); + + context.getMessageService().registerOperation("RemoteStartTransaction", [] (Context& context) -> Operation* { + return new Ocpp16::RemoteStartTransaction(context, *context.getModel16().getRemoteControlService());}); + context.getMessageService().registerOperation("RemoteStopTransaction", [] (Context& context) -> Operation* { + return new Ocpp16::RemoteStopTransaction(context, *context.getModel16().getRemoteControlService());}); + context.getMessageService().registerOperation("UnlockConnector", [] (Context& context) -> Operation* { + return new Ocpp16::UnlockConnector(context, *context.getModel16().getRemoteControlService());}); + } + #endif + #if MO_ENABLE_V201 + if (context.getOcppVersion() == MO_OCPP_V201) { + + auto varService = context.getModel201().getVariableService(); + if (!varService) { + return false; + } + + authorizeRemoteStart = varService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false); + if (!authorizeRemoteStart) { + MO_DBG_ERR("failure to declare variable"); + return false; + } + + txService201 = context.getModel201().getTransactionService(); + if (!txService201) { + MO_DBG_ERR("setup failure"); + return false; + } + + numEvseId = context.getModel201().getNumEvseId(); + + context.getMessageService().registerOperation("RequestStartTransaction", [] (Context& context) -> Operation* { + return new Ocpp201::RequestStartTransaction(*context.getModel201().getRemoteControlService());}); + context.getMessageService().registerOperation("RequestStopTransaction", [] (Context& context) -> Operation* { + return new Ocpp201::RequestStopTransaction(*context.getModel201().getRemoteControlService());}); + + #if MO_ENABLE_CONNECTOR_LOCK + context.getMessageService().registerOperation("UnlockConnector", [] (Context& context) -> Operation* { + return new Ocpp201::UnlockConnector(context, *context.getModel201().getRemoteControlService());}); + #endif + } + #endif + + context.getMessageService().registerOperation("TriggerMessage", [] (Context& context) -> Operation* { + RemoteControlService *rcSvc = nullptr; + #if MO_ENABLE_V16 + if (context.getOcppVersion() == MO_OCPP_V16) { + rcSvc = context.getModel16().getRemoteControlService(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (context.getOcppVersion() == MO_OCPP_V201) { + rcSvc = context.getModel201().getRemoteControlService(); + } + #endif //MO_ENABLE_V201 + return new TriggerMessage(context, *rcSvc);}); + + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i) || !getEvse(i)->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } + } + + return true; +} + +bool RemoteControlService::addTriggerMessageHandler(const char *operationType, Operation* (*createOperationCb)(Context& context)) { + bool capacity = triggerMessageHandlers.size() + 1; + triggerMessageHandlers.reserve(capacity); + if (triggerMessageHandlers.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return false; + } + OperationCreator handler; + handler.operationType = operationType; + handler.create = createOperationCb; + triggerMessageHandlers.push_back(handler); + return true; +} + +bool RemoteControlService::addTriggerMessageHandler(const char *operationType, Operation* (*createOperationCb)(Context& context, unsigned int evseId)) { + bool capacity = triggerMessageHandlers.size() + 1; + triggerMessageHandlers.reserve(capacity); + if (triggerMessageHandlers.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return false; + } + OperationCreator handler; + handler.operationType = operationType; + handler.create2 = createOperationCb; + triggerMessageHandlers.push_back(handler); + return true; +} + +bool RemoteControlService::addTriggerMessageHandler(const char *operationType, TriggerMessageStatus (*triggerMessageHandlerCb)(Context& context, int evseId)) { + bool capacity = triggerMessageHandlers.size() + 1; + triggerMessageHandlers.reserve(capacity); + if (triggerMessageHandlers.capacity() < capacity) { + MO_DBG_ERR("OOM"); + return false; + } + OperationCreator handler; + handler.operationType = operationType; + handler.handler3 = triggerMessageHandlerCb; + triggerMessageHandlers.push_back(handler); + return true; +} + +#if MO_ENABLE_V16 +Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile) { + + auto configService = context.getModel16().getConfigurationService(); + auto authorizeRemoteTxRequests = configService ? configService->declareConfiguration("AuthorizeRemoteTxRequests", false) : nullptr; + if (!authorizeRemoteTxRequests) { + MO_DBG_ERR("internal error"); + return Ocpp16::RemoteStartStopStatus::ERR_INTERNAL; + } + + auto status = Ocpp16::RemoteStartStopStatus::Rejected; + + Ocpp16::TransactionServiceEvse *selectEvse = nullptr; + if (connectorId >= 1) { + //connectorId specified for given connector, try to start Transaction here + auto txSvcEvse = txService16->getEvse(connectorId); + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(connectorId) : nullptr; + if (txSvcEvse && availSvcEvse){ + if (!txSvcEvse->getTransaction() && + availSvcEvse->isOperative()) { + selectEvse = txSvcEvse; + } + } + } else { + //connectorId not specified. Find free connector + for (unsigned int eId = 1; eId < numEvseId; eId++) { + auto txSvcEvse = txService16->getEvse(eId); + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(eId) : nullptr; + if (txSvcEvse && availSvcEvse) { + if (!txSvcEvse->getTransaction() && + availSvcEvse->isOperative()) { + selectEvse = txSvcEvse; + connectorId = eId; + break; + } + } + } + } + + if (selectEvse) { + + bool success = true; + + int chargingProfileId = -1; //keep Id after moving charging profile to SCService + + if (chargingProfile && context.getModel16().getSmartChargingService()) { + chargingProfileId = chargingProfile->chargingProfileId; + success = context.getModel16().getSmartChargingService()->setChargingProfile(connectorId, std::move(chargingProfile)); + } + + if (success) { + if (authorizeRemoteTxRequests->getBool()) { + success = selectEvse->beginTransaction(idTag); + } else { + success = selectEvse->beginTransaction_authorized(idTag); + } + } + if (success) { + if (auto transaction = selectEvse->getTransaction()) { + selectEvse->updateTxNotification(MO_TxNotification_RemoteStart); + + if (chargingProfileId >= 0) { + transaction->setTxProfileId(chargingProfileId); + } + } else { + MO_DBG_ERR("internal error"); + status = Ocpp16::RemoteStartStopStatus::ERR_INTERNAL; + success = false; + } + } + + if (!success && chargingProfile && context.getModel16().getSmartChargingService()) { + context.getModel16().getSmartChargingService()->clearChargingProfile(chargingProfile->chargingProfileId, connectorId, ChargingProfilePurposeType::UNDEFINED, -1); + } + + if (success) { + status = Ocpp16::RemoteStartStopStatus::Accepted; + } else { + status = Ocpp16::RemoteStartStopStatus::Rejected; + } + } else { + MO_DBG_INFO("No connector to start transaction"); + status = Ocpp16::RemoteStartStopStatus::Rejected; + } + + return status; +} + +Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int transactionId) { + + auto status = Ocpp16::RemoteStartStopStatus::Rejected; + + for (unsigned int eId = 0; eId < numEvseId; eId++) { + auto txSvcEvse = txService16->getEvse(eId); + auto transaction = txSvcEvse ? txSvcEvse->getTransaction() : nullptr; + if (transaction && + txSvcEvse->getTransaction()->getTransactionId() == transactionId) { + if (transaction->isActive()) { + txSvcEvse->updateTxNotification(MO_TxNotification_RemoteStop); + } + txSvcEvse->endTransaction(nullptr, "Remote"); + status = Ocpp16::RemoteStartStopStatus::Accepted; + } } + + return status; +} +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 + +Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize) { - auto evse = txService->getEvse(evseId); + if (!txService201) { + MO_DBG_ERR("TxService uninitialized"); + return Ocpp201::RequestStartStopStatus::Rejected; + } + + auto evse = txService201->getEvse(evseId); if (!evse) { MO_DBG_ERR("EVSE not found"); - return RequestStartStopStatus_Rejected; + return Ocpp201::RequestStartStopStatus::Rejected; } if (!evse->beginAuthorization(idToken, authorizeRemoteStart->getBool())) { @@ -122,52 +429,122 @@ RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned in auto ret = snprintf(transactionIdOut, transactionIdBufSize, "%s", tx->transactionId); if (ret < 0 || (size_t)ret >= transactionIdBufSize) { MO_DBG_ERR("internal error"); - return RequestStartStopStatus_Rejected; + return Ocpp201::RequestStartStopStatus::Rejected; } } - return RequestStartStopStatus_Rejected; + return Ocpp201::RequestStartStopStatus::Rejected; } auto tx = evse->getTransaction(); if (!tx) { MO_DBG_ERR("internal error"); - return RequestStartStopStatus_Rejected; + return Ocpp201::RequestStartStopStatus::Rejected; } auto ret = snprintf(transactionIdOut, transactionIdBufSize, "%s", tx->transactionId); if (ret < 0 || (size_t)ret >= transactionIdBufSize) { MO_DBG_ERR("internal error"); - return RequestStartStopStatus_Rejected; + return Ocpp201::RequestStartStopStatus::Rejected; } tx->remoteStartId = remoteStartId; tx->notifyRemoteStartId = true; - return RequestStartStopStatus_Accepted; + return Ocpp201::RequestStartStopStatus::Accepted; } -RequestStartStopStatus RemoteControlService::requestStopTransaction(const char *transactionId) { +Ocpp201::RequestStartStopStatus RemoteControlService::requestStopTransaction(const char *transactionId) { - TransactionService *txService = context.getModel().getTransactionService(); - if (!txService) { + if (!txService201) { MO_DBG_ERR("TxService uninitialized"); - return RequestStartStopStatus_Rejected; + return Ocpp201::RequestStartStopStatus::Rejected; } bool success = false; for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID; evseId++) { - if (auto evse = txService->getEvse(evseId)) { + if (auto evse = txService201->getEvse(evseId)) { if (evse->getTransaction() && !strcmp(evse->getTransaction()->transactionId, transactionId)) { - success = evse->abortTransaction(Ocpp201::Transaction::StoppedReason::Remote, Ocpp201::TransactionEventTriggerReason::RemoteStop); + success = evse->abortTransaction(MO_TxStoppedReason_Remote, MO_TxEventTriggerReason_RemoteStop); break; } } } return success ? - RequestStartStopStatus_Accepted : - RequestStartStopStatus_Rejected; + Ocpp201::RequestStartStopStatus::Accepted : + Ocpp201::RequestStartStopStatus::Rejected; +} + +#endif //MO_ENABLE_V201 + +TriggerMessageStatus RemoteControlService::triggerMessage(const char *requestedMessage, int evseId) { + + TriggerMessageStatus status = TriggerMessageStatus::NotImplemented; + + for (size_t i = 0; i < triggerMessageHandlers.size(); i++) { + + auto& handler = triggerMessageHandlers[i]; + + if (!strcmp(requestedMessage, handler.operationType)) { + + if (handler.handler3) { + // handler3 is a fully custom implementation. Just execute the handler with the parameters + // from TriggerMessage and we're done + status = handler.handler3(context, evseId); + break; + } + + unsigned int evseIdRangeBegin = 0, evseIdRangeEnd = 1; + if (handler.create2) { + // create2 is defined if multiple evseIds are supported. Then determine evseId range + if (evseId < 0) { + evseIdRangeBegin = 0; + evseIdRangeEnd = numEvseId; + } else if ((unsigned int)evseId < numEvseId) { + evseIdRangeBegin = (unsigned int)evseId; + evseIdRangeEnd = (unsigned int)evseId + 1; + } else { + return TriggerMessageStatus::ERR_INTERNAL; + } + } + + for (int eId = evseIdRangeBegin; eId < evseIdRangeEnd; eId++) { + + Operation *operation = nullptr; + + if (handler.create) { + operation = handler.create(context); + } else if (handler.create2) { + operation = handler.create2(context, eId); + } + + if (!operation) { + MO_DBG_ERR("OOM"); + return TriggerMessageStatus::ERR_INTERNAL; + } + + auto request = makeRequest(context, operation); + if (!request) { + MO_DBG_ERR("OOM"); + return TriggerMessageStatus::ERR_INTERNAL; + } + + bool success = context.getMessageService().sendRequest(std::move(request)); + if (success) { + status = TriggerMessageStatus::Accepted; + } else { + MO_DBG_ERR("OOM"); + return TriggerMessageStatus::ERR_INTERNAL; + } + } + + // handler found, done + break; + } + } + + return status; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h index 0a26567b..e7d4d7de 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -5,61 +5,125 @@ #ifndef MO_REMOTECONTROLSERVICE_H #define MO_REMOTECONTROLSERVICE_H -#include - -#if MO_ENABLE_V201 - #include +#include #include -#include +#include #include +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 namespace MicroOcpp { class Context; +class RemoteControlService; + +#if MO_ENABLE_V16 +namespace Ocpp16 { +class Configuration; +class TransactionService; +class TransactionServiceEvse; +} //namespace Ocpp16 +#endif //MO_ENABLE_V16 + +#if MO_ENABLE_V201 +namespace Ocpp201 { class Variable; +class TransactionService; +class TransactionServiceEvse; +} //namespace Ocpp201 +#endif //MO_ENABLE_V201 class RemoteControlServiceEvse : public MemoryManaged { private: Context& context; + RemoteControlService& rcService; const unsigned int evseId; + #if MO_ENABLE_V16 + Ocpp16::TransactionServiceEvse *txServiceEvse16 = nullptr; + #endif + #if MO_ENABLE_V201 + Ocpp201::TransactionServiceEvse *txServiceEvse201 = nullptr; + #endif + #if MO_ENABLE_CONNECTOR_LOCK - UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *user) = nullptr; + MO_UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *user) = nullptr; void *onUnlockConnectorUserData = nullptr; -#endif +#endif //MO_ENABLE_CONNECTOR_LOCK public: - RemoteControlServiceEvse(Context& context, unsigned int evseId); + RemoteControlServiceEvse(Context& context, RemoteControlService& rcService, unsigned int evseId); #if MO_ENABLE_CONNECTOR_LOCK - void setOnUnlockConnector(UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData); + void setOnUnlockConnector(MO_UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData); +#endif //MO_ENABLE_CONNECTOR_LOCK - UnlockStatus unlockConnector(); -#endif + bool setup(); +#if MO_ENABLE_CONNECTOR_LOCK + #if MO_ENABLE_V16 + Ocpp16::UnlockStatus unlockConnector16(); + #endif + #if MO_ENABLE_V201 + Ocpp201::UnlockStatus unlockConnector201(); + #endif +#endif //MO_ENABLE_CONNECTOR_LOCK }; class RemoteControlService : public MemoryManaged { private: Context& context; RemoteControlServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; - - Variable *authorizeRemoteStart = nullptr; + unsigned int numEvseId = MO_NUM_EVSEID; + + #if MO_ENABLE_V16 + Ocpp16::TransactionService *txService16 = nullptr; + Ocpp16::Configuration *authorizeRemoteTxRequests = nullptr; + #endif + #if MO_ENABLE_V201 + Ocpp201::TransactionService *txService201 = nullptr; + Ocpp201::Variable *authorizeRemoteStart = nullptr; + #endif + + struct OperationCreator { + const char *operationType = nullptr; + Operation* (*create)(Context& context) = nullptr; + Operation* (*create2)(Context& context, unsigned int evseId) = nullptr; + TriggerMessageStatus (*handler3)(Context& context, int evseId) = nullptr; + }; + Vector triggerMessageHandlers; public: - RemoteControlService(Context& context, size_t numEvses); + RemoteControlService(Context& context); ~RemoteControlService(); RemoteControlServiceEvse *getEvse(unsigned int evseId); - RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize); //ChargingProfile, GroupIdToken not supported yet + bool addTriggerMessageHandler(const char *operationType, Operation* (*createOperationCb)(Context& context)); + bool addTriggerMessageHandler(const char *operationType, Operation* (*createOperationCb)(Context& context, unsigned int evseId)); + bool addTriggerMessageHandler(const char *operationType, TriggerMessageStatus (*triggerMessageHandlerCb)(Context& context, int evseId)); - RequestStartStopStatus requestStopTransaction(const char *transactionId); -}; + bool setup(); -} // namespace MicroOcpp + #if MO_ENABLE_V16 + Ocpp16::RemoteStartStopStatus remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile); -#endif // MO_ENABLE_V201 + Ocpp16::RemoteStartStopStatus remoteStopTransaction(int transactionId); + #endif //MO_ENABLE_V16 + + #if MO_ENABLE_V201 + Ocpp201::RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize); //ChargingProfile, GroupIdToken not supported yet + + Ocpp201::RequestStartStopStatus requestStopTransaction(const char *transactionId); + #endif //MO_ENABLE_V201 + + TriggerMessageStatus triggerMessage(const char *requestedMessage, int evseId = -1); + +friend RemoteControlServiceEvse; +}; +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Reservation/Reservation.cpp b/src/MicroOcpp/Model/Reservation/Reservation.cpp index eb856b5d..dd7d2736 100644 --- a/src/MicroOcpp/Model/Reservation/Reservation.cpp +++ b/src/MicroOcpp/Model/Reservation/Reservation.cpp @@ -2,36 +2,22 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include +#include -#if MO_ENABLE_RESERVATION +#include -#include +#include #include +#include #include -using namespace MicroOcpp; - -Reservation::Reservation(Model& model, unsigned int slot) : MemoryManaged("v16.Reservation.Reservation"), model(model), slot(slot) { - - snprintf(connectorIdKey, sizeof(connectorIdKey), MO_RESERVATION_CID_KEY "%u", slot); - connectorIdInt = declareConfiguration(connectorIdKey, -1, RESERVATION_FN, false, false, false); - - snprintf(expiryDateRawKey, sizeof(expiryDateRawKey), MO_RESERVATION_EXPDATE_KEY "%u", slot); - expiryDateRawString = declareConfiguration(expiryDateRawKey, "", RESERVATION_FN, false, false, false); - - snprintf(idTagKey, sizeof(idTagKey), MO_RESERVATION_IDTAG_KEY "%u", slot); - idTagString = declareConfiguration(idTagKey, "", RESERVATION_FN, false, false, false); +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION - snprintf(reservationIdKey, sizeof(reservationIdKey), MO_RESERVATION_RESID_KEY "%u", slot); - reservationIdInt = declareConfiguration(reservationIdKey, -1, RESERVATION_FN, false, false, false); +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; - snprintf(parentIdTagKey, sizeof(parentIdTagKey), MO_RESERVATION_PARENTID_KEY "%u", slot); - parentIdTagString = declareConfiguration(parentIdTagKey, "", RESERVATION_FN, false, false, false); +Reservation::Reservation(Context& context, unsigned int slot) : MemoryManaged("v16.Reservation.Reservation"), context(context), slot(slot) { - if (!connectorIdInt || !expiryDateRawString || !idTagString || !reservationIdInt || !parentIdTagString) { - MO_DBG_ERR("initialization failure"); - } } Reservation::~Reservation() { @@ -52,13 +38,73 @@ Reservation::~Reservation() { } } +bool Reservation::setup() { + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + int ret; + + ret = snprintf(connectorIdKey, sizeof(connectorIdKey), MO_RESERVATION_CID_KEY "%u", slot); + if (ret < 0 || (size_t)ret >= sizeof(connectorIdKey)) { + return false; + } + connectorIdInt = configService->declareConfiguration(connectorIdKey, -1, RESERVATION_FN, Mutability::None); + + ret = snprintf(expiryDateRawKey, sizeof(expiryDateRawKey), MO_RESERVATION_EXPDATE_KEY "%u", slot); + if (ret < 0 || (size_t)ret >= sizeof(expiryDateRawKey)) { + return false; + } + expiryDateRawString = configService->declareConfiguration(expiryDateRawKey, "", RESERVATION_FN, Mutability::None); + + ret = snprintf(idTagKey, sizeof(idTagKey), MO_RESERVATION_IDTAG_KEY "%u", slot); + if (ret < 0 || (size_t)ret >= sizeof(idTagKey)) { + return false; + } + idTagString = configService->declareConfiguration(idTagKey, "", RESERVATION_FN, Mutability::None); + + ret = snprintf(reservationIdKey, sizeof(reservationIdKey), MO_RESERVATION_RESID_KEY "%u", slot); + if (ret < 0 || (size_t)ret >= sizeof(reservationIdKey)) { + return false; + } + reservationIdInt = configService->declareConfiguration(reservationIdKey, -1, RESERVATION_FN, Mutability::None); + + ret = snprintf(parentIdTagKey, sizeof(parentIdTagKey), MO_RESERVATION_PARENTID_KEY "%u", slot); + if (ret < 0 || (size_t)ret >= sizeof(parentIdTagKey)) { + return false; + } + parentIdTagString = configService->declareConfiguration(parentIdTagKey, "", RESERVATION_FN, Mutability::None); + + if (!connectorIdInt || !expiryDateRawString || !idTagString || !reservationIdInt || !parentIdTagString) { + MO_DBG_ERR("initialization failure"); + return false; + } + + reservations = configService->findContainerOfConfiguration(connectorIdInt); + if (!reservations) { + MO_DBG_ERR("setup failure"); + return false; + } + + return true; +} + bool Reservation::isActive() { if (connectorIdInt->getInt() < 0) { //reservation invalidated return false; } - if (model.getClock().now() > getExpiryDate()) { + int32_t dt; + if (!context.getClock().delta(context.getClock().now(), getExpiryDate(), dt)) { + //expiryDate undefined + return false; + } + + if (dt >= 0) { //reservation expired return false; } @@ -91,8 +137,11 @@ int Reservation::getConnectorId() { } Timestamp& Reservation::getExpiryDate() { - if (expiryDate == MIN_TIME && *expiryDateRawString->getString()) { - expiryDate.setTime(expiryDateRawString->getString()); + if (!expiryDate.isDefined() && *expiryDateRawString->getString()) { + if (!context.getClock().parseString(expiryDateRawString->getString(), expiryDate)) { + MO_DBG_ERR("internal error"); + expiryDate = Timestamp(); + } } return expiryDate; } @@ -109,29 +158,39 @@ const char *Reservation::getParentIdTag() { return parentIdTagString->getString(); } -void Reservation::update(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag) { +bool Reservation::update(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag) { + bool success = true; + reservationIdInt->setInt(reservationId); connectorIdInt->setInt((int) connectorId); this->expiryDate = expiryDate; - char expiryDate_cstr [JSONDATE_LENGTH + 1]; - if (this->expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1)) { - expiryDateRawString->setString(expiryDate_cstr); + char expiryDateStr [MO_INTERNALTIME_SIZE]; + if (context.getClock().toInternalString(this->expiryDate, expiryDateStr, sizeof(expiryDateStr))) { + success &= expiryDateRawString->setString(expiryDateStr); + } else { + success = false; } - idTagString->setString(idTag); - parentIdTagString->setString(parentIdTag); + success &= idTagString->setString(idTag); + success &= parentIdTagString->setString(parentIdTag); - configuration_save(); + success &= reservations->commit(); + + return success; //no roll-back mechanism implemented yet } -void Reservation::clear() { +bool Reservation::clear() { + bool success = true; + connectorIdInt->setInt(-1); - expiryDate = MIN_TIME; - expiryDateRawString->setString(""); - idTagString->setString(""); + expiryDate = Timestamp(); + success &= expiryDateRawString->setString(""); + success &= idTagString->setString(""); reservationIdInt->setInt(-1); - parentIdTagString->setString(""); + success &= parentIdTagString->setString(""); + + success &= reservations->commit(); - configuration_save(); + return success; //no roll-back mechanism implemented yet } -#endif //MO_ENABLE_RESERVATION +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION diff --git a/src/MicroOcpp/Model/Reservation/Reservation.h b/src/MicroOcpp/Model/Reservation/Reservation.h index daae197a..e8e37eb0 100644 --- a/src/MicroOcpp/Model/Reservation/Reservation.h +++ b/src/MicroOcpp/Model/Reservation/Reservation.h @@ -5,16 +5,12 @@ #ifndef MO_RESERVATION_H #define MO_RESERVATION_H -#include - -#if MO_ENABLE_RESERVATION - -#include #include #include +#include #ifndef RESERVATION_FN -#define RESERVATION_FN (MO_FILENAME_PREFIX "reservations.jsn") +#define RESERVATION_FN "reservations.jsn" #endif #define MO_RESERVATION_CID_KEY "cid_" @@ -23,36 +19,47 @@ #define MO_RESERVATION_RESID_KEY "rsvid_" #define MO_RESERVATION_PARENTID_KEY "pidt_" +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION + namespace MicroOcpp { -class Model; +class Context; + +namespace Ocpp16 { + +class Configuration; +class ConfigurationContainer; class Reservation : public MemoryManaged { private: - Model& model; + Context& context; const unsigned int slot; - std::shared_ptr connectorIdInt; + ConfigurationContainer *reservations = nullptr; + + Configuration *connectorIdInt = nullptr; char connectorIdKey [sizeof(MO_RESERVATION_CID_KEY "xxx") + 1]; //"xxx" = placeholder for digits - std::shared_ptr expiryDateRawString; + Configuration *expiryDateRawString = nullptr; char expiryDateRawKey [sizeof(MO_RESERVATION_EXPDATE_KEY "xxx") + 1]; - Timestamp expiryDate = MIN_TIME; - std::shared_ptr idTagString; + Timestamp expiryDate; + Configuration *idTagString = nullptr; char idTagKey [sizeof(MO_RESERVATION_IDTAG_KEY "xxx") + 1]; - std::shared_ptr reservationIdInt; + Configuration *reservationIdInt = nullptr; char reservationIdKey [sizeof(MO_RESERVATION_RESID_KEY "xxx") + 1]; - std::shared_ptr parentIdTagString; + Configuration *parentIdTagString = nullptr; char parentIdTagKey [sizeof(MO_RESERVATION_PARENTID_KEY "xxx") + 1]; public: - Reservation(Model& model, unsigned int slot); + Reservation(Context& context, unsigned int slot); Reservation(const Reservation&) = delete; Reservation(Reservation&&) = delete; Reservation& operator=(const Reservation&) = delete; ~Reservation(); + bool setup(); + bool isActive(); //if this object contains a valid, unexpired reservation bool matches(unsigned int connectorId); @@ -64,11 +71,11 @@ class Reservation : public MemoryManaged { int getReservationId(); const char *getParentIdTag(); - void update(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag = nullptr); - void clear(); + bool update(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag = nullptr); + bool clear(); }; -} - -#endif //MO_ENABLE_RESERVATION +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION #endif diff --git a/src/MicroOcpp/Model/Reservation/ReservationService.cpp b/src/MicroOcpp/Model/Reservation/ReservationService.cpp index dde39aea..3c9d8cff 100644 --- a/src/MicroOcpp/Model/Reservation/ReservationService.cpp +++ b/src/MicroOcpp/Model/Reservation/ReservationService.cpp @@ -2,71 +2,109 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_RESERVATION - #include -#include + +#include #include -#include -#include +#include +#include +#include +#include #include #include - #include +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION + using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +ReservationService::ReservationService(Context& context) : MemoryManaged("v16.Reservation.ReservationService"), context(context) { + +} + +ReservationService::~ReservationService() { + for (size_t i = 0; i < sizeof(reservations) / sizeof(reservations[0]); i++) { + delete reservations[i]; + reservations[i] = nullptr; + } +} + +bool ReservationService::setup() { -ReservationService::ReservationService(Context& context, unsigned int numConnectors) : MemoryManaged("v16.Reservation.ReservationService"), context(context), maxReservations((int) numConnectors - 1), reservations(makeVector>(getMemoryTag())) { - if (maxReservations > 0) { - reservations.reserve((size_t) maxReservations); - for (int i = 0; i < maxReservations; i++) { - reservations.emplace_back(new Reservation(context.getModel(), i)); + numEvseId = context.getModel16().getNumEvseId(); + + maxReservations = numEvseId > 0 ? numEvseId - 1 : 0; // = number of physical connectors + for (size_t i = 0; i < maxReservations; i++) { + reservations[i] = new Reservation(context, i); + if (!reservations[i] || !reservations[i]->setup()) { + MO_DBG_ERR("OOM"); + return false; } } - reserveConnectorZeroSupportedBool = declareConfiguration("ReserveConnectorZeroSupported", true, CONFIGURATION_VOLATILE, true); - - context.getOperationRegistry().registerOperation("CancelReservation", [this] () { - return new Ocpp16::CancelReservation(*this);}); - context.getOperationRegistry().registerOperation("ReserveNow", [&context] () { - return new Ocpp16::ReserveNow(context.getModel());}); + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + reserveConnectorZeroSupportedBool = configService->declareConfiguration("ReserveConnectorZeroSupported", true, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + if (!reserveConnectorZeroSupportedBool) { + MO_DBG_ERR("declareConfiguration failed"); + return false; + } + + context.getMessageService().registerOperation("CancelReservation", [] (Context& context) -> Operation* { + return new CancelReservation(*context.getModel16().getReservationService());}); + context.getMessageService().registerOperation("ReserveNow", [] (Context& context) -> Operation* { + return new ReserveNow(context, *context.getModel16().getReservationService());}); + + return false; } void ReservationService::loop() { //check if to end reservations - for (auto& reservation : reservations) { - if (!reservation->isActive()) { + for (size_t i = 0; i < maxReservations; i++) { + if (!reservations[i]->isActive()) { continue; } - if (auto connector = context.getModel().getConnector(reservation->getConnectorId())) { + auto connectorid = reservations[i]->getConnectorId(); + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(reservations[i]->getConnectorId()) : nullptr; + if (availSvcEvse) { //check if connector went inoperative - auto cStatus = connector->getStatus(); - if (cStatus == ChargePointStatus_Faulted || cStatus == ChargePointStatus_Unavailable) { - reservation->clear(); + auto cStatus = availSvcEvse->getStatus(); + if (cStatus == MO_ChargePointStatus_Faulted || cStatus == MO_ChargePointStatus_Unavailable) { + reservations[i]->clear(); continue; } + } + auto txSvc = context.getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(reservations[i]->getConnectorId()) : nullptr; + if (txSvcEvse) { //check if other tx started at this connector (e.g. due to RemoteStartTransaction) - if (connector->getTransaction() && connector->getTransaction()->isAuthorized()) { - reservation->clear(); + if (txSvcEvse->getTransaction() && txSvcEvse->getTransaction()->isAuthorized()) { + reservations[i]->clear(); continue; } } //check if tx with same idTag or reservationId has started - for (unsigned int cId = 1; cId < context.getModel().getNumConnectors(); cId++) { - auto& transaction = context.getModel().getConnector(cId)->getTransaction(); + for (unsigned int evseId = 1; evseId < numEvseId; evseId++) { + auto txSvc = context.getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + auto transaction = txSvcEvse ? txSvcEvse->getTransaction() : nullptr; if (transaction && transaction->isAuthorized()) { - const char *cIdTag = transaction->getIdTag(); - if (transaction->getReservationId() == reservation->getReservationId() || - (cIdTag && !strcmp(cIdTag, reservation->getIdTag()))) { + const char *idTag = transaction->getIdTag(); + if (transaction->getReservationId() == reservations[i]->getReservationId() || + (idTag && !strcmp(idTag, reservations[i]->getIdTag()))) { - reservation->clear(); + reservations[i]->clear(); break; } } @@ -80,9 +118,9 @@ Reservation *ReservationService::getReservation(unsigned int connectorId) { return nullptr; //cannot fetch for connectorId 0 because multiple reservations are possible at a time } - for (auto& reservation : reservations) { - if (reservation->isActive() && reservation->matches(connectorId)) { - return reservation.get(); + for (size_t i = 0; i < maxReservations; i++) { + if (reservations[i]->isActive() && reservations[i]->matches(connectorId)) { + return reservations[i]; } } @@ -97,18 +135,18 @@ Reservation *ReservationService::getReservation(const char *idTag, const char *p Reservation *connectorReservation = nullptr; - for (auto& reservation : reservations) { - if (!reservation->isActive()) { + for (size_t i = 0; i < maxReservations; i++) { + if (!reservations[i]->isActive()) { continue; } //TODO check for parentIdTag - if (reservation->matches(idTag, parentIdTag)) { - if (reservation->getConnectorId() == 0) { - return reservation.get(); //reservation at connectorId 0 has higher priority + if (reservations[i]->matches(idTag, parentIdTag)) { + if (reservations[i]->getConnectorId() == 0) { + return reservations[i]; //reservation at connectorId 0 has higher priority } else { - connectorReservation = reservation.get(); + connectorReservation = reservations[i]; } } } @@ -132,7 +170,7 @@ Reservation *ReservationService::getReservation(unsigned int connectorId, const } } - if (reserveConnectorZeroSupportedBool && !reserveConnectorZeroSupportedBool->getBool()) { + if (!reserveConnectorZeroSupportedBool->getBool()) { //no connectorZero check - all done MO_DBG_DEBUG("no reservation"); return nullptr; @@ -143,23 +181,23 @@ Reservation *ReservationService::getReservation(unsigned int connectorId, const //Check if there are enough free connectors to satisfy all reservations at connectorId 0 unsigned int unspecifiedReservations = 0; - for (auto& reservation : reservations) { - if (reservation->isActive() && reservation->getConnectorId() == 0) { + for (size_t i = 0; i < maxReservations; i++) { + if (reservations[i]->isActive() && reservations[i]->getConnectorId() == 0) { unspecifiedReservations++; - blockingReservation = reservation.get(); + blockingReservation = reservations[i]; } } unsigned int availableCount = 0; - for (unsigned int cId = 1; cId < context.getModel().getNumConnectors(); cId++) { - if (cId == connectorId) { + for (unsigned int eId = 1; eId < numEvseId; eId++) { + if (eId == connectorId) { //don't count this connector continue; } - if (auto connector = context.getModel().getConnector(cId)) { - if (connector->getStatus() == ChargePointStatus_Available) { - availableCount++; - } + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(eId) : nullptr; + if (availSvcEvse && availSvcEvse->getStatus() == MO_ChargePointStatus_Available) { + availableCount++; } } @@ -173,9 +211,9 @@ Reservation *ReservationService::getReservation(unsigned int connectorId, const } Reservation *ReservationService::getReservationById(int reservationId) { - for (auto& reservation : reservations) { - if (reservation->isActive() && reservation->getReservationId() == reservationId) { - return reservation.get(); + for (size_t i = 0; i < maxReservations; i++) { + if (reservations[i]->isActive() && reservations[i]->getReservationId() == reservationId) { + return reservations[i]; } } @@ -204,9 +242,9 @@ bool ReservationService::updateReservation(int reservationId, unsigned int conne } //update free reservation slot - for (auto& reservation : reservations) { - if (!reservation->isActive()) { - reservation->update(reservationId, connectorId, expiryDate, idTag, parentIdTag); + for (size_t i = 0; i < maxReservations; i++) { + if (!reservations[i]->isActive()) { + reservations[i]->update(reservationId, connectorId, expiryDate, idTag, parentIdTag); return true; } } @@ -215,4 +253,4 @@ bool ReservationService::updateReservation(int reservationId, unsigned int conne return false; } -#endif //MO_ENABLE_RESERVATION +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION diff --git a/src/MicroOcpp/Model/Reservation/ReservationService.h b/src/MicroOcpp/Model/Reservation/ReservationService.h index 367f1f59..1d698be0 100644 --- a/src/MicroOcpp/Model/Reservation/ReservationService.h +++ b/src/MicroOcpp/Model/Reservation/ReservationService.h @@ -5,30 +5,37 @@ #ifndef MO_RESERVATIONSERVICE_H #define MO_RESERVATIONSERVICE_H -#include - -#if MO_ENABLE_RESERVATION +#include #include +#include #include +#include -#include +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION namespace MicroOcpp { class Context; +namespace Ocpp16 { + class ReservationService : public MemoryManaged { private: Context& context; - const int maxReservations; // = number of physical connectors - Vector> reservations; + Reservation *reservations [MO_NUM_EVSEID - 1] = {nullptr}; // = number of physical connectors + size_t maxReservations = MO_NUM_EVSEID - 1; + + unsigned int numEvseId = MO_NUM_EVSEID; - std::shared_ptr reserveConnectorZeroSupportedBool; + Configuration *reserveConnectorZeroSupportedBool = nullptr; public: - ReservationService(Context& context, unsigned int numConnectors); + ReservationService(Context& context); + ~ReservationService(); + + bool setup(); void loop(); @@ -47,7 +54,7 @@ class ReservationService : public MemoryManaged { bool updateReservation(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag = nullptr); }; -} - -#endif //MO_ENABLE_RESERVATION +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION #endif diff --git a/src/MicroOcpp/Model/Reset/ResetDefs.h b/src/MicroOcpp/Model/Reset/ResetDefs.h index baa5048d..154cb30d 100644 --- a/src/MicroOcpp/Model/Reset/ResetDefs.h +++ b/src/MicroOcpp/Model/Reset/ResetDefs.h @@ -9,10 +9,10 @@ #if MO_ENABLE_V201 -typedef enum ResetType { - ResetType_Immediate, - ResetType_OnIdle -} ResetType; +typedef enum MO_ResetType { + MO_ResetType_Immediate, + MO_ResetType_OnIdle +} MO_ResetType; typedef enum ResetStatus { ResetStatus_Accepted, diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index 2eef02b8..dad62615 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -3,50 +3,99 @@ // MIT License #include -#include + +#include #include -#include -#include #include - -#include -#include #include -#include - +#include +#include #include -#include - +#include +#include #include +#if MO_ENABLE_V16 + #ifndef MO_RESET_DELAY -#define MO_RESET_DELAY 10000 +#define MO_RESET_DELAY 10 #endif +#if MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266)) + +namespace MicroOcpp { +void defaultExecuteResetImpl() { + MO_DBG_DEBUG("Perform ESP reset"); + ESP.restart(); +} +} //namespace MicroOcpp + +#else + +namespace MicroOcpp { +void (*defaultExecuteResetImpl)() = nullptr; +} //namespace MicroOcpp + +#endif //MO_PLATFORM + using namespace MicroOcpp; -ResetService::ResetService(Context& context) +Ocpp16::ResetService::ResetService(Context& context) : MemoryManaged("v16.Reset.ResetService"), context(context) { - resetRetriesInt = declareConfiguration("ResetRetries", 2); - registerConfigurationValidator("ResetRetries", VALIDATE_UNSIGNED_INT); +} + +Ocpp16::ResetService::~ResetService() { - context.getOperationRegistry().registerOperation("Reset", [&context] () { - return new Ocpp16::Reset(context.getModel());}); } + +bool Ocpp16::ResetService::setup() { -ResetService::~ResetService() { + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + resetRetriesInt = configService->declareConfiguration("ResetRetries", 2); + if (!resetRetriesInt) { + MO_DBG_ERR("setup failure"); + return false; + } + if (!configService->registerValidator("ResetRetries", VALIDATE_UNSIGNED_INT)) { + MO_DBG_ERR("setup failure"); + return false; + } + + if (!executeResetCb && defaultExecuteResetImpl) { + MO_DBG_DEBUG("setup default reset function"); + executeResetCb = [] (bool, void*) { + defaultExecuteResetImpl(); + return false; + }; + } + + context.getMessageService().registerOperation("Reset", [] (Context& context) -> Operation* { + return new Reset(*context.getModel16().getResetService());}); + + return true; } -void ResetService::loop() { +void Ocpp16::ResetService::loop() { + + auto& clock = context.getClock(); + + int32_t dtLastResetAttempt; + if (!clock.delta(clock.getUptime(), lastResetAttempt, dtLastResetAttempt)) { + dtLastResetAttempt = MO_RESET_DELAY; + } - if (outstandingResetRetries > 0 && mocpp_tick_ms() - t_resetRetry >= MO_RESET_DELAY) { - t_resetRetry = mocpp_tick_ms(); + if (outstandingResetRetries > 0 && dtLastResetAttempt >= MO_RESET_DELAY) { + lastResetAttempt = clock.getUptime(); outstandingResetRetries--; - if (executeReset) { + if (executeResetCb) { MO_DBG_INFO("Reset device"); - executeReset(isHardReset); + executeResetCb(isHardReset, executeResetUserData); } else { MO_DBG_ERR("No Reset function set! Abort"); outstandingResetRetries = 0; @@ -56,88 +105,146 @@ void ResetService::loop() { MO_DBG_ERR("Reset device failure. Abort"); - ChargePointStatus cpStatus = ChargePointStatus_UNDEFINED; - if (context.getModel().getNumConnectors() > 0) { - cpStatus = context.getModel().getConnector(0)->getStatus(); - } + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcCp = availSvc ? availSvc->getEvse(0) : nullptr; + auto cpStatus = availSvcCp ? availSvcCp->getStatus() : MO_ChargePointStatus_UNDEFINED; + + MO_ErrorData errorCode; + mo_ErrorData_init(&errorCode); + mo_ErrorData_setErrorCode(&errorCode, "ResetFailure"); - auto statusNotification = makeRequest(new Ocpp16::StatusNotification( + auto statusNotification = makeRequest(context, new StatusNotification( + context, 0, - cpStatus, //will be determined in StatusNotification::initiate - context.getModel().getClock().now(), - "ResetFailure")); - statusNotification->setTimeout(60000); - context.initiateRequest(std::move(statusNotification)); + cpStatus, + context.getClock().now(), + errorCode)); + statusNotification->setTimeout(60); + context.getMessageService().sendRequest(std::move(statusNotification)); } } } -void ResetService::setPreReset(std::function preReset) { - this->preReset = preReset; +void Ocpp16::ResetService::setNotifyReset(bool (*notifyResetCb)(bool isHard, void *userData), void *userData) { + this->notifyResetCb = notifyResetCb; + this->notifyResetUserData = userData; +} + +bool Ocpp16::ResetService::isPreResetDefined() { + return notifyResetCb != nullptr; } -std::function ResetService::getPreReset() { - return this->preReset; +bool Ocpp16::ResetService::notifyReset(bool isHard) { + return notifyResetCb(isHard, notifyResetUserData); } -void ResetService::setExecuteReset(std::function executeReset) { - this->executeReset = executeReset; +void Ocpp16::ResetService::setExecuteReset(bool (*executeReset)(bool isHard, void *userData), void *userData) { + this->executeResetCb = executeReset; + this->executeResetUserData = userData; } -std::function ResetService::getExecuteReset() { - return this->executeReset; +bool Ocpp16::ResetService::isExecuteResetDefined() { + return executeResetCb != nullptr; } -void ResetService::initiateReset(bool isHard) { +void Ocpp16::ResetService::initiateReset(bool isHard) { + + for (unsigned int eId = 0; eId < context.getModel16().getNumEvseId(); eId++) { + auto txSvc = context.getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(eId) : nullptr; + if (txSvcEvse) { + txSvcEvse->endTransaction(nullptr, isHard ? "HardReset" : "SoftReset"); + } + } + isHardReset = isHard; outstandingResetRetries = 1 + resetRetriesInt->getInt(); //one initial try + no. of retries if (outstandingResetRetries > 5) { MO_DBG_ERR("no. of reset trials exceeds 5"); outstandingResetRetries = 5; } - t_resetRetry = mocpp_tick_ms(); + lastResetAttempt = context.getClock().getUptime(); } -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266)) -std::function MicroOcpp::makeDefaultResetFn() { - return [] (bool isHard) { - MO_DBG_DEBUG("Perform ESP reset"); - ESP.restart(); - }; -} -#endif //MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266)) +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { -ResetService::ResetService(Context& context) - : MemoryManaged("v201.Reset.ResetService"), context(context), evses(makeVector(getMemoryTag())) { +Ocpp201::ResetService::ResetService(Context& context) + : MemoryManaged("v201.Reset.ResetService"), context(context) { + +} - auto varService = context.getModel().getVariableService(); +Ocpp201::ResetService::~ResetService() { + for (unsigned int i = 0; i < numEvseId; i++) { + delete evses[i]; + evses[i] = 0; + } +} + +bool Ocpp201::ResetService::setup() { + auto varService = context.getModel201().getVariableService(); + if (!varService) { + return false; + } resetRetriesInt = varService->declareVariable("OCPPCommCtrlr", "ResetRetries", 0); + if (!resetRetriesInt) { + MO_DBG_ERR("setup failure"); + return false; + } + + if (defaultExecuteResetImpl) { + if (!getEvse(0)) { + MO_DBG_ERR("setup failure"); + } + + if (!getEvse(0)->executeReset) { + MO_DBG_DEBUG("setup default reset function"); + getEvse(0)->executeReset = [] (unsigned int, void*) -> bool { + defaultExecuteResetImpl(); + return true; + }; + } + } + + numEvseId = context.getModel201().getNumEvseId(); + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i) || !getEvse(i)->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } + } - context.getOperationRegistry().registerOperation("Reset", [this] () { - return new Ocpp201::Reset(*this);}); + context.getMessageService().registerOperation("Reset", [] (Context& context) -> Operation* { + return new Reset(*context.getModel201().getResetService());}); + + return true; } -ResetService::~ResetService() { +Ocpp201::ResetService::Evse::Evse(Context& context, ResetService& resetService, unsigned int evseId) : MemoryManaged("v201.Reset.ResetService"), context(context), resetService(resetService), evseId(evseId) { } -ResetService::Evse::Evse(Context& context, ResetService& resetService, unsigned int evseId) : context(context), resetService(resetService), evseId(evseId) { - auto varService = context.getModel().getVariableService(); - varService->declareVariable(ComponentId("EVSE", evseId >= 1 ? evseId : -1), "AllowReset", true, Variable::Mutability::ReadOnly, false); +bool Ocpp201::ResetService::Evse::setup() { + auto varService = context.getModel201().getVariableService(); + if (!varService) { + return false; + } + varService->declareVariable(ComponentId("EVSE", evseId >= 1 ? evseId : -1), "AllowReset", true, Mutability::ReadOnly, false); + return true; } -void ResetService::Evse::loop() { +void Ocpp201::ResetService::Evse::loop() { + + auto& clock = context.getClock(); if (outstandingResetRetries && awaitTxStop) { for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) { //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds > 0 - auto txService = context.getModel().getTransactionService(); + auto txService = context.getModel201().getTransactionService(); if (txService && txService->getEvse(eId) && txService->getEvse(eId)->getTransaction()) { auto tx = txService->getEvse(eId)->getTransaction(); @@ -151,111 +258,106 @@ void ResetService::Evse::loop() { awaitTxStop = false; MO_DBG_INFO("Reset - tx stopped"); - t_resetRetry = mocpp_tick_ms(); // wait for some more time until final reset + lastResetAttempt = clock.getUptime(); // wait for some more time until final reset + } + + int32_t dtLastResetAttempt; + if (!clock.delta(clock.getUptime(), lastResetAttempt, dtLastResetAttempt)) { + dtLastResetAttempt = MO_RESET_DELAY; } - if (outstandingResetRetries && mocpp_tick_ms() - t_resetRetry >= MO_RESET_DELAY) { - t_resetRetry = mocpp_tick_ms(); + if (outstandingResetRetries && dtLastResetAttempt >= MO_RESET_DELAY) { + lastResetAttempt = clock.getUptime(); outstandingResetRetries--; MO_DBG_INFO("Reset device"); - bool success = executeReset(); + bool success = executeReset(evseId, executeResetUserData); if (success) { outstandingResetRetries = 0; if (evseId != 0) { //Set this EVSE Available again - if (auto connector = context.getModel().getConnector(evseId)) { - connector->setAvailabilityVolatile(true); + + auto availSvc = context.getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (availSvcEvse) { + availSvcEvse->setAvailable(this); } } } else if (!outstandingResetRetries) { MO_DBG_ERR("Reset device failure"); + auto availabilityService = context.getModel201().getAvailabilityService(); + if (evseId == 0) { //Set all EVSEs Available again - for (unsigned int cId = 0; cId < context.getModel().getNumConnectors(); cId++) { - auto connector = context.getModel().getConnector(cId); - connector->setAvailabilityVolatile(true); + for (unsigned int eId = 0; eId < resetService.numEvseId; eId++) { + auto availSvc = context.getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(eId) : nullptr; + if (availSvcEvse) { + availSvcEvse->setAvailable(this); + } } } else { //Set only this EVSE Available - if (auto connector = context.getModel().getConnector(evseId)) { - connector->setAvailabilityVolatile(true); + auto availSvc = context.getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (availSvcEvse) { + availSvcEvse->setAvailable(this); } } } } } -ResetService::Evse *ResetService::getEvse(unsigned int evseId) { - for (size_t i = 0; i < evses.size(); i++) { - if (evses[i].evseId == evseId) { - return &evses[i]; - } - } - return nullptr; -} - -ResetService::Evse *ResetService::getOrCreateEvse(unsigned int evseId) { - if (auto evse = getEvse(evseId)) { - return evse; - } - - if (evseId >= MO_NUM_EVSEID) { +Ocpp201::ResetService::Evse *Ocpp201::ResetService::getEvse(unsigned int evseId) { + if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; } - evses.emplace_back(context, *this, evseId); - return &evses.back(); + if (!evses[evseId]) { + evses[evseId] = new Evse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + + return evses[evseId]; } -void ResetService::loop() { - for (Evse& evse : evses) { - evse.loop(); +void Ocpp201::ResetService::loop() { + for (unsigned i = 0; i < numEvseId; i++) { + if (evses[i]) { + evses[i]->loop(); + } } } -void ResetService::setNotifyReset(std::function notifyReset, unsigned int evseId) { - Evse *evse = getOrCreateEvse(evseId); +void Ocpp201::ResetService::setNotifyReset(unsigned int evseId, bool (*notifyReset)(MO_ResetType, unsigned int evseId, void *userData), void *userData) { + Evse *evse = getEvse(evseId); if (!evse) { MO_DBG_ERR("evseId not found"); return; } evse->notifyReset = notifyReset; + evse->notifyResetUserData = userData; } -std::function ResetService::getNotifyReset(unsigned int evseId) { - Evse *evse = getOrCreateEvse(evseId); - if (!evse) { - MO_DBG_ERR("evseId not found"); - return nullptr; - } - return evse->notifyReset; -} - -void ResetService::setExecuteReset(std::function executeReset, unsigned int evseId) { - Evse *evse = getOrCreateEvse(evseId); +void Ocpp201::ResetService::setExecuteReset(unsigned int evseId, bool (*executeReset)(unsigned int evseId, void *userData), void *userData) { + Evse *evse = getEvse(evseId); if (!evse) { MO_DBG_ERR("evseId not found"); return; } evse->executeReset = executeReset; + evse->executeResetUserData = userData; } -std::function ResetService::getExecuteReset(unsigned int evseId) { - Evse *evse = getOrCreateEvse(evseId); - if (!evse) { - MO_DBG_ERR("evseId not found"); - return nullptr; - } - return evse->executeReset; -} - -ResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) { +ResetStatus Ocpp201::ResetService::initiateReset(MO_ResetType type, unsigned int evseId) { auto evse = getEvse(evseId); if (!evse) { MO_DBG_ERR("evseId not found"); @@ -268,11 +370,11 @@ ResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) { } //Check if EVSEs are ready for Reset - for (unsigned int eId = evseId; eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) { + for (unsigned int eId = evseId; eId < (evseId == 0 ? numEvseId : evseId + 1); eId++) { //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds if (auto it = getEvse(eId)) { - if (it->notifyReset && !it->notifyReset(type)) { + if (it->notifyReset && !it->notifyReset(type, eId, it->notifyResetUserData)) { MO_DBG_INFO("EVSE %u not able to Reset", evseId); return ResetStatus_Rejected; } @@ -282,14 +384,19 @@ ResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) { //Set EVSEs Unavailable if (evseId == 0) { //Set all EVSEs Unavailable - for (unsigned int cId = 0; cId < context.getModel().getNumConnectors(); cId++) { - auto connector = context.getModel().getConnector(cId); - connector->setAvailabilityVolatile(false); + for (unsigned int eId = 0; eId < numEvseId; eId++) { + auto availSvc = context.getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(eId) : nullptr; + if (availSvcEvse) { + availSvcEvse->setUnavailable(this); + } } } else { //Set this EVSE Unavailable - if (auto connector = context.getModel().getConnector(evseId)) { - connector->setAvailabilityVolatile(false); + auto availSvc = context.getModel201().getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(evseId) : nullptr; + if (availSvcEvse) { + availSvcEvse->setUnavailable(this); } } @@ -299,13 +406,13 @@ ResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) { for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) { //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds > 0 - auto txService = context.getModel().getTransactionService(); + auto txService = context.getModel201().getTransactionService(); if (txService && txService->getEvse(eId) && txService->getEvse(eId)->getTransaction()) { auto tx = txService->getEvse(eId)->getTransaction(); if (tx->active) { //Tx in progress. Check behavior - if (type == ResetType_Immediate) { - txService->getEvse(eId)->abortTransaction(Transaction::StoppedReason::ImmediateReset, TransactionEventTriggerReason::ResetCommand); + if (type == MO_ResetType_Immediate) { + txService->getEvse(eId)->abortTransaction(MO_TxStoppedReason_ImmediateReset, MO_TxEventTriggerReason_ResetCommand); } else { scheduled = true; break; @@ -322,12 +429,11 @@ ResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) { } else { evse->outstandingResetRetries = 1 + resetRetriesInt->getInt(); //one initial try + no. of retries } - evse->t_resetRetry = mocpp_tick_ms(); + evse->lastResetAttempt = context.getClock().getUptime(); evse->awaitTxStop = scheduled; return scheduled ? ResetStatus_Scheduled : ResetStatus_Accepted; } } //namespace MicroOcpp -} //namespace Ocpp201 #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Reset/ResetService.h b/src/MicroOcpp/Model/Reset/ResetService.h index a84e82d6..a3d2187c 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.h +++ b/src/MicroOcpp/Model/Reset/ResetService.h @@ -5,106 +5,114 @@ #ifndef MO_RESETSERVICE_H #define MO_RESETSERVICE_H -#include - #include -#include #include +#include #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { class Context; +namespace Ocpp16 { + +class Configuration; + class ResetService : public MemoryManaged { private: Context& context; - std::function preReset; //true: reset is possible; false: reject reset; Await: need more time to determine - std::function executeReset; //please disconnect WebSocket (MO remains initialized), shut down device and restart with normal initialization routine; on failure reconnect WebSocket + bool (*notifyResetCb)(bool isHard, void *userData) = nullptr; //true: reset is possible; false: reject reset; Await: need more time to determine + void *notifyResetUserData = nullptr; + bool (*executeResetCb)(bool isHard, void *userData) = nullptr;; //please disconnect WebSocket (MO remains initialized), shut down device and restart with normal initialization routine; on failure reconnect WebSocket + void *executeResetUserData = nullptr; unsigned int outstandingResetRetries = 0; //0 = do not reset device bool isHardReset = false; - unsigned long t_resetRetry; + Timestamp lastResetAttempt; - std::shared_ptr resetRetriesInt; + Configuration *resetRetriesInt = nullptr; public: ResetService(Context& context); ~ResetService(); + + void setNotifyReset(bool (*notifyReset)(bool isHard, void *userData), void *userData); + + void setExecuteReset(bool (*executeReset)(bool isHard, void *userData), void *userData); + + bool setup(); void loop(); - void setPreReset(std::function preReset); - std::function getPreReset(); + bool isPreResetDefined(); + bool notifyReset(bool isHard); - void setExecuteReset(std::function executeReset); - std::function getExecuteReset(); + bool isExecuteResetDefined(); void initiateReset(bool isHard); }; -} //end namespace MicroOcpp - -#if MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266)) - -namespace MicroOcpp { - -std::function makeDefaultResetFn(); - -} - -#endif //MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266)) +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 namespace MicroOcpp { -class Variable; - namespace Ocpp201 { +class Variable; + class ResetService : public MemoryManaged { private: Context& context; - struct Evse { + struct Evse : public MemoryManaged { Context& context; ResetService& resetService; const unsigned int evseId; - std::function notifyReset; //notify firmware about a Reset command. Return true if Reset is okay; false if Reset cannot be executed - std::function executeReset; //execute Reset of connector. Return true if Reset will be executed; false if there is a failure to Reset + bool (*notifyReset)(MO_ResetType, unsigned int evseId, void *userData) = nullptr; //notify firmware about a Reset command. Return true if Reset is okay; false if Reset cannot be executed + void *notifyResetUserData = nullptr; + bool (*executeReset)(unsigned int evseId, void *userData) = nullptr; //execute Reset of connector. Return true if Reset will be executed; false if there is a failure to Reset + void *executeResetUserData = nullptr; unsigned int outstandingResetRetries = 0; //0 = do not reset device - unsigned long t_resetRetry; + Timestamp lastResetAttempt; bool awaitTxStop = false; Evse(Context& context, ResetService& resetService, unsigned int evseId); + bool setup(); + void loop(); }; - Vector evses; + Evse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; Evse *getEvse(unsigned int connectorId); - Evse *getOrCreateEvse(unsigned int connectorId); Variable *resetRetriesInt = nullptr; public: ResetService(Context& context); ~ResetService(); - - void loop(); - void setNotifyReset(std::function notifyReset, unsigned int evseId = 0); - std::function getNotifyReset(unsigned int evseId = 0); + void setNotifyReset(unsigned int evseId, bool (*notifyReset)(MO_ResetType, unsigned int evseId, void *userData), void *userData); - void setExecuteReset(std::function executeReset, unsigned int evseId = 0); - std::function getExecuteReset(unsigned int evseId = 0); + void setExecuteReset(unsigned int evseId, bool (*executeReset)(unsigned int evseId, void *userData), void *userData); + + bool setup(); + + void loop(); - ResetStatus initiateReset(ResetType type, unsigned int evseId = 0); + ResetStatus initiateReset(MO_ResetType type, unsigned int evseId = 0); }; } //namespace Ocpp201 diff --git a/src/MicroOcpp/Model/SecurityEvent/SecurityEvent.h b/src/MicroOcpp/Model/SecurityEvent/SecurityEvent.h new file mode 100644 index 00000000..fd36fd63 --- /dev/null +++ b/src/MicroOcpp/Model/SecurityEvent/SecurityEvent.h @@ -0,0 +1,16 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_SECURITYEVENTSERVICE_H +#define MO_SECURITYEVENTSERVICE_H + +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT + +// Length of field `type` of a SecurityEventNotificationRequest (1.49.1) +#define MO_SECURITY_EVENT_TYPE_LENGTH 50 + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT +#endif diff --git a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp new file mode 100644 index 00000000..df6f9a7a --- /dev/null +++ b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp @@ -0,0 +1,619 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT + +/* Queue naming schema + * + * The SecurityEvent queue keeps historic events for the GetLogs operation / later troubleshooting and it keeps + * outstanding events in case the charger is offline. + * + * To store the queue on the file system, it is partitioned into fewer, but larger files. Most filesystems come with + * an overhead if storing small, but many files. So MO uses one file to store up to 20 entries. This results in a + * two-dimensional index to address a SecurityEvent: first the position of the file on the filesystem and second the + * position of the SecurityEvent in the file. So a SecurityEvent is addressed by fileNr x entryNr. + * + * The SecurityEvent queue the queue consists of two sections, the historic section, followed by the outstanding + * section. The first element of the historic section is the "begin", the first of the outstanding is the "front", + * and the outstanding section is delimited by the "end" index which is one address higher than the last element. + * This results in the following arithmetics: + * + * front - begin: number of historic elements + * front == begin: true if no historic elements exist + * end - front: number of outstanding elements to be sent + * end == front: true if no outstanding elements exist + * end - begin: number of total elements on flash + * end == front: true if there are no files on flash + * + * The fileNr is a ring index which rolls over at MO_SECLOG_INDEX_MAX. Going to the next file from index + * (MO_SECLOG_INDEX_MAX - 1) results in the index 0. This breaks the < and > comparators, because the previous + * logfile index may be numerically higher than the following logfile index. This is handled by some explicit + * modulus operations. The other index, entryNr, is an ordinary index though and does not need any of the ring + * index arithmetics. + */ + +using namespace MicroOcpp; + +SecurityEventService::SecurityEventService(Context& context) : MemoryManaged("v201.Security.SecurityEventService"), context(context) { + +} + +bool SecurityEventService::setup() { + + ocppVersion = context.getOcppVersion(); + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("initialization error"); + return false; + } + + //if transactions can start before the BootNotification succeeds + timeAdjustmentReportingThresholdIntV16 = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "TimeAdjustmentReportingThreshold", 20); + if (!timeAdjustmentReportingThresholdIntV16) { + MO_DBG_ERR("initialization error"); + return false; + } + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + + auto varService = context.getModel201().getVariableService(); + if (!varService) { + MO_DBG_ERR("initialization error"); + return false; + } + + timeAdjustmentReportingThresholdIntV201 = varService->declareVariable("ClockCtrlr", "TimeAdjustmentReportingThreshold", 20); + if (!timeAdjustmentReportingThresholdIntV201) { + return false; + } + } + #endif //MO_ENABLE_V201 + + auto& clock = context.getClock(); + trackUnixTime = clock.now(); + trackUptime = clock.getUptime(); + + connection = context.getConnection(); + if (!connection) { + MO_DBG_ERR("setup failure"); + return false; + } + + filesystem = context.getFilesystem(); + if (!filesystem) { + MO_DBG_DEBUG("volatile mode"); + } + + if (filesystem) { + if (!FilesystemUtils::loadRingIndex(filesystem, MO_SECLOG_FN_PREFIX, MO_SECLOG_INDEX_MAX, &fileNrBegin, &fileNrEnd)) { + MO_DBG_ERR("failed to init security event log"); + return false; + } + + // find front file and front securityEvent inside that file + fileNrFront = fileNrEnd; + unsigned int fileNrSize = (fileNrEnd + MO_SECLOG_INDEX_MAX - fileNrBegin) % MO_SECLOG_INDEX_MAX; + + for (unsigned int i = 1; i <= fileNrSize; i++) { + + unsigned int fileNr = (fileNrEnd + MO_SECLOG_INDEX_MAX - i) % MO_SECLOG_INDEX_MAX; // iterate logFiles from back + + char fn [MO_MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fn, sizeof(fn), MO_SECLOG_FN_PREFIX "%u.jsn", fileNr); + if (ret < 0 || (size_t)ret >= sizeof(fn)) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + + JsonDoc logFile (0); + auto status = FilesystemUtils::loadJson(filesystem, fn, logFile, getMemoryTag()); + switch (status) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + break; //file gap - will skip this fileNr + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", fn); + break; + } + + if (status != FilesystemUtils::LoadStatus::Success) { + continue; + } + + unsigned int nextEntryNrFront = (unsigned int)logFile.size(); + unsigned int nextEntryNrEnd = (unsigned int)logFile.size(); + + for (size_t entryNr = 0; entryNr < logFile.size(); entryNr++) { + auto securityEvent = logFile[entryNr]; + if (securityEvent.containsKey("atnr")) { + // this is the first entry to be sent. Found front candidate + nextEntryNrFront = (unsigned int)entryNr; + break; + } + } + + if (nextEntryNrFront != nextEntryNrEnd) { + // This file contains outstanding SecurityEvents to be sent. These could be the front of the queue + fileNrFront = fileNr; + entryNrFront = nextEntryNrFront; + entryNrEnd = nextEntryNrEnd; + } else { + // This file does not contain any events to send. So this loop iteration already skipped over the front file + break; + } + + if (nextEntryNrFront > 0) { + // This file contains both historic and outstanding events. It is safe to say that this is the front and to stop the search + break; + } + + // continue: This file contains only events to send and no historic events. This could be the front file + // or in the middle of the queue. Took it as candidate, but continue searching at fileNr-1 + } + } + + return true; +} + +void SecurityEventService::loop() { + + auto& clock = context.getClock(); + Timestamp unixTime = clock.now(); + Timestamp uptime = clock.getUptime(); + + if (unixTime.isUnixTime() && trackUnixTime.isUnixTime()) { + // Got initial time - check if clock drift exceeds timeAdjustmentReportingThreshold + + int32_t deltaUnixTime = 0; //unix time jumps over clock adjustments + clock.delta(unixTime, trackUnixTime, deltaUnixTime); + + int32_t deltaUptime = 0; //uptime is steadily increasing without gaps + clock.delta(uptime, trackUptime, deltaUptime); + + int32_t timeAdjustment = deltaUnixTime - deltaUptime; //should be 0 if clock is not adjusted, otherwise the number of seconds adjusted + + int timeAdjustmentReportingThreshold = 0; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + timeAdjustmentReportingThreshold = timeAdjustmentReportingThresholdIntV16->getInt(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + timeAdjustmentReportingThreshold = timeAdjustmentReportingThresholdIntV201->getInt(); + } + #endif //MO_ENABLE_V201 + + if (abs(timeAdjustment) >= timeAdjustmentReportingThreshold && timeAdjustmentReportingThreshold > 0) { + triggerSecurityEvent("SettingSystemTime"); + } + } + trackUnixTime = unixTime; + trackUptime = uptime; + + if (!filesystem) { + // Nothing else to do in volatile mode + return; + } + + if (isSecurityEventInProgress) { + //avoid sending multiple messages at the same time + return; + } + + if (!connection->isConnected()) { + //don't use up attempts while offline + return; + } + + int32_t dtAttemptTime; + if (!attemptTime.isDefined() || !context.getClock().delta(context.getClock().now(), attemptTime, dtAttemptTime)) { + dtAttemptTime = 0; + } + + if (entryNrFront != entryNrEnd && + dtAttemptTime >= (int32_t)attemptNr * MO_SECLOG_SEND_ATTEMPT_INTERVAL) { + + // Send SecurityEventNotification + + attemptTime = context.getClock().now(); + + char type [MO_SECURITY_EVENT_TYPE_LENGTH + 1]; + Timestamp timestamp; + unsigned int attemptNr; + + bool success = fetchSecurityEventFront(type, timestamp, attemptNr); + if (!success) { + return; //retry + } + + this->attemptNr = attemptNr; + + auto securityEventNotification = makeRequest(context, new SecurityEventNotification(context, type, timestamp)); + securityEventNotification->setOnReceiveConf([this] (JsonObject) { + isSecurityEventInProgress = false; + + MO_DBG_DEBUG("completed front securityEvent"); + advanceSecurityEventFront(); + }); + securityEventNotification->setOnAbort([this] () { + isSecurityEventInProgress = false; + + if (this->attemptNr >= MO_SECLOG_SEND_ATTEMPTS) { + MO_DBG_ERR("final SecurityEventNotification attempt failed"); + advanceSecurityEventFront(); + } else { + MO_DBG_DEBUG("retry SecurityEventNotification"); + } + }); + isSecurityEventInProgress = true; + + securityEventNotification->setTimeout(std::min(20, MO_SECLOG_SEND_ATTEMPT_INTERVAL)); + context.getMessageService().sendRequest(std::move(securityEventNotification)); + } +} + +bool SecurityEventService::fetchSecurityEventFront(char *type, Timestamp& timestamp, unsigned int& attemptNr) { + if (!filesystem) { + MO_DBG_ERR("invalid state"); + return false; + } + + if (fileNrFront == fileNrEnd || entryNrFront == entryNrEnd) { + MO_DBG_ERR("invalid state"); + return false; + } + + char fn [MO_MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fn, sizeof(fn), MO_SECLOG_FN_PREFIX "%u.jsn", fileNrFront); + if (ret < 0 || (size_t)ret >= sizeof(fn)) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + + JsonDoc logFile (0); + auto status = FilesystemUtils::loadJson(filesystem, fn, logFile, getMemoryTag()); + switch (status) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + advanceSecurityEventFront(); + break; //file gap - will skip this fileNr + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + break; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + MO_DBG_ERR("failed to load %s", fn); + advanceSecurityEventFront(); + break; + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", fn); + break; + } + + if (status != FilesystemUtils::LoadStatus::Success) { + return false; + } + + if (logFile.size() != (size_t)entryNrEnd) { + MO_DBG_ERR("deserialization error: %s", fn); + advanceSecurityEventFront(); + return false; + } + + JsonObject securityEvent = logFile[entryNrFront]; + + const char *typeIn = securityEvent["type"] | (const char*)nullptr; + ret = -1; + if (typeIn) { + ret = snprintf(type, MO_SECURITY_EVENT_TYPE_LENGTH + 1, "%s", typeIn); + } + if (!typeIn || ret < 0 || ret >= MO_SECURITY_EVENT_TYPE_LENGTH) { + MO_DBG_ERR("deserialization error"); + advanceSecurityEventFront(); + return false; + } + + const char *time = securityEvent["time"] | (const char*)nullptr; + if (!time || !context.getClock().parseString(time, timestamp)) { + MO_DBG_ERR("deserialization error"); + advanceSecurityEventFront(); + return false; + } + + attemptNr = securityEvent["atnr"] | MO_SECLOG_SEND_ATTEMPTS; + if (attemptNr >= MO_SECLOG_SEND_ATTEMPTS) { + MO_DBG_ERR("invalid attemptNr"); + return false; + } + + attemptNr++; + + if (attemptNr < MO_SECLOG_SEND_ATTEMPTS) { + securityEvent["atnr"] = attemptNr; + } else { + securityEvent.remove("atnr"); + } + + if (FilesystemUtils::storeJson(filesystem, fn, logFile) != FilesystemUtils::StoreStatus::Success) { + MO_DBG_ERR("FS error: %s", fn); + return false; + } + + return true; +} + +bool SecurityEventService::advanceSecurityEventFront() { + + if (!filesystem) { + MO_DBG_ERR("invalid state"); + return false; + } + + // this invalidates attemptTime of previous securityEvent + attemptTime = Timestamp(); + attemptNr = 0; + + // Advance entryNrFront + entryNrFront++; + + if (entryNrFront != entryNrEnd) { + // This file has further securityEvents. Done here + return true; + } + + // Current front file done. Advance front file + fileNrFront = (fileNrFront + 1) % MO_SECLOG_INDEX_MAX; + entryNrFront = 0; + entryNrEnd = 0; + + if (fileNrFront == fileNrEnd) { + // All logs sent. Done here + return true; + } + + // Check if new front logfile already contains entries and if so, update entryNrEnd. Skip corrupt files if necessary + + for (; fileNrFront != fileNrEnd; fileNrFront = (fileNrFront + 1) % MO_SECLOG_INDEX_MAX) { + + char fn [MO_MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fn, sizeof(fn), MO_SECLOG_FN_PREFIX "%u.jsn", fileNrFront); + if (ret < 0 || (size_t)ret >= sizeof(fn)) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + + JsonDoc logFile (0); + auto status = FilesystemUtils::loadJson(filesystem, fn, logFile, getMemoryTag()); + switch (status) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + break; //file gap - will skip this fileNr + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failure to load %s. Skipping logfile", fn); + break; + } + + if (status != FilesystemUtils::LoadStatus::Success) { + continue; + } + + if (logFile.size() == 0) { + MO_DBG_ERR("invalid state: %s. Skpping logfile", fn); + continue; + } + + unsigned int nextEntryNrFront = (unsigned int)logFile.size(); + unsigned int nextEntryNrEnd = (unsigned int)logFile.size(); + + for (size_t entryNr = 0; entryNr < logFile.size(); entryNr++) { + auto securityEvent = logFile[entryNr]; + if (securityEvent.containsKey("atnr")) { + // this is the first entry to be sent. Found front + nextEntryNrFront = (unsigned int)entryNr; + break; + } + } + + if (nextEntryNrFront == nextEntryNrEnd) { + MO_DBG_ERR("invalid state: %s. Skpping logfile", fn); + continue; + } + + // logfile is valid. Apply Nrs and exit for-loop + entryNrFront = nextEntryNrFront; + entryNrEnd = nextEntryNrEnd; + break; + } + + // Advanced to the next front SecurityEvent + return true; +} + +bool SecurityEventService::triggerSecurityEvent(const char *eventType) { + + if (!filesystem) { + // Volatile mode. Just enqueue SecurityEventNotification to OCPP message queue and return + auto securityEventNotification = makeRequest(context, new SecurityEventNotification(context, eventType, context.getClock().now())); + context.getMessageService().sendRequest(std::move(securityEventNotification)); + return true; + } + + // Append SecurityEvent to queue + + unsigned int fileNrBack = fileNrEnd; + JsonDoc logBack (0); + bool logBackLoaded = false; + + if (fileNrBegin != fileNrEnd) { + + unsigned int fileNrBack = (fileNrEnd + MO_SECLOG_INDEX_MAX - 1) % MO_SECLOG_INDEX_MAX; + + char fn [MO_MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fn, sizeof(fn), MO_SECLOG_FN_PREFIX "%u.jsn", fileNrBack); + if (ret < 0 || (size_t)ret >= sizeof(fn)) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + + auto status = FilesystemUtils::loadJson(filesystem, fn, logBack, getMemoryTag()); + switch (status) { + case FilesystemUtils::LoadStatus::Success: + logBackLoaded = true; + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + break; //file gap - will skip this fileNr + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failure to load %s", fn); + break; + } + + if (!logBackLoaded || logBack.size() == 0) { // Is logBack errorneous? (load failure or empty JSON) + MO_DBG_ERR("invalid state: %s. Skpping logfile", fn); + fileNrBack = fileNrEnd; + logBack.clear(); + logBackLoaded = false; + } else if (logBack.size() >= MO_SECLOG_MAX_FILE_ENTRIES) { // Is logBack aready full? + // Yes, start new logfile + fileNrBack = fileNrEnd; + logBack.clear(); + logBackLoaded = false; + } + } + + // Is security log full? + unsigned int outstandingFiles = (fileNrBack + MO_SECLOG_INDEX_MAX - fileNrFront) % MO_SECLOG_INDEX_MAX; + if (outstandingFiles > MO_SECLOG_MAX_FILES) { + MO_DBG_ERR("SecurityLog capacity exceeded"); + return false; + } + + // Are there historic logfiles which should be deleted? + unsigned int historicFiles = (fileNrFront + MO_SECLOG_INDEX_MAX - fileNrBegin) % MO_SECLOG_INDEX_MAX; + if (historicFiles + outstandingFiles > MO_SECLOG_MAX_FILES) { + MO_DBG_ERR("Cleaning historic logfile"); + + char fn [MO_MAX_PATH_SIZE]; + auto ret = snprintf(fn, sizeof(fn), MO_SECLOG_FN_PREFIX "%u.jsn", fileNrBegin); + if (ret < 0 || (size_t)ret >= sizeof(fn)) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + + char path [MO_MAX_PATH_SIZE]; + if (!FilesystemUtils::printPath(filesystem, path, sizeof(path), fn)) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + + if (!filesystem->remove(path)) { + MO_DBG_ERR("failure to remove %s", path); + return false; + } + + // Advance fileNrBegin + fileNrBegin = (fileNrBegin + 1) % MO_SECLOG_INDEX_MAX; + } + + // Determine JSON capacity + + size_t capacity = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(3); + + if (logBackLoaded) { + // Keep exisitng logfile contents - therefore need enough space for copying everything over to next write + capacity += JSON_ARRAY_SIZE(logBack.size()); + for (size_t entryNr = 0; entryNr < logBack.size(); entryNr++) { + auto securityEvent = logBack[entryNr]; + if (securityEvent.containsKey("atnr")) { + capacity += JSON_ARRAY_SIZE(3); + } else { + capacity += JSON_ARRAY_SIZE(2); + } + } + } + + JsonDoc nextLogBack = initJsonDoc(getMemoryTag(), capacity); + + if (logBackLoaded) { + // Copy old Json object to new + nextLogBack = logBack; + } + + // Append SecurityEvent + auto securityEvent = nextLogBack.createNestedObject(); + securityEvent["type"] = eventType; + + Timestamp time = context.getClock().now(); + char timeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toJsonString(time, timeStr, sizeof(timeStr))) { + MO_DBG_ERR("Serialization error"); + return false; + } + securityEvent["time"] = (const char*)timeStr; // force zero-copy mode + + securityEvent["atnr"] = 0; + + if (nextLogBack.overflowed()) { + MO_DBG_ERR("Serialization error"); + return false; + } + + // Update logfile on flash + char fn [MO_MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fn, sizeof(fn), MO_SECLOG_FN_PREFIX "%u.jsn", fileNrBack); + if (ret < 0 || (size_t)ret >= sizeof(fn)) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + + if (FilesystemUtils::storeJson(filesystem, fn, nextLogBack) != FilesystemUtils::StoreStatus::Success) { + MO_DBG_ERR("FS error: %s", fn); + return false; + } + + // Successfully updated end of queue on flash. Update fileNrEnd (remains unchanged when SecurityEvent was appended to existing file) + fileNrEnd = (fileNrBack + 1) % MO_SECLOG_INDEX_MAX; + + // If current front logfile was updated, then need to update cached SecurityEvent counter + if (fileNrBack == fileNrFront) { + entryNrEnd++; + } + + return true; +} + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT diff --git a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h new file mode 100644 index 00000000..1a442b21 --- /dev/null +++ b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h @@ -0,0 +1,109 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_SECURITYEVENTSERVICE_H +#define MO_SECURITYEVENTSERVICE_H + +#include +#include +#include +#include +#include + +#define MO_SECLOG_FN_PREFIX "slog-" +#define MO_SECLOG_INDEX_MAX 10000 +#define MO_SECLOG_INDEX_DIGITS 4 //digits needed to print MAX_INDEX-1 (=9999, i.e. 4 digits) + +#ifndef MO_SECLOG_MAX_FILES +#define MO_SECLOG_MAX_FILES 10 // Number of locally stored event log files. Must be smaller than (MO_SECLOG_INDEX_MAX / 2) +#endif + +#if !(MO_SECLOG_MAX_FILES < MO_SECLOG_INDEX_MAX / 2) +#error MO_SECLOG_MAX_FILES must be smaller than (MO_SECLOG_INDEX_MAX / 2) to allow ring index initialization from file list +#endif + +#ifndef MO_SECLOG_MAX_FILE_ENTRIES +#define MO_SECLOG_MAX_FILE_ENTRIES 20 // Number of SecurityEvents per File +#endif + +#ifndef MO_SECLOG_SEND_ATTEMPTS +#define MO_SECLOG_SEND_ATTEMPTS 3 // Number of attempts to send a SecurityEventNotification +#endif + +#ifndef MO_SECLOG_SEND_ATTEMPTS +#define MO_SECLOG_SEND_ATTEMPTS 3 // Number of attempts to send a SecurityEventNotification +#endif + +#ifndef MO_SECLOG_SEND_ATTEMPT_INTERVAL +#define MO_SECLOG_SEND_ATTEMPT_INTERVAL 180 +#endif + +// Length of field `type` of a SecurityEventNotificationRequest (1.49.1) +#define MO_SECURITY_EVENT_TYPE_LENGTH 50 + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT + +namespace MicroOcpp { + +class Context; +class Connection; + +#if MO_ENABLE_V16 +namespace Ocpp16 { +class Configuration; +} +#endif +#if MO_ENABLE_V201 +namespace Ocpp201 { +class Variable; +} +#endif + +class SecurityEventService : public MemoryManaged { +private: + Context& context; + Connection *connection = nullptr; + MO_FilesystemAdapter *filesystem = nullptr; + + int ocppVersion = -1; + + #if MO_ENABLE_V16 + Ocpp16::Configuration *timeAdjustmentReportingThresholdIntV16 = nullptr; + #endif + #if MO_ENABLE_V201 + Ocpp201::Variable *timeAdjustmentReportingThresholdIntV201 = nullptr; + #endif + + Timestamp trackUptime; + Timestamp trackUnixTime; + int32_t trackTimeOffset = 0; + + unsigned int fileNrBegin = 0; //oldest (historical) log file on flash. Already sent via SecurityEventNotification, can still be fetched via GetLog + unsigned int fileNrFront = 0; //oldest log which is still queued to be sent to the server + unsigned int fileNrEnd = 0; //one position behind newest log file + + // Front logfile entries. entryNr specifies the position of a SecurityEvent in the front logfile + unsigned int entryNrFront = 0; //same logic as fileNr. The "Begin" number is always 0 and is not needed explict here + unsigned int entryNrEnd = 0; + + Timestamp attemptTime; + unsigned int attemptNr = 0; + bool isSecurityEventInProgress = false; + + bool fetchSecurityEventFront(char *type, Timestamp& timestamp, unsigned int& attemptNr); //read current front event into output parameters + bool advanceSecurityEventFront(); + +public: + SecurityEventService(Context& context); + + bool setup(); + + void loop(); + + bool triggerSecurityEvent(const char *eventType); +}; + +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT +#endif diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp index 3c71d539..dd320f68 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp @@ -3,107 +3,229 @@ // MIT License #include +#include #include #include #include +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING + using namespace MicroOcpp; -ChargeRate MicroOcpp::chargeRate_min(const ChargeRate& a, const ChargeRate& b) { - ChargeRate res; - res.power = std::min(a.power, b.power); - res.current = std::min(a.current, b.current); - res.nphases = std::min(a.nphases, b.nphases); +void mo_chargeRate_init(MO_ChargeRate *limit) { + limit->power = -1.f; + limit->current = -1.f; + limit->numberPhases = -1; + #if MO_ENABLE_V201 + limit->phaseToUse = -1; + #endif +} + +bool mo_chargeRate_equals(const MO_ChargeRate *a, const MO_ChargeRate *b) { + return + ((a->power < 0 && b->power < 0) || a->power == b->power) && + ((a->current < 0 && b->current < 0) || a->current == b->current) && + ((a->numberPhases < 0 && b->numberPhases < 0) || a->numberPhases == b->numberPhases) + #if MO_ENABLE_V201 + && ((a->phaseToUse < 0 && b->phaseToUse < 0) || a->phaseToUse == b->phaseToUse) + #endif + ; +} + +const char *MicroOcpp::serializeChargingProfilePurposeType(ChargingProfilePurposeType v) { + const char *res = ""; + switch (v) { + case ChargingProfilePurposeType::UNDEFINED: + MO_DBG_WARN("serialization error"); + break; + case ChargingProfilePurposeType::ChargePointMaxProfile: + res = "ChargePointMaxProfile"; + break; + case ChargingProfilePurposeType::TxDefaultProfile: + res = "TxDefaultProfile"; + break; + case ChargingProfilePurposeType::TxProfile: + res = "TxProfile"; + break; + } + return res; +} + +ChargingProfilePurposeType MicroOcpp::deserializeChargingProfilePurposeType(const char *str) { + ChargingProfilePurposeType res = ChargingProfilePurposeType::UNDEFINED; + if (!strcmp(str, "ChargePointMaxProfile")) { + res = ChargingProfilePurposeType::ChargePointMaxProfile; + } else if (!strcmp(str, "TxDefaultProfile")) { + res = ChargingProfilePurposeType::TxDefaultProfile; + } else if (!strcmp(str, "TxProfile")) { + res = ChargingProfilePurposeType::TxProfile; + } + return res; +} + +const char *MicroOcpp::serializeChargingRateUnitType(ChargingRateUnitType v) { + const char *res = ""; + switch (v) { + case ChargingRateUnitType::UNDEFINED: + res = ""; + break; + case ChargingRateUnitType::Watt: + res = "W"; + break; + case ChargingRateUnitType::Amp: + res = "A"; + break; + } + return res; +} + +ChargingRateUnitType MicroOcpp::deserializeChargingRateUnitType(const char *str) { + ChargingRateUnitType res = ChargingRateUnitType::UNDEFINED; + if (!strcmp(str, "W")) { + res = ChargingRateUnitType::Watt; + } else if (!strcmp(str, "A")) { + res = ChargingRateUnitType::Amp; + } else { + MO_DBG_ERR("deserialization error"); + } return res; } -ChargingSchedule::ChargingSchedule() : MemoryManaged("v16.SmartCharging.SmartChargingModel"), chargingSchedulePeriod{makeVector(getMemoryTag())} { +ChargingSchedule::ChargingSchedule() : MemoryManaged("v16.SmartCharging.SmartChargingModel") { + +} + +ChargingSchedule::~ChargingSchedule() { + resizeChargingSchedulePeriod(0); // frees all resources if called with 0 +} + +bool ChargingSchedule::resizeChargingSchedulePeriod(size_t chargingSchedulePeriodSize) { + for (size_t i = 0; i < this->chargingSchedulePeriodSize; i++) { + delete chargingSchedulePeriod[i]; + chargingSchedulePeriod[i] = nullptr; + } + MO_FREE(chargingSchedulePeriod); + chargingSchedulePeriod = nullptr; + this->chargingSchedulePeriodSize = 0; + + if (chargingSchedulePeriodSize == 0) { + return true; + } + + chargingSchedulePeriod = static_cast(MO_MALLOC(getMemoryTag("v16.SmartCharging.SmartChargingModel"), sizeof(ChargingSchedulePeriod*) * chargingSchedulePeriodSize)); + if (!chargingSchedulePeriod) { + MO_DBG_ERR("OOM"); + return false; + } + memset(chargingSchedulePeriod, 0, sizeof(ChargingSchedulePeriod*) * chargingSchedulePeriodSize); + for (size_t i = 0; i < chargingSchedulePeriodSize; i++) { + chargingSchedulePeriod[i] = new ChargingSchedulePeriod(); + if (!chargingSchedulePeriod[i]) { + MO_DBG_ERR("OOM"); + resizeChargingSchedulePeriod(0); + return false; + } + this->chargingSchedulePeriodSize = i; + } + this->chargingSchedulePeriodSize = chargingSchedulePeriodSize; + return true; } -bool ChargingSchedule::calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange) { - Timestamp basis = Timestamp(); //point in time to which schedule-related times are relative +bool ChargingSchedule::calculateLimit(int32_t unixTime, int32_t sessionDurationSecs, MO_ChargeRate& limit, int32_t& nextChangeSecs) { + if (unixTime == 0 && sessionDurationSecs < 0) { + MO_DBG_WARN("Neither absolute time nor session duration known. Cannot determine limit"); + return false; + } + if (startSchedule == 0 && sessionDurationSecs < 0) { //both undefined + MO_DBG_WARN("startSchedule not set and session duration unkown. Cannot determine limit"); + return false; + } + + int32_t scheduleSecs = -1; //time passed since begin of this schedule switch (chargingProfileKind) { - case (ChargingProfileKindType::Absolute): + case ChargingProfileKindType::Absolute: + if (unixTime == 0) { + //cannot use Absolute profiles when + MO_DBG_WARN("Need to set clock before using absolute profiles"); + return false; + } //check if schedule is not valid yet but begins in future - if (startSchedule > t) { + if (startSchedule != 0 && startSchedule > unixTime) { //not valid YET - nextChange = std::min(nextChange, startSchedule); + nextChangeSecs = std::min(nextChangeSecs, startSchedule - unixTime); return false; } - //If charging profile is absolute, prefer startSchedule as basis. If absent, use chargingStart instead. If absent, no - //behaviour is defined - if (startSchedule > MIN_TIME) { - basis = startSchedule; - } else if (startOfCharging > MIN_TIME && startOfCharging < t) { - basis = startOfCharging; + //If charging profile is absolute, prefer startSchedule as basis. If absent, use session duration instead + if (startSchedule != 0) { + scheduleSecs = unixTime - startSchedule; } else { - MO_DBG_ERR("Absolute profile, but neither startSchedule, nor start of charging are set. Undefined behavior, abort"); - return false; + scheduleSecs = sessionDurationSecs; //sessionDurationSecs valid per precondition } break; - case (ChargingProfileKindType::Recurring): - if (recurrencyKind == RecurrencyKindType::Daily) { - basis = t - ((t - startSchedule) % (24 * 3600)); - nextChange = std::min(nextChange, basis + (24 * 3600)); //constrain nextChange to basis + one day - } else if (recurrencyKind == RecurrencyKindType::Weekly) { - basis = t - ((t - startSchedule) % (7 * 24 * 3600)); - nextChange = std::min(nextChange, basis + (7 * 24 * 3600)); + case ChargingProfileKindType::Recurring: { + int32_t recurrencySecs = 0; + switch (recurrencyKind) { + case RecurrencyKindType::Daily: + recurrencySecs = 24 * 3600; + break; + case RecurrencyKindType::Weekly: + recurrencySecs = 7 * 24 * 3600; + break; + case RecurrencyKindType::UNDEFINED: + MO_DBG_ERR("Recurring ChargingProfile but no RecurrencyKindType set. Assume 'Daily'"); + recurrencySecs = 24 * 3600; + break; + } + if (unixTime != 0 && startSchedule != 0) { + scheduleSecs = (unixTime - startSchedule) % recurrencySecs; } else { - MO_DBG_ERR("Recurring ChargingProfile but no RecurrencyKindType set. Undefined behavior, assume 'Daily'"); - basis = t - ((t - startSchedule) % (24 * 3600)); - nextChange = std::min(nextChange, basis + (24 * 3600)); + scheduleSecs = sessionDurationSecs % recurrencySecs; //sessionDurationSecs valid per precondition } + nextChangeSecs = std::min(nextChangeSecs, recurrencySecs - scheduleSecs); //constrain nextChange to basis + one day break; - case (ChargingProfileKindType::Relative): + } + case ChargingProfileKindType::Relative: //assumed, that it is relative to start of charging //start of charging must be before t or equal to t - if (startOfCharging > t) { + if (sessionDurationSecs < 0) { //Relative charging profiles only work with a currently active charging session which is not the case here return false; } - basis = startOfCharging; + scheduleSecs = sessionDurationSecs; break; } - if (t < basis) { //check for error - MO_DBG_ERR("time basis is smaller than t, but t must be >= basis"); - return false; - } - - int t_toBasis = t - basis; - if (duration > 0){ //duration is set //check if duration is exceeded and if yes, abort limit algorithm //if no, the duration is an upper limit for the validity of the schedule - if (t_toBasis >= duration) { //"duration" is given relative to basis + if (scheduleSecs >= duration) { //"duration" is given relative to basis return false; } else { - nextChange = std::min(nextChange, basis + duration); + nextChangeSecs = std::min(nextChangeSecs, duration - scheduleSecs); } } /* * Work through the ChargingProfilePeriods here. If the right period was found, assign the limit parameter from it - * and make nextChange equal the beginning of the following period. If the right period is the last one, nextChange + * and make nextChange equal the beginning of the following chargingSchedulePeriod[i]. If the right period is the last one, nextChange * will remain the time determined before. */ float limit_res = -1.0f; //If limit_res is still -1 after the loop, the limit algorithm failed - int nphases_res = -1; - for (auto period = chargingSchedulePeriod.begin(); period != chargingSchedulePeriod.end(); period++) { - if (period->startPeriod > t_toBasis) { + int numberPhases_res = -1; + for (size_t i = 0; i < chargingSchedulePeriodSize; i++) { + if (chargingSchedulePeriod[i]->startPeriod > scheduleSecs) { // found the first period that comes after t_toBasis. - nextChange = basis + period->startPeriod; - nextChange = std::min(nextChange, basis + period->startPeriod); + nextChangeSecs = std::min(nextChangeSecs, chargingSchedulePeriod[i]->startPeriod - scheduleSecs); break; //The currently valid limit was set the iteration before } - limit_res = period->limit; - nphases_res = period->numberPhases; + limit_res = chargingSchedulePeriod[i]->limit; + numberPhases_res = chargingSchedulePeriod[i]->numberPhases; } - + if (limit_res >= 0.0f) { limit_res = std::max(limit_res, minChargingRate); @@ -113,402 +235,501 @@ bool ChargingSchedule::calculateLimit(const Timestamp &t, const Timestamp &start limit.power = limit_res; } - limit.nphases = nphases_res; + limit.numberPhases = numberPhases_res; return true; } else { return false; //No limit was found. Either there is no ChargingProfilePeriod, or each period begins after t_toBasis } } -bool ChargingSchedule::toJson(JsonDoc& doc) { - size_t capacity = 0; - capacity += JSON_OBJECT_SIZE(5); //no of fields of ChargingSchedule - capacity += JSONDATE_LENGTH + 1; //startSchedule - capacity += JSON_ARRAY_SIZE(chargingSchedulePeriod.size()) + chargingSchedulePeriod.size() * JSON_OBJECT_SIZE(3); +bool ChargingSchedule::parseJson(Clock& clock, int ocppVersion, JsonObject json) { - doc = initJsonDoc("v16.SmartCharging.ChargingSchedule", capacity); - if (duration >= 0) { - doc["duration"] = duration; - } - char startScheduleJson [JSONDATE_LENGTH + 1] = {'\0'}; - startSchedule.toJsonString(startScheduleJson, JSONDATE_LENGTH + 1); - doc["startSchedule"] = startScheduleJson; - doc["chargingRateUnit"] = chargingRateUnit == (ChargingRateUnitType::Amp) ? "A" : "W"; - JsonArray periodArray = doc.createNestedArray("chargingSchedulePeriod"); - for (auto period = chargingSchedulePeriod.begin(); period != chargingSchedulePeriod.end(); period++) { - JsonObject entry = periodArray.createNestedObject(); - entry["startPeriod"] = period->startPeriod; - entry["limit"] = period->limit; - if (period->numberPhases != 3) { - entry["numberPhases"] = period->numberPhases; + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + id = json["id"] | -1; + if (id < 0) { + MO_DBG_WARN("format violation"); + return false; } } - if (minChargingRate >= 0) { - doc["minChargeRate"] = minChargingRate; - } - - return true; -} + #endif //MO_ENABLE_V201 -void ChargingSchedule::printSchedule(){ - - char tmp[JSONDATE_LENGTH + 1] = {'\0'}; - startSchedule.toJsonString(tmp, JSONDATE_LENGTH + 1); + if (json.containsKey("duration")) { + duration = json["duration"] | -1; + if (duration < 0) { + MO_DBG_WARN("format violation"); + return false; + } + } - MO_CONSOLE_PRINTF(" > CHARGING SCHEDULE:\n" \ - " > duration: %i\n" \ - " > startSchedule: %s\n" \ - " > chargingRateUnit: %s\n" \ - " > minChargingRate: %f\n", - duration, - tmp, - chargingRateUnit == (ChargingRateUnitType::Amp) ? "A" : - chargingRateUnit == (ChargingRateUnitType::Watt) ? "W" : "Error", - minChargingRate); + if (json.containsKey("startSchedule")) { + Timestamp startSchedule2; + if (!clock.parseString(json["startSchedule"] | "_Invalid", startSchedule2)) { + //non-success + MO_DBG_WARN("datetime format violation, expect format like 2022-02-01T20:53:32.486Z"); + return false; + } + if (!clock.toUnixTime(startSchedule2, startSchedule)) { + MO_DBG_ERR("internal error"); + return false; + } + } - for (auto period = chargingSchedulePeriod.begin(); period != chargingSchedulePeriod.end(); period++) { - MO_CONSOLE_PRINTF(" > CHARGING SCHEDULE PERIOD:\n" \ - " > startPeriod: %i\n" \ - " > limit: %f\n" \ - " > numberPhases: %i\n", - period->startPeriod, - period->limit, - period->numberPhases); + chargingRateUnit = deserializeChargingRateUnitType(json["chargingRateUnit"] | "_Undefined"); + if (chargingRateUnit == ChargingRateUnitType::UNDEFINED) { + MO_DBG_WARN("format violation"); + return false; } -} -ChargingProfile::ChargingProfile() : MemoryManaged("v16.SmartCharging.ChargingProfile") { + JsonArray periodJsonArray = json["chargingSchedulePeriod"]; + if (periodJsonArray.size() < 1) { + MO_DBG_WARN("format violation"); + return false; + } -} + size_t chargingSchedulePeriodSize = periodJsonArray.size(); -bool ChargingProfile::calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange){ - if (t > validTo && validTo > MIN_TIME) { - return false; //no limit defined + if (chargingSchedulePeriodSize > MO_ChargingScheduleMaxPeriods) { + MO_DBG_WARN("exceed ChargingScheduleMaxPeriods"); + return false; } - if (t < validFrom) { - nextChange = std::min(nextChange, validFrom); - return false; //no limit defined + + if (!resizeChargingSchedulePeriod(chargingSchedulePeriodSize)) { + MO_DBG_ERR("OOM"); + return false; } - return chargingSchedule.calculateLimit(t, startOfCharging, limit, nextChange); -} + for (size_t i = 0; i < chargingSchedulePeriodSize; i++) { -bool ChargingProfile::calculateLimit(const Timestamp &t, ChargeRate& limit, Timestamp& nextChange){ - return calculateLimit(t, MIN_TIME, limit, nextChange); -} + JsonObject periodJson = periodJsonArray[i]; + auto& period = *chargingSchedulePeriod[i]; -int ChargingProfile::getChargingProfileId() { - return chargingProfileId; -} + period.startPeriod = periodJson["startPeriod"] | -1; + if (period.startPeriod < 0) { + MO_DBG_WARN("format violation"); + return false; + } -int ChargingProfile::getTransactionId() { - return transactionId; -} + period.limit = json["limit"] | -1.f; + if (period.limit < 0.f) { + MO_DBG_WARN("format violation"); + return false; + } -int ChargingProfile::getStackLevel(){ - return stackLevel; -} - -ChargingProfilePurposeType ChargingProfile::getChargingProfilePurpose(){ - return chargingProfilePurpose; -} + if (json.containsKey("numberPhases")) { + period.numberPhases = json["numberPhases"] | -1; + if (period.numberPhases < 0 || period.numberPhases > 3) { + MO_DBG_WARN("format violation"); + return false; + } + } -bool ChargingProfile::toJson(JsonDoc& doc) { - - auto chargingScheduleDoc = initJsonDoc("v16.SmartCharging.ChargingSchedule"); - if (!chargingSchedule.toJson(chargingScheduleDoc)) { - return false; + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (json.containsKey("phaseToUse")) { + period.phaseToUse = json["phaseToUse"] | -1; + if (period.phaseToUse < 0 || period.phaseToUse > 3) { + MO_DBG_WARN("format violation"); + return false; + } + } + } + #endif //MO_ENABLE_V201 } - doc = initJsonDoc("v16.SmartCharging.ChargingProfile", - JSON_OBJECT_SIZE(9) + //no. of fields in ChargingProfile - 2 * (JSONDATE_LENGTH + 1) + //validFrom and validTo - chargingScheduleDoc.memoryUsage()); //nested JSON object - - doc["chargingProfileId"] = chargingProfileId; - if (transactionId >= 0) { - doc["transactionId"] = transactionId; + if (json.containsKey("minChargingRate")) { + float minChargingRateIn = json["minChargingRate"]; + if (minChargingRateIn >= 0.f) { + minChargingRate = minChargingRateIn; + } else { + MO_DBG_WARN("format violation"); + return false; + } } - doc["stackLevel"] = stackLevel; - switch (chargingProfilePurpose) { - case (ChargingProfilePurposeType::ChargePointMaxProfile): - doc["chargingProfilePurpose"] = "ChargePointMaxProfile"; - break; - case (ChargingProfilePurposeType::TxDefaultProfile): - doc["chargingProfilePurpose"] = "TxDefaultProfile"; - break; - case (ChargingProfilePurposeType::TxProfile): - doc["chargingProfilePurpose"] = "TxProfile"; - break; - } + return true; +} - switch (chargingProfileKind) { - case (ChargingProfileKindType::Absolute): - doc["chargingProfileKind"] = "Absolute"; - break; - case (ChargingProfileKindType::Recurring): - doc["chargingProfileKind"] = "Recurring"; - break; - case (ChargingProfileKindType::Relative): - doc["chargingProfileKind"] = "Relative"; - break; - } +size_t ChargingSchedule::getJsonCapacity(int ocppVersion, bool compositeSchedule) { + size_t capacity = 0; - switch (recurrencyKind) { - case (RecurrencyKindType::Daily): - doc["recurrencyKind"] = "Daily"; - break; - case (RecurrencyKindType::Weekly): - doc["recurrencyKind"] = "Weekly"; - break; - default: - break; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + capacity += JSON_OBJECT_SIZE(5); //no of fields of ChargingSchedule } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (compositeSchedule) { + capacity += JSON_OBJECT_SIZE(5); //no of fields of a compositie ChargingSchedule + } else { + capacity += JSON_OBJECT_SIZE(6); //no of fields of ChargingSchedule + } + } + #endif //MO_ENABLE_V201 + + capacity += MO_JSONDATE_SIZE; //startSchedule / scheduleStart + capacity += JSON_ARRAY_SIZE(chargingSchedulePeriodSize) + chargingSchedulePeriodSize * ( + ocppVersion == MO_OCPP_V16 ? + JSON_OBJECT_SIZE(3) : + JSON_OBJECT_SIZE(4) + ); - char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; + return capacity; +} + +bool ChargingSchedule::toJson(Clock& clock, int ocppVersion, bool compositeSchedule, JsonObject out, int evseId) { - if (validFrom > MIN_TIME) { - if (!validFrom.toJsonString(timeStr, JSONDATE_LENGTH + 1)) { + char startScheduleStr [MO_JSONDATE_SIZE] = {'\0'}; + if (startSchedule) { + Timestamp startSchedule2; + if (!clock.fromUnixTime(startSchedule2, startSchedule)) { MO_DBG_ERR("serialization error"); return false; } - doc["validFrom"] = timeStr; - } - - if (validTo > MIN_TIME) { - if (!validTo.toJsonString(timeStr, JSONDATE_LENGTH + 1)) { + char startScheduleStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(startSchedule2, startScheduleStr, sizeof(startScheduleStr))) { MO_DBG_ERR("serialization error"); return false; } - doc["validTo"] = timeStr; } - doc["chargingSchedule"] = chargingScheduleDoc; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + out["startSchedule"] = startScheduleStr; + if (minChargingRate >= 0) { + out["minChargeRate"] = minChargingRate; + } + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (compositeSchedule) { + out["evseId"] = evseId; + out["scheduleStart"] = startScheduleStr; + } else { + out["id"] = id; + out["startSchedule"] = startScheduleStr; + if (minChargingRate >= 0) { + out["minChargeRate"] = minChargingRate; + } + //salesTariff not supported + } + } + #endif //MO_ENABLE_V201 + + if (duration >= 0) { + out["duration"] = duration; + } + out["chargingRateUnit"] = serializeChargingRateUnitType(chargingRateUnit); + + JsonArray periodArray = out.createNestedArray("chargingSchedulePeriod"); + for (size_t i = 0; i < chargingSchedulePeriodSize; i++) { + JsonObject entry = periodArray.createNestedObject(); + entry["startPeriod"] = chargingSchedulePeriod[i]->startPeriod; + entry["limit"] = chargingSchedulePeriod[i]->limit; + if (chargingSchedulePeriod[i]->numberPhases != 3) { + entry["numberPhases"] = chargingSchedulePeriod[i]->numberPhases; + } + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (chargingSchedulePeriod[i]->phaseToUse >= 0) { + entry["numberPhases"] = chargingSchedulePeriod[i]->numberPhases; + } + } + #endif //MO_ENABLE_V201 + } return true; } -void ChargingProfile::printProfile(){ +void ChargingSchedule::printSchedule(Clock& clock){ - char tmp[JSONDATE_LENGTH + 1] = {'\0'}; - validFrom.toJsonString(tmp, JSONDATE_LENGTH + 1); - char tmp2[JSONDATE_LENGTH + 1] = {'\0'}; - validTo.toJsonString(tmp2, JSONDATE_LENGTH + 1); + char tmp[MO_JSONDATE_SIZE] = {'\0'}; + if (startSchedule) { + Timestamp startSchedule2; + clock.fromUnixTime(startSchedule2, startSchedule); + clock.toJsonString(startSchedule2, tmp, sizeof(tmp)); + } - MO_CONSOLE_PRINTF(" > CHARGING PROFILE:\n" \ - " > chargingProfileId: %i\n" \ - " > transactionId: %i\n" \ - " > stackLevel: %i\n" \ - " > chargingProfilePurpose: %s\n", - chargingProfileId, - transactionId, - stackLevel, - chargingProfilePurpose == (ChargingProfilePurposeType::ChargePointMaxProfile) ? "ChargePointMaxProfile" : - chargingProfilePurpose == (ChargingProfilePurposeType::TxDefaultProfile) ? "TxDefaultProfile" : - chargingProfilePurpose == (ChargingProfilePurposeType::TxProfile) ? "TxProfile" : "Error" - ); - MO_CONSOLE_PRINTF( - " > chargingProfileKind: %s\n" \ - " > recurrencyKind: %s\n" \ - " > validFrom: %s\n" \ - " > validTo: %s\n", - chargingProfileKind == (ChargingProfileKindType::Absolute) ? "Absolute" : - chargingProfileKind == (ChargingProfileKindType::Recurring) ? "Recurring" : - chargingProfileKind == (ChargingProfileKindType::Relative) ? "Relative" : "Error", - recurrencyKind == (RecurrencyKindType::Daily) ? "Daily" : - recurrencyKind == (RecurrencyKindType::Weekly) ? "Weekly" : - recurrencyKind == (RecurrencyKindType::NOT_SET) ? "NOT_SET" : "Error", + MO_DBG_VERBOSE(" > CHARGING SCHEDULE:\n" \ + " > duration: %i\n" \ + " > startSchedule: %s\n" \ + " > chargingRateUnit: %s\n" \ + " > minChargingRate: %f\n", + duration, tmp, - tmp2 - ); + serializeChargingRateUnitType(chargingRateUnit), + minChargingRate); - chargingSchedule.printSchedule(); + for (size_t i = 0; i < chargingSchedulePeriodSize; i++) { + MO_DBG_VERBOSE(" > CHARGING SCHEDULE PERIOD:\n" \ + " > startPeriod: %i\n" \ + " > limit: %f\n" \ + " > numberPhases: %i\n", + chargingSchedulePeriod[i]->startPeriod, + chargingSchedulePeriod[i]->limit, + chargingSchedulePeriod[i]->numberPhases); + } } -namespace MicroOcpp { +ChargingProfile::ChargingProfile() : MemoryManaged("v16.SmartCharging.ChargingProfile") { -bool loadChargingSchedulePeriod(JsonObject& json, ChargingSchedulePeriod& out) { - int startPeriod = json["startPeriod"] | -1; - if (startPeriod >= 0) { - out.startPeriod = startPeriod; - } else { - MO_DBG_WARN("format violation"); - return false; - } +} - float limit = json["limit"] | -1.f; - if (limit >= 0.f) { - out.limit = limit; - } else { - MO_DBG_WARN("format violation"); - return false; +bool ChargingProfile::calculateLimit(int32_t unixTime, int32_t sessionDurationSecs, MO_ChargeRate& limit, int32_t& nextChangeSecs){ + if (unixTime && validTo && unixTime > validTo) { + return false; //no limit defined } - - if (json.containsKey("numberPhases")) { - int numberPhases = json["numberPhases"]; - if (numberPhases >= 0 && numberPhases <= 3) { - out.numberPhases = numberPhases; - } else { - MO_DBG_WARN("format violation"); - return false; - } + if (unixTime && validFrom && unixTime < validFrom) { + nextChangeSecs = std::min(nextChangeSecs, validFrom - unixTime); + return false; //no limit defined } - return true; + return chargingSchedule.calculateLimit(unixTime, sessionDurationSecs, limit, nextChangeSecs); } -} //end namespace MicroOcpp +bool ChargingProfile::parseJson(Clock& clock, int ocppVersion, JsonObject json) { -std::unique_ptr MicroOcpp::loadChargingProfile(JsonObject& json) { - auto res = std::unique_ptr(new ChargingProfile()); - - int chargingProfileId = json["chargingProfileId"] | -1; - if (chargingProfileId >= 0) { - res->chargingProfileId = chargingProfileId; - } else { + chargingProfileId = json["chargingProfileId"] | -1; + if (chargingProfileId < 0) { MO_DBG_WARN("format violation"); - return nullptr; + return false; } - int transactionId = json["transactionId"] | -1; - if (transactionId >= 0) { - res->transactionId = transactionId; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + int transactionId = json["transactionId"] | -1; + if (transactionId >= 0) { + transactionId16 = transactionId; + } + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (json.containsKey("transactionId")) { + auto ret = snprintf(transactionId201, sizeof(transactionId201), "%s", json["transactionId"] | ""); + if (ret < 0 || ret >= sizeof(transactionId201)) { + MO_DBG_WARN("format violation"); + return false; + } + } } + #endif //MO_ENABLE_V201 - int stackLevel = json["stackLevel"] | -1; - if (stackLevel >= 0 && stackLevel <= MO_ChargeProfileMaxStackLevel) { - res->stackLevel = stackLevel; - } else { + stackLevel = json["stackLevel"] | -1; + if (stackLevel < 0 || stackLevel > MO_ChargeProfileMaxStackLevel) { MO_DBG_WARN("format violation"); - return nullptr; + return false; } - const char *chargingProfilePurposeStr = json["chargingProfilePurpose"] | "Invalid"; - if (!strcmp(chargingProfilePurposeStr, "ChargePointMaxProfile")) { - res->chargingProfilePurpose = ChargingProfilePurposeType::ChargePointMaxProfile; - } else if (!strcmp(chargingProfilePurposeStr, "TxDefaultProfile")) { - res->chargingProfilePurpose = ChargingProfilePurposeType::TxDefaultProfile; - } else if (!strcmp(chargingProfilePurposeStr, "TxProfile")) { - res->chargingProfilePurpose = ChargingProfilePurposeType::TxProfile; - } else { + chargingProfilePurpose = deserializeChargingProfilePurposeType(json["chargingProfilePurpose"] | "_Invalid"); + if (chargingProfilePurpose == ChargingProfilePurposeType::UNDEFINED) { MO_DBG_WARN("format violation"); - return nullptr; + return false; } - const char *chargingProfileKindStr = json["chargingProfileKind"] | "Invalid"; + const char *chargingProfileKindStr = json["chargingProfileKind"] | "_Invalid"; if (!strcmp(chargingProfileKindStr, "Absolute")) { - res->chargingProfileKind = ChargingProfileKindType::Absolute; + chargingProfileKind = ChargingProfileKindType::Absolute; } else if (!strcmp(chargingProfileKindStr, "Recurring")) { - res->chargingProfileKind = ChargingProfileKindType::Recurring; + chargingProfileKind = ChargingProfileKindType::Recurring; } else if (!strcmp(chargingProfileKindStr, "Relative")) { - res->chargingProfileKind = ChargingProfileKindType::Relative; + chargingProfileKind = ChargingProfileKindType::Relative; } else { MO_DBG_WARN("format violation"); - return nullptr; + return false; } - const char *recurrencyKindStr = json["recurrencyKind"] | "Invalid"; - if (!strcmp(recurrencyKindStr, "Daily")) { - res->recurrencyKind = RecurrencyKindType::Daily; - } else if (!strcmp(recurrencyKindStr, "Weekly")) { - res->recurrencyKind = RecurrencyKindType::Weekly; + if (json.containsKey("recurrencyKind")) { + const char *recurrencyKindStr = json["recurrencyKind"] | "_Invalid"; + if (!strcmp(recurrencyKindStr, "Daily")) { + recurrencyKind = RecurrencyKindType::Daily; + } else if (!strcmp(recurrencyKindStr, "Weekly")) { + recurrencyKind = RecurrencyKindType::Weekly; + } else { + MO_DBG_WARN("format violation"); + return false; + } } - MO_DBG_DEBUG("Deserialize JSON: chargingProfileId=%i, chargingProfilePurpose=%s, recurrencyKind=%s", chargingProfileId, chargingProfilePurposeStr, recurrencyKindStr); + MO_DBG_DEBUG("Deserialize JSON: chargingProfileId=%i, chargingProfilePurpose=%s", chargingProfileId, serializeChargingProfilePurposeType(chargingProfilePurpose)); if (json.containsKey("validFrom")) { - if (!res->validFrom.setTime(json["validFrom"] | "Invalid")) { + Timestamp validFrom2; + if (!clock.parseString(json["validFrom"] | "_Invalid", validFrom2)) { //non-success MO_DBG_WARN("datetime format violation, expect format like 2022-02-01T20:53:32.486Z"); - return nullptr; + return false; + } + if (!clock.toUnixTime(validFrom2, validFrom)) { + MO_DBG_ERR("internal error"); + return false; } - } else { - res->validFrom = MIN_TIME; } if (json.containsKey("validTo")) { - if (!res->validTo.setTime(json["validTo"] | "Invalid")) { + Timestamp validTo2; + if (!clock.parseString(json["validTo"] | "_Invalid", validTo2)) { //non-success MO_DBG_WARN("datetime format violation, expect format like 2022-02-01T20:53:32.486Z"); - return nullptr; + return false; + } + if (!clock.toUnixTime(validTo2, validTo)) { + MO_DBG_ERR("internal error"); + return false; } - } else { - res->validTo = MIN_TIME; } - JsonObject scheduleJson = json["chargingSchedule"]; - ChargingSchedule& schedule = res->chargingSchedule; - auto success = loadChargingSchedule(scheduleJson, schedule); + auto success = chargingSchedule.parseJson(clock, ocppVersion, json["chargingSchedule"]); if (!success) { - return nullptr; + return false; } //duplicate some fields to chargingSchedule to simplify the max charge rate calculation - schedule.chargingProfileKind = res->chargingProfileKind; - schedule.recurrencyKind = res->recurrencyKind; + chargingSchedule.chargingProfileKind = chargingProfileKind; + chargingSchedule.recurrencyKind = recurrencyKind; - return res; + return true; } -bool MicroOcpp::loadChargingSchedule(JsonObject& json, ChargingSchedule& out) { - if (json.containsKey("duration")) { - int duration = json["duration"] | -1; - if (duration >= 0) { - out.duration = duration; - } else { - MO_DBG_WARN("format violation"); - return false; +size_t ChargingProfile::getJsonCapacity(int ocppVersion) { + size_t capacity = 0; + + capacity += chargingSchedule.getJsonCapacity(ocppVersion, /*compositeSchedule*/ false); + + capacity += JSON_OBJECT_SIZE(9) + //no. of fields in ChargingProfile + 2 * MO_JSONDATE_SIZE; //validFrom and validTo + + return capacity; +} + +bool ChargingProfile::toJson(Clock& clock, int ocppVersion, JsonObject out) { + + out["chargingProfileId"] = chargingProfileId; + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + if (transactionId16 >= 0) { + out["transactionId"] = transactionId16; } } - - if (json.containsKey("startSchedule")) { - if (!out.startSchedule.setTime(json["startSchedule"] | "Invalid")) { - //non-success - MO_DBG_WARN("datetime format violation, expect format like 2022-02-01T20:53:32.486Z"); - return false; + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (*transactionId201) { + out["transactionId"] = (const char*)transactionId201; //force zero-copy } - } else { - out.startSchedule = MIN_TIME; } + #endif //MO_ENABLE_V201 - const char *unit = json["chargingRateUnit"] | "_Undefined"; - if (unit[0] == 'a' || unit[0] == 'A') { - out.chargingRateUnit = ChargingRateUnitType::Amp; - } else if (unit[0] == 'w' || unit[0] == 'W') { - out.chargingRateUnit = ChargingRateUnitType::Watt; - } else { - MO_DBG_WARN("format violation"); - return false; - } - - JsonArray periodJsonArray = json["chargingSchedulePeriod"]; - if (periodJsonArray.size() < 1) { - MO_DBG_WARN("format violation"); - return false; + out["stackLevel"] = stackLevel; + + out["chargingProfilePurpose"] = serializeChargingProfilePurposeType(chargingProfilePurpose); + + switch (chargingProfileKind) { + case ChargingProfileKindType::Absolute: + out["chargingProfileKind"] = "Absolute"; + break; + case ChargingProfileKindType::Recurring: + out["chargingProfileKind"] = "Recurring"; + break; + case ChargingProfileKindType::Relative: + out["chargingProfileKind"] = "Relative"; + break; } - if (periodJsonArray.size() > MO_ChargingScheduleMaxPeriods) { - MO_DBG_WARN("exceed ChargingScheduleMaxPeriods"); - return false; + switch (recurrencyKind) { + case RecurrencyKindType::Daily: + out["recurrencyKind"] = "Daily"; + break; + case RecurrencyKindType::Weekly: + out["recurrencyKind"] = "Weekly"; + break; + default: + break; } - for (JsonObject periodJson : periodJsonArray) { - out.chargingSchedulePeriod.emplace_back(); - if (!loadChargingSchedulePeriod(periodJson, out.chargingSchedulePeriod.back())) { + if (validFrom) { + Timestamp validFrom2; + if (!clock.fromUnixTime(validFrom2, validFrom)) { + MO_DBG_ERR("serialization error"); return false; } + char validFromStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(validFrom2, validFromStr, sizeof(validFromStr))) { + MO_DBG_ERR("serialization error"); + return false; + } + out["validFrom"] = validFromStr; } - - if (json.containsKey("minChargingRate")) { - float minChargingRate = json["minChargingRate"]; - if (minChargingRate >= 0.f) { - out.minChargingRate = minChargingRate; - } else { - MO_DBG_WARN("format violation"); + + if (validTo) { + Timestamp validTo2; + if (!clock.fromUnixTime(validTo2, validTo)) { + MO_DBG_ERR("serialization error"); + return false; + } + char validToStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(validTo2, validToStr, sizeof(validToStr))) { + MO_DBG_ERR("serialization error"); return false; } + out["validTo"] = validToStr; + } + + JsonObject chargingScheduleJson = out.createNestedObject("chargingSchedule"); + if (!chargingSchedule.toJson(clock, ocppVersion, /*compositeSchedule*/ false, chargingScheduleJson)) { + return false; } return true; } + +void ChargingProfile::printProfile(Clock& clock){ + + char validFromStr [MO_JSONDATE_SIZE] = {'\0'}; + if (validFrom) { + Timestamp validFrom2; + clock.fromUnixTime(validFrom2, validFrom); + clock.toJsonString(validFrom2, validFromStr, sizeof(validFromStr)); + } + + char validToStr [MO_JSONDATE_SIZE] = {'\0'}; + if (validTo) { + Timestamp validTo2; + clock.fromUnixTime(validTo2, validTo); + clock.toJsonString(validTo2, validToStr, sizeof(validToStr)); + } + + MO_DBG_VERBOSE(" > CHARGING PROFILE:\n" \ + " > chargingProfileId: %i\n" \ + " > transactionId16: %i\n" \ + " > transactionId201: %i\n" \ + " > stackLevel: %i\n" \ + " > chargingProfilePurpose: %s\n", + chargingProfileId, + transactionId16, + transactionId201, + stackLevel, + serializeChargingProfilePurposeType(chargingProfilePurpose) + ); + MO_DBG_VERBOSE( + " > chargingProfileKind: %s\n" \ + " > recurrencyKind: %s\n" \ + " > validFrom: %s\n" \ + " > validTo: %s\n", + chargingProfileKind == (ChargingProfileKindType::Absolute) ? "Absolute" : + chargingProfileKind == (ChargingProfileKindType::Recurring) ? "Recurring" : + chargingProfileKind == (ChargingProfileKindType::Relative) ? "Relative" : "Error", + recurrencyKind == (RecurrencyKindType::Daily) ? "Daily" : + recurrencyKind == (RecurrencyKindType::Weekly) ? "Weekly" : + recurrencyKind == (RecurrencyKindType::UNDEFINED) ? "NOT_SET" : "Error", + validFrom, + validTo + ); + + chargingSchedule.printSchedule(clock); +} + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h index 825d6866..3ef5db02 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h @@ -2,13 +2,21 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#ifndef SMARTCHARGINGMODEL_H -#define SMARTCHARGINGMODEL_H +#ifndef MO_SMARTCHARGINGMODEL_H +#define MO_SMARTCHARGINGMODEL_H #ifndef MO_ChargeProfileMaxStackLevel #define MO_ChargeProfileMaxStackLevel 8 #endif +#ifndef MO_ChargeProfileMaxStackLevel_digits +#define MO_ChargeProfileMaxStackLevel_digits 1 //digits needed to print MO_ChargeProfileMaxStackLevel (=8, i.e. 1 digit) +#endif + +#if MO_ChargeProfileMaxStackLevel >= 10 && MO_ChargeProfileMaxStackLevel_digits < 2 +#error Need to set build flag MO_ChargeProfileMaxStackLevel_digits +#endif + #ifndef MO_ChargingScheduleMaxPeriods #define MO_ChargingScheduleMaxPeriods 24 #endif @@ -24,70 +32,95 @@ #include #include +#include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + float power; //in W. Negative if undefined + float current; //in A. Negative if undefined + int numberPhases; //Negative if undefined + + #if MO_ENABLE_V201 + int phaseToUse; //select between L1, L2, L3 (negative if undefined) + #endif //MO_ENABLE_V201 +} MO_ChargeRate; + +void mo_chargeRate_init(MO_ChargeRate *limit); //initializes all fields to -1 +bool mo_chargeRate_equals(const MO_ChargeRate *a, const MO_ChargeRate *b); + +#ifdef __cplusplus +} //extern "C" namespace MicroOcpp { -enum class ChargingProfilePurposeType { +class Clock; + +enum class ChargingProfilePurposeType : uint8_t { + UNDEFINED, ChargePointMaxProfile, TxDefaultProfile, TxProfile }; +const char *serializeChargingProfilePurposeType(ChargingProfilePurposeType v); +ChargingProfilePurposeType deserializeChargingProfilePurposeType(const char *str); -enum class ChargingProfileKindType { +enum class ChargingProfileKindType : uint8_t { Absolute, Recurring, Relative }; -enum class RecurrencyKindType { - NOT_SET, //not part of OCPP 1.6 +enum class RecurrencyKindType : uint8_t { + UNDEFINED, //MO-internal Daily, Weekly }; -enum class ChargingRateUnitType { +enum class ChargingRateUnitType : uint8_t { + UNDEFINED, //MO-internal Watt, Amp }; - -struct ChargeRate { - float power = std::numeric_limits::max(); - float current = std::numeric_limits::max(); - int nphases = std::numeric_limits::max(); - - bool operator==(const ChargeRate& rhs) { - return power == rhs.power && - current == rhs.current && - nphases == rhs.nphases; - } - bool operator!=(const ChargeRate& rhs) { - return !(*this == rhs); - } -}; - -//returns a new vector with the minimum of each component -ChargeRate chargeRate_min(const ChargeRate& a, const ChargeRate& b); +const char *serializeChargingRateUnitType(ChargingRateUnitType v); +ChargingRateUnitType deserializeChargingRateUnitType(const char *str); class ChargingSchedulePeriod { public: int startPeriod; float limit; int numberPhases = 3; + #if MO_ENABLE_V201 + int phaseToUse = -1; + #endif }; class ChargingSchedule : public MemoryManaged { public: + #if MO_ENABLE_V201 + int id = -1; //defined for ChargingScheduleType (2.12), not used for CompositeScheduleType (2.18) + #endif int duration = -1; - Timestamp startSchedule; + int32_t startSchedule = 0; //unix time. 0 if undefined ChargingRateUnitType chargingRateUnit; - Vector chargingSchedulePeriod; + ChargingSchedulePeriod **chargingSchedulePeriod = nullptr; + size_t chargingSchedulePeriodSize = 0; float minChargingRate = -1.0f; ChargingProfileKindType chargingProfileKind; //copied from ChargingProfile to increase cohesion of limit algorithms - RecurrencyKindType recurrencyKind = RecurrencyKindType::NOT_SET; //copied from ChargingProfile to increase cohesion of limit algorithms + RecurrencyKindType recurrencyKind = RecurrencyKindType::UNDEFINED; //copied from ChargingProfile to increase cohesion of limit algorithms ChargingSchedule(); + ~ChargingSchedule(); + + bool resizeChargingSchedulePeriod(size_t chargingSchedulePeriodSize); + /** * limit: output parameter * nextChange: output parameter @@ -96,26 +129,33 @@ class ChargingSchedule : public MemoryManaged { * if true, limit and nextChange will be set according to this Schedule * if false, only nextChange will be set */ - bool calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange); + bool calculateLimit(int32_t unixTime, int32_t sessionDurationSecs, MO_ChargeRate& limit, int32_t& nextChangeSecs); - bool toJson(JsonDoc& out); + bool parseJson(Clock& clock, int ocppVersion, JsonObject json); + size_t getJsonCapacity(int ocppVersion, bool compositeSchedule); + bool toJson(Clock& clock, int ocppVersion, bool compositeSchedule, JsonObject out, int evseId = -1); /* * print on console */ - void printSchedule(); + void printSchedule(Clock& clock); }; class ChargingProfile : public MemoryManaged { public: int chargingProfileId = -1; - int transactionId = -1; + #if MO_ENABLE_V16 + int transactionId16 = -1; + #endif + #if MO_ENABLE_V201 + char transactionId201 [MO_TXID_SIZE] = {'\0'}; + #endif int stackLevel = 0; ChargingProfilePurposeType chargingProfilePurpose {ChargingProfilePurposeType::TxProfile}; ChargingProfileKindType chargingProfileKind {ChargingProfileKindType::Relative}; //copied to ChargingSchedule to increase cohesion of limit algorithms - RecurrencyKindType recurrencyKind {RecurrencyKindType::NOT_SET}; // copied to ChargingSchedule to increase cohesion - Timestamp validFrom; - Timestamp validTo; + RecurrencyKindType recurrencyKind {RecurrencyKindType::UNDEFINED}; // copied to ChargingSchedule to increase cohesion + int32_t validFrom = 0; //unix time. 0 if undefined + int32_t validTo = 0; //unix time. 0 if undefined ChargingSchedule chargingSchedule; ChargingProfile(); @@ -128,31 +168,19 @@ class ChargingProfile : public MemoryManaged { * if true, limit and nextChange will be set according to this Schedule * if false, only nextChange will be set */ - bool calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange); - - /* - * Simpler function if startOfCharging is not available. Caution: This likely will differ from function with startOfCharging - */ - bool calculateLimit(const Timestamp &t, ChargeRate& limit, Timestamp& nextChange); - - int getChargingProfileId(); - int getTransactionId(); - int getStackLevel(); - - ChargingProfilePurposeType getChargingProfilePurpose(); + bool calculateLimit(int32_t unixTime, int32_t sessionDurationSecs, MO_ChargeRate& limit, int32_t& nextChangeSecs); - bool toJson(JsonDoc& out); + bool parseJson(Clock& clock, int ocppVersion, JsonObject json); + size_t getJsonCapacity(int ocppVersion); + bool toJson(Clock& clock, int ocppVersion, JsonObject out); /* * print on console */ - void printProfile(); + void printProfile(Clock& clock); }; -std::unique_ptr loadChargingProfile(JsonObject& json); - -bool loadChargingSchedule(JsonObject& json, ChargingSchedule& out); - -} //end namespace MicroOcpp - +} //namespace MicroOcpp +#endif //__cplusplus +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING #endif diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index a73f53ec..51e9ee92 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -3,61 +3,108 @@ // MIT License #include -#include + +#include #include -#include -#include +#include +#include +#include +#include #include #include #include #include #include +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING + +namespace MicroOcpp { +template +const T& minIfDefined(const T& a, const T& b) { + return (a >= (T)0 && b >= (T)0) ? std::min(a, b) : std::max(a, b); +} + +//filesystem-related helper functions +namespace SmartChargingServiceUtils { +bool printProfileFileName(char *out, size_t bufsize, unsigned int evseId, ChargingProfilePurposeType purpose, unsigned int stackLevel); +bool storeProfile(MO_FilesystemAdapter *filesystem, Clock& clock, int ocppVersion, unsigned int evseId, ChargingProfile *chargingProfile); +bool removeProfile(MO_FilesystemAdapter *filesystem, unsigned int evseId, ChargingProfilePurposeType purpose, unsigned int stackLevel); +} //namespace SmartChargingServiceUtils +} //namespace MicroOcpp + using namespace::MicroOcpp; -SmartChargingServiceEvse::SmartChargingServiceEvse(Model& model, std::shared_ptr filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile) : - MemoryManaged("v16.SmartCharging.SmartChargingServiceEvse"), model(model), filesystem{filesystem}, connectorId{connectorId}, ChargePointMaxProfile(ChargePointMaxProfile), ChargePointTxDefaultProfile(ChargePointTxDefaultProfile) { +SmartChargingServiceEvse::SmartChargingServiceEvse(Context& context, SmartChargingService& scService, unsigned int evseId) : + MemoryManaged("v16.SmartCharging.SmartChargingServiceEvse"), context(context), clock(context.getClock()), scService{scService}, evseId{evseId} { } SmartChargingServiceEvse::~SmartChargingServiceEvse() { - + for (size_t i = 0; i < MO_CHARGEPROFILESTACK_SIZE; i++) { + delete txDefaultProfile[i]; + txDefaultProfile[i] = nullptr; + } + for (size_t i = 0; i < MO_CHARGEPROFILESTACK_SIZE; i++) { + delete txProfile[i]; + txProfile[i] = nullptr; + } } /* * limitOut: the calculated maximum charge rate at the moment, or the default value if no limit is defined * validToOut: The begin of the next SmartCharging restriction after time t */ -void SmartChargingServiceEvse::calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut) { +void SmartChargingServiceEvse::calculateLimit(int32_t unixTime, int32_t sessionDurationSecs, MO_ChargeRate& limitOut, int32_t& nextChangeSecsOut) { //initialize output parameters with the default values - limitOut = ChargeRate(); - validToOut = MAX_TIME; + mo_chargeRate_init(&limitOut); + nextChangeSecsOut = 365 * 24 * 3600; bool txLimitDefined = false; - ChargeRate txLimit; + MO_ChargeRate txLimit; + mo_chargeRate_init(&txLimit); - //first, check if TxProfile is defined and limits charging + //first, check if txProfile is defined and limits charging for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) { - if (TxProfile[i] && ((trackTxRmtProfileId >= 0 && trackTxRmtProfileId == TxProfile[i]->getChargingProfileId()) || - TxProfile[i]->getTransactionId() < 0 || - trackTxId == TxProfile[i]->getTransactionId())) { - ChargeRate crOut; - bool defined = TxProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut); - if (defined) { - txLimitDefined = true; - txLimit = crOut; - break; + if (txProfile[i]) { + + bool txMatch = false; + + #if MO_ENABLE_V16 + if (scService.ocppVersion == MO_OCPP_V16) { + txMatch = (trackTxRmtProfileId >= 0 && trackTxRmtProfileId == txProfile[i]->chargingProfileId) || + txProfile[i]->transactionId16 < 0 || + trackTransactionId16 == txProfile[i]->transactionId16; + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (scService.ocppVersion == MO_OCPP_V201) { + txMatch = (trackTxRmtProfileId >= 0 && trackTxRmtProfileId == txProfile[i]->chargingProfileId) || + !*txProfile[i]->transactionId201 < 0 || + !strcmp(trackTransactionId201, txProfile[i]->transactionId201); + } + #endif //MO_ENABLE_V201 + + if (txMatch) { + MO_ChargeRate crOut; + mo_chargeRate_init(&crOut); + bool defined = txProfile[i]->calculateLimit(unixTime, sessionDurationSecs, crOut, nextChangeSecsOut); + if (defined) { + txLimitDefined = true; + txLimit = crOut; + break; + } } } } - //if no TxProfile limits charging, check the TxDefaultProfiles for this connector - if (!txLimitDefined && trackTxStart < MAX_TIME) { + //if no txProfile limits charging, check the txDefaultProfiles for this connector + if (!txLimitDefined && sessionDurationSecs >= 0) { for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) { - if (TxDefaultProfile[i]) { - ChargeRate crOut; - bool defined = TxDefaultProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut); + if (txDefaultProfile[i]) { + MO_ChargeRate crOut; + mo_chargeRate_init(&crOut); + bool defined = txDefaultProfile[i]->calculateLimit(unixTime, sessionDurationSecs, crOut, nextChangeSecsOut); if (defined) { txLimitDefined = true; txLimit = crOut; @@ -67,12 +114,13 @@ void SmartChargingServiceEvse::calculateLimit(const Timestamp &t, ChargeRate& li } } - //if no appropriate TxDefaultProfile is set for this connector, search in the general TxDefaultProfiles - if (!txLimitDefined && trackTxStart < MAX_TIME) { + //if no appropriate txDefaultProfile is set for this connector, search in the general txDefaultProfiles + if (!txLimitDefined && sessionDurationSecs >= 0) { for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) { - if (ChargePointTxDefaultProfile[i]) { - ChargeRate crOut; - bool defined = ChargePointTxDefaultProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut); + if (scService.chargePointMaxProfile[i]) { + MO_ChargeRate crOut; + mo_chargeRate_init(&crOut); + bool defined = scService.chargePointMaxProfile[i]->calculateLimit(unixTime, sessionDurationSecs, crOut, nextChangeSecsOut); if (defined) { txLimitDefined = true; txLimit = crOut; @@ -82,13 +130,15 @@ void SmartChargingServiceEvse::calculateLimit(const Timestamp &t, ChargeRate& li } } - ChargeRate cpLimit; + MO_ChargeRate cpLimit; + mo_chargeRate_init(&cpLimit); //the calculated maximum charge rate is also limited by the ChargePointMaxProfiles for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) { - if (ChargePointMaxProfile[i]) { - ChargeRate crOut; - bool defined = ChargePointMaxProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut); + if (scService.chargePointMaxProfile[i]) { + MO_ChargeRate crOut; + mo_chargeRate_init(&crOut); + bool defined = scService.chargePointMaxProfile[i]->calculateLimit(unixTime, sessionDurationSecs, crOut, nextChangeSecsOut); if (defined) { cpLimit = crOut; break; @@ -96,50 +146,106 @@ void SmartChargingServiceEvse::calculateLimit(const Timestamp &t, ChargeRate& li } } - //apply ChargePointMaxProfile value to calculated limit - limitOut = chargeRate_min(txLimit, cpLimit); + //apply ChargePointMaxProfile value to calculated limit: + //determine the minimum of both values if at least one is defined, otherwise assign -1 for undefined + limitOut.power = minIfDefined(txLimit.power, cpLimit.power); + limitOut.current = minIfDefined(txLimit.current, cpLimit.current); + limitOut.numberPhases = minIfDefined(txLimit.numberPhases, cpLimit.numberPhases); + + #if MO_ENABLE_V201 + if (cpLimit.phaseToUse >= 0) { + //ChargePoint Schedule always takes precedence for phaseToUse + limitOut.phaseToUse = cpLimit.phaseToUse; + } else { + limitOut.phaseToUse = txLimit.phaseToUse; //note: can be -1 for undefined + } + #endif } void SmartChargingServiceEvse::trackTransaction() { - Transaction *tx = nullptr; - if (model.getConnector(connectorId)) { - tx = model.getConnector(connectorId)->getTransaction().get(); - } - bool update = false; - if (tx) { - if (tx->getTxProfileId() != trackTxRmtProfileId) { - update = true; - trackTxRmtProfileId = tx->getTxProfileId(); - } - if (tx->getStartSync().isRequested() && tx->getStartTimestamp() != trackTxStart) { - update = true; - trackTxStart = tx->getStartTimestamp(); - } - if (tx->getStartSync().isConfirmed() && tx->getTransactionId() != trackTxId) { - update = true; - trackTxId = tx->getTransactionId(); - } - } else { - //check if transaction has just been completed - if (trackTxRmtProfileId >= 0 || trackTxStart < MAX_TIME || trackTxId >= 0) { - //yes, clear data - update = true; - trackTxRmtProfileId = -1; - trackTxStart = MAX_TIME; - trackTxId = -1; + #if MO_ENABLE_V16 + if (scService.ocppVersion == MO_OCPP_V16) { + auto txSvc = context.getModel16().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + auto tx = txSvcEvse ? txSvcEvse->getTransaction() : nullptr; - clearChargingProfile([this] (int, int connectorId, ChargingProfilePurposeType purpose, int) { - return purpose == ChargingProfilePurposeType::TxProfile && - (int) this->connectorId == connectorId; - }); + if (tx) { + if (tx->getTxProfileId() != trackTxRmtProfileId) { + update = true; + trackTxRmtProfileId = tx->getTxProfileId(); + } + int32_t dtTxStart; + if (tx->getStartSync().isRequested() && clock.delta(tx->getStartTimestamp(), trackTxStart, dtTxStart) && dtTxStart != 0) { + update = true; + trackTxStart = tx->getStartTimestamp(); + } + if (tx->getStartSync().isConfirmed() && tx->getTransactionId() != trackTransactionId16) { + update = true; + trackTransactionId16 = tx->getTransactionId(); + } + } else { + //check if transaction has just been completed + if (trackTxRmtProfileId >= 0 || trackTxStart.isDefined() || trackTransactionId16 >= 0) { + //yes, clear data + update = true; + trackTxRmtProfileId = -1; + trackTxStart = Timestamp(); + trackTransactionId16 = -1; + + clearChargingProfile(-1, ChargingProfilePurposeType::TxProfile, -1); + } } } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (scService.ocppVersion == MO_OCPP_V201) { + auto txService = context.getModel201().getTransactionService(); + auto txServiceEvse = txService ? txService->getEvse(evseId) : nullptr; + auto tx = txServiceEvse ? txServiceEvse->getTransaction() : nullptr; + + if (tx) { + if (tx->txProfileId != trackTxRmtProfileId) { + update = true; + trackTxRmtProfileId = tx->txProfileId; + } + if (tx->started) { + int32_t dt; + if (!clock.delta(tx->startTimestamp, trackTxStart, dt)) { + dt = -1; //force update + } + if (dt != 0) { + update = true; + trackTxStart = tx->startTimestamp; + } + } + if (strcmp(tx->transactionId, trackTransactionId201)) { + update = true; + auto ret = snprintf(trackTransactionId201, sizeof(trackTransactionId201), "%s", tx->transactionId); + if (ret < 0 || ret >= sizeof(trackTransactionId201)) { + MO_DBG_ERR("snprintf: %i", ret); + memset(trackTransactionId201, 0, sizeof(trackTransactionId201)); + } + } + } else { + //check if transaction has just been completed + if (trackTxRmtProfileId >= 0 || trackTxStart.isDefined() || trackTransactionId16 >= 0) { + //yes, clear data + update = true; + trackTxRmtProfileId = -1; + trackTxStart = Timestamp(); + memset(trackTransactionId201, 0, sizeof(trackTransactionId201)); + + clearChargingProfile(-1, ChargingProfilePurposeType::TxProfile, -1); + } + } + } + #endif //MO_ENABLE_V201 if (update) { - nextChange = model.getClock().now(); //will refresh limit calculation + nextChange = clock.now(); //will refresh limit calculation } } @@ -150,269 +256,487 @@ void SmartChargingServiceEvse::loop(){ /** * check if to call onLimitChange */ - auto& tnow = model.getClock().now(); + auto& tnow = clock.now(); - if (tnow >= nextChange){ + int32_t dtNextChange; + if (!clock.delta(tnow, nextChange, dtNextChange)) { + dtNextChange = 0; + } - ChargeRate limit; - nextChange = MAX_TIME; //reset nextChange to default value and refresh it + if (dtNextChange >= 0) { - calculateLimit(tnow, limit, nextChange); + int32_t unixTime = -1; + if (!clock.toUnixTime(tnow, unixTime)) { + unixTime = 0; //undefined + } + + int32_t sessionDurationSecs = -1; + if (!trackTxStart.isDefined() || !clock.delta(tnow, trackTxStart, sessionDurationSecs)) { + sessionDurationSecs = -1; + } + + MO_ChargeRate limit; + int32_t nextChangeSecs; + + calculateLimit(unixTime, sessionDurationSecs, limit, nextChangeSecs); + + //reset nextChange with nextChangeSecs + nextChange = tnow; + clock.add(nextChange, nextChangeSecs); #if MO_DBG_LEVEL >= MO_DL_INFO { - char timestamp1[JSONDATE_LENGTH + 1] = {'\0'}; - tnow.toJsonString(timestamp1, JSONDATE_LENGTH + 1); - char timestamp2[JSONDATE_LENGTH + 1] = {'\0'}; - nextChange.toJsonString(timestamp2, JSONDATE_LENGTH + 1); + char timestamp1[MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(tnow, timestamp1, sizeof(timestamp1))) { + timestamp1[0] = '\0'; + } + char timestamp2[MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(nextChange, timestamp2, sizeof(timestamp2))) { + timestamp1[0] = '\0'; + } MO_DBG_INFO("New limit for connector %u, scheduled at = %s, nextChange = %s, limit = {%.1f, %.1f, %i}", - connectorId, + evseId, timestamp1, timestamp2, - limit.power != std::numeric_limits::max() ? limit.power : -1.f, - limit.current != std::numeric_limits::max() ? limit.current : -1.f, - limit.nphases != std::numeric_limits::max() ? limit.nphases : -1); + limit.power, + limit.current, + limit.numberPhases); } #endif - if (trackLimitOutput != limit) { + if (!mo_chargeRate_equals(&trackLimitOutput, &limit)) { if (limitOutput) { - limitOutput( - limit.power != std::numeric_limits::max() ? limit.power : -1.f, - limit.current != std::numeric_limits::max() ? limit.current : -1.f, - limit.nphases != std::numeric_limits::max() ? limit.nphases : -1); + limitOutput(limit, evseId, limitOutputUserData); trackLimitOutput = limit; } } } } -void SmartChargingServiceEvse::setSmartChargingOutput(std::function limitOutput) { +void SmartChargingServiceEvse::setSmartChargingOutput(void (*limitOutput)(MO_ChargeRate limit, unsigned int evseId, void *userData), bool powerSupported, bool currentSupported, bool phases3to1Supported, bool phaseSwitchingSupported, void *userData) { if (this->limitOutput) { MO_DBG_WARN("replacing existing SmartChargingOutput"); } this->limitOutput = limitOutput; + this->limitOutputUserData = userData; + + scService.powerSupported |= powerSupported; + scService.currentSupported |= currentSupported; + scService.phases3to1Supported |= phases3to1Supported; + #if MO_ENABLE_V201 + scService.phaseSwitchingSupported |= phaseSwitchingSupported; + #endif //MO_ENABLE_V201 } -ChargingProfile *SmartChargingServiceEvse::updateProfiles(std::unique_ptr chargingProfile) { - - int stackLevel = chargingProfile->getStackLevel(); //already validated +bool SmartChargingServiceEvse::updateProfile(std::unique_ptr chargingProfile, bool updateFile) { - switch (chargingProfile->getChargingProfilePurpose()) { - case (ChargingProfilePurposeType::ChargePointMaxProfile): + ChargingProfile **stack = nullptr; + + switch (chargingProfile->chargingProfilePurpose) { + case ChargingProfilePurposeType::TxDefaultProfile: + stack = txDefaultProfile; + break; + case ChargingProfilePurposeType::TxProfile: + stack = txProfile; break; - case (ChargingProfilePurposeType::TxDefaultProfile): - TxDefaultProfile[stackLevel] = std::move(chargingProfile); - return TxDefaultProfile[stackLevel].get(); - case (ChargingProfilePurposeType::TxProfile): - TxProfile[stackLevel] = std::move(chargingProfile); - return TxProfile[stackLevel].get(); + default: + break; + } + + if (!stack) { + MO_DBG_ERR("invalid args"); + return false; } + + if (updateFile && scService.filesystem) { + if (!SmartChargingServiceUtils::storeProfile(scService.filesystem, clock, scService.ocppVersion, evseId, chargingProfile.get())) { + MO_DBG_ERR("fs error"); + return false; + } + } + - MO_DBG_ERR("invalid args"); - return nullptr; + int stackLevel = chargingProfile->stackLevel; //already validated + + stack[stackLevel] = chargingProfile.release(); + + return true; } void SmartChargingServiceEvse::notifyProfilesUpdated() { - nextChange = model.getClock().now(); + nextChange = clock.now(); } -bool SmartChargingServiceEvse::clearChargingProfile(const std::function filter) { +bool SmartChargingServiceEvse::clearChargingProfile(int chargingProfileId, ChargingProfilePurposeType chargingProfilePurpose, int stackLevel) { bool found = false; - ProfileStack *profileStacks [] = {&TxProfile, &TxDefaultProfile}; + ChargingProfilePurposeType purposes[] = {ChargingProfilePurposeType::TxProfile, ChargingProfilePurposeType::TxDefaultProfile}; + for (unsigned int p = 0; p < sizeof(purposes) / sizeof(purposes[0]); p++) { + ChargingProfilePurposeType purpose = purposes[p]; + if (chargingProfilePurpose != ChargingProfilePurposeType::UNDEFINED && chargingProfilePurpose != purpose) { + continue; + } - for (auto stack : profileStacks) { - for (size_t iLevel = 0; iLevel < stack->size(); iLevel++) { - if (auto& profile = stack->at(iLevel)) { - if (profile && filter(profile->getChargingProfileId(), connectorId, profile->getChargingProfilePurpose(), iLevel)) { - found = true; - SmartChargingServiceUtils::removeProfile(filesystem, connectorId, profile->getChargingProfilePurpose(), iLevel); - profile.reset(); - } + ChargingProfile **stack = nullptr; + if (purpose == ChargingProfilePurposeType::TxProfile) { + stack = txProfile; + } else if (purpose == ChargingProfilePurposeType::TxDefaultProfile) { + stack = txDefaultProfile; + } + + for (size_t sLvl = 0; sLvl < MO_CHARGEPROFILESTACK_SIZE; sLvl++) { + if (stackLevel >= 0 && (size_t)stackLevel != sLvl) { + continue; + } + + if (!stack[sLvl]) { + // no profile installed at this stack and stackLevel + continue; } + + if (chargingProfileId >= 0 && chargingProfileId != stack[sLvl]->chargingProfileId) { + continue; + } + + // this profile matches all filter criteria + if (scService.filesystem) { + SmartChargingServiceUtils::removeProfile(scService.filesystem, evseId, purpose, sLvl); + } + delete stack[sLvl]; + stack[sLvl] = nullptr; + found = true; } } return found; } -std::unique_ptr SmartChargingServiceEvse::getCompositeSchedule(int duration, ChargingRateUnitType_Optional unit) { +std::unique_ptr SmartChargingServiceEvse::getCompositeSchedule(int duration, ChargingRateUnitType unit) { - auto& startSchedule = model.getClock().now(); + int32_t nowUnixTime; + if (!clock.toUnixTime(clock.now(), nowUnixTime)) { + MO_DBG_ERR("internal error"); + return nullptr; + } + + // dry run to measure Schedule size + size_t periodsSize = 0; + + int32_t periodBegin = nowUnixTime; + int32_t measuredDuration = 0; + while (measuredDuration < duration && periodsSize < MO_ChargingScheduleMaxPeriods) { + MO_ChargeRate limit; + int32_t nextChangeSecs; + + calculateLimit(periodBegin, -1, limit, nextChangeSecs); + + periodBegin += nextChangeSecs; + measuredDuration += nextChangeSecs; + periodsSize++; + } auto schedule = std::unique_ptr(new ChargingSchedule()); + if (!schedule || !schedule->resizeChargingSchedulePeriod(periodsSize)) { + MO_DBG_ERR("OOM"); + return nullptr; + } schedule->duration = duration; - schedule->startSchedule = startSchedule; + schedule->startSchedule = nowUnixTime; schedule->chargingProfileKind = ChargingProfileKindType::Absolute; - schedule->recurrencyKind = RecurrencyKindType::NOT_SET; - - auto& periods = schedule->chargingSchedulePeriod; + schedule->recurrencyKind = RecurrencyKindType::UNDEFINED; - Timestamp periodBegin = Timestamp(startSchedule); - Timestamp periodStop = Timestamp(startSchedule); + periodBegin = nowUnixTime; + measuredDuration = 0; - while (periodBegin - startSchedule < duration && periods.size() < MO_ChargingScheduleMaxPeriods) { + for (size_t i = 0; measuredDuration < duration && i < periodsSize; i++) { //calculate limit - ChargeRate limit; - calculateLimit(periodBegin, limit, periodStop); + MO_ChargeRate limit; + int32_t nextChangeSecs; + + calculateLimit(periodBegin, -1, limit, nextChangeSecs); //if the unit is still unspecified, guess by taking the unit of the first limit - if (unit == ChargingRateUnitType_Optional::None) { - if (limit.power < limit.current) { - unit = ChargingRateUnitType_Optional::Watt; + if (unit == ChargingRateUnitType::UNDEFINED) { + if (limit.power > limit.current) { + unit = ChargingRateUnitType::Watt; } else { - unit = ChargingRateUnitType_Optional::Amp; + unit = ChargingRateUnitType::Amp; } } - periods.emplace_back(); - float limit_opt = unit == ChargingRateUnitType_Optional::Watt ? limit.power : limit.current; - periods.back().limit = limit_opt != std::numeric_limits::max() ? limit_opt : -1.f, - periods.back().numberPhases = limit.nphases != std::numeric_limits::max() ? limit.nphases : -1; - periods.back().startPeriod = periodBegin - startSchedule; - - periodBegin = periodStop; - } + float limit_opt = unit == ChargingRateUnitType::Watt ? limit.power : limit.current; + schedule->chargingSchedulePeriod[i]->limit = (unit == ChargingRateUnitType::Watt) ? limit.power : limit.current, + schedule->chargingSchedulePeriod[i]->numberPhases = limit.numberPhases; + schedule->chargingSchedulePeriod[i]->startPeriod = measuredDuration; + #if MO_ENABLE_V201 + schedule->chargingSchedulePeriod[i]->phaseToUse = limit.phaseToUse; + #endif //MO_ENABLE_V201 - if (unit == ChargingRateUnitType_Optional::Watt) { - schedule->chargingRateUnit = ChargingRateUnitType::Watt; - } else { - schedule->chargingRateUnit = ChargingRateUnitType::Amp; + periodBegin += nextChangeSecs; + measuredDuration += nextChangeSecs; } + schedule->chargingRateUnit = unit; + return schedule; } size_t SmartChargingServiceEvse::getChargingProfilesCount() { size_t chargingProfilesCount = 0; - for (size_t i = 0; i < MO_ChargeProfileMaxStackLevel + 1; i++) { - if (TxDefaultProfile[i]) { + for (size_t i = 0; i < MO_CHARGEPROFILESTACK_SIZE; i++) { + if (txDefaultProfile[i]) { chargingProfilesCount++; } - if (TxProfile[i]) { + if (txProfile[i]) { chargingProfilesCount++; } } return chargingProfilesCount; } -SmartChargingServiceEvse *SmartChargingService::getScConnectorById(unsigned int connectorId) { - if (connectorId == 0) { - return nullptr; +SmartChargingService::SmartChargingService(Context& context) + : MemoryManaged("v16.SmartCharging.SmartChargingService"), context(context), clock(context.getClock()) { + +} + +SmartChargingService::~SmartChargingService() { + for (unsigned int i = 0; i < MO_NUM_EVSEID; i++) { + delete evses[i]; + evses[i] = nullptr; + } + for (size_t i = 0; i < MO_CHARGEPROFILESTACK_SIZE; i++) { + delete chargePointMaxProfile[i]; + chargePointMaxProfile[i] = nullptr; } + for (size_t i = 0; i < MO_CHARGEPROFILESTACK_SIZE; i++) { + delete chargePointTxDefaultProfile[i]; + chargePointTxDefaultProfile[i] = nullptr; + } +} + +bool SmartChargingService::setup() { + + ocppVersion = context.getOcppVersion(); + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { - if (connectorId - 1 >= connectors.size()) { - return nullptr; } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { - return &connectors[connectorId-1]; -} + } + #endif //MO_ENABLE_V201 + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } -SmartChargingService::SmartChargingService(Context& context, std::shared_ptr filesystem, unsigned int numConnectors) - : MemoryManaged("v16.SmartCharging.SmartChargingService"), context(context), filesystem{filesystem}, connectors{makeVector(getMemoryTag())}, numConnectors(numConnectors) { - - for (unsigned int cId = 1; cId < numConnectors; cId++) { - connectors.emplace_back(context.getModel(), filesystem, cId, ChargePointMaxProfile, ChargePointTxDefaultProfile); + const char *chargingScheduleAllowedChargingRateUnit = ""; + if (powerSupported && currentSupported) { + chargingScheduleAllowedChargingRateUnit = "Current,Power"; + } else if (powerSupported) { + chargingScheduleAllowedChargingRateUnit = "Power"; + } else if (currentSupported) { + chargingScheduleAllowedChargingRateUnit = "Current"; + } + configService->declareConfiguration("ChargingScheduleAllowedChargingRateUnit", chargingScheduleAllowedChargingRateUnit, MO_CONFIGURATION_VOLATILE); + + configService->declareConfiguration("ChargeProfileMaxStackLevel", MO_ChargeProfileMaxStackLevel, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + configService->declareConfiguration("ChargingScheduleAllowedChargingRateUnit", "", MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + configService->declareConfiguration("ChargingScheduleMaxPeriods", MO_ChargingScheduleMaxPeriods, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + configService->declareConfiguration("MaxChargingProfilesInstalled", MO_MaxChargingProfilesInstalled, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + if (phases3to1Supported) { + configService->declareConfiguration("ConnectorSwitch3to1PhaseSupported", phases3to1Supported, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + } + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + + auto varService = context.getModel201().getVariableService(); + if (!varService) { + MO_DBG_ERR("initialization error"); + return false; + } + + chargingProfileEntriesInt201 = varService->declareVariable("SmartChargingCtrlr", "ChargingProfileEntries", 0, Mutability::ReadOnly, false); + if (!chargingProfileEntriesInt201) { + MO_DBG_ERR("setup failure"); + return false; + } + + varService->declareVariable("SmartChargingCtrlr", "SmartChargingEnabled", true, Mutability::ReadOnly, false); + if (phaseSwitchingSupported) { + varService->declareVariable("SmartChargingCtrlr", "ACPhaseSwitchingSupported", phaseSwitchingSupported, Mutability::ReadOnly, false); + } + varService->declareVariable("SmartChargingCtrlr", "ChargingProfileMaxStackLevel", MO_ChargeProfileMaxStackLevel, Mutability::ReadOnly, false); + + const char *chargingScheduleChargingRateUnit = ""; + if (powerSupported && currentSupported) { + chargingScheduleChargingRateUnit = "A,W"; + } else if (powerSupported) { + chargingScheduleChargingRateUnit = "W"; + } else if (currentSupported) { + chargingScheduleChargingRateUnit = "A"; + } + varService->declareVariable("SmartChargingCtrlr", "ChargingScheduleChargingRateUnit", chargingScheduleChargingRateUnit, Mutability::ReadOnly, false); + + varService->declareVariable("SmartChargingCtrlr", "PeriodsPerSchedule", MO_ChargingScheduleMaxPeriods, Mutability::ReadOnly, false); + if (phases3to1Supported) { + varService->declareVariable("SmartChargingCtrlr", "Phases3to1", phases3to1Supported, Mutability::ReadOnly, false); + } + varService->declareVariable("SmartChargingCtrlr", "SmartChargingAvailable", true, Mutability::ReadOnly, false); + } + #endif //MO_ENABLE_V201 + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + context.getMessageService().registerOperation("ClearChargingProfile", [] (Context& context) -> Operation* { + return new ClearChargingProfile(*context.getModel16().getSmartChargingService(), MO_OCPP_V16);}); + context.getMessageService().registerOperation("GetCompositeSchedule", [] (Context& context) -> Operation* { + return new GetCompositeSchedule(context, *context.getModel16().getSmartChargingService());}); + context.getMessageService().registerOperation("SetChargingProfile", [] (Context& context) -> Operation* { + return new SetChargingProfile(context, *context.getModel16().getSmartChargingService());}); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + context.getMessageService().registerOperation("ClearChargingProfile", [] (Context& context) -> Operation* { + return new ClearChargingProfile(*context.getModel201().getSmartChargingService(), MO_OCPP_V201);}); + context.getMessageService().registerOperation("GetCompositeSchedule", [] (Context& context) -> Operation* { + return new GetCompositeSchedule(context, *context.getModel201().getSmartChargingService());}); + context.getMessageService().registerOperation("SetChargingProfile", [] (Context& context) -> Operation* { + return new SetChargingProfile(context, *context.getModel201().getSmartChargingService());}); } + #endif //MO_ENABLE_V201 - declareConfiguration("ChargeProfileMaxStackLevel", MO_ChargeProfileMaxStackLevel, CONFIGURATION_VOLATILE, true); - declareConfiguration("ChargingScheduleAllowedChargingRateUnit", "", CONFIGURATION_VOLATILE, true); - declareConfiguration("ChargingScheduleMaxPeriods", MO_ChargingScheduleMaxPeriods, CONFIGURATION_VOLATILE, true); - declareConfiguration("MaxChargingProfilesInstalled", MO_MaxChargingProfilesInstalled, CONFIGURATION_VOLATILE, true); + numEvseId = context.getModel16().getNumEvseId(); + for (unsigned int i = 1; i < numEvseId; i++) { //evseId 0 won't be populated + if (!getEvse(i)) { + MO_DBG_ERR("OOM"); + return false; + } + } - context.getOperationRegistry().registerOperation("ClearChargingProfile", [this] () { - return new Ocpp16::ClearChargingProfile(*this);}); - context.getOperationRegistry().registerOperation("GetCompositeSchedule", [&context, this] () { - return new Ocpp16::GetCompositeSchedule(context.getModel(), *this);}); - context.getOperationRegistry().registerOperation("SetChargingProfile", [&context, this] () { - return new Ocpp16::SetChargingProfile(context.getModel(), *this);}); + if (!loadProfiles()) { + MO_DBG_ERR("loadProfiles"); + return false; + } - loadProfiles(); + return true; } -SmartChargingService::~SmartChargingService() { +SmartChargingServiceEvse *SmartChargingService::getEvse(unsigned int evseId) { + if (evseId == 0 || evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); + return nullptr; + } + + if (!evses[evseId]) { + evses[evseId] = new SmartChargingServiceEvse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + return evses[evseId]; } -ChargingProfile *SmartChargingService::updateProfiles(unsigned int connectorId, std::unique_ptr chargingProfile){ +bool SmartChargingService::updateProfile(unsigned int evseId, std::unique_ptr chargingProfile, bool updateFile){ - if ((connectorId > 0 && !getScConnectorById(connectorId)) || !chargingProfile) { + if (evseId >= numEvseId || !chargingProfile) { MO_DBG_ERR("invalid args"); - return nullptr; + return false; } if (MO_DBG_LEVEL >= MO_DL_VERBOSE) { MO_DBG_VERBOSE("Charging Profile internal model:"); - chargingProfile->printProfile(); + chargingProfile->printProfile(clock); } - int stackLevel = chargingProfile->getStackLevel(); - if (stackLevel< 0 || stackLevel >= MO_ChargeProfileMaxStackLevel + 1) { + int stackLevel = chargingProfile->stackLevel; + if (stackLevel < 0 || stackLevel >= MO_CHARGEPROFILESTACK_SIZE) { MO_DBG_ERR("input validation failed"); - return nullptr; + return false; } - size_t chargingProfilesCount = 0; - for (size_t i = 0; i < MO_ChargeProfileMaxStackLevel + 1; i++) { - if (ChargePointTxDefaultProfile[i]) { - chargingProfilesCount++; - } - if (ChargePointMaxProfile[i]) { - chargingProfilesCount++; - } - } - for (size_t i = 0; i < connectors.size(); i++) { - chargingProfilesCount += connectors[i].getChargingProfilesCount(); - } + size_t chargingProfilesCount = getChargingProfilesCount(); if (chargingProfilesCount >= MO_MaxChargingProfilesInstalled) { MO_DBG_WARN("number of maximum charging profiles exceeded"); - return nullptr; + return false; } - ChargingProfile *res = nullptr; + bool success = false; - switch (chargingProfile->getChargingProfilePurpose()) { - case (ChargingProfilePurposeType::ChargePointMaxProfile): - if (connectorId != 0) { + switch (chargingProfile->chargingProfilePurpose) { + case ChargingProfilePurposeType::ChargePointMaxProfile: + if (evseId != 0) { MO_DBG_WARN("invalid charging profile"); - return nullptr; + return false; } - ChargePointMaxProfile[stackLevel] = std::move(chargingProfile); - res = ChargePointMaxProfile[stackLevel].get(); + if (updateFile && filesystem) { + if (!SmartChargingServiceUtils::storeProfile(filesystem, clock, ocppVersion, evseId, chargingProfile.get())) { + MO_DBG_ERR("fs error"); + return false; + } + } + chargePointMaxProfile[stackLevel] = chargingProfile.release(); + success = true; break; - case (ChargingProfilePurposeType::TxDefaultProfile): - if (connectorId == 0) { - ChargePointTxDefaultProfile[stackLevel] = std::move(chargingProfile); - res = ChargePointTxDefaultProfile[stackLevel].get(); + case ChargingProfilePurposeType::TxDefaultProfile: + if (evseId == 0) { + if (updateFile && filesystem) { + if (!SmartChargingServiceUtils::storeProfile(filesystem, clock, ocppVersion, evseId, chargingProfile.get())) { + MO_DBG_ERR("fs error"); + return false; + } + } + chargePointTxDefaultProfile[stackLevel] = chargingProfile.release(); + success = true; } else { - res = getScConnectorById(connectorId)->updateProfiles(std::move(chargingProfile)); + success = evses[evseId]->updateProfile(std::move(chargingProfile), updateFile); } break; - case (ChargingProfilePurposeType::TxProfile): - if (connectorId == 0) { + case ChargingProfilePurposeType::TxProfile: + if (evseId == 0) { MO_DBG_WARN("invalid charging profile"); - return nullptr; + return false; } else { - res = getScConnectorById(connectorId)->updateProfiles(std::move(chargingProfile)); + success = evses[evseId]->updateProfile(std::move(chargingProfile), updateFile); } break; + default: + MO_DBG_ERR("input validation failed"); + return false; + } + + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + chargingProfileEntriesInt201->setInt((int)getChargingProfilesCount()); } + #endif //MO_ENABLE_V201 /** * Invalidate the last limit by setting the nextChange to now. By the next loop()-call, the limit * and nextChange will be recalculated and onLimitChanged will be called. */ - if (res) { - nextChange = context.getModel().getClock().now(); - for (size_t i = 0; i < connectors.size(); i++) { - connectors[i].notifyProfilesUpdated(); + if (success) { + nextChange = clock.now(); + for (unsigned int i = 1; i < numEvseId; i++) { + evses[i]->notifyProfilesUpdated(); } } - return res; + return success; } bool SmartChargingService::loadProfiles() { @@ -426,66 +750,111 @@ bool SmartChargingService::loadProfiles() { ChargingProfilePurposeType purposes[] = {ChargingProfilePurposeType::ChargePointMaxProfile, ChargingProfilePurposeType::TxDefaultProfile, ChargingProfilePurposeType::TxProfile}; - char fn [MO_MAX_PATH_SIZE] = {'\0'}; + char fname [MO_MAX_PATH_SIZE] = {'\0'}; - for (auto purpose : purposes) { - for (unsigned int cId = 0; cId < numConnectors; cId++) { + for (unsigned int p = 0; p < sizeof(purposes) / sizeof(purposes[0]); p++) { + ChargingProfilePurposeType purpose = purposes[p]; + + for (unsigned int cId = 0; cId < numEvseId; cId++) { if (cId > 0 && purpose == ChargingProfilePurposeType::ChargePointMaxProfile) { continue; } for (unsigned int iLevel = 0; iLevel < MO_ChargeProfileMaxStackLevel; iLevel++) { - if (!SmartChargingServiceUtils::printProfileFileName(fn, MO_MAX_PATH_SIZE, cId, purpose, iLevel)) { + if (!SmartChargingServiceUtils::printProfileFileName(fname, sizeof(fname), cId, purpose, iLevel)) { return false; } - size_t msize = 0; - if (filesystem->stat(fn, &msize) != 0) { - continue; //There is not a profile on the stack iStack with stacklevel iLevel. Normal case, just continue. + char path [MO_MAX_PATH_SIZE]; + if (!FilesystemUtils::printPath(filesystem, path, sizeof(path), fname)) { + MO_DBG_ERR("fname error: %s", fname); + return false; } - auto profileDoc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag()); - if (!profileDoc) { - success = false; - MO_DBG_ERR("profile corrupt: %s, remove", fn); - filesystem->remove(fn); + JsonDoc doc (0); + auto ret = FilesystemUtils::loadJson(filesystem, fname, doc, getMemoryTag()); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + break; //There is not a profile on the stack iStack with stacklevel iLevel. Normal case, just continue. + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: { + MO_DBG_ERR("profile corrupt: %s, remove", fname); + success = false; + filesystem->remove(path); + } + break; + } + + if (ret != FilesystemUtils::LoadStatus::Success) { continue; } - JsonObject profileJson = profileDoc->as(); - auto chargingProfile = loadChargingProfile(profileJson); + auto chargingProfile = std::unique_ptr(new ChargingProfile()); + if (!chargingProfile) { + MO_DBG_ERR("OOM"); + return false; + } - bool valid = false; - if (chargingProfile) { - valid = updateProfiles(cId, std::move(chargingProfile)); + bool valid = chargingProfile->parseJson(clock, ocppVersion, doc.as()); + if (valid) { + valid = updateProfile(cId, std::move(chargingProfile), false); } + if (!valid) { success = false; - MO_DBG_ERR("profile corrupt: %s, remove", fn); - filesystem->remove(fn); + MO_DBG_ERR("profile corrupt: %s, remove", fname); + filesystem->remove(path); } } } } + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + chargingProfileEntriesInt201->setInt((int)getChargingProfilesCount()); + } + #endif //MO_ENABLE_V201 + return success; } +size_t SmartChargingService::getChargingProfilesCount() { + size_t chargingProfilesCount = 0; + for (size_t i = 0; i < MO_CHARGEPROFILESTACK_SIZE; i++) { + if (chargePointTxDefaultProfile[i]) { + chargingProfilesCount++; + } + if (chargePointMaxProfile[i]) { + chargingProfilesCount++; + } + } + for (unsigned int i = 1; i < numEvseId; i++) { + chargingProfilesCount += evses[i]->getChargingProfilesCount(); + } + + return chargingProfilesCount; +} + /* * limitOut: the calculated maximum charge rate at the moment, or the default value if no limit is defined * validToOut: The begin of the next SmartCharging restriction after time t */ -void SmartChargingService::calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut){ +void SmartChargingService::calculateLimit(int32_t unixTime, MO_ChargeRate& limitOut, int32_t& nextChangeSecsOut){ //initialize output parameters with the default values - limitOut = ChargeRate(); - validToOut = MAX_TIME; + mo_chargeRate_init(&limitOut); + nextChangeSecsOut = 365 * 24 * 3600; //get ChargePointMaxProfile with the highest stackLevel for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) { - if (ChargePointMaxProfile[i]) { - ChargeRate crOut; - bool defined = ChargePointMaxProfile[i]->calculateLimit(t, crOut, validToOut); + if (chargePointMaxProfile[i]) { + MO_ChargeRate crOut; + bool defined = chargePointMaxProfile[i]->calculateLimit(unixTime, -1, crOut, nextChangeSecsOut); if (defined) { limitOut = crOut; break; @@ -496,88 +865,94 @@ void SmartChargingService::calculateLimit(const Timestamp &t, ChargeRate& limitO void SmartChargingService::loop(){ - for (size_t i = 0; i < connectors.size(); i++) { - connectors[i].loop(); + for (unsigned int i = 1; i < numEvseId; i++) { + evses[i]->loop(); } /** * check if to call onLimitChange */ - auto& tnow = context.getModel().getClock().now(); + auto& tnow = clock.now(); + + int32_t dtNextChange; + if (!clock.delta(tnow, nextChange, dtNextChange)) { + dtNextChange = 0; + } - if (tnow >= nextChange){ - - ChargeRate limit; - nextChange = MAX_TIME; //reset nextChange to default value and refresh it + if (dtNextChange >= 0) { - calculateLimit(tnow, limit, nextChange); + int32_t unixTime = -1; + if (!clock.toUnixTime(tnow, unixTime)) { + unixTime = 0; //undefined + } + + MO_ChargeRate limit; + int32_t nextChangeSecs; + + calculateLimit(unixTime, limit, nextChangeSecs); + + //reset nextChange with nextChangeSecs + nextChange = tnow; + clock.add(nextChange, nextChangeSecs); #if MO_DBG_LEVEL >= MO_DL_INFO { - char timestamp1[JSONDATE_LENGTH + 1] = {'\0'}; - tnow.toJsonString(timestamp1, JSONDATE_LENGTH + 1); - char timestamp2[JSONDATE_LENGTH + 1] = {'\0'}; - nextChange.toJsonString(timestamp2, JSONDATE_LENGTH + 1); + char timestamp1[MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(tnow, timestamp1, sizeof(timestamp1))) { + timestamp1[0] = '\0'; + } + char timestamp2[MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toJsonString(nextChange, timestamp2, sizeof(timestamp2))) { + timestamp1[0] = '\0'; + } MO_DBG_INFO("New limit for connector %u, scheduled at = %s, nextChange = %s, limit = {%.1f, %.1f, %i}", 0, timestamp1, timestamp2, - limit.power != std::numeric_limits::max() ? limit.power : -1.f, - limit.current != std::numeric_limits::max() ? limit.current : -1.f, - limit.nphases != std::numeric_limits::max() ? limit.nphases : -1); + limit.power, + limit.current, + limit.numberPhases); } #endif - if (trackLimitOutput != limit) { + if (!mo_chargeRate_equals(&trackLimitOutput, &limit)) { if (limitOutput) { - limitOutput( - limit.power != std::numeric_limits::max() ? limit.power : -1.f, - limit.current != std::numeric_limits::max() ? limit.current : -1.f, - limit.nphases != std::numeric_limits::max() ? limit.nphases : -1); + limitOutput(limit, 0, limitOutputUserData); trackLimitOutput = limit; } } } } -void SmartChargingService::setSmartChargingOutput(unsigned int connectorId, std::function limitOutput) { - if ((connectorId > 0 && !getScConnectorById(connectorId))) { +bool SmartChargingService::setSmartChargingOutput(unsigned int evseId, void (*limitOutput)(MO_ChargeRate limit, unsigned int evseId, void *userData), bool powerSupported, bool currentSupported, bool phases3to1Supported, bool phaseSwitchingSupported, void *userData) { + if (evseId >= numEvseId || (evseId > 0 && !getEvse(evseId))) { MO_DBG_ERR("invalid args"); - return; + return false; } - if (connectorId == 0) { + if (evseId == 0) { if (this->limitOutput) { MO_DBG_WARN("replacing existing SmartChargingOutput"); } this->limitOutput = limitOutput; + this->limitOutputUserData = userData; } else { - getScConnectorById(connectorId)->setSmartChargingOutput(limitOutput); + getEvse(evseId)->setSmartChargingOutput(limitOutput, powerSupported, currentSupported, phases3to1Supported, phaseSwitchingSupported, userData); } -} -void SmartChargingService::updateAllowedChargingRateUnit(bool powerSupported, bool currentSupported) { - if ((powerSupported != this->powerSupported) || (currentSupported != this->currentSupported)) { + this->powerSupported |= powerSupported; + this->currentSupported |= currentSupported; + this->phases3to1Supported |= phases3to1Supported; + #if MO_ENABLE_V201 + this->phaseSwitchingSupported |= phaseSwitchingSupported; + #endif //MO_ENABLE_V201 - auto chargingScheduleAllowedChargingRateUnitString = declareConfiguration("ChargingScheduleAllowedChargingRateUnit", "", CONFIGURATION_VOLATILE); - if (chargingScheduleAllowedChargingRateUnitString) { - if (powerSupported && currentSupported) { - chargingScheduleAllowedChargingRateUnitString->setString("Current,Power"); - } else if (powerSupported) { - chargingScheduleAllowedChargingRateUnitString->setString("Power"); - } else if (currentSupported) { - chargingScheduleAllowedChargingRateUnitString->setString("Current"); - } - } - - this->powerSupported = powerSupported; - this->currentSupported = currentSupported; - } + return true; } -bool SmartChargingService::setChargingProfile(unsigned int connectorId, std::unique_ptr chargingProfile) { +bool SmartChargingService::setChargingProfile(unsigned int evseId, std::unique_ptr chargingProfile) { - if ((connectorId > 0 && !getScConnectorById(connectorId)) || !chargingProfile) { + if (evseId >= numEvseId || (evseId > 0 && !evses[evseId]) || !chargingProfile) { MO_DBG_ERR("invalid args"); return false; } @@ -588,183 +963,229 @@ bool SmartChargingService::setChargingProfile(unsigned int connectorId, std::uni return false; } - int chargingProfileId = chargingProfile->getChargingProfileId(); - clearChargingProfile([chargingProfileId] (int id, int, ChargingProfilePurposeType, int) { - return id == chargingProfileId; - }); - - bool success = false; - - auto profilePtr = updateProfiles(connectorId, std::move(chargingProfile)); - - if (profilePtr) { - success = SmartChargingServiceUtils::storeProfile(filesystem, connectorId, profilePtr); - - if (!success) { - clearChargingProfile([chargingProfileId] (int id, int, ChargingProfilePurposeType, int) { - return id == chargingProfileId; - }); - } - } + int chargingProfileId = chargingProfile->chargingProfileId; + clearChargingProfile(chargingProfileId, -1, ChargingProfilePurposeType::UNDEFINED, -1); - return success; + return updateProfile(evseId, std::move(chargingProfile), true); } -bool SmartChargingService::clearChargingProfile(std::function filter) { +bool SmartChargingService::clearChargingProfile(int chargingProfileId, int evseId, ChargingProfilePurposeType chargingProfilePurpose, int stackLevel) { bool found = false; - for (size_t cId = 0; cId < connectors.size(); cId++) { - found |= connectors[cId].clearChargingProfile(filter); - } + // Enumerate all profiles, check for filter criteria and delete - ProfileStack *profileStacks [] = {&ChargePointMaxProfile, &ChargePointTxDefaultProfile}; + for (unsigned int cId = 0; cId < numEvseId; cId++) { + if (evseId >= 0 && (unsigned int)evseId != cId) { + continue; + } - for (auto stack : profileStacks) { - for (size_t iLevel = 0; iLevel < stack->size(); iLevel++) { - if (auto& profile = stack->at(iLevel)) { - if (filter(profile->getChargingProfileId(), 0, profile->getChargingProfilePurpose(), iLevel)) { + if (cId > 0) { + found |= evses[cId]->clearChargingProfile(chargingProfileId, chargingProfilePurpose, stackLevel); + } else { + ChargingProfilePurposeType purposes[] = {ChargingProfilePurposeType::ChargePointMaxProfile, ChargingProfilePurposeType::TxDefaultProfile}; + for (unsigned int p = 0; p < sizeof(purposes) / sizeof(purposes[0]); p++) { + ChargingProfilePurposeType purpose = purposes[p]; + if (chargingProfilePurpose != ChargingProfilePurposeType::UNDEFINED && chargingProfilePurpose != purpose) { + continue; + } + + ChargingProfile **stack = nullptr; + if (purpose == ChargingProfilePurposeType::ChargePointMaxProfile) { + stack = chargePointMaxProfile; + } else if (purpose == ChargingProfilePurposeType::TxDefaultProfile) { + stack = chargePointTxDefaultProfile; + } + + for (size_t sLvl = 0; sLvl < MO_CHARGEPROFILESTACK_SIZE; sLvl++) { + if (stackLevel >= 0 && (size_t)stackLevel != sLvl) { + continue; + } + + if (!stack[sLvl]) { + // no profile installed at this stack and stackLevel + continue; + } + + if (chargingProfileId >= 0 && chargingProfileId != stack[sLvl]->chargingProfileId) { + continue; + } + + // this profile matches all filter criteria + if (filesystem) { + SmartChargingServiceUtils::removeProfile(filesystem, cId, purpose, sLvl); + } + delete stack[sLvl]; + stack[sLvl] = nullptr; found = true; - SmartChargingServiceUtils::removeProfile(filesystem, 0, profile->getChargingProfilePurpose(), iLevel); - profile.reset(); } } } } + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + chargingProfileEntriesInt201->setInt((int)getChargingProfilesCount()); + } + #endif //MO_ENABLE_V201 + /** * Invalidate the last limit by setting the nextChange to now. By the next loop()-call, the limit * and nextChange will be recalculated and onLimitChanged will be called. */ - nextChange = context.getModel().getClock().now(); - for (size_t i = 0; i < connectors.size(); i++) { - connectors[i].notifyProfilesUpdated(); + nextChange = clock.now(); + for (unsigned int i = 1; i < numEvseId; i++) { + evses[i]->notifyProfilesUpdated(); } return found; } -std::unique_ptr SmartChargingService::getCompositeSchedule(unsigned int connectorId, int duration, ChargingRateUnitType_Optional unit) { +std::unique_ptr SmartChargingService::getCompositeSchedule(unsigned int evseId, int duration, ChargingRateUnitType unit) { - if (connectorId > 0 && !getScConnectorById(connectorId)) { + if (evseId >= numEvseId || (evseId > 0 && !evses[evseId])) { MO_DBG_ERR("invalid args"); return nullptr; } - - if (unit == ChargingRateUnitType_Optional::None) { + + if (unit == ChargingRateUnitType::UNDEFINED) { if (powerSupported && !currentSupported) { - unit = ChargingRateUnitType_Optional::Watt; + unit = ChargingRateUnitType::Watt; } else if (!powerSupported && currentSupported) { - unit = ChargingRateUnitType_Optional::Amp; + unit = ChargingRateUnitType::Amp; } } - if (connectorId > 0) { - return getScConnectorById(connectorId)->getCompositeSchedule(duration, unit); + if (evseId > 0) { + return evses[evseId]->getCompositeSchedule(duration, unit); + } + + int32_t nowUnixTime; + if (!clock.toUnixTime(clock.now(), nowUnixTime)) { + MO_DBG_ERR("internal error"); + return nullptr; + } + + // dry run to measure Schedule size + size_t periodsSize = 0; + + int32_t periodBegin = nowUnixTime; + int32_t measuredDuration = 0; + while (measuredDuration < duration && periodsSize < MO_ChargingScheduleMaxPeriods) { + MO_ChargeRate limit; + int32_t nextChangeSecs; + + calculateLimit(periodBegin, limit, nextChangeSecs); + + periodBegin += nextChangeSecs; + measuredDuration += nextChangeSecs; + periodsSize++; } - - auto& startSchedule = context.getModel().getClock().now(); auto schedule = std::unique_ptr(new ChargingSchedule()); + if (!schedule || !schedule->resizeChargingSchedulePeriod(periodsSize)) { + MO_DBG_ERR("OOM"); + return nullptr; + } schedule->duration = duration; - schedule->startSchedule = startSchedule; + schedule->startSchedule = nowUnixTime; schedule->chargingProfileKind = ChargingProfileKindType::Absolute; - schedule->recurrencyKind = RecurrencyKindType::NOT_SET; - - auto& periods = schedule->chargingSchedulePeriod; + schedule->recurrencyKind = RecurrencyKindType::UNDEFINED; - Timestamp periodBegin = Timestamp(startSchedule); - Timestamp periodStop = Timestamp(startSchedule); + periodBegin = nowUnixTime; + measuredDuration = 0; - while (periodBegin - startSchedule < duration && periods.size() < MO_ChargingScheduleMaxPeriods) { + for (size_t i = 0; measuredDuration < duration && i < periodsSize; i++) { //calculate limit - ChargeRate limit; - calculateLimit(periodBegin, limit, periodStop); + MO_ChargeRate limit; + int32_t nextChangeSecs; + + calculateLimit(periodBegin, limit, nextChangeSecs); //if the unit is still unspecified, guess by taking the unit of the first limit - if (unit == ChargingRateUnitType_Optional::None) { - if (limit.power < limit.current) { - unit = ChargingRateUnitType_Optional::Watt; + if (unit == ChargingRateUnitType::UNDEFINED) { + if (limit.power > limit.current) { + unit = ChargingRateUnitType::Watt; } else { - unit = ChargingRateUnitType_Optional::Amp; + unit = ChargingRateUnitType::Amp; } } - periods.push_back(ChargingSchedulePeriod()); - float limit_opt = unit == ChargingRateUnitType_Optional::Watt ? limit.power : limit.current; - periods.back().limit = limit_opt != std::numeric_limits::max() ? limit_opt : -1.f; - periods.back().numberPhases = limit.nphases != std::numeric_limits::max() ? limit.nphases : -1; - periods.back().startPeriod = periodBegin - startSchedule; + float limit_opt = (unit == ChargingRateUnitType::Watt) ? limit.power : limit.current; + schedule->chargingSchedulePeriod[i]->limit = (unit == ChargingRateUnitType::Watt) ? limit.power : limit.current; + schedule->chargingSchedulePeriod[i]->numberPhases = limit.numberPhases; + schedule->chargingSchedulePeriod[i]->startPeriod = measuredDuration; + #if MO_ENABLE_V201 + schedule->chargingSchedulePeriod[i]->phaseToUse = limit.phaseToUse; + #endif //MO_ENABLE_V201 - periodBegin = periodStop; + periodBegin += nextChangeSecs; + measuredDuration += nextChangeSecs; } - if (unit == ChargingRateUnitType_Optional::Watt) { - schedule->chargingRateUnit = ChargingRateUnitType::Watt; - } else { - schedule->chargingRateUnit = ChargingRateUnitType::Amp; - } + schedule->chargingRateUnit = unit; return schedule; } -bool SmartChargingServiceUtils::printProfileFileName(char *out, size_t bufsize, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel) { - int pret = 0; +bool SmartChargingServiceUtils::printProfileFileName(char *out, size_t bufsize, unsigned int evseId, ChargingProfilePurposeType purpose, unsigned int stackLevel) { + int ret = 0; switch (purpose) { - case (ChargingProfilePurposeType::ChargePointMaxProfile): - pret = snprintf(out, bufsize, MO_FILENAME_PREFIX "sc-cm-%u.jsn", stackLevel); + case ChargingProfilePurposeType::ChargePointMaxProfile: + ret = snprintf(out, bufsize, "sc-cm-%.*u-%.*u.jsn", MO_NUM_EVSEID_DIGITS, 0, MO_ChargeProfileMaxStackLevel_digits, stackLevel); break; - case (ChargingProfilePurposeType::TxDefaultProfile): - pret = snprintf(out, bufsize, MO_FILENAME_PREFIX "sc-td-%u-%u.jsn", connectorId, stackLevel); + case ChargingProfilePurposeType::TxDefaultProfile: + ret = snprintf(out, bufsize, "sc-td-%.*u-%.*u.jsn", MO_NUM_EVSEID_DIGITS, evseId, MO_ChargeProfileMaxStackLevel_digits, stackLevel); break; - case (ChargingProfilePurposeType::TxProfile): - pret = snprintf(out, bufsize, MO_FILENAME_PREFIX "sc-tx-%u-%u.jsn", connectorId, stackLevel); + case ChargingProfilePurposeType::TxProfile: + ret = snprintf(out, bufsize, "sc-tx-%.*u-%.*u.jsn", MO_NUM_EVSEID_DIGITS, evseId, MO_ChargeProfileMaxStackLevel_digits, stackLevel); break; } - if (pret < 0 || (size_t) pret >= bufsize) { - MO_DBG_ERR("fn error: %i", pret); + if (ret < 0 || (size_t) ret >= bufsize) { + MO_DBG_ERR("fname error: %i", ret); return false; } return true; } -bool SmartChargingServiceUtils::storeProfile(std::shared_ptr filesystem, unsigned int connectorId, ChargingProfile *chargingProfile) { - - if (!filesystem) { - MO_DBG_DEBUG("no filesystem"); - return true; //not an error - } +bool SmartChargingServiceUtils::storeProfile(MO_FilesystemAdapter *filesystem, Clock& clock, int ocppVersion, unsigned int evseId, ChargingProfile *chargingProfile) { - auto chargingProfileJson = initJsonDoc("v16.SmartCharging.ChargingProfile"); - if (!chargingProfile->toJson(chargingProfileJson)) { + auto capacity = chargingProfile->getJsonCapacity(ocppVersion); + auto chargingProfileJson = initJsonDoc("v16.SmartCharging.ChargingProfile", capacity); + if (!chargingProfile->toJson(clock, ocppVersion, chargingProfileJson.as())) { return false; } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; + char fname [MO_MAX_PATH_SIZE] = {'\0'}; - if (!printProfileFileName(fn, MO_MAX_PATH_SIZE, connectorId, chargingProfile->getChargingProfilePurpose(), chargingProfile->getStackLevel())) { + if (!printProfileFileName(fname, MO_MAX_PATH_SIZE, evseId, chargingProfile->chargingProfilePurpose, chargingProfile->stackLevel)) { return false; } - return FilesystemUtils::storeJson(filesystem, fn, chargingProfileJson); + if (FilesystemUtils::storeJson(filesystem, fname, chargingProfileJson) != FilesystemUtils::StoreStatus::Success) { + MO_DBG_ERR("failed to store %s", fname); + } + + return true; } -bool SmartChargingServiceUtils::removeProfile(std::shared_ptr filesystem, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel) { +bool SmartChargingServiceUtils::removeProfile(MO_FilesystemAdapter *filesystem, unsigned int evseId, ChargingProfilePurposeType purpose, unsigned int stackLevel) { - if (!filesystem) { + char fname [MO_MAX_PATH_SIZE] = {'\0'}; + + if (!printProfileFileName(fname, MO_MAX_PATH_SIZE, evseId, purpose, stackLevel)) { return false; } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - - if (!printProfileFileName(fn, MO_MAX_PATH_SIZE, connectorId, purpose, stackLevel)) { + char path [MO_MAX_PATH_SIZE]; + if (!FilesystemUtils::printPath(filesystem, path, sizeof(path), fname)) { return false; } - return filesystem->remove(fn); + return filesystem->remove(path); } - +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h index 995cf95e..44ed7ccc 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h @@ -2,73 +2,78 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#ifndef SMARTCHARGINGSERVICE_H -#define SMARTCHARGINGSERVICE_H - -#include -#include - -#include +#ifndef MO_SMARTCHARGINGSERVICE_H +#define MO_SMARTCHARGINGSERVICE_H #include +#include +#include #include #include #include +#include -namespace MicroOcpp { +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING -enum class ChargingRateUnitType_Optional { - Watt, - Amp, - None -}; +#define MO_CHARGEPROFILESTACK_SIZE (MO_ChargeProfileMaxStackLevel + 1) + +namespace MicroOcpp { class Context; -class Model; -using ProfileStack = std::array, MO_ChargeProfileMaxStackLevel + 1>; +class SmartChargingService; + +#if MO_ENABLE_V201 +namespace Ocpp201 { +class Variable; +} +#endif //MO_ENABLE_V201 class SmartChargingServiceEvse : public MemoryManaged { private: - Model& model; - std::shared_ptr filesystem; - const unsigned int connectorId; + Context& context; + Clock& clock; + SmartChargingService& scService; + const unsigned int evseId; - ProfileStack& ChargePointMaxProfile; - ProfileStack& ChargePointTxDefaultProfile; - ProfileStack TxDefaultProfile; - ProfileStack TxProfile; + ChargingProfile *txDefaultProfile [MO_CHARGEPROFILESTACK_SIZE] = {nullptr}; + ChargingProfile *txProfile [MO_CHARGEPROFILESTACK_SIZE] = {nullptr}; - std::function limitOutput; + void (*limitOutput)(MO_ChargeRate limit, unsigned int evseId, void *userData) = nullptr; + void *limitOutputUserData = nullptr; + MO_ChargeRate trackLimitOutput; int trackTxRmtProfileId = -1; //optional Charging Profile ID when tx is started via RemoteStartTx - Timestamp trackTxStart = MAX_TIME; //time basis for relative profiles - int trackTxId = -1; //transactionId assigned by OCPP server + Timestamp trackTxStart; //time basis for relative profiles + #if MO_ENABLE_V16 + int trackTransactionId16 = -1; //transactionId + #endif + #if MO_ENABLE_V201 + char trackTransactionId201[MO_TXID_SIZE] = {'\0'}; //transactionId + #endif //MO_ENABLE_V201 - Timestamp nextChange = MIN_TIME; + Timestamp nextChange; - ChargeRate trackLimitOutput; - - void calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut); + void calculateLimit(int32_t unixTime, int32_t sessionDurationSecs, MO_ChargeRate& limit, int32_t& nextChangeSecs); void trackTransaction(); public: - SmartChargingServiceEvse(Model& model, std::shared_ptr filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile); + SmartChargingServiceEvse(Context& context, SmartChargingService& scService, unsigned int evseId); SmartChargingServiceEvse(SmartChargingServiceEvse&&) = default; ~SmartChargingServiceEvse(); void loop(); - void setSmartChargingOutput(std::function limitOutput); //read maximum Watt x Amps x numberPhases + void setSmartChargingOutput(void (*limitOutput)(MO_ChargeRate limit, unsigned int evseId, void *userData), bool powerSupported, bool currentSupported, bool phases3to1Supported, bool phaseSwitchingSupported, void *userData); - ChargingProfile *updateProfiles(std::unique_ptr chargingProfile); + bool updateProfile(std::unique_ptr chargingProfile, bool updateFile); void notifyProfilesUpdated(); - bool clearChargingProfile(std::function filter); + bool clearChargingProfile(int chargingProfileId, ChargingProfilePurposeType chargingProfilePurpose, int stackLevel); - std::unique_ptr getCompositeSchedule(int duration, ChargingRateUnitType_Optional unit); + std::unique_ptr getCompositeSchedule(int duration, ChargingRateUnitType unit); size_t getChargingProfilesCount(); }; @@ -76,49 +81,59 @@ class SmartChargingServiceEvse : public MemoryManaged { class SmartChargingService : public MemoryManaged { private: Context& context; - std::shared_ptr filesystem; - Vector connectors; //connectorId 0 excluded - SmartChargingServiceEvse *getScConnectorById(unsigned int connectorId); - unsigned int numConnectors; //connectorId 0 included - - ProfileStack ChargePointMaxProfile; - ProfileStack ChargePointTxDefaultProfile; - - std::function limitOutput; - ChargeRate trackLimitOutput; + Clock& clock; + MO_FilesystemAdapter *filesystem = nullptr; + SmartChargingServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; //evseId 0 won't be populated + unsigned int numEvseId = MO_NUM_EVSEID; + + ChargingProfile *chargePointMaxProfile [MO_CHARGEPROFILESTACK_SIZE] = {nullptr}; + ChargingProfile *chargePointTxDefaultProfile [MO_CHARGEPROFILESTACK_SIZE] = {nullptr}; + + void (*limitOutput)(MO_ChargeRate limit, unsigned int evseId, void *userData) = nullptr; + void *limitOutputUserData = nullptr; bool powerSupported = false; bool currentSupported = false; + bool phases3to1Supported = false; + #if MO_ENABLE_V201 + bool phaseSwitchingSupported = false; + #endif + MO_ChargeRate trackLimitOutput; + + Timestamp nextChange; + + int ocppVersion = -1; - Timestamp nextChange = MIN_TIME; + #if MO_ENABLE_V201 + Ocpp201::Variable *chargingProfileEntriesInt201 = nullptr; + #endif //MO_ENABLE_V201 - ChargingProfile *updateProfiles(unsigned int connectorId, std::unique_ptr chargingProfile); + SmartChargingServiceEvse *getEvse(unsigned int evseId); + bool updateProfile(unsigned int evseId, std::unique_ptr chargingProfile, bool updateFile); bool loadProfiles(); - void calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut); - + size_t getChargingProfilesCount(); + + void calculateLimit(int32_t unixTime, MO_ChargeRate& limit, int32_t& nextChangeSecs); + public: - SmartChargingService(Context& context, std::shared_ptr filesystem, unsigned int numConnectors); + SmartChargingService(Context& context); ~SmartChargingService(); - void loop(); + bool setSmartChargingOutput(unsigned int evseId, void (*limitOutput)(MO_ChargeRate limit, unsigned int evseId, void *userData), bool powerSupported, bool currentSupported, bool phases3to1Supported, bool phaseSwitchingSupported, void *userData); - void setSmartChargingOutput(unsigned int connectorId, std::function limitOutput); //read maximum Watt x Amps x numberPhases - void updateAllowedChargingRateUnit(bool powerSupported, bool currentSupported); //set supported measurand of SmartChargingOutput + bool setup(); - bool setChargingProfile(unsigned int connectorId, std::unique_ptr chargingProfile); + void loop(); - bool clearChargingProfile(std::function filter); + bool setChargingProfile(unsigned int evseId, std::unique_ptr chargingProfile); - std::unique_ptr getCompositeSchedule(unsigned int connectorId, int duration, ChargingRateUnitType_Optional unit = ChargingRateUnitType_Optional::None); -}; + bool clearChargingProfile(int chargingProfileId, int evseId, ChargingProfilePurposeType chargingProfilePurpose, int stackLevel); -//filesystem-related helper functions -namespace SmartChargingServiceUtils { -bool printProfileFileName(char *out, size_t bufsize, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel); -bool storeProfile(std::shared_ptr filesystem, unsigned int connectorId, ChargingProfile *chargingProfile); -bool removeProfile(std::shared_ptr filesystem, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel); -} + std::unique_ptr getCompositeSchedule(unsigned int evseId, int duration, ChargingRateUnitType unit = ChargingRateUnitType::UNDEFINED); -} //end namespace MicroOcpp +friend class SmartChargingServiceEvse; +}; +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING #endif diff --git a/src/MicroOcpp/Model/Transactions/Transaction.cpp b/src/MicroOcpp/Model/Transactions/Transaction.cpp index a53b68f1..6ba568d0 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.cpp +++ b/src/MicroOcpp/Model/Transactions/Transaction.cpp @@ -6,145 +6,159 @@ #include #include +#if MO_ENABLE_V16 + using namespace MicroOcpp; -bool Transaction::setIdTag(const char *idTag) { +Ocpp16::Transaction::Transaction(unsigned int connectorId, unsigned int txNr, bool silent) : + MemoryManaged("v16.Transactions.Transaction"), + connectorId(connectorId), + txNr(txNr), + silent(silent), + meterValues(makeVector("v16.Transactions.TransactionMeterData")) { } + +Ocpp16::Transaction::~Transaction() { + for (size_t i = 0; i < meterValues.size(); i++) { + delete meterValues[i]; + } + meterValues.clear(); +} + +bool Ocpp16::Transaction::setIdTag(const char *idTag) { auto ret = snprintf(this->idTag, IDTAG_LEN_MAX + 1, "%s", idTag); return ret >= 0 && ret < IDTAG_LEN_MAX + 1; } -bool Transaction::setParentIdTag(const char *idTag) { +bool Ocpp16::Transaction::setParentIdTag(const char *idTag) { auto ret = snprintf(this->parentIdTag, IDTAG_LEN_MAX + 1, "%s", idTag); return ret >= 0 && ret < IDTAG_LEN_MAX + 1; } -bool Transaction::setStopIdTag(const char *idTag) { +bool Ocpp16::Transaction::setStopIdTag(const char *idTag) { auto ret = snprintf(stop_idTag, IDTAG_LEN_MAX + 1, "%s", idTag); return ret >= 0 && ret < IDTAG_LEN_MAX + 1; } -bool Transaction::setStopReason(const char *reason) { +bool Ocpp16::Transaction::setStopReason(const char *reason) { auto ret = snprintf(stop_reason, REASON_LEN_MAX + 1, "%s", reason); return ret >= 0 && ret < REASON_LEN_MAX + 1; } -bool Transaction::commit() { - return context.commit(this); -} +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 namespace MicroOcpp { namespace Ocpp201 { -const char *serializeTransactionStoppedReason(Transaction::StoppedReason stoppedReason) { +const char *serializeTransactionStoppedReason(MO_TxStoppedReason stoppedReason) { const char *stoppedReasonCstr = nullptr; switch (stoppedReason) { - case Transaction::StoppedReason::UNDEFINED: + case MO_TxStoppedReason_UNDEFINED: // optional, okay break; - case Transaction::StoppedReason::Local: + case MO_TxStoppedReason_Local: stoppedReasonCstr = "Local"; break; - case Transaction::StoppedReason::DeAuthorized: + case MO_TxStoppedReason_DeAuthorized: stoppedReasonCstr = "DeAuthorized"; break; - case Transaction::StoppedReason::EmergencyStop: + case MO_TxStoppedReason_EmergencyStop: stoppedReasonCstr = "EmergencyStop"; break; - case Transaction::StoppedReason::EnergyLimitReached: + case MO_TxStoppedReason_EnergyLimitReached: stoppedReasonCstr = "EnergyLimitReached"; break; - case Transaction::StoppedReason::EVDisconnected: + case MO_TxStoppedReason_EVDisconnected: stoppedReasonCstr = "EVDisconnected"; break; - case Transaction::StoppedReason::GroundFault: + case MO_TxStoppedReason_GroundFault: stoppedReasonCstr = "GroundFault"; break; - case Transaction::StoppedReason::ImmediateReset: + case MO_TxStoppedReason_ImmediateReset: stoppedReasonCstr = "ImmediateReset"; break; - case Transaction::StoppedReason::LocalOutOfCredit: + case MO_TxStoppedReason_LocalOutOfCredit: stoppedReasonCstr = "LocalOutOfCredit"; break; - case Transaction::StoppedReason::MasterPass: + case MO_TxStoppedReason_MasterPass: stoppedReasonCstr = "MasterPass"; break; - case Transaction::StoppedReason::Other: + case MO_TxStoppedReason_Other: stoppedReasonCstr = "Other"; break; - case Transaction::StoppedReason::OvercurrentFault: + case MO_TxStoppedReason_OvercurrentFault: stoppedReasonCstr = "OvercurrentFault"; break; - case Transaction::StoppedReason::PowerLoss: + case MO_TxStoppedReason_PowerLoss: stoppedReasonCstr = "PowerLoss"; break; - case Transaction::StoppedReason::PowerQuality: + case MO_TxStoppedReason_PowerQuality: stoppedReasonCstr = "PowerQuality"; break; - case Transaction::StoppedReason::Reboot: + case MO_TxStoppedReason_Reboot: stoppedReasonCstr = "Reboot"; break; - case Transaction::StoppedReason::Remote: + case MO_TxStoppedReason_Remote: stoppedReasonCstr = "Remote"; break; - case Transaction::StoppedReason::SOCLimitReached: + case MO_TxStoppedReason_SOCLimitReached: stoppedReasonCstr = "SOCLimitReached"; break; - case Transaction::StoppedReason::StoppedByEV: + case MO_TxStoppedReason_StoppedByEV: stoppedReasonCstr = "StoppedByEV"; break; - case Transaction::StoppedReason::TimeLimitReached: + case MO_TxStoppedReason_TimeLimitReached: stoppedReasonCstr = "TimeLimitReached"; break; - case Transaction::StoppedReason::Timeout: + case MO_TxStoppedReason_Timeout: stoppedReasonCstr = "Timeout"; break; } return stoppedReasonCstr; } -bool deserializeTransactionStoppedReason(const char *stoppedReasonCstr, Transaction::StoppedReason& stoppedReasonOut) { +bool deserializeTransactionStoppedReason(const char *stoppedReasonCstr, MO_TxStoppedReason& stoppedReasonOut) { if (!stoppedReasonCstr || !*stoppedReasonCstr) { - stoppedReasonOut = Transaction::StoppedReason::UNDEFINED; + stoppedReasonOut = MO_TxStoppedReason_UNDEFINED; } else if (!strcmp(stoppedReasonCstr, "DeAuthorized")) { - stoppedReasonOut = Transaction::StoppedReason::DeAuthorized; + stoppedReasonOut = MO_TxStoppedReason_DeAuthorized; } else if (!strcmp(stoppedReasonCstr, "EmergencyStop")) { - stoppedReasonOut = Transaction::StoppedReason::EmergencyStop; + stoppedReasonOut = MO_TxStoppedReason_EmergencyStop; } else if (!strcmp(stoppedReasonCstr, "EnergyLimitReached")) { - stoppedReasonOut = Transaction::StoppedReason::EnergyLimitReached; + stoppedReasonOut = MO_TxStoppedReason_EnergyLimitReached; } else if (!strcmp(stoppedReasonCstr, "EVDisconnected")) { - stoppedReasonOut = Transaction::StoppedReason::EVDisconnected; + stoppedReasonOut = MO_TxStoppedReason_EVDisconnected; } else if (!strcmp(stoppedReasonCstr, "GroundFault")) { - stoppedReasonOut = Transaction::StoppedReason::GroundFault; + stoppedReasonOut = MO_TxStoppedReason_GroundFault; } else if (!strcmp(stoppedReasonCstr, "ImmediateReset")) { - stoppedReasonOut = Transaction::StoppedReason::ImmediateReset; + stoppedReasonOut = MO_TxStoppedReason_ImmediateReset; } else if (!strcmp(stoppedReasonCstr, "Local")) { - stoppedReasonOut = Transaction::StoppedReason::Local; + stoppedReasonOut = MO_TxStoppedReason_Local; } else if (!strcmp(stoppedReasonCstr, "LocalOutOfCredit")) { - stoppedReasonOut = Transaction::StoppedReason::LocalOutOfCredit; + stoppedReasonOut = MO_TxStoppedReason_LocalOutOfCredit; } else if (!strcmp(stoppedReasonCstr, "MasterPass")) { - stoppedReasonOut = Transaction::StoppedReason::MasterPass; + stoppedReasonOut = MO_TxStoppedReason_MasterPass; } else if (!strcmp(stoppedReasonCstr, "Other")) { - stoppedReasonOut = Transaction::StoppedReason::Other; + stoppedReasonOut = MO_TxStoppedReason_Other; } else if (!strcmp(stoppedReasonCstr, "OvercurrentFault")) { - stoppedReasonOut = Transaction::StoppedReason::OvercurrentFault; + stoppedReasonOut = MO_TxStoppedReason_OvercurrentFault; } else if (!strcmp(stoppedReasonCstr, "PowerLoss")) { - stoppedReasonOut = Transaction::StoppedReason::PowerLoss; + stoppedReasonOut = MO_TxStoppedReason_PowerLoss; } else if (!strcmp(stoppedReasonCstr, "PowerQuality")) { - stoppedReasonOut = Transaction::StoppedReason::PowerQuality; + stoppedReasonOut = MO_TxStoppedReason_PowerQuality; } else if (!strcmp(stoppedReasonCstr, "Reboot")) { - stoppedReasonOut = Transaction::StoppedReason::Reboot; + stoppedReasonOut = MO_TxStoppedReason_Reboot; } else if (!strcmp(stoppedReasonCstr, "Remote")) { - stoppedReasonOut = Transaction::StoppedReason::Remote; + stoppedReasonOut = MO_TxStoppedReason_Remote; } else if (!strcmp(stoppedReasonCstr, "SOCLimitReached")) { - stoppedReasonOut = Transaction::StoppedReason::SOCLimitReached; + stoppedReasonOut = MO_TxStoppedReason_SOCLimitReached; } else if (!strcmp(stoppedReasonCstr, "StoppedByEV")) { - stoppedReasonOut = Transaction::StoppedReason::StoppedByEV; + stoppedReasonOut = MO_TxStoppedReason_StoppedByEV; } else if (!strcmp(stoppedReasonCstr, "TimeLimitReached")) { - stoppedReasonOut = Transaction::StoppedReason::TimeLimitReached; + stoppedReasonOut = MO_TxStoppedReason_TimeLimitReached; } else if (!strcmp(stoppedReasonCstr, "Timeout")) { - stoppedReasonOut = Transaction::StoppedReason::Timeout; + stoppedReasonOut = MO_TxStoppedReason_Timeout; } else { MO_DBG_ERR("deserialization error"); return false; @@ -181,124 +195,124 @@ bool deserializeTransactionEventType(const char *typeCstr, TransactionEventData: return true; } -const char *serializeTransactionEventTriggerReason(TransactionEventTriggerReason triggerReason) { +const char *serializeTxEventTriggerReason(MO_TxEventTriggerReason triggerReason) { const char *triggerReasonCstr = nullptr; switch(triggerReason) { - case TransactionEventTriggerReason::UNDEFINED: + case MO_TxEventTriggerReason_UNDEFINED: break; - case TransactionEventTriggerReason::Authorized: + case MO_TxEventTriggerReason_Authorized: triggerReasonCstr = "Authorized"; break; - case TransactionEventTriggerReason::CablePluggedIn: + case MO_TxEventTriggerReason_CablePluggedIn: triggerReasonCstr = "CablePluggedIn"; break; - case TransactionEventTriggerReason::ChargingRateChanged: + case MO_TxEventTriggerReason_ChargingRateChanged: triggerReasonCstr = "ChargingRateChanged"; break; - case TransactionEventTriggerReason::ChargingStateChanged: + case MO_TxEventTriggerReason_ChargingStateChanged: triggerReasonCstr = "ChargingStateChanged"; break; - case TransactionEventTriggerReason::Deauthorized: + case MO_TxEventTriggerReason_Deauthorized: triggerReasonCstr = "Deauthorized"; break; - case TransactionEventTriggerReason::EnergyLimitReached: + case MO_TxEventTriggerReason_EnergyLimitReached: triggerReasonCstr = "EnergyLimitReached"; break; - case TransactionEventTriggerReason::EVCommunicationLost: + case MO_TxEventTriggerReason_EVCommunicationLost: triggerReasonCstr = "EVCommunicationLost"; break; - case TransactionEventTriggerReason::EVConnectTimeout: + case MO_TxEventTriggerReason_EVConnectTimeout: triggerReasonCstr = "EVConnectTimeout"; break; - case TransactionEventTriggerReason::MeterValueClock: + case MO_TxEventTriggerReason_MeterValueClock: triggerReasonCstr = "MeterValueClock"; break; - case TransactionEventTriggerReason::MeterValuePeriodic: + case MO_TxEventTriggerReason_MeterValuePeriodic: triggerReasonCstr = "MeterValuePeriodic"; break; - case TransactionEventTriggerReason::TimeLimitReached: + case MO_TxEventTriggerReason_TimeLimitReached: triggerReasonCstr = "TimeLimitReached"; break; - case TransactionEventTriggerReason::Trigger: + case MO_TxEventTriggerReason_Trigger: triggerReasonCstr = "Trigger"; break; - case TransactionEventTriggerReason::UnlockCommand: + case MO_TxEventTriggerReason_UnlockCommand: triggerReasonCstr = "UnlockCommand"; break; - case TransactionEventTriggerReason::StopAuthorized: + case MO_TxEventTriggerReason_StopAuthorized: triggerReasonCstr = "StopAuthorized"; break; - case TransactionEventTriggerReason::EVDeparted: + case MO_TxEventTriggerReason_EVDeparted: triggerReasonCstr = "EVDeparted"; break; - case TransactionEventTriggerReason::EVDetected: + case MO_TxEventTriggerReason_EVDetected: triggerReasonCstr = "EVDetected"; break; - case TransactionEventTriggerReason::RemoteStop: + case MO_TxEventTriggerReason_RemoteStop: triggerReasonCstr = "RemoteStop"; break; - case TransactionEventTriggerReason::RemoteStart: + case MO_TxEventTriggerReason_RemoteStart: triggerReasonCstr = "RemoteStart"; break; - case TransactionEventTriggerReason::AbnormalCondition: + case MO_TxEventTriggerReason_AbnormalCondition: triggerReasonCstr = "AbnormalCondition"; break; - case TransactionEventTriggerReason::SignedDataReceived: + case MO_TxEventTriggerReason_SignedDataReceived: triggerReasonCstr = "SignedDataReceived"; break; - case TransactionEventTriggerReason::ResetCommand: + case MO_TxEventTriggerReason_ResetCommand: triggerReasonCstr = "ResetCommand"; break; } return triggerReasonCstr; } -bool deserializeTransactionEventTriggerReason(const char *triggerReasonCstr, TransactionEventTriggerReason& triggerReasonOut) { +bool deserializeTxEventTriggerReason(const char *triggerReasonCstr, MO_TxEventTriggerReason& triggerReasonOut) { if (!triggerReasonCstr || !*triggerReasonCstr) { - triggerReasonOut = TransactionEventTriggerReason::UNDEFINED; + triggerReasonOut = MO_TxEventTriggerReason_UNDEFINED; } else if (!strcmp(triggerReasonCstr, "Authorized")) { - triggerReasonOut = TransactionEventTriggerReason::Authorized; + triggerReasonOut = MO_TxEventTriggerReason_Authorized; } else if (!strcmp(triggerReasonCstr, "CablePluggedIn")) { - triggerReasonOut = TransactionEventTriggerReason::CablePluggedIn; + triggerReasonOut = MO_TxEventTriggerReason_CablePluggedIn; } else if (!strcmp(triggerReasonCstr, "ChargingRateChanged")) { - triggerReasonOut = TransactionEventTriggerReason::ChargingRateChanged; + triggerReasonOut = MO_TxEventTriggerReason_ChargingRateChanged; } else if (!strcmp(triggerReasonCstr, "ChargingStateChanged")) { - triggerReasonOut = TransactionEventTriggerReason::ChargingStateChanged; + triggerReasonOut = MO_TxEventTriggerReason_ChargingStateChanged; } else if (!strcmp(triggerReasonCstr, "Deauthorized")) { - triggerReasonOut = TransactionEventTriggerReason::Deauthorized; + triggerReasonOut = MO_TxEventTriggerReason_Deauthorized; } else if (!strcmp(triggerReasonCstr, "EnergyLimitReached")) { - triggerReasonOut = TransactionEventTriggerReason::EnergyLimitReached; + triggerReasonOut = MO_TxEventTriggerReason_EnergyLimitReached; } else if (!strcmp(triggerReasonCstr, "EVCommunicationLost")) { - triggerReasonOut = TransactionEventTriggerReason::EVCommunicationLost; + triggerReasonOut = MO_TxEventTriggerReason_EVCommunicationLost; } else if (!strcmp(triggerReasonCstr, "EVConnectTimeout")) { - triggerReasonOut = TransactionEventTriggerReason::EVConnectTimeout; + triggerReasonOut = MO_TxEventTriggerReason_EVConnectTimeout; } else if (!strcmp(triggerReasonCstr, "MeterValueClock")) { - triggerReasonOut = TransactionEventTriggerReason::MeterValueClock; + triggerReasonOut = MO_TxEventTriggerReason_MeterValueClock; } else if (!strcmp(triggerReasonCstr, "MeterValuePeriodic")) { - triggerReasonOut = TransactionEventTriggerReason::MeterValuePeriodic; + triggerReasonOut = MO_TxEventTriggerReason_MeterValuePeriodic; } else if (!strcmp(triggerReasonCstr, "TimeLimitReached")) { - triggerReasonOut = TransactionEventTriggerReason::TimeLimitReached; + triggerReasonOut = MO_TxEventTriggerReason_TimeLimitReached; } else if (!strcmp(triggerReasonCstr, "Trigger")) { - triggerReasonOut = TransactionEventTriggerReason::Trigger; + triggerReasonOut = MO_TxEventTriggerReason_Trigger; } else if (!strcmp(triggerReasonCstr, "UnlockCommand")) { - triggerReasonOut = TransactionEventTriggerReason::UnlockCommand; + triggerReasonOut = MO_TxEventTriggerReason_UnlockCommand; } else if (!strcmp(triggerReasonCstr, "StopAuthorized")) { - triggerReasonOut = TransactionEventTriggerReason::StopAuthorized; + triggerReasonOut = MO_TxEventTriggerReason_StopAuthorized; } else if (!strcmp(triggerReasonCstr, "EVDeparted")) { - triggerReasonOut = TransactionEventTriggerReason::EVDeparted; + triggerReasonOut = MO_TxEventTriggerReason_EVDeparted; } else if (!strcmp(triggerReasonCstr, "EVDetected")) { - triggerReasonOut = TransactionEventTriggerReason::EVDetected; + triggerReasonOut = MO_TxEventTriggerReason_EVDetected; } else if (!strcmp(triggerReasonCstr, "RemoteStop")) { - triggerReasonOut = TransactionEventTriggerReason::RemoteStop; + triggerReasonOut = MO_TxEventTriggerReason_RemoteStop; } else if (!strcmp(triggerReasonCstr, "RemoteStart")) { - triggerReasonOut = TransactionEventTriggerReason::RemoteStart; + triggerReasonOut = MO_TxEventTriggerReason_RemoteStart; } else if (!strcmp(triggerReasonCstr, "AbnormalCondition")) { - triggerReasonOut = TransactionEventTriggerReason::AbnormalCondition; + triggerReasonOut = MO_TxEventTriggerReason_AbnormalCondition; } else if (!strcmp(triggerReasonCstr, "SignedDataReceived")) { - triggerReasonOut = TransactionEventTriggerReason::SignedDataReceived; + triggerReasonOut = MO_TxEventTriggerReason_SignedDataReceived; } else if (!strcmp(triggerReasonCstr, "ResetCommand")) { - triggerReasonOut = TransactionEventTriggerReason::ResetCommand; + triggerReasonOut = MO_TxEventTriggerReason_ResetCommand; } else { MO_DBG_ERR("deserialization error"); return false; @@ -354,181 +368,3 @@ bool deserializeTransactionEventChargingState(const char *chargingStateCstr, Tra } //namespace MicroOcpp #endif //MO_ENABLE_V201 - -#if MO_ENABLE_V201 -bool g_ocpp_tx_compat_v201; - -void ocpp_tx_compat_setV201(bool isV201) { - g_ocpp_tx_compat_v201 = isV201; -} -#endif - -int ocpp_tx_getTransactionId(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v16"); - return -1; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getTransactionId(); -} -#if MO_ENABLE_V201 -const char *ocpp_tx_getTransactionIdV201(OCPP_Transaction *tx) { - if (!g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v201"); - return nullptr; - } - return reinterpret_cast(tx)->transactionId; -} -#endif //MO_ENABLE_V201 -bool ocpp_tx_isAuthorized(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - return reinterpret_cast(tx)->isAuthorized; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->isAuthorized(); -} -bool ocpp_tx_isIdTagDeauthorized(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - return reinterpret_cast(tx)->isDeauthorized; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->isIdTagDeauthorized(); -} - -bool ocpp_tx_isRunning(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - auto transaction = reinterpret_cast(tx); - return transaction->started && !transaction->stopped; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->isRunning(); -} -bool ocpp_tx_isActive(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - return reinterpret_cast(tx)->active; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->isActive(); -} -bool ocpp_tx_isAborted(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - auto transaction = reinterpret_cast(tx); - return !transaction->active && !transaction->started; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->isAborted(); -} -bool ocpp_tx_isCompleted(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - auto transaction = reinterpret_cast(tx); - return transaction->stopped && transaction->seqNos.empty(); - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->isCompleted(); -} - -const char *ocpp_tx_getIdTag(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - auto transaction = reinterpret_cast(tx); - return transaction->idToken.get(); - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getIdTag(); -} - -const char *ocpp_tx_getParentIdTag(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v16"); - return nullptr; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getParentIdTag(); -} - -bool ocpp_tx_getBeginTimestamp(OCPP_Transaction *tx, char *buf, size_t len) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - return reinterpret_cast(tx)->beginTimestamp.toJsonString(buf, len); - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getBeginTimestamp().toJsonString(buf, len); -} - -int32_t ocpp_tx_getMeterStart(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v16"); - return -1; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getMeterStart(); -} - -bool ocpp_tx_getStartTimestamp(OCPP_Transaction *tx, char *buf, size_t len) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v16"); - return -1; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getStartTimestamp().toJsonString(buf, len); -} - -const char *ocpp_tx_getStopIdTag(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - auto transaction = reinterpret_cast(tx); - return transaction->stopIdToken ? transaction->stopIdToken->get() : ""; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getStopIdTag(); -} - -int32_t ocpp_tx_getMeterStop(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v16"); - return -1; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getMeterStop(); -} - -void ocpp_tx_setMeterStop(OCPP_Transaction* tx, int32_t meter) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v16"); - return; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->setMeterStop(meter); -} - -bool ocpp_tx_getStopTimestamp(OCPP_Transaction *tx, char *buf, size_t len) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - MO_DBG_ERR("only supported in v16"); - return -1; - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getStopTimestamp().toJsonString(buf, len); -} - -const char *ocpp_tx_getStopReason(OCPP_Transaction *tx) { - #if MO_ENABLE_V201 - if (g_ocpp_tx_compat_v201) { - auto transaction = reinterpret_cast(tx); - return serializeTransactionStoppedReason(transaction->stoppedReason); - } - #endif //MO_ENABLE_V201 - return reinterpret_cast(tx)->getStopReason(); -} diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index ecbb17fd..5d06c318 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -2,49 +2,29 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#ifndef TRANSACTION_H -#define TRANSACTION_H +#ifndef MO_TRANSACTION_H +#define MO_TRANSACTION_H -#include - -/* General Tx defs */ -#ifdef __cplusplus -extern "C" { -#endif //__cplusplus - -//TxNotification - event from MO to the main firmware to notify it about transaction state changes -typedef enum { - TxNotification_UNDEFINED, - - //Authorization events - TxNotification_Authorized, //success - TxNotification_AuthorizationRejected, //IdTag/token not authorized - TxNotification_AuthorizationTimeout, //authorization failed - offline - TxNotification_ReservationConflict, //connector/evse reserved for other IdTag - - TxNotification_ConnectionTimeout, //user took to long to plug vehicle after the authorization - TxNotification_DeAuthorized, //server rejected StartTx/TxEvent - TxNotification_RemoteStart, //authorized via RemoteStartTx/RequestStartTx - TxNotification_RemoteStop, //stopped via RemoteStopTx/RequestStopTx - - //Tx lifecycle events - TxNotification_StartTx, //entered running state (StartTx/TxEvent was initiated) - TxNotification_StopTx, //left running state (StopTx/TxEvent was initiated) -} TxNotification; - -#ifdef __cplusplus -} -#endif //__cplusplus - -#ifdef __cplusplus +#include +#include #include #include +#include #include +#include +#include +#include +#include +#include + +#define MO_TXNR_MAX 10000U //upper limit of txNr (internal usage). Must be at least 2*MO_TXRECORD_SIZE+1 +#define MO_TXNR_DIGITS 4 //digits needed to print MAX_INDEX-1 (=9999, i.e. 4 digits) -#define MAX_TX_CNT 100000U //upper limit of txNr (internal usage). Must be at least 2*MO_TXRECORD_SIZE+1 +#if MO_ENABLE_V16 namespace MicroOcpp { +namespace Ocpp16 { /* * A transaction is initiated by the client (charging station) and processed by the server (central system). @@ -63,7 +43,7 @@ class SendStatus { unsigned int opNr = 0; unsigned int attemptNr = 0; - Timestamp attemptTime = MIN_TIME; + Timestamp attemptTime; public: void setRequested() {this->requested = true;} bool isRequested() {return requested;} @@ -80,8 +60,6 @@ class SendStatus { class Transaction : public MemoryManaged { private: - TransactionStoreEvse& context; - bool active = true; //once active is false, the tx must stop (or cannot start at all) /* @@ -91,7 +69,7 @@ class Transaction : public MemoryManaged { char parentIdTag [IDTAG_LEN_MAX + 1] = {'\0'}; bool authorized = false; //if the given idTag was authorized bool deauthorized = false; //if the server revoked a local authorization - Timestamp begin_timestamp = MIN_TIME; + Timestamp begin_timestamp; int reservationId = -1; int txProfileId = -1; @@ -100,8 +78,7 @@ class Transaction : public MemoryManaged { */ SendStatus start_sync; int32_t start_meter = -1; //meterStart of StartTx - Timestamp start_timestamp = MIN_TIME; //timestamp of StartTx; can be set before actually initiating - uint16_t start_bootNr = 0; + Timestamp start_timestamp; //timestamp of StartTx; can be set before actually initiating int transactionId = -1; //only valid if confirmed = true /* @@ -110,8 +87,7 @@ class Transaction : public MemoryManaged { SendStatus stop_sync; char stop_idTag [IDTAG_LEN_MAX + 1] = {'\0'}; int32_t stop_meter = -1; - Timestamp stop_timestamp = MIN_TIME; - uint16_t stop_bootNr = 0; + Timestamp stop_timestamp; char stop_reason [REASON_LEN_MAX + 1] = {'\0'}; /* @@ -122,13 +98,12 @@ class Transaction : public MemoryManaged { bool silent = false; //silent Tx: process tx locally, without reporting to the server + Vector meterValues; + public: - Transaction(TransactionStoreEvse& context, unsigned int connectorId, unsigned int txNr, bool silent = false) : - MemoryManaged("v16.Transactions.Transaction"), - context(context), - connectorId(connectorId), - txNr(txNr), - silent(silent) {} + Transaction(unsigned int connectorId, unsigned int txNr, bool silent = false); + + ~Transaction(); /* * data assigned by OCPP server @@ -145,11 +120,6 @@ class Transaction : public MemoryManaged { bool isAborted() {return !start_sync.isRequested() && !active;} //tx ended before startTx was sent bool isCompleted() {return stop_sync.isConfirmed();} //tx ended and startTx and stopTx have been confirmed by server - /* - * After modifying a field of tx, commit to make the data persistent - */ - bool commit(); - /* * Getters and setters for (mostly) internal use */ @@ -182,9 +152,6 @@ class Transaction : public MemoryManaged { void setStartTimestamp(Timestamp timestamp) {start_timestamp = timestamp;} const Timestamp& getStartTimestamp() {return start_timestamp;} - void setStartBootNr(uint16_t bootNr) {start_bootNr = bootNr;} - uint16_t getStartBootNr() {return start_bootNr;} - void setTransactionId(int transactionId) {this->transactionId = transactionId;} SendStatus& getStopSync() {return stop_sync;} @@ -199,9 +166,6 @@ class Transaction : public MemoryManaged { void setStopTimestamp(Timestamp timestamp) {stop_timestamp = timestamp;} const Timestamp& getStopTimestamp() {return stop_timestamp;} - void setStopBootNr(uint16_t bootNr) {stop_bootNr = bootNr;} - uint16_t getStopBootNr() {return stop_bootNr;} - bool setStopReason(const char *reason); const char *getStopReason() {return stop_reason;} @@ -213,81 +177,32 @@ class Transaction : public MemoryManaged { void setSilent() {silent = true;} bool isSilent() {return silent;} //no data will be sent to server and server will not assign transactionId + + Vector& getTxMeterValues() {return meterValues;} }; -} // namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -#include -#include - -#include -#include -#include -#include +#define MO_TXID_SIZE MO_UUID_STR_SIZE #ifndef MO_SAMPLEDDATATXENDED_SIZE_MAX #define MO_SAMPLEDDATATXENDED_SIZE_MAX 5 #endif namespace MicroOcpp { -namespace Ocpp201 { -// TriggerReasonEnumType (3.82) -enum class TransactionEventTriggerReason : uint8_t { - UNDEFINED, // not part of OCPP - Authorized, - CablePluggedIn, - ChargingRateChanged, - ChargingStateChanged, - Deauthorized, - EnergyLimitReached, - EVCommunicationLost, - EVConnectTimeout, - MeterValueClock, - MeterValuePeriodic, - TimeLimitReached, - Trigger, - UnlockCommand, - StopAuthorized, - EVDeparted, - EVDetected, - RemoteStop, - RemoteStart, - AbnormalCondition, - SignedDataReceived, - ResetCommand -}; +class Clock; + +namespace Ocpp201 { class Transaction : public MemoryManaged { +private: + Clock& clock; public: - - // ReasonEnumType (3.67) - enum class StoppedReason : 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 substates. Notify server about any change when transaction is running */ @@ -312,9 +227,11 @@ class Transaction : public MemoryManaged { bool isAuthorized = false; //if the given idToken was authorized bool isDeauthorized = false; //if the server revoked a local authorization IdToken idToken; - Timestamp beginTimestamp = MIN_TIME; - char transactionId [MO_TXID_LEN_MAX + 1] = {'\0'}; + Timestamp beginTimestamp; + Timestamp startTimestamp; + char transactionId [MO_TXID_SIZE] = {'\0'}; int remoteStartId = -1; + int txProfileId = -1; //if to fill next TxEvent with optional fields bool notifyEvseId = false; @@ -326,18 +243,18 @@ class Transaction : public MemoryManaged { bool evConnectionTimeoutListen = true; - StoppedReason stoppedReason = StoppedReason::UNDEFINED; - TransactionEventTriggerReason stopTrigger = TransactionEventTriggerReason::UNDEFINED; + MO_TxStoppedReason stoppedReason = MO_TxStoppedReason_UNDEFINED; + MO_TxEventTriggerReason stopTrigger = MO_TxEventTriggerReason_UNDEFINED; std::unique_ptr stopIdToken; // if null, then stopIdToken equals idToken /* * Tx-related metering */ - Vector> sampledDataTxEnded; + Vector> sampledDataTxEnded; - unsigned long lastSampleTimeTxUpdated = 0; //0 means not charging right now - unsigned long lastSampleTimeTxEnded = 0; + Timestamp lastSampleTimeTxUpdated; + Timestamp lastSampleTimeTxEnded; /* * Attributes for internal store @@ -350,23 +267,27 @@ class Transaction : public MemoryManaged { bool silent = false; //silent Tx: process tx locally, without reporting to the server - Transaction() : + Transaction(Clock& clock) : MemoryManaged("v201.Transactions.Transaction"), - sampledDataTxEnded(makeVector>(getMemoryTag())), + clock(clock), + sampledDataTxEnded(makeVector>(getMemoryTag())), seqNos(makeVector(getMemoryTag())) { } - void addSampledDataTxEnded(std::unique_ptr mv) { + void addSampledDataTxEnded(std::unique_ptr mv) { if (sampledDataTxEnded.size() >= MO_SAMPLEDDATATXENDED_SIZE_MAX) { - int deltaMin = std::numeric_limits::max(); + int32_t deltaMin = std::numeric_limits::max(); size_t indexMin = sampledDataTxEnded.size(); for (size_t i = 1; i + 1 <= sampledDataTxEnded.size(); i++) { size_t t0 = sampledDataTxEnded.size() - i - 1; size_t t1 = sampledDataTxEnded.size() - i; - auto delta = sampledDataTxEnded[t1]->getTimestamp() - sampledDataTxEnded[t0]->getTimestamp(); + int32_t dt; + if (!clock.delta(sampledDataTxEnded[t1]->timestamp, sampledDataTxEnded[t0]->timestamp, dt)) { + dt = std::numeric_limits::max(); + } - if (delta < deltaMin) { - deltaMin = delta; + if (dt < deltaMin) { + deltaMin = dt; indexMin = t1; } } @@ -399,12 +320,10 @@ class TransactionEventData : public MemoryManaged { Idle }; -//private: Transaction *transaction; Type eventType; Timestamp timestamp; - uint16_t bootNr = 0; - TransactionEventTriggerReason triggerReason; + MO_TxEventTriggerReason triggerReason; const unsigned int seqNo; bool offline = false; int numberOfPhasesUsed = -1; @@ -417,81 +336,30 @@ class TransactionEventData : public MemoryManaged { //int timeSpentCharging = 0; // not supported std::unique_ptr idToken; EvseId evse = -1; - //meterValue not supported Vector> meterValue; unsigned int opNr = 0; unsigned int attemptNr = 0; - Timestamp attemptTime = MIN_TIME; + Timestamp attemptTime; TransactionEventData(Transaction *transaction, unsigned int seqNo) : MemoryManaged("v201.Transactions.TransactionEventData"), transaction(transaction), seqNo(seqNo), meterValue(makeVector>(getMemoryTag())) { } }; -const char *serializeTransactionStoppedReason(Transaction::StoppedReason stoppedReason); -bool deserializeTransactionStoppedReason(const char *stoppedReasonCstr, Transaction::StoppedReason& stoppedReasonOut); +const char *serializeTransactionStoppedReason(MO_TxStoppedReason stoppedReason); +bool deserializeTransactionStoppedReason(const char *stoppedReasonCstr, MO_TxStoppedReason& stoppedReasonOut); const char *serializeTransactionEventType(TransactionEventData::Type type); bool deserializeTransactionEventType(const char *typeCstr, TransactionEventData::Type& typeOut); -const char *serializeTransactionEventTriggerReason(TransactionEventTriggerReason triggerReason); -bool deserializeTransactionEventTriggerReason(const char *triggerReasonCstr, TransactionEventTriggerReason& triggerReasonOut); +const char *serializeTxEventTriggerReason(MO_TxEventTriggerReason triggerReason); +bool deserializeTxEventTriggerReason(const char *triggerReasonCstr, MO_TxEventTriggerReason& triggerReasonOut); const char *serializeTransactionEventChargingState(TransactionEventData::ChargingState chargingState); bool deserializeTransactionEventChargingState(const char *chargingStateCstr, TransactionEventData::ChargingState& chargingStateOut); -} // namespace Ocpp201 -} // namespace MicroOcpp - -#endif // MO_ENABLE_V201 - -extern "C" { -#endif //__cplusplus - -struct OCPP_Transaction; -typedef struct OCPP_Transaction OCPP_Transaction; - -/* - * Compat mode for transactions. This means that all following C-wrapper functions will interprete the handle as v201 transactions - */ -#if MO_ENABLE_V201 -void ocpp_tx_compat_setV201(bool isV201); //if set, all OCPP_Transaction* handles are treated as v201 transactions -#endif - -int ocpp_tx_getTransactionId(OCPP_Transaction *tx); -#if MO_ENABLE_V201 -const char *ocpp_tx_getTransactionIdV201(OCPP_Transaction *tx); -#endif - -bool ocpp_tx_isAuthorized(OCPP_Transaction *tx); -bool ocpp_tx_isIdTagDeauthorized(OCPP_Transaction *tx); - -bool ocpp_tx_isRunning(OCPP_Transaction *tx); -bool ocpp_tx_isActive(OCPP_Transaction *tx); -bool ocpp_tx_isAborted(OCPP_Transaction *tx); -bool ocpp_tx_isCompleted(OCPP_Transaction *tx); - -const char *ocpp_tx_getIdTag(OCPP_Transaction *tx); - -const char *ocpp_tx_getParentIdTag(OCPP_Transaction *tx); - -bool ocpp_tx_getBeginTimestamp(OCPP_Transaction *tx, char *buf, size_t len); - -int32_t ocpp_tx_getMeterStart(OCPP_Transaction *tx); - -bool ocpp_tx_getStartTimestamp(OCPP_Transaction *tx, char *buf, size_t len); - -const char *ocpp_tx_getStopIdTag(OCPP_Transaction *tx); - -int32_t ocpp_tx_getMeterStop(OCPP_Transaction *tx); -void ocpp_tx_setMeterStop(OCPP_Transaction* tx, int32_t meter); - -bool ocpp_tx_getStopTimestamp(OCPP_Transaction *tx, char *buf, size_t len); - -const char *ocpp_tx_getStopReason(OCPP_Transaction *tx); - -#ifdef __cplusplus -} //end extern "C" -#endif +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionDefs.h b/src/MicroOcpp/Model/Transactions/TransactionDefs.h index 62ef657a..17dbdfc2 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionDefs.h +++ b/src/MicroOcpp/Model/Transactions/TransactionDefs.h @@ -7,9 +7,90 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + +#if MO_ENABLE_V16 || MO_ENABLE_V201 + +//MO_TxNotification - event from MO to the main firmware to notify it about transaction state changes +typedef enum { + MO_TxNotification_UNDEFINED, + + //Authorization events + MO_TxNotification_Authorized, //success + MO_TxNotification_AuthorizationRejected, //IdTag/token not authorized + MO_TxNotification_AuthorizationTimeout, //authorization failed - offline + MO_TxNotification_ReservationConflict, //connector/evse reserved for other IdTag + + MO_TxNotification_ConnectionTimeout, //user took to long to plug vehicle after the authorization + MO_TxNotification_DeAuthorized, //server rejected StartTx/TxEvent + MO_TxNotification_RemoteStart, //authorized via RemoteStartTx/RequestStartTx + MO_TxNotification_RemoteStop, //stopped via RemoteStopTx/RequestStopTx + + //Tx lifecycle events + MO_TxNotification_StartTx, //entered running state (StartTx/TxEvent was initiated) + MO_TxNotification_StopTx, //left running state (StopTx/TxEvent was initiated) +} MO_TxNotification; + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 + #if MO_ENABLE_V201 -#define MO_TXID_LEN_MAX 36 +// TriggerReasonEnumType (3.82) +typedef enum { + MO_TxEventTriggerReason_UNDEFINED, // not part of OCPP + MO_TxEventTriggerReason_Authorized, + MO_TxEventTriggerReason_CablePluggedIn, + MO_TxEventTriggerReason_ChargingRateChanged, + MO_TxEventTriggerReason_ChargingStateChanged, + MO_TxEventTriggerReason_Deauthorized, + MO_TxEventTriggerReason_EnergyLimitReached, + MO_TxEventTriggerReason_EVCommunicationLost, + MO_TxEventTriggerReason_EVConnectTimeout, + MO_TxEventTriggerReason_MeterValueClock, + MO_TxEventTriggerReason_MeterValuePeriodic, + MO_TxEventTriggerReason_TimeLimitReached, + MO_TxEventTriggerReason_Trigger, + MO_TxEventTriggerReason_UnlockCommand, + MO_TxEventTriggerReason_StopAuthorized, + MO_TxEventTriggerReason_EVDeparted, + MO_TxEventTriggerReason_EVDetected, + MO_TxEventTriggerReason_RemoteStop, + MO_TxEventTriggerReason_RemoteStart, + MO_TxEventTriggerReason_AbnormalCondition, + MO_TxEventTriggerReason_SignedDataReceived, + MO_TxEventTriggerReason_ResetCommand +} MO_TxEventTriggerReason; + +// ReasonEnumType (3.67) +typedef enum { + MO_TxStoppedReason_UNDEFINED, // not part of OCPP + MO_TxStoppedReason_DeAuthorized, + MO_TxStoppedReason_EmergencyStop, + MO_TxStoppedReason_EnergyLimitReached, + MO_TxStoppedReason_EVDisconnected, + MO_TxStoppedReason_GroundFault, + MO_TxStoppedReason_ImmediateReset, + MO_TxStoppedReason_Local, + MO_TxStoppedReason_LocalOutOfCredit, + MO_TxStoppedReason_MasterPass, + MO_TxStoppedReason_Other, + MO_TxStoppedReason_OvercurrentFault, + MO_TxStoppedReason_PowerLoss, + MO_TxStoppedReason_PowerQuality, + MO_TxStoppedReason_Reboot, + MO_TxStoppedReason_Remote, + MO_TxStoppedReason_SOCLimitReached, + MO_TxStoppedReason_StoppedByEV, + MO_TxStoppedReason_TimeLimitReached, + MO_TxStoppedReason_Timeout +} MO_TxStoppedReason; #endif //MO_ENABLE_V201 + +#ifdef __cplusplus +} //extern "C" +#endif + #endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp b/src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp deleted file mode 100644 index e3c412f0..00000000 --- a/src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include - -#include -#include - -namespace MicroOcpp { - -bool serializeSendStatus(SendStatus& status, JsonObject out) { - if (status.isRequested()) { - out["requested"] = true; - } - if (status.isConfirmed()) { - out["confirmed"] = true; - } - out["opNr"] = status.getOpNr(); - if (status.getAttemptNr() != 0) { - out["attemptNr"] = status.getAttemptNr(); - } - if (status.getAttemptTime() > MIN_TIME) { - char attemptTime [JSONDATE_LENGTH + 1]; - status.getAttemptTime().toJsonString(attemptTime, sizeof(attemptTime)); - out["attemptTime"] = attemptTime; - } - return true; -} - -bool deserializeSendStatus(SendStatus& status, JsonObject in) { - if (in["requested"] | false) { - status.setRequested(); - } - if (in["confirmed"] | false) { - status.confirm(); - } - unsigned int opNr = in["opNr"] | (unsigned int)0; - if (opNr >= 10) { //10 is first valid tx-related opNr - status.setOpNr(opNr); - } - status.setAttemptNr(in["attemptNr"] | (unsigned int)0); - if (in.containsKey("attemptTime")) { - Timestamp attemptTime; - if (!attemptTime.setTime(in["attemptTime"] | "_Invalid")) { - MO_DBG_ERR("deserialization error"); - return false; - } - status.setAttemptTime(attemptTime); - } - return true; -} - -bool serializeTransaction(Transaction& tx, JsonDoc& out) { - out = initJsonDoc("v16.Transactions.TransactionDeserialize", 1024); - JsonObject state = out.to(); - - JsonObject sessionState = state.createNestedObject("session"); - if (!tx.isActive()) { - sessionState["active"] = false; - } - if (tx.getIdTag()[0] != '\0') { - sessionState["idTag"] = tx.getIdTag(); - } - if (tx.getParentIdTag()[0] != '\0') { - sessionState["parentIdTag"] = tx.getParentIdTag(); - } - if (tx.isAuthorized()) { - sessionState["authorized"] = true; - } - if (tx.isIdTagDeauthorized()) { - sessionState["deauthorized"] = true; - } - if (tx.getBeginTimestamp() > MIN_TIME) { - char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; - tx.getBeginTimestamp().toJsonString(timeStr, JSONDATE_LENGTH + 1); - sessionState["timestamp"] = timeStr; - } - if (tx.getReservationId() >= 0) { - sessionState["reservationId"] = tx.getReservationId(); - } - if (tx.getTxProfileId() >= 0) { - sessionState["txProfileId"] = tx.getTxProfileId(); - } - - JsonObject txStart = state.createNestedObject("start"); - - if (!serializeSendStatus(tx.getStartSync(), txStart)) { - return false; - } - - if (tx.isMeterStartDefined()) { - txStart["meter"] = tx.getMeterStart(); - } - - char startTimeStr [JSONDATE_LENGTH + 1] = {'\0'}; - tx.getStartTimestamp().toJsonString(startTimeStr, JSONDATE_LENGTH + 1); - txStart["timestamp"] = startTimeStr; - - txStart["bootNr"] = tx.getStartBootNr(); - - if (tx.getStartSync().isConfirmed()) { - txStart["transactionId"] = tx.getTransactionId(); - } - - JsonObject txStop = state.createNestedObject("stop"); - - if (!serializeSendStatus(tx.getStopSync(), txStop)) { - return false; - } - - if (tx.getStopIdTag()[0] != '\0') { - txStop["idTag"] = tx.getStopIdTag(); - } - - if (tx.isMeterStopDefined()) { - txStop["meter"] = tx.getMeterStop(); - } - - char stopTimeStr [JSONDATE_LENGTH + 1] = {'\0'}; - tx.getStopTimestamp().toJsonString(stopTimeStr, JSONDATE_LENGTH + 1); - txStop["timestamp"] = stopTimeStr; - - txStop["bootNr"] = tx.getStopBootNr(); - - if (tx.getStopReason()[0] != '\0') { - txStop["reason"] = tx.getStopReason(); - } - - if (tx.isSilent()) { - state["silent"] = true; - } - - if (out.overflowed()) { - MO_DBG_ERR("JSON capacity exceeded"); - return false; - } - - return true; -} - -bool deserializeTransaction(Transaction& tx, JsonObject state) { - - JsonObject sessionState = state["session"]; - - if (!(sessionState["active"] | true)) { - tx.setInactive(); - } - - if (sessionState.containsKey("idTag")) { - if (!tx.setIdTag(sessionState["idTag"] | "")) { - MO_DBG_ERR("read err"); - return false; - } - } - - if (sessionState.containsKey("parentIdTag")) { - if (!tx.setParentIdTag(sessionState["parentIdTag"] | "")) { - MO_DBG_ERR("read err"); - return false; - } - } - - if (sessionState["authorized"] | false) { - tx.setAuthorized(); - } - - if (sessionState["deauthorized"] | false) { - tx.setIdTagDeauthorized(); - } - - if (sessionState.containsKey("timestamp")) { - Timestamp timestamp; - if (!timestamp.setTime(sessionState["timestamp"] | "Invalid")) { - MO_DBG_ERR("read err"); - return false; - } - tx.setBeginTimestamp(timestamp); - } - - if (sessionState.containsKey("reservationId")) { - tx.setReservationId(sessionState["reservationId"] | -1); - } - - if (sessionState.containsKey("txProfileId")) { - tx.setTxProfileId(sessionState["txProfileId"] | -1); - } - - JsonObject txStart = state["start"]; - - if (!deserializeSendStatus(tx.getStartSync(), txStart)) { - return false; - } - - if (txStart.containsKey("meter")) { - tx.setMeterStart(txStart["meter"] | 0); - } - - if (txStart.containsKey("timestamp")) { - Timestamp timestamp; - if (!timestamp.setTime(txStart["timestamp"] | "Invalid")) { - MO_DBG_ERR("read err"); - return false; - } - tx.setStartTimestamp(timestamp); - } - - if (txStart.containsKey("bootNr")) { - int bootNrIn = txStart["bootNr"]; - if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits::max()) { - tx.setStartBootNr((uint16_t) bootNrIn); - } else { - MO_DBG_ERR("read err"); - return false; - } - } - - if (txStart.containsKey("transactionId")) { - tx.setTransactionId(txStart["transactionId"] | -1); - } - - JsonObject txStop = state["stop"]; - - if (!deserializeSendStatus(tx.getStopSync(), txStop)) { - return false; - } - - if (txStop.containsKey("idTag")) { - if (!tx.setStopIdTag(txStop["idTag"] | "")) { - MO_DBG_ERR("read err"); - return false; - } - } - - if (txStop.containsKey("meter")) { - tx.setMeterStop(txStop["meter"] | 0); - } - - if (txStop.containsKey("timestamp")) { - Timestamp timestamp; - if (!timestamp.setTime(txStop["timestamp"] | "Invalid")) { - MO_DBG_ERR("read err"); - return false; - } - tx.setStopTimestamp(timestamp); - } - - if (txStop.containsKey("bootNr")) { - int bootNrIn = txStop["bootNr"]; - if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits::max()) { - tx.setStopBootNr((uint16_t) bootNrIn); - } else { - MO_DBG_ERR("read err"); - return false; - } - } - - if (txStop.containsKey("reason")) { - if (!tx.setStopReason(txStop["reason"] | "")) { - MO_DBG_ERR("read err"); - return false; - } - } - - if (state["silent"] | false) { - tx.setSilent(); - } - - MO_DBG_DEBUG("DUMP TX (%s)", tx.getIdTag() ? tx.getIdTag() : "idTag missing"); - MO_DBG_DEBUG("Session | idTag %s, active: %i, authorized: %i, deauthorized: %i", tx.getIdTag(), tx.isActive(), tx.isAuthorized(), tx.isIdTagDeauthorized()); - MO_DBG_DEBUG("Start RPC | req: %i, conf: %i", tx.getStartSync().isRequested(), tx.getStartSync().isConfirmed()); - MO_DBG_DEBUG("Stop RPC | req: %i, conf: %i", tx.getStopSync().isRequested(), tx.getStopSync().isConfirmed()); - if (tx.isSilent()) { - MO_DBG_DEBUG(" | silent Tx"); - } - - return true; -} - -} diff --git a/src/MicroOcpp/Model/Transactions/TransactionDeserialize.h b/src/MicroOcpp/Model/Transactions/TransactionDeserialize.h deleted file mode 100644 index c8cfc427..00000000 --- a/src/MicroOcpp/Model/Transactions/TransactionDeserialize.h +++ /dev/null @@ -1,20 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_TRANSACTIONDESERIALIZE_H -#define MO_TRANSACTIONDESERIALIZE_H - -#include -#include - -#include - -namespace MicroOcpp { - -bool serializeTransaction(Transaction& tx, JsonDoc& out); -bool deserializeTransaction(Transaction& tx, JsonObject in); - -} - -#endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.h b/src/MicroOcpp/Model/Transactions/TransactionService.h deleted file mode 100644 index 36a0bc99..00000000 --- a/src/MicroOcpp/Model/Transactions/TransactionService.h +++ /dev/null @@ -1,144 +0,0 @@ -// 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 -#include - -#include -#include - -#ifndef MO_TXRECORD_SIZE_V201 -#define MO_TXRECORD_SIZE_V201 4 //maximum number of tx to hold on flash storage -#endif - -namespace MicroOcpp { - -class Context; -class FilesystemAdapter; -class Variable; - -class TransactionService : public MemoryManaged { -public: - - class Evse : public RequestEmitter, public MemoryManaged { - private: - Context& context; - TransactionService& txService; - Ocpp201::TransactionStoreEvse& txStore; - const unsigned int evseId; - unsigned int txNrCounter = 0; - std::unique_ptr transaction; - Ocpp201::TransactionEventData::ChargingState trackChargingState = Ocpp201::TransactionEventData::ChargingState::UNDEFINED; - - std::function connectorPluggedInput; - std::function evReadyInput; - std::function evseReadyInput; - - std::function startTxReadyInput; - std::function stopTxReadyInput; - - std::function txNotificationOutput; - - bool beginTransaction(); - bool endTransaction(Ocpp201::Transaction::StoppedReason stoppedReason, Ocpp201::TransactionEventTriggerReason stopTrigger); - - unsigned int txNrBegin = 0; //oldest (historical) transaction on flash. Has no function, but is useful for error diagnosis - unsigned int txNrFront = 0; //oldest transaction which is still queued to be sent to the server - unsigned int txNrEnd = 0; //one position behind newest transaction - - Ocpp201::Transaction *txFront = nullptr; - std::unique_ptr txFrontCache; //helper owner for txFront. Empty if txFront == transaction.get() - std::unique_ptr txEventFront; - bool txEventFrontIsRequested = false; - - public: - Evse(Context& context, TransactionService& txService, Ocpp201::TransactionStoreEvse& txStore, unsigned int evseId); - virtual ~Evse(); - - void loop(); - - void setConnectorPluggedInput(std::function connectorPlugged); - void setEvReadyInput(std::function evRequestsEnergy); - void setEvseReadyInput(std::function connectorEnergized); - - void setTxNotificationOutput(std::function txNotificationOutput); - void updateTxNotification(TxNotification event); - - bool beginAuthorization(IdToken idToken, bool validateIdToken = true); // authorize by swipe RFID - bool endAuthorization(IdToken idToken = IdToken(), bool validateIdToken = false); // stop authorization by swipe RFID - - // stop transaction, but neither upon user request nor OCPP server request (e.g. after PowerLoss) - bool abortTransaction(Ocpp201::Transaction::StoppedReason stoppedReason = Ocpp201::Transaction::StoppedReason::Other, Ocpp201::TransactionEventTriggerReason stopTrigger = Ocpp201::TransactionEventTriggerReason::AbnormalCondition); - - Ocpp201::Transaction *getTransaction(); - - bool ocppPermitsCharge(); - - unsigned int getFrontRequestOpNr() override; - std::unique_ptr fetchFrontRequest() override; - - friend TransactionService; - }; - - // TxStartStopPoint (2.6.4.1) - enum class TxStartStopPoint : uint8_t { - ParkingBayOccupancy, - EVConnected, - Authorized, - DataSigned, - PowerPathClosed, - EnergyTransfer - }; - -private: - Context& context; - Ocpp201::TransactionStore txStore; - Evse *evses [MO_NUM_EVSEID] = {nullptr}; - - Variable *txStartPointString = nullptr; - Variable *txStopPointString = nullptr; - Variable *stopTxOnInvalidIdBool = nullptr; - Variable *stopTxOnEVSideDisconnectBool = nullptr; - Variable *evConnectionTimeOutInt = nullptr; - Variable *sampledDataTxUpdatedInterval = nullptr; - Variable *sampledDataTxEndedInterval = nullptr; - Variable *messageAttemptsTransactionEventInt = nullptr; - Variable *messageAttemptIntervalTransactionEventInt = nullptr; - Variable *silentOfflineTransactionsBool = nullptr; - uint16_t trackTxStartPoint = -1; - uint16_t trackTxStopPoint = -1; - Vector txStartPointParsed; - Vector txStopPointParsed; - bool isTxStartPoint(TxStartStopPoint check); - bool isTxStopPoint(TxStartStopPoint check); -public: - TransactionService(Context& context, std::shared_ptr filesystem, unsigned int numEvseIds); - ~TransactionService(); - - void loop(); - - Evse *getEvse(unsigned int evseId); - - bool parseTxStartStopPoint(const char *src, Vector& dst); -}; - -} // namespace MicroOcpp - -#endif // MO_ENABLE_V201 - -#endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp new file mode 100644 index 00000000..402646fb --- /dev/null +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -0,0 +1,1520 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef MO_TX_CLEAN_ABORTED +#define MO_TX_CLEAN_ABORTED 1 +#endif + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +TransactionServiceEvse::TransactionServiceEvse(Context& context, TransactionService& cService, unsigned int evseId) + : MemoryManaged("v16.Transactions.TransactionServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), cService(cService), evseId(evseId) { + +} + +TransactionServiceEvse::~TransactionServiceEvse() { + if (transaction != transactionFront) { + delete transaction; + } + transaction = nullptr; + delete transactionFront; + transactionFront = nullptr; +} + +bool TransactionServiceEvse::setup() { + + context.getMessageService().addSendQueue(this); //register at RequestQueue as Request emitter + + connection = context.getConnection(); + if (!connection) { + MO_DBG_ERR("setup failure"); + return false; + } + + meteringServiceEvse = model.getMeteringService() ? model.getMeteringService()->getEvse(evseId) : nullptr; + if (!meteringServiceEvse) { + MO_DBG_ERR("setup failure"); + return false; + } + + availServiceEvse = model.getAvailabilityService() ? model.getAvailabilityService()->getEvse(evseId) : nullptr; + if (!availServiceEvse) { + MO_DBG_ERR("setup failure"); + return false; + } + + filesystem = context.getFilesystem(); + if (!filesystem) { + MO_DBG_DEBUG("no FS access. Can enqueue only one transaction"); + } + + char txFnamePrefix [30]; + snprintf(txFnamePrefix, sizeof(txFnamePrefix), "tx-%.*u-", MO_NUM_EVSEID_DIGITS, evseId); + size_t txFnamePrefixLen = strlen(txFnamePrefix); + + if (filesystem) { + if (!FilesystemUtils::loadRingIndex(filesystem, txFnamePrefix, MO_TXNR_MAX, &txNrBegin, &txNrEnd)) { + MO_DBG_ERR("failed to load tx index"); + return false; + } + } + + MO_DBG_DEBUG("found %u transactions for connector %u. Internal range from %u to %u (exclusive)", (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX, evseId, txNrBegin, txNrEnd); + txNrFront = txNrBegin; + + if (filesystem) { + unsigned int txNrLatest = (txNrEnd + MO_TXNR_MAX - 1) % MO_TXNR_MAX; //txNr of the most recent tx on flash + auto txLoaded = new Transaction(evseId, txNrLatest); + if (!txLoaded) { + MO_DBG_ERR("OOM"); + return false; + } + + bool setupFailure = false; + + auto ret = TransactionStore::load(filesystem, context, evseId, txNrLatest, *txLoaded); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + //continue loading txMeterValues + break; + case FilesystemUtils::LoadStatus::FileNotFound: + break; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + setupFailure = true; + break; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("tx load failure"); //leave the corrupt file on flash and wait for the queue front to reach and clean it + break; + } + + if (ret == FilesystemUtils::LoadStatus::Success) { + auto ret2 = MeterStore::load(filesystem, context, meteringServiceEvse->getMeterInputs(), evseId, txNrLatest, txLoaded->getTxMeterValues()); + switch (ret2) { + case FilesystemUtils::LoadStatus::Success: + case FilesystemUtils::LoadStatus::FileNotFound: + transaction = txLoaded; + txLoaded = nullptr; + break; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + setupFailure = true; + break; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("tx load failure"); + MeterStore::remove(filesystem, evseId, txNrLatest); + transaction = txLoaded; + txLoaded = nullptr; + break; + } + } + + delete txLoaded; + txLoaded = nullptr; + + if (setupFailure) { + return false; + } + } + + return true; +} + +bool TransactionServiceEvse::ocppPermitsCharge() { + if (evseId == 0) { + MO_DBG_WARN("not supported for evseId == 0"); + return false; + } + + bool suspendDeAuthorizedIdTag = transaction && transaction->isIdTagDeauthorized(); //if idTag status is "DeAuthorized" and if charging should stop + + //check special case for DeAuthorized idTags: FreeVend mode + if (suspendDeAuthorizedIdTag && cService.freeVendActiveBool->getBool()) { + suspendDeAuthorizedIdTag = false; + } + + // check charge permission depending on TxStartPoint + if (cService.txStartOnPowerPathClosedBool->getBool()) { + // tx starts when the power path is closed. Advertise charging before transaction + return transaction && + transaction->isActive() && + transaction->isAuthorized() && + !suspendDeAuthorizedIdTag; + } else { + // tx must be started before the power path can be closed + return transaction && + transaction->isRunning() && + transaction->isActive() && + !suspendDeAuthorizedIdTag; + } +} + +void TransactionServiceEvse::loop() { + + if (!trackLoopExecute) { + trackLoopExecute = true; + if (connectorPluggedInput) { + freeVendTrackPlugged = connectorPluggedInput(evseId, connectorPluggedInputUserData); + } + } + + if (transaction && filesystem && ((transaction->isAborted() && MO_TX_CLEAN_ABORTED) || (transaction->isSilent() && transaction->getStopSync().isRequested()))) { + //If the transaction is aborted (invalidated before started) or is silent and has stopped. Delete all artifacts from flash + //This is an optimization. The memory management will attempt to remove those files again later + bool removed = MeterStore::remove(filesystem, evseId, transaction->getTxNr()); + + if (removed) { + removed &= TransactionStore::remove(filesystem, evseId, transaction->getTxNr()); + } + + if (removed) { + if (txNrFront == txNrEnd) { + txNrFront = transaction->getTxNr(); + } + txNrEnd = transaction->getTxNr(); //roll back creation of last tx entry + } + + MO_DBG_DEBUG("collect aborted or silent transaction %u-%u %s", evseId, transaction->getTxNr(), removed ? "" : "failure"); + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + if (transaction != transactionFront) { + delete transaction; + } + transaction = nullptr; + } + + if (transaction && transaction->isAborted()) { + MO_DBG_DEBUG("collect aborted transaction %u-%u", evseId, transaction->getTxNr()); + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + if (transaction != transactionFront) { + delete transaction; + } + transaction = nullptr; + } + + if (transaction && transaction->getStopSync().isRequested()) { + MO_DBG_DEBUG("collect obsolete transaction %u-%u", evseId, transaction->getTxNr()); + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + if (transaction != transactionFront) { + delete transaction; + } + transaction = nullptr; + } + + if (transaction) { //begin exclusively transaction-related operations + + if (connectorPluggedInput) { + if (transaction->isRunning() && transaction->isActive() && !connectorPluggedInput(evseId, connectorPluggedInputUserData)) { + if (cService.stopTransactionOnEVSideDisconnectBool->getBool()) { + MO_DBG_DEBUG("Stop Tx due to EV disconnect"); + transaction->setStopReason("EVDisconnected"); + transaction->setInactive(); + commitTransaction(transaction); + } + } + + int32_t runtimeSecs; + if (!clock.delta(clock.now(), transaction->getBeginTimestamp(), runtimeSecs)) { + runtimeSecs = 0; + } + + if (transaction->isActive() && + !transaction->getStartSync().isRequested() && + cService.connectionTimeOutInt->getInt() > 0 && + !connectorPluggedInput(evseId, connectorPluggedInputUserData) && + runtimeSecs >= cService.connectionTimeOutInt->getInt()) { + + MO_DBG_INFO("Session mngt: timeout"); + transaction->setInactive(); + commitTransaction(transaction); + + updateTxNotification(MO_TxNotification_ConnectionTimeout); + } + } + + if (transaction->isActive() && + transaction->isIdTagDeauthorized() && ( //transaction has been deAuthorized + !transaction->isRunning() || //if transaction hasn't started yet, always end + cService.stopTransactionOnInvalidIdBool->getBool())) { //if transaction is running, behavior depends on StopTransactionOnInvalidId + + MO_DBG_DEBUG("DeAuthorize session"); + transaction->setStopReason("DeAuthorized"); + transaction->setInactive(); + commitTransaction(transaction); + } + + /* + * Check conditions for start or stop transaction + */ + + if (!transaction->isRunning()) { + //start tx? + + if (transaction->isActive() && transaction->isAuthorized() && //tx must be authorized + (!connectorPluggedInput || connectorPluggedInput(evseId, connectorPluggedInputUserData)) && //if applicable, connector must be plugged + availServiceEvse->isOperative() && //only start tx if charger is free of error conditions + (!cService.txStartOnPowerPathClosedBool->getBool() || !evReadyInput || evReadyInput(evseId, evReadyInputUserData)) && //if applicable, postpone tx start point to PowerPathClosed + (!startTxReadyInput || startTxReadyInput(evseId, startTxReadyInputUserData))) { //if defined, user Input for allowing StartTx must be true + //start Transaction + + MO_DBG_INFO("Session mngt: trigger StartTransaction"); + + if (transaction->getMeterStart() < 0) { + auto meterStart = meteringServiceEvse->readTxEnergyMeter(MO_ReadingContext_TransactionBegin); + transaction->setMeterStart(meterStart); + } + + if (!transaction->getStartTimestamp().isDefined()) { + transaction->setStartTimestamp(clock.now()); + } + + transaction->getStartSync().setRequested(); + transaction->getStartSync().setOpNr(context.getMessageService().getNextOpNr()); + + if (transaction->isSilent()) { + MO_DBG_INFO("silent Transaction: omit StartTx"); + transaction->getStartSync().confirm(); + } else { + //normal transaction, record txMeterData + meteringServiceEvse->beginTxMeterData(transaction); + } + + commitTransaction(transaction); + + updateTxNotification(MO_TxNotification_StartTx); + + //fetchFrontRequest will create the StartTransaction and pass it to the message sender + return; + } + } else { + //stop tx? + + if (!transaction->isActive() && + (!stopTxReadyInput || stopTxReadyInput(evseId, stopTxReadyInputUserData))) { + //stop transaction + + MO_DBG_INFO("Session mngt: trigger StopTransaction"); + + if (transaction->getMeterStop() < 0) { + auto meterStop = meteringServiceEvse->readTxEnergyMeter(MO_ReadingContext_TransactionEnd); + transaction->setMeterStop(meterStop); + } + + if (!transaction->getStopTimestamp().isDefined()) { + transaction->setStopTimestamp(clock.now()); + } + + transaction->getStopSync().setRequested(); + transaction->getStopSync().setOpNr(context.getMessageService().getNextOpNr()); + + if (transaction->isSilent()) { + MO_DBG_INFO("silent Transaction: omit StopTx"); + transaction->getStopSync().confirm(); + } else { + //normal transaction, record txMeterData + meteringServiceEvse->endTxMeterData(transaction); + } + + commitTransaction(transaction); + + updateTxNotification(MO_TxNotification_StopTx); + + //fetchFrontRequest will create the StopTransaction and pass it to the message sender + return; + } + } + } //end transaction-related operations + + //handle FreeVend mode + if (cService.freeVendActiveBool->getBool() && connectorPluggedInput) { + if (!freeVendTrackPlugged && connectorPluggedInput(evseId, connectorPluggedInputUserData) && !transaction) { + const char *idTag = cService.freeVendIdTagString->getString(); + MO_DBG_INFO("begin FreeVend Tx using idTag %s", idTag); + beginTransaction_authorized(idTag); + + if (!transaction) { + MO_DBG_ERR("could not begin FreeVend Tx"); + } + } + + freeVendTrackPlugged = connectorPluggedInput(evseId, connectorPluggedInputUserData); + } + + + return; +} + +bool TransactionServiceEvse::commitTransaction(Transaction *transaction) { + if (filesystem) { + auto ret = TransactionStore::store(filesystem, context, *transaction); + if (ret != FilesystemUtils::StoreStatus::Success) { + MO_DBG_ERR("tx commit failure"); + return false; + } + } + return true; +} + +Transaction *TransactionServiceEvse::allocateTransaction() { + + Transaction *tx = nullptr; + + if (!filesystem) { + //volatile mode shortcuts file restore and capacity logic + if (transactionFront) { + MO_DBG_WARN("volatile mode: cannot start tx while older is still in progress"); + return nullptr; + } + tx = new Transaction(evseId, txNrEnd); + if (!tx) { + MO_DBG_ERR("OOM"); + return nullptr; + } + txNrEnd = (txNrEnd + 1) % MO_TXNR_MAX; + MO_DBG_DEBUG("advance txNrEnd %u-%u", evseId, txNrEnd); + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + return tx; + } + + //clean possible aborted tx + unsigned int txr = txNrEnd; + unsigned int txSize = (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX; + for (unsigned int i = 0; i < txSize; i++) { + txr = (txr + MO_TXNR_MAX - 1) % MO_TXNR_MAX; //decrement by 1 + + Transaction txLoaded {evseId, txr}; + + auto ret = TransactionStore::load(filesystem, context, evseId, txr, txLoaded); + if (ret == FilesystemUtils::LoadStatus::ErrOOM) { + MO_DBG_ERR("OOM"); + goto fail; + } + + bool found = (ret != FilesystemUtils::LoadStatus::Success); //possible to restore tx from this slot, i.e. exists and non-corrupt + + //check if dangling silent tx, aborted tx, or corrupted entry (tx == null) + if (!found || txLoaded.isSilent() || (txLoaded.isAborted() && MO_TX_CLEAN_ABORTED)) { + //yes, remove + bool removed = MeterStore::remove(filesystem, evseId, txr); + if (removed) { + removed &= TransactionStore::remove(filesystem, evseId, txr); + } + if (removed) { + if (txNrFront == txNrEnd) { + txNrFront = txr; + } + txNrEnd = txr; + MO_DBG_WARN("deleted dangling silent or aborted tx for new transaction"); + } else { + MO_DBG_ERR("memory corruption"); + break; + } + } else { + //no, tx record trimmed, end + break; + } + } + + txSize = (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX; //refresh after cleaning txs + + //try to create new transaction + if (txSize < MO_TXRECORD_SIZE) { + tx = new Transaction(evseId, txNrEnd); + if (!tx) { + MO_DBG_ERR("OOM"); + goto fail; + } + } + + if (!tx) { + //could not create transaction - now, try to replace tx history entry + + unsigned int txl = txNrBegin; + txSize = (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX; + + for (unsigned int i = 0; i < txSize; i++) { + + if (tx) { + //success, finished here + break; + } + + //no transaction allocated, delete history entry to make space + + Transaction txhist {evseId, txl}; + + auto ret = TransactionStore::load(filesystem, context, evseId, txl, txhist); + if (ret == FilesystemUtils::LoadStatus::ErrOOM) { + MO_DBG_ERR("OOM"); + goto fail; + } + + bool found = (ret != FilesystemUtils::LoadStatus::Success); //possible to restore tx from this slot, i.e. exists and non-corrupt + + //oldest entry, now check if it's history and can be removed or corrupted entry + if (!found || txhist.isCompleted() || txhist.isAborted() || (txhist.isSilent() && txhist.getStopSync().isRequested())) { + //yes, remove + bool removed = MeterStore::remove(filesystem, evseId, txl); + if (removed) { + removed &= TransactionStore::remove(filesystem, evseId, txl); + } + if (removed) { + txNrBegin = (txl + 1) % MO_TXNR_MAX; + if (txNrFront == txl) { + txNrFront = txNrBegin; + } + MO_DBG_DEBUG("deleted tx history entry for new transaction"); + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + + tx = new Transaction(evseId, txNrEnd); + if (!tx) { + MO_DBG_ERR("OOM"); + goto fail; + } + } else { + MO_DBG_ERR("memory corruption"); + break; + } + } else { + //no, end of history reached, don't delete further tx + MO_DBG_DEBUG("cannot delete more tx"); + break; + } + + txl++; + txl %= MO_TXNR_MAX; + } + } + + if (!tx) { + //couldn't create normal transaction -> check if to start charging without real transaction + if (cService.silentOfflineTransactionsBool->getBool()) { + //run charging session without sending StartTx or StopTx to the server + tx = new Transaction(evseId, txNrEnd, true); + if (!tx) { + MO_DBG_ERR("OOM"); + goto fail; + } + MO_DBG_DEBUG("created silent transaction"); + } + } + + if (tx) { + //clean meter data which could still be here from a rolled-back transaction + if (!MeterStore::remove(filesystem, evseId, tx->getTxNr())) { + MO_DBG_ERR("memory corruption"); + } + } + + if (tx) { + txNrEnd = (txNrEnd + 1) % MO_TXNR_MAX; + MO_DBG_DEBUG("advance txNrEnd %u-%u", evseId, txNrEnd); + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + } + + return tx; +fail: + delete tx; + return nullptr; +} + +bool TransactionServiceEvse::beginTransaction(const char *idTag) { + + if (transaction) { + MO_DBG_WARN("tx process still running. Please call endTransaction(...) before"); + return false; + } + + MO_DBG_DEBUG("Begin transaction process (%s), prepare", idTag != nullptr ? idTag : ""); + + bool localAuthFound = false; + const char *parentIdTag = nullptr; //locally stored parentIdTag + bool offlineBlockedAuth = false; //if offline authorization will be blocked by local auth list entry + + //check local OCPP whitelist + #if MO_ENABLE_LOCAL_AUTH + if (auto authService = model.getAuthorizationService()) { + auto localAuth = authService->getLocalAuthorization(idTag); + + //check authorization status + if (localAuth && localAuth->getAuthorizationStatus() != AuthorizationStatus::Accepted) { + MO_DBG_DEBUG("local auth denied (%s)", idTag); + offlineBlockedAuth = true; + localAuth = nullptr; + } + + //check expiry + int32_t dtExpiryDate; + if (localAuth && localAuth->getExpiryDate() && clock.delta(clock.now(), *localAuth->getExpiryDate(), dtExpiryDate) && dtExpiryDate >= 0) { + MO_DBG_DEBUG("idTag %s local auth entry expired", idTag); + offlineBlockedAuth = true; + localAuth = nullptr; + } + + if (localAuth) { + localAuthFound = true; + parentIdTag = localAuth->getParentIdTag(); + } + } + #endif //MO_ENABLE_LOCAL_AUTH + + int reservationId = -1; + bool offlineBlockedResv = false; //if offline authorization will be blocked by reservation + + //check if blocked by reservation + #if MO_ENABLE_RESERVATION + if (model.getReservationService()) { + + auto reservation = model.getReservationService()->getReservation( + evseId, + idTag, + parentIdTag); + + if (reservation) { + reservationId = reservation->getReservationId(); + } + + if (reservation && !reservation->matches( + idTag, + parentIdTag)) { + //reservation blocks connector + offlineBlockedResv = true; //when offline, tx is always blocked + + //if parentIdTag is known, abort this tx immediately, otherwise wait for Authorize.conf to decide + if (parentIdTag) { + //parentIdTag known + MO_DBG_INFO("connector %u reserved - abort transaction", evseId); + updateTxNotification(MO_TxNotification_ReservationConflict); + return false; + } else { + //parentIdTag unkown but local authorization failed in any case + MO_DBG_INFO("connector %u reserved - no local auth", evseId); + localAuthFound = false; + } + } + } + #endif //MO_ENABLE_RESERVATION + + transaction = allocateTransaction(); + + if (!transaction) { + MO_DBG_WARN("tx queue full - cannot start another tx"); + return false; + } + + if (!idTag || *idTag == '\0') { + //input string is empty + transaction->setIdTag(""); + } else { + transaction->setIdTag(idTag); + } + + if (parentIdTag) { + transaction->setParentIdTag(parentIdTag); + } + + transaction->setBeginTimestamp(clock.now()); + + //check for local preauthorization + if (localAuthFound && cService.localPreAuthorizeBool->getBool()) { + MO_DBG_DEBUG("Begin transaction process (%s), preauthorized locally", idTag != nullptr ? idTag : ""); + + if (reservationId >= 0) { + transaction->setReservationId(reservationId); + } + transaction->setAuthorized(); + + updateTxNotification(MO_TxNotification_Authorized); + } + + commitTransaction(transaction); + + auto authorize = makeRequest(context, new Authorize(model, idTag)); + authorize->setTimeout(cService.authorizationTimeoutInt->getInt() > 0 ? cService.authorizationTimeoutInt->getInt(): 20); + + if (!connection->isConnected()) { + //WebSockt unconnected. Enter offline mode immediately + authorize->setTimeout(1); + } + + //Sending Authorize, but operation could have become outdated when the response from the server arrives. Should discard + //it then. To match the server response with currently active tx object, capture tx identifiers + unsigned int txNr_capture = transaction->getTxNr(); + Timestamp beginTimestamp_capture = transaction->getBeginTimestamp(); + + authorize->setOnReceiveConf([this, txNr_capture, beginTimestamp_capture] (JsonObject response) { + + int32_t dtBeginTimestamp = 0; + if (!transaction || !context.getClock().delta(transaction->getBeginTimestamp(), beginTimestamp_capture, dtBeginTimestamp)) { + dtBeginTimestamp = 0; + } + + if (!transaction || transaction->getTxNr() != txNr_capture || dtBeginTimestamp != 0) { + MO_DBG_DEBUG("stale Authorize"); + return; + } + + JsonObject idTagInfo = response["idTagInfo"]; + + if (strcmp("Accepted", idTagInfo["status"] | "UNDEFINED")) { + //Authorization rejected, abort transaction + MO_DBG_DEBUG("Authorize rejected (%s), abort tx process", transaction->getIdTag()); + transaction->setIdTagDeauthorized(); + commitTransaction(transaction); + updateTxNotification(MO_TxNotification_AuthorizationRejected); + return; + } + + #if MO_ENABLE_RESERVATION + if (model.getReservationService()) { + auto reservation = model.getReservationService()->getReservation( + evseId, + transaction->getIdTag(), + idTagInfo["parentIdTag"] | (const char*) nullptr); + if (reservation) { + //reservation found for connector + if (reservation->matches( + transaction->getIdTag(), + idTagInfo["parentIdTag"] | (const char*) nullptr)) { + MO_DBG_INFO("connector %u matches reservationId %i", evseId, reservation->getReservationId()); + transaction->setReservationId(reservation->getReservationId()); + } else { + //reservation found for connector but does not match idTag or parentIdTag + MO_DBG_INFO("connector %u reserved - abort transaction", evseId); + transaction->setInactive(); + commitTransaction(transaction); + updateTxNotification(MO_TxNotification_ReservationConflict); + return; + } + } + } + #endif //MO_ENABLE_RESERVATION + + if (idTagInfo.containsKey("parentIdTag")) { + transaction->setParentIdTag(idTagInfo["parentIdTag"] | ""); + } + + MO_DBG_DEBUG("Authorized transaction process (%s)", transaction->getIdTag()); + transaction->setAuthorized(); + commitTransaction(transaction); + + updateTxNotification(MO_TxNotification_Authorized); + }); + + //capture local auth and reservation check in for timeout handler + authorize->setOnTimeout([this, txNr_capture, beginTimestamp_capture, + offlineBlockedAuth, + offlineBlockedResv, + localAuthFound, + reservationId] () { + + int32_t dtBeginTimestamp = 0; + if (!transaction || !context.getClock().delta(transaction->getBeginTimestamp(), beginTimestamp_capture, dtBeginTimestamp)) { + dtBeginTimestamp = 0; + } + + if (!transaction || transaction->getTxNr() != txNr_capture || dtBeginTimestamp != 0) { + MO_DBG_DEBUG("stale Authorize"); + return; + } + + if (offlineBlockedAuth) { + //local auth entry exists, but is expired -> avoid offline tx + MO_DBG_DEBUG("Abort transaction process (%s), timeout, expired local auth", transaction->getIdTag()); + transaction->setInactive(); + commitTransaction(transaction); + updateTxNotification(MO_TxNotification_AuthorizationTimeout); + return; + } + + if (offlineBlockedResv) { + //reservation found for connector but does not match idTag or parentIdTag + MO_DBG_INFO("connector %u reserved (offline) - abort transaction", evseId); + transaction->setInactive(); + commitTransaction(transaction); + updateTxNotification(MO_TxNotification_ReservationConflict); + return; + } + + if (localAuthFound && cService.localAuthorizeOfflineBool->getBool()) { + MO_DBG_DEBUG("Offline transaction process (%s), locally authorized", transaction->getIdTag()); + if (reservationId >= 0) { + transaction->setReservationId(reservationId); + } + transaction->setAuthorized(); + commitTransaction(transaction); + + updateTxNotification(MO_TxNotification_Authorized); + return; + } + + if (cService.allowOfflineTxForUnknownIdBool->getBool()) { + MO_DBG_DEBUG("Offline transaction process (%s), allow unknown ID", transaction->getIdTag()); + if (reservationId >= 0) { + transaction->setReservationId(reservationId); + } + transaction->setAuthorized(); + commitTransaction(transaction); + updateTxNotification(MO_TxNotification_Authorized); + return; + } + + MO_DBG_DEBUG("Abort transaction process (%s): timeout", transaction->getIdTag()); + transaction->setInactive(); + commitTransaction(transaction); + updateTxNotification(MO_TxNotification_AuthorizationTimeout); + return; //offline tx disabled + }); + context.getMessageService().sendRequest(std::move(authorize)); + + return transaction; +} + +bool TransactionServiceEvse::beginTransaction_authorized(const char *idTag, const char *parentIdTag) { + + if (transaction) { + MO_DBG_WARN("tx process still running. Please call endTransaction(...) before"); + return false; + } + + transaction = allocateTransaction(); + + if (!transaction) { + MO_DBG_ERR("could not allocate Tx"); + return false; + } + + if (!idTag || *idTag == '\0') { + //input string is empty + transaction->setIdTag(""); + } else { + transaction->setIdTag(idTag); + } + + if (parentIdTag) { + transaction->setParentIdTag(parentIdTag); + } + + transaction->setBeginTimestamp(clock.now()); + + MO_DBG_DEBUG("Begin transaction process (%s), already authorized", idTag != nullptr ? idTag : ""); + + transaction->setAuthorized(); + + #if MO_ENABLE_RESERVATION + if (model.getReservationService()) { + if (auto reservation = model.getReservationService()->getReservation(evseId, idTag, parentIdTag)) { + if (reservation->matches(idTag, parentIdTag)) { + transaction->setReservationId(reservation->getReservationId()); + } + } + } + #endif //MO_ENABLE_RESERVATION + + commitTransaction(transaction); + + return true; +} + +bool TransactionServiceEvse::endTransaction(const char *idTag, const char *reason) { + + if (!transaction || !transaction->isActive()) { + //transaction already ended / not active anymore + return false; + } + + MO_DBG_DEBUG("End session started by idTag %s", + transaction->getIdTag()); + + if (!idTag || !strcmp(idTag, transaction->getIdTag())) { + return endTransaction_authorized(idTag, reason); + } + + bool res = false; + + const char *parentIdTag = transaction->getParentIdTag(); + if (strlen(parentIdTag) > 0) + { + // We have a parent ID tag, so we need to check if this new card also has one + auto authorize = makeRequest(context, new Authorize(model, idTag)); + authorize->setTimeout(cService.authorizationTimeoutInt->getInt() > 0 ? cService.authorizationTimeoutInt->getInt(): 20); + + if (!connection->isConnected()) { + //WebSockt unconnected. Enter offline mode immediately + authorize->setTimeout(1); + } + + auto txNr_capture = transaction->getTxNr(); + auto beginTimestamp_capture = transaction->getBeginTimestamp(); + auto idTag_capture = makeString(getMemoryTag(), idTag); + auto reason_capture = makeString(getMemoryTag(), reason ? reason : ""); + authorize->setOnReceiveConf([this, txNr_capture, beginTimestamp_capture, idTag_capture, reason_capture] (JsonObject response) { + + int32_t dtBeginTimestamp = 0; + if (!transaction || !context.getClock().delta(transaction->getBeginTimestamp(), beginTimestamp_capture, dtBeginTimestamp)) { + dtBeginTimestamp = 0; + } + + if (!transaction || transaction->getTxNr() != txNr_capture || dtBeginTimestamp != 0) { + MO_DBG_DEBUG("stale Authorize"); + return; + } + + JsonObject idTagInfo = response["idTagInfo"]; + + if (strcmp("Accepted", idTagInfo["status"] | "_Undefined")) { + //Authorization rejected, do nothing + MO_DBG_DEBUG("Authorize rejected (%s), continue transaction", idTag_capture.c_str()); + updateTxNotification(MO_TxNotification_AuthorizationRejected); + return; + } + if (idTagInfo.containsKey("parentIdTag") && *transaction->getParentIdTag() && !strcmp(idTagInfo["parenIdTag"] | "", transaction->getParentIdTag())) + { + endTransaction_authorized(idTag_capture.c_str(), reason_capture.empty() ? (const char*)nullptr : reason_capture.c_str()); + } + }); + + authorize->setOnTimeout([this, txNr_capture, beginTimestamp_capture, idTag_capture, reason_capture] () { + //Authorization timed out, do nothing + int32_t dtBeginTimestamp = 0; + if (!transaction || !context.getClock().delta(transaction->getBeginTimestamp(), beginTimestamp_capture, dtBeginTimestamp)) { + dtBeginTimestamp = 0; + } + + if (!transaction || transaction->getTxNr() != txNr_capture || dtBeginTimestamp != 0) { + MO_DBG_DEBUG("stale Authorize"); + return; + } + + //check local OCPP whitelist + const char *parentIdTag = nullptr; //locally stored parentIdTag + + #if MO_ENABLE_LOCAL_AUTH + if (auto authService = model.getAuthorizationService()) { + auto localAuth = authService->getLocalAuthorization(idTag_capture.c_str()); + + //check authorization status + if (localAuth && localAuth->getAuthorizationStatus() != AuthorizationStatus::Accepted) { + MO_DBG_DEBUG("local auth denied (%s)", idTag); + localAuth = nullptr; + } + + //check expiry + int32_t dtExpiryDate; + if (localAuth && localAuth->getExpiryDate() && clock.delta(clock.now(), *localAuth->getExpiryDate(), dtExpiryDate) && dtExpiryDate >= 0) { + MO_DBG_DEBUG("idTag %s local auth entry expired", idTag_capture.c_str()); + localAuth = nullptr; + } + + if (localAuth) { + parentIdTag = localAuth->getParentIdTag(); + } + } + #endif //MO_ENABLE_LOCAL_AUTH + + if (parentIdTag && *parentIdTag && !strcmp(parentIdTag, transaction->getParentIdTag())) { + MO_DBG_DEBUG("IdTag locally authorized (%s)", idTag_capture.c_str()); + endTransaction_authorized(idTag_capture.c_str(), reason_capture.empty() ? (const char*)nullptr : reason_capture.c_str()); + return; + } + + MO_DBG_DEBUG("Authorization timeout (%s), continue transaction", idTag_capture.c_str()); + updateTxNotification(MO_TxNotification_AuthorizationTimeout); + }); + + context.getMessageService().sendRequest(std::move(authorize)); + return true; + } else { + MO_DBG_INFO("endTransaction: idTag doesn't match"); + return false; + } + + return false; +} + +bool TransactionServiceEvse::endTransaction_authorized(const char *idTag, const char *reason) { + + if (!transaction || !transaction->isActive()) { + //transaction already ended / not active anymore + return false; + } + + MO_DBG_DEBUG("End session started by idTag %s", + transaction->getIdTag()); + + if (idTag && *idTag != '\0') { + transaction->setStopIdTag(idTag); + } + + if (reason) { + transaction->setStopReason(reason); + } + transaction->setInactive(); + commitTransaction(transaction); + return true; +} + +Transaction *TransactionServiceEvse::getTransaction() { + return transaction; +} + +void TransactionServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { + this->connectorPluggedInput = connectorPlugged; + this->connectorPluggedInputUserData = userData; +} + +void TransactionServiceEvse::setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData) { + this->evReadyInput = evReady; + this->evReadyInputUserData = userData; +} + +/* +void TransactionServiceEvse::addErrorCodeInput(const char* (*errorCodeInput)(unsigned int)) { + addErrorDataInput([] (unsigned int evseId, void *userData) -> MO_ErrorData { + auto errorCodeInput = reinterpret_cast(userData); + + MO_ErrorData res; + mo_ErrorData_init(&res); + mo_ErrorData_setErrorCode(&res, errorCodeInput(evseId)); + return res; + }, reinterpret_cast(errorCodeInput)); + addErrorDataInput([connectorErrorCode] () -> ErrorData { + return ErrorData(connectorErrorCode()); + }); +}*/ + +void TransactionServiceEvse::setStartTxReadyInput(bool (*startTxReady)(unsigned int, void*), void *userData) { + this->startTxReadyInput = startTxReady; + this->startTxReadyInputUserData = userData; +} + +void TransactionServiceEvse::setStopTxReadyInput(bool (*stopTxReady)(unsigned int, void*), void *userData) { + this->stopTxReadyInput = stopTxReady; + this->stopTxReadyInputUserData = userData; +} + +void TransactionServiceEvse::setTxNotificationOutput(void (*txNotificationOutput)(MO_TxNotification, unsigned int, void*), void *userData) { + this->txNotificationOutput = txNotificationOutput; + this->txNotificationOutputUserData = userData; +} + +void TransactionServiceEvse::updateTxNotification(MO_TxNotification event) { + if (txNotificationOutput) { + txNotificationOutput(event, evseId, txNotificationOutputUserData); + } +} + +unsigned int TransactionServiceEvse::getFrontRequestOpNr() { + + if (transactionFront && startTransactionInProgress) { + return transactionFront->getStartSync().getOpNr(); + } + + if (transactionFront && stopTransactionInProgress) { + return transactionFront->getStopSync().getOpNr(); + } + + /* + * Advance front transaction? + */ + unsigned int txSize = (txNrEnd + MO_TXNR_MAX - txNrFront) % MO_TXNR_MAX; + + if (transactionFront && txSize == 0) { + //catch edge case where txBack has been rolled back and txFront was equal to txBack + MO_DBG_DEBUG("collect front transaction %u-%u after tx rollback", evseId, transactionFront->getTxNr()); + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + if (transactionFront != transaction) { + delete transactionFront; + } + transactionFront = nullptr; + } + + for (unsigned int i = 0; i < txSize; i++) { + + if (!transactionFront) { + if (transaction && transaction->getTxNr() == txNrFront) { + //back == front + transactionFront = transaction; //share ownership + + } else if (filesystem) { + + auto txLoaded = new Transaction(evseId, txNrFront); + if (!txLoaded) { + MO_DBG_ERR("OOM"); + return NoOperation; + } + + bool hardFailure = false; + + auto ret = TransactionStore::load(filesystem, context, evseId, txNrFront, *txLoaded); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + //continue loading txMeterValues + break; + case FilesystemUtils::LoadStatus::FileNotFound: + break; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + hardFailure = true; + break; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("tx load failure"); + break; + } + + if (ret == FilesystemUtils::LoadStatus::Success) { + auto ret2 = MeterStore::load(filesystem, context, meteringServiceEvse->getMeterInputs(), evseId, txNrFront, txLoaded->getTxMeterValues()); + switch (ret2) { + case FilesystemUtils::LoadStatus::Success: + case FilesystemUtils::LoadStatus::FileNotFound: + transactionFront = txLoaded; + txLoaded = nullptr; + break; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + hardFailure = true; + break; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("tx load failure"); + MeterStore::remove(filesystem, evseId, txNrFront); + transactionFront = txLoaded; + txLoaded = nullptr; + break; + } + } + + delete txLoaded; + txLoaded = nullptr; + + if (hardFailure) { + return NoOperation; + } + + if (transactionFront) { + MO_DBG_VERBOSE("load front transaction %u-%u", evseId, transactionFront->getTxNr()); + } + } + } + + if (transactionFront && (transactionFront->isAborted() || transactionFront->isCompleted() || transactionFront->isSilent())) { + //advance front + MO_DBG_DEBUG("collect front transaction %u-%u", evseId, transactionFront->getTxNr()); + if (transactionFront != transaction) { + delete transactionFront; + } + transactionFront = nullptr; + txNrFront = (txNrFront + 1) % MO_TXNR_MAX; + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + } else { + //front is accurate. Done here + break; + } + } + + if (transactionFront) { + if (transactionFront->getStartSync().isRequested() && !transactionFront->getStartSync().isConfirmed()) { + return transactionFront->getStartSync().getOpNr(); + } + + if (transactionFront->getStopSync().isRequested() && !transactionFront->getStopSync().isConfirmed()) { + return transactionFront->getStopSync().getOpNr(); + } + } + + return NoOperation; +} + +std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { + + if (!connection->isConnected()) { + //offline behavior: pause sending messages and do not increment attempt counters + return nullptr; + } + + if (!clock.now().isUnixTime()) { + //before unix time is set, do not process send queue. The attempt timestamps require absolute time + return nullptr; + } + + if (transactionFront && !transactionFront->isSilent()) { + if (transactionFront->getStartSync().isRequested() && !transactionFront->getStartSync().isConfirmed()) { + //send StartTx? + + if (startTransactionInProgress) { + //ensure that only one StartTx request is being executed at the same time + return nullptr; + } + + bool cancelStartTx = false; + + //if not unix yet, convert (noop if unix time already) + auto convertTime = transactionFront->getStartTimestamp(); + if (!clock.toUnixTime(transactionFront->getStartTimestamp(), convertTime)) { + //time not set, cannot be restored anymore -> invalid tx + MO_DBG_ERR("cannot recover tx from previus run"); + + cancelStartTx = true; + } + transactionFront->setStartTimestamp(convertTime); + + if ((int)transactionFront->getStartSync().getAttemptNr() >= cService.transactionMessageAttemptsInt->getInt()) { + MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); + + cancelStartTx = true; + } + + if (cancelStartTx) { + transactionFront->setSilent(); + transactionFront->setInactive(); + commitTransaction(transactionFront); + + //clean up possible tx records + if (filesystem) { + MeterStore::remove(filesystem, evseId, transactionFront->getTxNr()); + } + //next getFrontRequestOpNr() call will collect transactionFront + return nullptr; + } + + int32_t dtLastAttempt; + if (!clock.delta(clock.now(), transactionFront->getStartSync().getAttemptTime(), dtLastAttempt)) { + MO_DBG_ERR("internal error"); + dtLastAttempt = 0; + } + + if (dtLastAttempt < transactionFront->getStartSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { + return nullptr; + } + + transactionFront->getStartSync().advanceAttemptNr(); + transactionFront->getStartSync().setAttemptTime(clock.now()); + commitTransaction(transactionFront); + + auto startTxOp = new StartTransaction(context, transactionFront); + if (!startTxOp) { + MO_DBG_ERR("OOM"); + return nullptr; + } + + auto startTx = makeRequest(context, startTxOp); + if (!startTx) { + MO_DBG_ERR("OOM"); + return nullptr; + } + startTx->setOnReceiveConf([this] (JsonObject response) { + //fetch authorization status from StartTransaction.conf() for user notification + commitTransaction(transactionFront); + startTransactionInProgress = false; + + const char* idTagInfoStatus = response["idTagInfo"]["status"] | "_Undefined"; + if (strcmp(idTagInfoStatus, "Accepted")) { + if (transactionFront == transaction) { //check if the front transaction is the currently active transaciton + updateTxNotification(MO_TxNotification_DeAuthorized); + } + } + }); + startTx->setOnAbort([this] () { + //shortcut to the attemptNr check above. Relevant if other operations block the queue while this StartTx is timing out + + startTransactionInProgress = false; + + if ((int)transactionFront->getStartSync().getAttemptNr() >= cService.transactionMessageAttemptsInt->getInt()) { + MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); + + transactionFront->setSilent(); + transactionFront->setInactive(); + commitTransaction(transactionFront); + + //clean up possible tx records + if (filesystem) { + MeterStore::remove(filesystem, evseId, transactionFront->getTxNr()); + } + //next getFrontRequestOpNr() call will collect transactionFront + } + }); + + startTransactionInProgress = true; + + return startTx; + } + + if (transactionFront->getStopSync().isRequested() && !transactionFront->getStopSync().isConfirmed()) { + //send StopTx? + + if (stopTransactionInProgress) { + //ensure that only one TransactionEvent request is being executed at the same time + return nullptr; + } + + if ((int)transactionFront->getStopSync().getAttemptNr() >= cService.transactionMessageAttemptsInt->getInt()) { + MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); + + transactionFront->setSilent(); + + //clean up possible tx records + if (filesystem) { + MeterStore::remove(filesystem, evseId, transactionFront->getTxNr()); + } + //next getFrontRequestOpNr() call will collect transactionFront + return nullptr; + } + + bool fallbackStopTime = false; + + //if not unix yet, convert (noop if unix time already) + auto convertTime = transactionFront->getStopTimestamp(); + if (!clock.toUnixTime(transactionFront->getStopTimestamp(), convertTime)) { + //fall back to start timestamp + MO_DBG_ERR("cannot recover tx from previus run"); + fallbackStopTime = true; + } + transactionFront->setStopTimestamp(convertTime); + + int32_t dt; + if (clock.delta(transactionFront->getStopTimestamp(), transactionFront->getStartTimestamp(), dt) && + dt < 0) { + MO_DBG_ERR("StopTx time is before StartTx time"); + fallbackStopTime = true; + } + + if (fallbackStopTime) { + transactionFront->setStopTimestamp(transactionFront->getStartTimestamp()); + auto timestamp = transactionFront->getStopTimestamp(); + clock.add(timestamp, 1); //set 1 second after start timestamp + transactionFront->setStopTimestamp(timestamp); + } + + //check timestamps in tx meter values + auto& txMeterValues = transactionFront->getTxMeterValues(); + for (size_t i = 0; i < txMeterValues.size(); i++) { + auto mv = txMeterValues[i]; + + char dummy [MO_JSONDATE_SIZE]; + if (!context.getClock().toJsonString(mv->timestamp, dummy, sizeof(dummy))) { + //try to fix timestamp + if (mv->readingContext == MO_ReadingContext_TransactionBegin) { + mv->timestamp = transaction->getStartTimestamp(); + } else if (mv->readingContext == MO_ReadingContext_TransactionEnd) { + mv->timestamp = transaction->getStopTimestamp(); + } else { + mv->timestamp = transaction->getStartTimestamp(); + clock.add(mv->timestamp, 1 + (int)i); + } + } + } + + int32_t dtLastAttempt; + if (!clock.delta(clock.now(), transactionFront->getStopSync().getAttemptTime(), dtLastAttempt)) { + MO_DBG_ERR("internal error"); + dtLastAttempt = 0; + } + + if (dtLastAttempt < transactionFront->getStopSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { + return nullptr; + } + + transactionFront->getStopSync().advanceAttemptNr(); + transactionFront->getStopSync().setAttemptTime(clock.now()); + commitTransaction(transactionFront); + + auto stopTxOp = new StopTransaction(context, transactionFront); + if (!stopTxOp) { + MO_DBG_ERR("OOM"); + return nullptr; + } + + auto stopTx = makeRequest(context, stopTxOp); + if (!stopTx) { + MO_DBG_ERR("OOM"); + return nullptr; + } + stopTx->setOnReceiveConf([this] (JsonObject response) { + commitTransaction(transactionFront); + stopTransactionInProgress = false; + }); + stopTx->setOnAbort([this] () { + stopTransactionInProgress = false; + + //shortcut to the attemptNr check above. Relevant if other operations block the queue while this StopTx is timing out + if ((int)transactionFront->getStopSync().getAttemptNr() >= cService.transactionMessageAttemptsInt->getInt()) { + MO_DBG_WARN("exceeded TransactionMessageAttempts. Discard transaction"); + + transactionFront->setSilent(); + transactionFront->setInactive(); + commitTransaction(transactionFront); + + //clean up possible tx records + if (filesystem) { + MeterStore::remove(filesystem, evseId, transactionFront->getTxNr()); + } + //next getFrontRequestOpNr() call will collect transactionFront + } + }); + + stopTransactionInProgress = true; + + return stopTx; + } + } + + return nullptr; +} + +unsigned int TransactionServiceEvse::getTxNrBeginHistory() { + return txNrBegin; +} + +unsigned int TransactionServiceEvse::getTxNrFront() { + return txNrFront; +} + +unsigned int TransactionServiceEvse::getTxNrEnd() { + return txNrEnd; +} + +Transaction *TransactionServiceEvse::getTransactionFront() { + return transactionFront; +} + +TransactionService::TransactionService(Context& context) : + MemoryManaged("v16.Transactions.TransactionService"), context(context) { + +} + +TransactionService::~TransactionService() { + for (unsigned int i = 0; i < MO_NUM_EVSEID; i++) { + delete evses[i]; + evses[i] = nullptr; + } +} + +bool TransactionService::setup() { + + auto configService = context.getModel16().getConfigurationService(); + if (!configService) { + MO_DBG_ERR("setup failure"); + return false; + } + + numEvseId = context.getModel16().getNumEvseId(); + + configService->declareConfiguration("NumberOfConnectors", numEvseId >= 1 ? numEvseId - 1 : 0, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); + + connectionTimeOutInt = configService->declareConfiguration("ConnectionTimeOut", 30); + configService->registerValidator("ConnectionTimeOut", VALIDATE_UNSIGNED_INT); + configService->registerValidator("MinimumStatusDuration", VALIDATE_UNSIGNED_INT); + stopTransactionOnInvalidIdBool = configService->declareConfiguration("StopTransactionOnInvalidId", true); + stopTransactionOnEVSideDisconnectBool = configService->declareConfiguration("StopTransactionOnEVSideDisconnect", true); + localPreAuthorizeBool = configService->declareConfiguration("LocalPreAuthorize", false); + localAuthorizeOfflineBool = configService->declareConfiguration("LocalAuthorizeOffline", true); + allowOfflineTxForUnknownIdBool = configService->declareConfiguration("AllowOfflineTxForUnknownId", false); + + //if the EVSE goes offline, can it continue to charge without sending StartTx / StopTx to the server when going online again? + silentOfflineTransactionsBool = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "SilentOfflineTransactions", false); + + //how long the EVSE tries the Authorize request before it enters offline mode + authorizationTimeoutInt = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "AuthorizationTimeout", 20); + configService->registerValidator(MO_CONFIG_EXT_PREFIX "AuthorizationTimeout", VALIDATE_UNSIGNED_INT); + + //FreeVend mode + freeVendActiveBool = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "FreeVendActive", false); + freeVendIdTagString = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "FreeVendIdTag", ""); + + txStartOnPowerPathClosedBool = configService->declareConfiguration(MO_CONFIG_EXT_PREFIX "TxStartOnPowerPathClosed", false); + + transactionMessageAttemptsInt = configService->declareConfiguration("TransactionMessageAttempts", 3); + configService->registerValidator("TransactionMessageAttempts", VALIDATE_UNSIGNED_INT); + transactionMessageRetryIntervalInt = configService->declareConfiguration("TransactionMessageRetryInterval", 60); + configService->registerValidator("TransactionMessageRetryInterval", VALIDATE_UNSIGNED_INT); + + if (!connectionTimeOutInt || + !stopTransactionOnInvalidIdBool || + !stopTransactionOnEVSideDisconnectBool || + !localPreAuthorizeBool || + !localAuthorizeOfflineBool || + !allowOfflineTxForUnknownIdBool || + !silentOfflineTransactionsBool || + !authorizationTimeoutInt || + !freeVendActiveBool || + !freeVendIdTagString || + !txStartOnPowerPathClosedBool || + !transactionMessageAttemptsInt || + !transactionMessageRetryIntervalInt) { + + MO_DBG_ERR("declareConfiguration failed"); + return false; + } + + context.getMessageService().registerOperation("ClearCache", [] (Context& context) -> Operation* { + return new ClearCache(context.getFilesystem());}); + + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("Authorize", nullptr, Authorize::writeMockConf); + context.getMessageService().registerOperation("StartTransaction", nullptr, StartTransaction::writeMockConf); + context.getMessageService().registerOperation("StopTransaction", nullptr, StopTransaction::writeMockConf); + #endif //MO_ENABLE_MOCK_SERVER + + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i) || !getEvse(i)->setup()) { + MO_DBG_ERR("connector init failure"); + return false; + } + } + + return true; +} + +void TransactionService::loop() { + for (unsigned int i = 0; i < numEvseId; i++) { + evses[i]->loop(); + } +} + +TransactionServiceEvse* TransactionService::getEvse(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); + return nullptr; + } + + if (!evses[evseId]) { + evses[evseId] = new TransactionServiceEvse(context, *this, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + + return evses[evseId]; +} diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.h b/src/MicroOcpp/Model/Transactions/TransactionService16.h new file mode 100644 index 00000000..340109cd --- /dev/null +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.h @@ -0,0 +1,176 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#ifndef MO_CONNECTOR_H +#define MO_CONNECTOR_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#if MO_ENABLE_V16 + +#ifndef MO_TXRECORD_SIZE +#define MO_TXRECORD_SIZE 4 //no. of tx to hold on flash storage +#endif + +#ifndef MO_REPORT_NOERROR +#define MO_REPORT_NOERROR 0 +#endif + +namespace MicroOcpp { + +class Context; +class Clock; +class Connection; +class Operation; + +namespace Ocpp16 { + +class Model; +class Configuration; +class MeteringServiceEvse; +class TransactionService; +class AvailabilityServiceEvse; + +class TransactionServiceEvse : public RequestQueue, public MemoryManaged { +private: + Context& context; + Clock& clock; + Model& model; + TransactionService& cService; + const unsigned int evseId; + + Connection *connection = nullptr; + MO_FilesystemAdapter *filesystem = nullptr; + MeteringServiceEvse *meteringServiceEvse = nullptr; + AvailabilityServiceEvse *availServiceEvse = nullptr; + + Transaction *transaction = nullptr; //shares ownership with transactionFront + + bool (*connectorPluggedInput)(unsigned int evseId, void *userData) = nullptr; + void *connectorPluggedInputUserData = nullptr; + bool (*evReadyInput)(unsigned int evseId, void *userData) = nullptr; + void *evReadyInputUserData = nullptr; + + bool (*startTxReadyInput)(unsigned int evseId, void *userData) = nullptr; //the StartTx request will be delayed while this Input is false + void *startTxReadyInputUserData = nullptr; + bool (*stopTxReadyInput)(unsigned int evseId, void *userData) = nullptr; //the StopTx request will be delayed while this Input is false + void *stopTxReadyInputUserData = nullptr; + + void (*txNotificationOutput)(MO_TxNotification, unsigned int evseId, void *userData) = nullptr; + void *txNotificationOutputUserData = nullptr; + + bool freeVendTrackPlugged = false; + + bool trackLoopExecute = false; //if loop has been executed once + + unsigned int txNrBegin = 0; //oldest (historical) transaction on flash. Has no function, but is useful for error diagnosis + unsigned int txNrFront = 0; //oldest transaction which is still queued to be sent to the server + unsigned int txNrEnd = 0; //one position behind newest transaction + + Transaction *transactionFront = nullptr; //shares ownership with transaction + bool startTransactionInProgress = false; + bool stopTransactionInProgress = false; + + bool commitTransaction(Transaction *transaction); +public: + TransactionServiceEvse(Context& context, TransactionService& conService, unsigned int evseId); + ~TransactionServiceEvse(); + + bool setup(); + + /* + * beginTransaction begins the transaction process which eventually leads to a StartTransaction + * request in the normal case. + * + * Returns true if the transaction process begins successfully + * Returns false if no transaction process begins due to this call (e.g. other transaction still running) + */ + bool beginTransaction(const char *idTag); + bool beginTransaction_authorized(const char *idTag, const char *parentIdTag = nullptr); + + /* + * End the current transaction process, if existing and not ended yet. This eventually leads to + * a StopTransaction request, if the transaction process has actually ended due to this call. It + * is safe to call this function at any time even if no transaction is running + */ + bool endTransaction(const char *idTag = nullptr, const char *reason = nullptr); + bool endTransaction_authorized(const char *idTag = nullptr, const char *reason = nullptr); + + Transaction *getTransaction(); + + Transaction *allocateTransaction(); + + void setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData); + void setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData); + void setEvseReadyInput(bool (*evseReady)(unsigned int, void*), void *userData); + + void loop(); + + bool ocppPermitsCharge(); + + void setStartTxReadyInput(bool (*startTxReady)(unsigned int, void*), void *userData); + void setStopTxReadyInput(bool (*stopTxReady)(unsigned int, void*), void *userData); + + void setTxNotificationOutput(void (*txNotificationOutput)(MO_TxNotification, unsigned int, void*), void *userData); + void updateTxNotification(MO_TxNotification event); + + unsigned int getFrontRequestOpNr() override; + std::unique_ptr fetchFrontRequest() override; + + unsigned int getTxNrBeginHistory(); //if getTxNrBeginHistory() != getTxNrFront(), then return value is the txNr of the oldest tx history entry. If equal to getTxNrFront(), then the history is empty + unsigned int getTxNrFront(); //if getTxNrEnd() != getTxNrFront(), then return value is the txNr of the oldest transaction queued to be sent to the server. If equal to getTxNrEnd(), then there is no tx to be sent to the server + unsigned int getTxNrEnd(); //upper limit for the range of valid txNrs + + Transaction *getTransactionFront(); +}; + +class TransactionService : public MemoryManaged { +private: + Context& context; + + TransactionServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; + + Configuration *connectionTimeOutInt = nullptr; //in seconds + Configuration *stopTransactionOnInvalidIdBool = nullptr; + Configuration *stopTransactionOnEVSideDisconnectBool = nullptr; + Configuration *localPreAuthorizeBool = nullptr; + Configuration *localAuthorizeOfflineBool = nullptr; + Configuration *allowOfflineTxForUnknownIdBool = nullptr; + + Configuration *silentOfflineTransactionsBool = nullptr; + Configuration *authorizationTimeoutInt = nullptr; //in seconds + Configuration *freeVendActiveBool = nullptr; + Configuration *freeVendIdTagString = nullptr; + + Configuration *txStartOnPowerPathClosedBool = nullptr; // this postpones the tx start point to when evReadyInput becomes true + + Configuration *transactionMessageAttemptsInt = nullptr; + Configuration *transactionMessageRetryIntervalInt = nullptr; +public: + TransactionService(Context& context); + + ~TransactionService(); + + bool setup(); + + void loop(); + + TransactionServiceEvse *getEvse(unsigned int evseId); + +friend class TransactionServiceEvse; +}; + +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 +#endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionService.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp similarity index 60% rename from src/MicroOcpp/Model/Transactions/TransactionService.cpp rename to src/MicroOcpp/Model/Transactions/TransactionService201.cpp index d0e58e1f..e3ac0a16 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -2,19 +2,15 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - -#include +#include #include -#include -#include +#include #include #include #include +#include #include #include #include @@ -23,6 +19,8 @@ #include #include +#if MO_ENABLE_V201 + #ifndef MO_TX_CLEAN_ABORTED #define MO_TX_CLEAN_ABORTED 1 #endif @@ -30,59 +28,75 @@ using namespace MicroOcpp; using namespace MicroOcpp::Ocpp201; -TransactionService::Evse::Evse(Context& context, TransactionService& txService, Ocpp201::TransactionStoreEvse& txStore, unsigned int evseId) : +TransactionServiceEvse::TransactionServiceEvse(Context& context, TransactionService& txService, TransactionStoreEvse& txStore, unsigned int evseId) : MemoryManaged("v201.Transactions.TransactionServiceEvse"), context(context), + clock(context.getClock()), txService(txService), txStore(txStore), evseId(evseId) { - context.getRequestQueue().addSendQueue(this); //register at RequestQueue as Request emitter +} - txStore.discoverStoredTx(txNrBegin, txNrEnd); //initializes txNrBegin and txNrEnd - txNrFront = txNrBegin; - MO_DBG_DEBUG("found %u transactions for evseId %u. Internal range from %u to %u (exclusive)", (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT, evseId, txNrBegin, txNrEnd); +TransactionServiceEvse::~TransactionServiceEvse() { - unsigned int txNrLatest = (txNrEnd + MAX_TX_CNT - 1) % MAX_TX_CNT; //txNr of the most recent tx on flash - transaction = txStore.loadTransaction(txNrLatest); //returns nullptr if txNrLatest does not exist on flash } -TransactionService::Evse::~Evse() { - +bool TransactionServiceEvse::setup() { + context.getMessageService().addSendQueue(this); //register at RequestQueue as Request emitter + + auto meteringService = context.getModel201().getMeteringService(); + meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; + if (!meteringEvse) { + MO_DBG_ERR("depends on MeteringService"); + return false; + } + + if (txService.filesystem) { + + char fnPrefix [MO_MAX_PATH_SIZE]; + snprintf(fnPrefix, sizeof(fnPrefix), "tx201-%u-", evseId); + + if (!FilesystemUtils::loadRingIndex(txService.filesystem, fnPrefix, MO_TXNR_MAX, &txNrBegin, &txNrEnd)) { + MO_DBG_ERR("failure to init tx index"); + return false; + } + + txNrFront = txNrBegin; + MO_DBG_DEBUG("found %u transactions for evseId %u. Internal range from %u to %u (exclusive)", (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX, evseId, txNrBegin, txNrEnd); + + unsigned int txNrLatest = (txNrEnd + MO_TXNR_MAX - 1) % MO_TXNR_MAX; //txNr of the most recent tx on flash + transaction = txStore.loadTransaction(txNrLatest); //returns nullptr if txNrLatest does not exist on flash + } + + return true; } -bool TransactionService::Evse::beginTransaction() { +bool TransactionServiceEvse::beginTransaction() { if (transaction) { MO_DBG_ERR("transaction still running"); return false; } - std::unique_ptr tx; + std::unique_ptr tx; - char txId [sizeof(Ocpp201::Transaction::transactionId)]; + char txId [sizeof(Transaction::transactionId)]; - //simple clock-based hash - int v = context.getModel().getClock().now() - Timestamp(2020,0,0,0,0,0); - unsigned int h = v; - h += mocpp_tick_ms(); - h *= 749572633U; - h %= 24593209U; - for (size_t i = 0; i < sizeof(tx->transactionId) - 3; i += 2) { - snprintf(txId + i, 3, "%02X", (uint8_t)h); - h *= 749572633U; - h %= 24593209U; + if (!UuidUtils::generateUUID(context.getRngCb(), txId, sizeof(txId))) { + MO_DBG_ERR("UUID failure"); + return false; } //clean possible aborted tx unsigned int txr = txNrEnd; - unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; + unsigned int txSize = (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX; for (unsigned int i = 0; i < txSize; i++) { - txr = (txr + MAX_TX_CNT - 1) % MAX_TX_CNT; //decrement by 1 + txr = (txr + MO_TXNR_MAX - 1) % MO_TXNR_MAX; //decrement by 1 - std::unique_ptr intermediateTx; + std::unique_ptr intermediateTx; - Ocpp201::Transaction *txhist = nullptr; + Transaction *txhist = nullptr; if (transaction && transaction->txNr == txr) { txhist = transaction.get(); } else if (txFront && txFront->txNr == txr) { @@ -111,10 +125,10 @@ bool TransactionService::Evse::beginTransaction() { } } - txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; //refresh after cleaning txs + txSize = (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX; //refresh after cleaning txs //try to create new transaction - if (txSize < MO_TXRECORD_SIZE) { + if (txSize < MO_TXRECORD_SIZE_V201) { tx = txStore.createTransaction(txNrEnd, txId); } @@ -122,7 +136,7 @@ bool TransactionService::Evse::beginTransaction() { //could not create transaction - now, try to replace tx history entry unsigned int txl = txNrBegin; - txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; + txSize = (txNrEnd + MO_TXNR_MAX - txNrBegin) % MO_TXNR_MAX; for (unsigned int i = 0; i < txSize; i++) { @@ -132,9 +146,9 @@ bool TransactionService::Evse::beginTransaction() { } //no transaction allocated, delete history entry to make space - std::unique_ptr intermediateTx; + std::unique_ptr intermediateTx; - Ocpp201::Transaction *txhist = nullptr; + Transaction *txhist = nullptr; if (transaction && transaction->txNr == txl) { txhist = transaction.get(); } else if (txFront && txFront->txNr == txl) { @@ -149,7 +163,7 @@ bool TransactionService::Evse::beginTransaction() { //yes, remove if (txStore.remove(txl)) { - txNrBegin = (txl + 1) % MAX_TX_CNT; + txNrBegin = (txl + 1) % MO_TXNR_MAX; if (txNrFront == txl) { txNrFront = txNrBegin; } @@ -168,13 +182,13 @@ bool TransactionService::Evse::beginTransaction() { } txl++; - txl %= MAX_TX_CNT; + txl %= MO_TXNR_MAX; } } if (!tx) { //couldn't create normal transaction -> check if to start charging without real transaction - if (txService.silentOfflineTransactionsBool && txService.silentOfflineTransactionsBool->getBool()) { + if (txService.silentOfflineTransactionsBool->getBool()) { //try to handle charging session without sending StartTx or StopTx to the server tx = txStore.createTransaction(txNrEnd, txId); @@ -190,7 +204,7 @@ bool TransactionService::Evse::beginTransaction() { return false; } - tx->beginTimestamp = context.getModel().getClock().now(); + tx->beginTimestamp = clock.now(); if (!txStore.commit(tx.get())) { MO_DBG_ERR("fs error"); @@ -199,14 +213,14 @@ bool TransactionService::Evse::beginTransaction() { transaction = std::move(tx); - txNrEnd = (txNrEnd + 1) % MAX_TX_CNT; + txNrEnd = (txNrEnd + 1) % MO_TXNR_MAX; MO_DBG_DEBUG("advance txNrEnd %u-%u", evseId, txNrEnd); MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); return true; } -bool TransactionService::Evse::endTransaction(Ocpp201::Transaction::StoppedReason stoppedReason = Ocpp201::Transaction::StoppedReason::Other, Ocpp201::TransactionEventTriggerReason stopTrigger = Ocpp201::TransactionEventTriggerReason::AbnormalCondition) { +bool TransactionServiceEvse::endTransaction(MO_TxStoppedReason stoppedReason = MO_TxStoppedReason_Other, MO_TxEventTriggerReason stopTrigger = MO_TxEventTriggerReason_AbnormalCondition) { if (!transaction || !transaction->active) { //transaction already ended / not active anymore @@ -224,7 +238,23 @@ bool TransactionService::Evse::endTransaction(Ocpp201::Transaction::StoppedReaso return true; } -void TransactionService::Evse::loop() { +TransactionEventData::ChargingState TransactionServiceEvse::getChargingState() { + auto res = TransactionEventData::ChargingState::Idle; + if (connectorPluggedInput && !connectorPluggedInput(evseId, connectorPluggedInputUserData)) { + res = TransactionEventData::ChargingState::Idle; + } else if (!transaction || !transaction->isAuthorizationActive || !transaction->isAuthorized) { + res = TransactionEventData::ChargingState::EVConnected; + } else if (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData)) { + res = TransactionEventData::ChargingState::SuspendedEVSE; + } else if (evReadyInput && !evReadyInput(evseId, evReadyInputUserData)) { + res = TransactionEventData::ChargingState::SuspendedEV; + } else if (ocppPermitsCharge()) { + res = TransactionEventData::ChargingState::Charging; + } + return res; +} + +void TransactionServiceEvse::loop() { if (transaction && !transaction->active && !transaction->started) { MO_DBG_DEBUG("collect aborted transaction %u-%s", evseId, transaction->transactionId); @@ -247,22 +277,27 @@ void TransactionService::Evse::loop() { // tx-related behavior if (transaction) { if (connectorPluggedInput) { - if (connectorPluggedInput()) { + if (connectorPluggedInput(evseId, connectorPluggedInputUserData)) { // if cable has been plugged at least once, EVConnectionTimeout will never get triggered transaction->evConnectionTimeoutListen = false; } + int32_t dtTxBegin; + if (!clock.delta(clock.now(), transaction->beginTimestamp, dtTxBegin)) { + dtTxBegin = txService.evConnectionTimeOutInt->getInt(); + } + if (transaction->active && transaction->evConnectionTimeoutListen && - transaction->beginTimestamp > MIN_TIME && - txService.evConnectionTimeOutInt && txService.evConnectionTimeOutInt->getInt() > 0 && - !connectorPluggedInput() && - context.getModel().getClock().now() - transaction->beginTimestamp >= txService.evConnectionTimeOutInt->getInt()) { + transaction->beginTimestamp.isDefined() && + txService.evConnectionTimeOutInt->getInt() > 0 && + !connectorPluggedInput(evseId, connectorPluggedInputUserData) && + dtTxBegin >= txService.evConnectionTimeOutInt->getInt()) { MO_DBG_INFO("Session mngt: timeout"); - endTransaction(Ocpp201::Transaction::StoppedReason::Timeout, TransactionEventTriggerReason::EVConnectTimeout); + endTransaction(MO_TxStoppedReason_Timeout, MO_TxEventTriggerReason_EVConnectTimeout); - updateTxNotification(TxNotification_ConnectionTimeout); + updateTxNotification(MO_TxNotification_ConnectionTimeout); } if (transaction->active && @@ -272,7 +307,7 @@ void TransactionService::Evse::loop() { txService.isTxStopPoint(TxStartStopPoint::Authorized) || txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed))) { MO_DBG_INFO("Session mngt: Deauthorized before start"); - endTransaction(Ocpp201::Transaction::StoppedReason::DeAuthorized, TransactionEventTriggerReason::Deauthorized); + endTransaction(MO_TxStoppedReason_DeAuthorized, MO_TxEventTriggerReason_Deauthorized); } } } @@ -284,8 +319,8 @@ void TransactionService::Evse::loop() { { // stop tx? - TransactionEventTriggerReason triggerReason = TransactionEventTriggerReason::UNDEFINED; - Ocpp201::Transaction::StoppedReason stoppedReason = Ocpp201::Transaction::StoppedReason::UNDEFINED; + MO_TxEventTriggerReason triggerReason = MO_TxEventTriggerReason_UNDEFINED; + MO_TxStoppedReason stoppedReason = MO_TxStoppedReason_UNDEFINED; if (transaction && !transaction->active) { // tx ended via endTransaction @@ -294,37 +329,37 @@ void TransactionService::Evse::loop() { stoppedReason = transaction->stoppedReason; } else if ((txService.isTxStopPoint(TxStartStopPoint::EVConnected) || txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) && - connectorPluggedInput && !connectorPluggedInput() && + connectorPluggedInput && !connectorPluggedInput(evseId, connectorPluggedInputUserData) && (txService.stopTxOnEVSideDisconnectBool->getBool() || !transaction || !transaction->started)) { txStopCondition = true; - triggerReason = TransactionEventTriggerReason::EVCommunicationLost; - stoppedReason = Ocpp201::Transaction::StoppedReason::EVDisconnected; + triggerReason = MO_TxEventTriggerReason_EVCommunicationLost; + stoppedReason = MO_TxStoppedReason_EVDisconnected; } else if ((txService.isTxStopPoint(TxStartStopPoint::Authorized) || txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) && (!transaction || !transaction->isAuthorizationActive)) { // user revoked authorization (or EV or any "local" entity) txStopCondition = true; - triggerReason = TransactionEventTriggerReason::StopAuthorized; - stoppedReason = Ocpp201::Transaction::StoppedReason::Local; + triggerReason = MO_TxEventTriggerReason_StopAuthorized; + stoppedReason = MO_TxStoppedReason_Local; } else if (txService.isTxStopPoint(TxStartStopPoint::EnergyTransfer) && - evReadyInput && !evReadyInput()) { + evReadyInput && !evReadyInput(evseId, evReadyInputUserData)) { txStopCondition = true; - triggerReason = TransactionEventTriggerReason::ChargingStateChanged; - stoppedReason = Ocpp201::Transaction::StoppedReason::StoppedByEV; + triggerReason = MO_TxEventTriggerReason_ChargingStateChanged; + stoppedReason = MO_TxStoppedReason_StoppedByEV; } else if (txService.isTxStopPoint(TxStartStopPoint::EnergyTransfer) && (evReadyInput || evseReadyInput) && // at least one of the two defined - !(evReadyInput && evReadyInput()) && - !(evseReadyInput && evseReadyInput())) { + !(evReadyInput && evReadyInput(evseId, evReadyInputUserData)) && + !(evseReadyInput && evseReadyInput(evseId, evseReadyInputUserData))) { txStopCondition = true; - triggerReason = TransactionEventTriggerReason::ChargingStateChanged; - stoppedReason = Ocpp201::Transaction::StoppedReason::Other; + triggerReason = MO_TxEventTriggerReason_ChargingStateChanged; + stoppedReason = MO_TxStoppedReason_Other; } else if (txService.isTxStopPoint(TxStartStopPoint::Authorized) && transaction && transaction->isDeauthorized && txService.stopTxOnInvalidIdBool->getBool()) { // OCPP server rejected authorization txStopCondition = true; - triggerReason = TransactionEventTriggerReason::Deauthorized; - stoppedReason = Ocpp201::Transaction::StoppedReason::DeAuthorized; + triggerReason = MO_TxEventTriggerReason_Deauthorized; + stoppedReason = MO_TxStoppedReason_DeAuthorized; } if (txStopCondition && @@ -336,7 +371,7 @@ void TransactionService::Evse::loop() { if (transaction && transaction->started && !transaction->stopped && !transaction->active && - (!stopTxReadyInput || stopTxReadyInput())) { + (!stopTxReadyInput || stopTxReadyInput(evseId, stopTxReadyInputUserData))) { // yes, stop running tx txEvent = txStore.createTransactionEvent(*transaction); @@ -358,41 +393,41 @@ void TransactionService::Evse::loop() { bool txStartCondition = false; - TransactionEventTriggerReason triggerReason = TransactionEventTriggerReason::UNDEFINED; + MO_TxEventTriggerReason triggerReason = MO_TxEventTriggerReason_UNDEFINED; // tx should be started? if (txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) && - (!connectorPluggedInput || connectorPluggedInput()) && + (!connectorPluggedInput || connectorPluggedInput(evseId, connectorPluggedInputUserData)) && transaction && transaction->isAuthorizationActive && transaction->isAuthorized) { txStartCondition = true; if (transaction->remoteStartId >= 0) { - triggerReason = TransactionEventTriggerReason::RemoteStart; + triggerReason = MO_TxEventTriggerReason_RemoteStart; } else { - triggerReason = TransactionEventTriggerReason::CablePluggedIn; + triggerReason = MO_TxEventTriggerReason_CablePluggedIn; } } else if (txService.isTxStartPoint(TxStartStopPoint::Authorized) && transaction && transaction->isAuthorizationActive && transaction->isAuthorized) { txStartCondition = true; if (transaction->remoteStartId >= 0) { - triggerReason = TransactionEventTriggerReason::RemoteStart; + triggerReason = MO_TxEventTriggerReason_RemoteStart; } else { - triggerReason = TransactionEventTriggerReason::Authorized; + triggerReason = MO_TxEventTriggerReason_Authorized; } } else if (txService.isTxStartPoint(TxStartStopPoint::EVConnected) && - connectorPluggedInput && connectorPluggedInput()) { + connectorPluggedInput && connectorPluggedInput(evseId, connectorPluggedInputUserData)) { txStartCondition = true; - triggerReason = TransactionEventTriggerReason::CablePluggedIn; + triggerReason = MO_TxEventTriggerReason_CablePluggedIn; } else if (txService.isTxStartPoint(TxStartStopPoint::EnergyTransfer) && (evReadyInput || evseReadyInput) && // at least one of the two defined - (!evReadyInput || evReadyInput()) && - (!evseReadyInput || evseReadyInput())) { + (!evReadyInput || evReadyInput(evseId, evReadyInputUserData)) && + (!evseReadyInput || evseReadyInput(evseId, evseReadyInputUserData))) { txStartCondition = true; - triggerReason = TransactionEventTriggerReason::ChargingStateChanged; + triggerReason = MO_TxEventTriggerReason_ChargingStateChanged; } if (txStartCondition && (!transaction || (transaction->active && !transaction->started)) && - (!startTxReadyInput || startTxReadyInput())) { + (!startTxReadyInput || startTxReadyInput(evseId, startTxReadyInputUserData))) { // start tx if (!transaction) { @@ -417,38 +452,33 @@ void TransactionService::Evse::loop() { } } - TransactionEventData::ChargingState chargingState = TransactionEventData::ChargingState::Idle; - if (connectorPluggedInput && !connectorPluggedInput()) { - chargingState = TransactionEventData::ChargingState::Idle; - } else if (!transaction || !transaction->isAuthorizationActive || !transaction->isAuthorized) { - chargingState = TransactionEventData::ChargingState::EVConnected; - } else if (evseReadyInput && !evseReadyInput()) { - chargingState = TransactionEventData::ChargingState::SuspendedEVSE; - } else if (evReadyInput && !evReadyInput()) { - chargingState = TransactionEventData::ChargingState::SuspendedEV; - } else if (ocppPermitsCharge()) { - chargingState = TransactionEventData::ChargingState::Charging; - } - //General Metering behavior. There is another section for TxStarted, Updated and TxEnded MeterValues - std::unique_ptr mvTxUpdated; + std::unique_ptr mvTxUpdated; if (transaction) { - if (txService.sampledDataTxUpdatedInterval && txService.sampledDataTxUpdatedInterval->getInt() > 0 && mocpp_tick_ms() - transaction->lastSampleTimeTxUpdated >= (unsigned long)txService.sampledDataTxUpdatedInterval->getInt() * 1000UL) { - transaction->lastSampleTimeTxUpdated = mocpp_tick_ms(); - auto meteringService = context.getModel().getMeteringServiceV201(); - auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; - mvTxUpdated = meteringEvse ? meteringEvse->takeTxUpdatedMeterValue() : nullptr; + int32_t dtLastSampleTimeTxUpdated; + if (!transaction->lastSampleTimeTxUpdated.isDefined() || !clock.delta(clock.getUptime(), transaction->lastSampleTimeTxUpdated, dtLastSampleTimeTxUpdated)) { + dtLastSampleTimeTxUpdated = 0; + transaction->lastSampleTimeTxUpdated = clock.getUptime(); + } + + if (txService.sampledDataTxUpdatedInterval->getInt() > 0 && dtLastSampleTimeTxUpdated >= txService.sampledDataTxUpdatedInterval->getInt()) { + transaction->lastSampleTimeTxUpdated = clock.getUptime(); + mvTxUpdated = meteringEvse->takeTxUpdatedMeterValue(); + } + + int32_t dtLastSampleTimeTxEnded; + if (!transaction->lastSampleTimeTxEnded.isDefined() || !clock.delta(clock.getUptime(), transaction->lastSampleTimeTxEnded, dtLastSampleTimeTxEnded)) { + dtLastSampleTimeTxEnded = 0; + transaction->lastSampleTimeTxEnded = clock.getUptime(); } if (transaction->started && !transaction->stopped && - txService.sampledDataTxEndedInterval && txService.sampledDataTxEndedInterval->getInt() > 0 && - mocpp_tick_ms() - transaction->lastSampleTimeTxEnded >= (unsigned long)txService.sampledDataTxEndedInterval->getInt() * 1000UL) { - transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); - auto meteringService = context.getModel().getMeteringServiceV201(); - auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; - auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_SamplePeriodic) : nullptr; + txService.sampledDataTxEndedInterval->getInt() > 0 && + dtLastSampleTimeTxEnded >= txService.sampledDataTxEndedInterval->getInt()) { + transaction->lastSampleTimeTxEnded = clock.getUptime(); + auto mvTxEnded = meteringEvse->takeTxEndedMeterValue(MO_ReadingContext_SamplePeriodic); if (mvTxEnded) { transaction->addSampledDataTxEnded(std::move(mvTxEnded)); } @@ -460,11 +490,13 @@ void TransactionService::Evse::loop() { bool txUpdateCondition = false; - TransactionEventTriggerReason triggerReason = TransactionEventTriggerReason::UNDEFINED; + MO_TxEventTriggerReason triggerReason = MO_TxEventTriggerReason_UNDEFINED; + + auto chargingState = getChargingState(); if (chargingState != trackChargingState) { txUpdateCondition = true; - triggerReason = TransactionEventTriggerReason::ChargingStateChanged; + triggerReason = MO_TxEventTriggerReason_ChargingStateChanged; transaction->notifyChargingState = true; } trackChargingState = chargingState; @@ -473,28 +505,28 @@ void TransactionService::Evse::loop() { transaction->trackAuthorized = true; txUpdateCondition = true; if (transaction->remoteStartId >= 0) { - triggerReason = TransactionEventTriggerReason::RemoteStart; + triggerReason = MO_TxEventTriggerReason_RemoteStart; } else { - triggerReason = TransactionEventTriggerReason::Authorized; + triggerReason = MO_TxEventTriggerReason_Authorized; } - } else if (connectorPluggedInput && connectorPluggedInput() && !transaction->trackEvConnected) { + } else if (connectorPluggedInput && connectorPluggedInput(evseId, connectorPluggedInputUserData) && !transaction->trackEvConnected) { transaction->trackEvConnected = true; txUpdateCondition = true; - triggerReason = TransactionEventTriggerReason::CablePluggedIn; - } else if (connectorPluggedInput && !connectorPluggedInput() && transaction->trackEvConnected) { + triggerReason = MO_TxEventTriggerReason_CablePluggedIn; + } else if (connectorPluggedInput && !connectorPluggedInput(evseId, connectorPluggedInputUserData) && transaction->trackEvConnected) { transaction->trackEvConnected = false; txUpdateCondition = true; - triggerReason = TransactionEventTriggerReason::EVCommunicationLost; + triggerReason = MO_TxEventTriggerReason_EVCommunicationLost; } else if (!(transaction->isAuthorizationActive && transaction->isAuthorized) && transaction->trackAuthorized) { transaction->trackAuthorized = false; txUpdateCondition = true; - triggerReason = TransactionEventTriggerReason::StopAuthorized; + triggerReason = MO_TxEventTriggerReason_StopAuthorized; } else if (mvTxUpdated) { txUpdateCondition = true; - triggerReason = TransactionEventTriggerReason::MeterValuePeriodic; - } else if (evReadyInput && evReadyInput() && !transaction->trackPowerPathClosed) { + triggerReason = MO_TxEventTriggerReason_MeterValuePeriodic; + } else if (evReadyInput && evReadyInput(evseId, evReadyInputUserData) && !transaction->trackPowerPathClosed) { transaction->trackPowerPathClosed = true; - } else if (evReadyInput && !evReadyInput() && transaction->trackPowerPathClosed) { + } else if (evReadyInput && !evReadyInput(evseId, evReadyInputUserData) && transaction->trackPowerPathClosed) { transaction->trackPowerPathClosed = false; } @@ -513,9 +545,9 @@ void TransactionService::Evse::loop() { } if (txEvent) { - txEvent->timestamp = context.getModel().getClock().now(); + txEvent->timestamp = clock.now(); if (transaction->notifyChargingState) { - txEvent->chargingState = chargingState; + txEvent->chargingState = getChargingState(); transaction->notifyChargingState = false; } if (transaction->notifyEvseId) { @@ -527,26 +559,22 @@ void TransactionService::Evse::loop() { transaction->notifyRemoteStartId = false; } if (txEvent->eventType == TransactionEventData::Type::Started) { - auto meteringService = context.getModel().getMeteringServiceV201(); - auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; - auto mvTxStarted = meteringEvse ? meteringEvse->takeTxStartedMeterValue() : nullptr; + auto mvTxStarted = meteringEvse->takeTxStartedMeterValue(); if (mvTxStarted) { txEvent->meterValue.push_back(std::move(mvTxStarted)); } - auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_TransactionBegin) : nullptr; + auto mvTxEnded = meteringEvse->takeTxEndedMeterValue(MO_ReadingContext_TransactionBegin); if (mvTxEnded) { transaction->addSampledDataTxEnded(std::move(mvTxEnded)); } - transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); - transaction->lastSampleTimeTxUpdated = mocpp_tick_ms(); + transaction->lastSampleTimeTxEnded = clock.getUptime(); + transaction->lastSampleTimeTxUpdated = clock.getUptime(); } else if (txEvent->eventType == TransactionEventData::Type::Ended) { - auto meteringService = context.getModel().getMeteringServiceV201(); - auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr; - auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_TransactionEnd) : nullptr; + auto mvTxEnded = meteringEvse->takeTxEndedMeterValue(MO_ReadingContext_TransactionEnd); if (mvTxEnded) { transaction->addSampledDataTxEnded(std::move(mvTxEnded)); } - transaction->lastSampleTimeTxEnded = mocpp_tick_ms(); + transaction->lastSampleTimeTxEnded = clock.getUptime(); } if (mvTxUpdated) { txEvent->meterValue.push_back(std::move(mvTxUpdated)); @@ -564,13 +592,14 @@ void TransactionService::Evse::loop() { if (txEvent) { if (txEvent->eventType == TransactionEventData::Type::Started) { transaction->started = true; + transaction->startTimestamp = txEvent->timestamp; } else if (txEvent->eventType == TransactionEventData::Type::Ended) { transaction->stopped = true; } } if (txEvent) { - txEvent->opNr = context.getRequestQueue().getNextOpNr(); + txEvent->opNr = context.getMessageService().getNextOpNr(); MO_DBG_DEBUG("enqueueing new txEvent at opNr %u", txEvent->opNr); } @@ -580,9 +609,9 @@ void TransactionService::Evse::loop() { if (txEvent) { if (txEvent->eventType == TransactionEventData::Type::Started) { - updateTxNotification(TxNotification_StartTx); + updateTxNotification(MO_TxNotification_StartTx); } else if (txEvent->eventType == TransactionEventData::Type::Ended) { - updateTxNotification(TxNotification_StartTx); + updateTxNotification(MO_TxNotification_StartTx); } } @@ -602,29 +631,43 @@ void TransactionService::Evse::loop() { } } -void TransactionService::Evse::setConnectorPluggedInput(std::function connectorPlugged) { +void TransactionServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { this->connectorPluggedInput = connectorPlugged; + this->connectorPluggedInputUserData = userData; +} + +void TransactionServiceEvse::setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData) { + this->evReadyInput = evReady; + this->evReadyInputUserData = userData; } -void TransactionService::Evse::setEvReadyInput(std::function evRequestsEnergy) { - this->evReadyInput = evRequestsEnergy; +void TransactionServiceEvse::setEvseReadyInput(bool (*evseReady)(unsigned int, void*), void *userData) { + this->evseReadyInput = evseReady; + this->evseReadyInputUserData = userData; } -void TransactionService::Evse::setEvseReadyInput(std::function connectorEnergized) { - this->evseReadyInput = connectorEnergized; +void TransactionServiceEvse::setStartTxReadyInput(bool (*startTxReady)(unsigned int, void*), void *userData) { + this->startTxReadyInput = startTxReady; + this->startTxReadyInputUserData = userData; } -void TransactionService::Evse::setTxNotificationOutput(std::function txNotificationOutput) { +void TransactionServiceEvse::setStopTxReadyInput(bool (*stopTxReady)(unsigned int, void*), void *userData) { + this->stopTxReadyInput = stopTxReady; + this->stopTxReadyInputUserData = userData; +} + +void TransactionServiceEvse::setTxNotificationOutput(void (*txNotificationOutput)(MO_TxNotification, unsigned int, void*), void *userData) { this->txNotificationOutput = txNotificationOutput; + this->txNotificationOutputUserData = userData; } -void TransactionService::Evse::updateTxNotification(TxNotification event) { +void TransactionServiceEvse::updateTxNotification(MO_TxNotification event) { if (txNotificationOutput) { - txNotificationOutput(transaction.get(), event); + txNotificationOutput(event, evseId, txNotificationOutputUserData); } } -bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validateIdToken) { +bool TransactionServiceEvse::beginAuthorization(IdToken idToken, bool validateIdToken, IdToken groupIdToken) { MO_DBG_DEBUG("begin auth: %s", idToken.get()); if (transaction && transaction->isAuthorizationActive) { @@ -632,6 +675,10 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate return false; } + if (*groupIdToken.get()) { + MO_DBG_WARN("groupIdToken not supported"); + } + if (!transaction) { beginTransaction(); if (!transaction) { @@ -645,10 +692,10 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate transaction->isAuthorizationActive = true; transaction->idToken = idToken; - transaction->beginTimestamp = context.getModel().getClock().now(); + transaction->beginTimestamp = clock.now(); if (validateIdToken) { - auto authorize = makeRequest(new Authorize(context.getModel(), idToken)); + auto authorize = makeRequest(context, new Authorize(context.getModel201(), idToken)); if (!authorize) { // OOM abortTransaction(); @@ -658,7 +705,7 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate char txId [sizeof(transaction->transactionId)]; //capture txId to check if transaction reference is still the same snprintf(txId, sizeof(txId), "%s", transaction->transactionId); - authorize->setOnReceiveConfListener([this, txId] (JsonObject response) { + authorize->setOnReceiveConf([this, txId] (JsonObject response) { auto tx = getTransaction(); if (!tx || strcmp(tx->transactionId, txId)) { MO_DBG_INFO("dangling Authorize -- discard"); @@ -670,7 +717,7 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate tx->isDeauthorized = true; txStore.commit(tx); - updateTxNotification(TxNotification_AuthorizationRejected); + updateTxNotification(MO_TxNotification_AuthorizationRejected); return; } @@ -679,9 +726,9 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate tx->notifyIdToken = true; txStore.commit(tx); - updateTxNotification(TxNotification_Authorized); + updateTxNotification(MO_TxNotification_Authorized); }); - authorize->setOnAbortListener([this, txId] () { + authorize->setOnAbort([this, txId] () { auto tx = getTransaction(); if (!tx || strcmp(tx->transactionId, txId)) { MO_DBG_INFO("dangling Authorize -- discard"); @@ -692,28 +739,32 @@ bool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validate tx->isDeauthorized = true; txStore.commit(tx); - updateTxNotification(TxNotification_AuthorizationTimeout); + updateTxNotification(MO_TxNotification_AuthorizationTimeout); }); - authorize->setTimeout(20 * 1000); - context.initiateRequest(std::move(authorize)); + authorize->setTimeout(20); + context.getMessageService().sendRequest(std::move(authorize)); } else { MO_DBG_DEBUG("Authorized tx directly (%s)", transaction->idToken.get()); transaction->isAuthorized = true; transaction->notifyIdToken = true; txStore.commit(transaction.get()); - updateTxNotification(TxNotification_Authorized); + updateTxNotification(MO_TxNotification_Authorized); } return true; } -bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateIdToken) { +bool TransactionServiceEvse::endAuthorization(IdToken idToken, bool validateIdToken, IdToken groupIdToken) { if (!transaction || !transaction->isAuthorizationActive) { //transaction already ended / not active anymore return false; } + if (*groupIdToken.get()) { + MO_DBG_WARN("groupIdToken not supported"); + } + MO_DBG_DEBUG("End session started by idTag %s", transaction->idToken.get()); @@ -723,18 +774,18 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateId transaction->notifyIdToken = true; txStore.commit(transaction.get()); - updateTxNotification(TxNotification_Authorized); + updateTxNotification(MO_TxNotification_Authorized); } else if (!validateIdToken) { transaction->stopIdToken = std::unique_ptr(new IdToken(idToken, getMemoryTag())); transaction->isAuthorizationActive = false; transaction->notifyStopIdToken = true; txStore.commit(transaction.get()); - updateTxNotification(TxNotification_Authorized); + updateTxNotification(MO_TxNotification_Authorized); } else { // use a different idToken for stopping the tx - auto authorize = makeRequest(new Authorize(context.getModel(), idToken)); + auto authorize = makeRequest(context, new Authorize(context.getModel201(), idToken)); if (!authorize) { // OOM abortTransaction(); @@ -744,7 +795,7 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateId char txId [sizeof(transaction->transactionId)]; //capture txId to check if transaction reference is still the same snprintf(txId, sizeof(txId), "%s", transaction->transactionId); - authorize->setOnReceiveConfListener([this, txId, idToken] (JsonObject response) { + authorize->setOnReceiveConf([this, txId, idToken] (JsonObject response) { auto tx = getTransaction(); if (!tx || strcmp(tx->transactionId, txId)) { MO_DBG_INFO("dangling Authorize -- discard"); @@ -754,7 +805,7 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateId if (strcmp(response["idTokenInfo"]["status"] | "_Undefined", "Accepted")) { MO_DBG_DEBUG("Authorize rejected (%s), don't stop tx", idToken.get()); - updateTxNotification(TxNotification_AuthorizationRejected); + updateTxNotification(MO_TxNotification_AuthorizationRejected); return; } @@ -773,31 +824,33 @@ bool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateId tx->notifyStopIdToken = true; txStore.commit(tx); - updateTxNotification(TxNotification_Authorized); + updateTxNotification(MO_TxNotification_Authorized); }); - authorize->setOnTimeoutListener([this, txId] () { + authorize->setOnTimeout([this, txId] () { auto tx = getTransaction(); if (!tx || strcmp(tx->transactionId, txId)) { MO_DBG_INFO("dangling Authorize -- discard"); return; } - updateTxNotification(TxNotification_AuthorizationTimeout); + updateTxNotification(MO_TxNotification_AuthorizationTimeout); }); - authorize->setTimeout(20 * 1000); - context.initiateRequest(std::move(authorize)); + authorize->setTimeout(20); + context.getMessageService().sendRequest(std::move(authorize)); } return true; } -bool TransactionService::Evse::abortTransaction(Ocpp201::Transaction::StoppedReason stoppedReason, TransactionEventTriggerReason stopTrigger) { + +bool TransactionServiceEvse::abortTransaction(MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReason stopTrigger) { return endTransaction(stoppedReason, stopTrigger); } -MicroOcpp::Ocpp201::Transaction *TransactionService::Evse::getTransaction() { + +Transaction *TransactionServiceEvse::getTransaction() { return transaction.get(); } -bool TransactionService::Evse::ocppPermitsCharge() { +bool TransactionServiceEvse::ocppPermitsCharge() { return transaction && transaction->active && transaction->isAuthorizationActive && @@ -805,7 +858,60 @@ bool TransactionService::Evse::ocppPermitsCharge() { !transaction->isDeauthorized; } -unsigned int TransactionService::Evse::getFrontRequestOpNr() { +TriggerMessageStatus TransactionServiceEvse::triggerTransactionEvent() { + //F06.FR.07: The Charging Station SHALL send a TransactionEventRequest to the CSMS with + //triggerReason = Trigger, transactionInfo with at least the chargingState, and meterValue + //with the most recent measurements for all measurands configured in Configuration Variable: + //SampledDataTxUpdatedMeasurands. + + if (!transaction) { + return TriggerMessageStatus::Rejected; + } + + auto txEvent = txStore.createTransactionEvent(*transaction); + if (!txEvent) { + MO_DBG_ERR("OOM"); + return TriggerMessageStatus::ERR_INTERNAL; + } + + txEvent->eventType = TransactionEventData::Type::Updated; + txEvent->timestamp = clock.now(); + txEvent->triggerReason = MO_TxEventTriggerReason_Trigger; + txEvent->chargingState = getChargingState(); + txEvent->evse = evseId; + + auto mvTriggerMessage = meteringEvse->takeTriggeredMeterValues(); + if (mvTriggerMessage) { + txEvent->meterValue.push_back(std::move(mvTriggerMessage)); + } + + txEvent->opNr = context.getMessageService().getNextOpNr(); + MO_DBG_DEBUG("enqueueing triggered txEvent at opNr %u", txEvent->opNr); + + if (!txStore.commit(txEvent.get())) { + MO_DBG_ERR("could not commit triggered txEvent"); + return TriggerMessageStatus::Rejected; + } + + //try to pass ownership to front txEvent immediatley + if (!txEventFront && + transaction->txNr == txNrFront && + !transaction->seqNos.empty() && transaction->seqNos.front() == txEvent->seqNo) { + + //txFront set up? + if (!txFront) { + txFront = transaction.get(); + } + + //keep txEvent loaded (otherwise ReqEmitter would load it again from flash) + MO_DBG_DEBUG("new txEvent is front element"); + txEventFront = std::move(txEvent); + } + + return TriggerMessageStatus::Accepted; +} + +unsigned int TransactionServiceEvse::getFrontRequestOpNr() { if (txEventFront) { return txEventFront->opNr; @@ -815,7 +921,7 @@ unsigned int TransactionService::Evse::getFrontRequestOpNr() { * Advance front transaction? */ - unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrFront) % MAX_TX_CNT; + unsigned int txSize = (txNrEnd + MO_TXNR_MAX - txNrFront) % MO_TXNR_MAX; if (txFront && txSize == 0) { //catch edge case where txBack has been rolled back and txFront was equal to txBack @@ -848,7 +954,7 @@ unsigned int TransactionService::Evse::getFrontRequestOpNr() { txEventFront = nullptr; txFrontCache = nullptr; txFront = nullptr; - txNrFront = (txNrFront + 1) % MAX_TX_CNT; + txNrFront = (txNrFront + 1) % MO_TXNR_MAX; MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); } else { //front is accurate. Done here @@ -868,7 +974,7 @@ unsigned int TransactionService::Evse::getFrontRequestOpNr() { return NoOperation; } -std::unique_ptr TransactionService::Evse::fetchFrontRequest() { +std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { if (!txEventFront) { return nullptr; @@ -878,9 +984,10 @@ std::unique_ptr TransactionService::Evse::fetchFrontRequest() { return nullptr; } + char dummyTimestamp [MO_JSONDATE_SIZE]; if (txEventFront->seqNo == 0 && - txEventFront->timestamp < MIN_TIME && - txEventFront->bootNr != context.getModel().getBootNr()) { + !clock.toJsonString(txEventFront->timestamp, dummyTimestamp, sizeof(dummyTimestamp))) { + //time not set, cannot be restored anymore -> invalid tx MO_DBG_ERR("cannot recover tx from previous power cycle"); @@ -907,10 +1014,13 @@ std::unique_ptr TransactionService::Evse::fetchFrontRequest() { return nullptr; } - Timestamp nextAttempt = txEventFront->attemptTime + - txEventFront->attemptNr * std::max(0, txService.messageAttemptIntervalTransactionEventInt->getInt()); + int32_t dtLastAttempt; + if (!clock.delta(clock.now(), txEventFront->attemptTime, dtLastAttempt)) { + MO_DBG_ERR("internal error"); + dtLastAttempt = MO_MAX_TIME; + } - if (nextAttempt > context.getModel().getClock().now()) { + if (dtLastAttempt < txEventFront->attemptNr * std::max(0, txService.messageAttemptIntervalTransactionEventInt->getInt())) { return nullptr; } @@ -920,21 +1030,21 @@ std::unique_ptr TransactionService::Evse::fetchFrontRequest() { } txEventFront->attemptNr++; - txEventFront->attemptTime = context.getModel().getClock().now(); + txEventFront->attemptTime = clock.now(); txStore.commit(txEventFront.get()); - auto txEventRequest = makeRequest(new TransactionEvent(context.getModel(), txEventFront.get())); - txEventRequest->setOnReceiveConfListener([this] (JsonObject) { + auto txEventRequest = makeRequest(context, new TransactionEvent(context, txEventFront.get())); + txEventRequest->setOnReceiveConf([this] (JsonObject) { MO_DBG_DEBUG("completed front txEvent"); txStore.remove(*txFront, txEventFront->seqNo); txEventFront = nullptr; txEventFrontIsRequested = false; }); - txEventRequest->setOnAbortListener([this] () { + txEventRequest->setOnAbort([this] () { MO_DBG_DEBUG("unsuccessful front txEvent"); txEventFrontIsRequested = false; }); - txEventRequest->setTimeout(std::min(20, std::max(5, txService.messageAttemptIntervalTransactionEventInt->getInt())) * 1000); + txEventRequest->setTimeout(std::min(20, std::max(5, txService.messageAttemptIntervalTransactionEventInt->getInt()))); txEventFrontIsRequested = true; @@ -1009,7 +1119,7 @@ namespace MicroOcpp { bool validateTxStartStopPoint(const char *value, void *userPtr) { auto txService = static_cast(userPtr); - auto validated = makeVector("v201.Transactions.TransactionService"); + auto validated = makeVector("v201.Transactions.TransactionService"); return txService->parseTxStartStopPoint(value, validated); } @@ -1017,17 +1127,45 @@ bool validateUnsignedInt(int val, void*) { return val >= 0; } -} //namespace MicroOcpp +bool disabledInput(unsigned int, void*) { + return false; +} -using namespace MicroOcpp; +} //namespace MicroOcpp -TransactionService::TransactionService(Context& context, std::shared_ptr filesystem, unsigned int numEvseIds) : +TransactionService::TransactionService(Context& context) : MemoryManaged("v201.Transactions.TransactionService"), context(context), - txStore(filesystem, numEvseIds), + txStore(context), txStartPointParsed(makeVector(getMemoryTag())), txStopPointParsed(makeVector(getMemoryTag())) { - auto varService = context.getModel().getVariableService(); + +} + +TransactionService::~TransactionService() { + for (unsigned int i = 0; i < MO_NUM_EVSEID; i++) { + delete evses[i]; + evses[i] = nullptr; + } +} + +bool TransactionService::setup() { + + filesystem = context.getFilesystem(); + if (!filesystem) { + MO_DBG_DEBUG("volatile mode"); + } + + rngCb = context.getRngCb(); + if (!rngCb) { + MO_DBG_ERR("setup failure"); + return false; + } + + auto varService = context.getModel201().getVariableService(); + if (!varService) { + return false; + } txStartPointString = varService->declareVariable("TxCtrlr", "TxStartPoint", "PowerPathClosed"); txStopPointString = varService->declareVariable("TxCtrlr", "TxStopPoint", "PowerPathClosed"); @@ -1040,33 +1178,97 @@ TransactionService::TransactionService(Context& context, std::shared_ptrdeclareVariable("OCPPCommCtrlr", "MessageAttemptInterval", 60); silentOfflineTransactionsBool = varService->declareVariable("CustomizationCtrlr", "SilentOfflineTransactions", false); - varService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false, Variable::Mutability::ReadOnly, false); + if (!txStartPointString || + !txStopPointString || + !stopTxOnInvalidIdBool || + !stopTxOnEVSideDisconnectBool || + !evConnectionTimeOutInt || + !sampledDataTxUpdatedInterval || + !sampledDataTxEndedInterval || + !messageAttemptsTransactionEventInt || + !messageAttemptIntervalTransactionEventInt || + !silentOfflineTransactionsBool) { + MO_DBG_ERR("failure to declare variables"); + return false; + } + + varService->declareVariable("AuthCtrlr", "AuthorizeRemoteStart", false, Mutability::ReadOnly, false); + varService->declareVariable("AuthCtrlr", "LocalAuthorizeOffline", false, Mutability::ReadOnly, false); varService->registerValidator("TxCtrlr", "TxStartPoint", validateTxStartStopPoint, this); varService->registerValidator("TxCtrlr", "TxStopPoint", validateTxStartStopPoint, this); varService->registerValidator("SampledDataCtrlr", "TxUpdatedInterval", validateUnsignedInt); varService->registerValidator("SampledDataCtrlr", "TxEndedInterval", validateUnsignedInt); - for (unsigned int evseId = 0; evseId < std::min(numEvseIds, (unsigned int)MO_NUM_EVSEID); evseId++) { - if (!txStore.getEvse(evseId)) { + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("Authorize", nullptr, Authorize::writeMockConf); + context.getMessageService().registerOperation("TransactionEvent", TransactionEvent::onRequestMock, TransactionEvent::writeMockConf); + #endif //MO_ENABLE_MOCK_SERVER + + auto rcService = context.getModel201().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } + + rcService->addTriggerMessageHandler("TransactionEvent", [] (Context& context, int evseId) -> TriggerMessageStatus { + TriggerMessageStatus status = TriggerMessageStatus::Rejected; + auto txSvc = context.getModel201().getTransactionService(); + + unsigned int evseIdRangeBegin = 1; + unsigned int evseIdRangeEnd = context.getModel201().getNumEvseId(); + + if (evseId > 0) { + //send for one evseId only + evseIdRangeBegin = (unsigned int)evseId; + evseIdRangeEnd = evseIdRangeBegin + 1; + } + + for (unsigned i = evseIdRangeBegin; i < evseIdRangeEnd; i++) { + auto txSvcEvse = txSvc ? txSvc->getEvse(i) : nullptr; + if (!txSvcEvse) { + return TriggerMessageStatus::ERR_INTERNAL; + } + + auto ret = txSvcEvse->triggerTransactionEvent(); + + bool abortLoop = false; + + switch (ret) { + case TriggerMessageStatus::Accepted: + //Accepted takes precedence over Rejected + status = TriggerMessageStatus::Accepted; + break; + case TriggerMessageStatus::Rejected: + //if one EVSE rejects, but another accepts, respond with Accepted + break; + case TriggerMessageStatus::ERR_INTERNAL: + case TriggerMessageStatus::NotImplemented: + status = TriggerMessageStatus::ERR_INTERNAL; + abortLoop = true; + break; + } + } + + return status; + }); + + numEvseId = context.getModel201().getNumEvseId(); + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i) || !getEvse(i)->setup()) { MO_DBG_ERR("initialization error"); - break; + return false; } - evses[evseId] = new Evse(context, *this, *txStore.getEvse(evseId), evseId); } //make sure EVSE 0 will only trigger transactions if TxStartPoint is Authorized if (evses[0]) { - evses[0]->connectorPluggedInput = [] () {return false;}; - evses[0]->evReadyInput = [] () {return false;}; - evses[0]->evseReadyInput = [] () {return false;}; - } -} - -TransactionService::~TransactionService() { - for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) { - delete evses[evseId]; + evses[0]->connectorPluggedInput = disabledInput; + evses[0]->evReadyInput = disabledInput; + evses[0]->evseReadyInput = disabledInput; } + + return true; } void TransactionService::loop() { @@ -1088,7 +1290,7 @@ void TransactionService::loop() { if (evses[0]->transaction->active) { for (unsigned int evseId = 1; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) { if (!evses[evseId]->getTransaction() && - (!evses[evseId]->connectorPluggedInput || evses[evseId]->connectorPluggedInput())) { + (!evses[evseId]->connectorPluggedInput || evses[evseId]->connectorPluggedInput(evseId, evses[evseId]->connectorPluggedInputUserData))) { MO_DBG_INFO("assign tx to evse %u", evseId); evses[0]->transaction->notifyEvseId = true; evses[0]->transaction->evseId = evseId; @@ -1099,11 +1301,27 @@ void TransactionService::loop() { } } -TransactionService::Evse *TransactionService::getEvse(unsigned int evseId) { - if (evseId >= MO_NUM_EVSEID) { +TransactionServiceEvse *TransactionService::getEvse(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); + return nullptr; + } + + auto txStoreEvse = txStore.getEvse(evseId); + if (!txStoreEvse) { + MO_DBG_ERR("OOM"); return nullptr; } + + if (!evses[evseId]) { + evses[evseId] = new TransactionServiceEvse(context, *this, *txStoreEvse, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + return evses[evseId]; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.h b/src/MicroOcpp/Model/Transactions/TransactionService201.h new file mode 100644 index 00000000..5792f37d --- /dev/null +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.h @@ -0,0 +1,173 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef MO_TXRECORD_SIZE_V201 +#define MO_TXRECORD_SIZE_V201 4 //maximum number of tx to hold on flash storage +#endif + +#if MO_ENABLE_V201 + +namespace MicroOcpp { + +class Context; +class Clock; + +namespace Ocpp201 { + +// TxStartStopPoint (2.6.4.1) +enum class TxStartStopPoint : uint8_t { + ParkingBayOccupancy, + EVConnected, + Authorized, + DataSigned, + PowerPathClosed, + EnergyTransfer +}; + +class Variable; +class TransactionService; +class MeteringServiceEvse; + +class TransactionServiceEvse : public RequestQueue, public MemoryManaged { +private: + Context& context; + Clock& clock; + TransactionService& txService; + TransactionStoreEvse& txStore; + MeteringServiceEvse *meteringEvse = nullptr; + const unsigned int evseId; + unsigned int txNrCounter = 0; + std::unique_ptr transaction; + TransactionEventData::ChargingState trackChargingState = TransactionEventData::ChargingState::UNDEFINED; + + bool (*connectorPluggedInput)(unsigned int evseId, void *userData) = nullptr; + void *connectorPluggedInputUserData = nullptr; + bool (*evReadyInput)(unsigned int evseId, void *userData) = nullptr; + void *evReadyInputUserData = nullptr; + bool (*evseReadyInput)(unsigned int evseId, void *userData) = nullptr; + void *evseReadyInputUserData = nullptr; + + bool (*startTxReadyInput)(unsigned int evseId, void *userData) = nullptr; //the StartTx request will be delayed while this Input is false + void *startTxReadyInputUserData = nullptr; + bool (*stopTxReadyInput)(unsigned int evseId, void *userData) = nullptr; //the StopTx request will be delayed while this Input is false + void *stopTxReadyInputUserData = nullptr; + + void (*txNotificationOutput)(MO_TxNotification, unsigned int evseId, void *userData) = nullptr; + void *txNotificationOutputUserData = nullptr; + + bool beginTransaction(); + bool endTransaction(MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReason stopTrigger); + + TransactionEventData::ChargingState getChargingState(); + + unsigned int txNrBegin = 0; //oldest (historical) transaction on flash. Has no function, but is useful for error diagnosis + unsigned int txNrFront = 0; //oldest transaction which is still queued to be sent to the server + unsigned int txNrEnd = 0; //one position behind newest transaction + + Transaction *txFront = nullptr; + std::unique_ptr txFrontCache; //helper owner for txFront. Empty if txFront == transaction.get() + std::unique_ptr txEventFront; + bool txEventFrontIsRequested = false; + +public: + TransactionServiceEvse(Context& context, TransactionService& txService, TransactionStoreEvse& txStore, unsigned int evseId); + ~TransactionServiceEvse(); + + bool setup(); + + void loop(); + + void setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData); + void setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData); + void setEvseReadyInput(bool (*evseReady)(unsigned int, void*), void *userData); + + void setStartTxReadyInput(bool (*startTxReady)(unsigned int, void*), void *userData); + void setStopTxReadyInput(bool (*stopTxReady)(unsigned int, void*), void *userData); + + void setTxNotificationOutput(void (*txNotificationOutput)(MO_TxNotification, unsigned int, void*), void *userData); + void updateTxNotification(MO_TxNotification event); + + bool beginAuthorization(IdToken idToken, bool validateIdToken = true, IdToken groupIdToken = nullptr); // authorize by swipe RFID, groupIdToken ignored if validateIdToken = true + bool endAuthorization(IdToken idToken = IdToken(), bool validateIdToken = false, IdToken groupIdToken = nullptr); // stop authorization by swipe RFID, groupIdToken ignored if validateIdToken = true + + // stop transaction, but neither upon user request nor OCPP server request (e.g. after PowerLoss) + bool abortTransaction(MO_TxStoppedReason stoppedReason = MO_TxStoppedReason_Other, MO_TxEventTriggerReason stopTrigger = MO_TxEventTriggerReason_AbnormalCondition); + + Transaction *getTransaction(); + + bool ocppPermitsCharge(); + + TriggerMessageStatus triggerTransactionEvent(); + + unsigned int getFrontRequestOpNr() override; + std::unique_ptr fetchFrontRequest() override; + + friend TransactionService; +}; + +class TransactionService : public MemoryManaged { +private: + Context& context; + TransactionStore txStore; + TransactionServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; + + MO_FilesystemAdapter *filesystem = nullptr; + uint32_t (*rngCb)() = nullptr; + + Variable *txStartPointString = nullptr; + Variable *txStopPointString = nullptr; + Variable *stopTxOnInvalidIdBool = nullptr; + Variable *stopTxOnEVSideDisconnectBool = nullptr; + Variable *evConnectionTimeOutInt = nullptr; + Variable *sampledDataTxUpdatedInterval = nullptr; + Variable *sampledDataTxEndedInterval = nullptr; + Variable *messageAttemptsTransactionEventInt = nullptr; + Variable *messageAttemptIntervalTransactionEventInt = nullptr; + Variable *silentOfflineTransactionsBool = nullptr; + uint16_t trackTxStartPoint = -1; + uint16_t trackTxStopPoint = -1; + Vector txStartPointParsed; + Vector txStopPointParsed; + bool isTxStartPoint(TxStartStopPoint check); + bool isTxStopPoint(TxStartStopPoint check); +public: + TransactionService(Context& context); + ~TransactionService(); + + bool setup(); + + void loop(); + + TransactionServiceEvse *getEvse(unsigned int evseId); + + bool parseTxStartStopPoint(const char *src, Vector& dst); + +friend class TransactionServiceEvse; +}; + +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 +#endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index 41a7e747..5322426f 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -3,187 +3,375 @@ // MIT License #include -#include +#include #include +#include +#include #include +#include + +#if MO_ENABLE_V16 + +namespace MicroOcpp { +namespace Ocpp16 { +namespace TransactionStore { + +bool serializeSendStatus(Clock& clock, SendStatus& status, JsonObject out); +bool deserializeSendStatus(Clock& clock, SendStatus& status, JsonObject in); + +bool serializeTransaction(Clock& clock, Transaction& tx, JsonDoc& out); +bool deserializeTransaction(Clock& clock, Transaction& tx, JsonObject in); + +} //namespace TransactionStore +} //namespace Ocpp16 +} //namespace MicroOcpp + using namespace MicroOcpp; -TransactionStoreEvse::TransactionStoreEvse(TransactionStore& context, unsigned int connectorId, std::shared_ptr filesystem) : - MemoryManaged("v16.Transactions.TransactionStore"), - context(context), - connectorId(connectorId), - filesystem(filesystem), - transactions{makeVector>(getMemoryTag())} { +bool Ocpp16::TransactionStore::printTxFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr) { + auto ret = snprintf(fname, size, "tx-%.*u-%.*u.json", + MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + return true; +} +FilesystemUtils::LoadStatus Ocpp16::TransactionStore::load(MO_FilesystemAdapter *filesystem, Context& context, unsigned int evseId, unsigned int txNr, Transaction& transaction) { + + char fname [MO_MAX_PATH_SIZE]; + if (!printTxFname(fname, sizeof(fname), evseId, txNr)) { + MO_DBG_ERR("fname error %u %u", evseId, txNr); + return FilesystemUtils::LoadStatus::ErrOther; + } + + JsonDoc doc (0); + auto ret = FilesystemUtils::loadJson(filesystem, fname, doc, "v16.Transactions.TransactionStore"); + if (ret != FilesystemUtils::LoadStatus::Success) { + return ret; + } + + auto& clock = context.getClock(); + + if (!deserializeTransaction(clock, transaction, doc.as())) { + MO_DBG_ERR("deserialization error %s", fname); + return FilesystemUtils::LoadStatus::ErrFileCorruption; + } + + //success + + MO_DBG_DEBUG("Restored tx %u-%u", evseId, txNr, txData.size()); + + return FilesystemUtils::LoadStatus::Success; } -TransactionStoreEvse::~TransactionStoreEvse() { +FilesystemUtils::StoreStatus Ocpp16::TransactionStore::store(MO_FilesystemAdapter *filesystem, Context& context, Transaction& transaction) { + + char fname [MO_MAX_PATH_SIZE]; + if (!printTxFname(fname, sizeof(fname), transaction.getConnectorId(), transaction.getTxNr())) { + MO_DBG_ERR("fname error %u %u", transaction.getConnectorId(), transaction.getTxNr()); + return FilesystemUtils::StoreStatus::ErrOther; + } + + auto& clock = context.getClock(); + + JsonDoc doc (0); + if (!serializeTransaction(clock, transaction, doc)) { + MO_DBG_ERR("serialization error %s", fname); + return FilesystemUtils::StoreStatus::ErrJsonCorruption; + } + + auto ret = FilesystemUtils::storeJson(filesystem, fname, doc); + if (ret != FilesystemUtils::StoreStatus::Success) { + MO_DBG_ERR("fs error %s %i", fname, (int)ret); + } + return ret; } -std::shared_ptr TransactionStoreEvse::getTransaction(unsigned int txNr) { +bool Ocpp16::TransactionStore::remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr) { - //check for most recent element of cache first because of temporal locality - if (!transactions.empty()) { - if (auto cached = transactions.back().lock()) { - if (cached->getTxNr() == txNr) { - //cache hit - return cached; - } + char fname [MO_MAX_PATH_SIZE]; + if (!printTxFname(fname, sizeof(fname), evseId, txNr)) { + MO_DBG_ERR("fname error %u %u", evseId, txNr); + return false; + } + + char path [MO_MAX_PATH_SIZE]; + if (!FilesystemUtils::printPath(filesystem, path, sizeof(path), "%s", fname)) { + MO_DBG_ERR("fname error %u %u", evseId, txNr); + return false; + } + + return filesystem->remove(path); +} + +bool Ocpp16::TransactionStore::serializeSendStatus(Clock& clock, SendStatus& status, JsonObject out) { + if (status.isRequested()) { + out["requested"] = true; + } + if (status.isConfirmed()) { + out["confirmed"] = true; + } + out["opNr"] = status.getOpNr(); + if (status.getAttemptNr() != 0) { + out["attemptNr"] = status.getAttemptNr(); + } + + if (status.getAttemptTime().isDefined()) { + char attemptTime [MO_JSONDATE_SIZE]; + if (!clock.toInternalString(status.getAttemptTime(), attemptTime, sizeof(attemptTime))) { + return false; } + out["attemptTime"] = attemptTime; } + return true; +} - //check all other elements (and free up unused entries) - auto cached = transactions.begin(); - while (cached != transactions.end()) { - if (auto tx = cached->lock()) { - if (tx->getTxNr() == txNr) { - //cache hit - return tx; - } - cached++; - } else { - //collect outdated cache reference - cached = transactions.erase(cached); +bool Ocpp16::TransactionStore::deserializeSendStatus(Clock& clock, SendStatus& status, JsonObject in) { + if (in["requested"] | false) { + status.setRequested(); + } + if (in["confirmed"] | false) { + status.confirm(); + } + unsigned int opNr = in["opNr"] | (unsigned int)0; + if (opNr >= 10) { //10 is first valid tx-related opNr + status.setOpNr(opNr); + } + status.setAttemptNr(in["attemptNr"] | (unsigned int)0); + if (in.containsKey("attemptTime")) { + Timestamp attemptTime; + if (!clock.parseString(in["attemptTime"] | "_Invalid", attemptTime)) { + MO_DBG_ERR("deserialization error"); + return false; } + status.setAttemptTime(attemptTime); } + return true; +} - //cache miss - load tx from flash if existent - - if (!filesystem) { - MO_DBG_DEBUG("no FS adapter"); - return nullptr; +bool Ocpp16::TransactionStore::serializeTransaction(Clock& clock, Transaction& tx, JsonDoc& out) { + out = initJsonDoc("v16.Transactions.TransactionDeserialize", 1024); + JsonObject state = out.to(); + + JsonObject sessionState = state.createNestedObject("session"); + if (!tx.isActive()) { + sessionState["active"] = false; + } + if (tx.getIdTag()[0] != '\0') { + sessionState["idTag"] = tx.getIdTag(); + } + if (tx.getParentIdTag()[0] != '\0') { + sessionState["parentIdTag"] = tx.getParentIdTag(); + } + if (tx.isAuthorized()) { + sessionState["authorized"] = true; + } + if (tx.isIdTagDeauthorized()) { + sessionState["deauthorized"] = true; + } + if (tx.getBeginTimestamp().isDefined()) { + char timeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toInternalString(tx.getBeginTimestamp(), timeStr, sizeof(timeStr))) { + return false; + } + sessionState["timestamp"] = timeStr; + } + if (tx.getReservationId() >= 0) { + sessionState["reservationId"] = tx.getReservationId(); + } + if (tx.getTxProfileId() >= 0) { + sessionState["txProfileId"] = tx.getTxProfileId(); } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "tx" "-%u-%u.json", connectorId, txNr); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return nullptr; + JsonObject txStart = state.createNestedObject("start"); + + if (!serializeSendStatus(clock, tx.getStartSync(), txStart)) { + return false; } - size_t msize; - if (filesystem->stat(fn, &msize) != 0) { - MO_DBG_DEBUG("%u-%u does not exist", connectorId, txNr); - return nullptr; + if (tx.isMeterStartDefined()) { + txStart["meter"] = tx.getMeterStart(); } - auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag()); + if (tx.getStartTimestamp().isDefined()) { + char startTimeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toInternalString(tx.getStartTimestamp(), startTimeStr, sizeof(startTimeStr))) { + return false; + } + txStart["timestamp"] = startTimeStr; + } - if (!doc) { - MO_DBG_ERR("memory corruption"); - return nullptr; + if (tx.getStartSync().isConfirmed()) { + txStart["transactionId"] = tx.getTransactionId(); } - auto transaction = std::allocate_shared(makeAllocator(getMemoryTag()), *this, connectorId, txNr); - JsonObject txJson = doc->as(); - if (!deserializeTransaction(*transaction, txJson)) { - MO_DBG_ERR("deserialization error"); - return nullptr; + JsonObject txStop = state.createNestedObject("stop"); + + if (!serializeSendStatus(clock, tx.getStopSync(), txStop)) { + return false; } - //before adding new entry, clean cache - cached = transactions.begin(); - while (cached != transactions.end()) { - if (cached->expired()) { - //collect outdated cache reference - cached = transactions.erase(cached); - } else { - cached++; + if (tx.getStopIdTag()[0] != '\0') { + txStop["idTag"] = tx.getStopIdTag(); + } + + if (tx.isMeterStopDefined()) { + txStop["meter"] = tx.getMeterStop(); + } + + if (tx.getStopTimestamp().isDefined()) { + char stopTimeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!clock.toInternalString(tx.getStopTimestamp(), stopTimeStr, sizeof(stopTimeStr))) { + return false; } + txStop["timestamp"] = stopTimeStr; } - transactions.push_back(transaction); - return transaction; + if (tx.getStopReason()[0] != '\0') { + txStop["reason"] = tx.getStopReason(); + } + + if (tx.isSilent()) { + state["silent"] = true; + } + + if (out.overflowed()) { + MO_DBG_ERR("JSON capacity exceeded"); + return false; + } + + return true; } -std::shared_ptr TransactionStoreEvse::createTransaction(unsigned int txNr, bool silent) { +bool Ocpp16::TransactionStore::deserializeTransaction(Clock& clock, Transaction& tx, JsonObject state) { - auto transaction = std::allocate_shared(makeAllocator(getMemoryTag()), *this, connectorId, txNr, silent); + JsonObject sessionState = state["session"]; - if (!commit(transaction.get())) { - MO_DBG_ERR("FS error"); - return nullptr; + if (!(sessionState["active"] | true)) { + tx.setInactive(); } - //before adding new entry, clean cache - auto cached = transactions.begin(); - while (cached != transactions.end()) { - if (cached->expired()) { - //collect outdated cache reference - cached = transactions.erase(cached); - } else { - cached++; + if (sessionState.containsKey("idTag")) { + if (!tx.setIdTag(sessionState["idTag"] | "")) { + MO_DBG_ERR("read err"); + return false; } } - transactions.push_back(transaction); - return transaction; -} + if (sessionState.containsKey("parentIdTag")) { + if (!tx.setParentIdTag(sessionState["parentIdTag"] | "")) { + MO_DBG_ERR("read err"); + return false; + } + } -bool TransactionStoreEvse::commit(Transaction *transaction) { + if (sessionState["authorized"] | false) { + tx.setAuthorized(); + } - if (!filesystem) { - MO_DBG_DEBUG("no FS: nothing to commit"); - return true; + if (sessionState["deauthorized"] | false) { + tx.setIdTagDeauthorized(); } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "tx" "-%u-%u.json", connectorId, transaction->getTxNr()); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return false; + if (sessionState.containsKey("timestamp")) { + Timestamp timestamp; + if (!clock.parseString(sessionState["timestamp"] | "_Invalid", timestamp)) { + MO_DBG_ERR("read err"); + return false; + } + tx.setBeginTimestamp(timestamp); } - - auto txDoc = initJsonDoc(getMemoryTag()); - if (!serializeTransaction(*transaction, txDoc)) { - MO_DBG_ERR("Serialization error"); - return false; + + if (sessionState.containsKey("reservationId")) { + tx.setReservationId(sessionState["reservationId"] | -1); } - if (!FilesystemUtils::storeJson(filesystem, fn, txDoc)) { - MO_DBG_ERR("FS error"); + if (sessionState.containsKey("txProfileId")) { + tx.setTxProfileId(sessionState["txProfileId"] | -1); + } + + JsonObject txStart = state["start"]; + + if (!deserializeSendStatus(clock, tx.getStartSync(), txStart)) { return false; } - //success - return true; -} + if (txStart.containsKey("meter")) { + tx.setMeterStart(txStart["meter"] | 0); + } -bool TransactionStoreEvse::remove(unsigned int txNr) { + if (txStart.containsKey("timestamp")) { + Timestamp timestamp; + if (!clock.parseString(txStart["timestamp"] | "_Invalid", timestamp)) { + MO_DBG_ERR("deserialization error"); + return false; + } + tx.setStartTimestamp(timestamp); + } - if (!filesystem) { - MO_DBG_DEBUG("no FS: nothing to remove"); - return true; + if (txStart.containsKey("transactionId")) { + tx.setTransactionId(txStart["transactionId"] | -1); } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "tx" "-%u-%u.json", connectorId, txNr); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); + JsonObject txStop = state["stop"]; + + if (!deserializeSendStatus(clock, tx.getStopSync(), txStop)) { return false; } - size_t msize; - if (filesystem->stat(fn, &msize) != 0) { - MO_DBG_DEBUG("%s already removed", fn); - return true; + if (txStop.containsKey("idTag")) { + if (!tx.setStopIdTag(txStop["idTag"] | "")) { + MO_DBG_ERR("read err"); + return false; + } } - MO_DBG_DEBUG("remove %s", fn); - - return filesystem->remove(fn); + if (txStop.containsKey("meter")) { + tx.setMeterStop(txStop["meter"] | 0); + } + + if (txStop.containsKey("timestamp")) { + Timestamp timestamp; + if (!clock.parseString(txStop["timestamp"] | "_Invalid", timestamp)) { + MO_DBG_ERR("deserialization error"); + return false; + } + tx.setStopTimestamp(timestamp); + } + + if (txStop.containsKey("reason")) { + if (!tx.setStopReason(txStop["reason"] | "")) { + MO_DBG_ERR("read err"); + return false; + } + } + + if (state["silent"] | false) { + tx.setSilent(); + } + + MO_DBG_DEBUG("DUMP TX (%s)", tx.getIdTag() ? tx.getIdTag() : "idTag missing"); + MO_DBG_DEBUG("Session | idTag %s, active: %i, authorized: %i, deauthorized: %i", tx.getIdTag(), tx.isActive(), tx.isAuthorized(), tx.isIdTagDeauthorized()); + MO_DBG_DEBUG("Start RPC | req: %i, conf: %i", tx.getStartSync().isRequested(), tx.getStartSync().isConfirmed()); + MO_DBG_DEBUG("Stop RPC | req: %i, conf: %i", tx.getStopSync().isRequested(), tx.getStopSync().isConfirmed()); + if (tx.isSilent()) { + MO_DBG_DEBUG(" | silent Tx"); + } + + return true; } -#if MO_ENABLE_V201 +#endif //MO_ENABLE_V16 -#include +#if MO_ENABLE_V201 -namespace MicroOcpp { -namespace Ocpp201 { +using namespace MicroOcpp; -bool TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJson) { +bool Ocpp201::TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJson) { if (tx.trackEvConnected) { txJson["trackEvConnected"] = tx.trackEvConnected; @@ -230,16 +418,32 @@ bool TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJs txJson["idToken"]["type"] = tx.idToken.getTypeCstr(); } - if (tx.beginTimestamp > MIN_TIME) { - char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; - tx.beginTimestamp.toJsonString(timeStr, JSONDATE_LENGTH + 1); + if (tx.beginTimestamp.isDefined()) { + char timeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toInternalString(tx.beginTimestamp, timeStr, sizeof(timeStr))) { + MO_DBG_ERR("serialization error"); + return false; + } txJson["beginTimestamp"] = timeStr; } + if (tx.startTimestamp.isDefined()) { + char timeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toInternalString(tx.startTimestamp, timeStr, sizeof(timeStr))) { + MO_DBG_ERR("serialization error"); + return false; + } + txJson["startTimestamp"] = timeStr; + } + if (tx.remoteStartId >= 0) { txJson["remoteStartId"] = tx.remoteStartId; } + if (tx.txProfileId >= 0) { + txJson["txProfileId"] = tx.txProfileId; + } + if (tx.evConnectionTimeoutListen) { txJson["evConnectionTimeoutListen"] = true; } @@ -248,8 +452,8 @@ bool TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJs txJson["stoppedReason"] = serializeTransactionStoppedReason(tx.stoppedReason); } - if (serializeTransactionEventTriggerReason(tx.stopTrigger)) { - txJson["stopTrigger"] = serializeTransactionEventTriggerReason(tx.stopTrigger); + if (serializeTxEventTriggerReason(tx.stopTrigger)) { + txJson["stopTrigger"] = serializeTxEventTriggerReason(tx.stopTrigger); } if (tx.stopIdToken) { @@ -269,7 +473,7 @@ bool TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJs return true; } -bool TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject txJson) { +bool Ocpp201::TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject txJson) { if (txJson.containsKey("trackEvConnected") && !txJson["trackEvConnected"].is()) { return false; @@ -335,7 +539,13 @@ bool TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject tx } if (txJson.containsKey("beginTimestamp")) { - if (!tx.beginTimestamp.setTime(txJson["beginTimestamp"] | "_Undefined")) { + if (!context.getClock().parseString(txJson["beginTimestamp"] | "_Invalid", tx.beginTimestamp)) { + return false; + } + } + + if (txJson.containsKey("startTimestamp")) { + if (!context.getClock().parseString(txJson["startTimestamp"] | "_Invalid", tx.startTimestamp)) { return false; } } @@ -348,19 +558,27 @@ bool TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject tx tx.remoteStartId = remoteStartIdIn; } + if (txJson.containsKey("txProfileId")) { + int txProfileIdIn = txJson["txProfileId"] | -1; + if (txProfileIdIn < 0) { + return false; + } + tx.txProfileId = txProfileIdIn; + } + if (txJson.containsKey("evConnectionTimeoutListen") && !txJson["evConnectionTimeoutListen"].is()) { return false; } tx.evConnectionTimeoutListen = txJson["evConnectionTimeoutListen"] | false; - Transaction::StoppedReason stoppedReason; + MO_TxStoppedReason stoppedReason; if (!deserializeTransactionStoppedReason(txJson["stoppedReason"] | (const char*)nullptr, stoppedReason)) { return false; } tx.stoppedReason = stoppedReason; - TransactionEventTriggerReason stopTrigger; - if (!deserializeTransactionEventTriggerReason(txJson["stopTrigger"] | (const char*)nullptr, stopTrigger)) { + MO_TxEventTriggerReason stopTrigger; + if (!deserializeTxEventTriggerReason(txJson["stopTrigger"] | (const char*)nullptr, stopTrigger)) { return false; } tx.stopTrigger = stopTrigger; @@ -398,22 +616,22 @@ bool TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject tx return true; } -bool TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { +bool Ocpp201::TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { if (txEvent.eventType != TransactionEventData::Type::Updated) { txEventJson["eventType"] = serializeTransactionEventType(txEvent.eventType); } - if (txEvent.timestamp > MIN_TIME) { - char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; - txEvent.timestamp.toJsonString(timeStr, JSONDATE_LENGTH + 1); + if (txEvent.timestamp.isDefined()) { + char timeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toInternalString(txEvent.timestamp, timeStr, sizeof(timeStr))) { + return false; + } txEventJson["timestamp"] = timeStr; } - txEventJson["bootNr"] = txEvent.bootNr; - - if (serializeTransactionEventTriggerReason(txEvent.triggerReason)) { - txEventJson["triggerReason"] = serializeTransactionEventTriggerReason(txEvent.triggerReason); + if (serializeTxEventTriggerReason(txEvent.triggerReason)) { + txEventJson["triggerReason"] = serializeTxEventTriggerReason(txEvent.triggerReason); } if (txEvent.offline) { @@ -459,16 +677,18 @@ bool TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEve txEventJson["opNr"] = txEvent.opNr; txEventJson["attemptNr"] = txEvent.attemptNr; - if (txEvent.attemptTime > MIN_TIME) { - char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; - txEvent.attemptTime.toJsonString(timeStr, JSONDATE_LENGTH + 1); + if (txEvent.attemptTime.isDefined()) { + char timeStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toInternalString(txEvent.attemptTime, timeStr, sizeof(timeStr))) { + return false; + } txEventJson["attemptTime"] = timeStr; } return true; } -bool TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { +bool Ocpp201::TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { TransactionEventData::Type eventType; if (!deserializeTransactionEventType(txEventJson["eventType"] | "Updated", eventType)) { @@ -477,20 +697,13 @@ bool TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txE txEvent.eventType = eventType; if (txEventJson.containsKey("timestamp")) { - if (!txEvent.timestamp.setTime(txEventJson["timestamp"] | "_Undefined")) { + if (!context.getClock().parseString(txEventJson["timestamp"] | "_Undefined", txEvent.timestamp)) { return false; } } - int bootNrIn = txEventJson["bootNr"] | -1; - if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits::max()) { - txEvent.bootNr = (uint16_t)bootNrIn; - } else { - return false; - } - - TransactionEventTriggerReason triggerReason; - if (!deserializeTransactionEventTriggerReason(txEventJson["triggerReason"] | "_Undefined", triggerReason)) { + MO_TxEventTriggerReason triggerReason; + if (!deserializeTxEventTriggerReason(txEventJson["triggerReason"] | "_Undefined", triggerReason)) { return false; } txEvent.triggerReason = triggerReason; @@ -585,7 +798,7 @@ bool TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txE } if (txEventJson.containsKey("attemptTime")) { - if (!txEvent.attemptTime.setTime(txEventJson["attemptTime"] | "_Undefined")) { + if (!context.getClock().parseString(txEventJson["attemptTime"] | "_Undefined", txEvent.attemptTime)) { return false; } } @@ -593,71 +806,55 @@ bool TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txE return true; } -TransactionStoreEvse::TransactionStoreEvse(TransactionStore& txStore, unsigned int evseId, std::shared_ptr filesystem) : +Ocpp201::TransactionStoreEvse::TransactionStoreEvse(Context& context, unsigned int evseId) : MemoryManaged("v201.Transactions.TransactionStore"), - txStore(txStore), - evseId(evseId), - filesystem(filesystem) { + context(context), + evseId(evseId) { } -bool TransactionStoreEvse::discoverStoredTx(unsigned int& txNrBeginOut, unsigned int& txNrEndOut) { - +bool Ocpp201::TransactionStoreEvse::setup() { + filesystem = context.getFilesystem(); if (!filesystem) { - MO_DBG_DEBUG("no FS adapter"); - return true; + MO_DBG_DEBUG("volatile mode"); } + return true; +} - char fnPrefix [MO_MAX_PATH_SIZE]; - snprintf(fnPrefix, sizeof(fnPrefix), "tx201-%u-", evseId); - size_t fnPrefixLen = strlen(fnPrefix); - - unsigned int txNrPivot = std::numeric_limits::max(); - unsigned int txNrBegin = 0, txNrEnd = 0; - - auto ret = filesystem->ftw_root([fnPrefix, fnPrefixLen, &txNrPivot, &txNrBegin, &txNrEnd] (const char *fn) { - if (!strncmp(fn, fnPrefix, fnPrefixLen)) { - unsigned int parsedTxNr = 0; - for (size_t i = fnPrefixLen; fn[i] >= '0' && fn[i] <= '9'; i++) { - parsedTxNr *= 10; - parsedTxNr += fn[i] - '0'; - } - - if (txNrPivot == std::numeric_limits::max()) { - txNrPivot = parsedTxNr; - txNrBegin = parsedTxNr; - txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT; - return 0; - } +bool Ocpp201::TransactionStoreEvse::printTxEventFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int seqNo) { + auto ret = snprintf(fname, size, "tx201-%.*u-%.*u-%.*u.json", + MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr, MO_TXEVENTRECORD_DIGITS, seqNo); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + return true; +} - if ((parsedTxNr + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT < MAX_TX_CNT / 2) { - //parsedTxNr is after pivot point - if ((parsedTxNr + 1 + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT > (txNrEnd + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT) { - txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT; - } - } else if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT < MAX_TX_CNT / 2) { - //parsedTxNr is before pivot point - if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT > (txNrPivot + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT) { - txNrBegin = parsedTxNr; - } - } +namespace MicroOcpp { - MO_DBG_DEBUG("found %s%u-*.json - Internal range from %u to %u (exclusive)", fnPrefix, parsedTxNr, txNrBegin, txNrEnd); +struct LoadSeqNosData { + const char *fnPrefix = nullptr; + size_t fnPrefixLen = 0; + Vector *seqNos = nullptr; +}; + +int loadSeqNoEntry(const char *fname, void* user_data) { + auto data = reinterpret_cast(user_data); + if (!strncmp(fname, data->fnPrefix, data->fnPrefixLen)) { + unsigned int parsedSeqNo = 0; + for (size_t i = data->fnPrefixLen; fname[i] >= '0' && fname[i] <= '9'; i++) { + parsedSeqNo *= 10; + parsedSeqNo += fname[i] - '0'; } - return 0; - }); - if (ret == 0) { - txNrBeginOut = txNrBegin; - txNrEndOut = txNrEnd; - return true; - } else { - MO_DBG_ERR("fs error"); - return false; + data->seqNos->push_back(parsedSeqNo); } + return 0; } +} //namespace MicroOcpp -std::unique_ptr TransactionStoreEvse::loadTransaction(unsigned int txNr) { +std::unique_ptr Ocpp201::TransactionStoreEvse::loadTransaction(unsigned int txNr) { if (!filesystem) { MO_DBG_DEBUG("no FS adapter"); @@ -665,7 +862,8 @@ std::unique_ptr TransactionStoreEvse::loadTransaction(unsigned int } char fnPrefix [MO_MAX_PATH_SIZE]; - auto ret= snprintf(fnPrefix, sizeof(fnPrefix), "tx201-%u-%u-", evseId, txNr); + auto ret = snprintf(fnPrefix, sizeof(fnPrefix), "tx201-%.*u-%.*u-", + MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr); if (ret < 0 || (size_t)ret >= sizeof(fnPrefix)) { MO_DBG_ERR("fn error"); return nullptr; @@ -674,18 +872,12 @@ std::unique_ptr TransactionStoreEvse::loadTransaction(unsigned int Vector seqNos = makeVector(getMemoryTag()); - filesystem->ftw_root([fnPrefix, fnPrefixLen, &seqNos] (const char *fn) { - if (!strncmp(fn, fnPrefix, fnPrefixLen)) { - unsigned int parsedSeqNo = 0; - for (size_t i = fnPrefixLen; fn[i] >= '0' && fn[i] <= '9'; i++) { - parsedSeqNo *= 10; - parsedSeqNo += fn[i] - '0'; - } + LoadSeqNosData data; + data.fnPrefix = fnPrefix; + data.fnPrefixLen = fnPrefixLen; + data.seqNos = &seqNos; - seqNos.push_back(parsedSeqNo); - } - return 0; - }); + filesystem->ftw(filesystem->path_prefix, loadSeqNoEntry, reinterpret_cast(&data)); if (seqNos.empty()) { MO_DBG_DEBUG("no tx at tx201-%u-%u", evseId, txNr); @@ -695,26 +887,29 @@ std::unique_ptr TransactionStoreEvse::loadTransaction(unsigned int std::sort(seqNos.begin(), seqNos.end()); char fn [MO_MAX_PATH_SIZE] = {'\0'}; - ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "tx201" "-%u-%u-%u.json", evseId, txNr, seqNos.back()); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { + if (!printTxEventFname(fn, sizeof(fn), evseId, txNr, seqNos.back())) { MO_DBG_ERR("fn error: %i", ret); return nullptr; } - size_t msize; - if (filesystem->stat(fn, &msize) != 0) { - MO_DBG_ERR("tx201-%u-%u memory corruption", evseId, txNr); - return nullptr; - } - - auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag()); - - if (!doc) { - MO_DBG_ERR("memory corruption"); - return nullptr; + JsonDoc doc (0); + auto status = FilesystemUtils::loadJson(filesystem, fn, doc, getMemoryTag()); + switch (status) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + MO_DBG_ERR("memory corruption"); + return nullptr; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return nullptr; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", fn); + return nullptr; } - auto transaction = std::unique_ptr(new Transaction()); + auto transaction = std::unique_ptr(new Transaction(context.getClock())); if (!transaction) { MO_DBG_ERR("OOM"); return nullptr; @@ -724,7 +919,7 @@ std::unique_ptr TransactionStoreEvse::loadTransaction(unsigned int transaction->txNr = txNr; transaction->seqNos = std::move(seqNos); - JsonObject txJson = (*doc)["tx"]; + JsonObject txJson = doc["tx"]; if (!deserializeTransaction(*transaction, txJson)) { MO_DBG_ERR("deserialization error"); @@ -732,7 +927,7 @@ std::unique_ptr TransactionStoreEvse::loadTransaction(unsigned int } //determine seqNoEnd and trim seqNos record - if (doc->containsKey("txEvent")) { + if (doc.containsKey("txEvent")) { //last tx201 file contains txEvent -> txNoEnd is one place after tx201 file and seqNos is accurate transaction->seqNoEnd = transaction->seqNos.back() + 1; } else { @@ -746,7 +941,7 @@ std::unique_ptr TransactionStoreEvse::loadTransaction(unsigned int return transaction; } -std::unique_ptr TransactionStoreEvse::createTransaction(unsigned int txNr, const char *txId) { +std::unique_ptr Ocpp201::TransactionStoreEvse::createTransaction(unsigned int txNr, const char *txId) { //clean data which could still be here from a rolled-back transaction if (!remove(txNr)) { @@ -754,7 +949,7 @@ std::unique_ptr TransactionStoreEvse::createTransaction(un return nullptr; } - auto transaction = std::unique_ptr(new Transaction()); + auto transaction = std::unique_ptr(new Transaction(context.getClock())); if (!transaction) { MO_DBG_ERR("OOM"); return nullptr; @@ -777,7 +972,7 @@ std::unique_ptr TransactionStoreEvse::createTransaction(un return transaction; } -std::unique_ptr TransactionStoreEvse::createTransactionEvent(Transaction& tx) { +std::unique_ptr Ocpp201::TransactionStoreEvse::createTransactionEvent(Transaction& tx) { auto txEvent = std::unique_ptr(new TransactionEventData(&tx, tx.seqNoEnd)); if (!txEvent) { @@ -789,7 +984,7 @@ std::unique_ptr TransactionStoreEvse::createTransactionEve return txEvent; } -std::unique_ptr TransactionStoreEvse::loadTransactionEvent(Transaction& tx, unsigned int seqNo) { +std::unique_ptr Ocpp201::TransactionStoreEvse::loadTransactionEvent(Transaction& tx, unsigned int seqNo) { if (!filesystem) { MO_DBG_DEBUG("no FS adapter"); @@ -807,27 +1002,30 @@ std::unique_ptr TransactionStoreEvse::loadTransactionEvent return nullptr; } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "tx201" "-%u-%u-%u.json", evseId, tx.txNr, seqNo); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); - return nullptr; - } - - size_t msize; - if (filesystem->stat(fn, &msize) != 0) { - MO_DBG_ERR("seqNos out of sync: could not find %u-%u-%u", evseId, tx.txNr, seqNo); + char fn [MO_MAX_PATH_SIZE]; + if (!printTxEventFname(fn, sizeof(fn), evseId, tx.txNr, seqNo)) { + MO_DBG_ERR("fn error: %s", fn); return nullptr; } - auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag()); - - if (!doc) { - MO_DBG_ERR("memory corruption"); - return nullptr; + JsonDoc doc (0); + auto ret = FilesystemUtils::loadJson(filesystem, fn, doc, getMemoryTag()); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + MO_DBG_ERR("seqNos out of sync: could not find %u-%u-%u", evseId, tx.txNr, seqNo); + return nullptr; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return nullptr; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", fn); + return nullptr; } - if (!doc->containsKey("txEvent")) { + if (!doc.containsKey("txEvent")) { MO_DBG_DEBUG("%u-%u-%u does not contain txEvent", evseId, tx.txNr, seqNo); return nullptr; } @@ -838,7 +1036,7 @@ std::unique_ptr TransactionStoreEvse::loadTransactionEvent return nullptr; } - if (!deserializeTransactionEvent(*txEvent, (*doc)["txEvent"])) { + if (!deserializeTransactionEvent(*txEvent, doc["txEvent"])) { MO_DBG_ERR("deserialization error"); return nullptr; } @@ -846,7 +1044,7 @@ std::unique_ptr TransactionStoreEvse::loadTransactionEvent return txEvent; } -bool TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent) { +bool Ocpp201::TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent) { if (!filesystem) { MO_DBG_DEBUG("no FS: nothing to commit"); @@ -871,7 +1069,7 @@ bool TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent } // Check if to delete intermediate offline txEvent - if (seqNosNewSize > MO_TXEVENTRECORD_SIZE_V201) { + if (seqNosNewSize > MO_TXEVENTRECORD_SIZE) { auto deltaMin = std::numeric_limits::max(); size_t indexMin = tx.seqNos.size(); for (size_t i = 2; i + 1 <= tx.seqNos.size(); i++) { //always keep first and final txEvent @@ -896,10 +1094,9 @@ bool TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent } } - char fn [MO_MAX_PATH_SIZE] = {'\0'}; - auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX "tx201" "-%u-%u-%u.json", evseId, tx.txNr, seqNo); - if (ret < 0 || ret >= MO_MAX_PATH_SIZE) { - MO_DBG_ERR("fn error: %i", ret); + char fn [MO_MAX_PATH_SIZE]; + if (!printTxEventFname(fn, sizeof(fn), evseId, tx.txNr, seqNo)) { + MO_DBG_ERR("fn error: %s", fn); return false; } @@ -915,7 +1112,7 @@ bool TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent return false; } - if (!FilesystemUtils::storeJson(filesystem, fn, txDoc)) { + if (FilesystemUtils::storeJson(filesystem, fn, txDoc) != FilesystemUtils::StoreStatus::Success) { MO_DBG_ERR("FS error"); return false; } @@ -931,15 +1128,15 @@ bool TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent return true; } -bool TransactionStoreEvse::commit(Transaction *transaction) { +bool Ocpp201::TransactionStoreEvse::commit(Transaction *transaction) { return commit(*transaction, nullptr); } -bool TransactionStoreEvse::commit(TransactionEventData *txEvent) { +bool Ocpp201::TransactionStoreEvse::commit(TransactionEventData *txEvent) { return commit(*txEvent->transaction, txEvent); } -bool TransactionStoreEvse::remove(unsigned int txNr) { +bool Ocpp201::TransactionStoreEvse::remove(unsigned int txNr) { if (!filesystem) { MO_DBG_DEBUG("no FS: nothing to remove"); @@ -947,21 +1144,17 @@ bool TransactionStoreEvse::remove(unsigned int txNr) { } char fnPrefix [MO_MAX_PATH_SIZE]; - auto ret= snprintf(fnPrefix, sizeof(fnPrefix), "tx201-%u-%u-", evseId, txNr); + auto ret = snprintf(fnPrefix, sizeof(fnPrefix), "tx201-%.*u-%.*u-", + MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr); if (ret < 0 || (size_t)ret >= sizeof(fnPrefix)) { MO_DBG_ERR("fn error"); return false; } - size_t fnPrefixLen = strlen(fnPrefix); - auto success = FilesystemUtils::remove_if(filesystem, [fnPrefix, fnPrefixLen] (const char *fn) { - return !strncmp(fn, fnPrefix, fnPrefixLen); - }); - - return success; + return FilesystemUtils::removeByPrefix(filesystem, fnPrefix); } -bool TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) { +bool Ocpp201::TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) { if (tx.seqNos.empty()) { //nothing to do @@ -973,15 +1166,28 @@ bool TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) { //information is commited into tx201 file at seqNoEnd, then delete file at seqNo char fn [MO_MAX_PATH_SIZE]; - auto ret = snprintf(fn, sizeof(fn), "%stx201-%u-%u-%u.json", MO_FILENAME_PREFIX, evseId, tx.txNr, tx.seqNoEnd); - if (ret < 0 || (size_t)ret >= sizeof(fn)) { + if (!printTxEventFname(fn, sizeof(fn), evseId, tx.txNr, seqNo)) { MO_DBG_ERR("fn error"); return false; } - auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag()); + JsonDoc doc (0); + auto ret = FilesystemUtils::loadJson(filesystem, fn, doc, getMemoryTag()); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + break; + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", fn); + break; + } - if (!doc || !doc->containsKey("tx")) { + if (ret != FilesystemUtils::LoadStatus::Success || !doc.containsKey("tx")) { //no valid tx201 file at seqNoEnd. Commit tx into file seqNoEnd, then remove file at seqNo if (!commit(tx, nullptr)) { @@ -1008,15 +1214,20 @@ bool TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) { if (filesystem) { char fn [MO_MAX_PATH_SIZE]; - auto ret = snprintf(fn, sizeof(fn), "%stx201-%u-%u-%u.json", MO_FILENAME_PREFIX, evseId, tx.txNr, seqNo); - if (ret < 0 || (size_t)ret >= sizeof(fn)) { - MO_DBG_ERR("fn error"); + if (!printTxEventFname(fn, sizeof(fn), evseId, tx.txNr, seqNo)) { + MO_DBG_ERR("fn error: %s", fn); + return false; + } + + char path [MO_MAX_PATH_SIZE]; + if (!FilesystemUtils::printPath(filesystem, path, sizeof(path), fn)) { + MO_DBG_ERR("path error: %s", fn); return false; } size_t msize; - if (filesystem->stat(fn, &msize) == 0) { - success &= filesystem->remove(fn); + if (filesystem->stat(path, &msize) == 0) { + success &= filesystem->remove(path); } else { MO_DBG_ERR("internal error: seqNos out of sync"); (void)0; @@ -1037,28 +1248,45 @@ bool TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) { return success; } -TransactionStore::TransactionStore(std::shared_ptr filesystem, size_t numEvses) : - MemoryManaged{"v201.Transactions.TransactionStore"} { +Ocpp201::TransactionStore::TransactionStore(Context& context) : + MemoryManaged{"v201.Transactions.TransactionStore"}, context(context) { - for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID && (size_t)evseId < numEvses; evseId++) { - evses[evseId] = new TransactionStoreEvse(*this, evseId, filesystem); - } } -TransactionStore::~TransactionStore() { - for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) { +Ocpp201::TransactionStore::~TransactionStore() { + for (unsigned int evseId = 0; evseId < numEvseId; evseId++) { delete evses[evseId]; } } -TransactionStoreEvse *TransactionStore::getEvse(unsigned int evseId) { - if (evseId >= MO_NUM_EVSEID) { +bool Ocpp201::TransactionStore::setup() { + + numEvseId = context.getModel201().getNumEvseId(); + for (unsigned int i = 0; i < numEvseId; i++) { + if (!getEvse(i) || !getEvse(i)->setup()) { + MO_DBG_ERR("initialization error"); + return false; + } + } + + return true; +} + +Ocpp201::TransactionStoreEvse *Ocpp201::TransactionStore::getEvse(unsigned int evseId) { + if (evseId >= numEvseId) { + MO_DBG_ERR("evseId out of bound"); return nullptr; } + + if (!evses[evseId]) { + evses[evseId] = new TransactionStoreEvse(context, evseId); + if (!evses[evseId]) { + MO_DBG_ERR("OOM"); + return nullptr; + } + } + return evses[evseId]; } -} //namespace Ocpp201 -} //namespace MicroOcpp - #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.h b/src/MicroOcpp/Model/Transactions/TransactionStore.h index 133161c0..20215093 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.h +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.h @@ -5,59 +5,69 @@ #ifndef MO_TRANSACTIONSTORE_H #define MO_TRANSACTIONSTORE_H -#include -#include #include +#include #include +#include +#include -namespace MicroOcpp { +#if MO_OCPP_V16 -class TransactionStore; +#ifndef MO_STOPTXDATA_MAX_SIZE +#define MO_STOPTXDATA_MAX_SIZE 4 +#endif -class TransactionStoreEvse : public MemoryManaged { -private: - TransactionStore& context; - const unsigned int connectorId; +#ifndef MO_STOPTXDATA_DIGITS +#define MO_STOPTXDATA_DIGITS 1 //digits needed to print MO_STOPTXDATA_MAX_SIZE-1 (="3", i.e. 1 digit) +#endif - std::shared_ptr filesystem; - - Vector> transactions; +namespace MicroOcpp { -public: - TransactionStoreEvse(TransactionStore& context, unsigned int connectorId, std::shared_ptr filesystem); - TransactionStoreEvse(const TransactionStoreEvse&) = delete; - TransactionStoreEvse(TransactionStoreEvse&&) = delete; - TransactionStoreEvse& operator=(const TransactionStoreEvse&) = delete; +class Context; - ~TransactionStoreEvse(); +namespace Ocpp16 { - bool commit(Transaction *transaction); +class Transaction; - std::shared_ptr getTransaction(unsigned int txNr); - std::shared_ptr createTransaction(unsigned int txNr, bool silent = false); +namespace TransactionStore { - bool remove(unsigned int txNr); -}; +bool printTxFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr); + +FilesystemUtils::LoadStatus load(MO_FilesystemAdapter *filesystem, Context& context, unsigned int evseId, unsigned int txNr, Transaction& transaction); +FilesystemUtils::StoreStatus store(MO_FilesystemAdapter *filesystem, Context& context, Transaction& transaction); +bool remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr); -} +} //namespace TransactionStore +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_OCPP_V16 #if MO_ENABLE_V201 -#ifndef MO_TXEVENTRECORD_SIZE_V201 -#define MO_TXEVENTRECORD_SIZE_V201 10 //maximum number of of txEvents per tx to hold on flash storage +#ifndef MO_TXEVENTRECORD_SIZE +#define MO_TXEVENTRECORD_SIZE 10 //maximum number of of txEvents per tx to hold on flash storage +#endif + +#ifndef MO_TXEVENTRECORD_DIGITS +#define MO_TXEVENTRECORD_DIGITS 10 //digits needed to print MO_TXEVENTRECORD_SIZE-1 (="9", i.e. 1 digit) #endif namespace MicroOcpp { + +class Context; + namespace Ocpp201 { +class Transaction; +class TransactionEventData; class TransactionStore; class TransactionStoreEvse : public MemoryManaged { private: - TransactionStore& txStore; + Context& context; const unsigned int evseId; - std::shared_ptr filesystem; + MO_FilesystemAdapter *filesystem = nullptr; bool serializeTransaction(Transaction& tx, JsonObject out); bool serializeTransactionEvent(TransactionEventData& txEvent, JsonObject out); @@ -66,10 +76,12 @@ class TransactionStoreEvse : public MemoryManaged { bool commit(Transaction& transaction, TransactionEventData *transactionEvent); + bool printTxEventFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int seqNo); + public: - TransactionStoreEvse(TransactionStore& txStore, unsigned int evseId, std::shared_ptr filesystem); + TransactionStoreEvse(Context& context, unsigned int evseId); - bool discoverStoredTx(unsigned int& txNrBeginOut, unsigned int& txNrEndOut); + bool setup(); bool commit(Transaction *transaction); bool commit(TransactionEventData *transactionEvent); @@ -86,18 +98,21 @@ class TransactionStoreEvse : public MemoryManaged { class TransactionStore : public MemoryManaged { private: - TransactionStoreEvse *evses [MO_NUM_EVSEID] = {nullptr}; + Context& context; + TransactionStoreEvse* evses [MO_NUM_EVSEID] = {nullptr}; + unsigned int numEvseId = MO_NUM_EVSEID; public: - TransactionStore(std::shared_ptr filesystem, size_t numEvses); + TransactionStore(Context& context); ~TransactionStore(); + bool setup(); + TransactionStoreEvse *getEvse(unsigned int evseId); }; } //namespace Ocpp201 } //namespace MicroOcpp - #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Variables/Variable.cpp b/src/MicroOcpp/Model/Variables/Variable.cpp index ee3f0229..f5c1d891 100644 --- a/src/MicroOcpp/Model/Variables/Variable.cpp +++ b/src/MicroOcpp/Model/Variables/Variable.cpp @@ -6,17 +6,15 @@ * Implementation of the UCs B05 - B06 */ -#include - -#if MO_ENABLE_V201 - -#include - #include +#include #include +#if MO_ENABLE_V201 + using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; ComponentId::ComponentId(const char *name) : name(name) { } ComponentId::ComponentId(const char *name, EvseId evse) : name(name), evse(evse) { } @@ -147,7 +145,7 @@ void Variable::setRebootRequired() { void Variable::setMutability(Mutability m) { this->mutability = m; } -Variable::Mutability Variable::getMutability() { +Mutability Variable::getMutability() { return mutability; } @@ -369,7 +367,7 @@ class VariableString : public Variable { } }; -std::unique_ptr MicroOcpp::makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes) { +std::unique_ptr MicroOcpp::Ocpp201::makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes) { switch(dtype) { case Variable::InternalDataType::Int: if (supportAttributes.count() > 1) { @@ -395,4 +393,4 @@ std::unique_ptr MicroOcpp::makeVariable(Variable::InternalDataType dty return nullptr; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index 79178303..596ce08c 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -9,16 +9,16 @@ #ifndef MO_VARIABLE_H #define MO_VARIABLE_H -#include - -#if MO_ENABLE_V201 - #include #include #include -#include +#include +#include #include +#include + +#if MO_ENABLE_V201 #ifndef MO_VARIABLE_TYPECHECK #define MO_VARIABLE_TYPECHECK 1 @@ -26,6 +26,8 @@ namespace MicroOcpp { +namespace Ocpp201 { + // VariableCharacteristicsType (2.51) struct VariableCharacteristics : public MemoryManaged { @@ -147,13 +149,6 @@ class Variable : public MemoryManaged { AttributeTypeSet(AttributeType attrType = AttributeType::Actual); }; - //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, @@ -226,7 +221,7 @@ class Variable : public MemoryManaged { std::unique_ptr makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes); -} // namespace MicroOcpp - -#endif // MO_ENABLE_V201 +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.cpp b/src/MicroOcpp/Model/Variables/VariableContainer.cpp index 0d751d8f..d06021a5 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.cpp +++ b/src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -6,18 +6,16 @@ * Implementation of the UCs B05 - B06 */ -#include - -#if MO_ENABLE_V201 +#include #include #include - -#include - #include +#if MO_ENABLE_V201 + using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; VariableContainer::~VariableContainer() { @@ -105,7 +103,7 @@ bool VariableContainerOwning::add(std::unique_ptr variable) { return true; } -bool VariableContainerOwning::enablePersistency(std::shared_ptr filesystem, const char *filename) { +bool VariableContainerOwning::enablePersistency(MO_FilesystemAdapter *filesystem, const char *filename) { this->filesystem = filesystem; MO_FREE(this->filename); @@ -132,20 +130,24 @@ bool VariableContainerOwning::load() { return true; //persistency disabled - nothing to do } - size_t file_size = 0; - if (filesystem->stat(filename, &file_size) != 0 // file does not exist - || file_size == 0) { // file exists, but empty - MO_DBG_DEBUG("Populate FS: create variables file"); - return commit(); - } - - auto doc = FilesystemUtils::loadJson(filesystem, filename, getMemoryTag()); - if (!doc) { - MO_DBG_ERR("failed to load %s", filename); - return false; + JsonDoc doc (0); + auto ret = FilesystemUtils::loadJson(filesystem, filename, doc, getMemoryTag()); + switch (ret) { + case FilesystemUtils::LoadStatus::Success: + break; //continue loading JSON + case FilesystemUtils::LoadStatus::FileNotFound: + MO_DBG_DEBUG("Populate FS: create variables file"); + return commit(); + case FilesystemUtils::LoadStatus::ErrOOM: + MO_DBG_ERR("OOM"); + return false; + case FilesystemUtils::LoadStatus::ErrFileCorruption: + case FilesystemUtils::LoadStatus::ErrOther: + MO_DBG_ERR("failed to load %s", filename); + return false; } - JsonArray variablesJson = (*doc)["variables"]; + JsonArray variablesJson = doc["variables"]; for (JsonObject stored : variablesJson) { @@ -280,8 +282,8 @@ bool VariableContainerOwning::commit() { } } - - bool success = FilesystemUtils::storeJson(filesystem, filename, doc); + auto ret = FilesystemUtils::storeJson(filesystem, filename, doc); + bool success = (ret == FilesystemUtils::StoreStatus::Success); if (success) { MO_DBG_DEBUG("Saving variables finished"); @@ -292,4 +294,4 @@ bool VariableContainerOwning::commit() { return success; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.h b/src/MicroOcpp/Model/Variables/VariableContainer.h index d4dd0c69..339d8460 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.h +++ b/src/MicroOcpp/Model/Variables/VariableContainer.h @@ -9,17 +9,17 @@ #ifndef MO_VARIABLECONTAINER_H #define MO_VARIABLECONTAINER_H -#include - -#if MO_ENABLE_V201 - #include #include #include #include +#include + +#if MO_ENABLE_V201 namespace MicroOcpp { +namespace Ocpp201 { class VariableContainer { public: @@ -47,7 +47,7 @@ class VariableContainerNonOwning : public VariableContainer, public MemoryManage class VariableContainerOwning : public VariableContainer, public MemoryManaged { private: Vector> variables; - std::shared_ptr filesystem; + MO_FilesystemAdapter *filesystem = nullptr; char *filename = nullptr; uint16_t trackWriteCount = 0; @@ -65,12 +65,12 @@ class VariableContainerOwning : public VariableContainer, public MemoryManaged { bool add(std::unique_ptr variable); - bool enablePersistency(std::shared_ptr filesystem, const char *filename); + bool enablePersistency(MO_FilesystemAdapter *filesystem, const char *filename); bool load(); //load variables from flash bool commit() override; }; -} //end namespace MicroOcpp - +} //namespace Ocpp201 +} //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp index 9c0d6fde..b9611eb9 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.cpp +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -6,28 +6,30 @@ * Implementation of the UCs B05 - B06 */ -#include - -#if MO_ENABLE_V201 +#include +#include #include -#include +#include #include #include #include #include #include +#include -#include -#include +#if MO_ENABLE_V201 -#include +#ifndef MO_GETBASEREPORT_CHUNKSIZE +#define MO_GETBASEREPORT_CHUNKSIZE 4 +#endif namespace MicroOcpp { +namespace Ocpp201 { template VariableValidator::VariableValidator(const ComponentId& component, const char *name, bool (*validateFn)(T, void*), void *userPtr) : - MemoryManaged("v201.Variables.VariableValidator.", name), component(component), name(name), userPtr(userPtr), validateFn(validateFn) { + component(component), name(name), userPtr(userPtr), validateFn(validateFn) { } @@ -121,34 +123,114 @@ Variable *VariableService::getVariable(const ComponentId& component, const char return nullptr; } -VariableService::VariableService(Context& context, std::shared_ptr filesystem) : +VariableService::VariableService(Context& context) : MemoryManaged("v201.Variables.VariableService"), - context(context), filesystem(filesystem), + context(context), containers(makeVector(getMemoryTag())), validatorInt(makeVector>(getMemoryTag())), validatorBool(makeVector>(getMemoryTag())), - validatorString(makeVector>(getMemoryTag())) { - + validatorString(makeVector>(getMemoryTag())), + getBaseReportVars(makeVector(getMemoryTag())) { + +} + +bool VariableService::init() { containers.reserve(MO_VARIABLESTORE_BUCKETS + 1); + if (containers.capacity() < MO_VARIABLESTORE_BUCKETS + 1) { + MO_DBG_ERR("OOM"); + return false; + } + + for (unsigned int i = 0; i < MO_VARIABLESTORE_BUCKETS; i++) { + containers.push_back(&containersInternal[i]); + } + containers.push_back(&containerExternal); + + return true; +} + +bool VariableService::setup() { + + filesystem = context.getFilesystem(); + if (!filesystem) { + MO_DBG_DEBUG("no fs access"); + } for (unsigned int i = 0; i < MO_VARIABLESTORE_BUCKETS; i++) { char fn [MO_MAX_PATH_SIZE]; auto ret = snprintf(fn, sizeof(fn), "%s%02x%s", MO_VARIABLESTORE_FN_PREFIX, i, MO_VARIABLESTORE_FN_SUFFIX); if (ret < 0 || (size_t)ret >= sizeof(fn)) { MO_DBG_ERR("fn error"); - continue; + return false; } containersInternal[i].enablePersistency(filesystem, fn); - containers.push_back(&containersInternal[i]); + + if (!containersInternal[i].load()) { + MO_DBG_ERR("failure to load %"); + return false; + } } - containers.push_back(&containerExternal); - context.getOperationRegistry().registerOperation("SetVariables", [this] () { - return new Ocpp201::SetVariables(*this);}); - context.getOperationRegistry().registerOperation("GetVariables", [this] () { - return new Ocpp201::GetVariables(*this);}); - context.getOperationRegistry().registerOperation("GetBaseReport", [this] () { - return new Ocpp201::GetBaseReport(*this);}); + context.getMessageService().registerOperation("SetVariables", [] (Context& context) -> Operation* { + return new SetVariables(*context.getModel201().getVariableService());}); + context.getMessageService().registerOperation("GetVariables", [] (Context& context) -> Operation* { + return new GetVariables(*context.getModel201().getVariableService());}); + context.getMessageService().registerOperation("GetBaseReport", [] (Context& context) -> Operation* { + return new GetBaseReport(*context.getModel201().getVariableService());}); + + return true; +} + +void VariableService::loop() { + + if (notifyReportInProgress) { + return; + } + + if (getBaseReportVars.empty()) { + // all done + return; + } + + auto variablesChunk = makeVector(getMemoryTag()); + variablesChunk.reserve(MO_GETBASEREPORT_CHUNKSIZE); + if (variablesChunk.capacity() < MO_GETBASEREPORT_CHUNKSIZE) { + MO_DBG_ERR("OOM"); + getBaseReportVars.clear(); + return; + } + + for (size_t i = 0; i < MO_GETBASEREPORT_CHUNKSIZE && !getBaseReportVars.empty(); i++) { + variablesChunk.push_back(getBaseReportVars.back()); + getBaseReportVars.pop_back(); + } + + auto notifyReport = makeRequest(context, new NotifyReport( + context, + getBaseReportRequestId, + context.getClock().now(), + !getBaseReportVars.empty(), // tbc: to be continued + getBaseReportSeqNo, + variablesChunk)); + + if (!notifyReport) { + MO_DBG_ERR("OOM"); + getBaseReportVars.clear(); + return; + } + + getBaseReportSeqNo++; + + notifyReport->setOnReceiveConf([this] (JsonObject) { + notifyReportInProgress = false; + }); + notifyReport->setOnAbort([this] () { + notifyReportInProgress = false; + }); + + notifyReportInProgress = true; + + context.getMessageService().sendRequest(std::move(notifyReport)); } template @@ -171,8 +253,8 @@ bool loadVariableFactoryDefault(Variable& variable, const char *fac return variable.setString(factoryDef); } -void loadVariableCharacteristics(Variable& variable, Variable::Mutability mutability, bool persistent, bool rebootRequired, Variable::InternalDataType defaultDataType) { - if (variable.getMutability() == Variable::Mutability::ReadWrite) { +void loadVariableCharacteristics(Variable& variable, Mutability mutability, bool persistent, bool rebootRequired, Variable::InternalDataType defaultDataType) { + if (variable.getMutability() == Mutability::ReadWrite) { variable.setMutability(mutability); } @@ -186,13 +268,13 @@ void loadVariableCharacteristics(Variable& variable, Variable::Mutability mutabi switch (defaultDataType) { case Variable::InternalDataType::Int: - variable.setVariableDataType(MicroOcpp::VariableCharacteristics::DataType::integer); + variable.setVariableDataType(VariableCharacteristics::DataType::integer); break; case Variable::InternalDataType::Bool: - variable.setVariableDataType(MicroOcpp::VariableCharacteristics::DataType::boolean); + variable.setVariableDataType(VariableCharacteristics::DataType::boolean); break; case Variable::InternalDataType::String: - variable.setVariableDataType(MicroOcpp::VariableCharacteristics::DataType::string); + variable.setVariableDataType(VariableCharacteristics::DataType::string); break; default: MO_DBG_ERR("internal error"); @@ -208,7 +290,7 @@ template<> Variable::InternalDataType getInternalDataType() {return Variab template<> Variable::InternalDataType getInternalDataType() {return Variable::InternalDataType::String;} template -Variable *VariableService::declareVariable(const ComponentId& component, const char *name, T factoryDefault, Variable::Mutability mutability, bool persistent, Variable::AttributeTypeSet attributes, bool rebootRequired) { +Variable *VariableService::declareVariable(const ComponentId& component, const char *name, T factoryDefault, Mutability mutability, bool persistent, Variable::AttributeTypeSet attributes, bool rebootRequired) { auto res = getVariable(component, name); if (!res) { @@ -236,9 +318,9 @@ Variable *VariableService::declareVariable(const ComponentId& component, const c return res; } -template Variable *VariableService::declareVariable( const ComponentId&, const char*, int, Variable::Mutability, bool, Variable::AttributeTypeSet, bool); -template Variable *VariableService::declareVariable( const ComponentId&, const char*, bool, Variable::Mutability, bool, Variable::AttributeTypeSet, bool); -template Variable *VariableService::declareVariable(const ComponentId&, const char*, const char*, Variable::Mutability, bool, Variable::AttributeTypeSet, bool); +template Variable *VariableService::declareVariable( const ComponentId&, const char*, int, Mutability, bool, Variable::AttributeTypeSet, bool); +template Variable *VariableService::declareVariable( const ComponentId&, const char*, bool, Mutability, bool, Variable::AttributeTypeSet, bool); +template Variable *VariableService::declareVariable(const ComponentId&, const char*, const char*, Mutability, bool, Variable::AttributeTypeSet, bool); bool VariableService::addVariable(Variable *variable) { return containerExternal.add(variable); @@ -248,18 +330,6 @@ bool VariableService::addVariable(std::unique_ptr variable) { return getContainerInternalByVariable(variable->getComponentId(), variable->getName()).add(std::move(variable)); } -bool VariableService::load() { - bool success = true; - - for (size_t i = 0; i < MO_VARIABLESTORE_BUCKETS; i++) { - if (!containersInternal[i].load()) { - success = false; - } - } - - return success; -} - bool VariableService::commit() { bool success = true; @@ -308,7 +378,7 @@ SetVariableStatus VariableService::setVariable(Variable::AttributeType attrType, } } - if (variable->getMutability() == Variable::Mutability::ReadOnly) { + if (variable->getMutability() == Mutability::ReadOnly) { return SetVariableStatus::Rejected; } @@ -424,7 +494,7 @@ GetVariableStatus VariableService::getVariable(Variable::AttributeType attrType, if (!strcmp(variable->getName(), variableName)) { // found variable. Search terminated in this block - if (variable->getMutability() == Variable::Mutability::WriteOnly) { + if (variable->getMutability() == Mutability::WriteOnly) { return GetVariableStatus::Rejected; } @@ -452,7 +522,10 @@ GenericDeviceModelStatus VariableService::getBaseReport(int requestId, ReportBas return GenericDeviceModelStatus_NotSupported; } - Vector variables = makeVector(getMemoryTag()); + if (!getBaseReportVars.empty()) { + MO_DBG_ERR("request new base report while old is still pending"); + return GenericDeviceModelStatus_Rejected; + } for (size_t i = 0; i < containers.size(); i++) { auto container = containers[i]; @@ -460,31 +533,24 @@ GenericDeviceModelStatus VariableService::getBaseReport(int requestId, ReportBas for (size_t i = 0; i < container->size(); i++) { auto variable = container->getVariable(i); - if (reportBase == ReportBase_ConfigurationInventory && variable->getMutability() == Variable::Mutability::ReadOnly) { + if (reportBase == ReportBase_ConfigurationInventory && variable->getMutability() == Mutability::ReadOnly) { continue; } - variables.push_back(variable); + getBaseReportVars.push_back(variable); } } - if (variables.empty()) { + if (getBaseReportVars.empty()) { return GenericDeviceModelStatus_EmptyResultSet; } - auto notifyReport = makeRequest(new Ocpp201::NotifyReport( - context.getModel(), - requestId, - context.getModel().getClock().now(), - false, - 0, - variables)); - - context.initiateRequest(std::move(notifyReport)); + getBaseReportRequestId = requestId; + getBaseReportSeqNo = 0; return GenericDeviceModelStatus_Accepted; } -} // namespace MicroOcpp - -#endif // MO_ENABLE_V201 +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/VariableService.h b/src/MicroOcpp/Model/Variables/VariableService.h index afe19099..2641f3bb 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.h +++ b/src/MicroOcpp/Model/Variables/VariableService.h @@ -13,27 +13,34 @@ #include #include -#include - -#if MO_ENABLE_V201 - #include #include #include #include +#include + +#if MO_ENABLE_V201 #ifndef MO_VARIABLESTORE_FN_PREFIX -#define MO_VARIABLESTORE_FN_PREFIX (MO_FILENAME_PREFIX "ocpp-vars-") +#define MO_VARIABLESTORE_FN_PREFIX "ocpp-vars-" #endif #ifndef MO_VARIABLESTORE_FN_SUFFIX #define MO_VARIABLESTORE_FN_SUFFIX ".jsn" #endif +#ifndef MO_VARIABLESTORE_BUCKETS +#define MO_VARIABLESTORE_BUCKETS 8 +#endif + namespace MicroOcpp { +class Context; + +namespace Ocpp201 { + template -struct VariableValidator : public MemoryManaged { +struct VariableValidator { ComponentId component; const char *name; void *userPtr; @@ -42,16 +49,10 @@ struct VariableValidator : public MemoryManaged { bool validate(T); }; -class Context; - -#ifndef MO_VARIABLESTORE_BUCKETS -#define MO_VARIABLESTORE_BUCKETS 8 -#endif - class VariableService : public MemoryManaged { private: Context& context; - std::shared_ptr filesystem; + MO_FilesystemAdapter *filesystem = nullptr; Vector containers; VariableContainerNonOwning containerExternal; VariableContainerOwning containersInternal [MO_VARIABLESTORE_BUCKETS]; @@ -64,12 +65,19 @@ class VariableService : public MemoryManaged { VariableValidator *getValidatorInt(const ComponentId& component, const char *name); VariableValidator *getValidatorBool(const ComponentId& component, const char *name); VariableValidator *getValidatorString(const ComponentId& component, const char *name); + + Vector getBaseReportVars; + int getBaseReportRequestId = -1; + unsigned int getBaseReportSeqNo = 0; + bool notifyReportInProgress = false; public: - VariableService(Context& context, std::shared_ptr filesystem); + VariableService(Context& context); + + bool init(); //Get Variable. If not existent, create Variable owned by MO and return template - Variable *declareVariable(const ComponentId& component, const char *name, T factoryDefault, Variable::Mutability mutability = Variable::Mutability::ReadWrite, bool persistent = true, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false); + Variable *declareVariable(const ComponentId& component, const char *name, T factoryDefault, Mutability mutability = Mutability::ReadWrite, bool persistent = true, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false); bool addVariable(Variable *variable); //Add Variable without transferring ownership bool addVariable(std::unique_ptr variable); //Add Variable and transfer ownership @@ -77,14 +85,17 @@ class VariableService : public MemoryManaged { //Get Variable. If not existent, return nullptr Variable *getVariable(const ComponentId& component, const char *name); - bool load(); - bool commit(); - void addContainer(VariableContainer *container); template bool registerValidator(const ComponentId& component, const char *name, bool (*validate)(T, void*), void *userPtr = nullptr); + bool setup(); + + void loop(); + + bool commit(); + 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); @@ -92,8 +103,7 @@ class VariableService : public MemoryManaged { GenericDeviceModelStatus getBaseReport(int requestId, ReportBase reportBase); }; -} // namespace MicroOcpp - -#endif // MO_ENABLE_V201 - +} //namespace MicroOcpp +} //namespace Ocpp201 +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/Authorize.cpp b/src/MicroOcpp/Operations/Authorize.cpp index e12050be..8846a667 100644 --- a/src/MicroOcpp/Operations/Authorize.cpp +++ b/src/MicroOcpp/Operations/Authorize.cpp @@ -10,10 +10,9 @@ using namespace MicroOcpp; -namespace MicroOcpp { -namespace Ocpp16 { +#if MO_ENABLE_V16 -Authorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged("v16.Operation.", "Authorize"), model(model) { +Ocpp16::Authorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged("v16.Operation.", "Authorize"), model(model) { if (idTagIn && strnlen(idTagIn, IDTAG_LEN_MAX + 2) <= IDTAG_LEN_MAX) { snprintf(idTag, IDTAG_LEN_MAX + 1, "%s", idTagIn); } else { @@ -21,18 +20,18 @@ Authorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged("v16.Ope } } -const char* Authorize::getOperationType(){ +const char* Ocpp16::Authorize::getOperationType(){ return "Authorize"; } -std::unique_ptr Authorize::createReq() { +std::unique_ptr Ocpp16::Authorize::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + (IDTAG_LEN_MAX + 1)); JsonObject payload = doc->to(); payload["idTag"] = idTag; return doc; } -void Authorize::processConf(JsonObject payload){ +void Ocpp16::Authorize::processConf(JsonObject payload){ const char *idTagInfo = payload["idTagInfo"]["status"] | "not specified"; if (!strcmp(idTagInfo, "Accepted")) { @@ -48,37 +47,27 @@ void Authorize::processConf(JsonObject payload){ #endif //MO_ENABLE_LOCAL_AUTH } -void Authorize::processReq(JsonObject payload){ - /* - * Ignore Contents of this Req-message, because this is for debug purposes only - */ +#if MO_ENABLE_MOCK_SERVER +int Ocpp16::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { + (void)userStatus; + (void)userData; + return snprintf(buf, size, "{\"idTagInfo\":{\"status\":\"Accepted\"}}"); } +#endif //MO_ENABLE_MOCK_SERVER -std::unique_ptr Authorize::createConf(){ - auto doc = makeJsonDoc(getMemoryTag(), 2 * JSON_OBJECT_SIZE(1)); - JsonObject payload = doc->to(); - JsonObject idTagInfo = payload.createNestedObject("idTagInfo"); - idTagInfo["status"] = "Accepted"; - return doc; -} - -} // namespace Ocpp16 -} // namespace MicroOcpp +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -namespace MicroOcpp { -namespace Ocpp201 { - -Authorize::Authorize(Model& model, const IdToken& idToken) : MemoryManaged("v201.Operation.Authorize"), model(model) { +Ocpp201::Authorize::Authorize(Model& model, const IdToken& idToken) : MemoryManaged("v201.Operation.Authorize"), model(model) { this->idToken = idToken; } -const char* Authorize::getOperationType(){ +const char* Ocpp201::Authorize::getOperationType(){ return "Authorize"; } -std::unique_ptr Authorize::createReq() { +std::unique_ptr Ocpp201::Authorize::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2)); @@ -88,7 +77,7 @@ std::unique_ptr Authorize::createReq() { return doc; } -void Authorize::processConf(JsonObject payload){ +void Ocpp201::Authorize::processConf(JsonObject payload){ const char *idTagInfo = payload["idTokenInfo"]["status"] | "_Undefined"; if (!strcmp(idTagInfo, "Accepted")) { @@ -102,21 +91,12 @@ void Authorize::processConf(JsonObject payload){ //} } -void Authorize::processReq(JsonObject payload){ - /* - * Ignore Contents of this Req-message, because this is for debug purposes only - */ +#if MO_ENABLE_MOCK_SERVER +int Ocpp201::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { + (void)userStatus; + (void)userData; + return snprintf(buf, size, "{\"idTokenInfo\":{\"status\":\"Accepted\"}}"); } - -std::unique_ptr Authorize::createConf(){ - auto doc = makeJsonDoc(getMemoryTag(), 2 * JSON_OBJECT_SIZE(1)); - JsonObject payload = doc->to(); - JsonObject idTagInfo = payload.createNestedObject("idTokenInfo"); - idTagInfo["status"] = "Accepted"; - return doc; -} - -} // namespace Ocpp201 -} // namespace MicroOcpp +#endif //MO_ENABLE_MOCK_SERVER #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/Authorize.h b/src/MicroOcpp/Operations/Authorize.h index b49226ef..fc743916 100644 --- a/src/MicroOcpp/Operations/Authorize.h +++ b/src/MicroOcpp/Operations/Authorize.h @@ -2,19 +2,20 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#ifndef AUTHORIZE_H -#define AUTHORIZE_H +#ifndef MO_AUTHORIZE_H +#define MO_AUTHORIZE_H #include #include #include +#if MO_ENABLE_V16 + namespace MicroOcpp { +namespace Ocpp16 { class Model; -namespace Ocpp16 { - class Authorize : public Operation, public MemoryManaged { private: Model& model; @@ -28,14 +29,16 @@ class Authorize : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; - void processReq(JsonObject payload) override; - - std::unique_ptr createConf() override; +#if MO_ENABLE_MOCK_SERVER + static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); +#endif }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 @@ -44,6 +47,8 @@ class Authorize : public Operation, public MemoryManaged { namespace MicroOcpp { namespace Ocpp201 { +class Model; + class Authorize : public Operation, public MemoryManaged { private: Model& model; @@ -57,14 +62,14 @@ class Authorize : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; - void processReq(JsonObject payload) override; - - std::unique_ptr createConf() override; +#if MO_ENABLE_MOCK_SERVER + static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); +#endif }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp +} //namespace Ocpp201 +} //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index 1f4b7c77..2a61f237 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -3,18 +3,21 @@ // MIT License #include + +#include #include #include -#include +#include #include #include #include -using MicroOcpp::Ocpp16::BootNotification; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 || MO_ENABLE_V201 + +using namespace MicroOcpp; -BootNotification::BootNotification(Model& model, std::unique_ptr payload) : MemoryManaged("v16.Operation.", "BootNotification"), model(model), credentials(std::move(payload)) { +BootNotification::BootNotification(Context& context, BootService& bootService, HeartbeatService *heartbeatService, const MO_BootNotificationData& bnData) : MemoryManaged("v16/v201.Operation.", "BootNotification"), context(context), bootService(bootService), heartbeatService(heartbeatService), bnData(bnData), ocppVersion(context.getOcppVersion()) { } @@ -23,27 +26,48 @@ const char* BootNotification::getOperationType(){ } std::unique_ptr BootNotification::createReq() { - if (credentials) { -#if MO_ENABLE_V201 - if (model.getVersion().major == 2) { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2) + credentials->memoryUsage()); - JsonObject payload = doc->to(); - payload["reason"] = "PowerUp"; - payload["chargingStation"] = *credentials; - return doc; + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(9)); + JsonObject payload = doc->to(); + if (bnData.chargePointModel) {payload["chargePointModel"] = bnData.chargePointModel;} + if (bnData.chargePointVendor) {payload["chargePointVendor"] = bnData.chargePointVendor;} + if (bnData.firmwareVersion) {payload["firmwareVersion"] = bnData.firmwareVersion;} + if (bnData.chargePointSerialNumber) {payload["chargePointSerialNumber"] = bnData.chargePointSerialNumber;} + if (bnData.meterSerialNumber) {payload["meterSerialNumber"] = bnData.meterSerialNumber;} + if (bnData.meterType) {payload["meterType"] = bnData.meterType;} + if (bnData.chargeBoxSerialNumber) {payload["chargeBoxSerialNumber"] = bnData.chargeBoxSerialNumber;} + if (bnData.iccid) {payload["iccid"] = bnData.iccid;} + if (bnData.imsi) {payload["imsi"] = bnData.imsi;} + return doc; + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(2)); + JsonObject payload = doc->to(); + if (bnData.chargePointSerialNumber) {payload["serialNumber"] = bnData.chargePointSerialNumber;} + if (bnData.chargePointModel) {payload["model"] = bnData.chargePointModel;} + if (bnData.chargePointVendor) {payload["vendorName"] = bnData.chargePointVendor;} + if (bnData.firmwareVersion) {payload["firmwareVersion"] = bnData.firmwareVersion;} + if (bnData.iccid || bnData.imsi) { + JsonObject modem = payload.createNestedObject("modem"); + if (bnData.iccid) {modem["iccid"] = bnData.iccid;} + if (bnData.imsi) {modem["imsi"] = bnData.imsi;} } -#endif - return std::unique_ptr(new JsonDoc(*credentials)); - } else { - MO_DBG_ERR("payload undefined"); - return createEmptyDocument(); + return doc; } + #endif //MO_ENABLE_V201 + + MO_DBG_ERR("internal error"); + return createEmptyDocument(); } void BootNotification::processConf(JsonObject payload) { const char* currentTime = payload["currentTime"] | "Invalid"; if (strcmp(currentTime, "Invalid")) { - if (model.getClock().setTime(currentTime)) { + if (context.getClock().setTime(currentTime)) { //success } else { MO_DBG_ERR("Time string format violation. Expect format like 2022-02-01T20:53:32.486Z"); @@ -70,55 +94,37 @@ void BootNotification::processConf(JsonObject payload) { } if (status == RegistrationStatus::Accepted) { - //only write if in valid range - if (interval >= 1) { - auto heartbeatIntervalInt = declareConfiguration("HeartbeatInterval", 86400); - if (heartbeatIntervalInt && interval != heartbeatIntervalInt->getInt()) { - heartbeatIntervalInt->setInt(interval); - configuration_save(); - } + if (heartbeatService) { + heartbeatService->setHeartbeatInterval(interval); } } - if (auto bootService = model.getBootService()) { - - if (status != RegistrationStatus::Accepted) { - bootService->setRetryInterval(interval); - } - - bootService->notifyRegistrationStatus(status); + if (status != RegistrationStatus::Accepted) { + bootService.setRetryInterval(interval); } + bootService.notifyRegistrationStatus(status); MO_DBG_INFO("request has been %s", status == RegistrationStatus::Accepted ? "Accepted" : status == RegistrationStatus::Pending ? "replied with Pending" : "Rejected"); } -void BootNotification::processReq(JsonObject payload){ - /* - * Ignore Contents of this Req-message, because this is for debug purposes only - */ -} +#if MO_ENABLE_MOCK_SERVER +int BootNotification::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { + (void)userStatus; -std::unique_ptr BootNotification::createConf(){ - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(3) + (JSONDATE_LENGTH + 1)); - JsonObject payload = doc->to(); - - //safety mechanism; in some test setups the library has to answer BootNotifications without valid system time - Timestamp ocppTimeReference = Timestamp(2022,0,27,11,59,55); - Timestamp ocppSelect = ocppTimeReference; - auto& ocppTime = model.getClock(); - Timestamp ocppNow = ocppTime.now(); - if (ocppNow > ocppTimeReference) { - //time has already been set - ocppSelect = ocppNow; - } + auto& context = *reinterpret_cast(userData); - char ocppNowJson [JSONDATE_LENGTH + 1] = {'\0'}; - ocppSelect.toJsonString(ocppNowJson, JSONDATE_LENGTH + 1); - payload["currentTime"] = ocppNowJson; + char timeStr [MO_JSONDATE_SIZE]; - payload["interval"] = 86400; //heartbeat send interval - not relevant for JSON variant of OCPP so send dummy value that likely won't break - payload["status"] = "Accepted"; - return doc; + if (context.getClock().now().isUnixTime()) { + context.getClock().toJsonString(context.getClock().now(), timeStr, sizeof(timeStr)); + } else { + (void)snprintf(timeStr, sizeof(timeStr), "2025-05-18T18:55:13Z"); + } + + return snprintf(buf, size, "{\"currentTime\":\"%s\",\"interval\":86400,\"status\":\"Accepted\"}", timeStr); } +#endif //MO_ENABLE_MOCK_SERVER + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/BootNotification.h b/src/MicroOcpp/Operations/BootNotification.h index c560e03f..f0212985 100644 --- a/src/MicroOcpp/Operations/BootNotification.h +++ b/src/MicroOcpp/Operations/BootNotification.h @@ -5,8 +5,12 @@ #ifndef MO_BOOTNOTIFICATION_H #define MO_BOOTNOTIFICATION_H +#include #include #include +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 #define CP_MODEL_LEN_MAX CiString20TypeLen #define CP_SERIALNUMBER_LEN_MAX CiString25TypeLen @@ -15,17 +19,20 @@ namespace MicroOcpp { -class Model; - -namespace Ocpp16 { +class Context; +class BootService; +class HeartbeatService; class BootNotification : public Operation, public MemoryManaged { private: - Model& model; - std::unique_ptr credentials; + Context& context; + BootService& bootService; + HeartbeatService *heartbeatService = nullptr; + const MO_BootNotificationData& bnData; + int ocppVersion = -1; const char *errorCode = nullptr; public: - BootNotification(Model& model, std::unique_ptr payload); + BootNotification(Context& context, BootService& bootService, HeartbeatService *heartbeatService, const MO_BootNotificationData& bnData); ~BootNotification() = default; @@ -35,14 +42,14 @@ class BootNotification : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; - void processReq(JsonObject payload) override; - - std::unique_ptr createConf() override; - const char *getErrorCode() override {return errorCode;} + +#if MO_ENABLE_MOCK_SERVER + static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); +#endif }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/CancelReservation.cpp b/src/MicroOcpp/Operations/CancelReservation.cpp index 7741d9b9..6f331e5d 100644 --- a/src/MicroOcpp/Operations/CancelReservation.cpp +++ b/src/MicroOcpp/Operations/CancelReservation.cpp @@ -4,14 +4,14 @@ #include -#if MO_ENABLE_RESERVATION +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION #include #include #include -using MicroOcpp::Ocpp16::CancelReservation; -using MicroOcpp::JsonDoc; +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; CancelReservation::CancelReservation(ReservationService& reservationService) : MemoryManaged("v16.Operation.", "CancelReservation"), reservationService(reservationService) { @@ -44,4 +44,4 @@ std::unique_ptr CancelReservation::createConf(){ return doc; } -#endif //MO_ENABLE_RESERVATION +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION diff --git a/src/MicroOcpp/Operations/CancelReservation.h b/src/MicroOcpp/Operations/CancelReservation.h index 0e39bb60..e2386f90 100644 --- a/src/MicroOcpp/Operations/CancelReservation.h +++ b/src/MicroOcpp/Operations/CancelReservation.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_CANCELRESERVATION_H @@ -7,16 +7,15 @@ #include -#if MO_ENABLE_RESERVATION +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION #include namespace MicroOcpp { +namespace Ocpp16 { class ReservationService; -namespace Ocpp16 { - class CancelReservation : public Operation, public MemoryManaged { private: ReservationService& reservationService; @@ -34,8 +33,8 @@ class CancelReservation : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp -#endif //MO_ENABLE_RESERVATION +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION #endif diff --git a/src/MicroOcpp/Operations/ChangeAvailability.cpp b/src/MicroOcpp/Operations/ChangeAvailability.cpp index 7377657c..4bba4ac2 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.cpp +++ b/src/MicroOcpp/Operations/ChangeAvailability.cpp @@ -3,24 +3,23 @@ // MIT License #include + +#include #include -#include -#include -#include +using namespace MicroOcpp; -namespace MicroOcpp { -namespace Ocpp16 { +#if MO_ENABLE_V16 -ChangeAvailability::ChangeAvailability(Model& model) : MemoryManaged("v16.Operation.", "ChangeAvailability"), model(model) { +Ocpp16::ChangeAvailability::ChangeAvailability(Model& model) : MemoryManaged("v16.Operation.", "ChangeAvailability"), model(model) { } -const char* ChangeAvailability::getOperationType(){ +const char* Ocpp16::ChangeAvailability::getOperationType(){ return "ChangeAvailability"; } -void ChangeAvailability::processReq(JsonObject payload) { +void Ocpp16::ChangeAvailability::processReq(JsonObject payload) { int connectorIdRaw = payload["connectorId"] | -1; if (connectorIdRaw < 0) { errorCode = "FormationViolation"; @@ -28,7 +27,7 @@ void ChangeAvailability::processReq(JsonObject payload) { } unsigned int connectorId = (unsigned int)connectorIdRaw; - if (connectorId >= model.getNumConnectors()) { + if (connectorId >= model.getNumEvseId()) { errorCode = "PropertyConstraintViolation"; return; } @@ -48,23 +47,29 @@ void ChangeAvailability::processReq(JsonObject payload) { } if (connectorId == 0) { - for (unsigned int cId = 0; cId < model.getNumConnectors(); cId++) { - auto connector = model.getConnector(cId); - connector->setAvailability(available); - if (connector->isOperative() && !available) { - scheduled = true; + for (unsigned int eId = 0; eId < model.getNumEvseId(); eId++) { + auto availSvc = model.getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(eId) : nullptr; + if (availSvcEvse) { + availSvcEvse->setAvailability(available); + if (availSvcEvse->isOperative() && !available) { + scheduled = true; + } } } } else { - auto connector = model.getConnector(connectorId); - connector->setAvailability(available); - if (connector->isOperative() && !available) { - scheduled = true; + auto availSvc = model.getAvailabilityService(); + auto availSvcEvse = availSvc ? availSvc->getEvse(connectorId) : nullptr; + if (availSvcEvse) { + availSvcEvse->setAvailability(available); + if (availSvcEvse->isOperative() && !available) { + scheduled = true; + } } } } -std::unique_ptr ChangeAvailability::createConf(){ +std::unique_ptr Ocpp16::ChangeAvailability::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); if (!accepted) { @@ -78,25 +83,19 @@ std::unique_ptr ChangeAvailability::createConf(){ return doc; } -} // namespace Ocpp16 -} // namespace MicroOcpp +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -#include - -namespace MicroOcpp { -namespace Ocpp201 { - -ChangeAvailability::ChangeAvailability(AvailabilityService& availabilityService) : MemoryManaged("v201.Operation.", "ChangeAvailability"), availabilityService(availabilityService) { +Ocpp201::ChangeAvailability::ChangeAvailability(AvailabilityService& availabilityService) : MemoryManaged("v201.Operation.", "ChangeAvailability"), availabilityService(availabilityService) { } -const char* ChangeAvailability::getOperationType(){ +const char* Ocpp201::ChangeAvailability::getOperationType(){ return "ChangeAvailability"; } -void ChangeAvailability::processReq(JsonObject payload) { +void Ocpp201::ChangeAvailability::processReq(JsonObject payload) { unsigned int evseId = 0; @@ -136,7 +135,7 @@ void ChangeAvailability::processReq(JsonObject payload) { status = availabilityEvse->changeAvailability(operative); } -std::unique_ptr ChangeAvailability::createConf(){ +std::unique_ptr Ocpp201::ChangeAvailability::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); @@ -155,7 +154,4 @@ std::unique_ptr ChangeAvailability::createConf(){ return doc; } -} // namespace Ocpp201 -} // namespace MicroOcpp - #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/ChangeAvailability.h b/src/MicroOcpp/Operations/ChangeAvailability.h index 08914e52..ec1a54e1 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.h +++ b/src/MicroOcpp/Operations/ChangeAvailability.h @@ -1,18 +1,20 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_CHANGEAVAILABILITY_H #define MO_CHANGEAVAILABILITY_H #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { +namespace Ocpp16 { class Model; -namespace Ocpp16 { - class ChangeAvailability : public Operation, public MemoryManaged { private: Model& model; @@ -32,20 +34,21 @@ class ChangeAvailability : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp + +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -#include +#include #include namespace MicroOcpp { +namespace Ocpp201 { class AvailabilityService; -namespace Ocpp201 { - class ChangeAvailability : public Operation, public MemoryManaged { private: AvailabilityService& availabilityService; @@ -64,8 +67,8 @@ class ChangeAvailability : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp +} //namespace Ocpp201 +} //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/ChangeConfiguration.cpp b/src/MicroOcpp/Operations/ChangeConfiguration.cpp index 79abaf29..bc913c2d 100644 --- a/src/MicroOcpp/Operations/ChangeConfiguration.cpp +++ b/src/MicroOcpp/Operations/ChangeConfiguration.cpp @@ -1,17 +1,19 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include -#include +#include #include -#include //for tolower +#include //for tolower -using MicroOcpp::Ocpp16::ChangeConfiguration; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 -ChangeConfiguration::ChangeConfiguration() : MemoryManaged("v16.Operation.", "ChangeConfiguration") { +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +ChangeConfiguration::ChangeConfiguration(ConfigurationService& configService) : MemoryManaged("v16.Operation.", "ChangeConfiguration"), configService(configService) { } @@ -35,122 +37,29 @@ void ChangeConfiguration::processReq(JsonObject payload) { const char *value = payload["value"]; - auto configuration = getConfigurationPublic(key); - - if (!configuration) { - //configuration not found or hidden configuration - notSupported = true; - return; - } - - if (configuration->isReadOnly()) { - MO_DBG_WARN("Trying to override readonly value"); - readOnly = true; - return; - } - - //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", key, 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 { - convertibleBool = false; - } - - //check against optional validator - - auto validator = getConfigurationValidator(key); - if (validator && !(*validator)(value)) { - //validator exists and validation fails - reject = true; - MO_DBG_WARN("validation failed for key=%s value=%s", key, value); - return; - } - - //Store (parsed) value to Config - - if (configuration->getType() == TConfig::Int && convertibleInt) { - configuration->setInt(numInt); - } else if (configuration->getType() == TConfig::Bool && convertibleBool) { - configuration->setBool(numBool); - } else if (configuration->getType() == TConfig::String) { - configuration->setString(value); - } else { - reject = true; - MO_DBG_WARN("Value has incompatible type"); - return; - } - - if (!configuration_save()) { - MO_DBG_ERR("could not write changes to flash"); - errorCode = "InternalError"; - return; - } - - if (configuration->isRebootRequired()) { - rebootRequired = true; - } + status = configService.changeConfiguration(key, value); } std::unique_ptr ChangeConfiguration::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - if (notSupported) { - payload["status"] = "NotSupported"; - } else if (reject || readOnly) { - payload["status"] = "Rejected"; - } else if (rebootRequired) { - payload["status"] = "RebootRequired"; - } else { - payload["status"] = "Accepted"; + const char *statusStr = ""; + switch (status) { + case ConfigurationStatus::Accepted: + statusStr = "Accepted"; + break; + case ConfigurationStatus::Rejected: + statusStr = "Rejected"; + break; + case ConfigurationStatus::RebootRequired: + statusStr = "RebootRequired"; + break; + case ConfigurationStatus::NotSupported: + statusStr = "NotSupported"; + break; } + payload["status"] = statusStr; return doc; } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/ChangeConfiguration.h b/src/MicroOcpp/Operations/ChangeConfiguration.h index 3699cf6d..c785f12c 100644 --- a/src/MicroOcpp/Operations/ChangeConfiguration.h +++ b/src/MicroOcpp/Operations/ChangeConfiguration.h @@ -6,20 +6,24 @@ #define MO_CHANGECONFIGURATION_H #include +#include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { namespace Ocpp16 { +class ConfigurationService; + class ChangeConfiguration : public Operation, public MemoryManaged { private: - bool reject = false; - bool rebootRequired = false; - bool readOnly = false; - bool notSupported = false; + ConfigurationService& configService; + ConfigurationStatus status = ConfigurationStatus::Rejected; const char *errorCode = nullptr; public: - ChangeConfiguration(); + ChangeConfiguration(ConfigurationService& configService); const char* getOperationType() override; @@ -31,6 +35,7 @@ class ChangeConfiguration : public Operation, public MemoryManaged { }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/ClearCache.cpp b/src/MicroOcpp/Operations/ClearCache.cpp index 6666ae51..eb200f22 100644 --- a/src/MicroOcpp/Operations/ClearCache.cpp +++ b/src/MicroOcpp/Operations/ClearCache.cpp @@ -6,10 +6,12 @@ #include #include -using MicroOcpp::Ocpp16::ClearCache; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 -ClearCache::ClearCache(std::shared_ptr filesystem) : MemoryManaged("v16.Operation.", "ClearCache"), filesystem(filesystem) { +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +ClearCache::ClearCache(MO_FilesystemAdapter *filesystem) : MemoryManaged("v16.Operation.", "ClearCache"), filesystem(filesystem) { } @@ -21,15 +23,16 @@ void ClearCache::processReq(JsonObject payload) { MO_DBG_WARN("Clear transaction log (Authorization Cache not supported)"); if (!filesystem) { - //no persistency - nothing to do + //no persistency - operation not actually supported, so reject + success = false; return; } - success = FilesystemUtils::remove_if(filesystem, [] (const char *fname) -> bool { - return !strncmp(fname, "sd", strlen("sd")) || - !strncmp(fname, "tx", strlen("tx")) || - !strncmp(fname, "op", strlen("op")); - }); + success = true; + + success &= FilesystemUtils::removeByPrefix(filesystem, "sd"); + success &= FilesystemUtils::removeByPrefix(filesystem, "tx"); + success &= FilesystemUtils::removeByPrefix(filesystem, "op"); } std::unique_ptr ClearCache::createConf(){ @@ -42,3 +45,5 @@ std::unique_ptr ClearCache::createConf(){ } return doc; } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/ClearCache.h b/src/MicroOcpp/Operations/ClearCache.h index 110ab14a..8ac0acba 100644 --- a/src/MicroOcpp/Operations/ClearCache.h +++ b/src/MicroOcpp/Operations/ClearCache.h @@ -7,16 +7,20 @@ #include #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { + namespace Ocpp16 { class ClearCache : public Operation, public MemoryManaged { private: - std::shared_ptr filesystem; - bool success = true; + MO_FilesystemAdapter *filesystem = nullptr; + bool success = false; public: - ClearCache(std::shared_ptr filesystem); + ClearCache(MO_FilesystemAdapter *filesystem); const char* getOperationType() override; @@ -25,6 +29,7 @@ class ClearCache : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/ClearChargingProfile.cpp b/src/MicroOcpp/Operations/ClearChargingProfile.cpp index e99f9bd7..4b005a1b 100644 --- a/src/MicroOcpp/Operations/ClearChargingProfile.cpp +++ b/src/MicroOcpp/Operations/ClearChargingProfile.cpp @@ -1,17 +1,16 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include #include #include -#include +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING -using MicroOcpp::Ocpp16::ClearChargingProfile; -using MicroOcpp::JsonDoc; +using namespace MicroOcpp; -ClearChargingProfile::ClearChargingProfile(SmartChargingService& scService) : MemoryManaged("v16.Operation.", "ClearChargingProfile"), scService(scService) { +ClearChargingProfile::ClearChargingProfile(SmartChargingService& scService, int ocppVersion) : MemoryManaged("v16.Operation.", "ClearChargingProfile"), scService(scService), ocppVersion(ocppVersion) { } @@ -21,62 +20,45 @@ const char* ClearChargingProfile::getOperationType(){ void ClearChargingProfile::processReq(JsonObject payload) { - std::function filter = [payload] - (int chargingProfileId, int connectorId, ChargingProfilePurposeType chargingProfilePurpose, int stackLevel) { - - if (payload.containsKey("id")) { - if (chargingProfileId == (payload["id"] | -1)) { - return true; - } else { - return false; - } - } - - if (payload.containsKey("connectorId")) { - if (connectorId != (payload["connectorId"] | -1)) { - return false; - } - } + int chargingProfileId = -1; + int evseId = -1; + ChargingProfilePurposeType chargingProfilePurpose = ChargingProfilePurposeType::UNDEFINED; + int stackLevel = -1; - if (payload.containsKey("chargingProfilePurpose")) { - switch (chargingProfilePurpose) { - case ChargingProfilePurposeType::ChargePointMaxProfile: - if (strcmp(payload["chargingProfilePurpose"] | "INVALID", "ChargePointMaxProfile")) { - return false; - } - break; - case ChargingProfilePurposeType::TxDefaultProfile: - if (strcmp(payload["chargingProfilePurpose"] | "INVALID", "TxDefaultProfile")) { - return false; - } - break; - case ChargingProfilePurposeType::TxProfile: - if (strcmp(payload["chargingProfilePurpose"] | "INVALID", "TxProfile")) { - return false; - } - break; - } + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + chargingProfileId = payload["id"] | -1; + evseId = payload["connectorId"] | -1; + chargingProfilePurpose = deserializeChargingProfilePurposeType(payload["chargingProfilePurpose"] | "_Undefined"); + stackLevel = payload["stackLevel"] | -1; + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + if (payload.containsKey("chargingProfileId")) { + chargingProfileId = payload["chargingProfileId"] | -1; + } else { + JsonObject chargingProfileCriteria = payload["chargingProfileCriteria"]; + evseId = chargingProfileCriteria["evseId"] | -1; + chargingProfilePurpose = deserializeChargingProfilePurposeType(chargingProfileCriteria["chargingProfilePurpose"] | "_Undefined"); + stackLevel = chargingProfileCriteria["stackLevel"] | -1; } + } + #endif //MO_ENABLE_V201 - if (payload.containsKey("stackLevel")) { - if (stackLevel != (payload["stackLevel"] | -1)) { - return false; - } - } - - return true; - }; - - matchingProfilesFound = scService.clearChargingProfile(filter); + bool matchingProfilesFound = scService.clearChargingProfile(chargingProfileId, evseId, chargingProfilePurpose, stackLevel); + if (matchingProfilesFound) { + status = "Accepted"; + } else { + status = "Unknown"; + } } std::unique_ptr ClearChargingProfile::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - if (matchingProfilesFound) - payload["status"] = "Accepted"; - else - payload["status"] = "Unknown"; - + payload["status"] = status; return doc; } + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING diff --git a/src/MicroOcpp/Operations/ClearChargingProfile.h b/src/MicroOcpp/Operations/ClearChargingProfile.h index 40b7d210..41c736da 100644 --- a/src/MicroOcpp/Operations/ClearChargingProfile.h +++ b/src/MicroOcpp/Operations/ClearChargingProfile.h @@ -6,19 +6,23 @@ #define MO_CLEARCHARGINGPROFILE_H #include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING namespace MicroOcpp { class SmartChargingService; -namespace Ocpp16 { - class ClearChargingProfile : public Operation, public MemoryManaged { private: SmartChargingService& scService; - bool matchingProfilesFound = false; + int ocppVersion = -1; + + const char *status = ""; + const char *errorCode = nullptr; public: - ClearChargingProfile(SmartChargingService& scService); + ClearChargingProfile(SmartChargingService& scService, int ocppVersion); const char* getOperationType() override; @@ -26,8 +30,9 @@ class ClearChargingProfile : public Operation, public MemoryManaged { std::unique_ptr createConf() override; + const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING #endif diff --git a/src/MicroOcpp/Operations/CustomOperation.cpp b/src/MicroOcpp/Operations/CustomOperation.cpp index 317d01ef..791296e5 100644 --- a/src/MicroOcpp/Operations/CustomOperation.cpp +++ b/src/MicroOcpp/Operations/CustomOperation.cpp @@ -4,88 +4,190 @@ #include -using MicroOcpp::Ocpp16::CustomOperation; -using MicroOcpp::JsonDoc; - -CustomOperation::CustomOperation(const char *operationType, - std::function ()> fn_createReq, - std::function fn_processConf, - std::function fn_processErr) : - MemoryManaged("Operation.Custom.", operationType), - operationType{makeString(getMemoryTag(), operationType)}, - fn_createReq{fn_createReq}, - fn_processConf{fn_processConf}, - fn_processErr{fn_processErr} { - -} +#include +#include + +using namespace MicroOcpp; -CustomOperation::CustomOperation(const char *operationType, - std::function fn_processReq, - std::function ()> fn_createConf, - std::function fn_getErrorCode, - std::function fn_getErrorDescription, - std::function ()> fn_getErrorDetails) : - MemoryManaged("Operation.Custom.", operationType), - operationType{makeString(getMemoryTag(), operationType)}, - fn_processReq{fn_processReq}, - fn_createConf{fn_createConf}, - fn_getErrorCode{fn_getErrorCode}, - fn_getErrorDescription{fn_getErrorDescription}, - fn_getErrorDetails{fn_getErrorDetails} { +namespace MicroOcpp { + +std::unique_ptr makeDeserializedJson(const char *memoryTag, const char *jsonString) { + std::unique_ptr doc; + + size_t capacity = MO_MAX_JSON_CAPACITY / 4; + DeserializationError err = DeserializationError::NoMemory; + while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) { + + doc = makeJsonDoc(memoryTag, capacity); + if (!doc) { + MO_DBG_ERR("OOM"); + return nullptr; + } + err = deserializeJson(*doc, jsonString); + + capacity *= 2; + } + + if (err) { + MO_DBG_ERR("JSON deserialization error: %s", err.c_str()); + return nullptr; + } + + return doc; } -CustomOperation::~CustomOperation() { +char *makeSerializedJsonString(const char *memoryTag, JsonObject json) { + char *str = nullptr; + + size_t capacity = MO_MAX_JSON_CAPACITY / 4; + + while (!str && capacity <= MO_MAX_JSON_CAPACITY) { + str = static_cast(MO_MALLOC(memoryTag, capacity)); + if (!str) { + MO_DBG_ERR("OOM"); + break; + } + auto written = serializeJson(json, str, capacity); + + if (written >= 2 && written < capacity) { + //success + break; + } + + MO_FREE(str); + capacity *= 2; + + if (written <= 1) { + MO_DBG_ERR("serialization error"); + break; //don't retry + } + } + + if (!str) { + MO_DBG_ERR("could not serialize JSON"); + } + return str; } -const char* CustomOperation::getOperationType() { - return operationType.c_str(); +void freeSerializedJsonString(char *str) { + MO_FREE(str); } -std::unique_ptr CustomOperation::createReq() { - return fn_createReq(); +} //namespace MicroOcpp + +CustomOperation::CustomOperation() : MemoryManaged("v16/v201.CustomOperation") { + } -void CustomOperation::processConf(JsonObject payload) { - return fn_processConf(payload); +CustomOperation::~CustomOperation() { + MO_FREE(request); + request = nullptr; } -bool CustomOperation::processErr(const char *code, const char *description, JsonObject details) { - if (fn_processErr) { - return fn_processErr(code, description, details); +bool CustomOperation::setupEvseInitiated(const char *operationType, const char *request, void (*onResponse)(const char *payloadJson, void *userData), void *userData) { + this->operationType = operationType; + MO_FREE(this->request); + this->request = nullptr; + if (request) { + size_t size = strlen(request) + 1; + this->request = static_cast(MO_MALLOC(getMemoryTag(), size)); + if (!this->request) { + MO_DBG_ERR("OOM"); + return false; + } + int ret = snprintf(this->request, size, "%s", request); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("snprintf: %i", ret); + MO_FREE(this->request); + return false; + } } + this->onResponse = onResponse; + this->userData = userData; + return true; } -void CustomOperation::processReq(JsonObject payload) { - return fn_processReq(payload); +//for operations receied from remote +bool CustomOperation::setupCsmsInitiated(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), void *userData) { + this->operationType = operationType; + this->onRequest = onRequest; + this->writeResponse = writeResponse; + this->userData = userData; + return true; } -std::unique_ptr CustomOperation::createConf() { - return fn_createConf(); +const char *CustomOperation::getOperationType() { + return operationType; } -const char *CustomOperation::getErrorCode() { - if (fn_getErrorCode) { - return fn_getErrorCode(); - } else { - return nullptr; +std::unique_ptr CustomOperation::createReq() { + return makeDeserializedJson(getMemoryTag(), request ? request : "{}"); +} + +void CustomOperation::processConf(JsonObject payload) { + if (!onResponse) { + return; } + char *jsonString = makeSerializedJsonString(getMemoryTag(), payload); + if (!jsonString) { + MO_DBG_ERR("serialization error"); + return; + } + onResponse(jsonString, userData); + + freeSerializedJsonString(jsonString); } -const char *CustomOperation::getErrorDescription() { - if (fn_getErrorDescription) { - return fn_getErrorDescription(); - } else { - return ""; +void CustomOperation::processReq(JsonObject payload) { + if (!onRequest) { + return; } + char *jsonString = makeSerializedJsonString(getMemoryTag(), payload); + if (!jsonString) { + MO_DBG_ERR("serialization error"); + return; + } + onRequest(operationType, jsonString, &userStatus, userData); + + freeSerializedJsonString(jsonString); } -std::unique_ptr CustomOperation::getErrorDetails() { - if (fn_getErrorDetails) { - return fn_getErrorDetails(); - } else { +std::unique_ptr CustomOperation::createConf() { + if (!writeResponse) { return createEmptyDocument(); } + + char *str = nullptr; + + size_t capacity = MO_MAX_JSON_CAPACITY / 4; + + while (!str && capacity <= MO_MAX_JSON_CAPACITY) { + str = static_cast(MO_MALLOC(memoryTag, capacity)); + if (!str) { + MO_DBG_ERR("OOM"); + break; + } + + auto ret = writeResponse(operationType, str, capacity, userStatus, userData); + if (ret >= 0 && ret < 1) { + MO_DBG_ERR("cannot process empty string"); + } else if (ret >= 0 && (size_t)ret < capacity) { + //success + break; + } + + MO_FREE(str); + capacity *= 2; + } + + if (!str) { + MO_DBG_ERR("failure to create conf"); + } + + auto ret = makeDeserializedJson(getMemoryTag(), str); + freeSerializedJsonString(str); + return ret; } diff --git a/src/MicroOcpp/Operations/CustomOperation.h b/src/MicroOcpp/Operations/CustomOperation.h index 02f72e88..fbf3d518 100644 --- a/src/MicroOcpp/Operations/CustomOperation.h +++ b/src/MicroOcpp/Operations/CustomOperation.h @@ -7,40 +7,38 @@ #include -#include - namespace MicroOcpp { -namespace Ocpp16 { - class CustomOperation : public Operation, public MemoryManaged { private: - String operationType; - std::function ()> fn_createReq; - std::function fn_processConf; - std::function fn_processErr; //optional - std::function fn_processReq; - std::function ()> fn_createConf; - std::function fn_getErrorCode; //optional - std::function fn_getErrorDescription; //optional - std::function ()> fn_getErrorDetails; //optional + const char *operationType = nullptr; + void *userData = nullptr; + int userStatus = 0; //`onRequest` cb can write this number and pass status from `onRequest` to `writeResponse` + + //Operation initiated on this EVSE + char *request = nullptr; + void (*onResponse)(const char *payloadJson, void *userData) = nullptr; + + //Operation initiated by server + void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData) = nullptr; + int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData) = nullptr; public: + CustomOperation(); + + ~CustomOperation(); + //for operations initiated at this device - CustomOperation(const char *operationType, - std::function ()> fn_createReq, - std::function fn_processConf, - std::function fn_processErr = nullptr); - + bool setupEvseInitiated(const char *operationType, + const char *request, + void (*onResponse)(const char *payloadJson, void *userData), + void *userData); + //for operations receied from remote - CustomOperation(const char *operationType, - std::function fn_processReq, - std::function ()> fn_createConf, - std::function fn_getErrorCode = nullptr, - std::function fn_getErrorDescription = nullptr, - std::function ()> fn_getErrorDetails = nullptr); - - ~CustomOperation(); + bool setupCsmsInitiated(const char *operationType, + void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), + int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), + void *userData); const char* getOperationType() override; @@ -48,16 +46,10 @@ class CustomOperation : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; - bool processErr(const char *code, const char *description, JsonObject details) override; - void processReq(JsonObject payload) override; std::unique_ptr createConf() override; - const char *getErrorCode() override; - const char *getErrorDescription() override; - std::unique_ptr getErrorDetails() override; }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace MicroOcpp #endif diff --git a/src/MicroOcpp/Operations/DataTransfer.cpp b/src/MicroOcpp/Operations/DataTransfer.cpp index 5c605039..08cf365f 100644 --- a/src/MicroOcpp/Operations/DataTransfer.cpp +++ b/src/MicroOcpp/Operations/DataTransfer.cpp @@ -5,15 +5,35 @@ #include #include -using MicroOcpp::Ocpp16::DataTransfer; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 -DataTransfer::DataTransfer() : MemoryManaged("v16.Operation.", "DataTransfer") { +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; +DataTransfer::DataTransfer(const char *msg) : MemoryManaged("v16.Operation.", "DataTransfer") { + if (msg) { + size_t size = strlen(msg); + this->msg = static_cast(MO_MALLOC(getMemoryTag(), size)); + if (this->msg) { + memset(this->msg, 0, size); + auto ret = snprintf(this->msg, size, "%s", msg); + if (ret < 0 || (size_t)ret >= size) { + MO_DBG_ERR("snprintf: %i", ret); + MO_FREE(this->msg); + this->msg = nullptr; + //send empty string + } + //success + } else { + MO_DBG_ERR("OOM"); + //send empty string + } + } } -DataTransfer::DataTransfer(const String &msg) : MemoryManaged("v16.Operation.", "DataTransfer"), msg{makeString(getMemoryTag(), msg.c_str())} { - +DataTransfer::~DataTransfer() { + MO_FREE(msg); + msg = nullptr; } const char* DataTransfer::getOperationType(){ @@ -21,10 +41,10 @@ const char* DataTransfer::getOperationType(){ } std::unique_ptr DataTransfer::createReq() { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2) + (msg.length() + 1)); + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2)); JsonObject payload = doc->to(); payload["vendorId"] = "CustomVendor"; - payload["data"] = msg; + payload["data"] = msg ? (const char*)msg : ""; return doc; } @@ -48,3 +68,5 @@ std::unique_ptr DataTransfer::createConf(){ payload["status"] = "Rejected"; return doc; } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/DataTransfer.h b/src/MicroOcpp/Operations/DataTransfer.h index f7967202..ff644de1 100644 --- a/src/MicroOcpp/Operations/DataTransfer.h +++ b/src/MicroOcpp/Operations/DataTransfer.h @@ -6,16 +6,19 @@ #define MO_DATATRANSFER_H #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { namespace Ocpp16 { class DataTransfer : public Operation, public MemoryManaged { private: - String msg; + char *msg = nullptr; public: - DataTransfer(); - DataTransfer(const String &msg); + DataTransfer(const char *msg = nullptr); + ~DataTransfer(); const char* getOperationType() override; @@ -29,6 +32,7 @@ class DataTransfer : public Operation, public MemoryManaged { }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/DeleteCertificate.cpp b/src/MicroOcpp/Operations/DeleteCertificate.cpp index b7a7267a..30f6da07 100644 --- a/src/MicroOcpp/Operations/DeleteCertificate.cpp +++ b/src/MicroOcpp/Operations/DeleteCertificate.cpp @@ -3,15 +3,13 @@ // MIT License #include - -#if MO_ENABLE_CERT_MGMT - #include #include #include -using MicroOcpp::Ocpp201::DeleteCertificate; -using MicroOcpp::JsonDoc; +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT + +using namespace MicroOcpp; DeleteCertificate::DeleteCertificate(CertificateService& certService) : MemoryManaged("v201.Operation.", "DeleteCertificate"), certService(certService) { @@ -51,9 +49,9 @@ void DeleteCertificate::processReq(JsonObject payload) { return; } - auto retIN = ocpp_cert_set_issuerNameHash(&cert, certIdJson["issuerNameHash"] | "_Invalid", cert.hashAlgorithm); - auto retIK = ocpp_cert_set_issuerKeyHash(&cert, certIdJson["issuerKeyHash"] | "_Invalid", cert.hashAlgorithm); - auto retSN = ocpp_cert_set_serialNumber(&cert, certIdJson["serialNumber"] | "_Invalid"); + auto retIN = mo_cert_set_issuerNameHash(&cert, certIdJson["issuerNameHash"] | "_Invalid", cert.hashAlgorithm); + auto retIK = mo_cert_set_issuerKeyHash(&cert, certIdJson["issuerKeyHash"] | "_Invalid", cert.hashAlgorithm); + auto retSN = mo_cert_set_serialNumber(&cert, certIdJson["serialNumber"] | "_Invalid"); if (retIN < 0 || retIK < 0 || retSN < 0) { errorCode = "FormationViolation"; return; @@ -93,4 +91,4 @@ std::unique_ptr DeleteCertificate::createConf(){ return doc; } -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT diff --git a/src/MicroOcpp/Operations/DeleteCertificate.h b/src/MicroOcpp/Operations/DeleteCertificate.h index 4c07014b..946f43ac 100644 --- a/src/MicroOcpp/Operations/DeleteCertificate.h +++ b/src/MicroOcpp/Operations/DeleteCertificate.h @@ -5,18 +5,15 @@ #ifndef MO_DELETECERTIFICATE_H #define MO_DELETECERTIFICATE_H +#include #include -#if MO_ENABLE_CERT_MGMT - -#include +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT namespace MicroOcpp { class CertificateService; -namespace Ocpp201 { - class DeleteCertificate : public Operation, public MemoryManaged { private: CertificateService& certService; @@ -34,8 +31,7 @@ class DeleteCertificate : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp +} //namespace MicroOcpp -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #endif diff --git a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp index 8fc99913..53626c8f 100644 --- a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp +++ b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp @@ -3,35 +3,19 @@ // MIT License #include -#include +#include #include #include -using MicroOcpp::Ocpp16::DiagnosticsStatusNotification; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; DiagnosticsStatusNotification::DiagnosticsStatusNotification(DiagnosticsStatus status) : MemoryManaged("v16.Operation.", "DiagnosticsStatusNotification"), status(status) { } -const char *DiagnosticsStatusNotification::cstrFromStatus(DiagnosticsStatus status) { - switch (status) { - case (DiagnosticsStatus::Idle): - return "Idle"; - break; - case (DiagnosticsStatus::Uploaded): - return "Uploaded"; - break; - case (DiagnosticsStatus::UploadFailed): - return "UploadFailed"; - break; - case (DiagnosticsStatus::Uploading): - return "Uploading"; - break; - } - return nullptr; //cannot be reached -} - std::unique_ptr DiagnosticsStatusNotification::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); @@ -42,3 +26,5 @@ std::unique_ptr DiagnosticsStatusNotification::createReq() { void DiagnosticsStatusNotification::processConf(JsonObject payload){ // no payload, nothing to do } + +#endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h index e21c8c07..86249487 100644 --- a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h +++ b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h @@ -2,12 +2,15 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include -#include - #ifndef MO_DIAGNOSTICSSTATUSNOTIFICATION_H #define MO_DIAGNOSTICSSTATUSNOTIFICATION_H +#include +#include +#include + +#if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS + namespace MicroOcpp { namespace Ocpp16 { @@ -23,10 +26,9 @@ class DiagnosticsStatusNotification : public Operation, public MemoryManaged { std::unique_ptr createReq() override; void processConf(JsonObject payload) override; - }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp - +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS #endif diff --git a/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp b/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp index 0b52c56f..5b49ffbd 100644 --- a/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp +++ b/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp @@ -3,12 +3,14 @@ // MIT License #include -#include +#include #include #include -using MicroOcpp::Ocpp16::FirmwareStatusNotification; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; FirmwareStatusNotification::FirmwareStatusNotification(FirmwareStatus status) : MemoryManaged("v16.Operation.", "FirmwareStatusNotification"), status{status} { @@ -51,3 +53,5 @@ std::unique_ptr FirmwareStatusNotification::createReq() { void FirmwareStatusNotification::processConf(JsonObject payload){ // no payload, nothing to do } + +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT diff --git a/src/MicroOcpp/Operations/FirmwareStatusNotification.h b/src/MicroOcpp/Operations/FirmwareStatusNotification.h index 026ea45b..c49ecfb7 100644 --- a/src/MicroOcpp/Operations/FirmwareStatusNotification.h +++ b/src/MicroOcpp/Operations/FirmwareStatusNotification.h @@ -2,12 +2,14 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include +#ifndef MO_FIRMWARESTATUSNOTIFICATION_H +#define MO_FIRMWARESTATUSNOTIFICATION_H +#include #include +#include -#ifndef MO_FIRMWARESTATUSNOTIFICATION_H -#define MO_FIRMWARESTATUSNOTIFICATION_H +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT namespace MicroOcpp { namespace Ocpp16 { @@ -24,10 +26,9 @@ class FirmwareStatusNotification : public Operation, public MemoryManaged { std::unique_ptr createReq() override; void processConf(JsonObject payload) override; - }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp - +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/src/MicroOcpp/Operations/GetBaseReport.cpp b/src/MicroOcpp/Operations/GetBaseReport.cpp index 95414481..8fe0b1cb 100644 --- a/src/MicroOcpp/Operations/GetBaseReport.cpp +++ b/src/MicroOcpp/Operations/GetBaseReport.cpp @@ -2,16 +2,14 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include #include #include -using MicroOcpp::Ocpp201::GetBaseReport; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V201 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; GetBaseReport::GetBaseReport(VariableService& variableService) : MemoryManaged("v201.Operation.", "GetBaseReport"), variableService(variableService) { @@ -78,4 +76,4 @@ std::unique_ptr GetBaseReport::createConf(){ return doc; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/GetBaseReport.h b/src/MicroOcpp/Operations/GetBaseReport.h index f23b7e81..61e5a6b1 100644 --- a/src/MicroOcpp/Operations/GetBaseReport.h +++ b/src/MicroOcpp/Operations/GetBaseReport.h @@ -5,20 +5,18 @@ #ifndef MO_GETBASEREPORT_H #define MO_GETBASEREPORT_H -#include - -#if MO_ENABLE_V201 - #include #include #include +#include + +#if MO_ENABLE_V201 namespace MicroOcpp { +namespace Ocpp201 { class VariableService; -namespace Ocpp201 { - class GetBaseReport : public Operation, public MemoryManaged { private: VariableService& variableService; @@ -41,7 +39,5 @@ class GetBaseReport : public Operation, public MemoryManaged { } //namespace Ocpp201 } //namespace MicroOcpp - #endif //MO_ENABLE_V201 - #endif diff --git a/src/MicroOcpp/Operations/GetCompositeSchedule.cpp b/src/MicroOcpp/Operations/GetCompositeSchedule.cpp index 760e0e2f..f93b1ea0 100644 --- a/src/MicroOcpp/Operations/GetCompositeSchedule.cpp +++ b/src/MicroOcpp/Operations/GetCompositeSchedule.cpp @@ -3,14 +3,17 @@ // MIT License #include + +#include #include #include #include -using MicroOcpp::Ocpp16::GetCompositeSchedule; -using MicroOcpp::JsonDoc; +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING + +using namespace MicroOcpp; -GetCompositeSchedule::GetCompositeSchedule(Model& model, SmartChargingService& scService) : MemoryManaged("v16.Operation.", "GetCompositeSchedule"), model(model), scService(scService) { +GetCompositeSchedule::GetCompositeSchedule(Context& context, SmartChargingService& scService) : MemoryManaged("v16.Operation.", "GetCompositeSchedule"), context(context), scService(scService), ocppVersion(context.getOcppVersion()) { } @@ -20,24 +23,38 @@ const char* GetCompositeSchedule::getOperationType() { void GetCompositeSchedule::processReq(JsonObject payload) { - connectorId = payload["connectorId"] | -1; - duration = payload["duration"] | 0; + unsigned int numEvseId = MO_NUM_EVSEID; + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + evseId = payload["connectorId"] | -1; + numEvseId = context.getModel16().getNumEvseId(); + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + evseId = payload["evseId"] | -1; + numEvseId = context.getModel201().getNumEvseId(); + } + #endif //MO_ENABLE_V201 + + duration = payload["duration"] | -1; - if (connectorId < 0 || !payload.containsKey("duration")) { + if (evseId < 0 || duration < 0) { errorCode = "FormationViolation"; return; } - if ((unsigned int) connectorId >= model.getNumConnectors()) { + if ((unsigned int) evseId >= numEvseId) { errorCode = "PropertyConstraintViolation"; } - const char *unitStr = payload["chargingRateUnit"] | "_Undefined"; - - if (unitStr[0] == 'A' || unitStr[0] == 'a') { - chargingRateUnit = ChargingRateUnitType_Optional::Amp; - } else if (unitStr[0] == 'W' || unitStr[0] == 'w') { - chargingRateUnit = ChargingRateUnitType_Optional::Watt; + if (payload.containsKey("chargingRateUnit")) { + chargingRateUnit = deserializeChargingRateUnitType(payload["chargingRateUnit"] | "_Invalid"); + if (chargingRateUnit == ChargingRateUnitType::UNDEFINED) { + errorCode = "FormationViolation"; + return; + } } } @@ -45,27 +62,46 @@ std::unique_ptr GetCompositeSchedule::createConf(){ bool success = false; - auto chargingSchedule = scService.getCompositeSchedule((unsigned int) connectorId, duration, chargingRateUnit); - JsonDoc chargingScheduleDoc {0}; + auto chargingSchedule = scService.getCompositeSchedule((unsigned int) evseId, duration, chargingRateUnit); + JsonDoc chargingScheduleDoc (0); if (chargingSchedule) { - success = chargingSchedule->toJson(chargingScheduleDoc); + auto capacity = chargingSchedule->getJsonCapacity(ocppVersion, /* compositeSchedule */ true); + chargingScheduleDoc = initJsonDoc(getMemoryTag(), capacity); + success = chargingSchedule->toJson(context.getClock(), ocppVersion, /* compositeSchedule */ true, chargingScheduleDoc.to()); } - char scheduleStart_str [JSONDATE_LENGTH + 1] = {'\0'}; - - if (success && chargingSchedule) { - success = chargingSchedule->startSchedule.toJsonString(scheduleStart_str, JSONDATE_LENGTH + 1); + char scheduleStartStr [MO_JSONDATE_SIZE] = {'\0'}; + + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + if (success && chargingSchedule) { + Timestamp scheduleStart; + if (context.getClock().fromUnixTime(scheduleStart, chargingSchedule->startSchedule)) { + success = context.getClock().toJsonString(scheduleStart, scheduleStartStr, sizeof(scheduleStartStr)); + } else { + success = false; + } + } } + #endif //MO_ENABLE_V16 if (success && chargingSchedule) { auto doc = makeJsonDoc(getMemoryTag(), - JSON_OBJECT_SIZE(4) + + (ocppVersion == MO_OCPP_V16 ? + JSON_OBJECT_SIZE(4) + MO_JSONDATE_SIZE : + JSON_OBJECT_SIZE(2)) + chargingScheduleDoc.memoryUsage()); JsonObject payload = doc->to(); payload["status"] = "Accepted"; - payload["connectorId"] = connectorId; - payload["scheduleStart"] = scheduleStart_str; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + payload["connectorId"] = evseId; + if (ocppVersion == MO_OCPP_V16) { + payload["scheduleStart"] = scheduleStartStr; + } + } + #endif //MO_ENABLE_V16 payload["chargingSchedule"] = chargingScheduleDoc; return doc; } else { @@ -75,3 +111,5 @@ std::unique_ptr GetCompositeSchedule::createConf(){ return doc; } } + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING diff --git a/src/MicroOcpp/Operations/GetCompositeSchedule.h b/src/MicroOcpp/Operations/GetCompositeSchedule.h index 30e427c4..7f748e9b 100644 --- a/src/MicroOcpp/Operations/GetCompositeSchedule.h +++ b/src/MicroOcpp/Operations/GetCompositeSchedule.h @@ -6,26 +6,28 @@ #define MO_GETCOMPOSITESCHEDULE_H #include -#include -#include +#include +#include -namespace MicroOcpp { +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING -class Model; +namespace MicroOcpp { -namespace Ocpp16 { +class Context; +class SmartChargingService; class GetCompositeSchedule : public Operation, public MemoryManaged { private: - Model& model; + Context& context; SmartChargingService& scService; - int connectorId = -1; + int ocppVersion = -1; + int evseId = -1; int duration = -1; - ChargingRateUnitType_Optional chargingRateUnit = ChargingRateUnitType_Optional::None; + ChargingRateUnitType chargingRateUnit = ChargingRateUnitType::UNDEFINED; const char *errorCode {nullptr}; public: - GetCompositeSchedule(Model& model, SmartChargingService& scService); + GetCompositeSchedule(Context& context, SmartChargingService& scService); const char* getOperationType() override; @@ -36,6 +38,6 @@ class GetCompositeSchedule : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING #endif diff --git a/src/MicroOcpp/Operations/GetConfiguration.cpp b/src/MicroOcpp/Operations/GetConfiguration.cpp index 8146bba1..8bbedc94 100644 --- a/src/MicroOcpp/Operations/GetConfiguration.cpp +++ b/src/MicroOcpp/Operations/GetConfiguration.cpp @@ -3,14 +3,27 @@ // MIT License #include -#include +#include #include -using MicroOcpp::Ocpp16::GetConfiguration; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 -GetConfiguration::GetConfiguration() : MemoryManaged("v16.Operation.", "GetConfiguration"), keys{makeVector(getMemoryTag())} { +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; +GetConfiguration::GetConfiguration(ConfigurationService& configService) : + MemoryManaged("v16.Operation.", "GetConfiguration"), + configService(configService), + configurations(makeVector(getMemoryTag())), + unknownKeys(makeVector(getMemoryTag())) { + +} + +GetConfiguration::~GetConfiguration() { + for (size_t i = 0; i < unknownKeys.size(); i++) { + MO_FREE(unknownKeys[i]); + unknownKeys[i] = nullptr; + } } const char* GetConfiguration::getOperationType(){ @@ -19,66 +32,61 @@ const char* GetConfiguration::getOperationType(){ void GetConfiguration::processReq(JsonObject payload) { - JsonArray requestedKeys = payload["key"]; - for (size_t i = 0; i < requestedKeys.size(); i++) { - keys.push_back(makeString(getMemoryTag(), requestedKeys[i].as())); + JsonArray keysArray = payload["key"]; + const char **keys = nullptr; + size_t keysSize = keysArray.size(); + if (keysSize > 0) { + keys = static_cast(MO_MALLOC(getMemoryTag(), sizeof(const char**) * keysSize)); + if (!keys) { + MO_DBG_ERR("OOM"); + errorCode = "InternalError"; + errorDescription = "Query too big. Try fewer keys"; + goto exit; + } + memset(keys, 0, sizeof(const char**) * keysSize); } -} -std::unique_ptr GetConfiguration::createConf(){ + for (size_t i = 0; i < keysArray.size(); i++) { + const char *key = keysArray[i].as(); + if (!key || !*key) { + errorCode = "FormationViolation"; + goto exit; + } - Vector configurations = makeVector(getMemoryTag()); - Vector unknownKeys = makeVector(getMemoryTag()); + keys[i] = key; + } - auto containers = getConfigurationContainersPublic(); + if (!configService.getConfiguration(keys, keysSize, configurations, unknownKeys)) { + errorCode = "InternalError"; + errorDescription = "Query too big. Try fewer keys"; + goto exit; + } - if (keys.empty()) { - //return all existing keys - for (auto container : containers) { - for (size_t i = 0; i < container->size(); i++) { - if (!container->getConfiguration(i)->getKey()) { - MO_DBG_ERR("invalid config"); - continue; - } - if (!container->getConfiguration(i)->isReadable()) { - continue; - } - configurations.push_back(container->getConfiguration(i)); - } - } - } else { - //only return keys that were searched using the "key" parameter - for (auto& key : keys) { - Configuration *res = nullptr; - for (auto container : containers) { - if ((res = container->getConfiguration(key.c_str()).get())) { - break; - } - } +exit: + MO_FREE(keys); + keys = nullptr; + keysSize = 0; +} - if (res && res->isReadable()) { - configurations.push_back(res); - } else { - unknownKeys.push_back(key.c_str()); - } - } - } +std::unique_ptr GetConfiguration::createConf(){ - #define VALUE_BUFSIZE 30 + const size_t VALUE_BUFSIZE = 30; //capacity of the resulting document size_t jcapacity = JSON_OBJECT_SIZE(2); //document root: configurationKey, unknownKey jcapacity += JSON_ARRAY_SIZE(configurations.size()) + configurations.size() * JSON_OBJECT_SIZE(3); //configurationKey: [{"key":...},{"key":...}] - for (auto config : configurations) { + for (size_t i = 0; i < configurations.size(); i++) { + auto config = configurations[i]; //need to store ints by copied string: measure necessary capacity - if (config->getType() == TConfig::Int) { + if (config->getType() == Configuration::Type::Int) { char vbuf [VALUE_BUFSIZE]; - auto ret = snprintf(vbuf, VALUE_BUFSIZE, "%i", config->getInt()); - if (ret < 0 || ret >= VALUE_BUFSIZE) { + auto ret = snprintf(vbuf, sizeof(vbuf), "%i", config->getInt()); + if (ret < 0 || (size_t)ret >= sizeof(vbuf)) { + MO_DBG_ERR("snprintf: %i", ret); continue; } - jcapacity += ret + 1; + jcapacity += (size_t)ret + 1; } } @@ -105,11 +113,12 @@ std::unique_ptr GetConfiguration::createConf(){ JsonObject payload = doc->to(); JsonArray jsonConfigurationKey = payload.createNestedArray("configurationKey"); - for (auto config : configurations) { + for (size_t i = 0; i < configurations.size(); i++) { + auto config = configurations[i]; char vbuf [VALUE_BUFSIZE]; const char *v = ""; switch (config->getType()) { - case TConfig::Int: { + case Configuration::Type::Int: { auto ret = snprintf(vbuf, VALUE_BUFSIZE, "%i", config->getInt()); if (ret < 0 || ret >= VALUE_BUFSIZE) { MO_DBG_ERR("value error"); @@ -118,17 +127,17 @@ std::unique_ptr GetConfiguration::createConf(){ v = vbuf; break; } - case TConfig::Bool: + case Configuration::Type::Bool: v = config->getBool() ? "true" : "false"; break; - case TConfig::String: + case Configuration::Type::String: v = config->getString(); break; } JsonObject jconfig = jsonConfigurationKey.createNestedObject(); jconfig["key"] = config->getKey(); - jconfig["readonly"] = config->isReadOnly(); + jconfig["readonly"] = config->getMutability() == Mutability::ReadOnly; if (v == vbuf) { //value points to buffer on stack, needs to be copied into JSON memory pool jconfig["value"] = (char*) v; @@ -140,11 +149,14 @@ std::unique_ptr GetConfiguration::createConf(){ if (!unknownKeys.empty()) { JsonArray jsonUnknownKey = payload.createNestedArray("unknownKey"); - for (auto key : unknownKeys) { + for (size_t i = 0; i < unknownKeys.size(); i++) { + auto key = unknownKeys[i]; MO_DBG_DEBUG("Unknown key: %s", key); - jsonUnknownKey.add(key); + jsonUnknownKey.add((const char*)key); //force zero-copy mode } } return doc; } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/GetConfiguration.h b/src/MicroOcpp/Operations/GetConfiguration.h index c6781119..a18e22da 100644 --- a/src/MicroOcpp/Operations/GetConfiguration.h +++ b/src/MicroOcpp/Operations/GetConfiguration.h @@ -7,18 +7,28 @@ #include #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { namespace Ocpp16 { +class Configuration; +class ConfigurationService; + class GetConfiguration : public Operation, public MemoryManaged { private: - Vector keys; + ConfigurationService& configService; + + Vector configurations; + Vector unknownKeys; const char *errorCode {nullptr}; const char *errorDescription = ""; public: - GetConfiguration(); + GetConfiguration(ConfigurationService& configService); + ~GetConfiguration(); const char* getOperationType() override; @@ -31,6 +41,7 @@ class GetConfiguration : public Operation, public MemoryManaged { }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/GetDiagnostics.cpp b/src/MicroOcpp/Operations/GetDiagnostics.cpp index e3bab7eb..4adb6407 100644 --- a/src/MicroOcpp/Operations/GetDiagnostics.cpp +++ b/src/MicroOcpp/Operations/GetDiagnostics.cpp @@ -3,14 +3,18 @@ // MIT License #include + +#include #include #include #include -using MicroOcpp::Ocpp16::GetDiagnostics; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -GetDiagnostics::GetDiagnostics(DiagnosticsService& diagService) : MemoryManaged("v16.Operation.", "GetDiagnostics"), diagService(diagService), fileName(makeString(getMemoryTag())) { +GetDiagnostics::GetDiagnostics(Context& context, DiagnosticsService& diagService) : MemoryManaged("v16.Operation.", "GetDiagnostics"), context(context), diagService(diagService) { } @@ -32,8 +36,8 @@ void GetDiagnostics::processReq(JsonObject payload) { Timestamp startTime; if (payload.containsKey("startTime")) { - if (!startTime.setTime(payload["startTime"] | "Invalid")) { - errorCode = "PropertyConstraintViolation"; + if (!context.getClock().parseString(payload["startTime"] | "_Invalid", startTime)) { + errorCode = "FormationViolation"; MO_DBG_WARN("bad time format"); return; } @@ -41,23 +45,25 @@ void GetDiagnostics::processReq(JsonObject payload) { Timestamp stopTime; if (payload.containsKey("stopTime")) { - if (!stopTime.setTime(payload["stopTime"] | "Invalid")) { - errorCode = "PropertyConstraintViolation"; + if (!context.getClock().parseString(payload["stopTime"] | "_Invalid", stopTime)) { + errorCode = "FormationViolation"; MO_DBG_WARN("bad time format"); return; } } - fileName = diagService.requestDiagnosticsUpload(location, (unsigned int) retries, (unsigned int) retryInterval, startTime, stopTime); + (void)diagService.requestDiagnosticsUpload(location, (unsigned int) retries, (unsigned int) retryInterval, startTime, stopTime, filename); } std::unique_ptr GetDiagnostics::createConf(){ - if (fileName.empty()) { + if (!*filename) { return createEmptyDocument(); } else { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - payload["fileName"] = fileName.c_str(); + payload["fileName"] = (const char*)filename; //force zero-copy return doc; } } + +#endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp/Operations/GetDiagnostics.h b/src/MicroOcpp/Operations/GetDiagnostics.h index 704f9199..16ed8546 100644 --- a/src/MicroOcpp/Operations/GetDiagnostics.h +++ b/src/MicroOcpp/Operations/GetDiagnostics.h @@ -6,22 +6,27 @@ #define MO_GETDIAGNOSTICS_H #include -#include +#include +#include + +#if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS namespace MicroOcpp { +class Context; class DiagnosticsService; namespace Ocpp16 { class GetDiagnostics : public Operation, public MemoryManaged { private: + Context& context; DiagnosticsService& diagService; - String fileName; + char filename [MO_GETLOG_FNAME_SIZE] = {'\0'}; const char *errorCode = nullptr; public: - GetDiagnostics(DiagnosticsService& diagService); + GetDiagnostics(Context& context, DiagnosticsService& diagService); const char* getOperationType() override {return "GetDiagnostics";} @@ -32,7 +37,7 @@ class GetDiagnostics : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp - +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS #endif diff --git a/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp index 87d4c2dd..5a525b3a 100644 --- a/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp +++ b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp @@ -3,17 +3,15 @@ // MIT License #include - -#if MO_ENABLE_CERT_MGMT - #include #include #include -using MicroOcpp::Ocpp201::GetInstalledCertificateIds; -using MicroOcpp::JsonDoc; +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT + +using namespace MicroOcpp; -GetInstalledCertificateIds::GetInstalledCertificateIds(CertificateService& certService) : MemoryManaged("v201.Operation.", "GetInstalledCertificateIds"), certService(certService), certificateHashDataChain(makeVector(getMemoryTag())) { +GetInstalledCertificateIds::GetInstalledCertificateIds(CertificateService& certService) : MemoryManaged("v16/v201.Operation.", "GetInstalledCertificateIds"), certService(certService), certificateHashDataChain(makeVector(getMemoryTag())) { } @@ -111,13 +109,13 @@ std::unique_ptr GetInstalledCertificateIds::createConf() { char buf [MO_CERT_HASH_ISSUER_NAME_KEY_SIZE]; - ocpp_cert_print_issuerNameHash(&chainElem.certificateHashData, buf, sizeof(buf)); + mo_cert_print_issuerNameHash(&chainElem.certificateHashData, buf, sizeof(buf)); certHashJson["certificateHashData"]["issuerNameHash"] = buf; - ocpp_cert_print_issuerKeyHash(&chainElem.certificateHashData, buf, sizeof(buf)); + mo_cert_print_issuerKeyHash(&chainElem.certificateHashData, buf, sizeof(buf)); certHashJson["certificateHashData"]["issuerKeyHash"] = buf; - ocpp_cert_print_serialNumber(&chainElem.certificateHashData, buf, sizeof(buf)); + mo_cert_print_serialNumber(&chainElem.certificateHashData, buf, sizeof(buf)); certHashJson["certificateHashData"]["serialNumber"] = buf; if (!chainElem.childCertificateHashData.empty()) { @@ -128,4 +126,4 @@ std::unique_ptr GetInstalledCertificateIds::createConf() { return doc; } -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT diff --git a/src/MicroOcpp/Operations/GetInstalledCertificateIds.h b/src/MicroOcpp/Operations/GetInstalledCertificateIds.h index 28a31c92..c5813adc 100644 --- a/src/MicroOcpp/Operations/GetInstalledCertificateIds.h +++ b/src/MicroOcpp/Operations/GetInstalledCertificateIds.h @@ -5,19 +5,16 @@ #ifndef MO_GETINSTALLEDCERTIFICATEIDS_H #define MO_GETINSTALLEDCERTIFICATEIDS_H -#include - -#if MO_ENABLE_CERT_MGMT - #include #include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT namespace MicroOcpp { class CertificateService; -namespace Ocpp201 { - class GetInstalledCertificateIds : public Operation, public MemoryManaged { private: CertificateService& certService; @@ -36,8 +33,6 @@ class GetInstalledCertificateIds : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp - -#endif //MO_ENABLE_CERT_MGMT +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #endif diff --git a/src/MicroOcpp/Operations/GetLocalListVersion.cpp b/src/MicroOcpp/Operations/GetLocalListVersion.cpp index f2c8a058..7de43dd6 100644 --- a/src/MicroOcpp/Operations/GetLocalListVersion.cpp +++ b/src/MicroOcpp/Operations/GetLocalListVersion.cpp @@ -2,17 +2,15 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_LOCAL_AUTH - #include #include #include #include -using MicroOcpp::Ocpp16::GetLocalListVersion; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; GetLocalListVersion::GetLocalListVersion(Model& model) : MemoryManaged("v16.Operation.", "GetLocalListVersion"), model(model) { @@ -38,4 +36,4 @@ std::unique_ptr GetLocalListVersion::createConf(){ return doc; } -#endif //MO_ENABLE_LOCAL_AUTH +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH diff --git a/src/MicroOcpp/Operations/GetLocalListVersion.h b/src/MicroOcpp/Operations/GetLocalListVersion.h index 554f7a6b..c96be3d2 100644 --- a/src/MicroOcpp/Operations/GetLocalListVersion.h +++ b/src/MicroOcpp/Operations/GetLocalListVersion.h @@ -5,18 +5,16 @@ #ifndef MO_GETLOCALLISTVERSION_H #define MO_GETLOCALLISTVERSION_H +#include #include -#if MO_ENABLE_LOCAL_AUTH - -#include +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH namespace MicroOcpp { +namespace Ocpp16 { class Model; -namespace Ocpp16 { - class GetLocalListVersion : public Operation, public MemoryManaged { private: Model& model; @@ -30,8 +28,7 @@ class GetLocalListVersion : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp - -#endif //MO_ENABLE_LOCAL_AUTH +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Operations/GetLog.cpp b/src/MicroOcpp/Operations/GetLog.cpp new file mode 100644 index 00000000..d1d00e79 --- /dev/null +++ b/src/MicroOcpp/Operations/GetLog.cpp @@ -0,0 +1,77 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#include + +#include +#include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +using namespace MicroOcpp; + +GetLog::GetLog(Context& context, DiagnosticsService& diagService) : MemoryManaged("v16/v201.Operation.", "GetLog"), context(context), diagService(diagService) { + +} + +void GetLog::processReq(JsonObject payload) { + + auto logType = mo_deserializeLogType(payload["logType"] | "_Undefined"); + if (logType == MO_LogType_UNDEFINED) { + errorCode = "FormationViolation"; + return; + } + + int requestId = payload["requestId"] | -1; + if (requestId < 0) { + errorCode = "FormationViolation"; + return; + } + + int retries = payload["retries"] | 1; + int retryInterval = payload["retryInterval"] | 180; + if (retries < 0 || retryInterval < 0) { + errorCode = "PropertyConstraintViolation"; + return; + } + + JsonObject log = payload["log"]; + + const char *remoteLocation = log["remoteLocation"] | ""; + if (!*remoteLocation) { + errorCode = "FormationViolation"; + return; + } + + Timestamp oldestTimestamp; + if (payload.containsKey("oldestTimestamp")) { + if (!context.getClock().parseString(payload["oldestTimestamp"] | "_Invalid", oldestTimestamp)) { + errorCode = "FormationViolation"; + MO_DBG_WARN("bad time format"); + return; + } + } + + Timestamp latestTimestamp; + if (payload.containsKey("latestTimestamp")) { + if (!context.getClock().parseString(payload["latestTimestamp"] | "_Invalid", latestTimestamp)) { + errorCode = "FormationViolation"; + MO_DBG_WARN("bad time format"); + return; + } + } + + status = diagService.getLog(logType, requestId, retries, retryInterval, remoteLocation, oldestTimestamp, latestTimestamp, filename); +} + +std::unique_ptr GetLog::createConf(){ + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2)); + JsonObject payload = doc->to(); + payload["status"] = mo_serializeGetLogStatus(status); + payload["fileName"] = (const char*)filename; //force zero-copy + return doc; +} + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp/Operations/GetLog.h b/src/MicroOcpp/Operations/GetLog.h new file mode 100644 index 00000000..15c08750 --- /dev/null +++ b/src/MicroOcpp/Operations/GetLog.h @@ -0,0 +1,41 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_GETLOG_H +#define MO_GETLOG_H + +#include +#include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +namespace MicroOcpp { + +class Context; +class DiagnosticsService; + +class GetLog : public Operation, public MemoryManaged { +private: + Context& context; + DiagnosticsService& diagService; + MO_GetLogStatus status = MO_GetLogStatus_UNDEFINED; + char filename [MO_GETLOG_FNAME_SIZE] = {'\0'}; + + const char *errorCode = nullptr; +public: + GetLog(Context& context, DiagnosticsService& diagService); + + const char* getOperationType() override {return "GetLog";} + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} +}; + +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS +#endif diff --git a/src/MicroOcpp/Operations/GetVariables.cpp b/src/MicroOcpp/Operations/GetVariables.cpp index 30f8dc7f..627c19a0 100644 --- a/src/MicroOcpp/Operations/GetVariables.cpp +++ b/src/MicroOcpp/Operations/GetVariables.cpp @@ -2,19 +2,16 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include #include #include -#include //for tolower +#include //for tolower + +#if MO_ENABLE_V201 -using MicroOcpp::Ocpp201::GetVariableData; -using MicroOcpp::Ocpp201::GetVariables; -using MicroOcpp::JsonDoc; +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; GetVariableData::GetVariableData(const char *memory_tag) : componentName{makeString(memory_tag)}, variableName{makeString(memory_tag)} { @@ -225,4 +222,4 @@ std::unique_ptr GetVariables::createConf(){ return doc; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/GetVariables.h b/src/MicroOcpp/Operations/GetVariables.h index a0360e62..2b7cc010 100644 --- a/src/MicroOcpp/Operations/GetVariables.h +++ b/src/MicroOcpp/Operations/GetVariables.h @@ -5,20 +5,18 @@ #ifndef MO_GETVARIABLES_H #define MO_GETVARIABLES_H -#include - -#if MO_ENABLE_V201 - #include #include #include +#include -namespace MicroOcpp { - -class VariableService; +#if MO_ENABLE_V201 +namespace MicroOcpp { namespace Ocpp201 { +class VariableService; + // GetVariableDataType (2.25) and // GetVariableResultType (2.26) struct GetVariableData { @@ -52,12 +50,10 @@ class GetVariables : public Operation, public MemoryManaged { 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/Heartbeat.cpp b/src/MicroOcpp/Operations/Heartbeat.cpp index 4dfd0439..dff78d89 100644 --- a/src/MicroOcpp/Operations/Heartbeat.cpp +++ b/src/MicroOcpp/Operations/Heartbeat.cpp @@ -3,14 +3,16 @@ // MIT License #include -#include +#include #include #include -using MicroOcpp::Ocpp16::Heartbeat; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 || MO_ENABLE_V201 -Heartbeat::Heartbeat(Model& model) : MemoryManaged("v16.Operation.", "Heartbeat"), model(model) { +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +Heartbeat::Heartbeat(Context& context) : MemoryManaged("v16.Operation.", "Heartbeat"), context(context) { } @@ -26,7 +28,7 @@ void Heartbeat::processConf(JsonObject payload) { const char* currentTime = payload["currentTime"] | "Invalid"; if (strcmp(currentTime, "Invalid")) { - if (model.getClock().setTime(currentTime)) { + if (context.getClock().setTime(currentTime)) { //success MO_DBG_DEBUG("Request has been accepted"); } else { @@ -37,30 +39,22 @@ void Heartbeat::processConf(JsonObject payload) { } } -void Heartbeat::processReq(JsonObject payload) { - - /** - * Ignore Contents of this Req-message, because this is for debug purposes only - */ +#if MO_ENABLE_MOCK_SERVER +int Heartbeat::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { + (void)userStatus; -} + auto& context = *reinterpret_cast(userData); -std::unique_ptr Heartbeat::createConf(){ - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + (JSONDATE_LENGTH + 1)); - JsonObject payload = doc->to(); + char timeStr [MO_JSONDATE_SIZE]; - //safety mechanism; in some test setups the library could have to answer Heartbeats without valid system time - Timestamp ocppTimeReference = Timestamp(2019,10,0,11,59,55); - Timestamp ocppSelect = ocppTimeReference; - auto& ocppNow = model.getClock().now(); - if (ocppNow > ocppTimeReference) { - //time has already been set - ocppSelect = ocppNow; + if (context.getClock().now().isUnixTime()) { + context.getClock().toJsonString(context.getClock().now(), timeStr, sizeof(timeStr)); + } else { + (void)snprintf(timeStr, sizeof(timeStr), "2025-05-18T18:55:13Z"); } - char ocppNowJson [JSONDATE_LENGTH + 1] = {'\0'}; - ocppSelect.toJsonString(ocppNowJson, JSONDATE_LENGTH + 1); - payload["currentTime"] = ocppNowJson; - - return doc; + return snprintf(buf, size, "{\"currentTime\":\"%s\"}", timeStr); } +#endif //MO_ENABLE_MOCK_SERVER + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/Heartbeat.h b/src/MicroOcpp/Operations/Heartbeat.h index b3ce6a0e..e22a4f83 100644 --- a/src/MicroOcpp/Operations/Heartbeat.h +++ b/src/MicroOcpp/Operations/Heartbeat.h @@ -1,23 +1,24 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_HEARTBEAT_H #define MO_HEARTBEAT_H #include +#include -namespace MicroOcpp { +#if MO_ENABLE_V16 || MO_ENABLE_V201 -class Model; +namespace MicroOcpp { -namespace Ocpp16 { +class Context; class Heartbeat : public Operation, public MemoryManaged { private: - Model& model; + Context& context; public: - Heartbeat(Model& model); + Heartbeat(Context& context); const char* getOperationType() override; @@ -25,11 +26,12 @@ class Heartbeat : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; - void processReq(JsonObject payload) override; - - std::unique_ptr createConf() override; +#if MO_ENABLE_MOCK_SERVER + static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); +#endif }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace MicroOcpp + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/InstallCertificate.cpp b/src/MicroOcpp/Operations/InstallCertificate.cpp index b9859501..4c60fedb 100644 --- a/src/MicroOcpp/Operations/InstallCertificate.cpp +++ b/src/MicroOcpp/Operations/InstallCertificate.cpp @@ -3,14 +3,12 @@ // MIT License #include - -#if MO_ENABLE_CERT_MGMT - #include #include -using MicroOcpp::Ocpp201::InstallCertificate; -using MicroOcpp::JsonDoc; +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT + +using namespace MicroOcpp; InstallCertificate::InstallCertificate(CertificateService& certService) : MemoryManaged("v201.Operation.", "InstallCertificate"), certService(certService) { @@ -82,4 +80,4 @@ std::unique_ptr InstallCertificate::createConf(){ return doc; } -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT diff --git a/src/MicroOcpp/Operations/InstallCertificate.h b/src/MicroOcpp/Operations/InstallCertificate.h index e2c22b55..9287f242 100644 --- a/src/MicroOcpp/Operations/InstallCertificate.h +++ b/src/MicroOcpp/Operations/InstallCertificate.h @@ -5,18 +5,15 @@ #ifndef MO_INSTALLCERTIFICATE_H #define MO_INSTALLCERTIFICATE_H +#include #include -#if MO_ENABLE_CERT_MGMT - -#include +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT namespace MicroOcpp { class CertificateService; -namespace Ocpp201 { - class InstallCertificate : public Operation, public MemoryManaged { private: CertificateService& certService; @@ -34,8 +31,7 @@ class InstallCertificate : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp +} //namespace MicroOcpp -#endif //MO_ENABLE_CERT_MGMT +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT #endif diff --git a/src/MicroOcpp/Operations/LogStatusNotification.cpp b/src/MicroOcpp/Operations/LogStatusNotification.cpp new file mode 100644 index 00000000..6fe66453 --- /dev/null +++ b/src/MicroOcpp/Operations/LogStatusNotification.cpp @@ -0,0 +1,49 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +using namespace MicroOcpp; + +LogStatusNotification::LogStatusNotification(MO_UploadLogStatus status, int requestId) + : MemoryManaged("v16/v201.Operation.", "LogStatusNotification"), status(status), requestId(requestId) { + +} + +const char* LogStatusNotification::getOperationType(){ + return "LogStatusNotification"; +} + +std::unique_ptr LogStatusNotification::createReq() { + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2)); + JsonObject payload = doc->to(); + payload["status"] = mo_serializeUploadLogStatus(status); + payload["requestId"] = requestId; + return doc; +} + +void LogStatusNotification::processConf(JsonObject payload) { + /* + * Empty payload + */ +} + +/* + * For debugging only + */ +void LogStatusNotification::processReq(JsonObject payload) { + +} + +/* + * For debugging only + */ +std::unique_ptr LogStatusNotification::createConf(){ + return createEmptyDocument(); +} + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp/Operations/LogStatusNotification.h b/src/MicroOcpp/Operations/LogStatusNotification.h new file mode 100644 index 00000000..f28b8021 --- /dev/null +++ b/src/MicroOcpp/Operations/LogStatusNotification.h @@ -0,0 +1,36 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2025 +// MIT License + +#ifndef MO_LOGSTATUSNOTIFICATION_H +#define MO_LOGSTATUSNOTIFICATION_H + +#include +#include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS + +namespace MicroOcpp { + +class LogStatusNotification : public Operation, public MemoryManaged { +private: + MO_UploadLogStatus status; + int requestId; +public: + LogStatusNotification(MO_UploadLogStatus status, int requestId); + + const char* getOperationType() override; + + std::unique_ptr createReq() override; + + void processConf(JsonObject payload) override; + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; +}; + +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS +#endif diff --git a/src/MicroOcpp/Operations/MeterValues.cpp b/src/MicroOcpp/Operations/MeterValues.cpp index ee2b4c7e..77fdbab1 100644 --- a/src/MicroOcpp/Operations/MeterValues.cpp +++ b/src/MicroOcpp/Operations/MeterValues.cpp @@ -3,31 +3,38 @@ // MIT License #include + +#include #include #include -#include #include -using MicroOcpp::Ocpp16::MeterValues; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; //can only be used for echo server debugging -MeterValues::MeterValues(Model& model) : MemoryManaged("v16.Operation.", "MeterValues"), model(model) { +MeterValues::MeterValues(Context& context) : MemoryManaged("v16.Operation.", "MeterValues"), context(context) { } -MeterValues::MeterValues(Model& model, MeterValue *meterValue, unsigned int connectorId, std::shared_ptr transaction) - : MemoryManaged("v16.Operation.", "MeterValues"), model(model), meterValue{meterValue}, connectorId{connectorId}, transaction{transaction} { +MeterValues::MeterValues(Context& context, unsigned int connectorId, int transactionId, MeterValue *meterValue, bool transferOwnership) : + MemoryManaged("v16.Operation.", "MeterValues"), + context(context), + connectorId(connectorId), + transactionId(transactionId), + meterValue(meterValue), + isMeterValueOwner(transferOwnership) { } -MeterValues::MeterValues(Model& model, std::unique_ptr meterValue, unsigned int connectorId, std::shared_ptr transaction) - : MeterValues(model, meterValue.get(), connectorId, transaction) { - this->meterValueOwnership = std::move(meterValue); -} - MeterValues::~MeterValues(){ - + if (isMeterValueOwner) { + delete meterValue; + meterValue = nullptr; + isMeterValueOwner = false; + } } const char* MeterValues::getOperationType(){ @@ -37,39 +44,28 @@ const char* MeterValues::getOperationType(){ std::unique_ptr MeterValues::createReq() { size_t capacity = 0; - - std::unique_ptr meterValueJson; - - if (meterValue) { - - if (meterValue->getTimestamp() < MIN_TIME) { - MO_DBG_DEBUG("adjust preboot MeterValue timestamp"); - Timestamp adjusted = model.getClock().adjustPrebootTimestamp(meterValue->getTimestamp()); - meterValue->setTimestamp(adjusted); - } - - meterValueJson = meterValue->toJson(); - if (meterValueJson) { - capacity += meterValueJson->capacity(); - } else { - MO_DBG_ERR("Energy meter reading not convertible to JSON"); - } - } - capacity += JSON_OBJECT_SIZE(3); capacity += JSON_ARRAY_SIZE(1); + int ret = meterValue->getJsonCapacity(MO_OCPP_V16, /*internalFormat*/ false); + if (ret >= 0) { + capacity += (size_t)ret; + } else { + MO_DBG_ERR("serialization error"); + } + auto doc = makeJsonDoc(getMemoryTag(), capacity); auto payload = doc->to(); payload["connectorId"] = connectorId; - if (transaction && transaction->getTransactionId() > 0) { //add txId if MVs are assigned to a tx with txId - payload["transactionId"] = transaction->getTransactionId(); + if (transactionId >= 0) { + payload["transactionId"] = transactionId; } auto meterValueArray = payload.createNestedArray("meterValue"); - if (meterValueJson) { - meterValueArray.add(*meterValueJson); + auto meterValueJson = meterValueArray.createNestedObject(); + if (!meterValue->toJson(context.getClock(), MO_OCPP_V16, /*internalFormat*/ false, meterValueJson)) { + MO_DBG_ERR("serialization error"); } return doc; @@ -91,3 +87,5 @@ void MeterValues::processReq(JsonObject payload) { std::unique_ptr MeterValues::createConf(){ return createEmptyDocument(); } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/MeterValues.h b/src/MicroOcpp/Operations/MeterValues.h index 774a538a..321632ee 100644 --- a/src/MicroOcpp/Operations/MeterValues.h +++ b/src/MicroOcpp/Operations/MeterValues.h @@ -8,30 +8,30 @@ #include #include #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { -class Model; -class MeterValue; -class Transaction; +class Context; +struct MeterValue; namespace Ocpp16 { class MeterValues : public Operation, public MemoryManaged { private: - Model& model; //for adjusting the timestamp if MeterValue has been created before BootNotification + Context& context; //for adjusting the timestamp if MeterValue has been created before BootNotification MeterValue *meterValue = nullptr; - std::unique_ptr meterValueOwnership; + bool isMeterValueOwner = false; unsigned int connectorId = 0; - - std::shared_ptr transaction; + int transactionId = -1; public: - MeterValues(Model& model, MeterValue *meterValue, unsigned int connectorId, std::shared_ptr transaction = nullptr); - MeterValues(Model& model, std::unique_ptr meterValue, unsigned int connectorId, std::shared_ptr transaction = nullptr); + MeterValues(Context& context, unsigned int connectorId, int transactionId, MeterValue *meterValue, bool transferOwnership); //`transferOwnership`: if this object should take ownership of the passed `meterValue` - MeterValues(Model& model); //for debugging only. Make this for the server pendant + MeterValues(Context& context); //for debugging only. Make this for the server pendant ~MeterValues(); @@ -46,6 +46,7 @@ class MeterValues : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/NotifyReport.cpp b/src/MicroOcpp/Operations/NotifyReport.cpp index 0528e452..c21795fb 100644 --- a/src/MicroOcpp/Operations/NotifyReport.cpp +++ b/src/MicroOcpp/Operations/NotifyReport.cpp @@ -2,20 +2,20 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include + +#include #include #include #include +#if MO_ENABLE_V201 + +using namespace MicroOcpp; using namespace MicroOcpp::Ocpp201; -using MicroOcpp::JsonDoc; -NotifyReport::NotifyReport(Model& model, int requestId, const Timestamp& generatedAt, bool tbc, int seqNo, const Vector& reportData) - : MemoryManaged("v201.Operation.", "NotifyReport"), model(model), requestId(requestId), generatedAt(generatedAt), tbc(tbc), seqNo(seqNo), reportData(reportData) { +NotifyReport::NotifyReport(Context& context, int requestId, const Timestamp& generatedAt, bool tbc, unsigned int seqNo, const Vector& reportData) + : MemoryManaged("v201.Operation.", "NotifyReport"), context(context), requestId(requestId), generatedAt(generatedAt), tbc(tbc), seqNo(seqNo), reportData(reportData) { } @@ -36,7 +36,7 @@ std::unique_ptr NotifyReport::createReq() { size_t capacity = JSON_OBJECT_SIZE(5) + //total of 5 fields - JSONDATE_LENGTH + 1; //timestamp string + MO_JSONDATE_SIZE; //timestamp string capacity += JSON_ARRAY_SIZE(reportData.size()); for (auto variable : reportData) { @@ -86,8 +86,11 @@ std::unique_ptr NotifyReport::createReq() { JsonObject payload = doc->to(); payload["requestId"] = requestId; - char generatedAtCstr [JSONDATE_LENGTH + 1]; - generatedAt.toJsonString(generatedAtCstr, sizeof(generatedAtCstr)); + char generatedAtCstr [MO_JSONDATE_SIZE]; + if (!context.getClock().toJsonString(generatedAt, generatedAtCstr, sizeof(generatedAtCstr))) { + MO_DBG_ERR("internal error"); + generatedAtCstr[0] = '\0'; + } payload["generatedAt"] = generatedAtCstr; if (tbc) { @@ -144,7 +147,7 @@ std::unique_ptr NotifyReport::createReq() { attribute["type"] = attributeTypeCstr; } - if (variable->getMutability() != Variable::Mutability::WriteOnly) { + if (variable->getMutability() != Mutability::WriteOnly) { switch (variable->getInternalDataType()) { case Variable::InternalDataType::Int: { char valbuf [VALUE_BUFSIZE]; @@ -169,13 +172,13 @@ std::unique_ptr NotifyReport::createReq() { const char *mutabilityCstr = nullptr; switch (variable->getMutability()) { - case Variable::Mutability::ReadOnly: + case Mutability::ReadOnly: mutabilityCstr = "ReadOnly"; break; - case Variable::Mutability::WriteOnly: + case Mutability::WriteOnly: mutabilityCstr = "WriteOnly"; break; - case Variable::Mutability::ReadWrite: + case Mutability::ReadWrite: // leave blank when ReadWrite break; default: @@ -239,4 +242,4 @@ void NotifyReport::processConf(JsonObject payload) { // empty payload } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/NotifyReport.h b/src/MicroOcpp/Operations/NotifyReport.h index 1a092678..a2ab1ffa 100644 --- a/src/MicroOcpp/Operations/NotifyReport.h +++ b/src/MicroOcpp/Operations/NotifyReport.h @@ -5,33 +5,33 @@ #ifndef MO_TRANSACTIONEVENT_H #define MO_TRANSACTIONEVENT_H -#include - -#if MO_ENABLE_V201 - #include #include #include +#include + +#if MO_ENABLE_V201 namespace MicroOcpp { -class Model; -class Variable; +class Context; namespace Ocpp201 { +class Variable; + class NotifyReport : public Operation, public MemoryManaged { private: - Model& model; + Context& context; int requestId; Timestamp generatedAt; bool tbc; - int seqNo; + unsigned int seqNo; Vector reportData; public: - NotifyReport(Model& model, int requestId, const Timestamp& generatedAt, bool tbc, int seqNo, const Vector& reportData); + NotifyReport(Context& context, int requestId, const Timestamp& generatedAt, bool tbc, unsigned int seqNo, const Vector& reportData); const char* getOperationType() override; @@ -40,7 +40,7 @@ class NotifyReport : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp -#endif // MO_ENABLE_V201 +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp index 77314f36..b4a62352 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp @@ -4,17 +4,22 @@ #include -#include + +#include #include -#include +#include +#include +#include #include -#include +#include #include -using MicroOcpp::Ocpp16::RemoteStartTransaction; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -RemoteStartTransaction::RemoteStartTransaction(Model& model) : MemoryManaged("v16.Operation.", "RemoteStartTransaction"), model(model) { +RemoteStartTransaction::RemoteStartTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "RemoteStartTransaction"), context(context), rcService(rcService) { } @@ -40,11 +45,22 @@ void RemoteStartTransaction::processReq(JsonObject payload) { std::unique_ptr chargingProfile; - if (payload.containsKey("chargingProfile") && model.getSmartChargingService()) { + if (payload.containsKey("chargingProfile") && context.getModel16().getSmartChargingService()) { MO_DBG_INFO("Setting Charging profile via RemoteStartTransaction"); - JsonObject chargingProfileJson = payload["chargingProfile"]; - chargingProfile = loadChargingProfile(chargingProfileJson); + chargingProfile = std::unique_ptr(new ChargingProfile()); + if (!chargingProfile) { + MO_DBG_ERR("OOM"); + errorCode = "InternalError"; + return; + } + + bool valid = chargingProfile->parseJson(context.getClock(), MO_OCPP_V16, payload["chargingProfile"]); + if (!valid) { + errorCode = "FormationViolation"; + errorDescription = "chargingProfile validation failed"; + return; + } if (!chargingProfile) { errorCode = "PropertyConstraintViolation"; @@ -52,81 +68,32 @@ void RemoteStartTransaction::processReq(JsonObject payload) { return; } - if (chargingProfile->getChargingProfilePurpose() != ChargingProfilePurposeType::TxProfile) { + if (chargingProfile->chargingProfilePurpose != ChargingProfilePurposeType::TxProfile) { errorCode = "PropertyConstraintViolation"; errorDescription = "Can only set TxProfile here"; return; } - if (chargingProfile->getChargingProfileId() < 0) { + if (chargingProfile->chargingProfileId < 0) { errorCode = "PropertyConstraintViolation"; errorDescription = "RemoteStartTx profile requires non-negative chargingProfileId"; return; } } - Connector *selectConnector = nullptr; - if (connectorId >= 1) { - //connectorId specified for given connector, try to start Transaction here - if (auto connector = model.getConnector(connectorId)){ - if (!connector->getTransaction() && - connector->isOperative()) { - selectConnector = connector; - } - } - } else { - //connectorId not specified. Find free connector - for (unsigned int cid = 1; cid < model.getNumConnectors(); cid++) { - auto connector = model.getConnector(cid); - if (!connector->getTransaction() && - connector->isOperative()) { - selectConnector = connector; - connectorId = cid; - break; - } - } - } - - if (selectConnector) { - - bool success = true; + auto rcSvc = context.getModel16().getRemoteControlService(); - int chargingProfileId = -1; //keep Id after moving charging profile to SCService - - if (chargingProfile && model.getSmartChargingService()) { - chargingProfileId = chargingProfile->getChargingProfileId(); - success = model.getSmartChargingService()->setChargingProfile(connectorId, std::move(chargingProfile)); - } - - if (success) { - std::shared_ptr tx; - auto authorizeRemoteTxRequests = declareConfiguration("AuthorizeRemoteTxRequests", false); - if (authorizeRemoteTxRequests && authorizeRemoteTxRequests->getBool()) { - tx = selectConnector->beginTransaction(idTag); - } else { - tx = selectConnector->beginTransaction_authorized(idTag); - } - selectConnector->updateTxNotification(TxNotification_RemoteStart); - if (tx) { - if (chargingProfileId >= 0) { - tx->setTxProfileId(chargingProfileId); - } - } else { - success = false; - } - } - - accepted = success; - } else { - MO_DBG_INFO("No connector to start transaction"); - accepted = false; + status = rcService.remoteStartTransaction(connectorId, idTag, std::move(chargingProfile)); + if (status == Ocpp16::RemoteStartStopStatus::ERR_INTERNAL) { + errorCode = "InternalError"; + return; } } std::unique_ptr RemoteStartTransaction::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - if (accepted) { + if (status == Ocpp16::RemoteStartStopStatus::Accepted) { payload["status"] = "Accepted"; } else { payload["status"] = "Rejected"; @@ -134,15 +101,4 @@ std::unique_ptr RemoteStartTransaction::createConf(){ return doc; } -std::unique_ptr RemoteStartTransaction::createReq() { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); - JsonObject payload = doc->to(); - - payload["idTag"] = "A0000000"; - - return doc; -} - -void RemoteStartTransaction::processConf(JsonObject payload){ - -} +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.h b/src/MicroOcpp/Operations/RemoteStartTransaction.h index 411578bd..a4aecc15 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.h +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.h @@ -6,32 +6,34 @@ #define MO_REMOTESTARTTRANSACTION_H #include +#include #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { -class Model; +class Context; +class RemoteControlService; class ChargingProfile; namespace Ocpp16 { class RemoteStartTransaction : public Operation, public MemoryManaged { private: - Model& model; + Context& context; + RemoteControlService& rcService; - bool accepted = false; + RemoteStartStopStatus status = RemoteStartStopStatus::Rejected; const char *errorCode {nullptr}; const char *errorDescription = ""; public: - RemoteStartTransaction(Model& model); + RemoteStartTransaction(Context& context, RemoteControlService& rcService); const char* getOperationType() override; - std::unique_ptr createReq() override; - - void processConf(JsonObject payload) override; - void processReq(JsonObject payload) override; std::unique_ptr createConf() override; @@ -40,6 +42,7 @@ class RemoteStartTransaction : public Operation, public MemoryManaged { const char *getErrorDescription() override {return errorDescription;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/RemoteStopTransaction.cpp b/src/MicroOcpp/Operations/RemoteStopTransaction.cpp index 8a36699a..fa4a3f91 100644 --- a/src/MicroOcpp/Operations/RemoteStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStopTransaction.cpp @@ -3,45 +3,47 @@ // MIT License #include + #include -#include -#include +#include +#include + +#if MO_ENABLE_V16 -using MicroOcpp::Ocpp16::RemoteStopTransaction; -using MicroOcpp::JsonDoc; +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -RemoteStopTransaction::RemoteStopTransaction(Model& model) : MemoryManaged("v16.Operation.", "RemoteStopTransaction"), model(model) { +RemoteStopTransaction::RemoteStopTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "RemoteStopTransaction"), context(context), rcService(rcService) { } -const char* RemoteStopTransaction::getOperationType(){ +const char* RemoteStopTransaction::getOperationType() { return "RemoteStopTransaction"; } void RemoteStopTransaction::processReq(JsonObject payload) { - if (!payload.containsKey("transactionId")) { + if (!payload.containsKey("transactionId") || !payload["transactionId"].is()) { errorCode = "FormationViolation"; } int transactionId = payload["transactionId"]; - for (unsigned int cId = 0; cId < model.getNumConnectors(); cId++) { - auto connector = model.getConnector(cId); - if (connector->getTransaction() && - connector->getTransaction()->getTransactionId() == transactionId) { - connector->endTransaction(nullptr, "Remote"); - accepted = true; - } + status = rcService.remoteStopTransaction(transactionId); + if (status == Ocpp16::RemoteStartStopStatus::ERR_INTERNAL) { + errorCode = "InternalError"; + return; } } -std::unique_ptr RemoteStopTransaction::createConf(){ +std::unique_ptr RemoteStopTransaction::createConf() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - if (accepted){ + if (status == Ocpp16::RemoteStartStopStatus::Accepted) { payload["status"] = "Accepted"; } else { payload["status"] = "Rejected"; } return doc; } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/RemoteStopTransaction.h b/src/MicroOcpp/Operations/RemoteStopTransaction.h index 2a42d695..bc8bc7ac 100644 --- a/src/MicroOcpp/Operations/RemoteStopTransaction.h +++ b/src/MicroOcpp/Operations/RemoteStopTransaction.h @@ -6,21 +6,28 @@ #define MO_REMOTESTOPTRANSACTION_H #include +#include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { -class Model; +class Context; +class RemoteControlService; namespace Ocpp16 { class RemoteStopTransaction : public Operation, public MemoryManaged { private: - Model& model; - bool accepted = false; + Context& context; + RemoteControlService& rcService; + + RemoteStartStopStatus status = RemoteStartStopStatus::Rejected; const char *errorCode = nullptr; public: - RemoteStopTransaction(Model& model); + RemoteStopTransaction(Context& context, RemoteControlService& rcService); const char* getOperationType() override; @@ -31,6 +38,7 @@ class RemoteStopTransaction : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.cpp b/src/MicroOcpp/Operations/RequestStartTransaction.cpp index 1b37f051..00e459c5 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStartTransaction.cpp @@ -2,16 +2,14 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include #include #include -using MicroOcpp::Ocpp201::RequestStartTransaction; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V201 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; RequestStartTransaction::RequestStartTransaction(RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStartTransaction"), rcService(rcService) { @@ -54,10 +52,10 @@ std::unique_ptr RequestStartTransaction::createConf(){ const char *statusCstr = ""; switch (status) { - case RequestStartStopStatus_Accepted: + case RequestStartStopStatus::Accepted: statusCstr = "Accepted"; break; - case RequestStartStopStatus_Rejected: + case RequestStartStopStatus::Rejected: statusCstr = "Rejected"; break; default: @@ -74,4 +72,4 @@ std::unique_ptr RequestStartTransaction::createConf(){ return doc; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.h b/src/MicroOcpp/Operations/RequestStartTransaction.h index 4ee19761..e2bd1715 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.h +++ b/src/MicroOcpp/Operations/RequestStartTransaction.h @@ -5,14 +5,13 @@ #ifndef MO_REQUESTSTARTTRANSACTION_H #define MO_REQUESTSTARTTRANSACTION_H -#include - -#if MO_ENABLE_V201 - #include #include #include #include +#include + +#if MO_ENABLE_V201 namespace MicroOcpp { @@ -26,7 +25,7 @@ class RequestStartTransaction : public Operation, public MemoryManaged { RequestStartStopStatus status; std::shared_ptr transaction; - char transactionId [MO_TXID_LEN_MAX + 1] = {'\0'}; + char transactionId [MO_TXID_SIZE] = {'\0'}; const char *errorCode = nullptr; public: @@ -44,7 +43,5 @@ class RequestStartTransaction : public Operation, public MemoryManaged { } //namespace Ocpp201 } //namespace MicroOcpp - #endif //MO_ENABLE_V201 - #endif diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.cpp b/src/MicroOcpp/Operations/RequestStopTransaction.cpp index a316188e..ee78b96d 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStopTransaction.cpp @@ -2,16 +2,14 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include #include #include -using MicroOcpp::Ocpp201::RequestStopTransaction; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V201 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; RequestStopTransaction::RequestStopTransaction(RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStopTransaction"), rcService(rcService) { @@ -25,7 +23,7 @@ void RequestStopTransaction::processReq(JsonObject payload) { if (!payload.containsKey("transactionId") || !payload["transactionId"].is() || - strlen(payload["transactionId"].as()) > MO_TXID_LEN_MAX) { + strlen(payload["transactionId"].as()) + 1 >= MO_TXID_SIZE) { errorCode = "FormationViolation"; return; } @@ -41,10 +39,10 @@ std::unique_ptr RequestStopTransaction::createConf(){ const char *statusCstr = ""; switch (status) { - case RequestStartStopStatus_Accepted: + case RequestStartStopStatus::Accepted: statusCstr = "Accepted"; break; - case RequestStartStopStatus_Rejected: + case RequestStartStopStatus::Rejected: statusCstr = "Rejected"; break; default: @@ -57,4 +55,4 @@ std::unique_ptr RequestStopTransaction::createConf(){ return doc; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.h b/src/MicroOcpp/Operations/RequestStopTransaction.h index 30664b67..2bcf70a5 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.h +++ b/src/MicroOcpp/Operations/RequestStopTransaction.h @@ -4,14 +4,12 @@ #ifndef MO_REQUESTSTOPTRANSACTION_H #define MO_REQUESTSTOPTRANSACTION_H - -#include - -#if MO_ENABLE_V201 - #include #include #include +#include + +#if MO_ENABLE_V201 namespace MicroOcpp { @@ -41,7 +39,5 @@ class RequestStopTransaction : public Operation, public MemoryManaged { } //namespace Ocpp201 } //namespace MicroOcpp - #endif //MO_ENABLE_V201 - #endif diff --git a/src/MicroOcpp/Operations/ReserveNow.cpp b/src/MicroOcpp/Operations/ReserveNow.cpp index 26857fdb..c0808839 100644 --- a/src/MicroOcpp/Operations/ReserveNow.cpp +++ b/src/MicroOcpp/Operations/ReserveNow.cpp @@ -2,26 +2,27 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_RESERVATION - #include + +#include #include +#include +#include #include -#include #include #include -using MicroOcpp::Ocpp16::ReserveNow; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +ReserveNow::ReserveNow(Context& context, ReservationService& rService) : MemoryManaged("v16.Operation.", "ReserveNow"), context(context), rService(rService) { -ReserveNow::ReserveNow(Model& model) : MemoryManaged("v16.Operation.", "ReserveNow"), model(model) { - } ReserveNow::~ReserveNow() { - + } const char* ReserveNow::getOperationType(){ @@ -40,13 +41,13 @@ void ReserveNow::processReq(JsonObject payload) { } int connectorId = payload["connectorId"] | -1; - if (connectorId < 0 || (unsigned int) connectorId >= model.getNumConnectors()) { + if (connectorId < 0 || (unsigned int) connectorId >= context.getModel16().getNumEvseId()) { errorCode = "PropertyConstraintViolation"; return; } Timestamp expiryDate; - if (!expiryDate.setTime(payload["expiryDate"])) { + if (!context.getClock().parseString(payload["expiryDate"] | "_Undefined", expiryDate)) { MO_DBG_WARN("bad time format"); errorCode = "PropertyConstraintViolation"; return; @@ -65,53 +66,61 @@ void ReserveNow::processReq(JsonObject payload) { int reservationId = payload["reservationId"] | -1; - if (model.getReservationService() && - model.getNumConnectors() > 0) { - auto rService = model.getReservationService(); - auto chargePoint = model.getConnector(0); + auto availSvc = context.getModel16().getAvailabilityService(); + auto availSvcCp = availSvc ? availSvc->getEvse(0) : nullptr; + if (!availSvcCp) { + errorCode = "InternalError"; + return; + } - auto reserveConnectorZeroSupportedBool = declareConfiguration("ReserveConnectorZeroSupported", true, CONFIGURATION_VOLATILE); - if (connectorId == 0 && (!reserveConnectorZeroSupportedBool || !reserveConnectorZeroSupportedBool->getBool())) { - reservationStatus = "Rejected"; + AvailabilityServiceEvse *availSvcEvse = nullptr; + if (connectorId > 0) { + availSvcEvse = availSvc->getEvse(connectorId); + if (!availSvcEvse) { + errorCode = "InternalError"; return; } + } - if (auto reservation = rService->getReservationById(reservationId)) { - reservation->update(reservationId, (unsigned int) connectorId, expiryDate, idTag, parentIdTag); - reservationStatus = "Accepted"; - return; - } + auto configServce = context.getModel16().getConfigurationService(); + auto reserveConnectorZeroSupportedBool = configServce ? configServce->declareConfiguration("ReserveConnectorZeroSupported", true, MO_CONFIGURATION_VOLATILE) : nullptr; + if (!reserveConnectorZeroSupportedBool) { + errorCode = "InternalError"; + return; + } - Connector *connector = nullptr; - - if (connectorId > 0) { - connector = model.getConnector((unsigned int) connectorId); - } + if (connectorId == 0 && !reserveConnectorZeroSupportedBool->getBool()) { + reservationStatus = "Rejected"; + return; + } - if (chargePoint->getStatus() == ChargePointStatus_Faulted || - (connector && connector->getStatus() == ChargePointStatus_Faulted)) { - reservationStatus = "Faulted"; - return; - } + if (auto reservation = rService.getReservationById(reservationId)) { + reservation->update(reservationId, (unsigned int) connectorId, expiryDate, idTag, parentIdTag); + reservationStatus = "Accepted"; + return; + } - if (chargePoint->getStatus() == ChargePointStatus_Unavailable || - (connector && connector->getStatus() == ChargePointStatus_Unavailable)) { - reservationStatus = "Unavailable"; - return; - } + if (availSvcCp->getStatus() == MO_ChargePointStatus_Faulted || + (availSvcEvse && availSvcEvse->getStatus() == MO_ChargePointStatus_Faulted)) { + reservationStatus = "Faulted"; + return; + } - if (connector && connector->getStatus() != ChargePointStatus_Available) { - reservationStatus = "Occupied"; - return; - } + if (availSvcCp->getStatus() == MO_ChargePointStatus_Unavailable || + (availSvcEvse && availSvcEvse->getStatus() == MO_ChargePointStatus_Unavailable)) { + reservationStatus = "Unavailable"; + return; + } - if (rService->updateReservation(reservationId, (unsigned int) connectorId, expiryDate, idTag, parentIdTag)) { - reservationStatus = "Accepted"; - } else { - reservationStatus = "Occupied"; - } + if (availSvcEvse && availSvcEvse->getStatus() != MO_ChargePointStatus_Available) { + reservationStatus = "Occupied"; + return; + } + + if (rService.updateReservation(reservationId, (unsigned int) connectorId, expiryDate, idTag, parentIdTag)) { + reservationStatus = "Accepted"; } else { - errorCode = "InternalError"; + reservationStatus = "Occupied"; } } @@ -129,4 +138,4 @@ std::unique_ptr ReserveNow::createConf(){ return doc; } -#endif //MO_ENABLE_RESERVATION +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION diff --git a/src/MicroOcpp/Operations/ReserveNow.h b/src/MicroOcpp/Operations/ReserveNow.h index cf162ad6..3b430ae6 100644 --- a/src/MicroOcpp/Operations/ReserveNow.h +++ b/src/MicroOcpp/Operations/ReserveNow.h @@ -5,25 +5,27 @@ #ifndef MO_RESERVENOW_H #define MO_RESERVENOW_H +#include #include -#if MO_ENABLE_RESERVATION - -#include +#if MO_ENABLE_V16 && MO_ENABLE_RESERVATION namespace MicroOcpp { -class Model; +class Context; namespace Ocpp16 { +class ReservationService; + class ReserveNow : public Operation, public MemoryManaged { private: - Model& model; + Context& context; + ReservationService& rService; const char *errorCode = nullptr; const char *reservationStatus = nullptr; public: - ReserveNow(Model& model); + ReserveNow(Context& context, ReservationService& rService); ~ReserveNow(); @@ -36,8 +38,7 @@ class ReserveNow : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp - -#endif //MO_ENABLE_RESERVATION +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION #endif diff --git a/src/MicroOcpp/Operations/Reset.cpp b/src/MicroOcpp/Operations/Reset.cpp index 9ab42422..cdaabe6a 100644 --- a/src/MicroOcpp/Operations/Reset.cpp +++ b/src/MicroOcpp/Operations/Reset.cpp @@ -3,77 +3,70 @@ // MIT License #include -#include #include +#include #include -using MicroOcpp::Ocpp16::Reset; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 -Reset::Reset(Model& model) : MemoryManaged("v16.Operation.", "Reset"), model(model) { +using namespace MicroOcpp; + +Ocpp16::Reset::Reset(ResetService& resetService) : MemoryManaged("v16.Operation.", "Reset"), resetService(resetService) { } -const char* Reset::getOperationType(){ +const char* Ocpp16::Reset::getOperationType(){ return "Reset"; } -void Reset::processReq(JsonObject payload) { +void Ocpp16::Reset::processReq(JsonObject payload) { /* * Process the application data here. Note: you have to implement the device reset procedure in your client code. You have to set * a onSendConfListener in which you initiate a reset (e.g. calling ESP.reset() ) */ bool isHard = !strcmp(payload["type"] | "undefined", "Hard"); - if (auto rService = model.getResetService()) { - if (!rService->getExecuteReset()) { - MO_DBG_ERR("No reset handler set. Abort operation"); - //resetAccepted remains false - } else { - if (!rService->getPreReset() || rService->getPreReset()(isHard) || isHard) { - resetAccepted = true; - rService->initiateReset(isHard); - for (unsigned int cId = 0; cId < model.getNumConnectors(); cId++) { - model.getConnector(cId)->endTransaction(nullptr, isHard ? "HardReset" : "SoftReset"); - } - } - } + if (!resetService.isExecuteResetDefined()) { + MO_DBG_ERR("No reset handler set. Abort operation"); + resetAccepted = false; } else { - resetAccepted = true; //assume that onReceiveReset is set + if (!resetService.isPreResetDefined() || resetService.notifyReset(isHard) || isHard) { + resetAccepted = true; + resetService.initiateReset(isHard); + } } } -std::unique_ptr Reset::createConf() { +std::unique_ptr Ocpp16::Reset::createConf() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); payload["status"] = resetAccepted ? "Accepted" : "Rejected"; return doc; } -#if MO_ENABLE_V201 +#endif //MO_ENABLE_V16 -#include +#if MO_ENABLE_V201 -namespace MicroOcpp { -namespace Ocpp201 { +using namespace MicroOcpp; -Reset::Reset(ResetService& resetService) : MemoryManaged("v201.Operation.", "Reset"), resetService(resetService) { +Ocpp201::Reset::Reset(ResetService& resetService) : MemoryManaged("v201.Operation.", "Reset"), resetService(resetService) { } -const char* Reset::getOperationType(){ +const char* Ocpp201::Reset::getOperationType(){ return "Reset"; } -void Reset::processReq(JsonObject payload) { +void Ocpp201::Reset::processReq(JsonObject payload) { - ResetType type; + MO_ResetType type; const char *typeCstr = payload["type"] | "_Undefined"; if (!strcmp(typeCstr, "Immediate")) { - type = ResetType_Immediate; + type = MO_ResetType_Immediate; } else if (!strcmp(typeCstr, "OnIdle")) { - type = ResetType_OnIdle; + type = MO_ResetType_OnIdle; } else { errorCode = "FormationViolation"; return; @@ -91,7 +84,7 @@ void Reset::processReq(JsonObject payload) { status = resetService.initiateReset(type, evseId); } -std::unique_ptr Reset::createConf() { +std::unique_ptr Ocpp201::Reset::createConf() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); @@ -114,6 +107,4 @@ std::unique_ptr Reset::createConf() { return doc; } -} //end namespace Ocpp201 -} //end namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/Reset.h b/src/MicroOcpp/Operations/Reset.h index 53aca0d0..0218d47b 100644 --- a/src/MicroOcpp/Operations/Reset.h +++ b/src/MicroOcpp/Operations/Reset.h @@ -2,25 +2,27 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#ifndef RESET_H -#define RESET_H +#ifndef MO_RESET_H +#define MO_RESET_H #include #include #include +#include -namespace MicroOcpp { - -class Model; +#if MO_ENABLE_V16 +namespace MicroOcpp { namespace Ocpp16 { +class ResetService; + class Reset : public Operation, public MemoryManaged { private: - Model& model; + ResetService& resetService; bool resetAccepted {false}; public: - Reset(Model& model); + Reset(ResetService& resetService); const char* getOperationType() override; @@ -29,8 +31,9 @@ class Reset : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 @@ -56,8 +59,7 @@ class Reset : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp - +} //namespace Ocpp201 +} //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/SecurityEventNotification.cpp b/src/MicroOcpp/Operations/SecurityEventNotification.cpp index d4837315..78cacbd7 100644 --- a/src/MicroOcpp/Operations/SecurityEventNotification.cpp +++ b/src/MicroOcpp/Operations/SecurityEventNotification.cpp @@ -2,18 +2,20 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include + +#include #include -using MicroOcpp::Ocpp201::SecurityEventNotification; -using MicroOcpp::JsonDoc; +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT -SecurityEventNotification::SecurityEventNotification(const char *type, const Timestamp& timestamp) : MemoryManaged("v201.Operation.", "SecurityEventNotification"), type(makeString(getMemoryTag(), type ? type : "")), timestamp(timestamp) { +using namespace MicroOcpp; +SecurityEventNotification::SecurityEventNotification(Context& context, const char *type, const Timestamp& timestamp) : MemoryManaged("v201.Operation.", "SecurityEventNotification"), context(context), timestamp(timestamp) { + auto ret = snprintf(this->type, sizeof(this->type), "%s", type); + if (ret < 0 || (size_t)ret >= sizeof(this->type)) { + MO_DBG_ERR("type too long (ignored excess characters)"); + } } const char* SecurityEventNotification::getOperationType(){ @@ -24,14 +26,17 @@ std::unique_ptr SecurityEventNotification::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2) + - JSONDATE_LENGTH + 1); + MO_JSONDATE_SIZE); JsonObject payload = doc->to(); - payload["type"] = type.c_str(); + payload["type"] = (const char*)type; //force zero-copy - char timestampStr [JSONDATE_LENGTH + 1]; - timestamp.toJsonString(timestampStr, sizeof(timestampStr)); + char timestampStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toJsonString(timestamp, timestampStr, sizeof(timestampStr))) { + MO_DBG_ERR("internal error"); + timestampStr[0] = '\0'; + } payload["timestamp"] = timestampStr; return doc; @@ -51,4 +56,4 @@ std::unique_ptr SecurityEventNotification::createConf() { return createEmptyDocument(); } -#endif // MO_ENABLE_V201 +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT diff --git a/src/MicroOcpp/Operations/SecurityEventNotification.h b/src/MicroOcpp/Operations/SecurityEventNotification.h index 5d618c00..09a61bf4 100644 --- a/src/MicroOcpp/Operations/SecurityEventNotification.h +++ b/src/MicroOcpp/Operations/SecurityEventNotification.h @@ -1,30 +1,29 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_SECURITYEVENTNOTIFICATION_H #define MO_SECURITYEVENTNOTIFICATION_H -#include - -#if MO_ENABLE_V201 - #include #include #include +#include +#include -namespace MicroOcpp { +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT -namespace Ocpp201 { +namespace MicroOcpp { class SecurityEventNotification : public Operation, public MemoryManaged { private: - String type; + Context& context; + char type [MO_SECURITY_EVENT_TYPE_LENGTH + 1]; Timestamp timestamp; const char *errorCode = nullptr; public: - SecurityEventNotification(const char *type, const Timestamp& timestamp); + SecurityEventNotification(Context& context, const char *type, const Timestamp& timestamp); // the length of type, not counting the terminating 0, must be <= MO_SECURITY_EVENT_TYPE_LENGTH. Excess characters are cropped const char* getOperationType() override; @@ -40,9 +39,6 @@ class SecurityEventNotification : public Operation, public MemoryManaged { }; -} //namespace Ocpp201 } //namespace MicroOcpp - -#endif //MO_ENABLE_V201 - +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SECURITY_EVENT #endif diff --git a/src/MicroOcpp/Operations/SendLocalList.cpp b/src/MicroOcpp/Operations/SendLocalList.cpp index 45f0dfbe..8dcf5eaa 100644 --- a/src/MicroOcpp/Operations/SendLocalList.cpp +++ b/src/MicroOcpp/Operations/SendLocalList.cpp @@ -2,16 +2,14 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_LOCAL_AUTH - #include #include #include -using MicroOcpp::Ocpp16::SendLocalList; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; SendLocalList::SendLocalList(AuthorizationService& authService) : MemoryManaged("v16.Operation.", "SendLocalList"), authService(authService) { @@ -71,4 +69,4 @@ std::unique_ptr SendLocalList::createConf(){ return doc; } -#endif //MO_ENABLE_LOCAL_AUTH +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH diff --git a/src/MicroOcpp/Operations/SendLocalList.h b/src/MicroOcpp/Operations/SendLocalList.h index d58265bd..928091da 100644 --- a/src/MicroOcpp/Operations/SendLocalList.h +++ b/src/MicroOcpp/Operations/SendLocalList.h @@ -5,18 +5,16 @@ #ifndef MO_SENDLOCALLIST_H #define MO_SENDLOCALLIST_H +#include #include -#if MO_ENABLE_LOCAL_AUTH - -#include +#if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH namespace MicroOcpp { +namespace Ocpp16 { class AuthorizationService; -namespace Ocpp16 { - class SendLocalList : public Operation, public MemoryManaged { private: AuthorizationService& authService; @@ -37,8 +35,7 @@ class SendLocalList : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp - -#endif //MO_ENABLE_LOCAL_AUTH +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Operations/SetChargingProfile.cpp b/src/MicroOcpp/Operations/SetChargingProfile.cpp index 1ff28acd..fefdd434 100644 --- a/src/MicroOcpp/Operations/SetChargingProfile.cpp +++ b/src/MicroOcpp/Operations/SetChargingProfile.cpp @@ -3,15 +3,19 @@ // MIT License #include + +#include #include #include -#include +#include +#include #include -using MicroOcpp::Ocpp16::SetChargingProfile; -using MicroOcpp::JsonDoc; +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING + +using namespace MicroOcpp; -SetChargingProfile::SetChargingProfile(Model& model, SmartChargingService& scService) : MemoryManaged("v16.Operation.", "SetChargingProfile"), model(model), scService(scService) { +SetChargingProfile::SetChargingProfile(Context& context, SmartChargingService& scService) : MemoryManaged("v16.Operation.", "SetChargingProfile"), context(context), scService(scService), ocppVersion(context.getOcppVersion()) { } @@ -25,64 +29,124 @@ const char* SetChargingProfile::getOperationType(){ void SetChargingProfile::processReq(JsonObject payload) { - int connectorId = payload["connectorId"] | -1; - if (connectorId < 0 || !payload.containsKey("csChargingProfiles")) { - errorCode = "FormationViolation"; - return; - } + int evseId = -1; + JsonObject chargingProfileJson; - if ((unsigned int) connectorId >= model.getNumConnectors()) { - errorCode = "PropertyConstraintViolation"; - return; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + evseId = payload["connectorId"] | -1; + if (evseId < 0 || !payload.containsKey("csChargingProfiles")) { + errorCode = "FormationViolation"; + return; + } + + if ((unsigned int) evseId >= context.getModel16().getNumEvseId()) { + errorCode = "PropertyConstraintViolation"; + return; + } + + chargingProfileJson = payload["csChargingProfiles"]; } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + evseId = payload["evseId"] | -1; + if (evseId < 0 || !payload.containsKey("chargingProfile")) { + errorCode = "FormationViolation"; + return; + } - JsonObject csChargingProfiles = payload["csChargingProfiles"]; + if ((unsigned int) evseId >= context.getModel201().getNumEvseId()) { + errorCode = "PropertyConstraintViolation"; + return; + } + + chargingProfileJson = payload["chargingProfile"]; + } + #endif //MO_ENABLE_V201 - auto chargingProfile = loadChargingProfile(csChargingProfiles); + auto chargingProfile = std::unique_ptr(new ChargingProfile()); if (!chargingProfile) { - errorCode = "PropertyConstraintViolation"; - errorDescription = "csChargingProfiles validation failed"; + MO_DBG_ERR("OOM"); + errorCode = "InternalError"; + return; + } + + bool valid = chargingProfile->parseJson(context.getClock(), ocppVersion, chargingProfileJson); + if (!valid) { + errorCode = "FormationViolation"; + errorDescription = "chargingProfile validation failed"; return; } - if (chargingProfile->getChargingProfilePurpose() == ChargingProfilePurposeType::TxProfile) { + if (chargingProfile->chargingProfilePurpose == ChargingProfilePurposeType::TxProfile) { // if TxProfile, check if a transaction is running - if (connectorId == 0) { + if (evseId == 0) { errorCode = "PropertyConstraintViolation"; errorDescription = "Cannot set TxProfile at connectorId 0"; return; } - Connector *connector = model.getConnector(connectorId); - if (!connector) { - errorCode = "PropertyConstraintViolation"; - return; - } - auto& transaction = connector->getTransaction(); - if (!transaction || !connector->getTransaction()->isRunning()) { - //no transaction running, reject profile - accepted = false; - return; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + auto txService = context.getModel16().getTransactionService(); + auto txServiceEvse = txService ? txService->getEvse(evseId) : nullptr; + if (!txServiceEvse) { + errorCode = "InternalError"; + return; + } + + auto transaction = txServiceEvse->getTransaction(); + if (!transaction || !transaction->isRunning()) { + //no transaction running, reject profile + accepted = false; + return; + } + + if (chargingProfile->transactionId16 >= 0 && + chargingProfile->transactionId16 != transaction->getTransactionId()) { + //transactionId undefined / mismatch + accepted = false; + return; + } } - - if (chargingProfile->getTransactionId() >= 0 && - chargingProfile->getTransactionId() != transaction->getTransactionId()) { - //transactionId undefined / mismatch - accepted = false; - return; + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + auto txService = context.getModel201().getTransactionService(); + auto txServiceEvse = txService ? txService->getEvse(evseId) : nullptr; + if (!txServiceEvse) { + errorCode = "InternalError"; + return; + } + + auto transaction = txServiceEvse->getTransaction(); + if (!transaction || !(transaction->started && !transaction->stopped)) { + //no transaction running, reject profile + accepted = false; + return; + } + + if (*chargingProfile->transactionId201 >= 0 && + strcmp(chargingProfile->transactionId201, transaction->transactionId)) { + //transactionId undefined / mismatch + accepted = false; + return; + } } + #endif //MO_ENABLE_V201 //seems good - } else if (chargingProfile->getChargingProfilePurpose() == ChargingProfilePurposeType::ChargePointMaxProfile) { - if (connectorId > 0) { + } else if (chargingProfile->chargingProfilePurpose == ChargingProfilePurposeType::ChargePointMaxProfile) { + if (evseId > 0) { errorCode = "PropertyConstraintViolation"; errorDescription = "Cannot set ChargePointMaxProfile at connectorId > 0"; return; } } - accepted = scService.setChargingProfile(connectorId, std::move(chargingProfile)); + accepted = scService.setChargingProfile(evseId, std::move(chargingProfile)); } std::unique_ptr SetChargingProfile::createConf(){ @@ -95,3 +159,5 @@ std::unique_ptr SetChargingProfile::createConf(){ } return doc; } + +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING diff --git a/src/MicroOcpp/Operations/SetChargingProfile.h b/src/MicroOcpp/Operations/SetChargingProfile.h index 1ce35f1e..22559b63 100644 --- a/src/MicroOcpp/Operations/SetChargingProfile.h +++ b/src/MicroOcpp/Operations/SetChargingProfile.h @@ -6,24 +6,26 @@ #define MO_SETCHARGINGPROFILE_H #include +#include + +#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING namespace MicroOcpp { -class Model; +class Context; class SmartChargingService; -namespace Ocpp16 { - class SetChargingProfile : public Operation, public MemoryManaged { private: - Model& model; + Context& context; SmartChargingService& scService; + int ocppVersion = -1; bool accepted = false; const char *errorCode = nullptr; const char *errorDescription = ""; public: - SetChargingProfile(Model& model, SmartChargingService& scService); + SetChargingProfile(Context& context, SmartChargingService& scService); ~SetChargingProfile(); @@ -37,6 +39,6 @@ class SetChargingProfile : public Operation, public MemoryManaged { const char *getErrorDescription() override {return errorDescription;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace MicroOcpp +#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_SMARTCHARGING #endif diff --git a/src/MicroOcpp/Operations/SetVariables.cpp b/src/MicroOcpp/Operations/SetVariables.cpp index 23bd043d..fa1477dd 100644 --- a/src/MicroOcpp/Operations/SetVariables.cpp +++ b/src/MicroOcpp/Operations/SetVariables.cpp @@ -2,17 +2,14 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include #include #include -using MicroOcpp::Ocpp201::SetVariableData; -using MicroOcpp::Ocpp201::SetVariables; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V201 + +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp201; SetVariableData::SetVariableData(const char *memory_tag) : componentName{makeString(memory_tag)}, variableName{makeString(memory_tag)} { @@ -182,4 +179,4 @@ std::unique_ptr SetVariables::createConf(){ return doc; } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/SetVariables.h b/src/MicroOcpp/Operations/SetVariables.h index fb0ca889..16e6f4e0 100644 --- a/src/MicroOcpp/Operations/SetVariables.h +++ b/src/MicroOcpp/Operations/SetVariables.h @@ -5,20 +5,18 @@ #ifndef MO_SETVARIABLES_H #define MO_SETVARIABLES_H -#include - -#if MO_ENABLE_V201 - #include #include #include +#include + +#if MO_ENABLE_V201 namespace MicroOcpp { +namespace Ocpp201 { class VariableService; -namespace Ocpp201 { - // SetVariableDataType (2.44) and // SetVariableResultType (2.45) struct SetVariableData { diff --git a/src/MicroOcpp/Operations/StartTransaction.cpp b/src/MicroOcpp/Operations/StartTransaction.cpp index 907baaf3..5e321dd2 100644 --- a/src/MicroOcpp/Operations/StartTransaction.cpp +++ b/src/MicroOcpp/Operations/StartTransaction.cpp @@ -3,19 +3,21 @@ // MIT License #include + +#include #include #include #include #include #include #include -#include -using MicroOcpp::Ocpp16::StartTransaction; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -StartTransaction::StartTransaction(Model& model, std::shared_ptr transaction) : MemoryManaged("v16.Operation.", "StartTransaction"), model(model), transaction(transaction) { +StartTransaction::StartTransaction(Context& context, Transaction *transaction) : MemoryManaged("v16.Operation.", "StartTransaction"), context(context), transaction(transaction) { } @@ -31,28 +33,23 @@ std::unique_ptr StartTransaction::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(6) + - (IDTAG_LEN_MAX + 1) + - (JSONDATE_LENGTH + 1)); + MO_JSONDATE_SIZE); JsonObject payload = doc->to(); payload["connectorId"] = transaction->getConnectorId(); - payload["idTag"] = (char*) transaction->getIdTag(); + payload["idTag"] = transaction->getIdTag(); payload["meterStart"] = transaction->getMeterStart(); if (transaction->getReservationId() >= 0) { payload["reservationId"] = transaction->getReservationId(); } - if (transaction->getStartTimestamp() < MIN_TIME && - transaction->getStartBootNr() == model.getBootNr()) { - MO_DBG_DEBUG("adjust preboot StartTx timestamp"); - Timestamp adjusted = model.getClock().adjustPrebootTimestamp(transaction->getStartTimestamp()); - transaction->setStartTimestamp(adjusted); + char timestamp[MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toJsonString(transaction->getStartTimestamp(), timestamp, sizeof(timestamp))) { + MO_DBG_ERR("internal error"); + timestamp[0] = '\0'; } - - char timestamp[JSONDATE_LENGTH + 1] = {'\0'}; - transaction->getStartTimestamp().toJsonString(timestamp, JSONDATE_LENGTH + 1); payload["timestamp"] = timestamp; return doc; @@ -77,31 +74,25 @@ void StartTransaction::processConf(JsonObject payload) { } transaction->getStartSync().confirm(); - transaction->commit(); + + if (auto filesystem = context.getFilesystem()) { + TransactionStore::store(filesystem, context, *transaction); + } #if MO_ENABLE_LOCAL_AUTH - if (auto authService = model.getAuthorizationService()) { + if (auto authService = context.getModel16().getAuthorizationService()) { authService->notifyAuthorization(transaction->getIdTag(), payload["idTagInfo"]); } #endif //MO_ENABLE_LOCAL_AUTH } -void StartTransaction::processReq(JsonObject payload) { - - /** - * Ignore Contents of this Req-message, because this is for debug purposes only - */ - -} - -std::unique_ptr StartTransaction::createConf() { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2)); - JsonObject payload = doc->to(); - - JsonObject idTagInfo = payload.createNestedObject("idTagInfo"); - idTagInfo["status"] = "Accepted"; +#if MO_ENABLE_MOCK_SERVER +int StartTransaction::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { + (void)userStatus; + (void)userData; static int uniqueTxId = 1000; - payload["transactionId"] = uniqueTxId++; //sample data for debug purpose - - return doc; + return snprintf(buf, size, "{\"idTagInfo\":{\"status\":\"Accepted\"}, \"transactionId\":%i}", uniqueTxId++); } +#endif //MO_ENABLE_MOCK_SERVER + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/StartTransaction.h b/src/MicroOcpp/Operations/StartTransaction.h index a73b5368..d78aebea 100644 --- a/src/MicroOcpp/Operations/StartTransaction.h +++ b/src/MicroOcpp/Operations/StartTransaction.h @@ -8,22 +8,25 @@ #include #include #include -#include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { -class Model; -class Transaction; +class Context; namespace Ocpp16 { +class Transaction; + class StartTransaction : public Operation, public MemoryManaged { private: - Model& model; - std::shared_ptr transaction; + Context& context; + Transaction *transaction = nullptr; public: - StartTransaction(Model& model, std::shared_ptr transaction); + StartTransaction(Context& context, Transaction *transaction); ~StartTransaction(); @@ -33,11 +36,12 @@ class StartTransaction : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; - void processReq(JsonObject payload) override; - - std::unique_ptr createConf() override; +#if MO_ENABLE_MOCK_SERVER + static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); +#endif }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/StatusNotification.cpp b/src/MicroOcpp/Operations/StatusNotification.cpp index c5fb1a60..3d520811 100644 --- a/src/MicroOcpp/Operations/StatusNotification.cpp +++ b/src/MicroOcpp/Operations/StatusNotification.cpp @@ -3,62 +3,31 @@ // MIT License #include + +#include #include #include #include -namespace MicroOcpp { - -//helper function -const char *cstrFromOcppEveState(ChargePointStatus state) { - switch (state) { - case (ChargePointStatus_Available): - return "Available"; - case (ChargePointStatus_Preparing): - return "Preparing"; - case (ChargePointStatus_Charging): - return "Charging"; - case (ChargePointStatus_SuspendedEVSE): - return "SuspendedEVSE"; - case (ChargePointStatus_SuspendedEV): - return "SuspendedEV"; - case (ChargePointStatus_Finishing): - return "Finishing"; - case (ChargePointStatus_Reserved): - return "Reserved"; - case (ChargePointStatus_Unavailable): - return "Unavailable"; - case (ChargePointStatus_Faulted): - return "Faulted"; -#if MO_ENABLE_V201 - case (ChargePointStatus_Occupied): - return "Occupied"; -#endif - default: - MO_DBG_ERR("ChargePointStatus not specified"); - /* fall through */ - case (ChargePointStatus_UNDEFINED): - return "UNDEFINED"; - } -} +#if MO_ENABLE_V16 -namespace Ocpp16 { +using namespace MicroOcpp; -StatusNotification::StatusNotification(int connectorId, ChargePointStatus currentStatus, const Timestamp ×tamp, ErrorData errorData) - : MemoryManaged("v16.Operation.", "StatusNotification"), connectorId(connectorId), currentStatus(currentStatus), timestamp(timestamp), errorData(errorData) { +Ocpp16::StatusNotification::StatusNotification(Context& context, int connectorId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp, MO_ErrorData errorData) + : MemoryManaged("v16.Operation.", "StatusNotification"), context(context), connectorId(connectorId), currentStatus(currentStatus), timestamp(timestamp), errorData(errorData) { - if (currentStatus != ChargePointStatus_UNDEFINED) { - MO_DBG_INFO("New status: %s (connectorId %d)", cstrFromOcppEveState(currentStatus), connectorId); + if (currentStatus != MO_ChargePointStatus_UNDEFINED) { + MO_DBG_INFO("New status: %s (connectorId %d)", mo_serializeChargePointStatus(currentStatus), connectorId); } } -const char* StatusNotification::getOperationType(){ +const char* Ocpp16::StatusNotification::getOperationType(){ return "StatusNotification"; } -std::unique_ptr StatusNotification::createReq() { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(7) + (JSONDATE_LENGTH + 1)); +std::unique_ptr Ocpp16::StatusNotification::createReq() { + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(7) + MO_JSONDATE_SIZE); JsonObject payload = doc->to(); payload["connectorId"] = connectorId; @@ -75,23 +44,26 @@ std::unique_ptr StatusNotification::createReq() { if (errorData.vendorErrorCode) { payload["vendorErrorCode"] = errorData.vendorErrorCode; } - } else if (currentStatus == ChargePointStatus_UNDEFINED) { + } else if (currentStatus == MO_ChargePointStatus_UNDEFINED) { MO_DBG_ERR("Reporting undefined status"); payload["errorCode"] = "InternalError"; } else { payload["errorCode"] = "NoError"; } - payload["status"] = cstrFromOcppEveState(currentStatus); + payload["status"] = mo_serializeChargePointStatus(currentStatus); - char timestamp_cstr[JSONDATE_LENGTH + 1] = {'\0'}; - timestamp.toJsonString(timestamp_cstr, JSONDATE_LENGTH + 1); - payload["timestamp"] = timestamp_cstr; + char timestampStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toJsonString(timestamp, timestampStr, sizeof(timestampStr))) { + MO_DBG_ERR("internal error"); + timestampStr[0] = '\0'; + } + payload["timestamp"] = timestampStr; return doc; } -void StatusNotification::processConf(JsonObject payload) { +void Ocpp16::StatusNotification::processConf(JsonObject payload) { /* * Empty payload */ @@ -100,42 +72,43 @@ void StatusNotification::processConf(JsonObject payload) { /* * For debugging only */ -void StatusNotification::processReq(JsonObject payload) { +void Ocpp16::StatusNotification::processReq(JsonObject payload) { } /* * For debugging only */ -std::unique_ptr StatusNotification::createConf(){ +std::unique_ptr Ocpp16::StatusNotification::createConf(){ return createEmptyDocument(); } -} // namespace Ocpp16 -} // namespace MicroOcpp +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -namespace MicroOcpp { -namespace Ocpp201 { +using namespace MicroOcpp; -StatusNotification::StatusNotification(EvseId evseId, ChargePointStatus currentStatus, const Timestamp ×tamp) - : MemoryManaged("v201.Operation.", "StatusNotification"), evseId(evseId), timestamp(timestamp), currentStatus(currentStatus) { +Ocpp201::StatusNotification::StatusNotification(Context& context, EvseId evseId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp) + : MemoryManaged("v201.Operation.", "StatusNotification"), context(context), evseId(evseId), timestamp(timestamp), currentStatus(currentStatus) { } -const char* StatusNotification::getOperationType(){ +const char* Ocpp201::StatusNotification::getOperationType(){ return "StatusNotification"; } -std::unique_ptr StatusNotification::createReq() { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(4) + (JSONDATE_LENGTH + 1)); +std::unique_ptr Ocpp201::StatusNotification::createReq() { + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(4) + MO_JSONDATE_SIZE); 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); + char timestampStr [MO_JSONDATE_SIZE] = {'\0'}; + if (!context.getClock().toJsonString(timestamp, timestampStr, sizeof(timestampStr))) { + MO_DBG_ERR("internal error"); + timestampStr[0] = '\0'; + } + payload["timestamp"] = timestampStr; + payload["connectorStatus"] = mo_serializeChargePointStatus(currentStatus); payload["evseId"] = evseId.id; payload["connectorId"] = evseId.id == 0 ? 0 : evseId.connectorId >= 0 ? evseId.connectorId : 1; @@ -143,13 +116,24 @@ std::unique_ptr StatusNotification::createReq() { } -void StatusNotification::processConf(JsonObject payload) { +void Ocpp201::StatusNotification::processConf(JsonObject payload) { /* * Empty payload */ } -} // namespace Ocpp201 -} // namespace MicroOcpp +/* + * For debugging only + */ +void Ocpp201::StatusNotification::processReq(JsonObject payload) { + +} + +/* + * For debugging only + */ +std::unique_ptr Ocpp201::StatusNotification::createConf(){ + return createEmptyDocument(); +} #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/StatusNotification.h b/src/MicroOcpp/Operations/StatusNotification.h index 2e65dcec..3af10e7e 100644 --- a/src/MicroOcpp/Operations/StatusNotification.h +++ b/src/MicroOcpp/Operations/StatusNotification.h @@ -2,29 +2,32 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#ifndef STATUSNOTIFICATION_H -#define STATUSNOTIFICATION_H +#ifndef MO_STATUSNOTIFICATION_H +#define MO_STATUSNOTIFICATION_H #include #include -#include -#include +#include +#include #include +#if MO_ENABLE_V16 + namespace MicroOcpp { - -const char *cstrFromOcppEveState(ChargePointStatus state); + +class Context; namespace Ocpp16 { class StatusNotification : public Operation, public MemoryManaged { private: + Context& context; int connectorId = 1; - ChargePointStatus currentStatus = ChargePointStatus_UNDEFINED; + MO_ChargePointStatus currentStatus = MO_ChargePointStatus_UNDEFINED; Timestamp timestamp; - ErrorData errorData; + MO_ErrorData errorData; public: - StatusNotification(int connectorId, ChargePointStatus currentStatus, const Timestamp ×tamp, ErrorData errorData = nullptr); + StatusNotification(Context& context, int connectorId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp, MO_ErrorData errorData); const char* getOperationType() override; @@ -35,40 +38,38 @@ class StatusNotification : public Operation, public MemoryManaged { void processReq(JsonObject payload) override; std::unique_ptr createConf() override; - - int getConnectorId() { - return connectorId; - } }; -} // namespace Ocpp16 -} // namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -#include - namespace MicroOcpp { namespace Ocpp201 { class StatusNotification : public Operation, public MemoryManaged { private: + Context& context; EvseId evseId; Timestamp timestamp; - ChargePointStatus currentStatus = ChargePointStatus_UNDEFINED; + MO_ChargePointStatus currentStatus = MO_ChargePointStatus_UNDEFINED; public: - StatusNotification(EvseId evseId, ChargePointStatus currentStatus, const Timestamp ×tamp); + StatusNotification(Context& context, EvseId evseId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp); const char* getOperationType() override; std::unique_ptr createReq() override; void processConf(JsonObject payload) override; -}; -} // namespace Ocpp201 -} // namespace MicroOcpp + void processReq(JsonObject payload) override; -#endif //MO_ENABLE_V201 + std::unique_ptr createConf() override; +}; +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/StopTransaction.cpp b/src/MicroOcpp/Operations/StopTransaction.cpp index a3aef101..53cdbd5a 100644 --- a/src/MicroOcpp/Operations/StopTransaction.cpp +++ b/src/MicroOcpp/Operations/StopTransaction.cpp @@ -3,6 +3,8 @@ // MIT License #include + +#include #include #include #include @@ -10,18 +12,14 @@ #include #include #include -#include - -using MicroOcpp::Ocpp16::StopTransaction; -using MicroOcpp::JsonDoc; -StopTransaction::StopTransaction(Model& model, std::shared_ptr transaction) - : MemoryManaged("v16.Operation.", "StopTransaction"), model(model), transaction(transaction) { +#if MO_ENABLE_V16 -} +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; -StopTransaction::StopTransaction(Model& model, std::shared_ptr transaction, Vector> transactionData) - : MemoryManaged("v16.Operation.", "StopTransaction"), model(model), transaction(transaction), transactionData(std::move(transactionData)) { +StopTransaction::StopTransaction(Context& context, Transaction *transaction) + : MemoryManaged("v16.Operation.", "StopTransaction"), context(context), transaction(transaction) { } @@ -31,85 +29,58 @@ const char* StopTransaction::getOperationType() { std::unique_ptr StopTransaction::createReq() { - /* - * Adjust timestamps in case they were taken before initial Clock setting - */ - if (transaction->getStopTimestamp() < MIN_TIME) { - //Timestamp taken before Clock value defined. Determine timestamp - if (transaction->getStopBootNr() == model.getBootNr()) { - //possible to calculate real timestamp - Timestamp adjusted = model.getClock().adjustPrebootTimestamp(transaction->getStopTimestamp()); - transaction->setStopTimestamp(adjusted); - } else if (transaction->getStartTimestamp() >= MIN_TIME) { - MO_DBG_WARN("set stopTime = startTime because correct time is not available"); - transaction->setStopTimestamp(transaction->getStartTimestamp() + 1); //1s behind startTime to keep order in backend DB - } else { - MO_DBG_ERR("failed to determine StopTx timestamp"); - //send invalid value - } - } - - // if StopTx timestamp is before StartTx timestamp, something probably went wrong. Restore reasonable temporal order - if (transaction->getStopTimestamp() < transaction->getStartTimestamp()) { - MO_DBG_WARN("set stopTime = startTime because stopTime was before startTime"); - transaction->setStopTimestamp(transaction->getStartTimestamp() + 1); //1s behind startTime to keep order in backend DB - } - - for (auto mv = transactionData.begin(); mv != transactionData.end(); mv++) { - if ((*mv)->getTimestamp() < MIN_TIME) { - //time off. Try to adjust, otherwise send invalid value - if ((*mv)->getReadingContext() == ReadingContext_TransactionBegin) { - (*mv)->setTimestamp(transaction->getStartTimestamp()); - } else if ((*mv)->getReadingContext() == ReadingContext_TransactionEnd) { - (*mv)->setTimestamp(transaction->getStopTimestamp()); - } else { - (*mv)->setTimestamp(transaction->getStartTimestamp() + 1); - } - } - } + size_t capacity = + JSON_OBJECT_SIZE(6) + //total of 6 fields + MO_JSONDATE_SIZE; //timestamp string - auto txDataJson = makeVector>(getMemoryTag()); - size_t txDataJson_size = 0; - for (auto mv = transactionData.begin(); mv != transactionData.end(); mv++) { - auto mvJson = (*mv)->toJson(); - if (!mvJson) { - return nullptr; + auto& txMeterValues = transaction->getTxMeterValues(); + capacity += JSON_ARRAY_SIZE(txMeterValues.size()); + for (size_t i = 0; i < txMeterValues.size(); i++) { + int ret = txMeterValues[i]->getJsonCapacity(MO_OCPP_V16, /*internalFormat*/ false); + if (ret >= 0) { + capacity += (size_t)ret; + } else { + MO_DBG_ERR("tx meter values error"); } - txDataJson_size += mvJson->capacity(); - txDataJson.emplace_back(std::move(mvJson)); } - auto txDataDoc = initJsonDoc(getMemoryTag(), JSON_ARRAY_SIZE(txDataJson.size()) + txDataJson_size); - for (auto mvJson = txDataJson.begin(); mvJson != txDataJson.end(); mvJson++) { - txDataDoc.add(**mvJson); - } - - auto doc = makeJsonDoc(getMemoryTag(), - JSON_OBJECT_SIZE(6) + //total of 6 fields - (IDTAG_LEN_MAX + 1) + //stop idTag - (JSONDATE_LENGTH + 1) + //timestamp string - (REASON_LEN_MAX + 1) + //reason string - txDataDoc.capacity()); + auto doc = makeJsonDoc(getMemoryTag(), capacity); JsonObject payload = doc->to(); if (transaction->getStopIdTag() && *transaction->getStopIdTag()) { - payload["idTag"] = (char*) transaction->getStopIdTag(); + payload["idTag"] = transaction->getStopIdTag(); } payload["meterStop"] = transaction->getMeterStop(); - char timestamp[JSONDATE_LENGTH + 1] = {'\0'}; - transaction->getStopTimestamp().toJsonString(timestamp, JSONDATE_LENGTH + 1); + char timestamp [MO_JSONDATE_SIZE]; + if (!context.getClock().toJsonString(transaction->getStopTimestamp(), timestamp, sizeof(timestamp))) { + MO_DBG_ERR("internal error"); + } payload["timestamp"] = timestamp; payload["transactionId"] = transaction->getTransactionId(); - + if (transaction->getStopReason() && *transaction->getStopReason()) { - payload["reason"] = (char*) transaction->getStopReason(); + payload["reason"] = transaction->getStopReason(); } - if (!transactionData.empty()) { - payload["transactionData"] = txDataDoc; + if (!txMeterValues.empty()) { + JsonArray txMeterValuesJson = payload.createNestedArray("transactionData"); + + for (size_t i = 0; i < txMeterValues.size(); i++) { + auto mv = txMeterValues[i]; + auto mvJson = txMeterValuesJson.createNestedObject(); + + if (mv->getJsonCapacity(MO_OCPP_V16, /*internalFormat*/ false) < 0) { + //measurement has failed - ensure that serialization won't be attempted + continue; + } + + if (!mv->toJson(context.getClock(), MO_OCPP_V16, /*internal format*/ false, mvJson)) { + MO_DBG_ERR("serialization error"); + } + } } return doc; @@ -117,44 +88,23 @@ std::unique_ptr StopTransaction::createReq() { void StopTransaction::processConf(JsonObject payload) { - if (transaction) { - transaction->getStopSync().confirm(); - transaction->commit(); - } + transaction->getStopSync().confirm(); MO_DBG_INFO("Request has been accepted!"); #if MO_ENABLE_LOCAL_AUTH - if (auto authService = model.getAuthorizationService()) { + if (auto authService = context.getModel16().getAuthorizationService()) { authService->notifyAuthorization(transaction->getIdTag(), payload["idTagInfo"]); } #endif //MO_ENABLE_LOCAL_AUTH } -bool StopTransaction::processErr(const char *code, const char *description, JsonObject details) { - - if (transaction) { - transaction->getStopSync().confirm(); //no retry behavior for now; consider data "arrived" at server - transaction->commit(); - } - - MO_DBG_ERR("Server error, data loss!"); - - return false; -} - -void StopTransaction::processReq(JsonObject payload) { - /** - * Ignore Contents of this Req-message, because this is for debug purposes only - */ +#if MO_ENABLE_MOCK_SERVER +int StopTransaction::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { + (void)userStatus; + (void)userData; + return snprintf(buf, size, "{\"idTagInfo\":{\"status\":\"Accepted\"}}"); } +#endif //MO_ENABLE_MOCK_SERVER -std::unique_ptr StopTransaction::createConf(){ - auto doc = makeJsonDoc(getMemoryTag(), 2 * JSON_OBJECT_SIZE(1)); - JsonObject payload = doc->to(); - - JsonObject idTagInfo = payload.createNestedObject("idTagInfo"); - idTagInfo["status"] = "Accepted"; - - return doc; -} +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/StopTransaction.h b/src/MicroOcpp/Operations/StopTransaction.h index c573135c..7437dcf8 100644 --- a/src/MicroOcpp/Operations/StopTransaction.h +++ b/src/MicroOcpp/Operations/StopTransaction.h @@ -9,28 +9,25 @@ #include #include #include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { -class Model; +class Context; -class SampledValue; -class MeterValue; +namespace Ocpp16 { class Transaction; -namespace Ocpp16 { - class StopTransaction : public Operation, public MemoryManaged { private: - Model& model; - std::shared_ptr transaction; - Vector> transactionData; + Context& context; + Transaction *transaction = nullptr; public: - StopTransaction(Model& model, std::shared_ptr transaction); - - StopTransaction(Model& model, std::shared_ptr transaction, Vector> transactionData); + StopTransaction(Context& context, Transaction *transaction); const char* getOperationType() override; @@ -38,13 +35,12 @@ class StopTransaction : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; - bool processErr(const char *code, const char *description, JsonObject details) override; - - void processReq(JsonObject payload) override; - - std::unique_ptr createConf() override; +#if MO_ENABLE_MOCK_SERVER + static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); +#endif }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp index 12d91afb..93b878e4 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.cpp +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -2,20 +2,20 @@ // Copyright Matthias Akstaller 2019 - 2024 // MIT License -#include - -#if MO_ENABLE_V201 - #include + +#include #include #include #include +#if MO_ENABLE_V201 + +using namespace MicroOcpp; using namespace MicroOcpp::Ocpp201; -using MicroOcpp::JsonDoc; -TransactionEvent::TransactionEvent(Model& model, TransactionEventData *txEvent) - : MemoryManaged("v201.Operation.", "TransactionEvent"), model(model), txEvent(txEvent) { +TransactionEvent::TransactionEvent(Context& context, TransactionEventData *txEvent) + : MemoryManaged("v201.Operation.", "TransactionEvent"), context(context), txEvent(txEvent) { } @@ -29,36 +29,33 @@ std::unique_ptr TransactionEvent::createReq() { if (txEvent->eventType == TransactionEventData::Type::Ended) { for (size_t i = 0; i < txEvent->transaction->sampledDataTxEnded.size(); i++) { - JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); //just measure, create again for serialization later - txEvent->transaction->sampledDataTxEnded[i]->toJson(meterValueJson); - capacity += meterValueJson.capacity(); + capacity += txEvent->transaction->sampledDataTxEnded[i]->getJsonCapacity(MO_OCPP_V201, /* internalFormat */ false); } } for (size_t i = 0; i < txEvent->meterValue.size(); i++) { - JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); //just measure, create again for serialization later - txEvent->meterValue[i]->toJson(meterValueJson); - capacity += meterValueJson.capacity(); + capacity += txEvent->meterValue[i]->getJsonCapacity(MO_OCPP_V201, /* internalFormat */ false); } capacity += JSON_OBJECT_SIZE(12) + //total of 12 fields - JSONDATE_LENGTH + 1 + //timestamp string - JSON_OBJECT_SIZE(5) + //transactionInfo - MO_TXID_LEN_MAX + 1 + //transactionId - MO_IDTOKEN_LEN_MAX + 1; //idToken + MO_JSONDATE_SIZE + //timestamp string + JSON_OBJECT_SIZE(5); //transactionInfo auto doc = makeJsonDoc(getMemoryTag(), capacity); JsonObject payload = doc->to(); payload["eventType"] = serializeTransactionEventType(txEvent->eventType); - char timestamp [JSONDATE_LENGTH + 1]; - txEvent->timestamp.toJsonString(timestamp, JSONDATE_LENGTH + 1); + char timestamp [MO_JSONDATE_SIZE]; + if (!context.getClock().toJsonString(txEvent->timestamp, timestamp, sizeof(timestamp))) { + MO_DBG_ERR("internal error"); + timestamp[0] = '\0'; + } payload["timestamp"] = timestamp; - if (serializeTransactionEventTriggerReason(txEvent->triggerReason)) { - payload["triggerReason"] = serializeTransactionEventTriggerReason(txEvent->triggerReason); + if (serializeTxEventTriggerReason(txEvent->triggerReason)) { + payload["triggerReason"] = serializeTxEventTriggerReason(txEvent->triggerReason); } else { MO_DBG_ERR("serialization error"); } @@ -82,13 +79,13 @@ std::unique_ptr TransactionEvent::createReq() { } JsonObject transactionInfo = payload.createNestedObject("transactionInfo"); - transactionInfo["transactionId"] = txEvent->transaction->transactionId; + transactionInfo["transactionId"] = (const char*)txEvent->transaction->transactionId; //force zero-copy if (serializeTransactionEventChargingState(txEvent->chargingState)) { // optional transactionInfo["chargingState"] = serializeTransactionEventChargingState(txEvent->chargingState); } - if (txEvent->transaction->stoppedReason != Transaction::StoppedReason::Local && + if (txEvent->transaction->stoppedReason != MO_TxStoppedReason_Local && serializeTransactionStoppedReason(txEvent->transaction->stoppedReason)) { // optional transactionInfo["stoppedReason"] = serializeTransactionStoppedReason(txEvent->transaction->stoppedReason); } @@ -113,16 +110,14 @@ std::unique_ptr TransactionEvent::createReq() { if (txEvent->eventType == TransactionEventData::Type::Ended) { for (size_t i = 0; i < txEvent->transaction->sampledDataTxEnded.size(); i++) { - JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); - txEvent->transaction->sampledDataTxEnded[i]->toJson(meterValueJson); - payload["meterValue"].add(meterValueJson); + JsonObject meterValueJson = payload["meterValue"].createNestedObject(); + txEvent->transaction->sampledDataTxEnded[i]->toJson(context.getClock(), MO_OCPP_V201, /*internal format*/ false, meterValueJson); } } for (size_t i = 0; i < txEvent->meterValue.size(); i++) { - JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); - txEvent->meterValue[i]->toJson(meterValueJson); - payload["meterValue"].add(meterValueJson); + JsonObject meterValueJson = payload["meterValue"].createNestedObject(); + txEvent->meterValue[i]->toJson(context.getClock(), MO_OCPP_V201, /*internal format*/ false, meterValueJson); } return doc; @@ -139,14 +134,28 @@ void TransactionEvent::processConf(JsonObject payload) { } } -void TransactionEvent::processReq(JsonObject payload) { - /** - * Ignore Contents of this Req-message, because this is for debug purposes only - */ +#if MO_ENABLE_MOCK_SERVER +void TransactionEvent::onRequestMock(const char *operationType, const char *payloadJson, int *userStatus, void *userData) { + //if request contains `"idToken"`, then send response with `"idTokenInfo"`. Instead of building the + //full JSON DOM, just search for the substring + if (strstr(payloadJson, "\"idToken\"")) { + //found, pass status to `writeMockConf` + *userStatus = 1; + } else { + //not found, pass status to `writeMockConf` + *userStatus = 0; + } } -std::unique_ptr TransactionEvent::createConf() { - return createEmptyDocument(); +int TransactionEvent::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { + (void)userData; + + if (userStatus == 1) { + return snprintf(buf, size, "{\"idTokenInfo\":{\"status\":\"Accepted\"}}"); + } else { + return snprintf(buf, size, "{}"); + } } +#endif //MO_ENABLE_MOCK_SERVER -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/TransactionEvent.h b/src/MicroOcpp/Operations/TransactionEvent.h index af8bbf86..8de32c26 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.h +++ b/src/MicroOcpp/Operations/TransactionEvent.h @@ -5,15 +5,14 @@ #ifndef MO_TRANSACTIONEVENT_H #define MO_TRANSACTIONEVENT_H +#include #include #if MO_ENABLE_V201 -#include - namespace MicroOcpp { -class Model; +class Context; namespace Ocpp201 { @@ -21,13 +20,13 @@ class TransactionEventData; class TransactionEvent : public Operation, public MemoryManaged { private: - Model& model; - TransactionEventData *txEvent; + Context& context; + TransactionEventData *txEvent; //does not take ownership const char *errorCode = nullptr; public: - TransactionEvent(Model& model, TransactionEventData *txEvent); + TransactionEvent(Context& context, TransactionEventData *txEvent); const char* getOperationType() override; @@ -37,12 +36,13 @@ class TransactionEvent : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} - void processReq(JsonObject payload) override; - - std::unique_ptr createConf() override; +#if MO_ENABLE_MOCK_SERVER + static void onRequestMock(const char *operationType, const char *payloadJson, int *userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); +#endif }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp -#endif // MO_ENABLE_V201 +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/TriggerMessage.cpp b/src/MicroOcpp/Operations/TriggerMessage.cpp index 9b930697..2321cedd 100644 --- a/src/MicroOcpp/Operations/TriggerMessage.cpp +++ b/src/MicroOcpp/Operations/TriggerMessage.cpp @@ -3,17 +3,20 @@ // MIT License #include -#include -#include + +#include #include -#include #include +#include +#include +#include #include -using MicroOcpp::Ocpp16::TriggerMessage; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 || MO_ENABLE_V201 -TriggerMessage::TriggerMessage(Context& context) : MemoryManaged("v16.Operation.", "TriggerMessage"), context(context) { +using namespace MicroOcpp; + +TriggerMessage::TriggerMessage(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "TriggerMessage"), context(context), rcService(rcService) { } @@ -23,63 +26,66 @@ const char* TriggerMessage::getOperationType(){ void TriggerMessage::processReq(JsonObject payload) { - const char *requestedMessage = payload["requestedMessage"] | "Invalid"; - const int connectorId = payload["connectorId"] | -1; - - MO_DBG_INFO("Execute for message type %s, connectorId = %i", requestedMessage, connectorId); - - statusMessage = "Rejected"; - - if (!strcmp(requestedMessage, "MeterValues")) { - if (auto mService = context.getModel().getMeteringService()) { - if (connectorId < 0) { - auto nConnectors = mService->getNumConnectors(); - for (decltype(nConnectors) cId = 0; cId < nConnectors; cId++) { - if (auto meterValues = mService->takeTriggeredMeterValues(cId)) { - context.getRequestQueue().sendRequestPreBoot(std::move(meterValues)); - statusMessage = "Accepted"; - } - } - } else if (connectorId < mService->getNumConnectors()) { - if (auto meterValues = mService->takeTriggeredMeterValues(connectorId)) { - context.getRequestQueue().sendRequestPreBoot(std::move(meterValues)); - statusMessage = "Accepted"; - } - } else { - errorCode = "PropertyConstraintViolation"; - } - } - } else if (!strcmp(requestedMessage, "StatusNotification")) { - unsigned int cIdRangeBegin = 0, cIdRangeEnd = 0; - if (connectorId < 0) { - cIdRangeEnd = context.getModel().getNumConnectors(); - } else if ((unsigned int) connectorId < context.getModel().getNumConnectors()) { - cIdRangeBegin = connectorId; - cIdRangeEnd = connectorId + 1; - } else { + if (!payload.containsKey("requestedMessage") || !payload["requestedMessage"].is()) { + errorCode = "FormationViolation"; + return; + } + + const char *requestedMessage = payload["requestedMessage"]; + int evseId = -1; + + unsigned int numEvseId = MO_NUM_EVSEID; + + #if MO_ENABLE_V16 + if (context.getOcppVersion() == MO_OCPP_V16) { + numEvseId = context.getModel16().getNumEvseId(); + evseId = payload["connectorId"] | -1; + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (context.getOcppVersion() == MO_OCPP_V201) { + numEvseId = context.getModel201().getNumEvseId(); + evseId = payload["evse"]["id"] | -1; + + if ((payload["evse"]["connectorId"] | 1) != 1) { errorCode = "PropertyConstraintViolation"; + return; } + } + #endif //MO_ENABLE_V201 + + if (evseId >= numEvseId) { + errorCode = "PropertyConstraintViolation"; + return; + } - for (auto i = cIdRangeBegin; i < cIdRangeEnd; i++) { - auto connector = context.getModel().getConnector(i); - if (connector->triggerStatusNotification()) { - statusMessage = "Accepted"; - } - } - } else { - auto msg = context.getOperationRegistry().deserializeOperation(requestedMessage); - if (msg) { - context.getRequestQueue().sendRequestPreBoot(std::move(msg)); - statusMessage = "Accepted"; - } else { - statusMessage = "NotImplemented"; - } + MO_DBG_INFO("Execute for message type %s, evseId = %i", requestedMessage, evseId); + + status = rcService.triggerMessage(requestedMessage, evseId); + if (status == TriggerMessageStatus::ERR_INTERNAL) { + MO_DBG_ERR("triggerMessage() failed"); + errorCode = "InternalError"; + return; } } std::unique_ptr TriggerMessage::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - payload["status"] = statusMessage; + const char *statusStr = "Rejected"; + switch (status) { + case TriggerMessageStatus::Accepted: + statusStr = "Accepted"; + break; + case TriggerMessageStatus::Rejected: + statusStr = "Rejected"; + break; + case TriggerMessageStatus::NotImplemented: + statusStr = "NotImplemented"; + break; + } + payload["status"] = statusStr; return doc; } + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/TriggerMessage.h b/src/MicroOcpp/Operations/TriggerMessage.h index 7869e5a5..396c565d 100644 --- a/src/MicroOcpp/Operations/TriggerMessage.h +++ b/src/MicroOcpp/Operations/TriggerMessage.h @@ -6,22 +6,26 @@ #define MO_TRIGGERMESSAGE_H #include +#include #include +#include + +#if MO_ENABLE_V16 || MO_ENABLE_V201 namespace MicroOcpp { class Context; - -namespace Ocpp16 { +class RemoteControlService; class TriggerMessage : public Operation, public MemoryManaged { private: Context& context; - const char *statusMessage = nullptr; + RemoteControlService& rcService; + TriggerMessageStatus status = TriggerMessageStatus::Rejected; const char *errorCode = nullptr; public: - TriggerMessage(Context& context); + TriggerMessage(Context& context, RemoteControlService& rcService); const char* getOperationType() override; @@ -32,6 +36,6 @@ class TriggerMessage : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/UnlockConnector.cpp b/src/MicroOcpp/Operations/UnlockConnector.cpp index a64b7bd4..fc17a219 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.cpp +++ b/src/MicroOcpp/Operations/UnlockConnector.cpp @@ -3,100 +3,99 @@ // MIT License #include +#include #include +#include #include -using MicroOcpp::Ocpp16::UnlockConnector; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 + +using namespace MicroOcpp; -UnlockConnector::UnlockConnector(Model& model) : MemoryManaged("v16.Operation.", "UnlockConnector"), model(model) { +Ocpp16::UnlockConnector::UnlockConnector(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "UnlockConnector"), context(context), rcService(rcService) { } -const char* UnlockConnector::getOperationType(){ +const char* Ocpp16::UnlockConnector::getOperationType(){ return "UnlockConnector"; } -void UnlockConnector::processReq(JsonObject payload) { +void Ocpp16::UnlockConnector::processReq(JsonObject payload) { #if MO_ENABLE_CONNECTOR_LOCK { auto connectorId = payload["connectorId"] | -1; - auto connector = model.getConnector(connectorId); - - if (!connector) { + rcEvse = rcService.getEvse(connectorId); + if (!rcEvse) { // NotSupported + status = UnlockStatus::NotSupported; return; } - unlockConnector = connector->getOnUnlockConnector(); - - if (!unlockConnector) { - // NotSupported - return; - } - - connector->endTransaction(nullptr, "UnlockCommand"); - connector->updateTxNotification(TxNotification_RemoteStop); - - cbUnlockResult = unlockConnector(); - - timerStart = mocpp_tick_ms(); + status = rcEvse->unlockConnector16(); + timerStart = context.getClock().getUptime(); } #endif //MO_ENABLE_CONNECTOR_LOCK } -std::unique_ptr UnlockConnector::createConf() { - - const char *status = "NotSupported"; +std::unique_ptr Ocpp16::UnlockConnector::createConf() { #if MO_ENABLE_CONNECTOR_LOCK - if (unlockConnector) { - - if (mocpp_tick_ms() - timerStart < MO_UNLOCK_TIMEOUT) { - //do poll and if more time is needed, delay creation of conf msg - - if (cbUnlockResult == UnlockConnectorResult_Pending) { - cbUnlockResult = unlockConnector(); - if (cbUnlockResult == UnlockConnectorResult_Pending) { - return nullptr; //no result yet - delay confirmation response - } + { + int32_t dtTimerStart; + context.getClock().delta(context.getClock().getUptime(), timerStart, dtTimerStart); + + if (rcEvse && status == UnlockStatus::PENDING && dtTimerStart < MO_UNLOCK_TIMEOUT) { + status = rcEvse->unlockConnector16(); + + if (status == UnlockStatus::PENDING) { + return nullptr; //no result yet - delay confirmation response } } - - if (cbUnlockResult == UnlockConnectorResult_Unlocked) { - status = "Unlocked"; - } else { - status = "UnlockFailed"; - } } +#else + status = UnlockStatus::NotSupported; #endif //MO_ENABLE_CONNECTOR_LOCK + const char *statusStr = ""; + switch (status) { + case UnlockStatus::Unlocked: + statusStr = "Unlocked"; + break; + case UnlockStatus::UnlockFailed: + statusStr = "UnlockFailed"; + break; + case UnlockStatus::NotSupported: + statusStr = "NotSupported"; + break; + case UnlockStatus::PENDING: + MO_DBG_ERR("UnlockConnector timeout"); + statusStr = "UnlockFailed"; + break; + } + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - payload["status"] = status; + payload["status"] = statusStr; return doc; } +#endif //MO_ENABLE_V16 -#if MO_ENABLE_V201 -#if MO_ENABLE_CONNECTOR_LOCK - -#include +#if MO_ENABLE_V201 && MO_ENABLE_CONNECTOR_LOCK -namespace MicroOcpp { -namespace Ocpp201 { +using namespace MicroOcpp; -UnlockConnector::UnlockConnector(RemoteControlService& rcService) : MemoryManaged("v201.Operation.UnlockConnector"), rcService(rcService) { +Ocpp201::UnlockConnector::UnlockConnector(Context& context, RemoteControlService& rcService) : MemoryManaged("v201.Operation.UnlockConnector"), context(context), rcService(rcService) { } -const char* UnlockConnector::getOperationType(){ +const char* Ocpp201::UnlockConnector::getOperationType(){ return "UnlockConnector"; } -void UnlockConnector::processReq(JsonObject payload) { +void Ocpp201::UnlockConnector::processReq(JsonObject payload) { int evseId = payload["evseId"] | -1; int connectorId = payload["connectorId"] | -1; @@ -107,45 +106,48 @@ void UnlockConnector::processReq(JsonObject payload) { } if (connectorId != 1) { - status = UnlockStatus_UnknownConnector; + status = UnlockStatus::UnknownConnector; return; } rcEvse = rcService.getEvse(evseId); if (!rcEvse) { - status = UnlockStatus_UnlockFailed; + status = UnlockStatus::UnlockFailed; return; } - status = rcEvse->unlockConnector(); - timerStart = mocpp_tick_ms(); + status = rcEvse->unlockConnector201(); + timerStart = context.getClock().getUptime(); } -std::unique_ptr UnlockConnector::createConf() { +std::unique_ptr Ocpp201::UnlockConnector::createConf() { + + int32_t dtTimerStart; + context.getClock().delta(context.getClock().getUptime(), timerStart, dtTimerStart); - if (rcEvse && status == UnlockStatus_PENDING && mocpp_tick_ms() - timerStart < MO_UNLOCK_TIMEOUT) { - status = rcEvse->unlockConnector(); + if (rcEvse && status == UnlockStatus::PENDING && dtTimerStart < MO_UNLOCK_TIMEOUT) { + status = rcEvse->unlockConnector201(); - if (status == UnlockStatus_PENDING) { + if (status == UnlockStatus::PENDING) { return nullptr; //no result yet - delay confirmation response } } const char *statusStr = ""; switch (status) { - case UnlockStatus_Unlocked: + case UnlockStatus::Unlocked: statusStr = "Unlocked"; break; - case UnlockStatus_UnlockFailed: + case UnlockStatus::UnlockFailed: statusStr = "UnlockFailed"; break; - case UnlockStatus_OngoingAuthorizedTransaction: + case UnlockStatus::OngoingAuthorizedTransaction: statusStr = "OngoingAuthorizedTransaction"; break; - case UnlockStatus_UnknownConnector: + case UnlockStatus::UnknownConnector: statusStr = "UnknownConnector"; break; - case UnlockStatus_PENDING: + case UnlockStatus::PENDING: MO_DBG_ERR("UnlockConnector timeout"); statusStr = "UnlockFailed"; break; @@ -157,8 +159,4 @@ std::unique_ptr UnlockConnector::createConf() { return doc; } -} // namespace Ocpp201 -} // namespace MicroOcpp - -#endif //MO_ENABLE_CONNECTOR_LOCK -#endif //MO_ENABLE_V201 +#endif //MO_ENABLE_V201 && MO_ENABLE_CONNECTOR_LOCK diff --git a/src/MicroOcpp/Operations/UnlockConnector.h b/src/MicroOcpp/Operations/UnlockConnector.h index 518653cd..c8a0a2de 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.h +++ b/src/MicroOcpp/Operations/UnlockConnector.h @@ -5,31 +5,35 @@ #ifndef UNLOCKCONNECTOR_H #define UNLOCKCONNECTOR_H -#include +#include #include -#include -#include +#include +#include +#include + +#if MO_ENABLE_V16 namespace MicroOcpp { -class Model; +class Context; +class RemoteControlService; +class RemoteControlServiceEvse; namespace Ocpp16 { class UnlockConnector : public Operation, public MemoryManaged { private: - Model& model; + Context& context; + RemoteControlService& rcService; + RemoteControlServiceEvse *rcEvse = nullptr; -#if MO_ENABLE_CONNECTOR_LOCK - std::function unlockConnector; - UnlockConnectorResult cbUnlockResult; - unsigned long timerStart = 0; //for timeout -#endif //MO_ENABLE_CONNECTOR_LOCK + UnlockStatus status; + Timestamp timerStart; //for timeout const char *errorCode = nullptr; public: - UnlockConnector(Model& model); + UnlockConnector(Context& context, RemoteControlService& rcService); const char* getOperationType() override; @@ -40,16 +44,17 @@ class UnlockConnector : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 -#if MO_ENABLE_V201 -#if MO_ENABLE_CONNECTOR_LOCK +#if MO_ENABLE_V201 && MO_ENABLE_CONNECTOR_LOCK #include namespace MicroOcpp { +class Context; class RemoteControlService; class RemoteControlServiceEvse; @@ -57,15 +62,16 @@ namespace Ocpp201 { class UnlockConnector : public Operation, public MemoryManaged { private: + Context& context; RemoteControlService& rcService; RemoteControlServiceEvse *rcEvse = nullptr; UnlockStatus status; - unsigned long timerStart = 0; //for timeout + Timestamp timerStart; //for timeout const char *errorCode = nullptr; public: - UnlockConnector(RemoteControlService& rcService); + UnlockConnector(Context& context, RemoteControlService& rcService); const char* getOperationType() override; @@ -76,10 +82,7 @@ class UnlockConnector : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp201 -} //end namespace MicroOcpp - -#endif //MO_ENABLE_CONNECTOR_LOCK -#endif //MO_ENABLE_V201 - +} //namespace Ocpp201 +} //namespace MicroOcpp +#endif //MO_ENABLE_V201 && MO_ENABLE_CONNECTOR_LOCK #endif diff --git a/src/MicroOcpp/Operations/UpdateFirmware.cpp b/src/MicroOcpp/Operations/UpdateFirmware.cpp index d9be551f..24ac44c2 100644 --- a/src/MicroOcpp/Operations/UpdateFirmware.cpp +++ b/src/MicroOcpp/Operations/UpdateFirmware.cpp @@ -5,12 +5,16 @@ #include #include #include +#include +#include #include -using MicroOcpp::Ocpp16::UpdateFirmware; -using MicroOcpp::JsonDoc; +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT -UpdateFirmware::UpdateFirmware(FirmwareService& fwService) : MemoryManaged("v16.Operation.", "UpdateFirmware"), fwService(fwService) { +using namespace MicroOcpp; +using namespace MicroOcpp::Ocpp16; + +UpdateFirmware::UpdateFirmware(Context& context, FirmwareService& fwService) : MemoryManaged("v16.Operation.", "UpdateFirmware"), context(context), fwService(fwService) { } @@ -22,7 +26,7 @@ void UpdateFirmware::processReq(JsonObject payload) { errorCode = "FormationViolation"; return; } - + int retries = payload["retries"] | 1; int retryInterval = payload["retryInterval"] | 180; if (retries < 0 || retryInterval < 0) { @@ -37,15 +41,17 @@ void UpdateFirmware::processReq(JsonObject payload) { } Timestamp retrieveDate; - if (!retrieveDate.setTime(payload["retrieveDate"] | "Invalid")) { - errorCode = "PropertyConstraintViolation"; + if (!context.getClock().parseString(payload["retrieveDate"] | "_Unvalid", retrieveDate)) { + errorCode = "FormationViolation"; MO_DBG_WARN("bad time format"); return; } - + fwService.scheduleFirmwareUpdate(location, retrieveDate, (unsigned int) retries, (unsigned int) retryInterval); } std::unique_ptr UpdateFirmware::createConf(){ return createEmptyDocument(); } + +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT diff --git a/src/MicroOcpp/Operations/UpdateFirmware.h b/src/MicroOcpp/Operations/UpdateFirmware.h index da962141..120c4d5e 100644 --- a/src/MicroOcpp/Operations/UpdateFirmware.h +++ b/src/MicroOcpp/Operations/UpdateFirmware.h @@ -7,31 +7,37 @@ #include #include +#include + +#if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT namespace MicroOcpp { -class FirmwareService; +class Context; namespace Ocpp16 { +class FirmwareService; + class UpdateFirmware : public Operation, public MemoryManaged { private: - FirmwareService& fwService; + Context& context; + FirmwareService& fwService; - const char *errorCode = nullptr; + const char *errorCode = nullptr; public: - UpdateFirmware(FirmwareService& fwService); + UpdateFirmware(Context& context, FirmwareService& fwService); - const char* getOperationType() override {return "UpdateFirmware";} + const char* getOperationType() override {return "UpdateFirmware";} - void processReq(JsonObject payload) override; + void processReq(JsonObject payload) override; - std::unique_ptr createConf() override; + std::unique_ptr createConf() override; - const char *getErrorCode() override {return errorCode;} + const char *getErrorCode() override {return errorCode;} }; -} //end namespace Ocpp16 -} //end namespace MicroOcpp - +} //namespace Ocpp16 +} //namespace MicroOcpp +#endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/src/MicroOcpp/Platform.cpp b/src/MicroOcpp/Platform.cpp index 75980725..f4f5b8e5 100644 --- a/src/MicroOcpp/Platform.cpp +++ b/src/MicroOcpp/Platform.cpp @@ -1,112 +1,107 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include -#ifdef MO_CUSTOM_CONSOLE - -char _mo_console_msg_buf [MO_CUSTOM_CONSOLE_MAXMSGSIZE]; +#if MO_PLATFORM == MO_PLATFORM_ARDUINO +#include +#ifndef MO_USE_SERIAL +#define MO_USE_SERIAL Serial +#endif namespace MicroOcpp { -void (*mocpp_console_out_impl)(const char *msg) = nullptr; -} - -void _mo_console_out(const char *msg) { - if (MicroOcpp::mocpp_console_out_impl) { - MicroOcpp::mocpp_console_out_impl(msg); - } -} -void mocpp_set_console_out(void (*console_out)(const char *msg)) { - MicroOcpp::mocpp_console_out_impl = console_out; - if (console_out) { - console_out("[OCPP] console initialized\n"); - } +void defaultDebugCbImpl(const char *msg) { + MO_USE_SERIAL.printf("%s", msg); } -#endif - -#ifdef MO_CUSTOM_TIMER -unsigned long (*mocpp_tick_ms_impl)() = nullptr; - -void mocpp_set_timer(unsigned long (*get_ms)()) { - mocpp_tick_ms_impl = get_ms; +unsigned long defaultTickCbImpl() { + return millis(); } -unsigned long mocpp_tick_ms_custom() { - if (mocpp_tick_ms_impl) { - return mocpp_tick_ms_impl(); - } else { - return 0; - } -} -#else +} //namespace MicroOcpp -#if MO_PLATFORM == MO_PLATFORM_ESPIDF +#elif MO_PLATFORM == MO_PLATFORM_ESPIDF +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" namespace MicroOcpp { + +void defaultDebugCbImpl(const char *msg) { + printf("%s", msg); +} decltype(xTaskGetTickCount()) mocpp_ticks_count = 0; unsigned long mocpp_millis_count = 0; -} - -unsigned long mocpp_tick_ms_espidf() { +unsigned long defaultTickCbImpl() { auto ticks_now = xTaskGetTickCount(); - MicroOcpp::mocpp_millis_count += ((ticks_now - MicroOcpp::mocpp_ticks_count) * 1000UL) / configTICK_RATE_HZ; - MicroOcpp::mocpp_ticks_count = ticks_now; - return MicroOcpp::mocpp_millis_count; + mocpp_millis_count += ((ticks_now - mocpp_ticks_count) * 1000UL) / configTICK_RATE_HZ; + mocpp_ticks_count = ticks_now; + return mocpp_millis_count; } +} //namespace MicroOcpp #elif MO_PLATFORM == MO_PLATFORM_UNIX +#include #include namespace MicroOcpp { + +void defaultDebugCbImpl(const char *msg) { + printf("%s", msg); +} std::chrono::steady_clock::time_point clock_reference; bool clock_initialized = false; -} - -unsigned long mocpp_tick_ms_unix() { - if (!MicroOcpp::clock_initialized) { - MicroOcpp::clock_reference = std::chrono::steady_clock::now(); - MicroOcpp::clock_initialized = true; +unsigned long defaultTickCbImpl() { + if (!clock_initialized) { + clock_reference = std::chrono::steady_clock::now(); + clock_initialized = true; } std::chrono::milliseconds ms = std::chrono::duration_cast( - std::chrono::steady_clock::now() - MicroOcpp::clock_reference); + std::chrono::steady_clock::now() - clock_reference); return (unsigned long) ms.count(); } -#endif + +} //namespace MicroOcpp +#else +namespace MicroOcpp { +void (*defaultDebugCbImpl)(const char*) = nullptr +unsigned long (*defaultTickCbImpl)() = nullptr; +} //namespace MicroOcpp #endif -#ifdef MO_CUSTOM_RNG -uint32_t (*mocpp_rng_impl)() = nullptr; +namespace MicroOcpp { -void mocpp_set_rng(uint32_t (*rng)()) { - mocpp_rng_impl = rng; +void (*getDefaultDebugCb())(const char*) { + return defaultDebugCbImpl; } -uint32_t mocpp_rng_custom(void) { - if (mocpp_rng_impl) { - return mocpp_rng_impl(); - } else { - return 0; - } +unsigned long (*getDefaultTickCb())() { + return defaultTickCbImpl; } -#else // Time-based Pseudo RNG. // Contains internal state which is mixed with the current timestamp // each time it is called. Then this is passed through a multiply-with-carry // PRNG operation to get a pseudo-random number. -uint32_t mocpp_time_based_prng(void) { +uint32_t defaultRngCbImpl(void) { static uint32_t prng_state = 1; - uint32_t entropy = mocpp_tick_ms(); + uint32_t entropy = defaultTickCbImpl(); //ensure that RNG is only executed when defaultTickCbImpl is defined prng_state = (prng_state ^ entropy)*1664525U + 1013904223U; // assuming complement-2 integers and non-signaling overflow return prng_state; } -#endif + +uint32_t (*getDefaultRngCb())() { + if (!defaultTickCbImpl) { + //default RNG depends on default TickCb + return nullptr; + } + return defaultRngCbImpl; +} + +} //namespace MicroOcpp diff --git a/src/MicroOcpp/Platform.h b/src/MicroOcpp/Platform.h index a3da99ea..1a4f23bf 100644 --- a/src/MicroOcpp/Platform.h +++ b/src/MicroOcpp/Platform.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_PLATFORM_H @@ -17,92 +17,14 @@ #endif #ifdef __cplusplus -#define MO_EXTERN_C extern "C" -#else -#define MO_EXTERN_C -#endif - -#if MO_PLATFORM == MO_PLATFORM_NONE -#ifndef MO_CUSTOM_CONSOLE -#define MO_CUSTOM_CONSOLE -#endif -#ifndef MO_CUSTOM_TIMER -#define MO_CUSTOM_TIMER -#endif -#endif - -#ifdef MO_CUSTOM_CONSOLE -#include - -#ifndef MO_CUSTOM_CONSOLE_MAXMSGSIZE -#define MO_CUSTOM_CONSOLE_MAXMSGSIZE 256 -#endif - -extern char _mo_console_msg_buf [MO_CUSTOM_CONSOLE_MAXMSGSIZE]; //define msg_buf in data section to save memory (see https://github.com/matth-x/MicroOcpp/pull/304) -MO_EXTERN_C void _mo_console_out(const char *msg); - -MO_EXTERN_C void mocpp_set_console_out(void (*console_out)(const char *msg)); - -#define MO_CONSOLE_PRINTF(X, ...) \ - do { \ - auto _mo_ret = snprintf(_mo_console_msg_buf, MO_CUSTOM_CONSOLE_MAXMSGSIZE, X, ##__VA_ARGS__); \ - if (_mo_ret < 0 || _mo_ret >= MO_CUSTOM_CONSOLE_MAXMSGSIZE) { \ - sprintf(_mo_console_msg_buf + MO_CUSTOM_CONSOLE_MAXMSGSIZE - 7, " [...]"); \ - } \ - _mo_console_out(_mo_console_msg_buf); \ - } while (0) -#else -#define mocpp_set_console_out(X) \ - do { \ - X("[OCPP] CONSOLE ERROR: mocpp_set_console_out ignored if MO_CUSTOM_CONSOLE " \ - "not defined\n"); \ - char msg [196]; \ - snprintf(msg, 196, " > see %s:%i",__FILE__,__LINE__); \ - X(msg); \ - X("\n > see MicroOcpp/Platform.h\n"); \ - } while (0) +namespace MicroOcpp { -#if MO_PLATFORM == MO_PLATFORM_ARDUINO -#include -#ifndef MO_USE_SERIAL -#define MO_USE_SERIAL Serial -#endif - -#define MO_CONSOLE_PRINTF(X, ...) MO_USE_SERIAL.printf_P(PSTR(X), ##__VA_ARGS__) -#elif MO_PLATFORM == MO_PLATFORM_ESPIDF || MO_PLATFORM == MO_PLATFORM_UNIX -#include +void (*getDefaultDebugCb())(const char*); +unsigned long (*getDefaultTickCb())(); +uint32_t (*getDefaultRngCb())(); -#define MO_CONSOLE_PRINTF(X, ...) printf(X, ##__VA_ARGS__) -#endif -#endif - -#ifdef MO_CUSTOM_TIMER -MO_EXTERN_C void mocpp_set_timer(unsigned long (*get_ms)()); - -MO_EXTERN_C unsigned long mocpp_tick_ms_custom(); -#define mocpp_tick_ms mocpp_tick_ms_custom -#else - -#if MO_PLATFORM == MO_PLATFORM_ARDUINO -#include -#define mocpp_tick_ms millis -#elif MO_PLATFORM == MO_PLATFORM_ESPIDF -MO_EXTERN_C unsigned long mocpp_tick_ms_espidf(); -#define mocpp_tick_ms mocpp_tick_ms_espidf -#elif MO_PLATFORM == MO_PLATFORM_UNIX -MO_EXTERN_C unsigned long mocpp_tick_ms_unix(); -#define mocpp_tick_ms mocpp_tick_ms_unix -#endif -#endif - -#ifdef MO_CUSTOM_RNG -MO_EXTERN_C void mocpp_set_rng(uint32_t (*rng)()); -MO_EXTERN_C uint32_t mocpp_rng_custom(); -#define mocpp_rng mocpp_rng_custom -#else -MO_EXTERN_C uint32_t mocpp_time_based_prng(void); -#define mocpp_rng mocpp_time_based_prng -#endif +} //namespace MicroOcpp +#endif //__cplusplus #ifndef MO_MAX_JSON_CAPACITY #if MO_PLATFORM == MO_PLATFORM_UNIX @@ -113,7 +35,7 @@ MO_EXTERN_C uint32_t mocpp_time_based_prng(void); #endif #ifndef MO_ENABLE_MBEDTLS -#define MO_ENABLE_MBEDTLS 0 +#define MO_ENABLE_MBEDTLS 1 #endif #endif diff --git a/src/MicroOcpp/Version.h b/src/MicroOcpp/Version.h index d767c173..81acca34 100644 --- a/src/MicroOcpp/Version.h +++ b/src/MicroOcpp/Version.h @@ -1,5 +1,5 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #ifndef MO_VERSION_H @@ -8,7 +8,7 @@ /* * Version specification of MicroOcpp library (not related with the OCPP version) */ -#define MO_VERSION "1.2.0" +#define MO_VERSION "2.0.0" /* * OCPP version identifiers @@ -17,18 +17,40 @@ #define MO_OCPP_V201 201 // OCPP 2.0.1 /* - * Enable OCPP 2.0.1 support. If enabled, library can be initialized with v1.6 or v2.0.1. The choice - * of the protocol is done dynamically during initialization + * Enable OCPP 1.6 support. If multiple OCPP versions are enabled, the final choice + * of the protocol can be done dynamically during setup + */ +#ifndef MO_ENABLE_V16 +#define MO_ENABLE_V16 1 +#endif + +/* + * Enable OCPP 2.0.1 support. If multiple OCPP versions are enabled, the final choice + * of the protocol can be done dynamically during setup */ #ifndef MO_ENABLE_V201 #define MO_ENABLE_V201 1 #endif +/* + * Enable internal mock server. When MO uses the Loopback connection, or is connected + * to an echo WS server, then MO can respond to its own requests and mock real OCPP + * communication. This is useful for development and testing. + */ +#ifndef MO_ENABLE_MOCK_SERVER +#define MO_ENABLE_MOCK_SERVER 1 +#endif + // Certificate Management (UCs M03 - M05). Works with OCPP 1.6 and 2.0.1 #ifndef MO_ENABLE_CERT_MGMT #define MO_ENABLE_CERT_MGMT MO_ENABLE_V201 #endif +// Security Event log (UC A04). Works with OCPP 1.6 and 2.0.1 +#ifndef MO_ENABLE_SECURITY_EVENT +#define MO_ENABLE_SECURITY_EVENT MO_ENABLE_V201 +#endif + // Reservations #ifndef MO_ENABLE_RESERVATION #define MO_ENABLE_RESERVATION 1 @@ -47,4 +69,8 @@ #define MO_ENABLE_FIRMWAREMANAGEMENT 1 #endif +#ifndef MO_ENABLE_DIAGNOSTICS +#define MO_ENABLE_DIAGNOSTICS 1 +#endif + #endif diff --git a/src/MicroOcpp_c.cpp b/src/MicroOcpp_c.cpp deleted file mode 100644 index 381084b0..00000000 --- a/src/MicroOcpp_c.cpp +++ /dev/null @@ -1,462 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include "MicroOcpp_c.h" -#include "MicroOcpp.h" - -#include -#include -#include -#include -#include - -#include -#include - -MicroOcpp::Connection *ocppSocket = nullptr; - -void ocpp_initialize(OCPP_Connection *conn, const char *chargePointModel, const char *chargePointVendor, struct OCPP_FilesystemOpt fsopt, bool autoRecover, bool ocpp201) { - ocpp_initialize_full(conn, ocpp201 ? - ChargerCredentials::v201(chargePointModel, chargePointVendor) : - ChargerCredentials(chargePointModel, chargePointVendor), - fsopt, autoRecover, ocpp201); -} - -void ocpp_initialize_full(OCPP_Connection *conn, const char *bootNotificationCredentials, struct OCPP_FilesystemOpt fsopt, bool autoRecover, bool ocpp201) { - if (!conn) { - MO_DBG_ERR("conn is null"); - } - - ocppSocket = reinterpret_cast(conn); - - MicroOcpp::FilesystemOpt adaptFsopt = fsopt; - - mocpp_initialize(*ocppSocket, bootNotificationCredentials, MicroOcpp::makeDefaultFilesystemAdapter(adaptFsopt), autoRecover, - ocpp201 ? - MicroOcpp::ProtocolVersion(2,0,1) : - MicroOcpp::ProtocolVersion(1,6)); -} - -void ocpp_initialize_full2(OCPP_Connection *conn, const char *bootNotificationCredentials, FilesystemAdapterC *filesystem, bool autoRecover, bool ocpp201) { - if (!conn) { - MO_DBG_ERR("conn is null"); - } - - ocppSocket = reinterpret_cast(conn); - - mocpp_initialize(*ocppSocket, bootNotificationCredentials, *reinterpret_cast*>(filesystem), autoRecover, - ocpp201 ? - MicroOcpp::ProtocolVersion(2,0,1) : - MicroOcpp::ProtocolVersion(1,6)); -} - -void ocpp_deinitialize() { - mocpp_deinitialize(); -} - -bool ocpp_is_initialized() { - return getOcppContext() != nullptr; -} - -void ocpp_loop() { - mocpp_loop(); -} - -/* - * Helper functions for transforming callback functions from C-style to C++style - */ - -std::function adaptFn(InputBool fn) { - return fn; -} - -std::function adaptFn(unsigned int connectorId, InputBool_m fn) { - return [fn, connectorId] () {return fn(connectorId);}; -} - -std::function adaptFn(InputString fn) { - return fn; -} - -std::function adaptFn(unsigned int connectorId, InputString_m fn) { - return [fn, connectorId] () {return fn(connectorId);}; -} - -std::function adaptFn(InputFloat fn) { - return fn; -} - -std::function adaptFn(unsigned int connectorId, InputFloat_m fn) { - return [fn, connectorId] () {return fn(connectorId);}; -} - -std::function adaptFn(InputInt fn) { - return fn; -} - -std::function adaptFn(unsigned int connectorId, InputInt_m fn) { - return [fn, connectorId] () {return fn(connectorId);}; -} - -std::function adaptFn(OutputFloat fn) { - return fn; -} - -std::function adaptFn(OutputSmartCharging fn) { - return fn; -} - -std::function adaptFn(unsigned int connectorId, OutputSmartCharging_m fn) { - return [fn, connectorId] (float power, float current, int nphases) {fn(connectorId, power, current, nphases);}; -} - -std::function adaptFn(unsigned int connectorId, OutputFloat_m fn) { - return [fn, connectorId] (float value) {return fn(connectorId, value);}; -} - -std::function adaptFn(void (*fn)(void)) { - return fn; -} - -#ifndef MO_RECEIVE_PAYLOAD_BUFSIZE -#define MO_RECEIVE_PAYLOAD_BUFSIZE 512 -#endif - -char ocpp_recv_payload_buff [MO_RECEIVE_PAYLOAD_BUFSIZE] = {'\0'}; - -std::function adaptFn(OnMessage fn) { - if (!fn) return nullptr; - return [fn] (JsonObject payload) { - auto len = serializeJson(payload, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE); - if (len <= 0) { - MO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); - } - fn(len > 0 ? ocpp_recv_payload_buff : "", len); - }; -} - -MicroOcpp::OnReceiveErrorListener adaptFn(OnCallError fn) { - if (!fn) return nullptr; - return [fn] (const char *code, const char *description, JsonObject details) { - auto len = serializeJson(details, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE); - if (len <= 0) { - MO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); - } - fn(code, description, len > 0 ? ocpp_recv_payload_buff : "", len); - }; -} - -#if MO_ENABLE_CONNECTOR_LOCK -std::function adaptFn(PollUnlockResult fn) { - return [fn] () {return fn();}; -} - -std::function adaptFn(unsigned int connectorId, PollUnlockResult_m fn) { - return [fn, connectorId] () {return fn(connectorId);}; -} -#endif //MO_ENABLE_CONNECTOR_LOCK - -bool ocpp_beginTransaction(const char *idTag) { - return beginTransaction(idTag); -} -bool ocpp_beginTransaction_m(unsigned int connectorId, const char *idTag) { - return beginTransaction(idTag, connectorId); -} - -bool ocpp_beginTransaction_authorized(const char *idTag, const char *parentIdTag) { - return beginTransaction_authorized(idTag, parentIdTag); -} -bool ocpp_beginTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *parentIdTag) { - return beginTransaction_authorized(idTag, parentIdTag, connectorId); -} - -bool ocpp_endTransaction(const char *idTag, const char *reason) { - return endTransaction(idTag, reason); -} -bool ocpp_endTransaction_m(unsigned int connectorId, const char *idTag, const char *reason) { - return endTransaction(idTag, reason, connectorId); -} - -bool ocpp_endTransaction_authorized(const char *idTag, const char *reason) { - return endTransaction_authorized(idTag, reason); -} -bool ocpp_endTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *reason) { - return endTransaction_authorized(idTag, reason, connectorId); -} - -bool ocpp_isTransactionActive() { - return isTransactionActive(); -} -bool ocpp_isTransactionActive_m(unsigned int connectorId) { - return isTransactionActive(connectorId); -} - -bool ocpp_isTransactionRunning() { - return isTransactionRunning(); -} -bool ocpp_isTransactionRunning_m(unsigned int connectorId) { - return isTransactionRunning(connectorId); -} - -const char *ocpp_getTransactionIdTag() { - return getTransactionIdTag(); -} -const char *ocpp_getTransactionIdTag_m(unsigned int connectorId) { - return getTransactionIdTag(connectorId); -} - -OCPP_Transaction *ocpp_getTransaction() { - return ocpp_getTransaction_m(1); -} -OCPP_Transaction *ocpp_getTransaction_m(unsigned int connectorId) { - #if MO_ENABLE_V201 - { - if (!getOcppContext()) { - MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return nullptr; - } - if (getOcppContext()->getModel().getVersion().major == 2) { - ocpp_tx_compat_setV201(true); //set the ocpp_tx C-API into v201 mode globally - if (getTransactionV201(connectorId)) { - return reinterpret_cast(getTransactionV201(connectorId)); - } else { - return nullptr; - } - } else { - ocpp_tx_compat_setV201(false); //set the ocpp_tx C-API into v16 mode globally - //continue with V16 implementation - } - } - #endif //MO_ENABLE_V201 - if (getTransaction(connectorId)) { - return reinterpret_cast(getTransaction(connectorId).get()); - } else { - return nullptr; - } -} - -bool ocpp_ocppPermitsCharge() { - return ocppPermitsCharge(); -} -bool ocpp_ocppPermitsCharge_m(unsigned int connectorId) { - return ocppPermitsCharge(connectorId); -} - -ChargePointStatus ocpp_getChargePointStatus() { - return getChargePointStatus(); -} - -ChargePointStatus ocpp_getChargePointStatus_m(unsigned int connectorId) { - return getChargePointStatus(connectorId); -} - -void ocpp_setConnectorPluggedInput(InputBool pluggedInput) { - setConnectorPluggedInput(adaptFn(pluggedInput)); -} -void ocpp_setConnectorPluggedInput_m(unsigned int connectorId, InputBool_m pluggedInput) { - setConnectorPluggedInput(adaptFn(connectorId, pluggedInput), connectorId); -} - -void ocpp_setEnergyMeterInput(InputInt energyInput) { - setEnergyMeterInput(adaptFn(energyInput)); -} -void ocpp_setEnergyMeterInput_m(unsigned int connectorId, InputInt_m energyInput) { - setEnergyMeterInput(adaptFn(connectorId, energyInput), connectorId); -} - -void ocpp_setPowerMeterInput(InputFloat powerInput) { - setPowerMeterInput(adaptFn(powerInput)); -} -void ocpp_setPowerMeterInput_m(unsigned int connectorId, InputFloat_m powerInput) { - setPowerMeterInput(adaptFn(connectorId, powerInput), connectorId); -} - -void ocpp_setSmartChargingPowerOutput(OutputFloat maxPowerOutput) { - setSmartChargingPowerOutput(adaptFn(maxPowerOutput)); -} -void ocpp_setSmartChargingPowerOutput_m(unsigned int connectorId, OutputFloat_m maxPowerOutput) { - setSmartChargingPowerOutput(adaptFn(connectorId, maxPowerOutput), connectorId); -} -void ocpp_setSmartChargingCurrentOutput(OutputFloat maxCurrentOutput) { - setSmartChargingCurrentOutput(adaptFn(maxCurrentOutput)); -} -void ocpp_setSmartChargingCurrentOutput_m(unsigned int connectorId, OutputFloat_m maxCurrentOutput) { - setSmartChargingCurrentOutput(adaptFn(connectorId, maxCurrentOutput), connectorId); -} -void ocpp_setSmartChargingOutput(OutputSmartCharging chargingLimitOutput) { - setSmartChargingOutput(adaptFn(chargingLimitOutput)); -} -void ocpp_setSmartChargingOutput_m(unsigned int connectorId, OutputSmartCharging_m chargingLimitOutput) { - setSmartChargingOutput(adaptFn(connectorId, chargingLimitOutput), connectorId); -} - -void ocpp_setEvReadyInput(InputBool evReadyInput) { - setEvReadyInput(adaptFn(evReadyInput)); -} -void ocpp_setEvReadyInput_m(unsigned int connectorId, InputBool_m evReadyInput) { - setEvReadyInput(adaptFn(connectorId, evReadyInput), connectorId); -} - -void ocpp_setEvseReadyInput(InputBool evseReadyInput) { - setEvseReadyInput(adaptFn(evseReadyInput)); -} -void ocpp_setEvseReadyInput_m(unsigned int connectorId, InputBool_m evseReadyInput) { - setEvseReadyInput(adaptFn(connectorId, evseReadyInput), connectorId); -} - -void ocpp_addErrorCodeInput(InputString errorCodeInput) { - addErrorCodeInput(adaptFn(errorCodeInput)); -} -void ocpp_addErrorCodeInput_m(unsigned int connectorId, InputString_m errorCodeInput) { - addErrorCodeInput(adaptFn(connectorId, errorCodeInput), connectorId); -} - -void ocpp_addMeterValueInputFloat(InputFloat valueInput, const char *measurand, const char *unit, const char *location, const char *phase) { - addMeterValueInput(adaptFn(valueInput), measurand, unit, location, phase, 1); -} -void ocpp_addMeterValueInputFloat_m(unsigned int connectorId, InputFloat_m valueInput, const char *measurand, const char *unit, const char *location, const char *phase) { - addMeterValueInput(adaptFn(connectorId, valueInput), measurand, unit, location, phase, connectorId); -} - -void ocpp_addMeterValueInputIntTx(int (*valueInput)(ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase) { - MicroOcpp::SampledValueProperties props; - props.setMeasurand(measurand); - props.setUnit(unit); - props.setLocation(location); - props.setPhase(phase); - auto mvs = std::unique_ptr>>( - new MicroOcpp::SampledValueSamplerConcrete>( - props, - [valueInput] (ReadingContext readingContext) {return valueInput(readingContext);} - )); - addMeterValueInput(std::move(mvs)); -} -void ocpp_addMeterValueInputIntTx_m(unsigned int connectorId, int (*valueInput)(unsigned int cId, ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase) { - MicroOcpp::SampledValueProperties props; - props.setMeasurand(measurand); - props.setUnit(unit); - props.setLocation(location); - props.setPhase(phase); - auto mvs = std::unique_ptr>>( - new MicroOcpp::SampledValueSamplerConcrete>( - props, - [valueInput, connectorId] (ReadingContext readingContext) {return valueInput(connectorId, readingContext);} - )); - addMeterValueInput(std::move(mvs), connectorId); -} - -void ocpp_addMeterValueInput(MeterValueInput *meterValueInput) { - ocpp_addMeterValueInput_m(1, meterValueInput); -} -void ocpp_addMeterValueInput_m(unsigned int connectorId, MeterValueInput *meterValueInput) { - auto svs = std::unique_ptr( - reinterpret_cast(meterValueInput)); - - addMeterValueInput(std::move(svs), connectorId); -} - - -#if MO_ENABLE_CONNECTOR_LOCK -void ocpp_setOnUnlockConnectorInOut(PollUnlockResult onUnlockConnectorInOut) { - setOnUnlockConnectorInOut(adaptFn(onUnlockConnectorInOut)); -} -void ocpp_setOnUnlockConnectorInOut_m(unsigned int connectorId, PollUnlockResult_m onUnlockConnectorInOut) { - setOnUnlockConnectorInOut(adaptFn(connectorId, onUnlockConnectorInOut), connectorId); -} -#endif //MO_ENABLE_CONNECTOR_LOCK - -void ocpp_setStartTxReadyInput(InputBool startTxReady) { - setStartTxReadyInput(adaptFn(startTxReady)); -} -void ocpp_setStartTxReadyInput_m(unsigned int connectorId, InputBool_m startTxReady) { - setStartTxReadyInput(adaptFn(connectorId, startTxReady), connectorId); -} - -void ocpp_setStopTxReadyInput(InputBool stopTxReady) { - setStopTxReadyInput(adaptFn(stopTxReady)); -} -void ocpp_setStopTxReadyInput_m(unsigned int connectorId, InputBool_m stopTxReady) { - setStopTxReadyInput(adaptFn(connectorId, stopTxReady), connectorId); -} - -void ocpp_setTxNotificationOutput(void (*notificationOutput)(OCPP_Transaction*, TxNotification)) { - setTxNotificationOutput([notificationOutput] (MicroOcpp::Transaction *tx, TxNotification notification) { - notificationOutput(reinterpret_cast(tx), notification); - }); -} -void ocpp_setTxNotificationOutput_m(unsigned int connectorId, void (*notificationOutput)(unsigned int, OCPP_Transaction*, TxNotification)) { - setTxNotificationOutput([notificationOutput, connectorId] (MicroOcpp::Transaction *tx, TxNotification notification) { - notificationOutput(connectorId, reinterpret_cast(tx), notification); - }, connectorId); -} - -void ocpp_setOccupiedInput(InputBool occupied) { - setOccupiedInput(adaptFn(occupied)); -} -void ocpp_setOccupiedInput_m(unsigned int connectorId, InputBool_m occupied) { - setOccupiedInput(adaptFn(connectorId, occupied), connectorId); -} - -bool ocpp_isOperative() { - return isOperative(); -} -bool ocpp_isOperative_m(unsigned int connectorId) { - return isOperative(connectorId); -} -void ocpp_setOnResetNotify(bool (*onResetNotify)(bool)) { - setOnResetNotify([onResetNotify] (bool isHard) {return onResetNotify(isHard);}); -} - -void ocpp_setOnResetExecute(void (*onResetExecute)(bool)) { - setOnResetExecute([onResetExecute] (bool isHard) {onResetExecute(isHard);}); -} - -#if MO_ENABLE_CERT_MGMT -void ocpp_setCertificateStore(ocpp_cert_store *certs) { - std::unique_ptr certsCwrapper; - if (certs) { - certsCwrapper = MicroOcpp::makeCertificateStoreCwrapper(certs); - } - setCertificateStore(std::move(certsCwrapper)); -} -#endif //MO_ENABLE_CERT_MGMT - -void ocpp_setOnReceiveRequest(const char *operationType, OnMessage onRequest) { - setOnReceiveRequest(operationType, adaptFn(onRequest)); -} - -void ocpp_setOnSendConf(const char *operationType, OnMessage onConfirmation) { - setOnSendConf(operationType, adaptFn(onConfirmation)); -} - -void ocpp_authorize(const char *idTag, AuthorizeConfCallback onConfirmation, AuthorizeAbortCallback onAbort, AuthorizeTimeoutCallback onTimeout, AuthorizeErrorCallback onError, void *user_data) { - - auto idTag_capture = MicroOcpp::makeString("MicroOcpp_c.cpp", idTag); - - authorize(idTag, - onConfirmation ? [onConfirmation, idTag_capture, user_data] (JsonObject payload) { - auto len = serializeJson(payload, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE); - if (len <= 0) {MO_DBG_WARN("Received payload buffer exceeded. Continue without payload");} - onConfirmation(idTag_capture.c_str(), len > 0 ? ocpp_recv_payload_buff : "", len, user_data); - } : OnReceiveConfListener(nullptr), - onAbort ? [onAbort, idTag_capture, user_data] () -> void { - onAbort(idTag_capture.c_str(), user_data); - } : OnAbortListener(nullptr), - onTimeout ? [onTimeout, idTag_capture, user_data] () { - onTimeout(idTag_capture.c_str(), user_data); - } : OnTimeoutListener(nullptr), - onError ? [onError, idTag_capture, user_data] (const char *code, const char *description, JsonObject details) { - auto len = serializeJson(details, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE); - if (len <= 0) {MO_DBG_WARN("Received payload buffer exceeded. Continue without payload");} - onError(idTag_capture.c_str(), code, description, len > 0 ? ocpp_recv_payload_buff : "", len, user_data); - } : OnReceiveErrorListener(nullptr)); -} - -void ocpp_startTransaction(const char *idTag, OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError) { - startTransaction(idTag, adaptFn(onConfirmation), adaptFn(onAbort), adaptFn(onTimeout), adaptFn(onError)); -} - -void ocpp_stopTransaction(OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError) { - stopTransaction(adaptFn(onConfirmation), adaptFn(onAbort), adaptFn(onTimeout), adaptFn(onError)); -} diff --git a/src/MicroOcpp_c.h b/src/MicroOcpp_c.h deleted file mode 100644 index a517ff1d..00000000 --- a/src/MicroOcpp_c.h +++ /dev/null @@ -1,217 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_MICROOCPP_C_H -#define MO_MICROOCPP_C_H - -#include - -#include -#include -#include -#include -#include -#include - -struct OCPP_Connection; -typedef struct OCPP_Connection OCPP_Connection; - -struct MeterValueInput; -typedef struct MeterValueInput MeterValueInput; - -struct FilesystemAdapterC; -typedef struct FilesystemAdapterC FilesystemAdapterC; - -typedef void (*OnMessage) (const char *payload, size_t len); -typedef void (*OnAbort) (); -typedef void (*OnTimeout) (); -typedef void (*OnCallError) (const char *code, const char *description, const char *details_json, size_t details_len); -typedef void (*AuthorizeConfCallback) (const char *idTag, const char *payload, size_t len, void *user_data); -typedef void (*AuthorizeAbortCallback) (const char *idTag, void* user_data); -typedef void (*AuthorizeTimeoutCallback) (const char *idTag, void* user_data); -typedef void (*AuthorizeErrorCallback) (const char *idTag, const char *code, const char *description, const char *details_json, size_t details_len, void* user_data); - -typedef float (*InputFloat)(); -typedef float (*InputFloat_m)(unsigned int connectorId); //multiple connectors version -typedef int (*InputInt)(); -typedef int (*InputInt_m)(unsigned int connectorId); -typedef bool (*InputBool)(); -typedef bool (*InputBool_m)(unsigned int connectorId); -typedef const char* (*InputString)(); -typedef const char* (*InputString_m)(unsigned int connectorId); -typedef void (*OutputFloat)(float limit); -typedef void (*OutputFloat_m)(unsigned int connectorId, float limit); -typedef void (*OutputSmartCharging)(float power, float current, int nphases); -typedef void (*OutputSmartCharging_m)(unsigned int connectorId, float power, float current, int nphases); - -#if MO_ENABLE_CONNECTOR_LOCK -typedef UnlockConnectorResult (*PollUnlockResult)(); -typedef UnlockConnectorResult (*PollUnlockResult_m)(unsigned int connectorId); -#endif //MO_ENABLE_CONNECTOR_LOCK - - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Please refer to MicroOcpp.h for the documentation - */ - -void ocpp_initialize( - OCPP_Connection *conn, //WebSocket adapter for MicroOcpp - const char *chargePointModel, //model name of this charger (e.g. "My Charger") - const char *chargePointVendor, //brand name (e.g. "My Company Ltd.") - struct OCPP_FilesystemOpt fsopt, //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h - bool autoRecover, //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended - bool ocpp201); //true to select OCPP 2.0.1, false for OCPP 1.6 - -//same as above, but more fields for the BootNotification -void ocpp_initialize_full( - OCPP_Connection *conn, //WebSocket adapter for MicroOcpp - const char *bootNotificationCredentials, //e.g. '{"chargePointModel":"Demo Charger","chargePointVendor":"My Company Ltd."}' (refer to OCPP 1.6 Specification - Edition 2 p. 60) - struct OCPP_FilesystemOpt fsopt, //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h - bool autoRecover, //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended - bool ocpp201); //true to select OCPP 2.0.1, false for OCPP 1.6 - -//same as above, but pass FS handle instead of FS options -void ocpp_initialize_full2( - OCPP_Connection *conn, //WebSocket adapter for MicroOcpp - const char *bootNotificationCredentials, //e.g. '{"chargePointModel":"Demo Charger","chargePointVendor":"My Company Ltd."}' (refer to OCPP 1.6 Specification - Edition 2 p. 60) - FilesystemAdapterC *filesystem, //FilesystemAdapter handle initialized by client. MO takes ownership and deletes it during deinitialization - bool autoRecover, //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended - bool ocpp201); //true to select OCPP 2.0.1, false for OCPP 1.6 - -void ocpp_deinitialize(); - -bool ocpp_is_initialized(); - -void ocpp_loop(); - -/* - * Charging session management - */ - -bool ocpp_beginTransaction(const char *idTag); -bool ocpp_beginTransaction_m(unsigned int connectorId, const char *idTag); //multiple connectors version - -bool ocpp_beginTransaction_authorized(const char *idTag, const char *parentIdTag); -bool ocpp_beginTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *parentIdTag); - -bool ocpp_endTransaction(const char *idTag, const char *reason); //idTag, reason can be NULL -bool ocpp_endTransaction_m(unsigned int connectorId, const char *idTag, const char *reason); //idTag, reason can be NULL - -bool ocpp_endTransaction_authorized(const char *idTag, const char *reason); //idTag, reason can be NULL -bool ocpp_endTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *reason); //idTag, reason can be NULL - -bool ocpp_isTransactionActive(); -bool ocpp_isTransactionActive_m(unsigned int connectorId); - -bool ocpp_isTransactionRunning(); -bool ocpp_isTransactionRunning_m(unsigned int connectorId); - -const char *ocpp_getTransactionIdTag(); -const char *ocpp_getTransactionIdTag_m(unsigned int connectorId); - -OCPP_Transaction *ocpp_getTransaction(); -OCPP_Transaction *ocpp_getTransaction_m(unsigned int connectorId); - -bool ocpp_ocppPermitsCharge(); -bool ocpp_ocppPermitsCharge_m(unsigned int connectorId); - -ChargePointStatus ocpp_getChargePointStatus(); -ChargePointStatus ocpp_getChargePointStatus_m(unsigned int connectorId); - -/* - * Define the Inputs and Outputs of this library. - */ - -void ocpp_setConnectorPluggedInput(InputBool pluggedInput); -void ocpp_setConnectorPluggedInput_m(unsigned int connectorId, InputBool_m pluggedInput); - -void ocpp_setEnergyMeterInput(InputInt energyInput); -void ocpp_setEnergyMeterInput_m(unsigned int connectorId, InputInt_m energyInput); - -void ocpp_setPowerMeterInput(InputFloat powerInput); -void ocpp_setPowerMeterInput_m(unsigned int connectorId, InputFloat_m powerInput); - -void ocpp_setSmartChargingPowerOutput(OutputFloat maxPowerOutput); -void ocpp_setSmartChargingPowerOutput_m(unsigned int connectorId, OutputFloat_m maxPowerOutput); -void ocpp_setSmartChargingCurrentOutput(OutputFloat maxCurrentOutput); -void ocpp_setSmartChargingCurrentOutput_m(unsigned int connectorId, OutputFloat_m maxCurrentOutput); -void ocpp_setSmartChargingOutput(OutputSmartCharging chargingLimitOutput); -void ocpp_setSmartChargingOutput_m(unsigned int connectorId, OutputSmartCharging_m chargingLimitOutput); - -/* - * Define the Inputs and Outputs of this library. (Advanced) - */ - -void ocpp_setEvReadyInput(InputBool evReadyInput); -void ocpp_setEvReadyInput_m(unsigned int connectorId, InputBool_m evReadyInput); - -void ocpp_setEvseReadyInput(InputBool evseReadyInput); -void ocpp_setEvseReadyInput_m(unsigned int connectorId, InputBool_m evseReadyInput); - -void ocpp_addErrorCodeInput(InputString errorCodeInput); -void ocpp_addErrorCodeInput_m(unsigned int connectorId, InputString_m errorCodeInput); - -void ocpp_addMeterValueInputFloat(InputFloat valueInput, const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL -void ocpp_addMeterValueInputFloat_m(unsigned int connectorId, InputFloat_m valueInput, const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL - -void ocpp_addMeterValueInputIntTx(int (*valueInput)(ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL -void ocpp_addMeterValueInputIntTx_m(unsigned int connectorId, int (*valueInput)(unsigned int cId, ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL - -void ocpp_addMeterValueInput(MeterValueInput *meterValueInput); //takes ownership of meterValueInput -void ocpp_addMeterValueInput_m(unsigned int connectorId, MeterValueInput *meterValueInput); //takes ownership of meterValueInput - -void ocpp_setOccupiedInput(InputBool occupied); -void ocpp_setOccupiedInput_m(unsigned int connectorId, InputBool_m occupied); - -void ocpp_setStartTxReadyInput(InputBool startTxReady); -void ocpp_setStartTxReadyInput_m(unsigned int connectorId, InputBool_m startTxReady); - -void ocpp_setStopTxReadyInput(InputBool stopTxReady); -void ocpp_setStopTxReadyInput_m(unsigned int connectorId, InputBool_m stopTxReady); - -void ocpp_setTxNotificationOutput(void (*notificationOutput)(OCPP_Transaction*, TxNotification)); -void ocpp_setTxNotificationOutput_m(unsigned int connectorId, void (*notificationOutput)(unsigned int, OCPP_Transaction*, TxNotification)); - -#if MO_ENABLE_CONNECTOR_LOCK -void ocpp_setOnUnlockConnectorInOut(PollUnlockResult onUnlockConnectorInOut); -void ocpp_setOnUnlockConnectorInOut_m(unsigned int connectorId, PollUnlockResult_m onUnlockConnectorInOut); -#endif //MO_ENABLE_CONNECTOR_LOCK - -/* - * Access further information about the internal state of the library - */ - -bool ocpp_isOperative(); -bool ocpp_isOperative_m(unsigned int connectorId); - -void ocpp_setOnResetNotify(bool (*onResetNotify)(bool)); - -void ocpp_setOnResetExecute(void (*onResetExecute)(bool)); - -#if MO_ENABLE_CERT_MGMT -void ocpp_setCertificateStore(ocpp_cert_store *certs); -#endif //MO_ENABLE_CERT_MGMT - -void ocpp_setOnReceiveRequest(const char *operationType, OnMessage onRequest); - -void ocpp_setOnSendConf(const char *operationType, OnMessage onConfirmation); - -/* - * Send OCPP operations - */ -void ocpp_authorize(const char *idTag, AuthorizeConfCallback onConfirmation, AuthorizeAbortCallback onAbort, AuthorizeTimeoutCallback onTimeout, AuthorizeErrorCallback onError, void *user_data); - -void ocpp_startTransaction(const char *idTag, OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError); - -void ocpp_stopTransaction(OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/tests/Boot.cpp b/tests/Boot.cpp index a4ea610f..7e0a79d0 100644 --- a/tests/Boot.cpp +++ b/tests/Boot.cpp @@ -64,7 +64,7 @@ TEST_CASE( "Boot Behavior" ) { bool checkProcessed = false; - getOcppContext()->getOperationRegistry().registerOperation("BootNotification", + getOcppContext()->getMessageService().registerOperation("BootNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("BootNotification", [ &checkProcessed] (JsonObject payload) { @@ -113,7 +113,7 @@ TEST_CASE( "Boot Behavior" ) { mocpp_initialize(loopback, ChargerCredentials()); - getOcppContext()->getOperationRegistry().registerOperation("BootNotification", + getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { return new Ocpp16::CustomOperation("BootNotification", [] (JsonObject payload) { @@ -186,7 +186,7 @@ TEST_CASE( "Boot Behavior" ) { bool executedTriggerMessage = false; - getOcppContext()->getOperationRegistry().registerOperation("TriggeredOperation", + getOcppContext()->getMessageService().registerOperation("TriggeredOperation", [&executedTriggerMessage] () {return new TriggeredOperation(executedTriggerMessage);}); loopback.sendTXT(TRIGGER_MESSAGE, sizeof(TRIGGER_MESSAGE) - 1); @@ -202,7 +202,7 @@ TEST_CASE( "Boot Behavior" ) { MO_DBG_INFO("Now, accept BN and check if all queued messages finally arrive"); - getOcppContext()->getOperationRegistry().registerOperation("BootNotification", + getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { return new Ocpp16::CustomOperation("BootNotification", [] (JsonObject payload) { @@ -254,7 +254,7 @@ TEST_CASE( "Boot Behavior" ) { //start another transaction while BN is pending - getOcppContext()->getOperationRegistry().registerOperation("BootNotification", + getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { return new Ocpp16::CustomOperation("BootNotification", [] (JsonObject payload) { @@ -287,7 +287,7 @@ TEST_CASE( "Boot Behavior" ) { //Now, accept BN and check again - getOcppContext()->getOperationRegistry().registerOperation("BootNotification", + getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { return new Ocpp16::CustomOperation("BootNotification", [] (JsonObject payload) { @@ -435,7 +435,7 @@ TEST_CASE( "Boot Behavior" ) { bool checkProcessed = false; - getOcppContext()->getOperationRegistry().registerOperation("BootNotification", + getOcppContext()->getMessageService().registerOperation("BootNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("BootNotification", [ &checkProcessed] (JsonObject payload) { diff --git a/tests/ChargePointError.cpp b/tests/ChargePointError.cpp index 5f498de6..6c82b294 100644 --- a/tests/ChargePointError.cpp +++ b/tests/ChargePointError.cpp @@ -82,7 +82,7 @@ TEST_CASE( "ChargePointError" ) { bool checkProcessed = false; - getOcppContext()->getOperationRegistry().registerOperation("StatusNotification", + getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("StatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -110,7 +110,7 @@ TEST_CASE( "ChargePointError" ) { #if MO_REPORT_NOERROR checkProcessed = false; - getOcppContext()->getOperationRegistry().registerOperation("StatusNotification", + getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("StatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -208,7 +208,7 @@ TEST_CASE( "ChargePointError" ) { const char *errorInfo = "*"; bool checkErrorInfo = false; - getOcppContext()->getOperationRegistry().registerOperation("StatusNotification", + getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkErrorCode, &checkErrorInfo, &errorInfo, &errorCode] () { return new Ocpp16::CustomOperation("StatusNotification", [&checkErrorCode, &checkErrorInfo, &errorInfo, &errorCode] (JsonObject payload) { diff --git a/tests/ChargingSessions.cpp b/tests/ChargingSessions.cpp index 82090f16..c553d503 100644 --- a/tests/ChargingSessions.cpp +++ b/tests/ChargingSessions.cpp @@ -389,7 +389,7 @@ TEST_CASE( "Charging sessions" ) { int txId_generate = txId_base; int txId_confirm = txId_base; - getOcppContext()->getOperationRegistry().registerOperation("StartTransaction", [&txId_generate] () { + getOcppContext()->getMessageService().registerOperation("StartTransaction", [&txId_generate] () { return new Ocpp16::CustomOperation("StartTransaction", [] (JsonObject payload) {}, //ignore req [&txId_generate] () { @@ -404,7 +404,7 @@ TEST_CASE( "Charging sessions" ) { return doc; });}); - getOcppContext()->getOperationRegistry().registerOperation("StopTransaction", [&txId_generate, &txId_confirm] () { + getOcppContext()->getMessageService().registerOperation("StopTransaction", [&txId_generate, &txId_confirm] () { return new Ocpp16::CustomOperation("StopTransaction", [&txId_generate, &txId_confirm] (JsonObject payload) { //receive req @@ -469,7 +469,7 @@ TEST_CASE( "Charging sessions" ) { int txId_generate = txId_base; int txId_confirm = txId_base; - getOcppContext()->getOperationRegistry().registerOperation("StartTransaction", [&txId_generate] () { + getOcppContext()->getMessageService().registerOperation("StartTransaction", [&txId_generate] () { return new Ocpp16::CustomOperation("StartTransaction", [] (JsonObject payload) {}, //ignore req [&txId_generate] () { @@ -484,7 +484,7 @@ TEST_CASE( "Charging sessions" ) { return doc; });}); - getOcppContext()->getOperationRegistry().registerOperation("StopTransaction", [&txId_generate, &txId_confirm] () { + getOcppContext()->getMessageService().registerOperation("StopTransaction", [&txId_generate, &txId_confirm] () { return new Ocpp16::CustomOperation("StopTransaction", [&txId_generate, &txId_confirm] (JsonObject payload) { //receive req @@ -571,7 +571,7 @@ TEST_CASE( "Charging sessions" ) { // set up checks before getting online and starting Tx #4 bool check_1 = false, check_2 = false, check_3 = false, check_4 = false; - getOcppContext()->getOperationRegistry().registerOperation("StartTransaction", + getOcppContext()->getMessageService().registerOperation("StartTransaction", [&check_1, &check_2, &check_3, &check_4, tx1_idTag, tx2_idTag, tx3_idTag, tx4_idTag] () { return new Ocpp16::CustomOperation("StartTransaction", @@ -828,7 +828,7 @@ TEST_CASE( "Charging sessions" ) { * - final failure to send txMsg after tx terminated */ - getOcppContext()->getOperationRegistry().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId] () { + getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId] () { return new Ocpp16::CustomOperation("StartTransaction", [&checkProcessedStartTx] (JsonObject payload) { //receive req @@ -845,7 +845,7 @@ TEST_CASE( "Charging sessions" ) { return doc; });}); - getOcppContext()->getOperationRegistry().registerOperation("StopTransaction", [&checkProcessedStopTx] () { + getOcppContext()->getMessageService().registerOperation("StopTransaction", [&checkProcessedStopTx] () { return new Ocpp16::CustomOperation("StopTransaction", [&checkProcessedStopTx] (JsonObject payload) { //receive req @@ -957,7 +957,7 @@ TEST_CASE( "Charging sessions" ) { unsigned int attemptNr = 0; - getOcppContext()->getOperationRegistry().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId, &attemptNr] () { + getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId, &attemptNr] () { return new Ocpp16::CustomOperation("StartTransaction", [&attemptNr] (JsonObject payload) { //receive req @@ -1025,7 +1025,7 @@ TEST_CASE( "Charging sessions" ) { getOcppContext()->getModel().getClock().setTime(BASE_TIME); - getOcppContext()->getOperationRegistry().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId, &attemptNr] () { + getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId, &attemptNr] () { return new Ocpp16::CustomOperation("StartTransaction", [&attemptNr] (JsonObject payload) { //receive req @@ -1046,7 +1046,7 @@ TEST_CASE( "Charging sessions" ) { return attemptNr < NUM_ATTEMPTS ? "InternalError" : (const char*)nullptr; });}); - getOcppContext()->getOperationRegistry().registerOperation("StopTransaction", [&checkProcessedStopTx] () { + getOcppContext()->getMessageService().registerOperation("StopTransaction", [&checkProcessedStopTx] () { return new Ocpp16::CustomOperation("StopTransaction", [&checkProcessedStopTx] (JsonObject payload) { //receive req diff --git a/tests/FirmwareManagement.cpp b/tests/FirmwareManagement.cpp index 5f11b8c4..99518028 100644 --- a/tests/FirmwareManagement.cpp +++ b/tests/FirmwareManagement.cpp @@ -50,7 +50,7 @@ TEST_CASE( "FirmwareManagement" ) { bool checkProcessed = false; - getOcppContext()->getOperationRegistry().registerOperation("FirmwareStatusNotification", + getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -90,7 +90,7 @@ TEST_CASE( "FirmwareManagement" ) { int checkProcessed = 0; - getOcppContext()->getOperationRegistry().registerOperation("FirmwareStatusNotification", + getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -159,7 +159,7 @@ TEST_CASE( "FirmwareManagement" ) { int checkProcessed = 0; - getOcppContext()->getOperationRegistry().registerOperation("FirmwareStatusNotification", + getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -226,7 +226,7 @@ TEST_CASE( "FirmwareManagement" ) { int checkProcessed = 0; - getOcppContext()->getOperationRegistry().registerOperation("FirmwareStatusNotification", + getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -315,7 +315,7 @@ TEST_CASE( "FirmwareManagement" ) { int checkProcessed = 0; - getOcppContext()->getOperationRegistry().registerOperation("FirmwareStatusNotification", + getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -398,7 +398,7 @@ TEST_CASE( "FirmwareManagement" ) { int checkProcessed = 0; - getOcppContext()->getOperationRegistry().registerOperation("FirmwareStatusNotification", + getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { @@ -478,7 +478,7 @@ TEST_CASE( "FirmwareManagement" ) { int checkProcessed = 0; - getOcppContext()->getOperationRegistry().registerOperation("FirmwareStatusNotification", + getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { return new Ocpp16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { diff --git a/tests/LocalAuthList.cpp b/tests/LocalAuthList.cpp index 855a980b..004f4fa2 100644 --- a/tests/LocalAuthList.cpp +++ b/tests/LocalAuthList.cpp @@ -402,7 +402,7 @@ TEST_CASE( "LocalAuth" ) { authService->updateLocalList(localAuthList.as(), 1, false); //patch Authorize so it will reject all idTags - getOcppContext()->getOperationRegistry().registerOperation("Authorize", [] () { + getOcppContext()->getMessageService().registerOperation("Authorize", [] () { return new Ocpp16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req [] () { @@ -445,7 +445,7 @@ TEST_CASE( "LocalAuth" ) { //patch Authorize so it will reject all idTags bool checkAuthorize = false; - getOcppContext()->getOperationRegistry().registerOperation("Authorize", [&checkAuthorize] () { + getOcppContext()->getMessageService().registerOperation("Authorize", [&checkAuthorize] () { return new Ocpp16::CustomOperation("Authorize", [&checkAuthorize] (JsonObject) { checkAuthorize = true; @@ -460,7 +460,7 @@ TEST_CASE( "LocalAuth" ) { //patch StartTransaction so it will DeAuthorize all txs bool checkStartTx = false; - getOcppContext()->getOperationRegistry().registerOperation("StartTransaction", [&checkStartTx] () { + getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkStartTx] () { return new Ocpp16::CustomOperation("StartTransaction", [&checkStartTx] (JsonObject) { checkStartTx = true; @@ -477,7 +477,7 @@ TEST_CASE( "LocalAuth" ) { //check resulting StatusNotification message bool checkLocalListConflict = false; - getOcppContext()->getOperationRegistry().registerOperation("StatusNotification", [&checkLocalListConflict] () { + getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkLocalListConflict] () { return new Ocpp16::CustomOperation("StatusNotification", [&checkLocalListConflict] (JsonObject payload) { if (payload["connectorId"] == 0 && diff --git a/tests/Metering.cpp b/tests/Metering.cpp index 712490fe..4fcb77a6 100644 --- a/tests/Metering.cpp +++ b/tests/Metering.cpp @@ -634,7 +634,7 @@ TEST_CASE("Metering") { unsigned int attemptNr = 0; - getOcppContext()->getOperationRegistry().registerOperation("MeterValues", [&attemptNr] () { + getOcppContext()->getMessageService().registerOperation("MeterValues", [&attemptNr] () { return new Ocpp16::CustomOperation("MeterValues", [&attemptNr] (JsonObject payload) { //receive req diff --git a/tests/Reservation.cpp b/tests/Reservation.cpp index 306b804a..c2437a67 100644 --- a/tests/Reservation.cpp +++ b/tests/Reservation.cpp @@ -170,7 +170,7 @@ TEST_CASE( "Reservation" ) { REQUIRE( connector->getStatus() == ChargePointStatus_Reserved ); bool checkProcessed = false; - getOcppContext()->getOperationRegistry().registerOperation("Authorize", + getOcppContext()->getMessageService().registerOperation("Authorize", [parentIdTag, &checkProcessed] () { return new Ocpp16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req payload @@ -255,8 +255,8 @@ TEST_CASE( "Reservation" ) { REQUIRE( connector->getStatus() == ChargePointStatus_Reserved ); Timestamp expired = expiryDate + 1; - char expired_cstr [JSONDATE_LENGTH + 1]; - expired.toJsonString(expired_cstr, JSONDATE_LENGTH + 1); + char expired_cstr [MO_JSONDATE_SIZE]; + expired.toJsonString(expired_cstr, MO_JSONDATE_SIZE); model.getClock().setTime(expired_cstr); REQUIRE( connector->getStatus() == ChargePointStatus_Available ); @@ -321,11 +321,11 @@ TEST_CASE( "Reservation" ) { //create req auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(5) + - JSONDATE_LENGTH + 1); + MO_JSONDATE_SIZE); auto payload = doc->to(); payload["connectorId"] = connectorId; - char expiryDate_cstr [JSONDATE_LENGTH + 1]; - expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1); + char expiryDate_cstr [MO_JSONDATE_SIZE]; + expiryDate.toJsonString(expiryDate_cstr, MO_JSONDATE_SIZE); payload["expiryDate"] = expiryDate_cstr; payload["idTag"] = idTag; payload["parentIdTag"] = parentIdTag; @@ -357,11 +357,11 @@ TEST_CASE( "Reservation" ) { //create req auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(5) + - JSONDATE_LENGTH + 1); + MO_JSONDATE_SIZE); auto payload = doc->to(); payload["connectorId"] = connectorId; - char expiryDate_cstr [JSONDATE_LENGTH + 1]; - expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1); + char expiryDate_cstr [MO_JSONDATE_SIZE]; + expiryDate.toJsonString(expiryDate_cstr, MO_JSONDATE_SIZE); payload["expiryDate"] = expiryDate_cstr; payload["idTag"] = idTag; payload["parentIdTag"] = parentIdTag; @@ -392,11 +392,11 @@ TEST_CASE( "Reservation" ) { //create req auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(5) + - JSONDATE_LENGTH + 1); + MO_JSONDATE_SIZE); auto payload = doc->to(); payload["connectorId"] = connectorId; - char expiryDate_cstr [JSONDATE_LENGTH + 1]; - expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1); + char expiryDate_cstr [MO_JSONDATE_SIZE]; + expiryDate.toJsonString(expiryDate_cstr, MO_JSONDATE_SIZE); payload["expiryDate"] = expiryDate_cstr; payload["idTag"] = idTag; payload["parentIdTag"] = parentIdTag; @@ -429,11 +429,11 @@ TEST_CASE( "Reservation" ) { //create req auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(5) + - JSONDATE_LENGTH + 1); + MO_JSONDATE_SIZE); auto payload = doc->to(); payload["connectorId"] = connectorId; - char expiryDate_cstr [JSONDATE_LENGTH + 1]; - expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1); + char expiryDate_cstr [MO_JSONDATE_SIZE]; + expiryDate.toJsonString(expiryDate_cstr, MO_JSONDATE_SIZE); payload["expiryDate"] = expiryDate_cstr; payload["idTag"] = idTag; payload["parentIdTag"] = parentIdTag; diff --git a/tests/Reset.cpp b/tests/Reset.cpp index 1390b336..186771b3 100644 --- a/tests/Reset.cpp +++ b/tests/Reset.cpp @@ -39,7 +39,7 @@ TEST_CASE( "Reset" ) { mocpp_set_timer(custom_timer_cb); - getOcppContext()->getOperationRegistry().registerOperation("Authorize", [] () { + getOcppContext()->getMessageService().registerOperation("Authorize", [] () { return new Ocpp16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req [] () { @@ -50,7 +50,7 @@ TEST_CASE( "Reset" ) { return doc; });}); - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [] () { return new Ocpp16::CustomOperation("TransactionEvent", [] (JsonObject) {}, //ignore req [] () { @@ -234,7 +234,7 @@ TEST_CASE( "Reset" ) { bool checkProcessedTx = false; - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [&checkProcessedTx] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkProcessedTx] () { return new Ocpp16::CustomOperation("TransactionEvent", [&checkProcessedTx] (JsonObject payload) { //process req @@ -374,4 +374,4 @@ TEST_CASE( "Reset" ) { mocpp_deinitialize(); } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/tests/Security.cpp b/tests/Security.cpp index 43bf2d7a..72483522 100644 --- a/tests/Security.cpp +++ b/tests/Security.cpp @@ -55,4 +55,4 @@ TEST_CASE( "Security" ) { mocpp_deinitialize(); } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/tests/SmartCharging.cpp b/tests/SmartCharging.cpp index 7cf53b32..80961693 100644 --- a/tests/SmartCharging.cpp +++ b/tests/SmartCharging.cpp @@ -489,8 +489,8 @@ TEST_CASE( "SmartCharging" ) { REQUIRE(!strcmp(payload["status"], "Accepted")); REQUIRE(payload["connectorId"] == 1); - char checkScheduleStart [JSONDATE_LENGTH + 1]; - model.getClock().now().toJsonString(checkScheduleStart, JSONDATE_LENGTH + 1); + char checkScheduleStart [MO_JSONDATE_SIZE]; + model.getClock().now().toJsonString(checkScheduleStart, MO_JSONDATE_SIZE); REQUIRE(!strcmp(payload["scheduleStart"], checkScheduleStart)); JsonObject chargingScheduleJson = payload["chargingSchedule"]; diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp index 56d50c97..8b85d972 100644 --- a/tests/Transactions.cpp +++ b/tests/Transactions.cpp @@ -38,7 +38,7 @@ TEST_CASE( "Transactions" ) { mocpp_set_timer(custom_timer_cb); - getOcppContext()->getOperationRegistry().registerOperation("Authorize", [] () { + getOcppContext()->getMessageService().registerOperation("Authorize", [] () { return new Ocpp16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req [] () { @@ -49,7 +49,7 @@ TEST_CASE( "Transactions" ) { return doc; });}); - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [] () { return new Ocpp16::CustomOperation("TransactionEvent", [] (JsonObject) {}, //ignore req [] () { @@ -292,7 +292,7 @@ TEST_CASE( "Transactions" ) { bool checkReceivedStarted = false, checkReceivedEnded = false; - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded] () { return new Ocpp16::CustomOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded] (JsonObject request) { //process req @@ -343,7 +343,7 @@ TEST_CASE( "Transactions" ) { bool checkReceivedStarted = false, checkReceivedEnded = false; size_t checkSeqNosSize = 0; - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, &checkSeqNosSize] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, &checkSeqNosSize] () { return new Ocpp16::CustomOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, &checkSeqNosSize] (JsonObject request) { //process req @@ -416,7 +416,7 @@ TEST_CASE( "Transactions" ) { std::map> txEventRequests; - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [&txEventRequests] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&txEventRequests] () { return new Ocpp16::CustomOperation("TransactionEvent", [&txEventRequests] (JsonObject request) { //process req @@ -521,7 +521,7 @@ TEST_CASE( "Transactions" ) { bool checkProcessed = false; - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [&checkProcessed, txId] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkProcessed, txId] () { return new Ocpp16::CustomOperation("TransactionEvent", [&checkProcessed, txId] (JsonObject request) { //process req @@ -607,7 +607,7 @@ TEST_CASE( "Transactions" ) { bool checkReceivedStarted = false, checkReceivedEnded = false; - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, txId, &checkSeqNosSize] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, txId, &checkSeqNosSize] () { return new Ocpp16::CustomOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, txId, &checkSeqNosSize] (JsonObject request) { //process req @@ -698,7 +698,7 @@ TEST_CASE( "Transactions" ) { getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStartPoint", "")->setString("Authorized"); getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStopPoint", "")->setString("Authorized"); - getOcppContext()->getOperationRegistry().registerOperation("TransactionEvent", [&txEventRequests] () { + getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&txEventRequests] () { return new Ocpp16::CustomOperation("TransactionEvent", [&txEventRequests] (JsonObject request) { //process req @@ -734,4 +734,4 @@ TEST_CASE( "Transactions" ) { mocpp_deinitialize(); } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 diff --git a/tests/Variables.cpp b/tests/Variables.cpp index 158140fc..05f76b87 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -601,7 +601,7 @@ TEST_CASE( "Variable" ) { bool checkProcessedNotification = false; Timestamp checkTimestamp; - getOcppContext()->getOperationRegistry().registerOperation("NotifyReport", + getOcppContext()->getMessageService().registerOperation("NotifyReport", [&checkProcessedNotification, &checkTimestamp] () { return new Ocpp16::CustomOperation("NotifyReport", [ &checkProcessedNotification, &checkTimestamp] (JsonObject payload) { @@ -658,4 +658,4 @@ TEST_CASE( "Variable" ) { mocpp_deinitialize(); } -#endif // MO_ENABLE_V201 +#endif //MO_ENABLE_V201 From c23787b23aa3754536852b41f8ad8cb62d6f8296 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 14 Jun 2025 19:28:07 +0200 Subject: [PATCH 03/50] add missing functions --- src/MicroOcpp.cpp | 124 ++++++++++++---- src/MicroOcpp.h | 41 +++++- src/MicroOcpp/Context.cpp | 136 ++++++++++-------- src/MicroOcpp/Core/FilesystemUtils.h | 4 +- src/MicroOcpp/Core/Time.cpp | 98 +++++++++++-- src/MicroOcpp/Core/Time.h | 2 +- src/MicroOcpp/Model/Authorization/IdToken.cpp | 4 + src/MicroOcpp/Model/Authorization/IdToken.h | 1 + .../Availability/AvailabilityService.cpp | 10 ++ .../Model/Diagnostics/DiagnosticsService.cpp | 6 + .../DiagnosticsStatusNotification.cpp | 2 +- .../DiagnosticsStatusNotification.h | 1 - 12 files changed, 318 insertions(+), 111 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index a4aead8e..d40c6843 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -85,6 +85,32 @@ bool mo_isInitialized() { return g_context != nullptr; } +MO_Context *mo_getApiContext() { + return reinterpret_cast(g_context); +} + +#if MO_USE_FILEAPI != MO_CUSTOM_FS +//Set if MO can use the filesystem and if it needs to mount it +void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt) { + mo_setDefaultFilesystemConfig2(mo_getApiContext(), opt, MO_FILENAME_PREFIX); +} + +void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + MO_FilesystemConfig filesystemConfig; + memset(&filesystemConfig, 0, sizeof(filesystemConfig)); + filesystemConfig.opt = opt; + filesystemConfig.path_prefix = pathPrefix; + + context->setDefaultFilesystemConfig(filesystemConfig); +} +#endif // MO_USE_FILEAPI != MO_CUSTOM_FS + #if MO_WS_USE == MO_WS_ARDUINO //Setup MO with links2004/WebSockets library void mo_setWebsocketUrl(const char *backendUrl, const char *chargeBoxId, const char *authorizationKey, const char *CA_cert) { @@ -126,28 +152,6 @@ void mo_setConnection2(MO_Context *ctx, MicroOcpp::Connection *connection) { } #endif -#if MO_USE_FILEAPI != MO_CUSTOM_FS -//Set if MO can use the filesystem and if it needs to mount it -void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt) { - mo_setDefaultFilesystemConfig2(mo_getApiContext(), opt, MO_FILENAME_PREFIX); -} - -void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix) { - if (!ctx) { - MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; - } - auto context = mo_getContext2(ctx); - - MO_FilesystemConfig filesystemConfig; - memset(&filesystemConfig, 0, sizeof(filesystemConfig)); - filesystemConfig.opt = opt; - filesystemConfig.path_prefix = pathPrefix; - - context->setDefaultFilesystemConfig(filesystemConfig); -} -#endif // MO_USE_FILEAPI != MO_CUSTOM_FS - //Set the OCPP version void mo_setOcppVersion(int ocppVersion) { mo_setOcppVersion2(mo_getApiContext(), ocppVersion); @@ -1842,6 +1846,68 @@ void mo_v201_setOnResetExecute2(MO_Context *ctx, unsigned int evseId, bool (*onR } #endif //MO_ENABLE_V201 +void mo_setDebugCb(void (*debugCb)(const char *msg)) { + if (!g_context) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = g_context; + + context->setDebugCb(debugCb); +} + +void mo_setDebugCb2(MO_Context *ctx, void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + context->setDebugCb2(debugCb2); +} + +void mo_setTicksCb(unsigned long (*ticksCb)()) { + mo_setTicksCb2(mo_getApiContext(), ticksCb); +} + +void mo_setTicksCb2(MO_Context *ctx, unsigned long (*ticksCb)()) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + context->setTicksCb(ticksCb); +} + +void mo_setRngCb(uint32_t (*rngCb)()) { + mo_setRngCb2(mo_getApiContext(), rngCb); +} + +void mo_setRngCb2(MO_Context *ctx, uint32_t (*rngCb)()) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return; + } + auto context = mo_getContext2(ctx); + + context->setRngCb(rngCb); +} + +MO_FilesystemAdapter *mo_getFilesystem() { + return mo_getFilesystem2(mo_getApiContext()); +} + +MO_FilesystemAdapter *mo_getFilesystem2(MO_Context *ctx) { + if (!ctx) { + MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before + return nullptr; + } + auto context = mo_getContext2(ctx); + + return context->getFilesystem(); +} + #if MO_ENABLE_MBEDTLS void mo_setFtpConfig(MO_Context *ctx, MO_FTPConfig ftpConfig) { if (!ctx) { @@ -2522,7 +2588,7 @@ bool mo_setVarConfigInt(MO_Context *ctx, const char *component201, const char *n return false; } config->setInt(value); - success = true; + success = configSvc->commit(); } #endif #if MO_ENABLE_V201 @@ -2542,7 +2608,7 @@ bool mo_setVarConfigInt(MO_Context *ctx, const char *component201, const char *n return false; } variable->setInt(value); - success = true; + success = varSvc->commit(); } #endif @@ -2575,7 +2641,7 @@ bool mo_setVarConfigBool(MO_Context *ctx, const char *component201, const char * return false; } config->setBool(value); - success = true; + success = configSvc->commit(); } #endif #if MO_ENABLE_V201 @@ -2595,7 +2661,7 @@ bool mo_setVarConfigBool(MO_Context *ctx, const char *component201, const char * return false; } variable->setBool(value); - success = true; + success = varSvc->commit(); } #endif @@ -2628,6 +2694,9 @@ bool mo_setVarConfigString(MO_Context *ctx, const char *component201, const char return false; } success = config->setString(value); + if (success) { + success = configSvc->commit(); + } } #endif #if MO_ENABLE_V201 @@ -2647,6 +2716,9 @@ bool mo_setVarConfigString(MO_Context *ctx, const char *component201, const char return false; } success = variable->setString(value); + if (success) { + success = varSvc->commit(); + } } #endif diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index 0f9d6ce5..818e5bba 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -36,6 +36,12 @@ bool mo_isInitialized(); //Returns Context handle to pass around API functions MO_Context *mo_getApiContext(); +#if MO_USE_FILEAPI != MO_CUSTOM_FS +//Set if MO can use the filesystem and if it needs to mount it. See "FilesystemAdapter.h" for all options +void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt); +void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix); +#endif //#if MO_USE_FILEAPI != MO_CUSTOM_FS + #if MO_WS_USE == MO_WS_ARDUINO /* * Setup MO with links2004/WebSockets library. Only available on Arduino, for other platforms set custom @@ -71,12 +77,6 @@ void mo_setConnection(MicroOcpp::Connection *connection); void mo_setConnection2(MO_Context *ctx, MicroOcpp::Connection *connection); #endif -#if MO_USE_FILEAPI != MO_CUSTOM_FS -//Set if MO can use the filesystem and if it needs to mount it. See "FilesystemAdapter.h" for all options -void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt); -void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix); -#endif //#if MO_USE_FILEAPI != MO_CUSTOM_FS - //Set the OCPP version `MO_OCPP_V16` or `MO_OCPP_V201`. Works only if build flags `MO_ENABLE_V16` or //`MO_ENABLE_V201` are set to 1 void mo_setOcppVersion(int ocppVersion); @@ -445,6 +445,35 @@ void mo_v201_setOnResetExecute(void (*onResetExecute)()); //identical to `mo_set void mo_v201_setOnResetExecute2(MO_Context *ctx, unsigned int evseId, bool (*onResetExecute2)(unsigned int, void*), void *userData); #endif //MO_ENABLE_V201 +/* + * Further platform configurations (Advanced) + */ + +//Set custom debug function +//`setDebugCb`: MO calls `debugCb` with full lines of formatted debug messages +//`setDebugCb2`: MO calls `debugCb2` with the raw debug message, the file and line where it originated and debug level +void mo_setDebugCb(void (*debugCb)(const char *msg)); +void mo_setDebugCb2(MO_Context *ctx, void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)); + +//Set custom timer function which returns the number of milliseconds elapsed since boot. Note: the return value +//of this function must roll over from (2^32 - 1) to 0. It cannot roll over from an earlier value, otherwise it +//would break the unsigned integer arithmetics which MO relies on. This is a common pitfall if the platform +//tick frequency is not 1 tick per ms. Then a helper function is needed which counts the ticks up to 2^32 before +//rolling over. +void mo_setTicksCb(unsigned long (*ticksCb)()); +void mo_setTicksCb2(MO_Context *ctx, unsigned long (*ticksCb)()); + +//Set custom random number generator function. By default, MO uses a pseudo-RNG which with the system time as +//the only entropy source. Using a true RNG greatly improves security. +void mo_setRngCb(uint32_t (*rngCb)()); +void mo_setRngCb2(MO_Context *ctx, uint32_t (*rngCb)()); + +//Returns the internal filesystem adapter or NULL if the initialization failed. Need to set configs using +//via `mo_setDefaultFilesystemConfig` before accessing the FS adpater. The FS adapter can be passed to other +//MO utils functions or be used for other purposes than OCPP, as a file abstraction layer +MO_FilesystemAdapter *mo_getFilesystem(); +MO_FilesystemAdapter *mo_getFilesystem2(MO_Context *ctx); + #if MO_ENABLE_MBEDTLS //Set FTP security parameters (e.g. client cert). See "FtpMbedTLS.h" for all options //To use a custom FTP client, subclass `FtpClient` (see "Ftp.h") and pass to C++ `Context` object diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index c42cb0d2..91b3f08f 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -14,6 +14,9 @@ using namespace MicroOcpp; Context::Context() : MemoryManaged("Context") { + + (void)debug.setup(); //initialize with default debug function. Can replace later + #if MO_USE_FILEAPI != MO_CUSTOM_FS memset(&filesystemConfig, 0, sizeof(filesystemConfig)); filesystemConfig.opt = MO_FS_OPT_USE_MOUNT; @@ -63,11 +66,17 @@ void Context::setRngCb(uint32_t (*rngCb)()) { } uint32_t (*Context::getRngCb())() { + if (!rngCb) { + rngCb = getDefaultRngCb(); + } return rngCb; } #if MO_USE_FILEAPI != MO_CUSTOM_FS void Context::setDefaultFilesystemConfig(MO_FilesystemConfig filesystemConfig) { + if (filesystem) { + MO_DBG_ERR("need to set filesystem config before first filesystem usage"); + } this->filesystemConfig = filesystemConfig; } #endif //MO_USE_FILEAPI != MO_CUSTOM_FS @@ -77,18 +86,33 @@ void Context::setFilesystem(MO_FilesystemAdapter *filesystem) { if (this->filesystem && isFilesystemOwner) { mo_freeDefaultFilesystemAdapter(this->filesystem); this->filesystem = nullptr; + isFilesystemOwner = false; } - isFilesystemOwner = false; #endif //MO_USE_FILEAPI != MO_CUSTOM_FS this->filesystem = filesystem; } MO_FilesystemAdapter *Context::getFilesystem() { + #if MO_USE_FILEAPI != MO_CUSTOM_FS + if (!filesystem && filesystemConfig.opt != MO_FS_OPT_DISABLE) { + // init default FS implementaiton + filesystem = mo_makeDefaultFilesystemAdapter(filesystemConfig); + if (!filesystem) { + //nullptr if either FS is disabled or OOM. If OOM, then err msg is already printed + return nullptr; + } + isFilesystemOwner = true; + } + #endif //MO_USE_FILEAPI != MO_CUSTOM_FS + return filesystem; } #if MO_WS_USE != MO_WS_CUSTOM void Context::setDefaultConnectionConfig(MO_ConnectionConfig connectionConfig) { + if (connection) { + MO_DBG_ERR("need to set connection config before first connection usage"); + } this->connectionConfig = connectionConfig; } #endif //MO_WS_USE != MO_WS_CUSTOM @@ -101,8 +125,8 @@ void Context::setConnection(Connection *connection) { if (this->connection && isConnectionOwner) { freeDefaultConnection(this->connection); this->connection = nullptr; + isConnectionOwner = false; } - isConnectionOwner = false; #endif //MO_WS_USE != MO_WS_CUSTOM this->connection = connection; if (this->connection) { @@ -111,19 +135,51 @@ void Context::setConnection(Connection *connection) { } Connection *Context::getConnection() { + #if MO_WS_USE != MO_WS_CUSTOM + if (!connection) { + // init default Connection implementation + connection = static_cast(makeDefaultConnection(connectionConfig)); + if (!connection) { + MO_DBG_ERR("OOM"); + return nullptr; + } + isConnectionOwner = true; + } + #endif //MO_WS_USE != MO_WS_CUSTOM return connection; } +#if MO_ENABLE_MBEDTLS +void Context::setDefaultFtpConfig(MO_FTPConfig ftpConfig) { + if (ftpClient) { + MO_DBG_ERR("need to set FTP config before first FTP usage"); + } + this->ftpConfig = ftpConfig; +} +#endif //MO_ENABLE_MBEDTLS + void Context::setFtpClient(FtpClient *ftpClient) { + #if MO_ENABLE_MBEDTLS if (this->ftpClient && isFtpClientOwner) { delete this->ftpClient; this->ftpClient = nullptr; + isFtpClientOwner = false; } - isFtpClientOwner = false; + #endif //MO_ENABLE_MBEDTLS this->ftpClient = ftpClient; } FtpClient *Context::getFtpClient() { + #if MO_ENABLE_MBEDTLS + if (!ftpClient) { + ftpClient = makeFtpClientMbedTLS(ftpConfig).release(); + if (!ftpClient) { + MO_DBG_ERR("OOM"); + return nullptr; + } + isFtpClientOwner = true; + } + #endif //MO_ENABLE_MBEDTLS return ftpClient; } @@ -131,12 +187,22 @@ void Context::setCertificateStore(CertificateStore *certStore) { if (this->certStore && isCertStoreOwner) { delete this->certStore; this->certStore = nullptr; + isCertStoreOwner = false; } - isCertStoreOwner = false; this->certStore = certStore; } CertificateStore *Context::getCertificateStore() { + #if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS + if (!certStore) { + certStore = makeCertificateStoreMbedTLS(filesystem); + if (!certStore) { + MO_DBG_ERR("OOM"); + return nullptr; + } + isCertStoreOwner = true; + } + #endif //MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS return certStore; } @@ -203,73 +269,25 @@ bool Context::setup() { } } - if (!rngCb) { - rngCb = getDefaultRngCb(); - if (!rngCb) { - MO_DBG_ERR("random number generator cannot be found"); - return false; - } - } - -#if MO_USE_FILEAPI != MO_CUSTOM_FS - if (!filesystem && filesystemConfig.opt != MO_FS_OPT_DISABLE) { - // init default FS implementaiton - filesystem = mo_makeDefaultFilesystemAdapter(filesystemConfig); - if (!filesystem) { - MO_DBG_ERR("OOM"); - return false; - } - isFilesystemOwner = true; + if (!getRngCb()) { + MO_DBG_ERR("random number generator cannot be found"); + return false; } -#endif //MO_USE_FILEAPI != MO_CUSTOM_FS - if (!filesystem) { + if (!getFilesystem()) { MO_DBG_DEBUG("initialize MO without filesystem access"); } -#if MO_WS_USE != MO_WS_CUSTOM - if (!connection) { - // init default Connection implementation - connection = static_cast(makeDefaultConnection(connectionConfig)); - if (!connection) { - MO_DBG_ERR("OOM"); - return false; - } - isConnectionOwner = true; - } -#endif //MO_WS_USE != MO_WS_CUSTOM - - if (!connection) { + if (!getConnection()) { MO_DBG_ERR("must set WebSocket connection before setup. See the examples in this repository"); return false; } -#if MO_ENABLE_MBEDTLS - if (!ftpClient) { - ftpClient = makeFtpClientMbedTLS(ftpConfig).release(); - if (!ftpClient) { - MO_DBG_ERR(""); - return false; - } - isFtpClientOwner = true; - } -#endif //MO_ENABLE_MBEDTLS - - if (!ftpClient) { + if (!getFtpClient()) { MO_DBG_DEBUG("initialize MO without FTP client"); } - if (!certStore) { - #if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS - { - certStore = makeCertificateStoreMbedTLS(filesystem); - if (!certStore) { - return false; - } - isCertStoreOwner = true; - } - #endif //MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS - + if (!getCertificateStore() && MO_ENABLE_CERT_MGMT) { if (MO_ENABLE_CERT_MGMT && !certStore) { MO_DBG_DEBUG("initialize MO without certificate store"); } diff --git a/src/MicroOcpp/Core/FilesystemUtils.h b/src/MicroOcpp/Core/FilesystemUtils.h index acfab39d..61f21240 100644 --- a/src/MicroOcpp/Core/FilesystemUtils.h +++ b/src/MicroOcpp/Core/FilesystemUtils.h @@ -42,7 +42,7 @@ namespace FilesystemUtils { * `size`: number of bytes of `path` */ bool printPath(MO_FilesystemAdapter *filesystem, char *path, size_t size, const char *fname, ...); -enum class LoadStatus { +enum class LoadStatus : uint8_t { Success, //file operation successful FileNotFound, ErrOOM, @@ -51,7 +51,7 @@ enum class LoadStatus { }; LoadStatus loadJson(MO_FilesystemAdapter *filesystem, const char *fname, JsonDoc& doc, const char *memoryTag); -enum class StoreStatus { +enum class StoreStatus : uint8_t { Success, //file operation successful ErrFileWrite, ErrJsonCorruption, diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index cf9f9319..ac655ab4 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -106,14 +106,18 @@ void Clock::loop() { } #if MO_ENABLE_TIMESTAMP_MILLISECONDS - addMs(unixTime, platformDeltaMs); - addMs(uptime, platformDeltaMs); - lastIncrement = platformTimeMs; + { + addMs(unixTime, platformDeltaMs); + addMs(uptime, platformDeltaMs); + lastIncrement = platformTimeMs; + } #else - auto platformDeltaS = platformDeltaMs / 1000; - add(unixTime, platformDeltaS); - add(uptime, platformDeltaS) - lastIncrement += platformDeltaS * 1000; + { + auto platformDeltaS = platformDeltaMs / 1000; + add(unixTime, platformDeltaS); + add(uptime, platformDeltaS) + lastIncrement += platformDeltaS * 1000; + } #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS } @@ -175,8 +179,6 @@ bool Clock::delta(const Timestamp& t2, const Timestamp& t1, int32_t& dt) const { dt = t2.time - t1.time; return true; } else { - - Timestamp t1Unix, t2Unix; if (toUnixTime(t1, t1Unix) && toUnixTime(t2, t2Unix)) { dt = t2Unix.time - t1Unix.time; @@ -215,6 +217,72 @@ bool Clock::add(Timestamp& t, int32_t secs) const { return true; } +#if MO_ENABLE_TIMESTAMP_MILLISECONDS +bool Clock::deltaMs(const Timestamp& t2, const Timestamp& t1, int32_t& dtMs) const { + + if (t1.ms < 0 || t1.ms >= 1000 || t2.ms < 0 || t2.ms >= 1000) { + MO_DBG_ERR("invalid timestamp"); + return false; + } + + auto fullSec2 = t2; + fullSec2.ms = 0; + + auto fullSec1 = t1; + fullSec1.ms = 0; + + int32_t dtS; + if (!delta(fullSec2, fullSec1, dtS)) { + return false; + } + + if ((dtS > 0 && dtS * 1000 + 1000 < dtS) || + dtS < 0 && dtS * 1000 - 1000 > dtS) { + MO_DBG_ERR("integer overflow"); + return false; + } + + dtMs = dtS * 1000 + (int32_t)(t2.ms - t1.ms); + return true; +} + +bool Clock::addMs(Timestamp& t, int32_t ms) const { + + if (t.ms < 0 || t.ms >= 1000) { + MO_DBG_ERR("invalid timestamp"); + return false; + } + + if (ms + t.ms < ms) { + MO_DBG_ERR("integer overflow"); + return false; + } + ms += t.ms; + + int32_t s = 0; + if (ms >= 0) { + //positive, addition + s = ms / 1000; + ms %= 1000; + } else { + //negative, substraction + s = ms / 1000; + ms -= s * 1000; + if (ms != 0) { + s -= 1; + ms += 1000; + } + } + + if (!add(t, s)) { + return false; + } + + t.ms = ms; + return true; +} +#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS + bool Clock::toJsonString(const Timestamp& src, char *dst, size_t size) const { if (!src.isDefined()) { @@ -259,7 +327,7 @@ bool Clock::toJsonString(const Timestamp& src, char *dst, size_t size) const { int ret; #if MO_ENABLE_TIMESTAMP_MILLISECONDS ret = snprintf(dst, size, "%04i-%02i-%02iT%02i:%02i:%02i.%03uZ", - year, month, day, hour, minute, second, (unsigned int)(t.ms > 999 ? 999 : t.ms)); + year, month, day, hour, minute, second, (int)(t.ms >= 0 && t.ms <= 999 ? t.ms : 0)); #else ret = snprintf(dst, size, "%04i-%02i-%02iT%02i:%02i:%02iZ", year, month, day, hour, minute, second); @@ -285,9 +353,9 @@ bool Clock::toInternalString(const Timestamp& t, char *dst, size_t size) const { int ret; #if MO_ENABLE_TIMESTAMP_MILLISECONDS - ret = snprintf(dst, size, "i%ut%li", (unsigned int)t.bootNr, (long int)t.time); + ret = snprintf(dst, size, "i%ut%lim%u", (unsigned int)t.bootNr, (long int)t.time, (int)(t.ms >= 0 && t.ms <= 999 ? t.ms : 0)); #else - ret = snprintf(dst, size, "i%ut%lim%u", (unsigned int)t.bootNr, (long int)t.time, (unsigned int)(dst.ms > 999 ? 999 : dst.ms)); + ret = snprintf(dst, size, "i%ut%li", (unsigned int)t.bootNr, (long int)t.time); #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS if (ret < 0 || (size_t)ret >= size) { @@ -350,7 +418,7 @@ bool Clock::parseString(const char *src, Timestamp& dst) const { } // Optional ms - uint16_t ms = 0; + int16_t ms = 0; if (src[i] == 'm') { i++; for (; isdigit(src[i]); i++) { @@ -423,7 +491,7 @@ bool Clock::parseString(const char *src, Timestamp& dst) const { (src[18] - '0'); //optional fractals - int ms = 0; + int16_t ms = 0; if (src[19] == '.') { if (isdigit(src[20]) || //1 isdigit(src[21]) || //2 @@ -472,7 +540,7 @@ bool Clock::parseString(const char *src, Timestamp& dst) const { dst.time = time; dst.bootNr = unixTime.bootNr; //set bootNr to a defined value #if MO_ENABLE_TIMESTAMP_MILLISECONDS - dst.ms = (uint16_t)ms; + dst.ms = ms; #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS return true; } diff --git a/src/MicroOcpp/Core/Time.h b/src/MicroOcpp/Core/Time.h index 9e4363d4..68c87aee 100644 --- a/src/MicroOcpp/Core/Time.h +++ b/src/MicroOcpp/Core/Time.h @@ -39,7 +39,7 @@ class Timestamp : public MemoryManaged { uint16_t bootNr = 0; //bootNr when timestamp was taken #if MO_ENABLE_TIMESTAMP_MILLISECONDS - uint16_t ms = 0; //fractional ms of timestamp. Compound timestamp = time + ms. Range should be 0...999 + int16_t ms = 0; //fractional ms of timestamp. Compound timestamp = time + ms. Range should be 0...999 #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS public: diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp index 512f2220..d9ba2448 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.cpp +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -65,6 +65,10 @@ const char *IdToken::get() const { return idToken; } +MO_IdTokenType IdToken::getType() const { + return type; +} + const char *IdToken::getTypeCstr() const { const char *res = ""; switch (type) { diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h index 907f5606..0d8d2b94 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.h +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -50,6 +50,7 @@ class IdToken : public MemoryManaged { bool parseCstr(const char *token, const char *typeCstr); const char *get() const; + MO_IdTokenType getType() const; const char *getTypeCstr() const; bool equals(const IdToken& other); diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 01430ca3..c0d762cc 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -552,6 +552,16 @@ bool Ocpp201::AvailabilityServiceEvse::isAvailable() { return true; } +bool Ocpp201::AvailabilityServiceEvse::isFaulted() { + //for (auto i = errorDataInputs.begin(); i != errorDataInputs.end(); ++i) { + for (size_t i = 0; i < faultedInputs.size(); i++) { + if (faultedInputs[i].isFaulted(evseId, faultedInputs[i].userData)) { + return true; + } + } + return false; +} + bool Ocpp201::AvailabilityServiceEvse::addFaultedInput(FaultedInput faultedInput) { size_t capacity = faultedInputs.size() + 1; faultedInputs.reserve(capacity); diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index b46ea22f..6ef58abb 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -545,6 +545,12 @@ Ocpp16::DiagnosticsStatus DiagnosticsService::getUploadStatus16() { return res; } +void DiagnosticsService::setDiagnosticsReader(size_t (*diagnosticsReader)(char *buf, size_t size, void *user_data), void(*onClose)(void *user_data), void *user_data) { + this->diagnosticsReader = diagnosticsReader; + this->diagnosticsOnClose = onClose; + this->diagnosticsUserData = user_data; +} + void DiagnosticsService::setRefreshFilename(bool (*refreshFilename)(MO_LogType type, char filenameOut[MO_GETLOG_FNAME_SIZE], void *user_data), void *user_data) { this->refreshFilename = refreshFilename; this->refreshFilenameUserData = user_data; diff --git a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp index 53626c8f..85898387 100644 --- a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp +++ b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp @@ -19,7 +19,7 @@ DiagnosticsStatusNotification::DiagnosticsStatusNotification(DiagnosticsStatus s std::unique_ptr DiagnosticsStatusNotification::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - payload["status"] = cstrFromStatus(status); + payload["status"] = serializeDiagnosticsStatus(status); return doc; } diff --git a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h index 86249487..1d1e021d 100644 --- a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h +++ b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h @@ -17,7 +17,6 @@ namespace Ocpp16 { class DiagnosticsStatusNotification : public Operation, public MemoryManaged { private: DiagnosticsStatus status = DiagnosticsStatus::Idle; - static const char *cstrFromStatus(DiagnosticsStatus status); public: DiagnosticsStatusNotification(DiagnosticsStatus status); From 42d7bc413cdb21258865500742b0307a18199305 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:00:03 +0200 Subject: [PATCH 04/50] bugfixes --- CMakeLists.txt | 2 +- src/MicroOcpp/Context.cpp | 2 ++ src/MicroOcpp/Core/FilesystemAdapter.cpp | 4 ++-- src/MicroOcpp/Core/FilesystemUtils.cpp | 4 ++-- src/MicroOcpp/Core/Request.cpp | 4 ++-- .../Model/Availability/AvailabilityService.cpp | 13 ++++++++++--- .../Model/Configuration/Configuration.cpp | 16 ++++++++-------- .../Model/Diagnostics/DiagnosticsService.cpp | 2 +- .../Model/Heartbeat/HeartbeatService.cpp | 4 ++-- src/MicroOcpp/Model/Metering/MeterStore.cpp | 2 +- src/MicroOcpp/Model/Metering/MeteringService.cpp | 15 ++++++--------- src/MicroOcpp/Model/Metering/MeteringService.h | 4 ++-- .../Model/Transactions/TransactionService16.cpp | 2 +- .../Model/Transactions/TransactionStore.cpp | 2 +- 14 files changed, 41 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71628754..5fa3e2b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,7 +182,7 @@ target_include_directories(mo_unit_tests PUBLIC target_compile_definitions(mo_unit_tests PUBLIC MO_PLATFORM=MO_PLATFORM_UNIX - MO_NUMCONNECTORS=3 + MO_NUM_EVSEID=3 MO_CUSTOM_TIMER MO_DBG_LEVEL=MO_DL_INFO MO_TRAFFIC_OUT diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index 91b3f08f..c9a3f6f0 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -312,6 +312,8 @@ bool Context::setup() { } #endif + MO_DBG_INFO("MicroOCPP setup complete"); + return true; } diff --git a/src/MicroOcpp/Core/FilesystemAdapter.cpp b/src/MicroOcpp/Core/FilesystemAdapter.cpp index b8b4d37b..85362c93 100644 --- a/src/MicroOcpp/Core/FilesystemAdapter.cpp +++ b/src/MicroOcpp/Core/FilesystemAdapter.cpp @@ -731,7 +731,7 @@ MO_File* open(const char *path, const char *mode) { } bool close(MO_File *file) { - return fclose(reinterpret_cast(file)); + return fclose(reinterpret_cast(file)) == 0; } size_t read(MO_File *file, char *buf, size_t len) { @@ -926,7 +926,7 @@ MO_File* open(const char *path, const char *mode) { } bool close(MO_File *file) { - return fclose(reinterpret_cast(file)); + return fclose(reinterpret_cast(file)) == 0; } size_t read(MO_File *file, char *buf, size_t len) { diff --git a/src/MicroOcpp/Core/FilesystemUtils.cpp b/src/MicroOcpp/Core/FilesystemUtils.cpp index eb3321d7..ecadb117 100644 --- a/src/MicroOcpp/Core/FilesystemUtils.cpp +++ b/src/MicroOcpp/Core/FilesystemUtils.cpp @@ -96,7 +96,7 @@ FilesystemUtils::LoadStatus FilesystemUtils::loadJson(MO_FilesystemAdapter *file return FilesystemUtils::LoadStatus::ErrFileCorruption; } - MO_DBG_DEBUG("Loaded JSON file: %s", fn); + MO_DBG_DEBUG("Loaded JSON file: %s", fname); return FilesystemUtils::LoadStatus::Success;; } @@ -142,7 +142,7 @@ FilesystemUtils::StoreStatus FilesystemUtils::storeJson(MO_FilesystemAdapter *fi return FilesystemUtils::StoreStatus::ErrFileWrite; } - MO_DBG_DEBUG("Wrote JSON file: %s", fn); + MO_DBG_DEBUG("Wrote JSON file: %s", fname); return FilesystemUtils::StoreStatus::Success; } diff --git a/src/MicroOcpp/Core/Request.cpp b/src/MicroOcpp/Core/Request.cpp index 097b92a0..232dab9f 100644 --- a/src/MicroOcpp/Core/Request.cpp +++ b/src/MicroOcpp/Core/Request.cpp @@ -63,7 +63,7 @@ bool Request::setMessageID(const char *id){ messageID = nullptr; if (id) { - size_t size = strlen(id); + size_t size = strlen(id) + 1; messageID = static_cast(MO_MALLOC(getMemoryTag(), size)); if (!messageID) { MO_DBG_ERR("OOM"); @@ -143,7 +143,7 @@ Request::CreateRequestResult Request::createRequest(JsonDoc& requestJson) { bool Request::receiveResponse(JsonArray response){ //check if messageIDs match - if (!messageID || !strcmp(messageID, response[1].as())) { + if (!messageID || strcmp(messageID, response[1].as())) { return false; } diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index c0d762cc..fb4c5d26 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -56,6 +56,13 @@ bool Ocpp16::AvailabilityServiceEvse::setup() { return false; } + auto txSvc = context.getModel16().getTransactionService(); + txServiceEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txServiceEvse) { + MO_DBG_ERR("setup failure"); + return false; + } + return true; } @@ -388,7 +395,7 @@ bool Ocpp16::AvailabilityService::setup() { numEvseId = context.getModel16().getNumEvseId(); for (unsigned int i = 0; i < numEvseId; i++) { - if (!getEvse(i)) { + if (!getEvse(i) || !getEvse(i)->setup()) { MO_DBG_ERR("connector init failure"); return false; } @@ -404,7 +411,7 @@ void Ocpp16::AvailabilityService::loop() { } Ocpp16::AvailabilityServiceEvse *Ocpp16::AvailabilityService::getEvse(unsigned int evseId) { - if (evseId == 0 || evseId >= numEvseId) { + if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; } @@ -623,7 +630,7 @@ void Ocpp201::AvailabilityService::loop() { } Ocpp201::AvailabilityServiceEvse *Ocpp201::AvailabilityService::getEvse(unsigned int evseId) { - if (evseId == 0 || evseId >= numEvseId) { + if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; } diff --git a/src/MicroOcpp/Model/Configuration/Configuration.cpp b/src/MicroOcpp/Model/Configuration/Configuration.cpp index 443f9363..9e6c2161 100644 --- a/src/MicroOcpp/Model/Configuration/Configuration.cpp +++ b/src/MicroOcpp/Model/Configuration/Configuration.cpp @@ -72,9 +72,9 @@ class ConfigurationConcrete : public Configuration { uint16_t writeCount = 0; union { - int valueInt = 0; + int valueInt; bool valueBool; - char *valueString; + char *valueString = nullptr; }; Type type = Type::Int; @@ -116,7 +116,7 @@ class ConfigurationConcrete : public Configuration { void setInt(int val) override { #if MO_CONFIG_TYPECHECK if (type != Type::Int) { - MO_DBG_ERR("type err"); + MO_DBG_ERR("type err: %s is not int", getKey()); return; } #endif //MO_CONFIG_TYPECHECK @@ -126,7 +126,7 @@ class ConfigurationConcrete : public Configuration { void setBool(bool val) override { #if MO_CONFIG_TYPECHECK if (type != Type::Bool) { - MO_DBG_ERR("type err"); + MO_DBG_ERR("type err: %s is not bool", getKey()); return; } #endif //MO_CONFIG_TYPECHECK @@ -136,7 +136,7 @@ class ConfigurationConcrete : public Configuration { bool setString(const char *val) override { #if MO_CONFIG_TYPECHECK if (type != Type::String) { - MO_DBG_ERR("type err"); + MO_DBG_ERR("type err: %s is not string", getKey()); return false; } #endif //MO_CONFIG_TYPECHECK @@ -151,7 +151,7 @@ class ConfigurationConcrete : public Configuration { int getInt() override { #if MO_CONFIG_TYPECHECK if (type != Type::Int) { - MO_DBG_ERR("type err"); + MO_DBG_ERR("type err: %s is not int", getKey()); return 0; } #endif //MO_CONFIG_TYPECHECK @@ -160,7 +160,7 @@ class ConfigurationConcrete : public Configuration { bool getBool() override { #if MO_CONFIG_TYPECHECK if (type != Type::Bool) { - MO_DBG_ERR("type err"); + MO_DBG_ERR("type err: %s is not bool", getKey()); return false; } #endif //MO_CONFIG_TYPECHECK @@ -169,7 +169,7 @@ class ConfigurationConcrete : public Configuration { const char *getString() override { #if MO_CONFIG_TYPECHECK if (type != Type::String) { - MO_DBG_ERR("type err"); + MO_DBG_ERR("type err: %s is not string", getKey()); return ""; } #endif //MO_CONFIG_TYPECHECK diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 6ef58abb..25157bb6 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -60,7 +60,7 @@ bool DiagnosticsService::setup() { filesystem = context.getFilesystem(); ftpClient = context.getFtpClient(); -#if MO_USE_FW_UPDATER == MO_FW_UPDATER_CUSTOM +#if MO_USE_DIAGNOSTICS == MO_DIAGNOSTICS_CUSTOM if (!onUpload || !onUploadStatusInput) { MO_DBG_ERR("need to set onUpload cb and onUploadStatusInput cb"); return false; diff --git a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp index d1eb7e80..e48f0a5b 100644 --- a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp +++ b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp @@ -34,7 +34,7 @@ bool HeartbeatService::setup() { } //if transactions can start before the BootNotification succeeds - heartbeatIntervalIntV16 = configService->declareConfiguration("HeartbeatInterval", false); + heartbeatIntervalIntV16 = configService->declareConfiguration("HeartbeatInterval", 86400); if (!heartbeatIntervalIntV16) { MO_DBG_ERR("initialization error"); return false; @@ -56,7 +56,7 @@ bool HeartbeatService::setup() { } //if transactions can start before the BootNotification succeeds - heartbeatIntervalIntV201 = varService->declareVariable("OCPPCommCtrlr", "HeartbeatInterval", false); + heartbeatIntervalIntV201 = varService->declareVariable("OCPPCommCtrlr", "HeartbeatInterval", 86400); if (!heartbeatIntervalIntV201) { MO_DBG_ERR("initialization error"); return false; diff --git a/src/MicroOcpp/Model/Metering/MeterStore.cpp b/src/MicroOcpp/Model/Metering/MeterStore.cpp index 55bf6eac..d21bf360 100644 --- a/src/MicroOcpp/Model/Metering/MeterStore.cpp +++ b/src/MicroOcpp/Model/Metering/MeterStore.cpp @@ -70,7 +70,7 @@ FilesystemUtils::LoadStatus MeterStore::load(MO_FilesystemAdapter *filesystem, C //success - MO_DBG_DEBUG("Restored tx %u-%u with %zu meter values", evseId, txNr, txData.size(), txMeterValues.size()); + MO_DBG_DEBUG("Restored tx %u-%u with %zu meter values", evseId, txNr, txMeterValues.size()); if (txMeterValues.empty()) { return FilesystemUtils::LoadStatus::FileNotFound; diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index db47e6c7..6bf45f92 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -292,8 +292,7 @@ using namespace MicroOcpp; Ocpp16::MeteringServiceEvse::MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int connectorId) : MemoryManaged("v16.Metering.MeteringServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), mService(mService), connectorId(connectorId), meterData(makeVector(getMemoryTag())), meterInputs(makeVector(getMemoryTag())) { - Timestamp lastSampleTime = context.getClock().getUptime(); - Timestamp lastAlignedTime = context.getClock().now(); + } Ocpp16::MeteringServiceEvse::~MeteringServiceEvse() { @@ -337,6 +336,8 @@ bool Ocpp16::MeteringServiceEvse::setup() { context.getMessageService().addSendQueue(this); + lastAlignedTime = context.getClock().now(); + filesystem = context.getFilesystem(); if (!filesystem) { MO_DBG_DEBUG("volatile mode"); @@ -398,7 +399,7 @@ void Ocpp16::MeteringServiceEvse::loop() { trackTxRunning = (transaction && transaction->isRunning()); if (txBreak) { - lastSampleTime = clock.getUptime(); + lastSampleTime = clock.getUptimeInt(); } if (!transaction && connectorId != 0 && mService.meterValuesInTxOnlyBool->getBool()) { @@ -485,11 +486,7 @@ void Ocpp16::MeteringServiceEvse::loop() { if (mService.meterValueSampleIntervalInt->getInt() >= 1) { //record periodic tx data - auto uptime = clock.getUptime(); - int32_t dt; - clock.delta(uptime, lastSampleTime, dt); - - if (dt >= mService.meterValueSampleIntervalInt->getInt()) { + if (clock.getUptimeInt() - lastSampleTime >= mService.meterValueSampleIntervalInt->getInt()) { if (auto sampledMeterValue = takeMeterValue(MO_ReadingContext_SamplePeriodic, MO_FLAG_MeterValuesSampledData)) { if (meterData.size() >= MO_MVRECORD_SIZE) { auto mv = meterData.begin(); @@ -509,7 +506,7 @@ void Ocpp16::MeteringServiceEvse::loop() { } } - lastSampleTime = uptime; + lastSampleTime = clock.getUptimeInt(); } } } diff --git a/src/MicroOcpp/Model/Metering/MeteringService.h b/src/MicroOcpp/Model/Metering/MeteringService.h index 8a970b91..392b7cac 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.h +++ b/src/MicroOcpp/Model/Metering/MeteringService.h @@ -58,8 +58,8 @@ class MeteringServiceEvse : public MemoryManaged, public RequestQueue { MeterValue *takeMeterValue(MO_ReadingContext readingContext, uint8_t inputSelectFlag); - Timestamp lastSampleTime; - Timestamp lastAlignedTime; + int32_t lastSampleTime = 0; //in secs since boot + Timestamp lastAlignedTime; //as unix time stamp bool trackTxRunning = false; bool addTxMeterData(Transaction& transaction, MeterValue *mv); diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index 402646fb..b8644212 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -925,7 +925,7 @@ bool TransactionServiceEvse::endTransaction(const char *idTag, const char *reaso //check authorization status if (localAuth && localAuth->getAuthorizationStatus() != AuthorizationStatus::Accepted) { - MO_DBG_DEBUG("local auth denied (%s)", idTag); + MO_DBG_DEBUG("local auth denied (%s)", idTag_capture.c_str()); localAuth = nullptr; } diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index 5322426f..4d89c194 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -62,7 +62,7 @@ FilesystemUtils::LoadStatus Ocpp16::TransactionStore::load(MO_FilesystemAdapter //success - MO_DBG_DEBUG("Restored tx %u-%u", evseId, txNr, txData.size()); + MO_DBG_DEBUG("Restored tx %u-%u", evseId, txNr); return FilesystemUtils::LoadStatus::Success; } From b07399488890aeb1c252f8dc34f6440b91a8fc3f Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:25:48 +0200 Subject: [PATCH 05/50] bugfixes --- .../Model/Availability/AvailabilityService.cpp | 2 +- src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp | 4 +++- .../Model/FirmwareManagement/FirmwareService.cpp | 4 ++-- src/MicroOcpp/Model/Model.cpp | 10 ++++++---- .../Model/SmartCharging/SmartChargingService.cpp | 2 ++ src/MicroOcpp/Operations/BootNotification.cpp | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index fb4c5d26..92a14587 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -129,7 +129,7 @@ void Ocpp16::AvailabilityServiceEvse::loop() { } int32_t dtStatusTransition; - if (!clock.delta(clock.now(), t_statusTransition, dtStatusTransition)) { + if (!t_statusTransition.isDefined() || !clock.delta(clock.now(), t_statusTransition, dtStatusTransition)) { dtStatusTransition = 0; } diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 25157bb6..b3e07cd2 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -249,7 +249,9 @@ void DiagnosticsService::loop() { auto& clock = context.getClock(); int32_t dtNextTry; - clock.delta(clock.getUptime(), nextTry, dtNextTry); + if (!clock.delta(clock.getUptime(), nextTry, dtNextTry)) { + dtNextTry = -1; + } if (retries > 0 && dtNextTry >= 0) { diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp index 24c62f4a..cca91d8d 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp @@ -34,7 +34,7 @@ bool setupDefaultFwUpdater(FirmwareService *fwService); } #endif -FirmwareService::FirmwareService(Context& context) : MemoryManaged("v16.Firmware.FirmwareService"), context(context), clock(context.getClock()), timestampTransition(clock.getUptime()) { +FirmwareService::FirmwareService(Context& context) : MemoryManaged("v16.Firmware.FirmwareService"), context(context), clock(context.getClock()) { } @@ -147,7 +147,7 @@ void FirmwareService::loop() { int32_t dtTransition; if (!clock.delta(clock.getUptime(), timestampTransition, dtTransition)) { - dtTransition = 0; + dtTransition = -1; } if (dtTransition < delayTransition) { diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 9c0c3725..b35a498c 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -42,8 +42,6 @@ Ocpp16::Model::~Model() { bootService = nullptr; delete heartbeatService; heartbeatService = nullptr; - delete configurationService; - configurationService = nullptr; delete transactionService; transactionService = nullptr; delete meteringService; @@ -86,6 +84,9 @@ Ocpp16::Model::~Model() { delete secEventService; secEventService = nullptr; #endif //MO_ENABLE_SECURITY_EVENT + + delete configurationService; + configurationService = nullptr; } void Ocpp16::Model::updateSupportedStandardProfiles() { @@ -445,8 +446,6 @@ Ocpp201::Model::~Model() { bootService = nullptr; delete heartbeatService; heartbeatService = nullptr; - delete variableService; - variableService = nullptr; delete transactionService; transactionService = nullptr; delete meteringService; @@ -472,6 +471,9 @@ Ocpp201::Model::~Model() { delete secEventService; secEventService = nullptr; #endif //MO_ENABLE_SECURITY_EVENT + + delete variableService; + variableService = nullptr; } void Ocpp201::Model::setNumEvseId(unsigned int numEvseId) { diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index 51e9ee92..c17675e3 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -37,6 +37,7 @@ using namespace::MicroOcpp; SmartChargingServiceEvse::SmartChargingServiceEvse(Context& context, SmartChargingService& scService, unsigned int evseId) : MemoryManaged("v16.SmartCharging.SmartChargingServiceEvse"), context(context), clock(context.getClock()), scService{scService}, evseId{evseId} { + mo_chargeRate_init(&trackLimitOutput); } SmartChargingServiceEvse::~SmartChargingServiceEvse() { @@ -498,6 +499,7 @@ size_t SmartChargingServiceEvse::getChargingProfilesCount() { SmartChargingService::SmartChargingService(Context& context) : MemoryManaged("v16.SmartCharging.SmartChargingService"), context(context), clock(context.getClock()) { + mo_chargeRate_init(&trackLimitOutput); } SmartChargingService::~SmartChargingService() { diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index 2a61f237..3e825718 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -123,7 +123,7 @@ int BootNotification::writeMockConf(const char *operationType, char *buf, size_t (void)snprintf(timeStr, sizeof(timeStr), "2025-05-18T18:55:13Z"); } - return snprintf(buf, size, "{\"currentTime\":\"%s\",\"interval\":86400,\"status\":\"Accepted\"}", timeStr); + return snprintf(buf, size, "{\"currentTime\":\"%s\",\"interval\":60,\"status\":\"Accepted\"}", timeStr); } #endif //MO_ENABLE_MOCK_SERVER From a5ad5dbd85aabfce49fcbc7542b4047f9ef126c7 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:22:18 +0200 Subject: [PATCH 06/50] bugfixes --- src/MicroOcpp.cpp | 4 +- src/MicroOcpp.h | 4 +- src/MicroOcpp/Context.cpp | 4 +- src/MicroOcpp/Context.h | 6 +- src/MicroOcpp/Core/FilesystemUtils.cpp | 1 - src/MicroOcpp/Core/Memory.cpp | 8 +- src/MicroOcpp/Core/MessageService.h | 2 - src/MicroOcpp/Core/PersistencyUtils.cpp | 2 +- src/MicroOcpp/Core/Time.cpp | 16 ++- src/MicroOcpp/Core/Time.h | 2 +- .../Model/Authorization/AuthorizationData.cpp | 2 +- src/MicroOcpp/Model/Boot/BootService.cpp | 4 +- src/MicroOcpp/Model/Boot/BootService.h | 2 +- .../Model/Diagnostics/Diagnostics.cpp | 35 ++--- .../Model/Diagnostics/DiagnosticsService.cpp | 7 +- .../FirmwareManagement/FirmwareService.cpp | 2 +- src/MicroOcpp/Model/Metering/MeterValue.cpp | 15 ++- .../Model/Metering/MeteringService.cpp | 5 +- .../Model/Metering/MeteringService.h | 2 +- .../RemoteControl/RemoteControlService.cpp | 2 +- .../Model/Reservation/ReservationService.cpp | 2 - src/MicroOcpp/Model/Reset/ResetService.cpp | 2 - .../SmartCharging/SmartChargingModel.cpp | 4 +- .../SmartCharging/SmartChargingService.cpp | 9 +- .../Transactions/TransactionService16.cpp | 7 +- .../Model/Transactions/TransactionService16.h | 2 +- .../Transactions/TransactionService201.cpp | 6 +- .../Transactions/TransactionService201.h | 2 +- src/MicroOcpp/Operations/CustomOperation.cpp | 2 +- src/MicroOcpp/Operations/GetConfiguration.cpp | 2 +- src/MicroOcpp/Operations/MeterValues.cpp | 6 +- .../Operations/RemoteStartTransaction.cpp | 2 - src/MicroOcpp/Operations/TriggerMessage.cpp | 6 +- src/MicroOcpp/Platform.cpp | 14 +- src/MicroOcpp/Platform.h | 2 +- tests/Core.cpp | 120 ++++++++++++++++++ tests/helpers/testHelper.cpp | 6 +- tests/helpers/testHelper.h | 4 +- tests/ocppEngineLifecycle.cpp | 12 +- 39 files changed, 232 insertions(+), 103 deletions(-) create mode 100644 tests/Core.cpp diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index d40c6843..1afd362e 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -1866,11 +1866,11 @@ void mo_setDebugCb2(MO_Context *ctx, void (*debugCb2)(int lvl, const char *fn, i context->setDebugCb2(debugCb2); } -void mo_setTicksCb(unsigned long (*ticksCb)()) { +void mo_setTicksCb(uint32_t (*ticksCb)()) { mo_setTicksCb2(mo_getApiContext(), ticksCb); } -void mo_setTicksCb2(MO_Context *ctx, unsigned long (*ticksCb)()) { +void mo_setTicksCb2(MO_Context *ctx, uint32_t (*ticksCb)()) { if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index 818e5bba..46a373c3 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -460,8 +460,8 @@ void mo_setDebugCb2(MO_Context *ctx, void (*debugCb2)(int lvl, const char *fn, i //would break the unsigned integer arithmetics which MO relies on. This is a common pitfall if the platform //tick frequency is not 1 tick per ms. Then a helper function is needed which counts the ticks up to 2^32 before //rolling over. -void mo_setTicksCb(unsigned long (*ticksCb)()); -void mo_setTicksCb2(MO_Context *ctx, unsigned long (*ticksCb)()); +void mo_setTicksCb(uint32_t (*ticksCb)()); +void mo_setTicksCb2(MO_Context *ctx, uint32_t (*ticksCb)()); //Set custom random number generator function. By default, MO uses a pseudo-RNG which with the system time as //the only entropy source. Using a true RNG greatly improves security. diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index c9a3f6f0..88e9662d 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -48,11 +48,11 @@ void Context::setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, co debug.setDebugCb2(debugCb2); // `debug` globally declared in Debug.h } -void Context::setTicksCb(unsigned long (*ticksCb)()) { +void Context::setTicksCb(uint32_t (*ticksCb)()) { this->ticksCb = ticksCb; } -unsigned long Context::getTicksMs() { +uint32_t Context::getTicksMs() { if (this->ticksCb) { return this->ticksCb(); } else { diff --git a/src/MicroOcpp/Context.h b/src/MicroOcpp/Context.h index e5d74f4e..30f77d2b 100644 --- a/src/MicroOcpp/Context.h +++ b/src/MicroOcpp/Context.h @@ -33,7 +33,7 @@ class CertificateStore; class Context : public MemoryManaged { private: - unsigned long (*ticksCb)() = nullptr; + uint32_t (*ticksCb)() = nullptr; uint32_t (*rngCb)() = nullptr; #if MO_USE_FILEAPI != MO_CUSTOM_FS @@ -75,8 +75,8 @@ class Context : public MemoryManaged { void setDebugCb(void (*debugCb)(const char *msg)); void setDebugCb2(void (*debugCb2)(int lvl, const char *fn, int line, const char *msg)); - void setTicksCb(unsigned long (*ticksCb)()); - unsigned long getTicksMs(); + void setTicksCb(uint32_t (*ticksCb)()); + uint32_t getTicksMs(); void setRngCb(uint32_t (*rngCb)()); uint32_t (*getRngCb())(); diff --git a/src/MicroOcpp/Core/FilesystemUtils.cpp b/src/MicroOcpp/Core/FilesystemUtils.cpp index ecadb117..e10f8eca 100644 --- a/src/MicroOcpp/Core/FilesystemUtils.cpp +++ b/src/MicroOcpp/Core/FilesystemUtils.cpp @@ -14,7 +14,6 @@ bool FilesystemUtils::printPath(MO_FilesystemAdapter *filesystem, char *path, si size_t written = 0; int ret; - bool success = true; // write path_prefix ret = snprintf(path, size, "%s", filesystem->path_prefix ? filesystem->path_prefix : ""); diff --git a/src/MicroOcpp/Core/Memory.cpp b/src/MicroOcpp/Core/Memory.cpp index abd95bad..a89203b8 100644 --- a/src/MicroOcpp/Core/Memory.cpp +++ b/src/MicroOcpp/Core/Memory.cpp @@ -199,7 +199,7 @@ void mo_mem_set_tag(void *ptr, const char *tag) { void mo_mem_print_stats() { - MO_DBG_INFO("\n *** Heap usage statistics ***\n"); + MO_DBG_INFO("\n\n *** Heap usage statistics ***"); size_t size = 0; @@ -243,13 +243,13 @@ void mo_mem_print_stats() { size_t size_control2 = 0; for (const auto& tag : memTags) { size_control2 += tag.second.current_size; - MO_DBG_INFO("%s - %zu B (max. %zu B)\n", tag.first.c_str(), tag.second.current_size, tag.second.max_size); + MO_DBG_INFO("%s - %zu B (max. %zu B)", tag.first.c_str(), tag.second.current_size, tag.second.max_size); } - MO_DBG_INFO(" *** Summary ***\nBlocks: %zu\nTags: %zu\nCurrent usage: %zu B\nMaximum usage: %zu B\n", memBlocks.size(), memTags.size(), memTotal, memTotalMax); + MO_DBG_INFO("\n *** Summary ***\nBlocks: %zu\nTags: %zu\nCurrent usage: %zu B\nMaximum usage: %zu B\n", memBlocks.size(), memTags.size(), memTotal, memTotalMax); #if MO_DBG_LEVEL >= MO_DL_DEBUG { - MO_DBG_INFO(" *** Debug information ***\nTotal blocks (control value 1): %zu B\nTags (control value): %zu\nTotal tagged (control value 2): %zu B\nTotal tagged (control value 3): %zu B\nUntagged: %zu\nTotal untagged: %zu B\n", size, tags.size(), size_control, size_control2, untagged, untagged_size); + MO_DBG_INFO("\n *** Debug information ***\nTotal blocks (control value 1): %zu B\nTags (control value): %zu\nTotal tagged (control value 2): %zu B\nTotal tagged (control value 3): %zu B\nUntagged: %zu\nTotal untagged: %zu B\n", size, tags.size(), size_control, size_control2, untagged, untagged_size); } #endif } diff --git a/src/MicroOcpp/Core/MessageService.h b/src/MicroOcpp/Core/MessageService.h index c4ec4c2e..3e3ffe3d 100644 --- a/src/MicroOcpp/Core/MessageService.h +++ b/src/MicroOcpp/Core/MessageService.h @@ -67,8 +67,6 @@ class MessageService : public MemoryManaged { void receiveResponse(JsonArray json); std::unique_ptr createRequest(const char *operationType); - unsigned long sockTrackLastConnected = 0; - unsigned int nextOpNr = 10; //Nr 0 - 9 reservered for internal purposes public: MessageService(Context& context); diff --git a/src/MicroOcpp/Core/PersistencyUtils.cpp b/src/MicroOcpp/Core/PersistencyUtils.cpp index b1f31883..353770fe 100644 --- a/src/MicroOcpp/Core/PersistencyUtils.cpp +++ b/src/MicroOcpp/Core/PersistencyUtils.cpp @@ -18,7 +18,7 @@ bool PersistencyUtils::loadBootStats(MO_FilesystemAdapter *filesystem, BootStats auto ret = FilesystemUtils::loadJson(filesystem, "bootstats.jsn", json, "PersistencyUtils"); switch (ret) { case FilesystemUtils::LoadStatus::Success: { - bool success = true; + success = true; int bootNrIn = json["bootNr"] | 0; if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits::max()) { diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index ac655ab4..fa6b56df 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -100,22 +100,26 @@ void Clock::loop() { // rolled over if (platformDeltaMs > 3600 * 1000) { // timer rolled over and delta is above 1 hour - that's suspicious - MO_DBG_ERR("mocpp_tick_ms roll-over error. Must increment milliseconds up to (32^2 - 1) before resetting to 0"); + MO_DBG_ERR("getTickMs roll-over error. Must increment milliseconds up to (32^2 - 1) before resetting to 0"); platformDeltaMs = 0; } } #if MO_ENABLE_TIMESTAMP_MILLISECONDS { - addMs(unixTime, platformDeltaMs); - addMs(uptime, platformDeltaMs); + if (platformDeltaMs > (uint32_t)std::numeric_limits::max()) { + MO_DBG_ERR("integer overflow"); + platformDeltaMs = 0; + } + addMs(unixTime, (int32_t)platformDeltaMs); + addMs(uptime, (int32_t)platformDeltaMs); lastIncrement = platformTimeMs; } #else { - auto platformDeltaS = platformDeltaMs / 1000; + auto platformDeltaS = platformDeltaMs / (uint32_t)1000; add(unixTime, platformDeltaS); - add(uptime, platformDeltaS) + add(uptime, platformDeltaS); lastIncrement += platformDeltaS * 1000; } #endif //MO_ENABLE_TIMESTAMP_MILLISECONDS @@ -237,7 +241,7 @@ bool Clock::deltaMs(const Timestamp& t2, const Timestamp& t1, int32_t& dtMs) con } if ((dtS > 0 && dtS * 1000 + 1000 < dtS) || - dtS < 0 && dtS * 1000 - 1000 > dtS) { + (dtS < 0 && dtS * 1000 - 1000 > dtS)) { MO_DBG_ERR("integer overflow"); return false; } diff --git a/src/MicroOcpp/Core/Time.h b/src/MicroOcpp/Core/Time.h index 68c87aee..ec91ed16 100644 --- a/src/MicroOcpp/Core/Time.h +++ b/src/MicroOcpp/Core/Time.h @@ -61,7 +61,7 @@ class Clock { Timestamp unixTime; Timestamp uptime; - unsigned long lastIncrement = 0; + uint32_t lastIncrement = 0; public: diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp index bcf64bbd..25132561 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp @@ -69,7 +69,7 @@ bool AuthorizationData::readJson(Clock& clock, JsonObject entry, bool internalFo if (idTagInfo.containsKey(AUTHDATA_KEY_PARENTIDTAG(internalFormat))) { parentIdTag = static_cast(MO_MALLOC(getMemoryTag(), IDTAG_LEN_MAX + 1)); if (parentIdTag) { - snprintf(parentIdTag, sizeof(parentIdTag), "%s", idTagInfo[AUTHDATA_KEY_PARENTIDTAG(internalFormat)].as()); + snprintf(parentIdTag, IDTAG_LEN_MAX + 1, "%s", idTagInfo[AUTHDATA_KEY_PARENTIDTAG(internalFormat)].as()); } else { MO_DBG_ERR("OOM"); return false; diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index ef3a83ac..1f66ea56 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -323,8 +323,8 @@ RegistrationStatus BootService::getRegistrationStatus() { return status; } -void BootService::setRetryInterval(unsigned long interval_s) { - if (interval_s == 0) { +void BootService::setRetryInterval(int interval_s) { + if (interval_s <= 0) { this->interval_s = MO_BOOT_INTERVAL_DEFAULT; } else { this->interval_s = interval_s; diff --git a/src/MicroOcpp/Model/Boot/BootService.h b/src/MicroOcpp/Model/Boot/BootService.h index 6069a440..9cab2420 100644 --- a/src/MicroOcpp/Model/Boot/BootService.h +++ b/src/MicroOcpp/Model/Boot/BootService.h @@ -102,7 +102,7 @@ class BootService : public MemoryManaged { void notifyRegistrationStatus(RegistrationStatus status); RegistrationStatus getRegistrationStatus(); - void setRetryInterval(unsigned long interval); + void setRetryInterval(int interval); }; } diff --git a/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp b/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp index 824c2831..b018e46c 100644 --- a/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp +++ b/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp @@ -6,20 +6,25 @@ #include +#include + #if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS const char *mo_serializeGetLogStatus(MO_GetLogStatus status) { const char *statusCstr = ""; switch (status) { - case (MO_GetLogStatus_Accepted): + case MO_GetLogStatus_Accepted: statusCstr = "Accepted"; break; - case (MO_GetLogStatus_Rejected): + case MO_GetLogStatus_Rejected: statusCstr = "Rejected"; break; - case (MO_GetLogStatus_AcceptedCanceled): + case MO_GetLogStatus_AcceptedCanceled: statusCstr = "AcceptedCanceled"; break; + case MO_GetLogStatus_UNDEFINED: + MO_DBG_ERR("serialize undefined"); + break; } return statusCstr; } @@ -27,28 +32,28 @@ const char *mo_serializeGetLogStatus(MO_GetLogStatus status) { const char *mo_serializeUploadLogStatus(MO_UploadLogStatus status) { const char *statusCstr = ""; switch (status) { - case (MO_UploadLogStatus_BadMessage): + case MO_UploadLogStatus_BadMessage: statusCstr = "BadMessage"; break; - case (MO_UploadLogStatus_Idle): + case MO_UploadLogStatus_Idle: statusCstr = "Idle"; break; - case (MO_UploadLogStatus_NotSupportedOperation): + case MO_UploadLogStatus_NotSupportedOperation: statusCstr = "NotSupportedOperation"; break; - case (MO_UploadLogStatus_PermissionDenied): + case MO_UploadLogStatus_PermissionDenied: statusCstr = "PermissionDenied"; break; - case (MO_UploadLogStatus_Uploaded): + case MO_UploadLogStatus_Uploaded: statusCstr = "Uploaded"; break; - case (MO_UploadLogStatus_UploadFailure): + case MO_UploadLogStatus_UploadFailure: statusCstr = "UploadFailure"; break; - case (MO_UploadLogStatus_Uploading): + case MO_UploadLogStatus_Uploading: statusCstr = "Uploading"; break; - case (MO_UploadLogStatus_AcceptedCanceled): + case MO_UploadLogStatus_AcceptedCanceled: statusCstr = "AcceptedCanceled"; break; } @@ -80,16 +85,16 @@ namespace Ocpp16 { const char *serializeDiagnosticsStatus(DiagnosticsStatus status) { const char *statusCstr = ""; switch (status) { - case (DiagnosticsStatus::Idle): + case DiagnosticsStatus::Idle: statusCstr = "Idle"; break; - case (DiagnosticsStatus::Uploaded): + case DiagnosticsStatus::Uploaded: statusCstr = "Uploaded"; break; - case (DiagnosticsStatus::UploadFailed): + case DiagnosticsStatus::UploadFailed: statusCstr = "UploadFailed"; break; - case (DiagnosticsStatus::Uploading): + case DiagnosticsStatus::Uploading: statusCstr = "Uploading"; break; } diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index b3e07cd2..28994003 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -265,6 +265,10 @@ void DiagnosticsService::loop() { case MO_LogType_SecurityLog: success = uploadSecurityLog(); break; + case MO_LogType_UNDEFINED: + MO_DBG_ERR("undefined value"); + success = false; + break; } if (success) { @@ -417,7 +421,7 @@ MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int r MO_FREE(this->location); this->location = nullptr; size_t locationSize = strlen(location); - this->location = static_cast(MO_MALLOC(getMemoryTag, locationSize)); + this->location = static_cast(MO_MALLOC(getMemoryTag(), locationSize)); if (!this->location) { MO_DBG_ERR("OOM"); goto fail; @@ -669,7 +673,6 @@ bool DiagnosticsService::uploadDiagnostics() { auto& model = context.getModel16(); auto txSvc = model.getTransactionService(); - auto txSvcEvse0 = txSvc ? txSvc->getEvse(0) : nullptr; auto txSvcEvse1 = txSvc ? txSvc->getEvse(1) : nullptr; auto txSvcEvse2 = txSvc && model.getNumEvseId() > 2 ? txSvc->getEvse(2) : nullptr; auto txSvcEvse1Tx = txSvcEvse1 ? txSvcEvse1->getTransaction() : nullptr; diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp index cca91d8d..4fe5f9aa 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp @@ -320,7 +320,7 @@ bool FirmwareService::scheduleFirmwareUpdate(const char *location, Timestamp ret return false; } auto ret = snprintf(this->location, len, "%s", location); - if (ret < 0 || ret >= len) { + if (ret < 0 || (size_t)ret >= len) { MO_DBG_ERR("snprintf: %i", ret); MO_FREE(this->location); this->location = nullptr; diff --git a/src/MicroOcpp/Model/Metering/MeterValue.cpp b/src/MicroOcpp/Model/Metering/MeterValue.cpp index 2b177283..a63a3153 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValue.cpp @@ -88,7 +88,7 @@ MeterValue::~MeterValue() { #if MO_ENABLE_V201 namespace MicroOcpp { -bool loadSignedValue(MO_SignedMeterValue201* signedValue, const char *signedMeterData, const char *signingMethod, const char *encodingMethod, const char *publicKey) { +bool loadSignedValue(MO_SignedMeterValue201* signedValue, const char *signedMeterData, const char *signingMethod, const char *encodingMethod, const char *publicKey, const char *memoryTag) { size_t signedMeterDataSize = signedMeterData ? strlen(signedMeterData) + 1 : 0; size_t signingMethodSize = signingMethod ? strlen(signingMethod) + 1 : 0; @@ -101,7 +101,7 @@ bool loadSignedValue(MO_SignedMeterValue201* signedValue, const char *signedMete encodingMethodSize + publicKeySize; - char *buf = static_cast(MO_MALLOC(getMemoryTag(), bufsize)); + char *buf = static_cast(MO_MALLOC(memoryTag, bufsize)); if (!buf) { MO_DBG_ERR("OOM"); return false; @@ -306,7 +306,8 @@ bool MeterValue::parseJson(Clock& clock, Vector& meterInputs, Jso svJson["signedMeterData"] | (const char*)nullptr, svJson["signingMethod"] | (const char*)nullptr, svJson["encodingMethod"] | (const char*)nullptr, - svJson["publicKey"] | (const char*)nullptr)) { + svJson["publicKey"] | (const char*)nullptr, + getMemoryTag())) { MO_DBG_ERR("OOM"); MO_FREE(sv->valueSigned); return false; @@ -473,7 +474,7 @@ bool MeterValue::toJson(Clock& clock, int ocppVersion, bool internalFormat, Json svJson["value"] = sv->valueInt; } else { int ret = snprintf(valueBuf, sizeof(valueBuf), "%i", sv->valueInt); - if (ret < 0 || ret >= sizeof(valueBuf)) { + if (ret < 0 || (size_t)ret >= sizeof(valueBuf)) { MO_DBG_ERR("serialization error"); return false; } @@ -485,7 +486,7 @@ bool MeterValue::toJson(Clock& clock, int ocppVersion, bool internalFormat, Json svJson["value"] = sv->valueFloat; } else { int ret = snprintf(valueBuf, sizeof(valueBuf), MO_SAMPLEDVALUE_FLOAT_FORMAT, sv->valueFloat); - if (ret < 0 || ret >= sizeof(valueBuf)) { + if (ret < 0 || (size_t)ret >= sizeof(valueBuf)) { MO_DBG_ERR("serialization error"); return false; } @@ -503,7 +504,7 @@ bool MeterValue::toJson(Clock& clock, int ocppVersion, bool internalFormat, Json signedMeterValue = svJson; //write signature data into same sv JSON object } else { int ret = snprintf(valueBuf, sizeof(valueBuf), MO_SAMPLEDVALUE_FLOAT_FORMAT, sv->valueFloat); - if (ret < 0 || ret >= sizeof(valueBuf)) { + if (ret < 0 || (size_t)ret >= sizeof(valueBuf)) { MO_DBG_ERR("serialization error"); return false; } @@ -558,7 +559,7 @@ bool MeterValue::toJson(Clock& clock, int ocppVersion, bool internalFormat, Json #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 if (ocppVersion == MO_OCPP_V201) { - if (meterDevice.unit && strcmp(meterDevice.unit, "Wh") || meterDevice.unitOfMeasureMultiplier != 0) { + if ((meterDevice.unit && strcmp(meterDevice.unit, "Wh")) || meterDevice.unitOfMeasureMultiplier != 0) { JsonObject unitJson; if (internalFormat) { unitJson = svJson; diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index 6bf45f92..76849342 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -680,7 +680,7 @@ std::unique_ptr Ocpp16::MeteringServiceEvse::fetchFrontRequest() { dtLastAttempt = 0; } - if (dtLastAttempt < meterDataFront->attemptNr * std::max(0, mService.transactionMessageRetryIntervalInt->getInt())) { + if (dtLastAttempt < (int)meterDataFront->attemptNr * std::max(0, mService.transactionMessageRetryIntervalInt->getInt())) { return nullptr; } @@ -688,7 +688,8 @@ std::unique_ptr Ocpp16::MeteringServiceEvse::fetchFrontRequest() { meterDataFront->attemptTime = clock.getUptime(); auto transaction = txSvcEvse->getTransactionFront(); - if (transaction && transaction->getTxNr() != meterDataFront->txNr) { + bool txNrMatch = (!transaction && meterDataFront->txNr < 0) || (transaction && meterDataFront->txNr >= 0 && (unsigned int)meterDataFront->txNr == transaction->getTxNr()); + if (!txNrMatch) { MO_DBG_ERR("txNr mismatch"); //this could happen if tx-related MeterValues are sent shortly before StartTx or shortly after StopTx, or if txNr is mis-assigned during creation transaction = nullptr; } diff --git a/src/MicroOcpp/Model/Metering/MeteringService.h b/src/MicroOcpp/Model/Metering/MeteringService.h index 392b7cac..714ee720 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.h +++ b/src/MicroOcpp/Model/Metering/MeteringService.h @@ -69,7 +69,7 @@ class MeteringServiceEvse : public MemoryManaged, public RequestQueue { public: MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int connectorId); - ~MeteringServiceEvse(); + virtual ~MeteringServiceEvse(); bool addMeterInput(MO_MeterInput meterInput); Vector& getMeterInputs(); diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index d96bb1aa..ec9e30ec 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -509,7 +509,7 @@ TriggerMessageStatus RemoteControlService::triggerMessage(const char *requestedM } } - for (int eId = evseIdRangeBegin; eId < evseIdRangeEnd; eId++) { + for (unsigned int eId = evseIdRangeBegin; eId < evseIdRangeEnd; eId++) { Operation *operation = nullptr; diff --git a/src/MicroOcpp/Model/Reservation/ReservationService.cpp b/src/MicroOcpp/Model/Reservation/ReservationService.cpp index 3c9d8cff..893eb997 100644 --- a/src/MicroOcpp/Model/Reservation/ReservationService.cpp +++ b/src/MicroOcpp/Model/Reservation/ReservationService.cpp @@ -71,8 +71,6 @@ void ReservationService::loop() { continue; } - auto connectorid = reservations[i]->getConnectorId(); - auto availSvc = context.getModel16().getAvailabilityService(); auto availSvcEvse = availSvc ? availSvc->getEvse(reservations[i]->getConnectorId()) : nullptr; if (availSvcEvse) { diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index dad62615..62fcf56d 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -289,8 +289,6 @@ void Ocpp201::ResetService::Evse::loop() { } else if (!outstandingResetRetries) { MO_DBG_ERR("Reset device failure"); - auto availabilityService = context.getModel201().getAvailabilityService(); - if (evseId == 0) { //Set all EVSEs Available again for (unsigned int eId = 0; eId < resetService.numEvseId; eId++) { diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp index dd320f68..26fab4dc 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp @@ -113,7 +113,7 @@ bool ChargingSchedule::resizeChargingSchedulePeriod(size_t chargingSchedulePerio return true; } - chargingSchedulePeriod = static_cast(MO_MALLOC(getMemoryTag("v16.SmartCharging.SmartChargingModel"), sizeof(ChargingSchedulePeriod*) * chargingSchedulePeriodSize)); + chargingSchedulePeriod = static_cast(MO_MALLOC(getMemoryTag(), sizeof(ChargingSchedulePeriod*) * chargingSchedulePeriodSize)); if (!chargingSchedulePeriod) { MO_DBG_ERR("OOM"); return false; @@ -509,7 +509,7 @@ bool ChargingProfile::parseJson(Clock& clock, int ocppVersion, JsonObject json) if (ocppVersion == MO_OCPP_V201) { if (json.containsKey("transactionId")) { auto ret = snprintf(transactionId201, sizeof(transactionId201), "%s", json["transactionId"] | ""); - if (ret < 0 || ret >= sizeof(transactionId201)) { + if (ret < 0 || (size_t)ret >= sizeof(transactionId201)) { MO_DBG_WARN("format violation"); return false; } diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index c17675e3..fef72fd1 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -81,7 +81,7 @@ void SmartChargingServiceEvse::calculateLimit(int32_t unixTime, int32_t sessionD #if MO_ENABLE_V201 if (scService.ocppVersion == MO_OCPP_V201) { txMatch = (trackTxRmtProfileId >= 0 && trackTxRmtProfileId == txProfile[i]->chargingProfileId) || - !*txProfile[i]->transactionId201 < 0 || + !*txProfile[i]->transactionId201 || !strcmp(trackTransactionId201, txProfile[i]->transactionId201); } #endif //MO_ENABLE_V201 @@ -225,7 +225,7 @@ void SmartChargingServiceEvse::trackTransaction() { if (strcmp(tx->transactionId, trackTransactionId201)) { update = true; auto ret = snprintf(trackTransactionId201, sizeof(trackTransactionId201), "%s", tx->transactionId); - if (ret < 0 || ret >= sizeof(trackTransactionId201)) { + if (ret < 0 || (size_t)ret >= sizeof(trackTransactionId201)) { MO_DBG_ERR("snprintf: %i", ret); memset(trackTransactionId201, 0, sizeof(trackTransactionId201)); } @@ -466,7 +466,6 @@ std::unique_ptr SmartChargingServiceEvse::getCompositeSchedule } } - float limit_opt = unit == ChargingRateUnitType::Watt ? limit.power : limit.current; schedule->chargingSchedulePeriod[i]->limit = (unit == ChargingRateUnitType::Watt) ? limit.power : limit.current, schedule->chargingSchedulePeriod[i]->numberPhases = limit.numberPhases; schedule->chargingSchedulePeriod[i]->startPeriod = measuredDuration; @@ -1113,7 +1112,6 @@ std::unique_ptr SmartChargingService::getCompositeSchedule(uns } } - float limit_opt = (unit == ChargingRateUnitType::Watt) ? limit.power : limit.current; schedule->chargingSchedulePeriod[i]->limit = (unit == ChargingRateUnitType::Watt) ? limit.power : limit.current; schedule->chargingSchedulePeriod[i]->numberPhases = limit.numberPhases; schedule->chargingSchedulePeriod[i]->startPeriod = measuredDuration; @@ -1143,6 +1141,9 @@ bool SmartChargingServiceUtils::printProfileFileName(char *out, size_t bufsize, case ChargingProfilePurposeType::TxProfile: ret = snprintf(out, bufsize, "sc-tx-%.*u-%.*u.jsn", MO_NUM_EVSEID_DIGITS, evseId, MO_ChargeProfileMaxStackLevel_digits, stackLevel); break; + case ChargingProfilePurposeType::UNDEFINED: + MO_DBG_ERR("invalid args"); + return false; } if (ret < 0 || (size_t) ret >= bufsize) { diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index b8644212..9b417bbb 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -79,7 +79,6 @@ bool TransactionServiceEvse::setup() { char txFnamePrefix [30]; snprintf(txFnamePrefix, sizeof(txFnamePrefix), "tx-%.*u-", MO_NUM_EVSEID_DIGITS, evseId); - size_t txFnamePrefixLen = strlen(txFnamePrefix); if (filesystem) { if (!FilesystemUtils::loadRingIndex(filesystem, txFnamePrefix, MO_TXNR_MAX, &txNrBegin, &txNrEnd)) { @@ -860,8 +859,6 @@ bool TransactionServiceEvse::endTransaction(const char *idTag, const char *reaso return endTransaction_authorized(idTag, reason); } - bool res = false; - const char *parentIdTag = transaction->getParentIdTag(); if (strlen(parentIdTag) > 0) { @@ -1214,7 +1211,7 @@ std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { dtLastAttempt = 0; } - if (dtLastAttempt < transactionFront->getStartSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { + if (dtLastAttempt < (int)transactionFront->getStartSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { return nullptr; } @@ -1341,7 +1338,7 @@ std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { dtLastAttempt = 0; } - if (dtLastAttempt < transactionFront->getStopSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { + if (dtLastAttempt < (int)transactionFront->getStopSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { return nullptr; } diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.h b/src/MicroOcpp/Model/Transactions/TransactionService16.h index 340109cd..7a77ad59 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.h @@ -83,7 +83,7 @@ class TransactionServiceEvse : public RequestQueue, public MemoryManaged { bool commitTransaction(Transaction *transaction); public: TransactionServiceEvse(Context& context, TransactionService& conService, unsigned int evseId); - ~TransactionServiceEvse(); + virtual ~TransactionServiceEvse(); bool setup(); diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index e3ac0a16..07789a4c 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -1020,7 +1020,7 @@ std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { dtLastAttempt = MO_MAX_TIME; } - if (dtLastAttempt < txEventFront->attemptNr * std::max(0, txService.messageAttemptIntervalTransactionEventInt->getInt())) { + if (dtLastAttempt < (int)txEventFront->attemptNr * std::max(0, txService.messageAttemptIntervalTransactionEventInt->getInt())) { return nullptr; } @@ -1248,6 +1248,10 @@ bool TransactionService::setup() { abortLoop = true; break; } + + if (abortLoop) { + break; + } } return status; diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.h b/src/MicroOcpp/Model/Transactions/TransactionService201.h index 5792f37d..c624b1c3 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.h @@ -92,7 +92,7 @@ class TransactionServiceEvse : public RequestQueue, public MemoryManaged { public: TransactionServiceEvse(Context& context, TransactionService& txService, TransactionStoreEvse& txStore, unsigned int evseId); - ~TransactionServiceEvse(); + virtual ~TransactionServiceEvse(); bool setup(); diff --git a/src/MicroOcpp/Operations/CustomOperation.cpp b/src/MicroOcpp/Operations/CustomOperation.cpp index 791296e5..4a581146 100644 --- a/src/MicroOcpp/Operations/CustomOperation.cpp +++ b/src/MicroOcpp/Operations/CustomOperation.cpp @@ -165,7 +165,7 @@ std::unique_ptr CustomOperation::createConf() { size_t capacity = MO_MAX_JSON_CAPACITY / 4; while (!str && capacity <= MO_MAX_JSON_CAPACITY) { - str = static_cast(MO_MALLOC(memoryTag, capacity)); + str = static_cast(MO_MALLOC(getMemoryTag(), capacity)); if (!str) { MO_DBG_ERR("OOM"); break; diff --git a/src/MicroOcpp/Operations/GetConfiguration.cpp b/src/MicroOcpp/Operations/GetConfiguration.cpp index 8bbedc94..7e231c5d 100644 --- a/src/MicroOcpp/Operations/GetConfiguration.cpp +++ b/src/MicroOcpp/Operations/GetConfiguration.cpp @@ -120,7 +120,7 @@ std::unique_ptr GetConfiguration::createConf(){ switch (config->getType()) { case Configuration::Type::Int: { auto ret = snprintf(vbuf, VALUE_BUFSIZE, "%i", config->getInt()); - if (ret < 0 || ret >= VALUE_BUFSIZE) { + if (ret < 0 || (size_t)ret >= VALUE_BUFSIZE) { MO_DBG_ERR("value error"); continue; } diff --git a/src/MicroOcpp/Operations/MeterValues.cpp b/src/MicroOcpp/Operations/MeterValues.cpp index 77fdbab1..58cb8a98 100644 --- a/src/MicroOcpp/Operations/MeterValues.cpp +++ b/src/MicroOcpp/Operations/MeterValues.cpp @@ -22,10 +22,10 @@ MeterValues::MeterValues(Context& context) : MemoryManaged("v16.Operation.", "Me MeterValues::MeterValues(Context& context, unsigned int connectorId, int transactionId, MeterValue *meterValue, bool transferOwnership) : MemoryManaged("v16.Operation.", "MeterValues"), context(context), - connectorId(connectorId), - transactionId(transactionId), meterValue(meterValue), - isMeterValueOwner(transferOwnership) { + isMeterValueOwner(transferOwnership), + connectorId(connectorId), + transactionId(transactionId) { } diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp index b4a62352..75cb9e9f 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp @@ -81,8 +81,6 @@ void RemoteStartTransaction::processReq(JsonObject payload) { } } - auto rcSvc = context.getModel16().getRemoteControlService(); - status = rcService.remoteStartTransaction(connectorId, idTag, std::move(chargingProfile)); if (status == Ocpp16::RemoteStartStopStatus::ERR_INTERNAL) { errorCode = "InternalError"; diff --git a/src/MicroOcpp/Operations/TriggerMessage.cpp b/src/MicroOcpp/Operations/TriggerMessage.cpp index 2321cedd..cb73450a 100644 --- a/src/MicroOcpp/Operations/TriggerMessage.cpp +++ b/src/MicroOcpp/Operations/TriggerMessage.cpp @@ -54,7 +54,7 @@ void TriggerMessage::processReq(JsonObject payload) { } #endif //MO_ENABLE_V201 - if (evseId >= numEvseId) { + if (evseId >= 0 && (unsigned int)evseId >= numEvseId) { errorCode = "PropertyConstraintViolation"; return; } @@ -83,6 +83,10 @@ std::unique_ptr TriggerMessage::createConf(){ case TriggerMessageStatus::NotImplemented: statusStr = "NotImplemented"; break; + case TriggerMessageStatus::ERR_INTERNAL: + //dead code + statusStr = "Rejected"; + break; } payload["status"] = statusStr; return doc; diff --git a/src/MicroOcpp/Platform.cpp b/src/MicroOcpp/Platform.cpp index f4f5b8e5..5541ff7f 100644 --- a/src/MicroOcpp/Platform.cpp +++ b/src/MicroOcpp/Platform.cpp @@ -16,7 +16,7 @@ void defaultDebugCbImpl(const char *msg) { MO_USE_SERIAL.printf("%s", msg); } -unsigned long defaultTickCbImpl() { +uint32_t defaultTickCbImpl() { return millis(); } @@ -34,9 +34,9 @@ void defaultDebugCbImpl(const char *msg) { } decltype(xTaskGetTickCount()) mocpp_ticks_count = 0; -unsigned long mocpp_millis_count = 0; +uint32_t mocpp_millis_count = 0; -unsigned long defaultTickCbImpl() { +uint32_t defaultTickCbImpl() { auto ticks_now = xTaskGetTickCount(); mocpp_millis_count += ((ticks_now - mocpp_ticks_count) * 1000UL) / configTICK_RATE_HZ; mocpp_ticks_count = ticks_now; @@ -57,21 +57,21 @@ void defaultDebugCbImpl(const char *msg) { std::chrono::steady_clock::time_point clock_reference; bool clock_initialized = false; -unsigned long defaultTickCbImpl() { +uint32_t defaultTickCbImpl() { if (!clock_initialized) { clock_reference = std::chrono::steady_clock::now(); clock_initialized = true; } std::chrono::milliseconds ms = std::chrono::duration_cast( std::chrono::steady_clock::now() - clock_reference); - return (unsigned long) ms.count(); + return (uint32_t) ms.count(); } } //namespace MicroOcpp #else namespace MicroOcpp { void (*defaultDebugCbImpl)(const char*) = nullptr -unsigned long (*defaultTickCbImpl)() = nullptr; +uint32_t (*defaultTickCbImpl)() = nullptr; } //namespace MicroOcpp #endif @@ -81,7 +81,7 @@ void (*getDefaultDebugCb())(const char*) { return defaultDebugCbImpl; } -unsigned long (*getDefaultTickCb())() { +uint32_t (*getDefaultTickCb())() { return defaultTickCbImpl; } diff --git a/src/MicroOcpp/Platform.h b/src/MicroOcpp/Platform.h index 1a4f23bf..4ff554fd 100644 --- a/src/MicroOcpp/Platform.h +++ b/src/MicroOcpp/Platform.h @@ -20,7 +20,7 @@ namespace MicroOcpp { void (*getDefaultDebugCb())(const char*); -unsigned long (*getDefaultTickCb())(); +uint32_t (*getDefaultTickCb())(); uint32_t (*getDefaultRngCb())(); } //namespace MicroOcpp diff --git a/tests/Core.cpp b/tests/Core.cpp new file mode 100644 index 00000000..b846c2c3 --- /dev/null +++ b/tests/Core.cpp @@ -0,0 +1,120 @@ +// matth-x/MicroOcpp +// Copyright Matthias Akstaller 2019 - 2024 +// MIT License + +#include +#include +#include +#include "./helpers/testHelper.h" + +TEST_CASE( "Time" ) { + printf("\nRun %s\n", "Time"); + + //initialize Context without any configs + mo_initialize(); + + mtime = 0; + mo_getContext()->setTicksCb(custom_timer_cb); + + MicroOcpp::LoopbackConnection loopback; + mo_getContext()->setConnection(&loopback); + + mo_setup(); + + auto& clock = mo_getContext()->getClock(); + + SECTION("increment system time") { + + auto t0 = clock.now(); + auto uptime0 = clock.getUptime(); + + mo_loop(); + + mtime = 200; //200 ms + + mo_loop(); + + auto t200 = clock.now(); + auto uptime200 = clock.getUptime(); + + int32_t dt; + REQUIRE( clock.delta(t200, t0, dt) ); + REQUIRE( dt == 0 ); //floored to 0s + REQUIRE( clock.delta(uptime200, uptime0, dt) ); + REQUIRE( dt == 0 ); //floored to 0s + + mtime = 1000; //1s + + mo_loop(); + + auto t1 = clock.now(); + auto uptime1 = clock.getUptime(); + + REQUIRE( clock.delta(t1, t0, dt) ); + REQUIRE( dt == 1 ); + REQUIRE( clock.delta(uptime1, uptime0, dt) ); + REQUIRE( dt == 1 ); + + mtime = 3000; //3s, skipped 2s step + + mo_loop(); + + auto t3 = clock.now(); + auto uptime3 = clock.getUptime(); + + REQUIRE( clock.delta(t3, t0, dt) ); + REQUIRE( dt == 3 ); + REQUIRE( clock.delta(t3, t1, dt) ); + REQUIRE( dt == 2 ); + REQUIRE( clock.delta(uptime3, uptime0, dt) ); + REQUIRE( dt == 3 ); + REQUIRE( clock.delta(uptime3, uptime1, dt) ); + REQUIRE( dt == 2 ); + + //system time rolls over, need two steps because clock works with signed values internally + mtime = (std::numeric_limits::max() - 999UL) / 2UL; + mo_loop(); + mtime = std::numeric_limits::max() - 999UL; + mo_loop(); + + auto tRoll_min1 = clock.now(); + auto uptimeRoll_min1 = clock.getUptime(); + + mtime = 0; + mo_loop(); + + auto tRoll_0 = clock.now(); + auto uptimeRoll_0 = clock.getUptime(); + + REQUIRE( clock.delta(tRoll_0, tRoll_min1, dt) ); + REQUIRE( dt == 1 ); + REQUIRE( clock.delta(tRoll_0, t0, dt) ); + REQUIRE( dt == (int32_t)(std::numeric_limits::max() / 1000UL) ); + REQUIRE( clock.delta(uptimeRoll_0, uptimeRoll_min1, dt) ); + REQUIRE( dt == 1 ); + REQUIRE( clock.delta(uptimeRoll_0, uptime0, dt) ); + REQUIRE( dt == (int32_t)(std::numeric_limits::max() / 1000UL) ); + + //system time rolls over too early + mtime = std::numeric_limits::max() / 100UL - 999UL; //rollover from ~0.5 days to 0 instead of ~50 days to 0 + mo_loop(); + + tRoll_min1 = clock.now(); + uptimeRoll_min1 = clock.getUptime(); + + mtime = 0; + mo_loop(); + + tRoll_0 = clock.now(); + uptimeRoll_0 = clock.getUptime(); + + REQUIRE( clock.delta(tRoll_0, tRoll_min1, dt) ); + REQUIRE( dt == 0 ); //value corrected to 0 + REQUIRE( clock.delta(uptimeRoll_0, uptimeRoll_min1, dt) ); + REQUIRE( dt == 0 ); + } + + + + mo_deinitialize(); +} diff --git a/tests/helpers/testHelper.cpp b/tests/helpers/testHelper.cpp index ffc6fac5..f386ab98 100644 --- a/tests/helpers/testHelper.cpp +++ b/tests/helpers/testHelper.cpp @@ -10,15 +10,15 @@ using namespace MicroOcpp; -unsigned long mtime = 10000; -unsigned long custom_timer_cb() { +uint32_t mtime = 10000; +uint32_t custom_timer_cb() { return mtime; } void loop() { for (int i = 0; i < 30; i++) { mtime += 100; - mocpp_loop(); + mo_loop(); } } diff --git a/tests/helpers/testHelper.h b/tests/helpers/testHelper.h index 04e35d57..13d3ec52 100644 --- a/tests/helpers/testHelper.h +++ b/tests/helpers/testHelper.h @@ -7,8 +7,8 @@ #define UNIT_MEM_TAG "UnitTests" -extern unsigned long mtime; -unsigned long custom_timer_cb(); +extern uint32_t mtime; +uint32_t custom_timer_cb(); void loop(); diff --git a/tests/ocppEngineLifecycle.cpp b/tests/ocppEngineLifecycle.cpp index b3de2a18..267bb377 100644 --- a/tests/ocppEngineLifecycle.cpp +++ b/tests/ocppEngineLifecycle.cpp @@ -3,24 +3,22 @@ // MIT License #include -#include #include #include "./helpers/testHelper.h" TEST_CASE( "Context lifecycle" ) { printf("\nRun %s\n", "Context lifecycle"); - //initialize Context with dummy socket - MicroOcpp::LoopbackConnection loopback; - mocpp_initialize(loopback); + //initialize Context without any configs + mo_initialize(); SECTION("OCPP is initialized"){ - REQUIRE( getOcppContext() ); + REQUIRE( mo_getApiContext() ); } - mocpp_deinitialize(); + mo_deinitialize(); SECTION("OCPP is deinitialized"){ - REQUIRE( !( getOcppContext() ) ); + REQUIRE( !( mo_getApiContext() ) ); } } From d44d61f6e22adfb836c5e5d1ff6d6df85dabc3dc Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 15 Jun 2025 19:03:40 +0200 Subject: [PATCH 07/50] bugfixes --- src/MicroOcpp.cpp | 4 +-- src/MicroOcpp/Core/Request.cpp | 11 +++++--- src/MicroOcpp/Core/Request.h | 1 - src/MicroOcpp/Core/Time.cpp | 5 ++-- .../Model/Authorization/AuthorizationData.cpp | 4 +-- .../Model/Authorization/AuthorizationData.h | 4 +-- src/MicroOcpp/Model/Authorization/IdToken.h | 6 +++++ .../Model/Metering/MeteringServiceV201.h | 3 ++- .../Model/Transactions/Transaction.cpp | 16 ++++++------ .../Model/Transactions/Transaction.h | 11 ++++---- .../Transactions/TransactionService16.cpp | 1 - src/MicroOcpp/Operations/Authorize.cpp | 6 ++--- src/MicroOcpp/Operations/Authorize.h | 4 +-- src/MicroOcpp/Operations/BootNotification.h | 1 - src/MicroOcpp/Operations/CiStrings.h | 25 ------------------- src/MicroOcpp/Operations/CustomOperation.cpp | 6 ++--- .../Operations/RemoteStartTransaction.cpp | 4 +-- .../Operations/RemoteStartTransaction.h | 1 - src/MicroOcpp/Operations/StartTransaction.h | 1 - src/MicroOcpp/Operations/StopTransaction.h | 1 - src/MicroOcpp/Platform.h | 2 +- tests/Core.cpp | 21 +++++++++++++++- tests/LocalAuthList.cpp | 6 ++--- 23 files changed, 71 insertions(+), 73 deletions(-) delete mode 100644 src/MicroOcpp/Operations/CiStrings.h diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 1afd362e..01ced2d2 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -511,6 +511,7 @@ bool mo_authorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char * return success; } +#endif //MO_ENABLE_V201 //End the transaction process if idTag is authorized bool mo_endTransaction(const char *idTag, const char *reason) { @@ -3041,6 +3042,3 @@ bool mo_setOnSendConf(MO_Context *ctx, const char *operationType, } return success; } - -#endif - diff --git a/src/MicroOcpp/Core/Request.cpp b/src/MicroOcpp/Core/Request.cpp index 232dab9f..7a2ea5e4 100644 --- a/src/MicroOcpp/Core/Request.cpp +++ b/src/MicroOcpp/Core/Request.cpp @@ -121,13 +121,16 @@ Request::CreateRequestResult Request::createRequest(JsonDoc& requestJson) { if (dt >= 10) { debugRequestTime = clock.now(); - char *buf = static_cast(MO_MALLOC(getMemoryTag(), 1024)); + size_t size = measureJson(requestJson) + 1; + size = std::min(size, (size_t)256); + + char *buf = static_cast(MO_MALLOC(getMemoryTag(), size)); size_t len = 0; if (buf) { - len = serializeJson(requestJson, buf, 1024); + len = serializeJson(requestJson, buf, size); } - if (!buf || len < 1) { + if (!buf || len < 1 || (size_t)len >= size) { MO_DBG_DEBUG("Try to send request: %s", operation->getOperationType()); } else { MO_DBG_DEBUG("Try to send request: %.*s (...)", 128, buf); @@ -211,7 +214,7 @@ bool Request::receiveRequest(JsonArray request) { * Hand the payload over to the first Callback. It is a callback that notifies the client that request has been processed in the OCPP-library */ if (onReceiveReqListener) { - size_t bufsize = 1000; //fixed for now, may change to direct pass-through of the input message + size_t bufsize = measureJson(payload) + 1; char *buf = static_cast(MO_MALLOC(getMemoryTag(), bufsize)); if (buf) { auto written = serializeJson(payload, buf, bufsize); diff --git a/src/MicroOcpp/Core/Request.h b/src/MicroOcpp/Core/Request.h index 59bfb4fb..db2d5492 100644 --- a/src/MicroOcpp/Core/Request.h +++ b/src/MicroOcpp/Core/Request.h @@ -19,7 +19,6 @@ namespace MicroOcpp { class Context; class Operation; -class Model; class Request : public MemoryManaged { private: diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index fa6b56df..4e9d6af4 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -307,8 +307,8 @@ bool Clock::toJsonString(const Timestamp& src, char *dst, size_t size) const { int year = 1970; int month = 0; - while (time - (noDays(year, month) * 24 * 3600) >= 0) { - time -= noDays(year, month) * 24 * 3600; + while (time - (noDays(month, year) * 24 * 3600) >= 0) { + time -= noDays(month, year) * 24 * 3600; month++; if (month >= 12) { year++; @@ -533,6 +533,7 @@ bool Clock::parseString(const char *src, Timestamp& dst) const { time += day * 24 * 3600; time += hour * 3600; + time += minute * 60; time += second; if (time < MO_MIN_TIME || time > MO_MAX_TIME) { diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp index 25132561..b1479af4 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp @@ -67,9 +67,9 @@ bool AuthorizationData::readJson(Clock& clock, JsonObject entry, bool internalFo parentIdTag = nullptr; if (idTagInfo.containsKey(AUTHDATA_KEY_PARENTIDTAG(internalFormat))) { - parentIdTag = static_cast(MO_MALLOC(getMemoryTag(), IDTAG_LEN_MAX + 1)); + parentIdTag = static_cast(MO_MALLOC(getMemoryTag(), MO_IDTAG_LEN_MAX + 1)); if (parentIdTag) { - snprintf(parentIdTag, IDTAG_LEN_MAX + 1, "%s", idTagInfo[AUTHDATA_KEY_PARENTIDTAG(internalFormat)].as()); + snprintf(parentIdTag, MO_IDTAG_LEN_MAX + 1, "%s", idTagInfo[AUTHDATA_KEY_PARENTIDTAG(internalFormat)].as()); } else { MO_DBG_ERR("OOM"); return false; diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.h b/src/MicroOcpp/Model/Authorization/AuthorizationData.h index 0174d91b..22749cae 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.h +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.h @@ -5,9 +5,9 @@ #ifndef MO_AUTHORIZATIONDATA_H #define MO_AUTHORIZATIONDATA_H -#include #include #include +#include #include #include @@ -41,7 +41,7 @@ class AuthorizationData : public MemoryManaged { char *parentIdTag = nullptr; Timestamp *expiryDate = nullptr; //has ownership - char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; + char idTag [MO_IDTAG_LEN_MAX + 1] = {'\0'}; AuthorizationStatus status = AuthorizationStatus::UNDEFINED; public: diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h index 0d8d2b94..f8a2d683 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.h +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -10,6 +10,12 @@ #include #include +#if MO_ENABLE_V16 + +#define MO_IDTAG_LEN_MAX 20 + +#endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 #ifdef __cplusplus diff --git a/src/MicroOcpp/Model/Metering/MeteringServiceV201.h b/src/MicroOcpp/Model/Metering/MeteringServiceV201.h index f35ec146..e8385f78 100644 --- a/src/MicroOcpp/Model/Metering/MeteringServiceV201.h +++ b/src/MicroOcpp/Model/Metering/MeteringServiceV201.h @@ -22,11 +22,12 @@ namespace MicroOcpp { -class Model; class Variable; namespace Ocpp201 { +class Model; + class SampledValueProperties : public MemoryManaged { private: const char *format = nullptr; diff --git a/src/MicroOcpp/Model/Transactions/Transaction.cpp b/src/MicroOcpp/Model/Transactions/Transaction.cpp index 6ba568d0..d2d4d81e 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.cpp +++ b/src/MicroOcpp/Model/Transactions/Transaction.cpp @@ -25,23 +25,23 @@ Ocpp16::Transaction::~Transaction() { } bool Ocpp16::Transaction::setIdTag(const char *idTag) { - auto ret = snprintf(this->idTag, IDTAG_LEN_MAX + 1, "%s", idTag); - return ret >= 0 && ret < IDTAG_LEN_MAX + 1; + auto ret = snprintf(this->idTag, MO_IDTAG_LEN_MAX + 1, "%s", idTag); + return ret >= 0 && ret < MO_IDTAG_LEN_MAX + 1; } bool Ocpp16::Transaction::setParentIdTag(const char *idTag) { - auto ret = snprintf(this->parentIdTag, IDTAG_LEN_MAX + 1, "%s", idTag); - return ret >= 0 && ret < IDTAG_LEN_MAX + 1; + auto ret = snprintf(this->parentIdTag, MO_IDTAG_LEN_MAX + 1, "%s", idTag); + return ret >= 0 && ret < MO_IDTAG_LEN_MAX + 1; } bool Ocpp16::Transaction::setStopIdTag(const char *idTag) { - auto ret = snprintf(stop_idTag, IDTAG_LEN_MAX + 1, "%s", idTag); - return ret >= 0 && ret < IDTAG_LEN_MAX + 1; + auto ret = snprintf(stop_idTag, MO_IDTAG_LEN_MAX + 1, "%s", idTag); + return ret >= 0 && ret < MO_IDTAG_LEN_MAX + 1; } bool Ocpp16::Transaction::setStopReason(const char *reason) { - auto ret = snprintf(stop_reason, REASON_LEN_MAX + 1, "%s", reason); - return ret >= 0 && ret < REASON_LEN_MAX + 1; + auto ret = snprintf(stop_reason, sizeof(stop_reason) + 1, "%s", reason); + return ret >= 0 && (size_t)ret < sizeof(stop_reason) + 1; } #endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 5d06c318..2e0bf15c 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -21,6 +20,8 @@ #define MO_TXNR_MAX 10000U //upper limit of txNr (internal usage). Must be at least 2*MO_TXRECORD_SIZE+1 #define MO_TXNR_DIGITS 4 //digits needed to print MAX_INDEX-1 (=9999, i.e. 4 digits) +#define MO_REASON_LEN_MAX 15 + #if MO_ENABLE_V16 namespace MicroOcpp { @@ -65,8 +66,8 @@ class Transaction : public MemoryManaged { /* * Attributes existing before StartTransaction */ - char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; - char parentIdTag [IDTAG_LEN_MAX + 1] = {'\0'}; + char idTag [MO_IDTAG_LEN_MAX + 1] = {'\0'}; + char parentIdTag [MO_IDTAG_LEN_MAX + 1] = {'\0'}; bool authorized = false; //if the given idTag was authorized bool deauthorized = false; //if the server revoked a local authorization Timestamp begin_timestamp; @@ -85,10 +86,10 @@ class Transaction : public MemoryManaged { * Attributes of StopTransaction */ SendStatus stop_sync; - char stop_idTag [IDTAG_LEN_MAX + 1] = {'\0'}; + char stop_idTag [MO_IDTAG_LEN_MAX + 1] = {'\0'}; int32_t stop_meter = -1; Timestamp stop_timestamp; - char stop_reason [REASON_LEN_MAX + 1] = {'\0'}; + char stop_reason [MO_REASON_LEN_MAX + 1] = {'\0'}; /* * General attributes diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index 9b417bbb..f477130c 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include diff --git a/src/MicroOcpp/Operations/Authorize.cpp b/src/MicroOcpp/Operations/Authorize.cpp index 8846a667..e15e18d8 100644 --- a/src/MicroOcpp/Operations/Authorize.cpp +++ b/src/MicroOcpp/Operations/Authorize.cpp @@ -13,8 +13,8 @@ using namespace MicroOcpp; #if MO_ENABLE_V16 Ocpp16::Authorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged("v16.Operation.", "Authorize"), model(model) { - if (idTagIn && strnlen(idTagIn, IDTAG_LEN_MAX + 2) <= IDTAG_LEN_MAX) { - snprintf(idTag, IDTAG_LEN_MAX + 1, "%s", idTagIn); + if (idTagIn && strnlen(idTagIn, MO_IDTAG_LEN_MAX + 2) <= MO_IDTAG_LEN_MAX) { + snprintf(idTag, MO_IDTAG_LEN_MAX + 1, "%s", idTagIn); } else { MO_DBG_WARN("Format violation of idTag. Discard idTag"); } @@ -25,7 +25,7 @@ const char* Ocpp16::Authorize::getOperationType(){ } std::unique_ptr Ocpp16::Authorize::createReq() { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + (IDTAG_LEN_MAX + 1)); + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + (MO_IDTAG_LEN_MAX + 1)); JsonObject payload = doc->to(); payload["idTag"] = idTag; return doc; diff --git a/src/MicroOcpp/Operations/Authorize.h b/src/MicroOcpp/Operations/Authorize.h index fc743916..989305d5 100644 --- a/src/MicroOcpp/Operations/Authorize.h +++ b/src/MicroOcpp/Operations/Authorize.h @@ -6,7 +6,7 @@ #define MO_AUTHORIZE_H #include -#include +#include #include #if MO_ENABLE_V16 @@ -19,7 +19,7 @@ class Model; class Authorize : public Operation, public MemoryManaged { private: Model& model; - char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; + char idTag [MO_IDTAG_LEN_MAX + 1] = {'\0'}; public: Authorize(Model& model, const char *idTag); diff --git a/src/MicroOcpp/Operations/BootNotification.h b/src/MicroOcpp/Operations/BootNotification.h index f0212985..476cdb1a 100644 --- a/src/MicroOcpp/Operations/BootNotification.h +++ b/src/MicroOcpp/Operations/BootNotification.h @@ -7,7 +7,6 @@ #include #include -#include #include #if MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/CiStrings.h b/src/MicroOcpp/Operations/CiStrings.h deleted file mode 100644 index d52e176c..00000000 --- a/src/MicroOcpp/Operations/CiStrings.h +++ /dev/null @@ -1,25 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -/* - * A collection of the fixed-length string types in the OCPP specification - */ - -#ifndef MO_CI_STRINGS_H -#define MO_CI_STRINGS_H - -#define CiString20TypeLen 20 -#define CiString25TypeLen 25 -#define CiString50TypeLen 50 -#define CiString255TypeLen 255 -#define CiString500TypeLen 500 - -//specified by OCPP -#define IDTAG_LEN_MAX CiString20TypeLen -#define CONF_KEYLEN_MAX CiString50TypeLen - -//not specified by OCPP -#define REASON_LEN_MAX 15 - -#endif diff --git a/src/MicroOcpp/Operations/CustomOperation.cpp b/src/MicroOcpp/Operations/CustomOperation.cpp index 4a581146..370b8f02 100644 --- a/src/MicroOcpp/Operations/CustomOperation.cpp +++ b/src/MicroOcpp/Operations/CustomOperation.cpp @@ -14,7 +14,7 @@ namespace MicroOcpp { std::unique_ptr makeDeserializedJson(const char *memoryTag, const char *jsonString) { std::unique_ptr doc; - size_t capacity = MO_MAX_JSON_CAPACITY / 4; + size_t capacity = MO_MAX_JSON_CAPACITY / 8; DeserializationError err = DeserializationError::NoMemory; while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) { @@ -40,7 +40,7 @@ std::unique_ptr makeDeserializedJson(const char *memoryTag, char *makeSerializedJsonString(const char *memoryTag, JsonObject json) { char *str = nullptr; - size_t capacity = MO_MAX_JSON_CAPACITY / 4; + size_t capacity = MO_MAX_JSON_CAPACITY / 8; while (!str && capacity <= MO_MAX_JSON_CAPACITY) { str = static_cast(MO_MALLOC(memoryTag, capacity)); @@ -162,7 +162,7 @@ std::unique_ptr CustomOperation::createConf() { char *str = nullptr; - size_t capacity = MO_MAX_JSON_CAPACITY / 4; + size_t capacity = MO_MAX_JSON_CAPACITY / 8; while (!str && capacity <= MO_MAX_JSON_CAPACITY) { str = static_cast(MO_MALLOC(getMemoryTag(), capacity)); diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp index 75cb9e9f..16f04c2c 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp @@ -36,8 +36,8 @@ void RemoteStartTransaction::processReq(JsonObject payload) { } const char *idTag = payload["idTag"] | ""; - size_t len = strnlen(idTag, IDTAG_LEN_MAX + 1); - if (len == 0 || len > IDTAG_LEN_MAX) { + size_t len = strnlen(idTag, MO_IDTAG_LEN_MAX + 1); + if (len == 0 || len > MO_IDTAG_LEN_MAX) { errorCode = "PropertyConstraintViolation"; errorDescription = "idTag empty or too long"; return; diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.h b/src/MicroOcpp/Operations/RemoteStartTransaction.h index a4aecc15..15989043 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.h +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.h @@ -7,7 +7,6 @@ #include #include -#include #include #if MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/StartTransaction.h b/src/MicroOcpp/Operations/StartTransaction.h index d78aebea..c6ee1784 100644 --- a/src/MicroOcpp/Operations/StartTransaction.h +++ b/src/MicroOcpp/Operations/StartTransaction.h @@ -7,7 +7,6 @@ #include #include -#include #include #if MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/StopTransaction.h b/src/MicroOcpp/Operations/StopTransaction.h index 7437dcf8..3572d5e1 100644 --- a/src/MicroOcpp/Operations/StopTransaction.h +++ b/src/MicroOcpp/Operations/StopTransaction.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #if MO_ENABLE_V16 diff --git a/src/MicroOcpp/Platform.h b/src/MicroOcpp/Platform.h index 4ff554fd..c7dca053 100644 --- a/src/MicroOcpp/Platform.h +++ b/src/MicroOcpp/Platform.h @@ -28,7 +28,7 @@ uint32_t (*getDefaultRngCb())(); #ifndef MO_MAX_JSON_CAPACITY #if MO_PLATFORM == MO_PLATFORM_UNIX -#define MO_MAX_JSON_CAPACITY 16384 +#define MO_MAX_JSON_CAPACITY 5120 #else #define MO_MAX_JSON_CAPACITY 4096 #endif diff --git a/tests/Core.cpp b/tests/Core.cpp index b846c2c3..8751806e 100644 --- a/tests/Core.cpp +++ b/tests/Core.cpp @@ -7,6 +7,8 @@ #include #include "./helpers/testHelper.h" +using namespace MicroOcpp; + TEST_CASE( "Time" ) { printf("\nRun %s\n", "Time"); @@ -16,7 +18,7 @@ TEST_CASE( "Time" ) { mtime = 0; mo_getContext()->setTicksCb(custom_timer_cb); - MicroOcpp::LoopbackConnection loopback; + LoopbackConnection loopback; mo_getContext()->setConnection(&loopback); mo_setup(); @@ -114,6 +116,23 @@ TEST_CASE( "Time" ) { REQUIRE( dt == 0 ); } + SECTION("serialize and parse time strings") { + + int32_t tRefInt = 1750000000; + const char *tRefString = "2025-06-15T15:06:40.123Z"; + + Timestamp t; + REQUIRE( clock.parseString(tRefString, t) ); + int32_t tUnix; + REQUIRE( clock.toUnixTime(t, tUnix) ); + REQUIRE( tUnix == tRefInt ); + + char buf [MO_JSONDATE_SIZE]; + REQUIRE( clock.toJsonString(t, buf, sizeof(buf)) ); + INFO(buf); + REQUIRE( !strcmp(buf, tRefString) ); + } + mo_deinitialize(); diff --git a/tests/LocalAuthList.cpp b/tests/LocalAuthList.cpp index 004f4fa2..1253338b 100644 --- a/tests/LocalAuthList.cpp +++ b/tests/LocalAuthList.cpp @@ -37,7 +37,7 @@ void generateAuthList(JsonArray out, size_t size, bool compact) { idTagInfo = authData["idTagInfo"].to(); } - char buf [IDTAG_LEN_MAX + 1]; + char buf [MO_IDTAG_LEN_MAX + 1]; sprintf(buf, "mIdTag%zu", i); authData[AUTHDATA_KEY_IDTAG(compact)] = buf; idTagInfo[AUTHDATA_KEY_STATUS(compact)] = "Accepted"; @@ -838,7 +838,7 @@ TEST_CASE( "LocalAuth" ) { auto payload = doc->to(); payload["listVersion"] = (int) i; - char buf [IDTAG_LEN_MAX + 1]; + char buf [MO_IDTAG_LEN_MAX + 1]; sprintf(buf, "mIdTag%zu", i-1); payload["localAuthorizationList"][0]["idTag"] = buf; payload["localAuthorizationList"][0]["idTagInfo"]["status"] = "Accepted"; @@ -877,7 +877,7 @@ TEST_CASE( "LocalAuth" ) { payload["listVersion"] = listVersionInvalid; //update already existing entry - char buf [IDTAG_LEN_MAX + 1]; + char buf [MO_IDTAG_LEN_MAX + 1]; sprintf(buf, "mIdTag%zu", 0UL); payload["localAuthorizationList"][0]["idTag"] = buf; payload["localAuthorizationList"][0]["idTagInfo"]["status"] = "Accepted"; From 9164b959995b96bd2cfb7137ce9c2251d9221308 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 15 Jun 2025 20:10:45 +0200 Subject: [PATCH 08/50] bugfixes --- src/MicroOcpp/Core/PersistencyUtils.cpp | 4 ++- .../Model/Diagnostics/DiagnosticsService.cpp | 2 +- .../Model/Metering/MeteringService.cpp | 2 +- .../Model/Metering/MeteringService.h | 4 +-- src/MicroOcpp/Model/Model.cpp | 30 ++++++++++++++----- .../RemoteControl/RemoteControlService.cpp | 4 +-- .../SmartCharging/SmartChargingService.cpp | 4 +-- 7 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/MicroOcpp/Core/PersistencyUtils.cpp b/src/MicroOcpp/Core/PersistencyUtils.cpp index 353770fe..494a3bc7 100644 --- a/src/MicroOcpp/Core/PersistencyUtils.cpp +++ b/src/MicroOcpp/Core/PersistencyUtils.cpp @@ -66,7 +66,9 @@ bool PersistencyUtils::storeBootStats(MO_FilesystemAdapter *filesystem, BootStat json["bootNr"] = bstats.bootNr; json["attempts"] = bstats.attempts; - json["MicroOcppVersion"] = (const char*)bstats.microOcppVersion; + if (*bstats.microOcppVersion) { + json["MicroOcppVersion"] = (const char*)bstats.microOcppVersion; + } auto ret = FilesystemUtils::storeJson(filesystem, "bootstats.jsn", json); bool success = (ret == FilesystemUtils::StoreStatus::Success); diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 28994003..918621bc 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -38,7 +38,7 @@ void defaultDiagnosticsOnClose(void *user_data); using namespace MicroOcpp; -DiagnosticsService::DiagnosticsService(Context& context) : MemoryManaged("v16.Diagnostics.DiagnosticsService"), context(context), diagFileList(makeVector(getMemoryTag())) { +DiagnosticsService::DiagnosticsService(Context& context) : MemoryManaged("v16/v201.Diagnostics.DiagnosticsService"), context(context), diagFileList(makeVector(getMemoryTag())) { } diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index 76849342..0436bbcf 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -908,7 +908,7 @@ std::unique_ptr Ocpp201::MeteringServiceEvse::takeTriggeredMeterValu return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, MO_ReadingContext_Trigger, MO_FLAG_AlignedData, getMemoryTag())); } -Ocpp201::MeteringService::MeteringService(Context& context) : MemoryManaged("v16.Metering.MeteringService"), context(context) { +Ocpp201::MeteringService::MeteringService(Context& context) : MemoryManaged("v201.Metering.MeteringService"), context(context) { } diff --git a/src/MicroOcpp/Model/Metering/MeteringService.h b/src/MicroOcpp/Model/Metering/MeteringService.h index 714ee720..28fded6c 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.h +++ b/src/MicroOcpp/Model/Metering/MeteringService.h @@ -192,8 +192,8 @@ class MeteringService : public MemoryManaged { friend class MeteringServiceEvse; }; -} -} +} //namespace Ocpp201 +} //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index b35a498c..12f9c948 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -56,9 +56,12 @@ Ocpp16::Model::~Model() { #if MO_ENABLE_FIRMWAREMANAGEMENT delete firmwareService; firmwareService = nullptr; +#endif //MO_ENABLE_FIRMWAREMANAGEMENT + +#if MO_ENABLE_DIAGNOSTICS delete diagnosticsService; diagnosticsService = nullptr; -#endif //MO_ENABLE_FIRMWAREMANAGEMENT +#endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_LOCAL_AUTH delete authorizationService; @@ -412,11 +415,13 @@ void Ocpp16::Model::loop() { if (firmwareService) { firmwareService->loop(); } +#endif //MO_ENABLE_FIRMWAREMANAGEMENT +#if MO_ENABLE_DIAGNOSTICS if (diagnosticsService) { diagnosticsService->loop(); } -#endif //MO_ENABLE_FIRMWAREMANAGEMENT +#endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_RESERVATION if (reservationService) { @@ -457,6 +462,11 @@ Ocpp201::Model::~Model() { delete remoteControlService; remoteControlService = nullptr; +#if MO_ENABLE_DIAGNOSTICS + delete diagnosticsService; + diagnosticsService = nullptr; +#endif //MO_ENABLE_DIAGNOSTICS + #if MO_ENABLE_SMARTCHARGING delete smartChargingService; smartChargingService = nullptr; @@ -677,11 +687,17 @@ void Ocpp201::Model::loop() { availabilityService->loop(); } - #if MO_ENABLE_SMARTCHARGING - if (smartChargingService) { - smartChargingService->loop(); - } - #endif //MO_ENABLE_SMARTCHARGING +#if MO_ENABLE_DIAGNOSTICS + if (diagnosticsService) { + diagnosticsService->loop(); + } +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING + if (smartChargingService) { + smartChargingService->loop(); + } +#endif //MO_ENABLE_SMARTCHARGING } #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index ec9e30ec..9f90c314 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -47,7 +47,7 @@ bool RemoteControlServiceEvse::setup() { } #endif #if MO_ENABLE_V201 - if (context.getOcppVersion() == MO_OCPP_V16) { + if (context.getOcppVersion() == MO_OCPP_V201) { auto txServce = context.getModel201().getTransactionService(); txServiceEvse201 = txServce ? txServce->getEvse(evseId) : nullptr; if (!txServiceEvse201) { @@ -132,7 +132,7 @@ Ocpp201::UnlockStatus RemoteControlServiceEvse::unlockConnector201() { #endif //MO_ENABLE_CONNECTOR_LOCK -RemoteControlService::RemoteControlService(Context& context) : MemoryManaged("v16.RemoteControl.RemoteControlService"), context(context) { +RemoteControlService::RemoteControlService(Context& context) : MemoryManaged("v16/v201.RemoteControl.RemoteControlService"), context(context) { } diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index fef72fd1..9a6be366 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -35,7 +35,7 @@ bool removeProfile(MO_FilesystemAdapter *filesystem, unsigned int evseId, Chargi using namespace::MicroOcpp; SmartChargingServiceEvse::SmartChargingServiceEvse(Context& context, SmartChargingService& scService, unsigned int evseId) : - MemoryManaged("v16.SmartCharging.SmartChargingServiceEvse"), context(context), clock(context.getClock()), scService{scService}, evseId{evseId} { + MemoryManaged("v16/v201.SmartCharging.SmartChargingServiceEvse"), context(context), clock(context.getClock()), scService{scService}, evseId{evseId} { mo_chargeRate_init(&trackLimitOutput); } @@ -496,7 +496,7 @@ size_t SmartChargingServiceEvse::getChargingProfilesCount() { } SmartChargingService::SmartChargingService(Context& context) - : MemoryManaged("v16.SmartCharging.SmartChargingService"), context(context), clock(context.getClock()) { + : MemoryManaged("v16/v201.SmartCharging.SmartChargingService"), context(context), clock(context.getClock()) { mo_chargeRate_init(&trackLimitOutput); } From 1da28b34b12b8350b4d0775cb9d61af201b9ee76 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 21 Jun 2025 14:33:21 +0200 Subject: [PATCH 09/50] bugfixes --- src/MicroOcpp.cpp | 9 +- src/MicroOcpp.h | 4 +- src/MicroOcpp/Context.cpp | 2 +- src/MicroOcpp/Core/Connection.cpp | 46 ++- src/MicroOcpp/Core/Connection.h | 3 +- src/MicroOcpp/Core/FilesystemAdapter.cpp | 30 +- .../Availability/AvailabilityService.cpp | 1 - .../Model/Diagnostics/DiagnosticsService.cpp | 14 +- .../FirmwareManagement/FirmwareService.cpp | 15 +- .../Model/Metering/MeteringServiceV201.cpp | 361 ------------------ .../Model/Metering/MeteringServiceV201.h | 153 -------- .../SmartCharging/SmartChargingService.cpp | 2 +- .../Transactions/TransactionService201.cpp | 5 + .../Model/Transactions/TransactionStore.h | 2 +- src/MicroOcpp/Operations/BootNotification.cpp | 14 +- src/MicroOcpp/Operations/BootNotification.h | 5 - src/MicroOcpp/Platform.h | 2 +- tests/benchmarks/firmware_size/main.cpp | 87 ++--- tests/benchmarks/firmware_size/platformio.ini | 2 +- 19 files changed, 122 insertions(+), 635 deletions(-) delete mode 100644 src/MicroOcpp/Model/Metering/MeteringServiceV201.cpp delete mode 100644 src/MicroOcpp/Model/Metering/MeteringServiceV201.h diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 01ced2d2..4142f448 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -113,15 +113,15 @@ void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const #if MO_WS_USE == MO_WS_ARDUINO //Setup MO with links2004/WebSockets library -void mo_setWebsocketUrl(const char *backendUrl, const char *chargeBoxId, const char *authorizationKey, const char *CA_cert) { +bool mo_setWebsocketUrl(const char *backendUrl, const char *chargeBoxId, const char *authorizationKey, const char *CA_cert) { if (!g_context) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return; + return false; } if (!backendUrl) { MO_DBG_ERR("invalid args"); - return; + return false; } MO_ConnectionConfig config; @@ -132,6 +132,7 @@ void mo_setWebsocketUrl(const char *backendUrl, const char *chargeBoxId, const c config.CA_cert = CA_cert; g_context->setDefaultConnectionConfig(config); + return true; } #endif @@ -1271,7 +1272,7 @@ bool mo_addMeterValueInputFloat2(MO_Context *ctx, unsigned int evseId, float (*m return mo_addMeterValueInput(ctx, evseId, mInput); } -bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { +bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), const char *measurand, const char *unit, const char *location, const char *phase) { MO_MeterInput mInput; mo_MeterInput_init(&mInput, MO_MeterInputType_String); mInput.getString = meterInput; diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index 46a373c3..b7198291 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -353,12 +353,12 @@ bool mo_addMeterValueInputInt(int32_t (*meterInput)(), const char *measurand, co bool mo_addMeterValueInputInt2(MO_Context *ctx, unsigned int evseId, int32_t (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); //Input for further float MeterValue types -bool mo_addMeterValueInputFloat(float (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); +bool mo_addMeterValueInputFloat(float (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase); bool mo_addMeterValueInputFloat2(MO_Context *ctx, unsigned int evseId, float (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); //Input for further string MeterValue types. `meterInput` shall write the value into `buf` (which is `size` large) and return the number //of characters written (not including the terminating 0). See "MeterValue.h" for a full description -bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); +bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), const char *measurand, const char *unit, const char *location, const char *phase); bool mo_addMeterValueInputString2(MO_Context *ctx, unsigned int evseId, int (*meterInput)(char *buf, size_t size, MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData); //Input for further MeterValue types with more options. See "MeterValue.h" for how to use it diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index 88e9662d..273be971 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -138,7 +138,7 @@ Connection *Context::getConnection() { #if MO_WS_USE != MO_WS_CUSTOM if (!connection) { // init default Connection implementation - connection = static_cast(makeDefaultConnection(connectionConfig)); + connection = static_cast(makeDefaultConnection(connectionConfig, ocppVersion)); if (!connection) { MO_DBG_ERR("OOM"); return nullptr; diff --git a/src/MicroOcpp/Core/Connection.cpp b/src/MicroOcpp/Core/Connection.cpp index f055a24e..4083768c 100644 --- a/src/MicroOcpp/Core/Connection.cpp +++ b/src/MicroOcpp/Core/Connection.cpp @@ -6,7 +6,7 @@ #include #include -using namespace MicroOcpp; +namespace MicroOcpp { Connection::Connection() { @@ -51,6 +51,8 @@ bool LoopbackConnection::isConnected() { return connected; } +} //namespace MicroOcpp + #if MO_WS_USE == MO_WS_ARDUINO #include @@ -64,7 +66,7 @@ class ArduinoWSClient : public Connection, public MemoryManaged { public: ArduinoWSClient(WebSocketsClient *wsock, bool transferOwnership) : MemoryManaged("WebSocketsClient"), wsock(wsock), isWSockOwner(transferOwnership) { - wsock->onEvent([callback](WStype_t type, uint8_t * payload, size_t length) { + wsock->onEvent([this](WStype_t type, uint8_t * payload, size_t length) { switch (type) { case WStype_DISCONNECTED: MO_DBG_INFO("Disconnected"); @@ -104,7 +106,7 @@ class ArduinoWSClient : public Connection, public MemoryManaged { } } - void loop() overide { + void loop() override { wsock->loop(); } @@ -132,13 +134,13 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { if (!config.backendUrl) { MO_DBG_ERR("invalid args"); - goto fail; + return nullptr; } wsock = new WebSocketsClient(); if (!wsock) { MO_DBG_ERR("OOM"); - goto fail; + return nullptr; } /* @@ -158,7 +160,7 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { isTLS = false; } else { MO_DBG_ERR("only ws:// and wss:// supported"); - return; + return nullptr; } //parse host, port @@ -168,7 +170,7 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { auto host = host_port.substr(0, host_port.find_first_of(':')); if (host.empty()) { MO_DBG_ERR("could not parse host: %s", url.c_str()); - return; + return nullptr; } uint16_t port = 0; auto port_str = host_port.substr(host.length()); @@ -180,12 +182,12 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { for (auto c = port_str.begin(); c != port_str.end(); c++) { if (*c < '0' || *c > '9') { MO_DBG_ERR("could not parse port: %s", url.c_str()); - return; + return nullptr; } auto p = port * 10U + (*c - '0'); if (p < port) { MO_DBG_ERR("could not parse port (overflow): %s", url.c_str()); - return; + return nullptr; } port = p; } @@ -223,32 +225,35 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { goto fail; } - if (config.isTLS) { + if (isTLS) { // server address, port, path and TLS certificate - webSocket->beginSslWithCA(host.c_str(), port, path.c_str(), config.CA_cert, ocppVersionStr); + wsock->beginSslWithCA(host.c_str(), port, path.c_str(), config.CA_cert, ocppVersionStr); } else { // server address, port, path - webSocket->begin(host.c_str(), port, path.c_str(), ocppVersionStr); + wsock->begin(host.c_str(), port, path.c_str(), ocppVersionStr); } // try ever 5000 again if connection has failed - webSocket->setReconnectInterval(5000); + wsock->setReconnectInterval(5000); // start heartbeat (optional) // ping server every 15000 ms // expect pong from server within 3000 ms // consider connection disconnected if pong is not received 2 times - webSocket->enableHeartbeat(15000, 3000, 2); //comment this one out to for specific OCPP servers - - size_t chargeBoxIdLen = config.chargeBoxId ? strlen(config.chargeBoxId) : 0; - size_t authorizationKeyLen = config.authorizationKey ? strlen(config.authorizationKey) : 0; + wsock->enableHeartbeat(15000, 3000, 2); //comment this one out to for specific OCPP servers + // add authentication data (optional) - if (config.authorizationKey && (authorizationKeyLen + chargeBoxIdLen >= 4)) { - webSocket->setAuthorization(config.chargeBoxId ? config.chargeBoxId : "", config.authorizationKey); + { + size_t chargeBoxIdLen = config.chargeBoxId ? strlen(config.chargeBoxId) : 0; + size_t authorizationKeyLen = config.authorizationKey ? strlen(config.authorizationKey) : 0; + + if (config.authorizationKey && (authorizationKeyLen + chargeBoxIdLen >= 4)) { + wsock->setAuthorization(config.chargeBoxId ? config.chargeBoxId : "", config.authorizationKey); + } } - connection = new ArduinoWSClient(webSocket, true); + connection = new ArduinoWSClient(wsock, true); if (!connection) { MO_DBG_ERR("OOM"); goto fail; @@ -259,6 +264,7 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { fail: delete connection; delete wsock; + return nullptr; } void freeDefaultConnection(Connection *connection) { diff --git a/src/MicroOcpp/Core/Connection.h b/src/MicroOcpp/Core/Connection.h index b88c5bf7..80511da7 100644 --- a/src/MicroOcpp/Core/Connection.h +++ b/src/MicroOcpp/Core/Connection.h @@ -106,9 +106,10 @@ typedef struct { #ifdef __cplusplus } //extern "C" +class WebSocketsClient; + namespace MicroOcpp { -class WebSocketsClient; Connection *makeArduinoWSClient(WebSocketsClient& arduinoWebsockets); //does not take ownership of arduinoWebsockets void freeArduinoWSClient(Connection *connection); diff --git a/src/MicroOcpp/Core/FilesystemAdapter.cpp b/src/MicroOcpp/Core/FilesystemAdapter.cpp index 85362c93..1d1f3f3d 100644 --- a/src/MicroOcpp/Core/FilesystemAdapter.cpp +++ b/src/MicroOcpp/Core/FilesystemAdapter.cpp @@ -449,7 +449,7 @@ int stat(const char *path, size_t *size) { } //end stat bool remove(const char *path) { - return USE_FS.remove(fn); + return USE_FS.remove(path); } int ftw(const char *path_prefix, int(*fn)(const char *fname, void *mo_user_data), void *mo_user_data) { @@ -457,7 +457,7 @@ int ftw(const char *path_prefix, int(*fn)(const char *fname, void *mo_user_data) #if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS auto dir = USE_FS.open(prefix); if (!dir) { - MO_DBG_ERR("cannot open root directory: " prefix); + MO_DBG_ERR("cannot open root directory: %s", prefix); return -1; } @@ -508,7 +508,7 @@ MO_File* open(const char *path, const char *mode) { MO_DBG_ERR("OOM"); return nullptr; } - *file = USE_FS.open(fn, mode); + *file = USE_FS.open(path, mode); if (!*file) { MO_DBG_ERR("open file failure"); goto fail; @@ -517,7 +517,7 @@ MO_File* open(const char *path, const char *mode) { MO_DBG_ERR("file failure"); goto fail; } - MO_DBG_DEBUG("File open successful: %s", fn); + MO_DBG_DEBUG("File open successful: %s", path); return reinterpret_cast(file); fail: if (file) { @@ -530,9 +530,9 @@ MO_File* open(const char *path, const char *mode) { bool close(MO_File *fileHandle) { auto file = reinterpret_cast(fileHandle); - bool success = file->close(); + file->close(); delete file; - return success; + return true; } size_t read(MO_File *fileHandle, char *buf, size_t len) { @@ -552,7 +552,7 @@ size_t write(MO_File *fileHandle, const char *buf, size_t len) { int seek(MO_File *fileHandle, size_t fpos) { auto file = reinterpret_cast(fileHandle); - return file->seek(offset); + return file->seek(fpos); } int useCount; @@ -562,15 +562,15 @@ MO_FilesystemOpt opt = MO_FS_OPT_DISABLE; } //namespace MicroOcpp MO_FilesystemAdapter *mo_makeDefaultFilesystemAdapter(MO_FilesystemConfig config) { - - char *prefix_copy = nullptr; - MO_FilesystemAdapter *filesystem = nullptr; if (config.opt == MO_FS_OPT_DISABLE) { MO_DBG_DEBUG("Access to filesystem not allowed by opt"); - goto fail; + return nullptr; } + char *prefix_copy = nullptr; + MO_FilesystemAdapter *filesystem = nullptr; + const char *prefix = config.path_prefix; size_t prefix_len = prefix ? strlen(prefix) : 0; if (prefix_len > 0) { @@ -583,7 +583,7 @@ MO_FilesystemAdapter *mo_makeDefaultFilesystemAdapter(MO_FilesystemConfig config (void)snprintf(prefix_copy, prefix_size, "%s", prefix); } - auto filesystem = static_cast(MO_MALLOC("Filesystem", sizeof(MO_FilesystemAdapter))); + filesystem = static_cast(MO_MALLOC("Filesystem", sizeof(MO_FilesystemAdapter))); if (!filesystem) { MO_DBG_ERR("OOM"); goto fail; @@ -663,7 +663,11 @@ void mo_freeDefaultFilesystemAdapter(MO_FilesystemAdapter *filesystem) { USE_FS.end(); MicroOcpp::ArduinoFS::opt = MO_FS_OPT_DISABLE; } - MO_FREE(filesystem->path_prefix); + + //the default FS adapter owns the path_prefix buf and needs to free it + char *path_prefix_buf = const_cast(filesystem->path_prefix); + MO_FREE(path_prefix_buf); + MO_FREE(filesystem); } diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 92a14587..80a4e297 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -498,7 +498,6 @@ void Ocpp201::AvailabilityServiceEvse::setAvailable(void *requesterId) { return; } } - MO_DBG_ERR("could not find unavailable requester"); } ChangeAvailabilityStatus Ocpp201::AvailabilityServiceEvse::changeAvailability(bool operative) { diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 918621bc..7702008d 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -31,13 +31,13 @@ #define MO_DIAG_PREAMBLE_SIZE 192U #define MO_DIAG_POSTAMBLE_SIZE 1024U +namespace MicroOcpp { + #if MO_USE_DIAGNOSTICS == MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 size_t defaultDiagnosticsReader(char *buf, size_t size, void *user_data); void defaultDiagnosticsOnClose(void *user_data); #endif -using namespace MicroOcpp; - DiagnosticsService::DiagnosticsService(Context& context) : MemoryManaged("v16/v201.Diagnostics.DiagnosticsService"), context(context), diagFileList(makeVector(getMemoryTag())) { } @@ -586,14 +586,10 @@ bool DiagnosticsService::setOnUpload( return true; } -namespace MicroOcpp { struct DiagnosticsReaderFtwData { DiagnosticsService *diagService; int ret; }; -} //namespace MicroOcpp - -using namespace MicroOcpp; bool DiagnosticsService::uploadDiagnostics() { @@ -1045,6 +1041,8 @@ void DiagnosticsService::setFtpServerCert(const char *cert) { this->ftpServerCert = cert; } +} //namespace MicroOcpp + #endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_DIAGNOSTICS #if MO_USE_DIAGNOSTICS == MO_DIAGNOSTICS_BUILTIN_MBEDTLS_ESP32 @@ -1053,8 +1051,8 @@ void DiagnosticsService::setFtpServerCert(const char *cert) { #include namespace MicroOcpp { + bool g_diagsSent = false; -} size_t defaultDiagnosticsReader(char *buf, size_t size, void*) { if (!g_diagsSent) { @@ -1085,4 +1083,6 @@ void defaultDiagnosticsOnClose(void*) { g_diagsSent = false; }; +} //namespace MicroOcpp + #endif //MO_USE_DIAGNOSTICS diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp index 4fe5f9aa..b6f6fe5b 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp @@ -25,13 +25,11 @@ #define MO_IGNORE_FW_RETR_DATE 0 #endif -using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +namespace MicroOcpp { +namespace Ocpp16 { #if MO_USE_FW_UPDATER != MO_FW_UPDATER_CUSTOM -namespace MicroOcpp { bool setupDefaultFwUpdater(FirmwareService *fwService); -} #endif FirmwareService::FirmwareService(Context& context) : MemoryManaged("v16.Firmware.FirmwareService"), context(context), clock(context.getClock()) { @@ -484,11 +482,14 @@ void FirmwareService::setFtpServerCert(const char *cert) { this->ftpServerCert = cert; } +} //namespace Ocpp16 +} //namespace MicroOcpp + #if MO_USE_FW_UPDATER == MO_FW_UPDATER_BUILTIN_ESP32 #include -bool MicroOcpp::setupDefaultFwUpdater(FirmwareService *fwService) { +bool MicroOcpp::Ocpp16::setupDefaultFwUpdater(MicroOcpp::Ocpp16::FirmwareService *fwService) { fwService->setDownloadFileWriter( [fwService] (const unsigned char *data, size_t size) -> size_t { if (!Update.isRunning()) { @@ -557,11 +558,11 @@ bool MicroOcpp::setupDefaultFwUpdater(FirmwareService *fwService) { return true; } -#elif MO_USE_FW_UPDATER == MO_FW_UPDATER_BUILTIN_ESP32 +#elif MO_USE_FW_UPDATER == MO_FW_UPDATER_BUILTIN_ESP8266 #include -bool MicroOcpp::setupDefaultFwUpdater(FirmwareService *fwService) { +bool MicroOcpp::Ocpp16::setupDefaultFwUpdater(MicroOcpp::Ocpp16::FirmwareService *fwService) { fwService->setOnInstall([fwService] (const char *location) { diff --git a/src/MicroOcpp/Model/Metering/MeteringServiceV201.cpp b/src/MicroOcpp/Model/Metering/MeteringServiceV201.cpp deleted file mode 100644 index 97376774..00000000 --- a/src/MicroOcpp/Model/Metering/MeteringServiceV201.cpp +++ /dev/null @@ -1,361 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include - -#if MO_ENABLE_V201 - -#include -#include -#include -#include -#include - -//helper function -namespace MicroOcpp { - -bool csvContains(const char *csv, const char *elem) { - - if (!csv || !elem) { - return false; - } - - size_t elemLen = strlen(elem); - - size_t sl = 0, sr = 0; - while (csv[sr]) { - while (csv[sr]) { - if (csv[sr] == ',') { - break; - } - sr++; - } - //csv[sr] is either ',' or '\0' - - if (sr - sl == elemLen && !strncmp(csv + sl, elem, sr - sl)) { - return true; - } - - if (csv[sr]) { - sr++; - } - sl = sr; - } - return false; -} - -} //namespace MicroOcpp - -using namespace MicroOcpp::Ocpp201; - -SampledValueProperties::SampledValueProperties() : MemoryManaged("v201.MeterValues.SampledValueProperties") { } -SampledValueProperties::SampledValueProperties(const SampledValueProperties& other) : - MemoryManaged(other.getMemoryTag()), - format(other.format), - measurand(other.measurand), - phase(other.phase), - location(other.location), - unitOfMeasureUnit(other.unitOfMeasureUnit), - unitOfMeasureMultiplier(other.unitOfMeasureMultiplier) { - -} - -void SampledValueProperties::setFormat(const char *format) {this->format = format;} -const char *SampledValueProperties::getFormat() const {return format;} -void SampledValueProperties::setMeasurand(const char *measurand) {this->measurand = measurand;} -const char *SampledValueProperties::getMeasurand() const {return measurand;} -void SampledValueProperties::setPhase(const char *phase) {this->phase = phase;} -const char *SampledValueProperties::getPhase() const {return phase;} -void SampledValueProperties::setLocation(const char *location) {this->location = location;} -const char *SampledValueProperties::getLocation() const {return location;} -void SampledValueProperties::setUnitOfMeasureUnit(const char *unitOfMeasureUnit) {this->unitOfMeasureUnit = unitOfMeasureUnit;} -const char *SampledValueProperties::getUnitOfMeasureUnit() const {return unitOfMeasureUnit;} -void SampledValueProperties::setUnitOfMeasureMultiplier(int unitOfMeasureMultiplier) {this->unitOfMeasureMultiplier = unitOfMeasureMultiplier;} -int SampledValueProperties::getUnitOfMeasureMultiplier() const {return unitOfMeasureMultiplier;} - -SampledValue::SampledValue(double value, ReadingContext readingContext, SampledValueProperties& properties) - : MemoryManaged("v201.MeterValues.SampledValue"), value(value), readingContext(readingContext), properties(properties) { - -} - -bool SampledValue::toJson(JsonDoc& out) { - - size_t unitOfMeasureElements = - (properties.getUnitOfMeasureUnit() ? 1 : 0) + - (properties.getUnitOfMeasureMultiplier() ? 1 : 0); - - out = initJsonDoc(getMemoryTag(), - JSON_OBJECT_SIZE( - 1 + //value - (readingContext != ReadingContext_SamplePeriodic ? 1 : 0) + - (properties.getMeasurand() ? 1 : 0) + - (properties.getPhase() ? 1 : 0) + - (properties.getLocation() ? 1 : 0) + - (unitOfMeasureElements ? 1 : 0) - ) + - (unitOfMeasureElements ? JSON_OBJECT_SIZE(unitOfMeasureElements) : 0) - ); - - out["value"] = value; - if (readingContext != ReadingContext_SamplePeriodic) - out["context"] = serializeReadingContext(readingContext); - if (properties.getMeasurand()) - out["measurand"] = properties.getMeasurand(); - if (properties.getPhase()) - out["phase"] = properties.getPhase(); - if (properties.getLocation()) - out["location"] = properties.getLocation(); - if (properties.getUnitOfMeasureUnit()) - out["unitOfMeasure"]["unit"] = properties.getUnitOfMeasureUnit(); - if (properties.getUnitOfMeasureMultiplier()) - out["unitOfMeasure"]["multiplier"] = properties.getUnitOfMeasureMultiplier(); - - return true; -} - -SampledValueInput::SampledValueInput(std::function valueInput, const SampledValueProperties& properties) - : MemoryManaged("v201.MeterValues.SampledValueInput"), valueInput(valueInput), properties(properties) { - -} - -SampledValue *SampledValueInput::takeSampledValue(ReadingContext readingContext) { - return new SampledValue(valueInput(readingContext), readingContext, properties); -} - -const SampledValueProperties& SampledValueInput::getProperties() { - return properties; -} - -uint8_t& SampledValueInput::getMeasurandTypeFlags() { - return measurandTypeFlags; -} - -MeterValue::MeterValue(const Timestamp& timestamp, SampledValue **sampledValue, size_t sampledValueSize) : - MemoryManaged("v201.MeterValues.MeterValue"), timestamp(timestamp), sampledValue(sampledValue), sampledValueSize(sampledValueSize) { - -} - -MeterValue::~MeterValue() { - for (size_t i = 0; i < sampledValueSize; i++) { - delete sampledValue[i]; - } - MO_FREE(sampledValue); -} - -bool MeterValue::toJson(JsonDoc& out) { - - size_t capacity = 0; - - for (size_t i = 0; i < sampledValueSize; i++) { - //just measure, discard sampledValueJson afterwards - JsonDoc sampledValueJson = initJsonDoc(getMemoryTag()); - sampledValue[i]->toJson(sampledValueJson); - capacity += sampledValueJson.capacity(); - } - - capacity += JSON_OBJECT_SIZE(2) + - JSONDATE_LENGTH + 1 + - JSON_ARRAY_SIZE(sampledValueSize); - - - out = initJsonDoc("v201.MeterValues.MeterValue", capacity); - - char timestampStr [JSONDATE_LENGTH + 1]; - timestamp.toJsonString(timestampStr, sizeof(timestampStr)); - - out["timestamp"] = timestampStr; - JsonArray sampledValueArray = out.createNestedArray("sampledValue"); - - for (size_t i = 0; i < sampledValueSize; i++) { - JsonDoc sampledValueJson = initJsonDoc(getMemoryTag()); - sampledValue[i]->toJson(sampledValueJson); - sampledValueArray.add(sampledValueJson); - } - - return true; -} - -const MicroOcpp::Timestamp& MeterValue::getTimestamp() { - return timestamp; -} - -MeteringServiceEvse::MeteringServiceEvse(Model& model, unsigned int evseId) - : MemoryManaged("v201.MeterValues.MeteringServiceEvse"), model(model), evseId(evseId), sampledValueInputs(makeVector(getMemoryTag())) { - - auto varService = model.getVariableService(); - - sampledDataTxStartedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxStartedMeasurands", ""); - sampledDataTxUpdatedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxUpdatedMeasurands", ""); - sampledDataTxEndedMeasurands = varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", ""); - alignedDataMeasurands = varService->declareVariable("AlignedDataCtrlr", "AlignedDataMeasurands", ""); -} - -void MeteringServiceEvse::addMeterValueInput(std::function valueInput, const SampledValueProperties& properties) { - sampledValueInputs.emplace_back(valueInput, properties); -} - -std::unique_ptr MeteringServiceEvse::takeMeterValue(Variable *measurands, uint16_t& trackMeasurandsWriteCount, size_t& trackInputsSize, uint8_t measurandsMask, ReadingContext readingContext) { - - if (measurands->getWriteCount() != trackMeasurandsWriteCount || - sampledValueInputs.size() != trackInputsSize) { - MO_DBG_DEBUG("Updating observed samplers due to config change or samplers added"); - for (size_t i = 0; i < sampledValueInputs.size(); i++) { - if (csvContains(measurands->getString(), sampledValueInputs[i].getProperties().getMeasurand())) { - sampledValueInputs[i].getMeasurandTypeFlags() |= measurandsMask; - } else { - sampledValueInputs[i].getMeasurandTypeFlags() &= ~measurandsMask; - } - } - - trackMeasurandsWriteCount = measurands->getWriteCount(); - trackInputsSize = sampledValueInputs.size(); - } - - size_t samplesSize = 0; - - for (size_t i = 0; i < sampledValueInputs.size(); i++) { - if (sampledValueInputs[i].getMeasurandTypeFlags() & measurandsMask) { - samplesSize++; - } - } - - if (samplesSize == 0) { - return nullptr; - } - - SampledValue **sampledValue = static_cast(MO_MALLOC(getMemoryTag(), samplesSize * sizeof(SampledValue*))); - if (!sampledValue) { - MO_DBG_ERR("OOM"); - return nullptr; - } - size_t samplesWritten = 0; - - bool memoryErr = false; - - for (size_t i = 0; i < sampledValueInputs.size(); i++) { - if (sampledValueInputs[i].getMeasurandTypeFlags() & measurandsMask) { - auto sample = sampledValueInputs[i].takeSampledValue(readingContext); - if (!sample) { - MO_DBG_ERR("OOM"); - memoryErr = true; - break; - } - sampledValue[samplesWritten++] = sample; - } - } - - std::unique_ptr meterValue = std::unique_ptr(new MeterValue(model.getClock().now(), sampledValue, samplesWritten)); - if (!meterValue) { - MO_DBG_ERR("OOM"); - memoryErr = true; - } - - if (memoryErr) { - if (!meterValue) { - //meterValue did not take ownership, so clean resources manually - for (size_t i = 0; i < samplesWritten; i++) { - delete sampledValue[i]; - } - delete sampledValue; - } - return nullptr; - } - - return meterValue; -} - -std::unique_ptr MeteringServiceEvse::takeTxStartedMeterValue(ReadingContext readingContext) { - return takeMeterValue(sampledDataTxStartedMeasurands, trackSampledDataTxStartedMeasurandsWriteCount, trackSampledValueInputsSizeTxStarted, MO_MEASURAND_TYPE_TXSTARTED, readingContext); -} -std::unique_ptr MeteringServiceEvse::takeTxUpdatedMeterValue(ReadingContext readingContext) { - return takeMeterValue(sampledDataTxUpdatedMeasurands, trackSampledDataTxUpdatedMeasurandsWriteCount, trackSampledValueInputsSizeTxUpdated, MO_MEASURAND_TYPE_TXUPDATED, readingContext); -} -std::unique_ptr MeteringServiceEvse::takeTxEndedMeterValue(ReadingContext readingContext) { - return takeMeterValue(sampledDataTxEndedMeasurands, trackSampledDataTxEndedMeasurandsWriteCount, trackSampledValueInputsSizeTxEnded, MO_MEASURAND_TYPE_TXENDED, readingContext); -} -std::unique_ptr MeteringServiceEvse::takeTriggeredMeterValues() { - return takeMeterValue(alignedDataMeasurands, trackAlignedDataMeasurandsWriteCount, trackSampledValueInputsSizeAligned, MO_MEASURAND_TYPE_ALIGNED, ReadingContext_Trigger); -} - -bool MeteringServiceEvse::existsMeasurand(const char *measurand, size_t len) { - for (size_t i = 0; i < sampledValueInputs.size(); i++) { - const char *sviMeasurand = sampledValueInputs[i].getProperties().getMeasurand(); - if (sviMeasurand && len == strlen(sviMeasurand) && !strncmp(sviMeasurand, measurand, len)) { - return true; - } - } - return false; -} - -namespace MicroOcpp { -namespace Ocpp201 { - -bool validateSelectString(const char *csl, void *userPtr) { - auto mService = static_cast(userPtr); - - bool isValid = true; - const char *l = csl; //the beginning of an entry of the comma-separated list - const char *r = l; //one place after the last character of the entry beginning with l - while (*l) { - if (*l == ',') { - l++; - continue; - } - r = l + 1; - while (*r != '\0' && *r != ',') { - r++; - } - bool found = false; - for (size_t evseId = 0; evseId < MO_NUM_EVSEID && mService->getEvse(evseId); evseId++) { - if (mService->getEvse(evseId)->existsMeasurand(l, (size_t) (r - l))) { - found = true; - break; - } - } - if (!found) { - isValid = false; - MO_DBG_WARN("could not find metering device for %.*s", (int) (r - l), l); - break; - } - l = r; - } - return isValid; -} - -} //namespace Ocpp201 -} //namespace MicroOcpp - -using namespace MicroOcpp::Ocpp201; - -MeteringService::MeteringService(Model& model, size_t numEvses) { - - auto varService = model.getVariableService(); - - //define factory defaults - varService->declareVariable("SampledDataCtrlr", "TxStartedMeasurands", ""); - varService->declareVariable("SampledDataCtrlr", "TxUpdatedMeasurands", ""); - varService->declareVariable("SampledDataCtrlr", "TxEndedMeasurands", ""); - varService->declareVariable("AlignedDataCtrlr", "AlignedDataMeasurands", ""); - - varService->registerValidator("SampledDataCtrlr", "TxStartedMeasurands", validateSelectString, this); - varService->registerValidator("SampledDataCtrlr", "TxUpdatedMeasurands", validateSelectString, this); - varService->registerValidator("SampledDataCtrlr", "TxEndedMeasurands", validateSelectString, this); - varService->registerValidator("AlignedDataCtrlr", "AlignedDataMeasurands", validateSelectString, this); - - for (size_t evseId = 0; evseId < std::min(numEvses, (size_t)MO_NUM_EVSEID); evseId++) { - evses[evseId] = new MeteringServiceEvse(model, evseId); - } -} - -MeteringService::~MeteringService() { - for (size_t evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) { - delete evses[evseId]; - } -} - -MeteringServiceEvse *MeteringService::getEvse(unsigned int evseId) { - return evses[evseId]; -} - -#endif diff --git a/src/MicroOcpp/Model/Metering/MeteringServiceV201.h b/src/MicroOcpp/Model/Metering/MeteringServiceV201.h deleted file mode 100644 index e8385f78..00000000 --- a/src/MicroOcpp/Model/Metering/MeteringServiceV201.h +++ /dev/null @@ -1,153 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -/* - * Implementation of the UCs E01 - E12 - */ - -#ifndef MO_METERVALUESV201_H -#define MO_METERVALUESV201_H - -#include - -#if MO_ENABLE_V201 - -#include - -#include -#include -#include -#include - -namespace MicroOcpp { - -class Variable; - -namespace Ocpp201 { - -class Model; - -class SampledValueProperties : public MemoryManaged { -private: - const char *format = nullptr; - const char *measurand = nullptr; - const char *phase = nullptr; - const char *location = nullptr; - const char *unitOfMeasureUnit = nullptr; - int unitOfMeasureMultiplier = 0; - -public: - SampledValueProperties(); - SampledValueProperties(const SampledValueProperties&); - - void setFormat(const char *format); //zero-copy - const char *getFormat() const; - void setMeasurand(const char *measurand); //zero-copy - const char *getMeasurand() const; - void setPhase(const char *phase); //zero-copy - const char *getPhase() const; - void setLocation(const char *location); //zero-copy - const char *getLocation() const; - void setUnitOfMeasureUnit(const char *unitOfMeasureUnit); //zero-copy - const char *getUnitOfMeasureUnit() const; - void setUnitOfMeasureMultiplier(int unitOfMeasureMultiplier); - int getUnitOfMeasureMultiplier() const; -}; - -class SampledValue : public MemoryManaged { -private: - double value = 0.; - ReadingContext readingContext; - SampledValueProperties& properties; - //std::unique_ptr ... this could be an abstract type -public: - SampledValue(double value, ReadingContext readingContext, SampledValueProperties& properties); - - bool toJson(JsonDoc& out); -}; - -#define MO_MEASURAND_TYPE_TXSTARTED (1 << 0) -#define MO_MEASURAND_TYPE_TXUPDATED (1 << 1) -#define MO_MEASURAND_TYPE_TXENDED (1 << 2) -#define MO_MEASURAND_TYPE_ALIGNED (1 << 3) - -class SampledValueInput : public MemoryManaged { -private: - std::function valueInput; - SampledValueProperties properties; - - uint8_t measurandTypeFlags = 0; -public: - SampledValueInput(std::function valueInput, const SampledValueProperties& properties); - SampledValue *takeSampledValue(ReadingContext readingContext); - - const SampledValueProperties& getProperties(); - - uint8_t& getMeasurandTypeFlags(); -}; - -class MeterValue : public MemoryManaged { -private: - Timestamp timestamp; - SampledValue **sampledValue = nullptr; - size_t sampledValueSize = 0; -public: - MeterValue(const Timestamp& timestamp, SampledValue **sampledValue, size_t sampledValueSize); - ~MeterValue(); - - bool toJson(JsonDoc& out); - - const Timestamp& getTimestamp(); -}; - -class MeteringServiceEvse : public MemoryManaged { -private: - Model& model; - const unsigned int evseId; - - Vector sampledValueInputs; - - Variable *sampledDataTxStartedMeasurands = nullptr; - Variable *sampledDataTxUpdatedMeasurands = nullptr; - Variable *sampledDataTxEndedMeasurands = nullptr; - Variable *alignedDataMeasurands = nullptr; - - size_t trackSampledValueInputsSizeTxStarted = 0; - size_t trackSampledValueInputsSizeTxUpdated = 0; - size_t trackSampledValueInputsSizeTxEnded = 0; - size_t trackSampledValueInputsSizeAligned = 0; - uint16_t trackSampledDataTxStartedMeasurandsWriteCount = -1; - uint16_t trackSampledDataTxUpdatedMeasurandsWriteCount = -1; - uint16_t trackSampledDataTxEndedMeasurandsWriteCount = -1; - uint16_t trackAlignedDataMeasurandsWriteCount = -1; - - std::unique_ptr takeMeterValue(Variable *measurands, uint16_t& trackMeasurandsWriteCount, size_t& trackInputsSize, uint8_t measurandsMask, ReadingContext context); -public: - MeteringServiceEvse(Model& model, unsigned int evseId); - - void addMeterValueInput(std::function valueInput, const SampledValueProperties& properties); - - std::unique_ptr takeTxStartedMeterValue(ReadingContext context = ReadingContext_TransactionBegin); - std::unique_ptr takeTxUpdatedMeterValue(ReadingContext context = ReadingContext_SamplePeriodic); - std::unique_ptr takeTxEndedMeterValue(ReadingContext context); - std::unique_ptr takeTriggeredMeterValues(); - - bool existsMeasurand(const char *measurand, size_t len); -}; - -class MeteringService : public MemoryManaged { -private: - MeteringServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; -public: - MeteringService(Model& model, size_t numEvses); - ~MeteringService(); - - MeteringServiceEvse *getEvse(unsigned int evseId); -}; - -} -} - -#endif -#endif diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index 9a6be366..cf1eef0f 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -35,7 +35,7 @@ bool removeProfile(MO_FilesystemAdapter *filesystem, unsigned int evseId, Chargi using namespace::MicroOcpp; SmartChargingServiceEvse::SmartChargingServiceEvse(Context& context, SmartChargingService& scService, unsigned int evseId) : - MemoryManaged("v16/v201.SmartCharging.SmartChargingServiceEvse"), context(context), clock(context.getClock()), scService{scService}, evseId{evseId} { + MemoryManaged("v16/v201.SmartCharging.SmartChargingServiceEvse"), context(context), clock(context.getClock()), scService(scService), evseId(evseId) { mo_chargeRate_init(&trackLimitOutput); } diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index 07789a4c..5090e82e 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -1162,6 +1162,11 @@ bool TransactionService::setup() { return false; } + if (!txStore.setup()) { + MO_DBG_ERR("setup failure"); + return false; + } + auto varService = context.getModel201().getVariableService(); if (!varService) { return false; diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.h b/src/MicroOcpp/Model/Transactions/TransactionStore.h index 20215093..dac4755f 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.h +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.h @@ -49,7 +49,7 @@ bool remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int #endif #ifndef MO_TXEVENTRECORD_DIGITS -#define MO_TXEVENTRECORD_DIGITS 10 //digits needed to print MO_TXEVENTRECORD_SIZE-1 (="9", i.e. 1 digit) +#define MO_TXEVENTRECORD_DIGITS 1 //digits needed to print MO_TXEVENTRECORD_SIZE-1 (="9", i.e. 1 digit) #endif namespace MicroOcpp { diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index 3e825718..b7ceb7a5 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -45,14 +45,16 @@ std::unique_ptr BootNotification::createReq() { #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 if (ocppVersion == MO_OCPP_V201) { - auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(2)); + auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(2)); JsonObject payload = doc->to(); - if (bnData.chargePointSerialNumber) {payload["serialNumber"] = bnData.chargePointSerialNumber;} - if (bnData.chargePointModel) {payload["model"] = bnData.chargePointModel;} - if (bnData.chargePointVendor) {payload["vendorName"] = bnData.chargePointVendor;} - if (bnData.firmwareVersion) {payload["firmwareVersion"] = bnData.firmwareVersion;} + payload["reason"] = "Unknown"; + JsonObject chargingStation = payload.createNestedObject("chargingStation"); + if (bnData.chargePointSerialNumber) {chargingStation["serialNumber"] = bnData.chargePointSerialNumber;} + if (bnData.chargePointModel) {chargingStation["model"] = bnData.chargePointModel;} + if (bnData.chargePointVendor) {chargingStation["vendorName"] = bnData.chargePointVendor;} + if (bnData.firmwareVersion) {chargingStation["firmwareVersion"] = bnData.firmwareVersion;} if (bnData.iccid || bnData.imsi) { - JsonObject modem = payload.createNestedObject("modem"); + JsonObject modem = chargingStation.createNestedObject("modem"); if (bnData.iccid) {modem["iccid"] = bnData.iccid;} if (bnData.imsi) {modem["imsi"] = bnData.imsi;} } diff --git a/src/MicroOcpp/Operations/BootNotification.h b/src/MicroOcpp/Operations/BootNotification.h index 476cdb1a..abe85b9c 100644 --- a/src/MicroOcpp/Operations/BootNotification.h +++ b/src/MicroOcpp/Operations/BootNotification.h @@ -11,11 +11,6 @@ #if MO_ENABLE_V16 || MO_ENABLE_V201 -#define CP_MODEL_LEN_MAX CiString20TypeLen -#define CP_SERIALNUMBER_LEN_MAX CiString25TypeLen -#define CP_VENDOR_LEN_MAX CiString20TypeLen -#define FW_VERSION_LEN_MAX CiString50TypeLen - namespace MicroOcpp { class Context; diff --git a/src/MicroOcpp/Platform.h b/src/MicroOcpp/Platform.h index c7dca053..95d1ed92 100644 --- a/src/MicroOcpp/Platform.h +++ b/src/MicroOcpp/Platform.h @@ -35,7 +35,7 @@ uint32_t (*getDefaultRngCb())(); #endif #ifndef MO_ENABLE_MBEDTLS -#define MO_ENABLE_MBEDTLS 1 +#define MO_ENABLE_MBEDTLS 0 #endif #endif diff --git a/tests/benchmarks/firmware_size/main.cpp b/tests/benchmarks/firmware_size/main.cpp index 6e849b8d..b296c1ce 100644 --- a/tests/benchmarks/firmware_size/main.cpp +++ b/tests/benchmarks/firmware_size/main.cpp @@ -3,66 +3,53 @@ // MIT License #include -#include -#include -#include - -MicroOcpp::LoopbackConnection g_loopback; void setup() { - ocpp_deinitialize(); - -#if MO_ENABLE_V201 - mocpp_initialize(g_loopback, ChargerCredentials::v201(),MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail),true,MicroOcpp::ProtocolVersion(2,0,1)); -#else - mocpp_initialize(g_loopback, ChargerCredentials()); -#endif - - ocpp_beginTransaction(""); - ocpp_beginTransaction_authorized("",""); - ocpp_endTransaction("",""); - ocpp_endTransaction_authorized("",""); - ocpp_isTransactionActive(); - ocpp_isTransactionRunning(); - ocpp_getTransactionIdTag(); - ocpp_getTransaction(); - ocpp_ocppPermitsCharge(); - ocpp_getChargePointStatus(); - ocpp_setConnectorPluggedInput([] () {return false;}); - ocpp_setEnergyMeterInput([] () {return 0;}); - ocpp_setPowerMeterInput([] () {return 0.f;}); - ocpp_setSmartChargingPowerOutput([] (float) {}); - ocpp_setSmartChargingCurrentOutput([] (float) {}); - ocpp_setSmartChargingOutput([] (float,float,int) {}); - ocpp_setEvReadyInput([] () {return false;}); - ocpp_setEvseReadyInput([] () {return false;}); - ocpp_addErrorCodeInput([] () {return (const char*)nullptr;}); - addErrorDataInput([] () {return MicroOcpp::ErrorData("");}); - ocpp_addMeterValueInputFloat([] () {return 0.f;},"","","",""); - ocpp_setOccupiedInput([] () {return false;}); - ocpp_setStartTxReadyInput([] () {return false;}); - ocpp_setStopTxReadyInput([] () {return false;}); - ocpp_setTxNotificationOutput([] (OCPP_Transaction*, TxNotification) {}); + mo_deinitialize(); + + mo_initialize(); + + mo_setup(); + + mo_beginTransaction(""); + mo_beginTransaction_authorized("",""); + mo_endTransaction("",""); + mo_endTransaction_authorized("",""); + mo_isTransactionActive(); + mo_isTransactionRunning(); + mo_getTransactionIdTag(); + mo_v16_getTransactionId(); + mo_v201_getTransactionId(); + mo_ocppPermitsCharge(); + mo_getChargePointStatus(); + mo_setConnectorPluggedInput([] () {return false;}); + mo_setEnergyMeterInput([] () {return 0;}); + mo_setPowerMeterInput([] () {return 0.f;}); + mo_setSmartChargingPowerOutput([] (float) {}); + mo_setSmartChargingCurrentOutput([] (float) {}); + mo_setEvReadyInput([] () {return false;}); + mo_setEvseReadyInput([] () {return false;}); + mo_addErrorCodeInput([] () {return (const char*)nullptr;}); + mo_v16_addErrorDataInput(mo_getApiContext(), 0, [] (unsigned int, void*) {return MO_ErrorData();}, NULL); + mo_addMeterValueInputFloat([] () {return 0.f;}, NULL, NULL, NULL, NULL); + mo_setOccupiedInput([] () {return false;}); + mo_setStartTxReadyInput([] () {return false;}); + mo_setStopTxReadyInput([] () {return false;}); + mo_setTxNotificationOutput([] (MO_TxNotification) {}); #if MO_ENABLE_CONNECTOR_LOCK - ocpp_setOnUnlockConnectorInOut([] () {return UnlockConnectorResult_UnlockFailed;}); + mo_setOnUnlockConnector([] () {return MO_UnlockConnectorResult_UnlockFailed;}); #endif - isOperative(); - setOnResetNotify([] (bool) {return false;}); - setOnResetExecute([] (bool) {return false;}); - getFirmwareService()->getFirmwareStatus(); - getDiagnosticsService()->getDiagnosticsStatus(); - -#if MO_ENABLE_CERT_MGMT - setCertificateStore(nullptr); -#endif + mo_isOperative(); + mo_v16_setOnResetNotify([] (bool) {return false;}); + mo_setOnResetExecute([] () {}); - getOcppContext(); + mo_getContext(); } void loop() { - mocpp_loop(); + mo_loop(); } diff --git a/tests/benchmarks/firmware_size/platformio.ini b/tests/benchmarks/firmware_size/platformio.ini index 121a8de5..9873f36f 100644 --- a/tests/benchmarks/firmware_size/platformio.ini +++ b/tests/benchmarks/firmware_size/platformio.ini @@ -10,7 +10,7 @@ lib_deps = bblanchon/ArduinoJson@6.20.1 build_flags= -D MO_DBG_LEVEL=MO_DL_NONE ; don't take debug messages into account - -D MO_CUSTOM_WS + -D MO_WS_USE=MO_WS_CUSTOM [env:v16] platform = ${common.platform} From 42f7950420f96bb08d5660c1fa07a66d66bda694 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 22 Jun 2025 23:28:50 +0200 Subject: [PATCH 10/50] bugfixes --- src/MicroOcpp/Model/Transactions/TransactionService201.cpp | 2 +- src/MicroOcpp/Operations/RequestStopTransaction.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index 5090e82e..a3dc0d8c 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -287,7 +287,7 @@ void TransactionServiceEvse::loop() { dtTxBegin = txService.evConnectionTimeOutInt->getInt(); } - if (transaction->active && + if (transaction->active && !transaction->started && transaction->evConnectionTimeoutListen && transaction->beginTimestamp.isDefined() && txService.evConnectionTimeOutInt->getInt() > 0 && diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.cpp b/src/MicroOcpp/Operations/RequestStopTransaction.cpp index ee78b96d..21569046 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStopTransaction.cpp @@ -23,7 +23,7 @@ void RequestStopTransaction::processReq(JsonObject payload) { if (!payload.containsKey("transactionId") || !payload["transactionId"].is() || - strlen(payload["transactionId"].as()) + 1 >= MO_TXID_SIZE) { + strlen(payload["transactionId"].as()) + 1 > MO_TXID_SIZE) { errorCode = "FormationViolation"; return; } From a7107962a6feaf508a0bb431f54b913e11ec0e0c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 23 Jun 2025 00:18:57 +0200 Subject: [PATCH 11/50] bugfixes --- src/MicroOcpp.cpp | 7 ++++--- src/MicroOcpp.h | 5 +++-- src/MicroOcpp/Core/MessageService.cpp | 4 +++- src/MicroOcpp/Core/MessageService.h | 7 ++++--- src/MicroOcpp/Model/Boot/BootService.cpp | 2 +- .../Model/FirmwareManagement/FirmwareService.cpp | 2 +- src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp | 2 +- src/MicroOcpp/Operations/Authorize.cpp | 4 ++-- src/MicroOcpp/Operations/Authorize.h | 4 ++-- src/MicroOcpp/Operations/BootNotification.cpp | 2 +- src/MicroOcpp/Operations/BootNotification.h | 2 +- src/MicroOcpp/Operations/CustomOperation.cpp | 7 ++++++- src/MicroOcpp/Operations/CustomOperation.h | 12 +++++++----- src/MicroOcpp/Operations/Heartbeat.cpp | 2 +- src/MicroOcpp/Operations/Heartbeat.h | 2 +- src/MicroOcpp/Operations/StartTransaction.cpp | 2 +- src/MicroOcpp/Operations/StartTransaction.h | 2 +- src/MicroOcpp/Operations/StopTransaction.cpp | 2 +- src/MicroOcpp/Operations/StopTransaction.h | 2 +- src/MicroOcpp/Operations/TransactionEvent.cpp | 10 +++++----- src/MicroOcpp/Operations/TransactionEvent.h | 4 ++-- 21 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 4142f448..22d2230f 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -2991,8 +2991,9 @@ bool mo_sendRequest(MO_Context *ctx, const char *operationType, //Set custom operation handler for incoming reqeusts and bypass MO business logic bool mo_setRequestHandler(MO_Context *ctx, const char *operationType, - void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), - int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), + void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData), + int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData), + void (*finally)(const char *operationType, void *userStatus, void *userData), void *userData) { if (!ctx) { @@ -3001,7 +3002,7 @@ bool mo_setRequestHandler(MO_Context *ctx, const char *operationType, } auto context = mo_getContext2(ctx); - bool success = context->getMessageService().registerOperation(operationType, onRequest, writeResponse, userData); + bool success = context->getMessageService().registerOperation(operationType, onRequest, writeResponse, finally, userData); if (!success) { MO_DBG_ERR("could not register operation %s", operationType); } diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index b7198291..d3f0ea66 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -643,8 +643,9 @@ bool mo_sendRequest(MO_Context *ctx, //Set custom operation handler for incoming reqeusts and bypass MO business logic bool mo_setRequestHandler(MO_Context *ctx, const char *operationType, - void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), - int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), + void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData), + int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData), + void (*finally)(const char *operationType, void *userStatus, void *userData), void *userData); //Sniff incoming requests without control over the response diff --git a/src/MicroOcpp/Core/MessageService.cpp b/src/MicroOcpp/Core/MessageService.cpp index e3a69e14..d5accd9a 100644 --- a/src/MicroOcpp/Core/MessageService.cpp +++ b/src/MicroOcpp/Core/MessageService.cpp @@ -224,7 +224,7 @@ bool MessageService::registerOperation(const char *operationType, Operation* (*c return appendEntry(operationRegistry, entry); } -bool MessageService::registerOperation(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), void *userData) { +bool MessageService::registerOperation(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData), void (*finally)(const char *operationType, void *userStatus, void *userData), void *userData) { bool exists = false; @@ -241,6 +241,7 @@ bool MessageService::registerOperation(const char *operationType, void (*onReque entry.userData = userData; entry.onRequest = onRequest; entry.writeResponse = writeResponse; + entry.finally = finally; MO_DBG_DEBUG("registered operation %s", operationType); return appendEntry(operationRegistry2, entry); @@ -297,6 +298,7 @@ std::unique_ptr MessageService::createRequest(const char *operationType entry->operationType, entry->onRequest, entry->writeResponse, + entry->finally, entry->userData)) { MO_DBG_ERR("create operation failure"); delete customOperation; diff --git a/src/MicroOcpp/Core/MessageService.h b/src/MicroOcpp/Core/MessageService.h index 3e3ffe3d..aa73696e 100644 --- a/src/MicroOcpp/Core/MessageService.h +++ b/src/MicroOcpp/Core/MessageService.h @@ -32,8 +32,9 @@ struct OperationCreator { struct CustomOperationCreator { const char *operationType = nullptr; void *userData = nullptr; - void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData) = nullptr; - int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData) = nullptr; + void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData) = nullptr; + int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) = nullptr; + void (*finally)(const char *operationType, void *userStatus, void *userData) = nullptr; }; struct OperationListener { @@ -81,7 +82,7 @@ class MessageService : public MemoryManaged { // handle Requests from the OCPP Server bool registerOperation(const char *operationType, Operation* (*createOperationCb)(Context& context)); - bool registerOperation(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), void *userData = nullptr); + bool registerOperation(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData), void (*finally)(const char *operationType, void *userStatus, void *userData) = nullptr, void *userData = nullptr); bool setOnReceiveRequest(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, void *userData), void *userData = nullptr); bool setOnSendConf(const char *operationType, void (*onConfirmation)(const char *operationType, const char *payloadJson, void *userData), void *userData = nullptr); diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index 1f66ea56..f59bc2e2 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -153,7 +153,7 @@ bool BootService::setup() { #endif //MO_ENABLE_V201 #if MO_ENABLE_MOCK_SERVER - context.getMessageService().registerOperation("BootNotification", nullptr, BootNotification::writeMockConf, reinterpret_cast(&context)); + context.getMessageService().registerOperation("BootNotification", nullptr, BootNotification::writeMockConf, nullptr, reinterpret_cast(&context)); #endif //MO_ENABLE_MOCK_SERVER return true; diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp index b6f6fe5b..fe6190e7 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp @@ -78,7 +78,7 @@ bool FirmwareService::setup() { return new Ocpp16::UpdateFirmware(context, *context.getModel16().getFirmwareService());}); #if MO_ENABLE_MOCK_SERVER - context.getMessageService().registerOperation("FirmwareStatusNotification", nullptr, nullptr); + context.getMessageService().registerOperation("FirmwareStatusNotification", nullptr, nullptr, nullptr); #endif //MO_ENABLE_MOCK_SERVER auto rcService = context.getModel16().getRemoteControlService(); diff --git a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp index e48f0a5b..f8c2addd 100644 --- a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp +++ b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp @@ -72,7 +72,7 @@ bool HeartbeatService::setup() { lastHeartbeat = context.getClock().getUptime(); #if MO_ENABLE_MOCK_SERVER - context.getMessageService().registerOperation("Heartbeat", nullptr, Heartbeat::writeMockConf, reinterpret_cast(&context)); + context.getMessageService().registerOperation("Heartbeat", nullptr, Heartbeat::writeMockConf, nullptr, reinterpret_cast(&context)); #endif //MO_ENABLE_MOCK_SERVER auto rcService = context.getModel16().getRemoteControlService(); diff --git a/src/MicroOcpp/Operations/Authorize.cpp b/src/MicroOcpp/Operations/Authorize.cpp index e15e18d8..65d7ab81 100644 --- a/src/MicroOcpp/Operations/Authorize.cpp +++ b/src/MicroOcpp/Operations/Authorize.cpp @@ -48,7 +48,7 @@ void Ocpp16::Authorize::processConf(JsonObject payload){ } #if MO_ENABLE_MOCK_SERVER -int Ocpp16::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { +int Ocpp16::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; (void)userData; return snprintf(buf, size, "{\"idTagInfo\":{\"status\":\"Accepted\"}}"); @@ -92,7 +92,7 @@ void Ocpp201::Authorize::processConf(JsonObject payload){ } #if MO_ENABLE_MOCK_SERVER -int Ocpp201::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { +int Ocpp201::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; (void)userData; return snprintf(buf, size, "{\"idTokenInfo\":{\"status\":\"Accepted\"}}"); diff --git a/src/MicroOcpp/Operations/Authorize.h b/src/MicroOcpp/Operations/Authorize.h index 989305d5..5ae78a3c 100644 --- a/src/MicroOcpp/Operations/Authorize.h +++ b/src/MicroOcpp/Operations/Authorize.h @@ -30,7 +30,7 @@ class Authorize : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; #if MO_ENABLE_MOCK_SERVER - static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData); #endif }; @@ -63,7 +63,7 @@ class Authorize : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; #if MO_ENABLE_MOCK_SERVER - static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData); #endif }; diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index b7ceb7a5..6105d1cb 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -112,7 +112,7 @@ void BootNotification::processConf(JsonObject payload) { } #if MO_ENABLE_MOCK_SERVER -int BootNotification::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { +int BootNotification::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; auto& context = *reinterpret_cast(userData); diff --git a/src/MicroOcpp/Operations/BootNotification.h b/src/MicroOcpp/Operations/BootNotification.h index abe85b9c..043a3500 100644 --- a/src/MicroOcpp/Operations/BootNotification.h +++ b/src/MicroOcpp/Operations/BootNotification.h @@ -39,7 +39,7 @@ class BootNotification : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} #if MO_ENABLE_MOCK_SERVER - static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData); #endif }; diff --git a/src/MicroOcpp/Operations/CustomOperation.cpp b/src/MicroOcpp/Operations/CustomOperation.cpp index 370b8f02..c5b8b5b1 100644 --- a/src/MicroOcpp/Operations/CustomOperation.cpp +++ b/src/MicroOcpp/Operations/CustomOperation.cpp @@ -82,6 +82,10 @@ CustomOperation::CustomOperation() : MemoryManaged("v16/v201.CustomOperation") { } CustomOperation::~CustomOperation() { + if (finally) { + finally(operationType, userStatus, userData); + finally = nullptr; + } MO_FREE(request); request = nullptr; } @@ -111,11 +115,12 @@ bool CustomOperation::setupEvseInitiated(const char *operationType, const char * } //for operations receied from remote -bool CustomOperation::setupCsmsInitiated(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), void *userData) { +bool CustomOperation::setupCsmsInitiated(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData), void (*finally)(const char *operationType, void *userStatus, void *userData), void *userData) { this->operationType = operationType; this->onRequest = onRequest; this->writeResponse = writeResponse; this->userData = userData; + this->finally = finally; return true; } diff --git a/src/MicroOcpp/Operations/CustomOperation.h b/src/MicroOcpp/Operations/CustomOperation.h index fbf3d518..0f2272e9 100644 --- a/src/MicroOcpp/Operations/CustomOperation.h +++ b/src/MicroOcpp/Operations/CustomOperation.h @@ -13,15 +13,16 @@ class CustomOperation : public Operation, public MemoryManaged { private: const char *operationType = nullptr; void *userData = nullptr; - int userStatus = 0; //`onRequest` cb can write this number and pass status from `onRequest` to `writeResponse` + void *userStatus = nullptr; //`onRequest` cb can write this number and pass status from `onRequest` to `writeResponse` //Operation initiated on this EVSE char *request = nullptr; void (*onResponse)(const char *payloadJson, void *userData) = nullptr; //Operation initiated by server - void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData) = nullptr; - int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData) = nullptr; + void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData) = nullptr; + int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) = nullptr; + void (*finally)(const char *operationType, void *userStatus, void *userData) = nullptr; public: CustomOperation(); @@ -36,8 +37,9 @@ class CustomOperation : public Operation, public MemoryManaged { //for operations receied from remote bool setupCsmsInitiated(const char *operationType, - void (*onRequest)(const char *operationType, const char *payloadJson, int *userStatus, void *userData), - int (*writeResponse)(const char *operationType, char *buf, size_t size, int userStatus, void *userData), + void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData), + int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData), + void (*finally)(const char *operationType, void *userStatus, void *userData), void *userData); const char* getOperationType() override; diff --git a/src/MicroOcpp/Operations/Heartbeat.cpp b/src/MicroOcpp/Operations/Heartbeat.cpp index dff78d89..878cf5d3 100644 --- a/src/MicroOcpp/Operations/Heartbeat.cpp +++ b/src/MicroOcpp/Operations/Heartbeat.cpp @@ -40,7 +40,7 @@ void Heartbeat::processConf(JsonObject payload) { } #if MO_ENABLE_MOCK_SERVER -int Heartbeat::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { +int Heartbeat::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; auto& context = *reinterpret_cast(userData); diff --git a/src/MicroOcpp/Operations/Heartbeat.h b/src/MicroOcpp/Operations/Heartbeat.h index e22a4f83..30a7add3 100644 --- a/src/MicroOcpp/Operations/Heartbeat.h +++ b/src/MicroOcpp/Operations/Heartbeat.h @@ -27,7 +27,7 @@ class Heartbeat : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; #if MO_ENABLE_MOCK_SERVER - static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData); #endif }; diff --git a/src/MicroOcpp/Operations/StartTransaction.cpp b/src/MicroOcpp/Operations/StartTransaction.cpp index 5e321dd2..d3a5513b 100644 --- a/src/MicroOcpp/Operations/StartTransaction.cpp +++ b/src/MicroOcpp/Operations/StartTransaction.cpp @@ -87,7 +87,7 @@ void StartTransaction::processConf(JsonObject payload) { } #if MO_ENABLE_MOCK_SERVER -int StartTransaction::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { +int StartTransaction::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; (void)userData; static int uniqueTxId = 1000; diff --git a/src/MicroOcpp/Operations/StartTransaction.h b/src/MicroOcpp/Operations/StartTransaction.h index c6ee1784..fa29f9dc 100644 --- a/src/MicroOcpp/Operations/StartTransaction.h +++ b/src/MicroOcpp/Operations/StartTransaction.h @@ -36,7 +36,7 @@ class StartTransaction : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; #if MO_ENABLE_MOCK_SERVER - static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData); #endif }; diff --git a/src/MicroOcpp/Operations/StopTransaction.cpp b/src/MicroOcpp/Operations/StopTransaction.cpp index 53cdbd5a..4a5d4753 100644 --- a/src/MicroOcpp/Operations/StopTransaction.cpp +++ b/src/MicroOcpp/Operations/StopTransaction.cpp @@ -100,7 +100,7 @@ void StopTransaction::processConf(JsonObject payload) { } #if MO_ENABLE_MOCK_SERVER -int StopTransaction::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { +int StopTransaction::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; (void)userData; return snprintf(buf, size, "{\"idTagInfo\":{\"status\":\"Accepted\"}}"); diff --git a/src/MicroOcpp/Operations/StopTransaction.h b/src/MicroOcpp/Operations/StopTransaction.h index 3572d5e1..6844d778 100644 --- a/src/MicroOcpp/Operations/StopTransaction.h +++ b/src/MicroOcpp/Operations/StopTransaction.h @@ -35,7 +35,7 @@ class StopTransaction : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; #if MO_ENABLE_MOCK_SERVER - static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData); #endif }; diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp index 93b878e4..97ef2022 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.cpp +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -135,22 +135,22 @@ void TransactionEvent::processConf(JsonObject payload) { } #if MO_ENABLE_MOCK_SERVER -void TransactionEvent::onRequestMock(const char *operationType, const char *payloadJson, int *userStatus, void *userData) { +void TransactionEvent::onRequestMock(const char *operationType, const char *payloadJson, void **userStatus, void *userData) { //if request contains `"idToken"`, then send response with `"idTokenInfo"`. Instead of building the //full JSON DOM, just search for the substring if (strstr(payloadJson, "\"idToken\"")) { //found, pass status to `writeMockConf` - *userStatus = 1; + *userStatus = reinterpret_cast(1); } else { //not found, pass status to `writeMockConf` - *userStatus = 0; + *userStatus = reinterpret_cast(0); } } -int TransactionEvent::writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData) { +int TransactionEvent::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userData; - if (userStatus == 1) { + if (userStatus == reinterpret_cast(1)) { return snprintf(buf, size, "{\"idTokenInfo\":{\"status\":\"Accepted\"}}"); } else { return snprintf(buf, size, "{}"); diff --git a/src/MicroOcpp/Operations/TransactionEvent.h b/src/MicroOcpp/Operations/TransactionEvent.h index 8de32c26..7e513126 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.h +++ b/src/MicroOcpp/Operations/TransactionEvent.h @@ -37,8 +37,8 @@ class TransactionEvent : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} #if MO_ENABLE_MOCK_SERVER - static void onRequestMock(const char *operationType, const char *payloadJson, int *userStatus, void *userData); - static int writeMockConf(const char *operationType, char *buf, size_t size, int userStatus, void *userData); + static void onRequestMock(const char *operationType, const char *payloadJson, void **userStatus, void *userData); + static int writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData); #endif }; From 49c67108ad1a060f7402c15c426708c991b7435e Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:43:01 +0200 Subject: [PATCH 12/50] API update --- src/MicroOcpp.cpp | 14 +++---- src/MicroOcpp.h | 6 +-- src/MicroOcpp/Context.cpp | 55 +++++++++++++++++--------- src/MicroOcpp/Context.h | 9 +++-- src/MicroOcpp/Core/Connection.cpp | 65 +++++++++++++++++++++++++++++++ src/MicroOcpp/Core/Connection.h | 8 ++++ 6 files changed, 125 insertions(+), 32 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 22d2230f..3fa8a3e3 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -91,11 +91,11 @@ MO_Context *mo_getApiContext() { #if MO_USE_FILEAPI != MO_CUSTOM_FS //Set if MO can use the filesystem and if it needs to mount it -void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt) { - mo_setDefaultFilesystemConfig2(mo_getApiContext(), opt, MO_FILENAME_PREFIX); +void mo_setFilesystemConfig(MO_FilesystemOpt opt) { + mo_setFilesystemConfig2(mo_getApiContext(), opt, MO_FILENAME_PREFIX); } -void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix) { +void mo_setFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix) { if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; @@ -107,7 +107,7 @@ void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const filesystemConfig.opt = opt; filesystemConfig.path_prefix = pathPrefix; - context->setDefaultFilesystemConfig(filesystemConfig); + context->setFilesystemConfig(filesystemConfig); } #endif // MO_USE_FILEAPI != MO_CUSTOM_FS @@ -125,13 +125,13 @@ bool mo_setWebsocketUrl(const char *backendUrl, const char *chargeBoxId, const c } MO_ConnectionConfig config; - memset(&config, 0, sizeof(config)); + mo_connectionConfig_init(&config); config.backendUrl = backendUrl; config.chargeBoxId = chargeBoxId; config.authorizationKey = authorizationKey; config.CA_cert = CA_cert; - g_context->setDefaultConnectionConfig(config); + g_context->setConnectionConfig(config); return true; } #endif @@ -1918,7 +1918,7 @@ void mo_setFtpConfig(MO_Context *ctx, MO_FTPConfig ftpConfig) { } auto context = mo_getContext2(ctx); - context->setDefaultFtpConfig(ftpConfig); + context->setFtpConfig(ftpConfig); } #if MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index d3f0ea66..f154ac0a 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -38,8 +38,8 @@ MO_Context *mo_getApiContext(); #if MO_USE_FILEAPI != MO_CUSTOM_FS //Set if MO can use the filesystem and if it needs to mount it. See "FilesystemAdapter.h" for all options -void mo_setDefaultFilesystemConfig(MO_FilesystemOpt opt); -void mo_setDefaultFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix); +void mo_setFilesystemConfig(MO_FilesystemOpt opt); +void mo_setFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char *pathPrefix); #endif //#if MO_USE_FILEAPI != MO_CUSTOM_FS #if MO_WS_USE == MO_WS_ARDUINO @@ -469,7 +469,7 @@ void mo_setRngCb(uint32_t (*rngCb)()); void mo_setRngCb2(MO_Context *ctx, uint32_t (*rngCb)()); //Returns the internal filesystem adapter or NULL if the initialization failed. Need to set configs using -//via `mo_setDefaultFilesystemConfig` before accessing the FS adpater. The FS adapter can be passed to other +//via `mo_setFilesystemConfig` before accessing the FS adpater. The FS adapter can be passed to other //MO utils functions or be used for other purposes than OCPP, as a file abstraction layer MO_FilesystemAdapter *mo_getFilesystem(); MO_FilesystemAdapter *mo_getFilesystem2(MO_Context *ctx); diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index 273be971..ef32a062 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -19,12 +19,10 @@ Context::Context() : MemoryManaged("Context") { #if MO_USE_FILEAPI != MO_CUSTOM_FS memset(&filesystemConfig, 0, sizeof(filesystemConfig)); - filesystemConfig.opt = MO_FS_OPT_USE_MOUNT; - filesystemConfig.path_prefix = MO_FILENAME_PREFIX; #endif //MO_USE_FILEAPI != MO_CUSTOM_FS #if MO_WS_USE != MO_WS_CUSTOM - memset(&connectionConfig, 0, sizeof(connectionConfig)); + mo_connectionConfig_init(&connectionConfig); #endif //MO_WS_USE != MO_WS_CUSTOM #if MO_ENABLE_MBEDTLS @@ -38,6 +36,10 @@ Context::~Context() { setConnection(nullptr); setFtpClient(nullptr); setCertificateStore(nullptr); + +#if MO_WS_USE != MO_WS_CUSTOM + mo_connectionConfig_deinit(&connectionConfig); +#endif //MO_WS_USE != MO_WS_CUSTOM } void Context::setDebugCb(void (*debugCb)(const char *msg)) { @@ -73,11 +75,9 @@ uint32_t (*Context::getRngCb())() { } #if MO_USE_FILEAPI != MO_CUSTOM_FS -void Context::setDefaultFilesystemConfig(MO_FilesystemConfig filesystemConfig) { - if (filesystem) { - MO_DBG_ERR("need to set filesystem config before first filesystem usage"); - } +void Context::setFilesystemConfig(MO_FilesystemConfig filesystemConfig) { this->filesystemConfig = filesystemConfig; + filesystemConfigDefined = true; } #endif //MO_USE_FILEAPI != MO_CUSTOM_FS @@ -94,7 +94,7 @@ void Context::setFilesystem(MO_FilesystemAdapter *filesystem) { MO_FilesystemAdapter *Context::getFilesystem() { #if MO_USE_FILEAPI != MO_CUSTOM_FS - if (!filesystem && filesystemConfig.opt != MO_FS_OPT_DISABLE) { + if (!filesystem && filesystemConfigDefined) { // init default FS implementaiton filesystem = mo_makeDefaultFilesystemAdapter(filesystemConfig); if (!filesystem) { @@ -109,11 +109,13 @@ MO_FilesystemAdapter *Context::getFilesystem() { } #if MO_WS_USE != MO_WS_CUSTOM -void Context::setDefaultConnectionConfig(MO_ConnectionConfig connectionConfig) { - if (connection) { - MO_DBG_ERR("need to set connection config before first connection usage"); +bool Context::setConnectionConfig(MO_ConnectionConfig connectionConfig) { + if (!mo_connectionConfig_copy(&this->connectionConfig, &connectionConfig)) { + MO_DBG_ERR("OOM"); + return false; } - this->connectionConfig = connectionConfig; + connectionConfigDefined = true; + return true; } #endif //MO_WS_USE != MO_WS_CUSTOM @@ -136,7 +138,7 @@ void Context::setConnection(Connection *connection) { Connection *Context::getConnection() { #if MO_WS_USE != MO_WS_CUSTOM - if (!connection) { + if (!connection && connectionConfigDefined) { // init default Connection implementation connection = static_cast(makeDefaultConnection(connectionConfig, ocppVersion)); if (!connection) { @@ -144,17 +146,16 @@ Connection *Context::getConnection() { return nullptr; } isConnectionOwner = true; + mo_connectionConfig_deinit(&connectionConfig); } #endif //MO_WS_USE != MO_WS_CUSTOM return connection; } #if MO_ENABLE_MBEDTLS -void Context::setDefaultFtpConfig(MO_FTPConfig ftpConfig) { - if (ftpClient) { - MO_DBG_ERR("need to set FTP config before first FTP usage"); - } +void Context::setFtpConfig(MO_FTPConfig ftpConfig) { this->ftpConfig = ftpConfig; + ftpConfigDefined = true; } #endif //MO_ENABLE_MBEDTLS @@ -171,7 +172,7 @@ void Context::setFtpClient(FtpClient *ftpClient) { FtpClient *Context::getFtpClient() { #if MO_ENABLE_MBEDTLS - if (!ftpClient) { + if (!ftpClient && ftpConfigDefined) { ftpClient = makeFtpClientMbedTLS(ftpConfig).release(); if (!ftpClient) { MO_DBG_ERR("OOM"); @@ -194,7 +195,7 @@ void Context::setCertificateStore(CertificateStore *certStore) { CertificateStore *Context::getCertificateStore() { #if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS - if (!certStore) { + if (!certStore && filesystem) { certStore = makeCertificateStoreMbedTLS(filesystem); if (!certStore) { MO_DBG_ERR("OOM"); @@ -274,6 +275,15 @@ bool Context::setup() { return false; } + #if MO_USE_FILEAPI != MO_CUSTOM_FS + if (!filesystemConfigDefined) { + //set defaults + filesystemConfig.opt = MO_FS_OPT_USE_MOUNT; + filesystemConfig.path_prefix = MO_FILENAME_PREFIX; + filesystemConfigDefined = true; + } + #endif //MO_USE_FILEAPI != MO_CUSTOM_FS + if (!getFilesystem()) { MO_DBG_DEBUG("initialize MO without filesystem access"); } @@ -283,6 +293,13 @@ bool Context::setup() { return false; } + #if MO_ENABLE_MBEDTLS + if (!ftpConfigDefined) { + //set defaults + ftpConfigDefined = true; + } + #endif //MO_ENABLE_MBEDTLS + if (!getFtpClient()) { MO_DBG_DEBUG("initialize MO without FTP client"); } diff --git a/src/MicroOcpp/Context.h b/src/MicroOcpp/Context.h index 30f77d2b..1767f026 100644 --- a/src/MicroOcpp/Context.h +++ b/src/MicroOcpp/Context.h @@ -38,18 +38,21 @@ class Context : public MemoryManaged { #if MO_USE_FILEAPI != MO_CUSTOM_FS MO_FilesystemConfig filesystemConfig; + bool filesystemConfigDefined = false; bool isFilesystemOwner = false; #endif //MO_USE_FILEAPI != MO_CUSTOM_FS MO_FilesystemAdapter *filesystem = nullptr; #if MO_WS_USE != MO_WS_CUSTOM MO_ConnectionConfig connectionConfig; + bool connectionConfigDefined = false; bool isConnectionOwner = false; #endif //MO_WS_USE != MO_WS_CUSTOM Connection *connection = nullptr; #if MO_ENABLE_MBEDTLS MO_FTPConfig ftpConfig; + bool ftpConfigDefined = false; bool isFtpClientOwner = false; #endif //MO_ENABLE_MBEDTLS FtpClient *ftpClient = nullptr; @@ -82,19 +85,19 @@ class Context : public MemoryManaged { uint32_t (*getRngCb())(); #if MO_USE_FILEAPI != MO_CUSTOM_FS - void setDefaultFilesystemConfig(MO_FilesystemConfig filesystemConfig); + void setFilesystemConfig(MO_FilesystemConfig filesystemConfig); #endif //MO_USE_FILEAPI != MO_CUSTOM_FS void setFilesystem(MO_FilesystemAdapter *filesystem); MO_FilesystemAdapter *getFilesystem(); #if MO_WS_USE != MO_WS_CUSTOM - void setDefaultConnectionConfig(MO_ConnectionConfig connectionConfig); + bool setConnectionConfig(MO_ConnectionConfig connectionConfig); #endif //MO_WS_USE != MO_WS_CUSTOM void setConnection(Connection *connection); Connection *getConnection(); #if MO_ENABLE_MBEDTLS - void setDefaultFtpConfig(MO_FTPConfig ftpConfig); + void setFtpConfig(MO_FTPConfig ftpConfig); #endif //MO_ENABLE_MBEDTLS void setFtpClient(FtpClient *ftpClient); FtpClient *getFtpClient(); diff --git a/src/MicroOcpp/Core/Connection.cpp b/src/MicroOcpp/Core/Connection.cpp index 4083768c..707a1251 100644 --- a/src/MicroOcpp/Core/Connection.cpp +++ b/src/MicroOcpp/Core/Connection.cpp @@ -55,6 +55,71 @@ bool LoopbackConnection::isConnected() { #if MO_WS_USE == MO_WS_ARDUINO +void mo_connectionConfig_init(MO_ConnectionConfig *config) { + memset(config, 0, sizeof(*config)); +} + +bool mo_connectionConfig_copy(MO_ConnectionConfig *dst, MO_ConnectionConfig *src) { + + size_t size = 0; + size += src->backendUrl ? strlen(src->backendUrl) + 1 : 0; + size += src->chargeBoxId ? strlen(src->chargeBoxId) + 1 : 0; + size += src->authorizationKey ? strlen(src->authorizationKey) + 1 : 0; + + char *buf = static_cast(MO_MALLOC("WebSocketsClient", size)); + if (!buf) { + MO_DBG_ERR("OOM"); + return false; + } + + mo_connectionConfig_init(dst); + + size_t written = 0; + if (src->backendUrl) { + dst->backendUrl = buf + written; + auto ret = snprintf(buf + written, size - written, "%s", src->backendUrl); + if (ret < 0 || (size_t)ret >= size - written) { + goto fail; + } + written += ret + 1; + } + + if (src->chargeBoxId) { + dst->chargeBoxId = buf + written; + auto ret = snprintf(buf + written, size - written, "%s", src->chargeBoxId); + if (ret < 0 || (size_t)ret >= size - written) { + goto fail; + } + written += ret + 1; + } + + if (src->authorizationKey) { + dst->authorizationKey = buf + written; + auto ret = snprintf(buf + written, size - written, "%s", src->authorizationKey); + if (ret < 0 || (size_t)ret >= size - written) { + goto fail; + } + written += ret + 1; + } + + if (written != size) { + MO_DBG_ERR("internal error"); + goto fail; + } + + dst->internalBuf = buf; + return true; +fail: + MO_DBG_ERR("copy failed"); + MO_FREE(buf); + return false; +} + +void mo_connectionConfig_deinit(MO_ConnectionConfig *config) { + MO_FREE(config->internalBuf); + memset(config, 0, sizeof(*config)); +} + #include namespace MicroOcpp { diff --git a/src/MicroOcpp/Core/Connection.h b/src/MicroOcpp/Core/Connection.h index 80511da7..dbb2871b 100644 --- a/src/MicroOcpp/Core/Connection.h +++ b/src/MicroOcpp/Core/Connection.h @@ -101,8 +101,16 @@ typedef struct { const char *chargeBoxId; //e.g. "charger001". Can be NULL const char *authorizationKey; //authorizationKey present in the websocket message header. Can be NULL. Set this to enable OCPP Security Profile 2 const char *CA_cert; //TLS certificate. Can be NULL. Set this to enable OCPP Security Profile 2 + + char *internalBuf; //used by MO internally } MO_ConnectionConfig; +void mo_connectionConfig_init(MO_ConnectionConfig *config); + +bool mo_connectionConfig_copy(MO_ConnectionConfig *dst, MO_ConnectionConfig *src); + +void mo_connectionConfig_deinit(MO_ConnectionConfig *config); + #ifdef __cplusplus } //extern "C" From 626e3d12e8ecb87dbeba424d7e29f8bdbd59a229 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 28 Jun 2025 23:41:18 +0200 Subject: [PATCH 13/50] bugfixes --- CMakeLists.txt | 36 ++-- .../Availability/AvailabilityService.cpp | 15 +- .../Model/Availability/AvailabilityService.h | 3 + .../Transactions/TransactionService201.cpp | 1 + .../Model/Transactions/TransactionStore.cpp | 5 +- tests/TransactionSafety.cpp | 77 ++++---- tests/benchmarks/scripts/measure_heap.py | 172 +++++++----------- 7 files changed, 142 insertions(+), 167 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fa3e2b6..a1e57e0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,23 +136,24 @@ target_compile_definitions(MicroOcpp PUBLIC set(MO_SRC_UNIT tests/helpers/testHelper.cpp tests/ocppEngineLifecycle.cpp + tests/Core.cpp tests/TransactionSafety.cpp - tests/ChargingSessions.cpp - tests/ConfigurationBehavior.cpp - tests/SmartCharging.cpp - tests/Api.cpp - tests/Metering.cpp - tests/Configuration.cpp - tests/Reservation.cpp - tests/Reset.cpp - tests/LocalAuthList.cpp - tests/Variables.cpp - tests/Transactions.cpp - tests/Certificates.cpp - tests/FirmwareManagement.cpp - tests/ChargePointError.cpp - tests/Boot.cpp - tests/Security.cpp + #tests/ChargingSessions.cpp + #tests/ConfigurationBehavior.cpp + #tests/SmartCharging.cpp + #tests/Api.cpp + #tests/Metering.cpp + #tests/Configuration.cpp + #tests/Reservation.cpp + #tests/Reset.cpp + #tests/LocalAuthList.cpp + #tests/Variables.cpp + #tests/Transactions.cpp + #tests/Certificates.cpp + #tests/FirmwareManagement.cpp + #tests/ChargePointError.cpp + #tests/Boot.cpp + #tests/Security.cpp ) add_executable(mo_unit_tests @@ -189,7 +190,8 @@ target_compile_definitions(mo_unit_tests PUBLIC MO_FILENAME_PREFIX="./mo_store/" MO_LocalAuthListMaxLength=8 MO_SendLocalListMaxLength=4 - MO_ENABLE_FILE_INDEX=1 + MO_ENABLE_FILE_INDEX=0 + MO_ENABLE_TIMESTAMP_MILLISECONDS=1 MO_ChargeProfileMaxStackLevel=2 MO_ChargingScheduleMaxPeriods=4 MO_MaxChargingProfilesInstalled=3 diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 80a4e297..2015f57d 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -592,6 +593,18 @@ Ocpp201::AvailabilityService::~AvailabilityService() { bool Ocpp201::AvailabilityService::setup() { + auto varSvc = context.getModel201().getVariableService(); + if (!varSvc) { + MO_DBG_ERR("setup failure"); + return false; + } + + offlineThreshold = varSvc->declareVariable("OCPPCommCtrlr", "OfflineThreshold", 600); + if (!offlineThreshold) { + MO_DBG_ERR("setup failure"); + return false; + } + context.getMessageService().registerOperation("ChangeAvailability", [] (Context& context) -> Operation* { return new ChangeAvailability(*context.getModel201().getAvailabilityService());}); @@ -599,7 +612,7 @@ bool Ocpp201::AvailabilityService::setup() { context.getMessageService().registerOperation("StatusNotification", nullptr, nullptr); #endif //MO_ENABLE_MOCK_SERVER - auto rcService = context.getModel16().getRemoteControlService(); + auto rcService = context.getModel201().getRemoteControlService(); if (!rcService) { MO_DBG_ERR("initialization error"); return false; diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h index f103c159..2dbe61ab 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -143,6 +143,7 @@ class Context; namespace Ocpp201 { class AvailabilityService; +class Variable; struct FaultedInput { bool (*isFaulted)(unsigned int connectorId, void *userData) = nullptr; @@ -194,6 +195,8 @@ class AvailabilityService : public MemoryManaged { AvailabilityServiceEvse* evses [MO_NUM_EVSEID] = {nullptr}; unsigned int numEvseId = MO_NUM_EVSEID; + Variable *offlineThreshold = nullptr; + public: AvailabilityService(Context& context); ~AvailabilityService(); diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index a3dc0d8c..c87aa468 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -855,6 +855,7 @@ bool TransactionServiceEvse::ocppPermitsCharge() { transaction->active && transaction->isAuthorizationActive && transaction->isAuthorized && + (!connectorPluggedInput || connectorPluggedInput(evseId, connectorPluggedInputUserData)) && !transaction->isDeauthorized; } diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index 4d89c194..f26e382e 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -1166,7 +1166,7 @@ bool Ocpp201::TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) //information is commited into tx201 file at seqNoEnd, then delete file at seqNo char fn [MO_MAX_PATH_SIZE]; - if (!printTxEventFname(fn, sizeof(fn), evseId, tx.txNr, seqNo)) { + if (!printTxEventFname(fn, sizeof(fn), evseId, tx.txNr, tx.seqNoEnd)) { MO_DBG_ERR("fn error"); return false; } @@ -1186,7 +1186,7 @@ bool Ocpp201::TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) MO_DBG_ERR("failed to load %s", fn); break; } - + if (ret != FilesystemUtils::LoadStatus::Success || !doc.containsKey("tx")) { //no valid tx201 file at seqNoEnd. Commit tx into file seqNoEnd, then remove file at seqNo @@ -1203,6 +1203,7 @@ bool Ocpp201::TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) for (size_t i = 0; i < tx.seqNos.size(); i++) { if (tx.seqNos[i] == seqNo) { found = true; + break; } } if (!found) { diff --git a/tests/TransactionSafety.cpp b/tests/TransactionSafety.cpp index f3593e01..4109b08b 100644 --- a/tests/TransactionSafety.cpp +++ b/tests/TransactionSafety.cpp @@ -4,11 +4,8 @@ #include #include -#include #include -#include -#include -#include +#include #include #include #include "./helpers/testHelper.h" @@ -19,81 +16,79 @@ using namespace MicroOcpp; TEST_CASE( "Transaction safety" ) { printf("\nRun %s\n", "Transaction safety"); - //initialize Context with dummy socket + //initialize Context without any configs + mo_initialize(); + + mo_getContext()->setTicksCb(custom_timer_cb); + LoopbackConnection loopback; - mocpp_initialize(loopback); + mo_getContext()->setConnection(&loopback); - mocpp_set_timer(custom_timer_cb); + auto ocppVersion = GENERATE(MO_OCPP_V16, MO_OCPP_V201); + mo_setOcppVersion(ocppVersion); - declareConfiguration("ConnectionTimeOut", 30)->setInt(30); + mo_setup(); - SECTION("Basic transaction") { - MO_DBG_DEBUG("Basic transaction"); - loop(); - startTransaction("mIdTag"); - loop(); - REQUIRE(ocppPermitsCharge()); - stopTransaction(); - loop(); - REQUIRE(!ocppPermitsCharge()); + mo_setVarConfigInt(mo_getApiContext(), "TxCtrlr", "EVConnectionTimeOut", "ConnectionTimeOut", 30); + + if (ocppVersion == MO_OCPP_V201) { + mo_setVarConfigString(mo_getApiContext(), "TxCtrlr", "TxStartPoint", NULL, "PowerPathClosed"); + mo_setVarConfigString(mo_getApiContext(), "TxCtrlr", "TxStopPoint", NULL, "PowerPathClosed"); + } - mocpp_deinitialize(); + SECTION("Clean up files") { + auto filesystem = mo_getFilesystem(); + FilesystemUtils::removeByPrefix(filesystem, ""); } SECTION("Managed transaction") { MO_DBG_DEBUG("Managed transaction"); loop(); - setConnectorPluggedInput([] () {return true;}); - beginTransaction("mIdTag"); + mo_setConnectorPluggedInput([] () {return true;}); + mo_beginTransaction("mIdTag"); loop(); - REQUIRE(ocppPermitsCharge()); - endTransaction(); + REQUIRE(mo_ocppPermitsCharge()); + mo_endTransaction("mIdTag", NULL); loop(); - REQUIRE(!ocppPermitsCharge()); - - mocpp_deinitialize(); + REQUIRE(!mo_ocppPermitsCharge()); } SECTION("Reset during transaction 01 - interrupt initiation") { MO_DBG_DEBUG("Reset during transaction 01 - interrupt initiation"); - setConnectorPluggedInput([] () {return false;}); + mo_setConnectorPluggedInput([] () {return false;}); loop(); - beginTransaction("mIdTag"); + mo_beginTransaction("mIdTag"); loop(); - mocpp_deinitialize(); //reset and jump to next section } SECTION("Reset during transaction 02 - interrupt initiation second time") { MO_DBG_DEBUG("Reset during transaction 02 - interrupt initiation second time"); - setConnectorPluggedInput([] () {return false;}); + mo_setConnectorPluggedInput([] () {return false;}); loop(); - REQUIRE(!ocppPermitsCharge()); - mocpp_deinitialize(); + REQUIRE(!mo_ocppPermitsCharge()); } SECTION("Reset during transaction 03 - interrupt running tx") { MO_DBG_DEBUG("Reset during transaction 03 - interrupt running tx"); - setConnectorPluggedInput([] () {return true;}); + mo_setConnectorPluggedInput([] () {return true;}); loop(); - REQUIRE(ocppPermitsCharge()); - mocpp_deinitialize(); + REQUIRE(mo_ocppPermitsCharge()); } SECTION("Reset during transaction 04 - interrupt stopping tx") { MO_DBG_DEBUG("Reset during transaction 04 - interrupt stopping tx"); - setConnectorPluggedInput([] () {return true;}); + mo_setConnectorPluggedInput([] () {return true;}); loop(); - REQUIRE(ocppPermitsCharge()); - endTransaction(); - mocpp_deinitialize(); + REQUIRE(mo_ocppPermitsCharge()); + mo_endTransaction("mIdTag", NULL); } SECTION("Reset during transaction 06 - check tx finished") { MO_DBG_DEBUG("Reset during transaction 06 - check tx finished"); - setConnectorPluggedInput([] () {return true;}); + mo_setConnectorPluggedInput([] () {return true;}); loop(); - REQUIRE(!ocppPermitsCharge()); - mocpp_deinitialize(); + REQUIRE(!mo_ocppPermitsCharge()); } + mo_deinitialize(); } diff --git a/tests/benchmarks/scripts/measure_heap.py b/tests/benchmarks/scripts/measure_heap.py index 0b050544..a6c7c97d 100644 --- a/tests/benchmarks/scripts/measure_heap.py +++ b/tests/benchmarks/scripts/measure_heap.py @@ -8,6 +8,8 @@ import json import time import pandas as pd +import subprocess +import threading requests.packages.urllib3.disable_warnings() # avoid the URL to be printed to console @@ -105,105 +107,68 @@ max_memory_total = 0 min_memory_base = 1000 * 1000 * 1000 -def connect_ssh(): - - if not os.path.isfile(os.path.join('tests', 'benchmarks', 'scripts', 'id_ed25519')): - file = open(os.path.join('tests', 'benchmarks', 'scripts', 'id_ed25519'), 'w') - file.write(os.environ['SSH_LOCAL_PRIV']) - file.close() - print('SSH ID written to file') - - client = paramiko.SSHClient() - client.get_host_keys().add('cicd.micro-ocpp.com', 'ssh-ed25519', paramiko.pkey.PKey.from_type_string('ssh-ed25519', base64.b64decode(os.environ['SSH_HOST_PUB']))) - client.connect('cicd.micro-ocpp.com', username='ocpp', key_filename=os.path.join('tests', 'benchmarks', 'scripts', 'id_ed25519'), look_for_keys=False) - return client - -def close_ssh(client: paramiko.SSHClient): - - client.close() - -def deploy_simulator(): - - print('Deploy Simulator') - - client = connect_ssh() - - print(' - stop Simulator, if still running') - stdin, stdout, stderr = client.exec_command('killall -s SIGINT mo_simulator') - - print(' - clean previous deployment') - stdin, stdout, stderr = client.exec_command('rm -rf ' + os.path.join('MicroOcppSimulator')) - - print(' - init folder structure') - sftp = client.open_sftp() - sftp.mkdir(os.path.join('MicroOcppSimulator')) - sftp.mkdir(os.path.join('MicroOcppSimulator', 'build')) - sftp.mkdir(os.path.join('MicroOcppSimulator', 'public')) - sftp.mkdir(os.path.join('MicroOcppSimulator', 'mo_store')) - - print(' - upload files') - sftp.put( os.path.join('MicroOcppSimulator', 'build', 'mo_simulator'), - os.path.join('MicroOcppSimulator', 'build', 'mo_simulator')) - sftp.chmod(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator'), 0O777) - sftp.put( os.path.join('MicroOcppSimulator', 'public', 'bundle.html.gz'), - os.path.join('MicroOcppSimulator', 'public', 'bundle.html.gz')) - sftp.close() - close_ssh(client) - print(' - done') - def cleanup_simulator(): print('Clean up Simulator') - client = connect_ssh() - print(' - stop Simulator, if still running') - stdin, stdout, stderr = client.exec_command('killall -s SIGINT mo_simulator') + os.system('killall -s SIGINT mo_simulator') - print(' - clean deployment') - stdin, stdout, stderr = client.exec_command('rm -rf ' + os.path.join('MicroOcppSimulator')) + print(' - clean state') + os.system('rm -rf ' + os.path.join('MicroOcppSimulator', 'mo_store', '*')) - close_ssh(client) print(' - done') def setup_simulator(): - print('Setup Simulator') - - client = connect_ssh() + cleanup_simulator() - print(' - stop Simulator, if still running') - stdin, stdout, stderr = client.exec_command('killall -s SIGINT mo_simulator') + print('Setup Simulator') - print(' - clean state') - stdin, stdout, stderr = client.exec_command('rm -rf ' + os.path.join('MicroOcppSimulator', 'mo_store', '*')) + print(' - set credentials') - print(' - upload credentials') - sftp = client.open_sftp() - sftp.putfo(io.StringIO(os.environ['MO_SIM_CONFIG']), os.path.join('MicroOcppSimulator', 'mo_store', 'simulator.jsn')) - sftp.putfo(io.StringIO(os.environ['MO_SIM_OCPP_SERVER']),os.path.join('MicroOcppSimulator', 'mo_store', 'ws-conn-v201.jsn')) - sftp.putfo(io.StringIO(os.environ['MO_SIM_API_CERT']), os.path.join('MicroOcppSimulator', 'mo_store', 'api_cert.pem')) - sftp.putfo(io.StringIO(os.environ['MO_SIM_API_KEY']), os.path.join('MicroOcppSimulator', 'mo_store', 'api_key.pem')) - sftp.putfo(io.StringIO(os.environ['MO_SIM_API_CONFIG']), os.path.join('MicroOcppSimulator', 'mo_store', 'api.jsn')) - sftp.close() + with open(os.path.join('MicroOcppSimulator', 'mo_store', 'simulator.jsn'), 'w') as f: + f.write(os.environ['MO_SIM_CONFIG']) + with open(os.path.join('MicroOcppSimulator', 'mo_store', 'ws-conn-v201.jsn'), 'w') as f: + f.write(os.environ['MO_SIM_OCPP_SERVER']) + with open(os.path.join('MicroOcppSimulator', 'mo_store', 'rmt_ctrl.jsn'), 'w') as f: + f.write(os.environ['MO_SIM_RMT_CTRL_CONFIG']) + with open(os.path.join('MicroOcppSimulator', 'mo_store', 'rmt_ctrl.pem'), 'w') as f: + f.write(os.environ['MO_SIM_RMT_CTRL_CERT']) print(' - start Simulator') - stdin, stdout, stderr = client.exec_command('mkdir -p logs && cd ' + os.path.join('MicroOcppSimulator') + ' && ./build/mo_simulator > ~/logs/sim_"$(date +%Y-%m-%d_%H-%M-%S.log)"') - close_ssh(client) + os.system(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator') + ' &') print(' - done') +# Ensure Simulator is still running despite Reset requests via OCPP or the rmt_ctrl interface +keepalive_simulator = False + +def keepalive_simulator_thread(): + + while keepalive_simulator: + # Check if mo_simulator process can be found + try: + subprocess.check_output(['pgrep', '-f', 'mo_simulator']) + # Found, still running + except subprocess.CalledProcessError: + # Exited, restart + print('Keepalive: restart Simulator') + os.system(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator') + ' &') + + time.sleep(1) + def run_measurements(): global max_memory_total global min_memory_base + global keepalive_simulator print("Fetch TCs from Test Driver") response = requests.get(os.environ['TEST_DRIVER_URL'] + '/ocpp2.0.1/CS/testcases/' + os.environ['TEST_DRIVER_CONFIG'], - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}, - verify=False) + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) #print(json.dumps(response.json(), indent=4)) @@ -227,19 +192,14 @@ def run_measurements(): print(i['header'] + ' --- ' + j['functional_block'] + ' --- ' + j['description']) testcases.append(j) - deploy_simulator() - print('Get Simulator base memory data') setup_simulator() + time.sleep(1) - response = requests.post('https://cicd.micro-ocpp.com:8443/api/memory/reset', - auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'], - json.loads(os.environ['MO_SIM_API_CONFIG'])['pass'])) + response = requests.post('http://localhost:8000/api/memory/reset') print(f'Simulator API /memory/reset:\n > {response.status_code}') - response = requests.get('https://cicd.micro-ocpp.com:8443/api/memory/info', - auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'], - json.loads(os.environ['MO_SIM_API_CONFIG'])['pass'])) + response = requests.get('http://localhost:8000/api/memory/info') print(f'Simulator API /memory/info:\n > {response.status_code}, current heap={response.json()["total_current"]}, max heap={response.json()["total_max"]}') base_memory_level = response.json()["total_max"] min_memory_base = min(min_memory_base, response.json()["total_max"]) @@ -247,8 +207,7 @@ def run_measurements(): print("Start Test Driver") response = requests.post(os.environ['TEST_DRIVER_URL'] + '/ocpp2.0.1/CS/session/start/' + os.environ['TEST_DRIVER_CONFIG'], - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}, - verify=False) + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) print(f'Test Driver /*/*/session/start/*:\n > {response.status_code}') #print(json.dumps(response.json(), indent=4)) @@ -266,11 +225,10 @@ def run_measurements(): simulator_connected = False for i in range(5): response = requests.get(os.environ['TEST_DRIVER_URL'] + '/sut_connection_status', - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}, - verify=False) + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) print(f'Test Driver /sut_connection_status:\n > {response.status_code}') #print(json.dumps(response.json(), indent=4)) - if response.status_code == 200: + if response.status_code == 200 and response.json()['isConnected']: simulator_connected = True break else: @@ -281,23 +239,26 @@ def run_measurements(): print('Simulator could not connect to Test Driver') raise Exception() - response = requests.post('https://cicd.micro-ocpp.com:8443/api/memory/reset', - auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'], - json.loads(os.environ['MO_SIM_API_CONFIG'])['pass'])) + response = requests.post('http://localhost:8000/api/memory/reset') print(f'Simulator API /memory/reset:\n > {response.status_code}') + + # Keepalive Simulator while running the test cases (tests may triger Reset commands) + keepalive_simulator = True + keepalive_thread = threading.Thread(target=keepalive_simulator_thread, daemon=True) + keepalive_thread.start() test_response = requests.post(os.environ['TEST_DRIVER_URL'] + '/testcases/' + testcase['testcase_name'] + '/execute', - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}, - verify=False) + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) print(f'Test Driver /testcases/{testcase["testcase_name"]}/execute:\n > {test_response.status_code}') #try: # print(json.dumps(test_response.json(), indent=4)) #except: # print(' > No JSON') - sim_response = requests.get('https://cicd.micro-ocpp.com:8443/api/memory/info', - auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'], - json.loads(os.environ['MO_SIM_API_CONFIG'])['pass'])) + keepalive_simulator = False + keepalive_thread.join() + + sim_response = requests.get('http://localhost:8000/api/memory/info') print(f'Simulator API /memory/info:\n > {sim_response.status_code}, current heap={sim_response.json()["total_current"]}, max heap={sim_response.json()["total_max"]}') df.loc[testcase['testcase_name']] = [testcase['functional_block'], testcase['description'], 'x' if test_response.status_code == 200 and test_response.json()['data'][0]['verdict'] == "pass" else '-', str(sim_response.json()["total_max"] - min(base_memory_level, sim_response.json()["total_current"]))] @@ -305,11 +266,14 @@ def run_measurements(): max_memory_total = max(max_memory_total, sim_response.json()["total_max"]) min_memory_base = min(min_memory_base, sim_response.json()["total_current"]) + #if False and test_response.json()['data'][0]['verdict'] != "pass": + # print('Test failure, abort') + # break + print("Stop Test Driver") response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}, - verify=False) + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) print(f'Test Driver /session/stop:\n > {response.status_code}') #print(json.dumps(response.json(), indent=4)) @@ -348,16 +312,13 @@ def run_measurements(): def run_measurements_and_retry(): - if ( 'TEST_DRIVER_URL' not in os.environ or - 'TEST_DRIVER_CONFIG' not in os.environ or - 'TEST_DRIVER_KEY' not in os.environ or - 'MO_SIM_CONFIG' not in os.environ or - 'MO_SIM_OCPP_SERVER' not in os.environ or - 'MO_SIM_API_CERT' not in os.environ or - 'MO_SIM_API_KEY' not in os.environ or - 'MO_SIM_API_CONFIG' not in os.environ or - 'SSH_LOCAL_PRIV' not in os.environ or - 'SSH_HOST_PUB' not in os.environ): + if ( 'TEST_DRIVER_URL' not in os.environ or + 'TEST_DRIVER_CONFIG' not in os.environ or + 'TEST_DRIVER_KEY' not in os.environ or + 'MO_SIM_CONFIG' not in os.environ or + 'MO_SIM_OCPP_SERVER' not in os.environ or + 'MO_SIM_RMT_CTRL_CONFIG' not in os.environ or + 'MO_SIM_RMT_CTRL_CERT' not in os.environ): sys.exit('\nCould not read environment variables') n_tries = 3 @@ -375,8 +336,7 @@ def run_measurements_and_retry(): print("Stop Test Driver") response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}, - verify=False) + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) print(f'Test Driver /session/stop:\n > {response.status_code}') #print(json.dumps(response.json(), indent=4)) From 3d4632a016b67247ac5aa3ccc36ea9cd20951601 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 6 Jul 2025 14:16:20 +0200 Subject: [PATCH 14/50] fix unit tests --- CMakeLists.txt | 4 +- src/MicroOcpp.cpp | 62 +-- src/MicroOcpp.h | 16 +- src/MicroOcpp/Context.cpp | 16 +- src/MicroOcpp/Core/MessageService.cpp | 8 +- src/MicroOcpp/Core/Time.cpp | 4 + .../Availability/AvailabilityService.cpp | 16 + .../Model/Availability/AvailabilityService.h | 3 + src/MicroOcpp/Model/Model.cpp | 45 +- .../RemoteControl/RemoteControlService.cpp | 51 ++- .../RemoteControl/RemoteControlService.h | 2 +- .../Model/Reservation/ReservationService.cpp | 2 +- .../SmartCharging/SmartChargingModel.cpp | 21 +- .../SmartCharging/SmartChargingService.cpp | 25 +- .../SmartCharging/SmartChargingService.h | 4 +- .../Model/Transactions/Transaction.cpp | 11 + .../Model/Transactions/Transaction.h | 9 +- .../Transactions/TransactionService201.cpp | 17 +- .../Transactions/TransactionService201.h | 3 +- .../Model/Transactions/TransactionStore.cpp | 5 - .../Operations/RequestStartTransaction.cpp | 32 +- .../Operations/RequestStartTransaction.h | 4 +- tests/ChargingSessions.cpp | 426 ++++++++++++------ tests/SmartCharging.cpp | 225 +++++---- 24 files changed, 673 insertions(+), 338 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1e57e0b..c801fc22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,9 +138,9 @@ set(MO_SRC_UNIT tests/ocppEngineLifecycle.cpp tests/Core.cpp tests/TransactionSafety.cpp - #tests/ChargingSessions.cpp + tests/ChargingSessions.cpp #tests/ConfigurationBehavior.cpp - #tests/SmartCharging.cpp + tests/SmartCharging.cpp #tests/Api.cpp #tests/Metering.cpp #tests/Configuration.cpp diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 3fa8a3e3..bdee1ee8 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -890,19 +890,19 @@ const char *mo_getTransactionIdTag2(MO_Context *ctx, unsigned int evseId) { return res && *res ? res : nullptr; } -#if MO_ENABLE_V16 -int mo_v16_getTransactionId() { - return mo_v16_getTransactionId2(mo_getApiContext(), EVSE_ID_1); +#if MO_ENABLE_V201 +const char *mo_getTransactionId() { + return mo_getTransactionId2(mo_getApiContext(), EVSE_ID_1); } -int mo_v16_getTransactionId2(MO_Context *ctx, unsigned int evseId) { +const char *mo_getTransactionId2(MO_Context *ctx, unsigned int evseId) { if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return -1; + return nullptr; } auto context = mo_getContext2(ctx); - int res = -1; + const char *res = nullptr; #if MO_ENABLE_V16 if (context->getOcppVersion() == MO_OCPP_V16) { @@ -910,59 +910,67 @@ int mo_v16_getTransactionId2(MO_Context *ctx, unsigned int evseId) { auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; if (!txSvcEvse) { MO_DBG_ERR("init failure"); - return -1; + return nullptr; } if (auto tx = txSvcEvse->getTransaction()) { - res = tx->getTransactionId(); + res = tx->getTransactionIdCompat(); } } #endif #if MO_ENABLE_V201 if (context->getOcppVersion() == MO_OCPP_V201) { - MO_DBG_ERR("mo_v16_getTransactionId not supported with OCPP 2.0.1"); + auto txSvc = context->getModel201().getTransactionService(); + auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; + if (!txSvcEvse) { + MO_DBG_ERR("init failure"); + return nullptr; + } + if (auto tx = txSvcEvse->getTransaction()) { + res = tx->transactionId; + } } #endif - return res; + return res && *res ? res : nullptr; } -#endif //MO_ENABLE_V16 +#endif //MO_ENABLE_V201 -#if MO_ENABLE_V201 -const char *mo_v201_getTransactionId() { - return mo_v201_getTransactionId2(mo_getApiContext(), EVSE_ID_1); +#if MO_ENABLE_V16 +int mo_v16_getTransactionId() { + return mo_v16_getTransactionId2(mo_getApiContext(), EVSE_ID_1); } -const char *mo_v201_getTransactionId2(MO_Context *ctx, unsigned int evseId) { +int mo_v16_getTransactionId2(MO_Context *ctx, unsigned int evseId) { if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before - return nullptr; + return -1; } auto context = mo_getContext2(ctx); - const char *res = nullptr; + int res = -1; #if MO_ENABLE_V16 if (context->getOcppVersion() == MO_OCPP_V16) { - MO_DBG_ERR("mo_v201_getTransactionId not supported with OCPP 2.0.1"); - } - #endif - #if MO_ENABLE_V201 - if (context->getOcppVersion() == MO_OCPP_V201) { - auto txSvc = context->getModel201().getTransactionService(); + auto txSvc = context->getModel16().getTransactionService(); auto txSvcEvse = txSvc ? txSvc->getEvse(evseId) : nullptr; if (!txSvcEvse) { MO_DBG_ERR("init failure"); - return nullptr; + return -1; } if (auto tx = txSvcEvse->getTransaction()) { - res = tx->transactionId; + res = tx->getTransactionId(); } } #endif + #if MO_ENABLE_V201 + if (context->getOcppVersion() == MO_OCPP_V201) { + MO_DBG_ERR("mo_v16_getTransactionId not supported with OCPP 2.0.1"); + } + #endif return res; } -#endif //MO_ENABLE_V201 +#endif //MO_ENABLE_V16 MO_ChargePointStatus mo_getChargePointStatus() { return mo_getChargePointStatus2(mo_getApiContext(), EVSE_ID_1); @@ -1562,7 +1570,7 @@ bool mo_isOperative2(MO_Context *ctx, unsigned int evseId) { MO_DBG_ERR("init failure"); return false; } - result = availSvcEvse->isAvailable(); + result = availSvcEvse->isOperative(); } #endif diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index f154ac0a..ed111b92 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -303,6 +303,15 @@ bool mo_isTransactionRunning2(MO_Context *ctx, unsigned int evseId); const char *mo_getTransactionIdTag(); const char *mo_getTransactionIdTag2(MO_Context *ctx, unsigned int evseId); +#if MO_ENABLE_V201 +//Get the transactionId of the currently pending transaction, formatted as c-string. Returns NULL +//if no transaction is running. The returned string must be copied into own buffer, as it may be +//invalidated after call. +//Backwards-compatible: if initialized with OCPP 1.6, returns txId formatted as c-string +const char *mo_getTransactionId(); +const char *mo_getTransactionId2(MO_Context *ctx, unsigned int evseId); +#endif //MO_ENABLE_V201 + #if MO_ENABLE_V16 //Get the transactionId once the StartTransaction.conf from the server has arrived. Otherwise, //returns -1 @@ -310,13 +319,6 @@ int mo_v16_getTransactionId(); int mo_v16_getTransactionId2(MO_Context *ctx, unsigned int evseId); #endif //MO_ENABLE_V16 -#if MO_ENABLE_V201 -//Get the transactionId of the currently pending transaction. Returns NULL if no transaction is -//running. The returned string must be copied into own buffer, as it may be invalidated after call. -const char *mo_v201_getTransactionId(); -const char *mo_v201_getTransactionId2(MO_Context *ctx, unsigned int evseId); -#endif //MO_ENABLE_V201 - /* * Returns the latest MO_ChargePointStatus as reported via StatusNotification (standard OCPP data type) */ diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index ef32a062..5ad2d268 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -40,6 +40,8 @@ Context::~Context() { #if MO_WS_USE != MO_WS_CUSTOM mo_connectionConfig_deinit(&connectionConfig); #endif //MO_WS_USE != MO_WS_CUSTOM + + MO_DBG_INFO("MicroOCPP deinitialized\n"); } void Context::setDebugCb(void (*debugCb)(const char *msg)) { @@ -320,16 +322,24 @@ bool Context::setup() { #if MO_ENABLE_V16 if (ocppVersion == MO_OCPP_V16) { - modelV16.setup(); + if (!modelV16.setup()) { + MO_DBG_ERR("setup failure"); + return false; + } } #endif #if MO_ENABLE_V201 if (ocppVersion == MO_OCPP_V201) { - modelV201.setup(); + if (!modelV201.setup()) { + MO_DBG_ERR("setup failure"); + return false; + } } #endif - MO_DBG_INFO("MicroOCPP setup complete"); + MO_DBG_INFO("MicroOCPP setup complete. Run %s (MO version %s)", + ocppVersion == MO_OCPP_V16 ? "OCPP 1.6" : ocppVersion == MO_OCPP_V201 ? "OCPP 2.0.1" : "(OCPP version error)", + MO_VERSION); return true; } diff --git a/src/MicroOcpp/Core/MessageService.cpp b/src/MicroOcpp/Core/MessageService.cpp index d5accd9a..7732cf3e 100644 --- a/src/MicroOcpp/Core/MessageService.cpp +++ b/src/MicroOcpp/Core/MessageService.cpp @@ -83,7 +83,9 @@ void MessageService::loop() { bool success = connection->sendTXT(out.c_str(), out.length()); if (success) { - MO_DBG_DEBUG("Send %s", out.c_str()); + #ifdef MO_TRAFFIC_OUT + MO_DBG_INFO("Send %s", out.c_str()); + #endif recvReqFront.reset(); } @@ -342,7 +344,9 @@ std::unique_ptr MessageService::createRequest(const char *operationType bool MessageService::receiveMessage(const char* payload, size_t length) { - MO_DBG_DEBUG("Recv %.*s", (int)length, payload); + #ifdef MO_TRAFFIC_OUT + MO_DBG_INFO("Recv %.*s", (int)length, payload); + #endif size_t capacity_init = (3 * length) / 2; diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index 4e9d6af4..b4e063d7 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -602,6 +602,10 @@ bool Clock::fromUnixTime(Timestamp& dst, int32_t unixTimeInt) const { return false; } +#if MO_ENABLE_TIMESTAMP_MILLISECONDS + t.ms = 0; +#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS + dst = t; return true; } diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 2015f57d..5188d7b2 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -440,6 +440,10 @@ Ocpp201::AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, Avai void Ocpp201::AvailabilityServiceEvse::loop() { + if (!trackLoopExecute) { + trackLoopExecute = true; + } + if (evseId >= 1) { auto status = getStatus(); @@ -559,6 +563,18 @@ bool Ocpp201::AvailabilityServiceEvse::isAvailable() { return true; } +bool Ocpp201::AvailabilityServiceEvse::isOperative() { + if (isFaulted()) { + return false; + } + + if (!trackLoopExecute) { + return false; + } + + return isAvailable(); +} + bool Ocpp201::AvailabilityServiceEvse::isFaulted() { //for (auto i = errorDataInputs.begin(); i != errorDataInputs.end(); ++i) { for (size_t i = 0; i < faultedInputs.size(); i++) { diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h index 2dbe61ab..b2fd8832 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -165,6 +165,8 @@ class AvailabilityServiceEvse : public MemoryManaged { Vector faultedInputs; MO_ChargePointStatus reportedStatus = MO_ChargePointStatus_UNDEFINED; + + bool trackLoopExecute = false; //if loop has been executed once public: AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId); @@ -185,6 +187,7 @@ class AvailabilityServiceEvse : public MemoryManaged { Operation *createTriggeredStatusNotification(); bool isAvailable(); + bool isOperative(); bool isFaulted(); }; diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 12f9c948..f63ba6bd 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -298,77 +298,92 @@ SecurityEventService *Ocpp16::Model::getSecurityEventService() { bool Ocpp16::Model::setup() { if (!getBootService() || !getBootService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } if (!getHeartbeatService() || !getHeartbeatService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } if (!getTransactionService() || !getTransactionService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } if (!getMeteringService() || !getMeteringService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } if (!getResetService() || !getResetService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } if (!getAvailabilityService() || !getAvailabilityService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } if (!getRemoteControlService() || !getRemoteControlService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #if MO_ENABLE_FIRMWAREMANAGEMENT if (!getFirmwareService() || !getFirmwareService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } #endif //MO_ENABLE_FIRMWAREMANAGEMENT #if MO_ENABLE_DIAGNOSTICS if (!getDiagnosticsService() || !getDiagnosticsService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_LOCAL_AUTH if (!getAuthorizationService() || !getAuthorizationService()->setup()) { - return false; //OOM or memory corruption + MO_DBG_ERR("setup failure"); + return false; } #endif //MO_ENABLE_LOCAL_AUTH #if MO_ENABLE_RESERVATION if (!getReservationService() || !getReservationService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_RESERVATION #if MO_ENABLE_SMARTCHARGING if (!getSmartChargingService() || !getSmartChargingService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT if (!getCertificateService() || !getCertificateService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_SECURITY_EVENT if (!getSecurityEventService() || !getSecurityEventService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_SECURITY_EVENT // Ensure this is set up last. ConfigurationService::setup() loads the persistent config values from flash if (!getConfigurationService() || !getConfigurationService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } @@ -599,58 +614,70 @@ SecurityEventService *Ocpp201::Model::getSecurityEventService() { bool Ocpp201::Model::setup() { if (!getBootService() || !getBootService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } if (!getHeartbeatService() || !getHeartbeatService()->setup()) { - return false; //OOM + MO_DBG_ERR("setup failure"); + return false; } if (!getTransactionService() || !getTransactionService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } if (!getMeteringService() || !getMeteringService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } if (!getAvailabilityService() || !getAvailabilityService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } if (!getRemoteControlService() || !getRemoteControlService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } if (!getResetService() || !getResetService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #if MO_ENABLE_DIAGNOSTICS if (!getDiagnosticsService() || !getDiagnosticsService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_SMARTCHARGING if (!getSmartChargingService() || !getSmartChargingService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT if (!getCertificateService() || !getCertificateService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_SECURITY_EVENT if (!getSecurityEventService() || !getSecurityEventService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } #endif //MO_ENABLE_SECURITY_EVENT // Ensure this is set up last. VariableService::setup() loads the persistent variable values from flash if (!getVariableService() || !getVariableService()->setup()) { + MO_DBG_ERR("setup failure"); return false; } diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index 9f90c314..24560bdb 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -220,7 +220,7 @@ bool RemoteControlService::setup() { numEvseId = context.getModel201().getNumEvseId(); context.getMessageService().registerOperation("RequestStartTransaction", [] (Context& context) -> Operation* { - return new Ocpp201::RequestStartTransaction(*context.getModel201().getRemoteControlService());}); + return new Ocpp201::RequestStartTransaction(context, *context.getModel201().getRemoteControlService());}); context.getMessageService().registerOperation("RequestStopTransaction", [] (Context& context) -> Operation* { return new Ocpp201::RequestStopTransaction(*context.getModel201().getRemoteControlService());}); @@ -410,7 +410,7 @@ Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int tr #if MO_ENABLE_V201 -Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize) { +Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize) { if (!txService201) { MO_DBG_ERR("TxService uninitialized"); @@ -423,7 +423,7 @@ Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(un return Ocpp201::RequestStartStopStatus::Rejected; } - if (!evse->beginAuthorization(idToken, authorizeRemoteStart->getBool())) { + if (!evse->beginAuthorization(idToken, authorizeRemoteStart->getBool(), nullptr, /*commit*/ false)) { //only commit after storing the ChargingProfile MO_DBG_INFO("EVSE still occupied with pending tx"); if (auto tx = evse->getTransaction()) { auto ret = snprintf(transactionIdOut, transactionIdBufSize, "%s", tx->transactionId); @@ -435,22 +435,57 @@ Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(un return Ocpp201::RequestStartStopStatus::Rejected; } - auto tx = evse->getTransaction(); + int ret = -1; + + Ocpp201::Transaction *tx = evse->getTransaction(); if (!tx) { - MO_DBG_ERR("internal error"); - return Ocpp201::RequestStartStopStatus::Rejected; + goto fail; + } + + #if MO_ENABLE_SMARTCHARGING + if (chargingProfile && context.getModel201().getSmartChargingService()) { + auto scService = context.getModel201().getSmartChargingService(); + + auto ret = snprintf(chargingProfile->transactionId201, sizeof(chargingProfile->transactionId201), "%s", tx->transactionId); + if (ret < 0 || (size_t)ret >= transactionIdBufSize) { + MO_DBG_ERR("internal error"); + goto fail; + } + + bool success = scService->setChargingProfile(evseId, std::move(chargingProfile)); + if (!success) { + MO_DBG_ERR("setChargingProfile"); + goto fail; + } + } + #endif //MO_ENABLE_SMARTCHARGING - auto ret = snprintf(transactionIdOut, transactionIdBufSize, "%s", tx->transactionId); + ret = snprintf(transactionIdOut, transactionIdBufSize, "%s", tx->transactionId); if (ret < 0 || (size_t)ret >= transactionIdBufSize) { MO_DBG_ERR("internal error"); - return Ocpp201::RequestStartStopStatus::Rejected; + goto fail; } tx->remoteStartId = remoteStartId; tx->notifyRemoteStartId = true; + if (!evse->commitTransaction()) { + MO_DBG_ERR("internal error"); + goto fail; + + } + return Ocpp201::RequestStartStopStatus::Accepted; +fail: + evse->abortTransaction(); + #if MO_ENABLE_SMARTCHARGING + if (chargingProfile && context.getModel201().getSmartChargingService()) { + auto scService = context.getModel201().getSmartChargingService(); + scService->clearChargingProfile(chargingProfile->chargingProfileId,evseId,MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1); + } + #endif //MO_ENABLE_SMARTCHARGING + return Ocpp201::RequestStartStopStatus::Rejected; } Ocpp201::RequestStartStopStatus RemoteControlService::requestStopTransaction(const char *transactionId) { diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h index e7d4d7de..1bcf9bf7 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -114,7 +114,7 @@ class RemoteControlService : public MemoryManaged { #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 - Ocpp201::RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize); //ChargingProfile, GroupIdToken not supported yet + Ocpp201::RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize); //ChargingProfile, GroupIdToken not supported yet Ocpp201::RequestStartStopStatus requestStopTransaction(const char *transactionId); #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Reservation/ReservationService.cpp b/src/MicroOcpp/Model/Reservation/ReservationService.cpp index 893eb997..34ccb299 100644 --- a/src/MicroOcpp/Model/Reservation/ReservationService.cpp +++ b/src/MicroOcpp/Model/Reservation/ReservationService.cpp @@ -60,7 +60,7 @@ bool ReservationService::setup() { context.getMessageService().registerOperation("ReserveNow", [] (Context& context) -> Operation* { return new ReserveNow(context, *context.getModel16().getReservationService());}); - return false; + return true; } void ReservationService::loop() { diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp index 26fab4dc..a2365bff 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp @@ -310,14 +310,14 @@ bool ChargingSchedule::parseJson(Clock& clock, int ocppVersion, JsonObject json) return false; } - period.limit = json["limit"] | -1.f; + period.limit = periodJson["limit"] | -1.f; if (period.limit < 0.f) { MO_DBG_WARN("format violation"); return false; } if (json.containsKey("numberPhases")) { - period.numberPhases = json["numberPhases"] | -1; + period.numberPhases = periodJson["numberPhases"] | -1; if (period.numberPhases < 0 || period.numberPhases > 3) { MO_DBG_WARN("format violation"); return false; @@ -387,7 +387,6 @@ bool ChargingSchedule::toJson(Clock& clock, int ocppVersion, bool compositeSched MO_DBG_ERR("serialization error"); return false; } - char startScheduleStr [MO_JSONDATE_SIZE] = {'\0'}; if (!clock.toJsonString(startSchedule2, startScheduleStr, sizeof(startScheduleStr))) { MO_DBG_ERR("serialization error"); return false; @@ -491,7 +490,17 @@ bool ChargingProfile::calculateLimit(int32_t unixTime, int32_t sessionDurationSe bool ChargingProfile::parseJson(Clock& clock, int ocppVersion, JsonObject json) { - chargingProfileId = json["chargingProfileId"] | -1; + #if MO_ENABLE_V16 + if (ocppVersion == MO_OCPP_V16) { + chargingProfileId = json["chargingProfileId"] | -1; + } + #endif //MO_ENABLE_V16 + #if MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V201) { + chargingProfileId = json["id"] | -1; + } + #endif //MO_ENABLE_V201 + if (chargingProfileId < 0) { MO_DBG_WARN("format violation"); return false; @@ -606,10 +615,9 @@ size_t ChargingProfile::getJsonCapacity(int ocppVersion) { bool ChargingProfile::toJson(Clock& clock, int ocppVersion, JsonObject out) { - out["chargingProfileId"] = chargingProfileId; - #if MO_ENABLE_V16 if (ocppVersion == MO_OCPP_V16) { + out["chargingProfileId"] = chargingProfileId; if (transactionId16 >= 0) { out["transactionId"] = transactionId16; } @@ -617,6 +625,7 @@ bool ChargingProfile::toJson(Clock& clock, int ocppVersion, JsonObject out) { #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 if (ocppVersion == MO_OCPP_V201) { + out["id"] = chargingProfileId; if (*transactionId201) { out["transactionId"] = (const char*)transactionId201; //force zero-copy } diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index cf1eef0f..da0321e7 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -179,7 +179,7 @@ void SmartChargingServiceEvse::trackTransaction() { trackTxRmtProfileId = tx->getTxProfileId(); } int32_t dtTxStart; - if (tx->getStartSync().isRequested() && clock.delta(tx->getStartTimestamp(), trackTxStart, dtTxStart) && dtTxStart != 0) { + if (tx->getStartSync().isRequested() && (!trackTxStart.isDefined() || (clock.delta(tx->getStartTimestamp(), trackTxStart, dtTxStart) && dtTxStart != 0))) { update = true; trackTxStart = tx->getStartTimestamp(); } @@ -359,6 +359,8 @@ bool SmartChargingServiceEvse::updateProfile(std::unique_ptr ch int stackLevel = chargingProfile->stackLevel; //already validated + delete stack[stackLevel]; + stack[stackLevel] = nullptr; stack[stackLevel] = chargingProfile.release(); return true; @@ -518,18 +520,12 @@ SmartChargingService::~SmartChargingService() { bool SmartChargingService::setup() { - ocppVersion = context.getOcppVersion(); - - #if MO_ENABLE_V16 - if (ocppVersion == MO_OCPP_V16) { - + filesystem = context.getFilesystem(); + if (!filesystem) { + MO_DBG_DEBUG("volatile mode"); } - #endif //MO_ENABLE_V16 - #if MO_ENABLE_V201 - if (ocppVersion == MO_OCPP_V201) { - } - #endif //MO_ENABLE_V201 + ocppVersion = context.getOcppVersion(); #if MO_ENABLE_V16 if (ocppVersion == MO_OCPP_V16) { @@ -556,6 +552,8 @@ bool SmartChargingService::setup() { if (phases3to1Supported) { configService->declareConfiguration("ConnectorSwitch3to1PhaseSupported", phases3to1Supported, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); } + + numEvseId = context.getModel16().getNumEvseId(); } #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 @@ -594,6 +592,8 @@ bool SmartChargingService::setup() { varService->declareVariable("SmartChargingCtrlr", "Phases3to1", phases3to1Supported, Mutability::ReadOnly, false); } varService->declareVariable("SmartChargingCtrlr", "SmartChargingAvailable", true, Mutability::ReadOnly, false); + + numEvseId = context.getModel201().getNumEvseId(); } #endif //MO_ENABLE_V201 @@ -618,7 +618,6 @@ bool SmartChargingService::setup() { } #endif //MO_ENABLE_V201 - numEvseId = context.getModel16().getNumEvseId(); for (unsigned int i = 1; i < numEvseId; i++) { //evseId 0 won't be populated if (!getEvse(i)) { MO_DBG_ERR("OOM"); @@ -1158,7 +1157,7 @@ bool SmartChargingServiceUtils::storeProfile(MO_FilesystemAdapter *filesystem, C auto capacity = chargingProfile->getJsonCapacity(ocppVersion); auto chargingProfileJson = initJsonDoc("v16.SmartCharging.ChargingProfile", capacity); - if (!chargingProfile->toJson(clock, ocppVersion, chargingProfileJson.as())) { + if (!chargingProfile->toJson(clock, ocppVersion, chargingProfileJson.to())) { return false; } diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h index 44ed7ccc..dcefe9f5 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h @@ -111,8 +111,6 @@ class SmartChargingService : public MemoryManaged { bool updateProfile(unsigned int evseId, std::unique_ptr chargingProfile, bool updateFile); bool loadProfiles(); - size_t getChargingProfilesCount(); - void calculateLimit(int32_t unixTime, MO_ChargeRate& limit, int32_t& nextChangeSecs); public: @@ -131,6 +129,8 @@ class SmartChargingService : public MemoryManaged { std::unique_ptr getCompositeSchedule(unsigned int evseId, int duration, ChargingRateUnitType unit = ChargingRateUnitType::UNDEFINED); + size_t getChargingProfilesCount(); + friend class SmartChargingServiceEvse; }; diff --git a/src/MicroOcpp/Model/Transactions/Transaction.cpp b/src/MicroOcpp/Model/Transactions/Transaction.cpp index d2d4d81e..039ac0fe 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.cpp +++ b/src/MicroOcpp/Model/Transactions/Transaction.cpp @@ -24,6 +24,17 @@ Ocpp16::Transaction::~Transaction() { meterValues.clear(); } +void Ocpp16::Transaction::setTransactionId(int transactionId) { + this->transactionId = transactionId; + #if MO_ENABLE_V201 + if (transactionId > 0) { + snprintf(transactionIdCompat, sizeof(transactionIdCompat), "%d", transactionId); + } else { + transactionIdCompat[0] = '\0'; + } + #endif //MO_ENABLE_V201 +} + bool Ocpp16::Transaction::setIdTag(const char *idTag) { auto ret = snprintf(this->idTag, MO_IDTAG_LEN_MAX + 1, "%s", idTag); return ret >= 0 && ret < MO_IDTAG_LEN_MAX + 1; diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 2e0bf15c..a794b98b 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -82,6 +82,10 @@ class Transaction : public MemoryManaged { Timestamp start_timestamp; //timestamp of StartTx; can be set before actually initiating int transactionId = -1; //only valid if confirmed = true + #if MO_ENABLE_V201 + char transactionIdCompat [12] = {'\0'}; //v201 compat: provide txId as string too. Buffer dimensioned to hold max int value + #endif //MO_ENABLE_V201 + /* * Attributes of StopTransaction */ @@ -110,6 +114,9 @@ class Transaction : public MemoryManaged { * data assigned by OCPP server */ int getTransactionId() {return transactionId;} + #if MO_ENABLE_V201 + const char *getTransactionIdCompat() {return transactionIdCompat;} + #endif //MO_ENABLE_V201 bool isAuthorized() {return authorized;} //Authorize has been accepted bool isIdTagDeauthorized() {return deauthorized;} //StartTransaction has been rejected @@ -153,7 +160,7 @@ class Transaction : public MemoryManaged { void setStartTimestamp(Timestamp timestamp) {start_timestamp = timestamp;} const Timestamp& getStartTimestamp() {return start_timestamp;} - void setTransactionId(int transactionId) {this->transactionId = transactionId;} + void setTransactionId(int transactionId); SendStatus& getStopSync() {return stop_sync;} diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index c87aa468..a15e4357 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -667,7 +667,7 @@ void TransactionServiceEvse::updateTxNotification(MO_TxNotification event) { } } -bool TransactionServiceEvse::beginAuthorization(IdToken idToken, bool validateIdToken, IdToken groupIdToken) { +bool TransactionServiceEvse::beginAuthorization(IdToken idToken, bool validateIdToken, IdToken groupIdToken, bool commit) { MO_DBG_DEBUG("begin auth: %s", idToken.get()); if (transaction && transaction->isAuthorizationActive) { @@ -694,6 +694,13 @@ bool TransactionServiceEvse::beginAuthorization(IdToken idToken, bool validateId transaction->idToken = idToken; transaction->beginTimestamp = clock.now(); + if (commit) { + if (!txStore.commit(transaction.get())) { + MO_DBG_ERR("fs error"); + return false; + } + } + if (validateIdToken) { auto authorize = makeRequest(context, new Authorize(context.getModel201(), idToken)); if (!authorize) { @@ -850,6 +857,14 @@ Transaction *TransactionServiceEvse::getTransaction() { return transaction.get(); } +bool TransactionServiceEvse::commitTransaction() { + if (!transaction) { + MO_DBG_ERR("no tx to commit"); + return false; + } + return txStore.commit(transaction.get()); +} + bool TransactionServiceEvse::ocppPermitsCharge() { return transaction && transaction->active && diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.h b/src/MicroOcpp/Model/Transactions/TransactionService201.h index c624b1c3..98a9612c 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.h @@ -108,13 +108,14 @@ class TransactionServiceEvse : public RequestQueue, public MemoryManaged { void setTxNotificationOutput(void (*txNotificationOutput)(MO_TxNotification, unsigned int, void*), void *userData); void updateTxNotification(MO_TxNotification event); - bool beginAuthorization(IdToken idToken, bool validateIdToken = true, IdToken groupIdToken = nullptr); // authorize by swipe RFID, groupIdToken ignored if validateIdToken = true + bool beginAuthorization(IdToken idToken, bool validateIdToken = true, IdToken groupIdToken = nullptr, bool commit = true); // authorize by swipe RFID, groupIdToken ignored if validateIdToken = true, tx not stored on flash if commit = false bool endAuthorization(IdToken idToken = IdToken(), bool validateIdToken = false, IdToken groupIdToken = nullptr); // stop authorization by swipe RFID, groupIdToken ignored if validateIdToken = true // stop transaction, but neither upon user request nor OCPP server request (e.g. after PowerLoss) bool abortTransaction(MO_TxStoppedReason stoppedReason = MO_TxStoppedReason_Other, MO_TxEventTriggerReason stopTrigger = MO_TxEventTriggerReason_AbnormalCondition); Transaction *getTransaction(); + bool commitTransaction(); bool ocppPermitsCharge(); diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index f26e382e..ab899f00 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -964,11 +964,6 @@ std::unique_ptr Ocpp201::TransactionStoreEvse::createTrans return nullptr; } - if (!commit(transaction.get())) { - MO_DBG_ERR("FS error"); - return nullptr; - } - return transaction; } diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.cpp b/src/MicroOcpp/Operations/RequestStartTransaction.cpp index 00e459c5..a517cf28 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStartTransaction.cpp @@ -3,7 +3,11 @@ // MIT License #include + +#include +#include #include +#include #include #if MO_ENABLE_V201 @@ -11,7 +15,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::Ocpp201; -RequestStartTransaction::RequestStartTransaction(RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStartTransaction"), rcService(rcService) { +RequestStartTransaction::RequestStartTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStartTransaction"), context(context), rcService(rcService) { } @@ -41,7 +45,31 @@ void RequestStartTransaction::processReq(JsonObject payload) { return; } - status = rcService.requestStartTransaction(evseId, remoteStartId, idToken, transactionId, sizeof(transactionId)); + std::unique_ptr chargingProfile; + if (payload.containsKey("chargingProfile")) { + #if MO_ENABLE_SMARTCHARGING + if (context.getModel201().getSmartChargingService()) { + JsonObject chargingProfileJson = payload["chargingProfile"]; + chargingProfile = std::unique_ptr(new ChargingProfile()); + if (!chargingProfile) { + MO_DBG_ERR("OOM"); + errorCode = "InternalError"; + return; + } + + bool valid = chargingProfile->parseJson(context.getClock(), MO_OCPP_V201, chargingProfileJson); + if (!valid) { + errorCode = "FormationViolation"; + return; + } + } else + #endif + { + MO_DBG_INFO("ignore ChargingProfile"); //see F01.FR.12 + } + } + + status = rcService.requestStartTransaction(evseId, remoteStartId, idToken, std::move(chargingProfile), transactionId, sizeof(transactionId)); } std::unique_ptr RequestStartTransaction::createConf(){ diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.h b/src/MicroOcpp/Operations/RequestStartTransaction.h index e2bd1715..9a21749b 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.h +++ b/src/MicroOcpp/Operations/RequestStartTransaction.h @@ -15,12 +15,14 @@ namespace MicroOcpp { +class Context; class RemoteControlService; namespace Ocpp201 { class RequestStartTransaction : public Operation, public MemoryManaged { private: + Context& context; RemoteControlService& rcService; RequestStartStopStatus status; @@ -29,7 +31,7 @@ class RequestStartTransaction : public Operation, public MemoryManaged { const char *errorCode = nullptr; public: - RequestStartTransaction(RemoteControlService& rcService); + RequestStartTransaction(Context& context, RemoteControlService& rcService); const char* getOperationType() override; diff --git a/tests/ChargingSessions.cpp b/tests/ChargingSessions.cpp index c553d503..84d31ccc 100644 --- a/tests/ChargingSessions.cpp +++ b/tests/ChargingSessions.cpp @@ -3,250 +3,374 @@ // MIT License #include -#include -#include +#include +#include #include -#include -#include -#include -#include -#include -#include +#include #include #include #include "./helpers/testHelper.h" -#include +#include -#define BASE_TIME "2023-01-01T00:00:00.000Z" +#define BASE_TIME_UNIX 1750000000 +#define BASE_TIME_STRING "2025-06-15T15:06:40Z" using namespace MicroOcpp; +std::vector> sentOperations; + +void addSentOperation(const char *operationType, const char *payloadJson, void*) { + DynamicJsonDocument doc (1024); + auto err = deserializeJson(doc, payloadJson); + if (err) { + char buf [100]; + snprintf(buf, sizeof(buf), "JSON deserialization error: %s", err.c_str()); + FAIL(buf); + } + sentOperations.emplace_back(operationType, std::move(doc)); +} + +std::string getLastSentStatus(int ocppVersion, int evseId = 1) { + for (auto operation = sentOperations.rbegin(); operation != sentOperations.rend(); operation++) { + if (operation->first == "StatusNotification") { + if (ocppVersion == MO_OCPP_V16) { + if ((operation->second["connectorId"] | -1) != evseId) { + continue; + } + //found + return operation->second["status"] | "_Invalid"; + } else if (ocppVersion == MO_OCPP_V201) { + if ((operation->second["evseId"] | -1) != evseId) { + continue; + } + //found + return operation->second["connectorStatus"] | "_Invalid"; + } + } + } + return ""; +} TEST_CASE( "Charging sessions" ) { printf("\nRun %s\n", "Charging sessions"); - //initialize Context with dummy socket + sentOperations.clear(); + + //initialize Context without any configs + mo_initialize(); + + mo_getContext()->setTicksCb(custom_timer_cb); + LoopbackConnection loopback; - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); - - auto engine = getOcppContext(); - auto& checkMsg = engine->getOperationRegistry(); - - mocpp_set_timer(custom_timer_cb); - - auto connectionTimeOutInt = declareConfiguration("ConnectionTimeOut", 30, CONFIGURATION_FN); - connectionTimeOutInt->setInt(30); - auto minimumStatusDurationInt = declareConfiguration("MinimumStatusDuration", 0, CONFIGURATION_FN); - minimumStatusDurationInt->setInt(0); - - std::array expectedSN {"Available", "Available"}; - std::array checkedSN {false, false}; - checkMsg.registerOperation("StatusNotification", [] () -> Operation* {return new Ocpp16::StatusNotification(0, ChargePointStatus_UNDEFINED, MIN_TIME);}); - checkMsg.setOnRequest("StatusNotification", - [&checkedSN, &expectedSN] (JsonObject request) { - int connectorId = request["connectorId"] | -1; - if (connectorId == 0 || connectorId == 1) { //only test single connector case here - checkedSN[connectorId] = !strcmp(request["status"] | "Invalid", expectedSN[connectorId]); - } - }); + mo_getContext()->setConnection(&loopback); - SECTION("Check idle state"){ + auto ocppVersion = GENERATE(MO_OCPP_V16, MO_OCPP_V201); + mo_setOcppVersion(ocppVersion); - bool checkedBN = false; - checkMsg.registerOperation("BootNotification", [engine] () -> Operation* {return new Ocpp16::BootNotification(engine->getModel(), makeJsonDoc("UnitTests"));}); - checkMsg.setOnRequest("BootNotification", - [&checkedBN] (JsonObject request) { - checkedBN = !strcmp(request["chargePointModel"] | "Invalid", "test-runner1234"); - }); + mo_setBootNotificationData("TestModel", "TestVendor"); + + mo_setup(); + + mo_setOnReceiveRequest(mo_getApiContext(), "BootNotification", addSentOperation, nullptr); + mo_setOnReceiveRequest(mo_getApiContext(), "StatusNotification", addSentOperation, nullptr); + mo_setOnReceiveRequest(mo_getApiContext(), "StartTransaction", addSentOperation, nullptr); + mo_setOnReceiveRequest(mo_getApiContext(), "StopTransaction", addSentOperation, nullptr); + mo_setOnReceiveRequest(mo_getApiContext(), "TransactionEvent", addSentOperation, nullptr); + + mo_setVarConfigInt(mo_getApiContext(), "TxCtrlr", "EVConnectionTimeOut", "ConnectionTimeOut", 30); + + if (ocppVersion == MO_OCPP_V16) { + mo_setConfigurationInt("MinimumStatusDuration", 0); + } else if (ocppVersion == MO_OCPP_V201) { + mo_setVarConfigString(mo_getApiContext(), "TxCtrlr", "TxStartPoint", NULL, "PowerPathClosed"); + mo_setVarConfigString(mo_getApiContext(), "TxCtrlr", "TxStopPoint", NULL, "PowerPathClosed"); + } + + SECTION("Clean up files") { + auto filesystem = mo_getFilesystem(); + FilesystemUtils::removeByPrefix(filesystem, ""); + } + + SECTION("Check idle state"){ - REQUIRE( !isOperative() ); //not operative before reaching loop stage + REQUIRE( !mo_isOperative() ); //not operative before reaching loop stage loop(); loop(); - REQUIRE( checkedBN ); - REQUIRE( checkedSN[0] ); - REQUIRE( checkedSN[1] ); - REQUIRE( isOperative() ); - REQUIRE( !getTransaction() ); - REQUIRE( !ocppPermitsCharge() ); - } - loop(); + size_t bootNotificationCount = 0; - SECTION("StartTx") { - SECTION("StartTx directly"){ - startTransaction("mIdTag"); - loop(); - REQUIRE(ocppPermitsCharge()); + for (size_t i = 0; i < sentOperations.size(); i++) { + if (sentOperations[i].first == "BootNotification") { + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(sentOperations[i].second["chargePointModel"].as() == "TestModel"); + REQUIRE(sentOperations[i].second["chargePointVendor"].as() == "TestVendor"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(sentOperations[i].second["chargingStation"]["model"].as() == "TestModel"); + REQUIRE(sentOperations[i].second["chargingStation"]["vendorName"].as() == "TestVendor"); + } + bootNotificationCount++; + } } + REQUIRE( bootNotificationCount == 1 ); + REQUIRE( mo_isOperative() ); + REQUIRE( !mo_isTransactionActive() ); + REQUIRE( !mo_isTransactionRunning() ); + REQUIRE( !mo_ocppPermitsCharge() ); + } + + SECTION("StartTx") { + + loop(); SECTION("StartTx via session management - plug in first") { - expectedSN[1] = "Preparing"; - setConnectorPluggedInput([] () {return true;}); + + sentOperations.clear(); + mo_setConnectorPluggedInput([] () {return true;}); loop(); - REQUIRE(checkedSN[1]); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Preparing"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == "Occupied"); + } - checkedSN[1] = false; - expectedSN[1] = "Charging"; - beginTransaction("mIdTag"); + sentOperations.clear(); + mo_beginTransaction("mIdTag"); loop(); - REQUIRE(checkedSN[1]); - REQUIRE(ocppPermitsCharge()); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Charging"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == ""); + } } SECTION("StartTx via session management - authorization first") { - expectedSN[1] = "Preparing"; - setConnectorPluggedInput([] () {return false;}); - beginTransaction("mIdTag"); + sentOperations.clear(); + mo_setConnectorPluggedInput([] () {return false;}); + mo_beginTransaction("mIdTag"); loop(); - REQUIRE(checkedSN[1]); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Preparing"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == ""); + } - checkedSN[1] = false; - expectedSN[1] = "Charging"; - setConnectorPluggedInput([] () {return true;}); + REQUIRE(mo_getTransactionIdTag() != nullptr); + REQUIRE(std::string(mo_getTransactionIdTag()) == "mIdTag"); + + sentOperations.clear(); + mo_setConnectorPluggedInput([] () {return true;}); loop(); - REQUIRE(checkedSN[1]); - REQUIRE(ocppPermitsCharge()); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Charging"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == "Occupied"); + } } SECTION("StartTx via session management - no plug") { - expectedSN[1] = "Charging"; - beginTransaction("mIdTag"); + sentOperations.clear(); + mo_beginTransaction("mIdTag"); loop(); - REQUIRE(checkedSN[1]); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Charging"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == ""); + } } - SECTION("StartTx via session management - ConnectionTimeOut") { - expectedSN[1] = "Preparing"; - setConnectorPluggedInput([] () {return false;}); - beginTransaction("mIdTag"); - loop(); - REQUIRE(checkedSN[1]); - - checkedSN[1] = false; - expectedSN[1] = "Available"; - mtime += connectionTimeOutInt->getInt() * 1000; - loop(); - REQUIRE(checkedSN[1]); + REQUIRE(mo_ocppPermitsCharge()); + REQUIRE(mo_getTransactionId() != nullptr); + REQUIRE(!std::string(mo_getTransactionId()).empty()); + REQUIRE(mo_isTransactionActive()); + REQUIRE(mo_isTransactionRunning()); + + bool startTxSent = false; + for (auto& operation : sentOperations) { + if (operation.first == "StartTransaction") { + startTxSent = true; + break; + } else if (operation.first == "TransactionEvent") { + if (operation.second["eventType"].as() == "Started") { + startTxSent = true; + break; + } + } } + REQUIRE(startTxSent); + + mo_endTransaction(nullptr, nullptr); + loop(); + } + + SECTION("StartTx - ConnectionTimeOut") { + + loop(); + sentOperations.clear(); + mo_setConnectorPluggedInput([] () {return false;}); + mo_beginTransaction("mIdTag"); loop(); - if (ocppPermitsCharge()) { - stopTransaction(); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Preparing"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == ""); } + + REQUIRE(mo_getTransactionIdTag() != nullptr); + REQUIRE(std::string(mo_getTransactionIdTag()) == "mIdTag"); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(mo_getTransactionId() == nullptr); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(mo_getTransactionId() != nullptr); + REQUIRE(!std::string(mo_getTransactionId()).empty()); + } + + sentOperations.clear(); + int connectionTimeOut = 0; + mo_getVarConfigInt(mo_getApiContext(), "TxCtrlr", "EVConnectionTimeOut", "ConnectionTimeOut", &connectionTimeOut); + mtime += connectionTimeOut * 1000; loop(); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Available"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == ""); + } + REQUIRE(mo_getTransactionIdTag() == nullptr); + REQUIRE(!mo_isTransactionActive()); } SECTION("StopTx") { - startTransaction("mIdTag"); + loop(); - expectedSN[1] = "Available"; - SECTION("directly") { - stopTransaction(); - loop(); - REQUIRE(checkedSN[1]); - REQUIRE(!ocppPermitsCharge()); - } + mo_beginTransaction("mIdTag"); + loop(); + + REQUIRE(mo_ocppPermitsCharge()); + REQUIRE(mo_getTransactionId() != nullptr); + REQUIRE(!std::string(mo_getTransactionId()).empty()); + + sentOperations.clear(); SECTION("via session management - deauthorize") { - endTransaction(); + mo_endTransaction("mIdTag", nullptr); loop(); - REQUIRE(checkedSN[1]); - REQUIRE(!ocppPermitsCharge()); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Available"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == ""); + } } SECTION("via session management - deauthorize first") { - expectedSN[1] = "Finishing"; - setConnectorPluggedInput([] () {return true;}); - endTransaction(); + mo_setConnectorPluggedInput([] () {return true;}); loop(); - REQUIRE(checkedSN[1]); - REQUIRE(!ocppPermitsCharge()); + + sentOperations.clear(); + mo_endTransaction("mIdTag", nullptr); + loop(); + if (ocppVersion == MO_OCPP_V16) { + REQUIRE(getLastSentStatus(ocppVersion) == "Finishing"); + } else if (ocppVersion == MO_OCPP_V201) { + REQUIRE(getLastSentStatus(ocppVersion) == ""); + } + REQUIRE(!mo_isTransactionActive()); + REQUIRE(!mo_isTransactionRunning()); - checkedSN[1] = false; - expectedSN[1] = "Available"; - setConnectorPluggedInput([] () {return false;}); + sentOperations.clear(); + mo_setConnectorPluggedInput([] () {return false;}); loop(); - REQUIRE(checkedSN[1]); - REQUIRE(!ocppPermitsCharge()); + REQUIRE(getLastSentStatus(ocppVersion) == "Available"); } SECTION("via session management - plug out") { - setConnectorPluggedInput([] () {return false;}); + mo_setConnectorPluggedInput([] () {return true;}); + loop(); + + sentOperations.clear(); + mo_setConnectorPluggedInput([] () {return false;}); loop(); - REQUIRE(checkedSN[1]); - REQUIRE(!ocppPermitsCharge()); + REQUIRE(getLastSentStatus(ocppVersion) == "Available"); } - if (ocppPermitsCharge()) { - stopTransaction(); - } loop(); + + REQUIRE(!mo_ocppPermitsCharge()); + REQUIRE(mo_getTransactionId() == nullptr); + REQUIRE(!mo_isTransactionActive()); + REQUIRE(!mo_isTransactionRunning()); } SECTION("Preboot transactions - tx before BootNotification") { - mocpp_deinitialize(); loopback.setOnline(false); - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); - declareConfiguration(MO_CONFIG_EXT_PREFIX "PreBootTransactions", true, CONFIGURATION_FN)->setBool(true); - configuration_save(); + mo_setVarConfigBool(mo_getApiContext(), "CustomizationCtrlr", "PreBootTransactions", MO_CONFIG_EXT_PREFIX "PreBootTransactions", true); loop(); - beginTransaction_authorized("mIdTag"); + mo_beginTransaction_authorized("mIdTag", nullptr); loop(); - REQUIRE(isTransactionRunning()); + REQUIRE(mo_isTransactionRunning()); mtime += 3600 * 1000; //transaction duration ~1h - endTransaction(); + mo_endTransaction("mIdTag", nullptr); loop(); mtime += 3600 * 1000; //set base time one hour later - bool checkStartProcessed = false; + loop(); - getOcppContext()->getModel().getClock().setTime(BASE_TIME); - Timestamp basetime = Timestamp(); - basetime.setTime(BASE_TIME); + mo_setUnixTime(BASE_TIME_UNIX); - getOcppContext()->getOperationRegistry().setOnRequest("StartTransaction", - [&checkStartProcessed, basetime] (JsonObject payload) { - checkStartProcessed = true; - Timestamp timestamp; - timestamp.setTime(payload["timestamp"].as()); + loopback.setOnline(true); + loop(); - auto adjustmentDelay = basetime - timestamp; - REQUIRE((adjustmentDelay > 2 * 3600 - 10 && adjustmentDelay < 2 * 3600 + 10)); - }); - - bool checkStopProcessed = false; + const char *startTimeStr = nullptr; + const char *stopTimeStr = nullptr; - getOcppContext()->getOperationRegistry().setOnRequest("StopTransaction", - [&checkStopProcessed, basetime] (JsonObject payload) { - checkStopProcessed = true; - Timestamp timestamp; - timestamp.setTime(payload["timestamp"].as()); + for (auto& operation : sentOperations) { + if (operation.first == "StartTransaction") { + startTimeStr = operation.second["timestamp"] | "_Invalid"; + } else if (operation.first == "StopTransaction") { + stopTimeStr = operation.second["timestamp"] | "_Invalid"; + } else if (operation.first == "TransactionEvent") { + if (operation.second["eventType"].as() == "Started") { + startTimeStr = operation.second["timestamp"] | "_Invalid"; + } else if (operation.second["eventType"].as() == "Ended") { + stopTimeStr = operation.second["timestamp"] | "_Invalid"; + } + } + } - auto adjustmentDelay = basetime - timestamp; - REQUIRE((adjustmentDelay > 3600 - 10 && adjustmentDelay < 3600 + 10)); - }); - - loopback.setOnline(true); - loop(); + REQUIRE(startTimeStr != nullptr); + REQUIRE(stopTimeStr != nullptr); + + MicroOcpp::Timestamp baseTime, startTime, stopTime; + auto& clock = mo_getContext()->getClock(); + clock.fromUnixTime(baseTime, BASE_TIME_UNIX); + clock.parseString(startTimeStr, startTime); + clock.parseString(stopTimeStr, stopTime); - REQUIRE(checkStartProcessed); - REQUIRE(checkStopProcessed); + int32_t dtStart = -1, dtStop = -1; + clock.delta(baseTime, startTime, dtStart); + clock.delta(baseTime, stopTime, dtStop); + + REQUIRE(dtStart >= 2 * 3600 - 30); + REQUIRE(dtStart <= 2 * 3600 + 30); + REQUIRE(dtStop >= 3600 - 30); + REQUIRE(dtStop <= 3600 + 30); } + #if 0 //remaining tests need migration to MO v2 + SECTION("Preboot transactions - lose StartTx timestamp") { mocpp_deinitialize(); @@ -1279,5 +1403,7 @@ TEST_CASE( "Charging sessions" ) { //Note: queueing offline transactions without FS is currently not implemented } - mocpp_deinitialize(); +#endif + + mo_deinitialize(); } diff --git a/tests/SmartCharging.cpp b/tests/SmartCharging.cpp index 80961693..f26c4901 100644 --- a/tests/SmartCharging.cpp +++ b/tests/SmartCharging.cpp @@ -3,19 +3,18 @@ // MIT License #include -#include -#include +#include +#include #include -#include -#include #include -#include +#include #include #include "./helpers/testHelper.h" #define BASE_TIME "2023-01-01T00:00:00.000Z" -#define SCPROFILE_0 "[2,\"testmsg\",\"SetChargingProfile\",{\"connectorId\":1,\"csChargingProfiles\":{\"chargingProfileId\":0,\"stackLevel\":0,\"chargingProfilePurpose\":\"TxDefaultProfile\",\"chargingProfileKind\":\"Recurring\",\"recurrencyKind\":\"Daily\",\"validFrom\":\"2022-06-12T00:00:00.000Z\",\"validTo\":\"2023-06-21T00:00:00.000Z\",\"chargingSchedule\":{\"duration\":1000000,\"startSchedule\":\"2023-06-18T00:00:00.000Z\",\"chargingRateUnit\":\"W\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":16,\"numberPhases\":3},{\"startPeriod\":18000,\"limit\":32,\"numberPhases\":3}],\"minChargingRate\":6}}}]" +#define SCPROFILE_0 "[2,\"testmsg\",\"SetChargingProfile\",{\"connectorId\":1,\"csChargingProfiles\":{\"chargingProfileId\":0,\"stackLevel\":0,\"chargingProfilePurpose\":\"TxDefaultProfile\",\"chargingProfileKind\":\"Recurring\",\"recurrencyKind\":\"Daily\",\"validFrom\":\"2022-06-12T00:00:00.000Z\",\"validTo\":\"2023-06-21T00:00:00.000Z\",\"chargingSchedule\":{ \"duration\":1000000,\"startSchedule\":\"2023-06-18T00:00:00.000Z\",\"chargingRateUnit\":\"W\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":16,\"numberPhases\":3},{\"startPeriod\":18000,\"limit\":32,\"numberPhases\":3}],\"minChargingRate\":6}}}]" +#define SCPROFILE_0_V201 "[2,\"testmsg\",\"SetChargingProfile\",{\"evseId\": 1,\"chargingProfile\": {\"id\": 0,\"stackLevel\":0,\"chargingProfilePurpose\":\"TxDefaultProfile\",\"chargingProfileKind\":\"Recurring\",\"recurrencyKind\":\"Daily\",\"validFrom\":\"2022-06-12T00:00:00.000Z\",\"validTo\":\"2023-06-21T00:00:00.000Z\",\"chargingSchedule\":{\"id\":0,\"duration\":1000000,\"startSchedule\":\"2023-06-18T00:00:00.000Z\",\"chargingRateUnit\":\"W\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":16,\"numberPhases\":3},{\"startPeriod\":18000,\"limit\":32,\"numberPhases\":3}],\"minChargingRate\":6}}}]" #define SCPROFILE_0_ALT_SAME_ID "[2,\"testmsg\",\"SetChargingProfile\",{\"connectorId\":1,\"csChargingProfiles\":{\"chargingProfileId\":0,\"stackLevel\":1,\"chargingProfilePurpose\":\"TxDefaultProfile\",\"chargingProfileKind\":\"Recurring\",\"recurrencyKind\":\"Daily\",\"validFrom\":\"2022-06-12T00:00:00.000Z\",\"validTo\":\"2023-06-21T00:00:00.000Z\",\"chargingSchedule\":{\"duration\":1000000,\"startSchedule\":\"2023-06-18T00:00:00.000Z\",\"chargingRateUnit\":\"W\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":16,\"numberPhases\":3},{\"startPeriod\":18000,\"limit\":32,\"numberPhases\":3}],\"minChargingRate\":6}}}]" #define SCPROFILE_0_ALT_SAME_STACKEVEL_PURPOSE "[2,\"testmsg\",\"SetChargingProfile\",{\"connectorId\":1,\"csChargingProfiles\":{\"chargingProfileId\":1,\"stackLevel\":0,\"chargingProfilePurpose\":\"TxDefaultProfile\",\"chargingProfileKind\":\"Recurring\",\"recurrencyKind\":\"Daily\",\"validFrom\":\"2022-06-12T00:00:00.000Z\",\"validTo\":\"2023-06-21T00:00:00.000Z\",\"chargingSchedule\":{\"duration\":1000000,\"startSchedule\":\"2023-06-18T00:00:00.000Z\",\"chargingRateUnit\":\"W\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":16,\"numberPhases\":3},{\"startPeriod\":18000,\"limit\":32,\"numberPhases\":3}],\"minChargingRate\":6}}}]" @@ -33,10 +32,51 @@ #define SCPROFILE_7_RECURRING_DAY_2H_16A_20A "[2,\"testmsg\",\"SetChargingProfile\",{\"connectorId\":0,\"csChargingProfiles\":{\"chargingProfileId\":7,\"stackLevel\":0,\"chargingProfilePurpose\":\"ChargePointMaxProfile\",\"chargingProfileKind\":\"Recurring\",\"recurrencyKind\":\"Daily\", \"chargingSchedule\":{\"duration\":7200,\"startSchedule\":\"2023-01-01T00:00:00.000Z\",\"chargingRateUnit\":\"A\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":16,\"numberPhases\":3},{\"startPeriod\":3600,\"limit\":20,\"numberPhases\":3}]}}}]" #define SCPROFILE_8_RECURRING_WEEK_2H_10A_12A "[2,\"testmsg\",\"SetChargingProfile\",{\"connectorId\":0,\"csChargingProfiles\":{\"chargingProfileId\":8,\"stackLevel\":1,\"chargingProfilePurpose\":\"ChargePointMaxProfile\",\"chargingProfileKind\":\"Recurring\",\"recurrencyKind\":\"Weekly\",\"chargingSchedule\":{\"duration\":7200,\"startSchedule\":\"2023-01-01T00:00:00.000Z\",\"chargingRateUnit\":\"A\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":10,\"numberPhases\":3},{\"startPeriod\":3600,\"limit\":12,\"numberPhases\":3}]}}}]" -#define SCPROFILE_9_VIA_RMTSTARTTX_20A "[2,\"testmsg\",\"RemoteStartTransaction\",{\"connectorId\":1,\"idTag\":\"mIdTag\",\"chargingProfile\":{\"chargingProfileId\":9,\"stackLevel\":0,\"chargingProfilePurpose\":\"TxProfile\",\"chargingProfileKind\":\"Relative\",\"chargingSchedule\":{\"chargingRateUnit\":\"A\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":20,\"numberPhases\":1}]}}}]" +#define SCPROFILE_9_VIA_RMTSTARTTX_20A "[2,\"testmsg\",\"RemoteStartTransaction\", {\"connectorId\":1,\"idTag\": \"mIdTag\", \"chargingProfile\":{\"chargingProfileId\":9,\"stackLevel\":0,\"chargingProfilePurpose\":\"TxProfile\",\"chargingProfileKind\":\"Relative\",\"chargingSchedule\":{ \"chargingRateUnit\":\"A\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":20,\"numberPhases\":1}]}}}]" +#define SCPROFILE_9_VIA_REQSTARTTX_20A "[2,\"testmsg\",\"RequestStartTransaction\",{\"evseId\":1, \"idToken\":{\"idToken\":\"mIdTag\",\"type\":\"ISO14443\"},\"chargingProfile\":{\"id\":9, \"stackLevel\":0,\"chargingProfilePurpose\":\"TxProfile\",\"chargingProfileKind\":\"Relative\",\"chargingSchedule\":{\"id\":9,\"chargingRateUnit\":\"A\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":20,\"numberPhases\":1}]}}}]" #define SCPROFILE_10_ABSOLUTE_LIMIT_5KW "[2,\"testmsg\",\"SetChargingProfile\",{\"connectorId\":0,\"csChargingProfiles\":{\"chargingProfileId\":10,\"stackLevel\":0,\"chargingProfilePurpose\":\"ChargePointMaxProfile\",\"chargingProfileKind\":\"Absolute\",\"chargingSchedule\":{\"startSchedule\":\"2023-01-01T00:00:00.000Z\",\"chargingRateUnit\":\"W\",\"chargingSchedulePeriod\":[{\"startPeriod\":0,\"limit\":5000,\"numberPhases\":3}]}}}]" +char setChargingProfile [1024]; +void updateSetChargingProfile(const char *scprofile_raw, int ocppVersion) { + if (ocppVersion == MO_OCPP_V16) { + auto ret = snprintf(setChargingProfile, sizeof(setChargingProfile), "%s", scprofile_raw); + if (ret < 0 || (size_t)ret >= sizeof(setChargingProfile)) { + FAIL("snprintf"); + } + return; + } + + DynamicJsonDocument doc (4096); + auto err = deserializeJson(doc, scprofile_raw); + if (err) { + char buf [100]; + snprintf(buf, sizeof(buf), "JSON deserialization error: %s", err.c_str()); + FAIL(buf); + } + + JsonObject payload = doc[3]; + + payload["evseId"] = payload["connectorId"]; + payload.remove("connectorId"); + payload["chargingProfile"] = payload["csChargingProfiles"]; + payload.remove("csChargingProfiles"); + JsonObject chargingProfile = payload["chargingProfile"]; + chargingProfile["id"] = chargingProfile["chargingProfileId"]; + chargingProfile.remove("chargingProfileId"); + chargingProfile["chargingSchedule"]["id"] = chargingProfile["id"]; + + auto ret = serializeJson(doc, setChargingProfile); + + if (doc.isNull() || doc.overflowed() || ret >= sizeof(setChargingProfile) - 1 || ret < 2) { + FAIL("failure to convert JSON to OCPP 2.0.1"); + } +} + +float g_limit_current = -1.f; +void setCurrentCb(float limit_current) { + g_limit_current = limit_current; +} using namespace MicroOcpp; @@ -44,140 +84,133 @@ using namespace MicroOcpp; TEST_CASE( "SmartCharging" ) { printf("\nRun %s\n", "SmartCharging"); - //initialize Context with dummy socket - LoopbackConnection loopback; - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + g_limit_current = -1.f; - auto context = getOcppContext(); - auto& model = context->getModel(); + //initialize Context without any configs + mo_initialize(); - mocpp_set_timer(custom_timer_cb); + mo_getContext()->setTicksCb(custom_timer_cb); - model.getClock().setTime(BASE_TIME); + LoopbackConnection loopback; + mo_getContext()->setConnection(&loopback); - endTransaction(); + auto ocppVersion = GENERATE(MO_OCPP_V16, MO_OCPP_V201); + mo_setOcppVersion(ocppVersion); - SECTION("Load Smart Charging Service"){ + mo_setBootNotificationData("TestModel", "TestVendor"); - REQUIRE(!model.getSmartChargingService()); + mo_setSmartChargingPowerOutput([] (float) {}); - setSmartChargingOutput([] (float, float, int) {}); + mo_setup(); - REQUIRE(model.getSmartChargingService()); - } + loop(); - setSmartChargingOutput([] (float, float, int) {}); - auto scService = model.getSmartChargingService(); + MicroOcpp::SmartChargingService *scService = nullptr; + if (ocppVersion == MO_OCPP_V16) { + scService = mo_getContext()->getModel16().getSmartChargingService(); + } else if (ocppVersion == MO_OCPP_V201) { + scService = mo_getContext()->getModel201().getSmartChargingService(); + } + REQUIRE(scService != nullptr); - scService->clearChargingProfile([] (int, int, ChargingProfilePurposeType, int) { - return true; - }); + SECTION("Clean up files") { + auto filesystem = mo_getFilesystem(); + FilesystemUtils::removeByPrefix(filesystem, ""); + } SECTION("Set Charging Profile and clear") { - unsigned int count = 0; - scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) { - count++; - return true; - }); - - REQUIRE(count == 0); - - loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0)); - - //check if filter works by comparing the outcome of returning false and true and repeating the test - count = 0; - scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) { - count++; - return false; - }); + REQUIRE(scService->getChargingProfilesCount() == 0); - REQUIRE(count == 1); + if (ocppVersion == MO_OCPP_V16) { + loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0)); + } else if (ocppVersion == MO_OCPP_V201) { + loopback.sendTXT(SCPROFILE_0_V201, strlen(SCPROFILE_0_V201)); + } + updateSetChargingProfile(SCPROFILE_0, ocppVersion); + loopback.sendTXT(setChargingProfile, strlen(setChargingProfile)); - count = 0; - scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) { - count++; - return true; - }); + loop(); - REQUIRE(count == 1); + REQUIRE(scService->getChargingProfilesCount() == 1); - count = 0; - scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) { - count++; - return true; - }); + REQUIRE(scService->clearChargingProfile(-1, -1, MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1)); - REQUIRE(count == 0); + REQUIRE(scService->getChargingProfilesCount() == 0); } SECTION("Charging Profiles persistency over reboots") { - loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0)); - - mocpp_deinitialize(); - - mocpp_initialize(loopback, ChargerCredentials("test-runner1234")); + updateSetChargingProfile(SCPROFILE_0, ocppVersion); + loopback.sendTXT(setChargingProfile, strlen(setChargingProfile)); + loop(); - setSmartChargingOutput([] (float, float, int) {}); - scService = getOcppContext()->getModel().getSmartChargingService(); + mo_deinitialize(); - unsigned int count = 0; - scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) { - count++; - return true; - }); + mo_initialize(); + mo_getContext()->setConnection(&loopback); + mo_setOcppVersion(ocppVersion); + mo_setup(); + if (ocppVersion == MO_OCPP_V16) { + scService = mo_getContext()->getModel16().getSmartChargingService(); + } else if (ocppVersion == MO_OCPP_V201) { + scService = mo_getContext()->getModel201().getSmartChargingService(); + } - REQUIRE (count == 1); + REQUIRE(scService->getChargingProfilesCount() == 1); + REQUIRE(scService->clearChargingProfile(-1, -1, MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1)); } SECTION("Set conflicting profile") { - loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0)); - - loopback.sendTXT(SCPROFILE_0_ALT_SAME_ID, strlen(SCPROFILE_0_ALT_SAME_ID)); - - unsigned int count = 0; - scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) { - count++; - return true; - }); + updateSetChargingProfile(SCPROFILE_0, ocppVersion); + loopback.sendTXT(setChargingProfile, strlen(setChargingProfile)); + loop(); - REQUIRE(count == 1); + updateSetChargingProfile(SCPROFILE_0_ALT_SAME_ID, ocppVersion); + loopback.sendTXT(setChargingProfile, strlen(setChargingProfile)); + loop(); - loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0)); + REQUIRE(scService->getChargingProfilesCount() == 1); + REQUIRE(scService->clearChargingProfile(-1, -1, MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1)); - loopback.sendTXT(SCPROFILE_0_ALT_SAME_STACKEVEL_PURPOSE, strlen(SCPROFILE_0_ALT_SAME_STACKEVEL_PURPOSE)); + updateSetChargingProfile(SCPROFILE_0, ocppVersion); + loopback.sendTXT(setChargingProfile, strlen(setChargingProfile)); + loop(); - count = 0; - scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) { - count++; - return true; - }); + updateSetChargingProfile(SCPROFILE_0_ALT_SAME_STACKEVEL_PURPOSE, ocppVersion); + loopback.sendTXT(setChargingProfile, strlen(setChargingProfile)); + loop(); - REQUIRE(count == 1); + REQUIRE(scService->getChargingProfilesCount() == 1); + REQUIRE(scService->clearChargingProfile(-1, -1, MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1)); } SECTION("Set charging profile via RmtStartTx") { - float current = -1.f; - setSmartChargingOutput([¤t] (float, float limit_current, int) { - current = limit_current; - }); + mo_setSmartChargingCurrentOutput(setCurrentCb); + g_limit_current = -1.f; loop(); - loopback.sendTXT(SCPROFILE_9_VIA_RMTSTARTTX_20A, strlen(SCPROFILE_9_VIA_RMTSTARTTX_20A)); + if (ocppVersion == MO_OCPP_V16) { + loopback.sendTXT(SCPROFILE_9_VIA_RMTSTARTTX_20A, strlen(SCPROFILE_9_VIA_RMTSTARTTX_20A)); + } else if (ocppVersion == MO_OCPP_V201) { + loopback.sendTXT(SCPROFILE_9_VIA_REQSTARTTX_20A, strlen(SCPROFILE_9_VIA_REQSTARTTX_20A)); + } loop(); - REQUIRE((current > 19.99f && current < 20.01f)); + REQUIRE(g_limit_current > 19.99f); + REQUIRE(g_limit_current < 20.01f); - endTransaction(); + mo_endTransaction(nullptr, nullptr); loop(); } + #if 0 + SECTION("Set ChargePointMaxProfile - Absolute") { float current = -1.f; @@ -836,10 +869,10 @@ TEST_CASE( "SmartCharging" ) { REQUIRE( checkProcessed ); } - scService->clearChargingProfile([] (int, int, ChargingProfilePurposeType, int) { - return true; - }); + #endif + + scService->clearChargingProfile(-1, -1, MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1); - mocpp_deinitialize(); + mo_deinitialize(); } From 141161cf45f6f685f5335e456b47592a347c0385 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 6 Jul 2025 15:38:37 +0200 Subject: [PATCH 15/50] bugfix --- src/MicroOcpp/Model/Availability/AvailabilityService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 5188d7b2..17b2eba9 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -185,7 +185,7 @@ bool Ocpp16::AvailabilityServiceEvse::addErrorDataInput(MO_ErrorDataInput errorD size_t capacity = errorDataInputs.size() + 1; errorDataInputs.reserve(capacity); trackErrorDataInputs.reserve(capacity); - if (errorDataInputs.capacity() != capacity || trackErrorDataInputs.capacity() != capacity) { + if (errorDataInputs.capacity() < capacity || trackErrorDataInputs.capacity() < capacity) { MO_DBG_ERR("OOM"); return false; } From a084dc3738e2620cc69a7b9467ace4e0666f1bee Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 6 Jul 2025 15:39:40 +0200 Subject: [PATCH 16/50] consolidate start and stop of mo_simulator process --- tests/benchmarks/scripts/measure_heap.py | 106 ++++++++++++++++------- 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/tests/benchmarks/scripts/measure_heap.py b/tests/benchmarks/scripts/measure_heap.py index a6c7c97d..d921c49e 100644 --- a/tests/benchmarks/scripts/measure_heap.py +++ b/tests/benchmarks/scripts/measure_heap.py @@ -107,12 +107,58 @@ max_memory_total = 0 min_memory_base = 1000 * 1000 * 1000 +# Ensure Simulator is still running despite Reset requests via OCPP or the rmt_ctrl interface +run_simulator = False +exit_run_simulator_process = False + +def run_simulator_process(): + + global run_simulator + global exit_run_simulator_process + + track_run_simulator = run_simulator + iterations_since_last_check = 0 + + while not exit_run_simulator_process: + + time.sleep(0.001) + iterations_since_last_check += 1 + + if track_run_simulator == run_simulator and iterations_since_last_check <= 1000: + continue + + iterations_since_last_check = 0 + + if not track_run_simulator and run_simulator: + print('Starting simulator') + os.system(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator') + ' &') + track_run_simulator = True + + if track_run_simulator and not run_simulator: + print('Shutdown simulator') + os.system('killall -s SIGINT mo_simulator') + track_run_simulator = False + + if track_run_simulator == run_simulator: + # Check if mo_simulator process can be found + try: + subprocess.check_output(['pgrep', '-f', 'mo_simulator']) + # Found, still running + track_run_simulator = True + except subprocess.CalledProcessError: + # Exited, restart + track_run_simulator = False + + print('Stopped run_simulator_process') + def cleanup_simulator(): + global run_simulator print('Clean up Simulator') print(' - stop Simulator, if still running') - os.system('killall -s SIGINT mo_simulator') + run_simulator = False + time.sleep(1) print(' - clean state') os.system('rm -rf ' + os.path.join('MicroOcppSimulator', 'mo_store', '*')) @@ -120,6 +166,7 @@ def cleanup_simulator(): print(' - done') def setup_simulator(): + global run_simulator cleanup_simulator() @@ -138,32 +185,16 @@ def setup_simulator(): print(' - start Simulator') - os.system(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator') + ' &') + run_simulator = True + time.sleep(1) print(' - done') -# Ensure Simulator is still running despite Reset requests via OCPP or the rmt_ctrl interface -keepalive_simulator = False - -def keepalive_simulator_thread(): - - while keepalive_simulator: - # Check if mo_simulator process can be found - try: - subprocess.check_output(['pgrep', '-f', 'mo_simulator']) - # Found, still running - except subprocess.CalledProcessError: - # Exited, restart - print('Keepalive: restart Simulator') - os.system(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator') + ' &') - - time.sleep(1) - def run_measurements(): global max_memory_total global min_memory_base - global keepalive_simulator + global run_simulator print("Fetch TCs from Test Driver") @@ -241,11 +272,6 @@ def run_measurements(): response = requests.post('http://localhost:8000/api/memory/reset') print(f'Simulator API /memory/reset:\n > {response.status_code}') - - # Keepalive Simulator while running the test cases (tests may triger Reset commands) - keepalive_simulator = True - keepalive_thread = threading.Thread(target=keepalive_simulator_thread, daemon=True) - keepalive_thread.start() test_response = requests.post(os.environ['TEST_DRIVER_URL'] + '/testcases/' + testcase['testcase_name'] + '/execute', headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) @@ -255,9 +281,6 @@ def run_measurements(): #except: # print(' > No JSON') - keepalive_simulator = False - keepalive_thread.join() - sim_response = requests.get('http://localhost:8000/api/memory/info') print(f'Simulator API /memory/info:\n > {sim_response.status_code}, current heap={sim_response.json()["total_current"]}, max heap={sim_response.json()["total_max"]}') @@ -312,6 +335,8 @@ def run_measurements(): def run_measurements_and_retry(): + global exit_run_simulator_process + if ( 'TEST_DRIVER_URL' not in os.environ or 'TEST_DRIVER_CONFIG' not in os.environ or 'TEST_DRIVER_KEY' not in os.environ or @@ -325,7 +350,15 @@ def run_measurements_and_retry(): for i in range(n_tries): + run_simulator_thread = None + try: + # Keepalive Simulator while running the test cases (tests may triger Reset commands) + + exit_run_simulator_process = False + run_simulator_thread = threading.Thread(target=run_simulator_process, daemon=True) + run_simulator_thread.start() + run_measurements() print('\n **Test cases executed successfully**') break @@ -334,14 +367,21 @@ def run_measurements_and_retry(): traceback.print_exc() - print("Stop Test Driver") - response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) - print(f'Test Driver /session/stop:\n > {response.status_code}') - #print(json.dumps(response.json(), indent=4)) + try: + print("Stop Test Driver") + response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) + print(f'Test Driver /session/stop:\n > {response.status_code}') + #print(json.dumps(response.json(), indent=4)) + except: + print('Error stopping Test Driver') + traceback.print_exc() cleanup_simulator() + exit_run_simulator_process = True + run_simulator_thread.join() + if i + 1 < n_tries: print('Retry test cases') else: From 0afd0f35b107e6a7c8c9cda2cfc9c835483b0402 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 6 Jul 2025 15:46:14 +0200 Subject: [PATCH 17/50] update Ocpp16 / Ocpp201 namespace names --- src/MicroOcpp.cpp | 18 ++-- src/MicroOcpp/Context.cpp | 4 +- src/MicroOcpp/Context.h | 8 +- src/MicroOcpp/Core/RequestQueue.cpp | 4 +- .../Model/Authorization/AuthorizationData.cpp | 6 +- .../Model/Authorization/AuthorizationData.h | 4 +- .../Model/Authorization/AuthorizationList.cpp | 4 +- .../Model/Authorization/AuthorizationList.h | 4 +- .../Authorization/AuthorizationService.cpp | 2 +- .../Authorization/AuthorizationService.h | 4 +- src/MicroOcpp/Model/Authorization/IdToken.cpp | 2 +- src/MicroOcpp/Model/Authorization/IdToken.h | 4 +- .../Availability/AvailabilityService.cpp | 78 +++++++-------- .../Model/Availability/AvailabilityService.h | 8 +- src/MicroOcpp/Model/Boot/BootService.h | 8 +- .../Model/Configuration/Configuration.cpp | 4 +- .../Model/Configuration/Configuration.h | 4 +- .../Configuration/ConfigurationContainer.cpp | 2 +- .../Configuration/ConfigurationContainer.h | 4 +- .../Configuration/ConfigurationService.cpp | 4 +- .../Configuration/ConfigurationService.h | 4 +- .../Model/Diagnostics/Diagnostics.cpp | 4 +- src/MicroOcpp/Model/Diagnostics/Diagnostics.h | 4 +- .../Model/Diagnostics/DiagnosticsService.cpp | 20 ++-- .../Model/Diagnostics/DiagnosticsService.h | 4 +- .../FirmwareManagement/FirmwareService.cpp | 16 ++-- .../FirmwareManagement/FirmwareService.h | 4 +- .../Model/FirmwareManagement/FirmwareStatus.h | 4 +- .../Model/Heartbeat/HeartbeatService.h | 12 +-- src/MicroOcpp/Model/Metering/MeterStore.cpp | 2 +- src/MicroOcpp/Model/Metering/MeterStore.h | 4 +- .../Model/Metering/MeteringService.cpp | 70 +++++++------- .../Model/Metering/MeteringService.h | 8 +- src/MicroOcpp/Model/Model.cpp | 94 +++++++++---------- src/MicroOcpp/Model/Model.h | 12 +-- .../Model/RemoteControl/RemoteControlDefs.h | 8 +- .../RemoteControl/RemoteControlService.cpp | 84 ++++++++--------- .../RemoteControl/RemoteControlService.h | 32 +++---- .../Model/Reservation/Reservation.cpp | 2 +- src/MicroOcpp/Model/Reservation/Reservation.h | 4 +- .../Model/Reservation/ReservationService.cpp | 2 +- .../Model/Reservation/ReservationService.h | 4 +- src/MicroOcpp/Model/Reset/ResetService.cpp | 42 ++++----- src/MicroOcpp/Model/Reset/ResetService.h | 8 +- .../SecurityEvent/SecurityEventService.h | 8 +- .../SmartCharging/SmartChargingService.h | 4 +- .../Model/Transactions/Transaction.cpp | 18 ++-- .../Model/Transactions/Transaction.h | 8 +- .../Transactions/TransactionService16.cpp | 2 +- .../Model/Transactions/TransactionService16.h | 4 +- .../Transactions/TransactionService201.cpp | 2 +- .../Transactions/TransactionService201.h | 4 +- .../Model/Transactions/TransactionStore.cpp | 60 ++++++------ .../Model/Transactions/TransactionStore.h | 8 +- src/MicroOcpp/Model/Variables/Variable.cpp | 4 +- src/MicroOcpp/Model/Variables/Variable.h | 4 +- .../Model/Variables/VariableContainer.cpp | 2 +- .../Model/Variables/VariableContainer.h | 4 +- .../Model/Variables/VariableService.cpp | 4 +- .../Model/Variables/VariableService.h | 4 +- src/MicroOcpp/Operations/Authorize.cpp | 20 ++-- src/MicroOcpp/Operations/Authorize.h | 8 +- .../Operations/CancelReservation.cpp | 2 +- src/MicroOcpp/Operations/CancelReservation.h | 4 +- .../Operations/ChangeAvailability.cpp | 16 ++-- src/MicroOcpp/Operations/ChangeAvailability.h | 8 +- .../Operations/ChangeConfiguration.cpp | 2 +- .../Operations/ChangeConfiguration.h | 4 +- src/MicroOcpp/Operations/ClearCache.cpp | 2 +- src/MicroOcpp/Operations/ClearCache.h | 4 +- src/MicroOcpp/Operations/DataTransfer.cpp | 2 +- src/MicroOcpp/Operations/DataTransfer.h | 4 +- .../DiagnosticsStatusNotification.cpp | 2 +- .../DiagnosticsStatusNotification.h | 4 +- .../Operations/FirmwareStatusNotification.cpp | 2 +- .../Operations/FirmwareStatusNotification.h | 4 +- src/MicroOcpp/Operations/GetBaseReport.cpp | 2 +- src/MicroOcpp/Operations/GetBaseReport.h | 4 +- src/MicroOcpp/Operations/GetConfiguration.cpp | 2 +- src/MicroOcpp/Operations/GetConfiguration.h | 4 +- src/MicroOcpp/Operations/GetDiagnostics.cpp | 2 +- src/MicroOcpp/Operations/GetDiagnostics.h | 4 +- .../Operations/GetLocalListVersion.cpp | 2 +- .../Operations/GetLocalListVersion.h | 4 +- src/MicroOcpp/Operations/GetVariables.cpp | 2 +- src/MicroOcpp/Operations/GetVariables.h | 4 +- src/MicroOcpp/Operations/Heartbeat.cpp | 2 +- src/MicroOcpp/Operations/MeterValues.cpp | 2 +- src/MicroOcpp/Operations/MeterValues.h | 4 +- src/MicroOcpp/Operations/NotifyReport.cpp | 2 +- src/MicroOcpp/Operations/NotifyReport.h | 4 +- .../Operations/RemoteStartTransaction.cpp | 6 +- .../Operations/RemoteStartTransaction.h | 4 +- .../Operations/RemoteStopTransaction.cpp | 6 +- .../Operations/RemoteStopTransaction.h | 4 +- .../Operations/RequestStartTransaction.cpp | 2 +- .../Operations/RequestStartTransaction.h | 6 +- .../Operations/RequestStopTransaction.cpp | 2 +- .../Operations/RequestStopTransaction.h | 4 +- src/MicroOcpp/Operations/ReserveNow.cpp | 2 +- src/MicroOcpp/Operations/ReserveNow.h | 4 +- src/MicroOcpp/Operations/Reset.cpp | 16 ++-- src/MicroOcpp/Operations/Reset.h | 8 +- src/MicroOcpp/Operations/SendLocalList.cpp | 2 +- src/MicroOcpp/Operations/SendLocalList.h | 4 +- src/MicroOcpp/Operations/SetVariables.cpp | 2 +- src/MicroOcpp/Operations/SetVariables.h | 4 +- src/MicroOcpp/Operations/StartTransaction.cpp | 2 +- src/MicroOcpp/Operations/StartTransaction.h | 4 +- .../Operations/StatusNotification.cpp | 24 ++--- src/MicroOcpp/Operations/StatusNotification.h | 8 +- src/MicroOcpp/Operations/StopTransaction.cpp | 2 +- src/MicroOcpp/Operations/StopTransaction.h | 4 +- src/MicroOcpp/Operations/TransactionEvent.cpp | 2 +- src/MicroOcpp/Operations/TransactionEvent.h | 4 +- src/MicroOcpp/Operations/UnlockConnector.cpp | 16 ++-- src/MicroOcpp/Operations/UnlockConnector.h | 8 +- src/MicroOcpp/Operations/UpdateFirmware.cpp | 2 +- src/MicroOcpp/Operations/UpdateFirmware.h | 4 +- tests/Boot.cpp | 14 +-- tests/Certificates.cpp | 6 +- tests/ChargePointError.cpp | 6 +- tests/ChargingSessions.cpp | 28 +++--- tests/Configuration.cpp | 14 +-- tests/ConfigurationBehavior.cpp | 4 +- tests/FirmwareManagement.cpp | 28 +++--- tests/LocalAuthList.cpp | 26 ++--- tests/Metering.cpp | 2 +- tests/Reservation.cpp | 18 ++-- tests/Reset.cpp | 16 ++-- tests/Security.cpp | 2 +- tests/SmartCharging.cpp | 28 +++--- tests/Transactions.cpp | 16 ++-- tests/Variables.cpp | 22 ++--- 134 files changed, 641 insertions(+), 641 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index bdee1ee8..a7cb7b82 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -473,7 +473,7 @@ bool mo_authorizeTransaction2(MO_Context *ctx, unsigned int evseId, const char * MO_DBG_ERR("OOM"); return false; } - success = txSvcEvse->beginAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), /*validateIdToken*/ true); + success = txSvcEvse->beginAuthorization(MicroOcpp::v201::IdToken(idToken, type), /*validateIdToken*/ true); } #endif @@ -506,7 +506,7 @@ bool mo_authorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char * MO_DBG_ERR("OOM"); return false; } - success = txSvcEvse->beginAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), validateIdToken, groupIdToken); + success = txSvcEvse->beginAuthorization(MicroOcpp::v201::IdToken(idToken, type), validateIdToken, groupIdToken); } #endif @@ -630,7 +630,7 @@ bool mo_deauthorizeTransaction2(MO_Context *ctx, unsigned int evseId, const char MO_DBG_ERR("OOM"); return false; } - success = txSvcEvse->endAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), /*validateIdToken*/ true, nullptr); + success = txSvcEvse->endAuthorization(MicroOcpp::v201::IdToken(idToken, type), /*validateIdToken*/ true, nullptr); } #endif @@ -663,7 +663,7 @@ bool mo_deauthorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char MO_DBG_ERR("OOM"); return false; } - success = txSvcEvse->endAuthorization(MicroOcpp::Ocpp201::IdToken(idToken, type), validateIdToken, groupIdToken); + success = txSvcEvse->endAuthorization(MicroOcpp::v201::IdToken(idToken, type), validateIdToken, groupIdToken); } #endif @@ -1143,7 +1143,7 @@ bool mo_addErrorCodeInput(const char* (*errorCodeInput)()) { } //error codes not really supported in OCPP 2.0.1, just Faulted status - MicroOcpp::Ocpp201::FaultedInput faultedInput; + MicroOcpp::v201::FaultedInput faultedInput; faultedInput.userData = reinterpret_cast(errorCodeInput); faultedInput.isFaulted = [] (unsigned int evseId, void *userData) { auto errorCodeInput = reinterpret_cast(userData); @@ -1222,7 +1222,7 @@ bool mo_v201_addFaultedInput(MO_Context *ctx, unsigned int evseId, bool (*faulte } //error codes not really supported in OCPP 2.0.1, just Faulted status - MicroOcpp::Ocpp201::FaultedInput faultedInput; + MicroOcpp::v201::FaultedInput faultedInput; faultedInput.isFaulted = faulted; faultedInput.userData = userData; @@ -2478,7 +2478,7 @@ bool mo_declareVarConfigInt(MO_Context *ctx, const char *component201, const cha return false; } - success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::Ocpp201::Variable::AttributeTypeSet(), rebootRequired); + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::v201::Variable::AttributeTypeSet(), rebootRequired); } #endif @@ -2521,7 +2521,7 @@ bool mo_declareVarConfigBool(MO_Context *ctx, const char *component201, const ch return false; } - success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::Ocpp201::Variable::AttributeTypeSet(), rebootRequired); + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::v201::Variable::AttributeTypeSet(), rebootRequired); } #endif @@ -2564,7 +2564,7 @@ bool mo_declareVarConfigString(MO_Context *ctx, const char *component201, const return false; } - success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::Ocpp201::Variable::AttributeTypeSet(), rebootRequired); + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::v201::Variable::AttributeTypeSet(), rebootRequired); } #endif diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index 5ad2d268..6c79d597 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -235,13 +235,13 @@ int Context::getOcppVersion() { } #if MO_ENABLE_V16 -Ocpp16::Model& Context::getModel16() { +v16::Model& Context::getModel16() { return modelV16; } #endif #if MO_ENABLE_V201 -Ocpp201::Model& Context::getModel201() { +v201::Model& Context::getModel201() { return modelV201; } #endif diff --git a/src/MicroOcpp/Context.h b/src/MicroOcpp/Context.h index 1767f026..ab9d9b11 100644 --- a/src/MicroOcpp/Context.h +++ b/src/MicroOcpp/Context.h @@ -64,10 +64,10 @@ class Context : public MemoryManaged { MessageService msgService {*this}; #if MO_ENABLE_V16 - Ocpp16::Model modelV16 {*this}; + v16::Model modelV16 {*this}; #endif #if MO_ENABLE_V201 - Ocpp201::Model modelV201 {*this}; + v201::Model modelV201 {*this}; #endif int ocppVersion = MO_ENABLE_V16 ? MO_OCPP_V16 : MO_ENABLE_V201 ? MO_OCPP_V201 : -1; @@ -112,11 +112,11 @@ class Context : public MemoryManaged { int getOcppVersion(); #if MO_ENABLE_V16 - Ocpp16::Model& getModel16(); + v16::Model& getModel16(); #endif #if MO_ENABLE_V201 - Ocpp201::Model& getModel201(); + v201::Model& getModel201(); #endif // eliminate OCPP version specifiers if charger supports just one version anyway diff --git a/src/MicroOcpp/Core/RequestQueue.cpp b/src/MicroOcpp/Core/RequestQueue.cpp index fe7f70b8..ab3df59b 100644 --- a/src/MicroOcpp/Core/RequestQueue.cpp +++ b/src/MicroOcpp/Core/RequestQueue.cpp @@ -82,8 +82,8 @@ bool VolatileRequestQueue::pushRequestBack(std::unique_ptr request) { i++; continue; } - auto new_status_notification = static_cast(request->getOperation()); - auto old_status_notification = static_cast(requests[index]->getOperation()); + auto new_status_notification = static_cast(request->getOperation()); + auto old_status_notification = static_cast(requests[index]->getOperation()); if (old_status_notification->getConnectorId() == new_status_notification->getConnectorId()) { requests[index].reset(); for (size_t i = (index + MO_REQUEST_CACHE_MAXSIZE - front) % MO_REQUEST_CACHE_MAXSIZE; i < len - 1; i++) { diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp index b1479af4..43325673 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp @@ -8,7 +8,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; AuthorizationData::AuthorizationData() : MemoryManaged("v16.Authorization.AuthorizationData") { @@ -168,7 +168,7 @@ void AuthorizationData::reset() { } namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { const char *serializeAuthorizationStatus(AuthorizationStatus status) { switch (status) { @@ -207,6 +207,6 @@ AuthorizationStatus deserializeAuthorizationStatus(const char *cstr) { } } -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.h b/src/MicroOcpp/Model/Authorization/AuthorizationData.h index 22749cae..8645f054 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.h +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.h @@ -16,7 +16,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { enum class AuthorizationStatus : uint8_t { Accepted, @@ -64,7 +64,7 @@ class AuthorizationData : public MemoryManaged { void reset(); }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp index 1e7326d3..86c89869 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; AuthorizationList::AuthorizationList() : MemoryManaged("v16.Authorization.AuthorizationList") { @@ -21,7 +21,7 @@ AuthorizationList::~AuthorizationList() { clear(); } -MicroOcpp::Ocpp16::AuthorizationData *AuthorizationList::get(const char *idTag) { +MicroOcpp::v16::AuthorizationData *AuthorizationList::get(const char *idTag) { if (!idTag) { return nullptr; diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationList.h b/src/MicroOcpp/Model/Authorization/AuthorizationList.h index 762f8221..bb13b95a 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationList.h +++ b/src/MicroOcpp/Model/Authorization/AuthorizationList.h @@ -23,7 +23,7 @@ namespace MicroOcpp { class Clock; -namespace Ocpp16 { +namespace v16 { class AuthorizationList : public MemoryManaged { private: @@ -47,7 +47,7 @@ class AuthorizationList : public MemoryManaged { }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp index 38c63306..1a2c940d 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp @@ -19,7 +19,7 @@ #define MO_LOCALAUTHORIZATIONLIST_FN "localauth.jsn" using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; AuthorizationService::AuthorizationService(Context& context) : MemoryManaged("v16.Authorization.AuthorizationService"), context(context) { diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationService.h b/src/MicroOcpp/Model/Authorization/AuthorizationService.h index d97b754d..31146c1e 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationService.h +++ b/src/MicroOcpp/Model/Authorization/AuthorizationService.h @@ -16,7 +16,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class Configuration; @@ -46,6 +46,6 @@ class AuthorizationService : public MemoryManaged { }; } //namespace MicroOcpp -} //namespace Ocpp16 +} //namespace v16 #endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp index d9ba2448..06d9dc8d 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.cpp +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -10,7 +10,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; IdToken::IdToken(const char *token, MO_IdTokenType type, const char *memoryTag) : MemoryManaged(memoryTag ? memoryTag : "v201.Authorization.IdToken"), type(type) { if (token) { diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h index f8a2d683..2b9504f7 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.h +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -41,7 +41,7 @@ typedef enum { #define MO_IDTOKEN_LEN_MAX 36 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { // IdTokenType (2.28) class IdToken : public MemoryManaged { @@ -63,7 +63,7 @@ class IdToken : public MemoryManaged { }; } //namespace MicroOcpp -} //namespace Ocpp201 +} //namespace v201 #endif //__cplusplus #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 17b2eba9..3796ed06 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -19,18 +19,18 @@ using namespace MicroOcpp; -Ocpp16::AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId) : MemoryManaged("v16.Availability.AvailabilityServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), availService(availService), evseId(evseId) { +v16::AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId) : MemoryManaged("v16.Availability.AvailabilityServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), availService(availService), evseId(evseId) { } -Ocpp16::AvailabilityServiceEvse::~AvailabilityServiceEvse() { +v16::AvailabilityServiceEvse::~AvailabilityServiceEvse() { if (availabilityBool->getKey() == availabilityBoolKey) { availabilityBool->setKey(nullptr); } } -bool Ocpp16::AvailabilityServiceEvse::setup() { +bool v16::AvailabilityServiceEvse::setup() { connection = context.getConnection(); if (!connection) { @@ -67,7 +67,7 @@ bool Ocpp16::AvailabilityServiceEvse::setup() { return true; } -void Ocpp16::AvailabilityServiceEvse::loop() { +void v16::AvailabilityServiceEvse::loop() { if (!trackLoopExecute) { trackLoopExecute = true; @@ -161,27 +161,27 @@ void Ocpp16::AvailabilityServiceEvse::loop() { } } -void Ocpp16::AvailabilityServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { +void v16::AvailabilityServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { this->connectorPluggedInput = connectorPlugged; this->connectorPluggedInputUserData = userData; } -void Ocpp16::AvailabilityServiceEvse::setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData) { +void v16::AvailabilityServiceEvse::setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData) { this->evReadyInput = evReady; this->evReadyInputUserData = userData; } -void Ocpp16::AvailabilityServiceEvse::setEvseReadyInput(bool (*evseReady)(unsigned int, void*), void *userData) { +void v16::AvailabilityServiceEvse::setEvseReadyInput(bool (*evseReady)(unsigned int, void*), void *userData) { this->evseReadyInput = evseReady; this->evseReadyInputUserData = userData; } -void Ocpp16::AvailabilityServiceEvse::setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData) { +void v16::AvailabilityServiceEvse::setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData) { this->occupiedInput = occupied; this->occupiedInputUserData = userData; } -bool Ocpp16::AvailabilityServiceEvse::addErrorDataInput(MO_ErrorDataInput errorDataInput) { +bool v16::AvailabilityServiceEvse::addErrorDataInput(MO_ErrorDataInput errorDataInput) { size_t capacity = errorDataInputs.size() + 1; errorDataInputs.reserve(capacity); trackErrorDataInputs.reserve(capacity); @@ -195,16 +195,16 @@ bool Ocpp16::AvailabilityServiceEvse::addErrorDataInput(MO_ErrorDataInput errorD return true; } -void Ocpp16::AvailabilityServiceEvse::setAvailability(bool available) { +void v16::AvailabilityServiceEvse::setAvailability(bool available) { availabilityBool->setBool(available); availabilityContainer->commit(); } -void Ocpp16::AvailabilityServiceEvse::setAvailabilityVolatile(bool available) { +void v16::AvailabilityServiceEvse::setAvailabilityVolatile(bool available) { availabilityVolatile = available; } -MO_ChargePointStatus Ocpp16::AvailabilityServiceEvse::getStatus() { +MO_ChargePointStatus v16::AvailabilityServiceEvse::getStatus() { MO_ChargePointStatus res = MO_ChargePointStatus_UNDEFINED; @@ -274,7 +274,7 @@ MO_ChargePointStatus Ocpp16::AvailabilityServiceEvse::getStatus() { return res; } -bool Ocpp16::AvailabilityServiceEvse::isFaulted() { +bool v16::AvailabilityServiceEvse::isFaulted() { //for (auto i = errorDataInputs.begin(); i != errorDataInputs.end(); ++i) { for (size_t i = 0; i < errorDataInputs.size(); i++) { if (errorDataInputs[i].getErrorData(evseId, errorDataInputs[i].userData).isFaulted) { @@ -284,7 +284,7 @@ bool Ocpp16::AvailabilityServiceEvse::isFaulted() { return false; } -const char *Ocpp16::AvailabilityServiceEvse::getErrorCode() { +const char *v16::AvailabilityServiceEvse::getErrorCode() { if (reportedErrorIndex >= 0) { auto error = errorDataInputs[reportedErrorIndex].getErrorData(evseId, errorDataInputs[reportedErrorIndex].userData); if (error.isError && error.errorCode) { @@ -294,7 +294,7 @@ const char *Ocpp16::AvailabilityServiceEvse::getErrorCode() { return nullptr; } -bool Ocpp16::AvailabilityServiceEvse::isOperative() { +bool v16::AvailabilityServiceEvse::isOperative() { if (isFaulted()) { return false; } @@ -323,7 +323,7 @@ bool Ocpp16::AvailabilityServiceEvse::isOperative() { return availabilityVolatile && availabilityBool->getBool(); } -Operation *Ocpp16::AvailabilityServiceEvse::createTriggeredStatusNotification() { +Operation *v16::AvailabilityServiceEvse::createTriggeredStatusNotification() { MO_ErrorData errorData; mo_ErrorData_init(&errorData); @@ -350,18 +350,18 @@ Operation *Ocpp16::AvailabilityServiceEvse::createTriggeredStatusNotification() errorData); } -Ocpp16::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v16.Availability.AvailabilityService"), context(context) { +v16::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v16.Availability.AvailabilityService"), context(context) { } -Ocpp16::AvailabilityService::~AvailabilityService() { +v16::AvailabilityService::~AvailabilityService() { for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { delete evses[i]; evses[i] = nullptr; } } -bool Ocpp16::AvailabilityService::setup() { +bool v16::AvailabilityService::setup() { auto configService = context.getModel16().getConfigurationService(); if (!configService) { @@ -405,13 +405,13 @@ bool Ocpp16::AvailabilityService::setup() { return true; } -void Ocpp16::AvailabilityService::loop() { +void v16::AvailabilityService::loop() { for (size_t i = 0; i < numEvseId && evses[i]; i++) { evses[i]->loop(); } } -Ocpp16::AvailabilityServiceEvse *Ocpp16::AvailabilityService::getEvse(unsigned int evseId) { +v16::AvailabilityServiceEvse *v16::AvailabilityService::getEvse(unsigned int evseId) { if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; @@ -434,11 +434,11 @@ Ocpp16::AvailabilityServiceEvse *Ocpp16::AvailabilityService::getEvse(unsigned i using namespace MicroOcpp; -Ocpp201::AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId) : MemoryManaged("v201.Availability.AvailabilityServiceEvse"), context(context), availService(availService), evseId(evseId), faultedInputs(makeVector(getMemoryTag())) { +v201::AvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availService, unsigned int evseId) : MemoryManaged("v201.Availability.AvailabilityServiceEvse"), context(context), availService(availService), evseId(evseId), faultedInputs(makeVector(getMemoryTag())) { } -void Ocpp201::AvailabilityServiceEvse::loop() { +void v201::AvailabilityServiceEvse::loop() { if (!trackLoopExecute) { trackLoopExecute = true; @@ -459,17 +459,17 @@ void Ocpp201::AvailabilityServiceEvse::loop() { } } -void Ocpp201::AvailabilityServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { +void v201::AvailabilityServiceEvse::setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData) { this->connectorPluggedInput = connectorPlugged; this->connectorPluggedInputUserData = userData; } -void Ocpp201::AvailabilityServiceEvse::setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData) { +void v201::AvailabilityServiceEvse::setOccupiedInput(bool (*occupied)(unsigned int, void*), void *userData) { this->occupiedInput = occupied; this->occupiedInputUserData = userData; } -MO_ChargePointStatus Ocpp201::AvailabilityServiceEvse::getStatus() { +MO_ChargePointStatus v201::AvailabilityServiceEvse::getStatus() { MO_ChargePointStatus res = MO_ChargePointStatus_UNDEFINED; if (isFaulted()) { @@ -486,7 +486,7 @@ MO_ChargePointStatus Ocpp201::AvailabilityServiceEvse::getStatus() { return res; } -void Ocpp201::AvailabilityServiceEvse::setUnavailable(void *requesterId) { +void v201::AvailabilityServiceEvse::setUnavailable(void *requesterId) { for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { if (!unavailableRequesters[i]) { unavailableRequesters[i] = requesterId; @@ -496,7 +496,7 @@ void Ocpp201::AvailabilityServiceEvse::setUnavailable(void *requesterId) { MO_DBG_ERR("exceeded max. unavailable requesters"); } -void Ocpp201::AvailabilityServiceEvse::setAvailable(void *requesterId) { +void v201::AvailabilityServiceEvse::setAvailable(void *requesterId) { for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) { if (unavailableRequesters[i] == requesterId) { unavailableRequesters[i] = nullptr; @@ -505,7 +505,7 @@ void Ocpp201::AvailabilityServiceEvse::setAvailable(void *requesterId) { } } -ChangeAvailabilityStatus Ocpp201::AvailabilityServiceEvse::changeAvailability(bool operative) { +ChangeAvailabilityStatus v201::AvailabilityServiceEvse::changeAvailability(bool operative) { if (operative) { setAvailable(this); } else { @@ -529,7 +529,7 @@ ChangeAvailabilityStatus Ocpp201::AvailabilityServiceEvse::changeAvailability(bo return ChangeAvailabilityStatus::Accepted; } -Operation *Ocpp201::AvailabilityServiceEvse::createTriggeredStatusNotification() { +Operation *v201::AvailabilityServiceEvse::createTriggeredStatusNotification() { return new StatusNotification( context, evseId, @@ -537,7 +537,7 @@ Operation *Ocpp201::AvailabilityServiceEvse::createTriggeredStatusNotification() context.getClock().now()); } -bool Ocpp201::AvailabilityServiceEvse::isAvailable() { +bool v201::AvailabilityServiceEvse::isAvailable() { auto txService = context.getModel201().getTransactionService(); auto txEvse = txService ? txService->getEvse(evseId) : nullptr; @@ -563,7 +563,7 @@ bool Ocpp201::AvailabilityServiceEvse::isAvailable() { return true; } -bool Ocpp201::AvailabilityServiceEvse::isOperative() { +bool v201::AvailabilityServiceEvse::isOperative() { if (isFaulted()) { return false; } @@ -575,7 +575,7 @@ bool Ocpp201::AvailabilityServiceEvse::isOperative() { return isAvailable(); } -bool Ocpp201::AvailabilityServiceEvse::isFaulted() { +bool v201::AvailabilityServiceEvse::isFaulted() { //for (auto i = errorDataInputs.begin(); i != errorDataInputs.end(); ++i) { for (size_t i = 0; i < faultedInputs.size(); i++) { if (faultedInputs[i].isFaulted(evseId, faultedInputs[i].userData)) { @@ -585,7 +585,7 @@ bool Ocpp201::AvailabilityServiceEvse::isFaulted() { return false; } -bool Ocpp201::AvailabilityServiceEvse::addFaultedInput(FaultedInput faultedInput) { +bool v201::AvailabilityServiceEvse::addFaultedInput(FaultedInput faultedInput) { size_t capacity = faultedInputs.size() + 1; faultedInputs.reserve(capacity); if (faultedInputs.capacity() < capacity) { @@ -596,18 +596,18 @@ bool Ocpp201::AvailabilityServiceEvse::addFaultedInput(FaultedInput faultedInput return true; } -Ocpp201::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v201.Availability.AvailabilityService"), context(context) { +v201::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v201.Availability.AvailabilityService"), context(context) { } -Ocpp201::AvailabilityService::~AvailabilityService() { +v201::AvailabilityService::~AvailabilityService() { for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) { delete evses[i]; evses[i] = nullptr; } } -bool Ocpp201::AvailabilityService::setup() { +bool v201::AvailabilityService::setup() { auto varSvc = context.getModel201().getVariableService(); if (!varSvc) { @@ -651,13 +651,13 @@ bool Ocpp201::AvailabilityService::setup() { return true; } -void Ocpp201::AvailabilityService::loop() { +void v201::AvailabilityService::loop() { for (size_t i = 0; i < numEvseId && evses[i]; i++) { evses[i]->loop(); } } -Ocpp201::AvailabilityServiceEvse *Ocpp201::AvailabilityService::getEvse(unsigned int evseId) { +v201::AvailabilityServiceEvse *v201::AvailabilityService::getEvse(unsigned int evseId) { if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h index b2fd8832..24b5543b 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -27,7 +27,7 @@ class Context; class Clock; class Connection; -namespace Ocpp16 { +namespace v16 { class Model; class Configuration; @@ -123,7 +123,7 @@ friend class AvailabilityServiceEvse; }; } //namespace MicroOcpp -} //namespace Ocpp16 +} //namespace v16 #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 @@ -140,7 +140,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp201 { +namespace v201 { class AvailabilityService; class Variable; @@ -212,6 +212,6 @@ class AvailabilityService : public MemoryManaged { }; } //namespace MicroOcpp -} //namespace Ocpp201 +} //namespace v201 #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Boot/BootService.h b/src/MicroOcpp/Model/Boot/BootService.h index 9cab2420..8f945b12 100644 --- a/src/MicroOcpp/Model/Boot/BootService.h +++ b/src/MicroOcpp/Model/Boot/BootService.h @@ -45,12 +45,12 @@ class Context; class HeartbeatService; #if MO_ENABLE_V16 -namespace Ocpp16 { +namespace v16 { class Configuration; } #endif #if MO_ENABLE_V201 -namespace Ocpp201 { +namespace v201 { class Variable; } #endif @@ -75,10 +75,10 @@ class BootService : public MemoryManaged { int ocppVersion = -1; #if MO_ENABLE_V16 - Ocpp16::Configuration *preBootTransactionsBoolV16 = nullptr; + v16::Configuration *preBootTransactionsBoolV16 = nullptr; #endif #if MO_ENABLE_V201 - Ocpp201::Variable *preBootTransactionsBoolV201 = nullptr; + v201::Variable *preBootTransactionsBoolV201 = nullptr; #endif bool activatedModel = false; diff --git a/src/MicroOcpp/Model/Configuration/Configuration.cpp b/src/MicroOcpp/Model/Configuration/Configuration.cpp index 9e6c2161..41c213a1 100644 --- a/src/MicroOcpp/Model/Configuration/Configuration.cpp +++ b/src/MicroOcpp/Model/Configuration/Configuration.cpp @@ -10,7 +10,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; Configuration::~Configuration() { @@ -185,7 +185,7 @@ class ConfigurationConcrete : public Configuration { } }; -std::unique_ptr MicroOcpp::Ocpp16::makeConfiguration(Configuration::Type type) { +std::unique_ptr MicroOcpp::v16::makeConfiguration(Configuration::Type type) { return std::unique_ptr(new ConfigurationConcrete(type)); } diff --git a/src/MicroOcpp/Model/Configuration/Configuration.h b/src/MicroOcpp/Model/Configuration/Configuration.h index a5fc6703..86f8c5ac 100644 --- a/src/MicroOcpp/Model/Configuration/Configuration.h +++ b/src/MicroOcpp/Model/Configuration/Configuration.h @@ -20,7 +20,7 @@ namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { // ConfigurationStatus (7.22) enum class ConfigurationStatus : uint8_t { @@ -76,7 +76,7 @@ class Configuration : public MemoryManaged { std::unique_ptr makeConfiguration(Configuration::Type dtype); -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp index 51880c2d..bd613f02 100644 --- a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp +++ b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; ConfigurationContainer::~ConfigurationContainer() { diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.h b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.h index 65ec383c..be2dc3a3 100644 --- a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.h +++ b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.h @@ -18,7 +18,7 @@ #define MO_KEYVALUE_FN "client-state.jsn" namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class ConfigurationContainer { public: @@ -72,7 +72,7 @@ class ConfigurationContainerOwning : public ConfigurationContainer, public Memor bool commit() override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp b/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp index b985ec55..45ea0e1b 100644 --- a/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp +++ b/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp @@ -26,7 +26,7 @@ #endif namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { template ConfigurationValidator::ConfigurationValidator(const char *key, bool (*validateFn)(T, void*), void *user_data) : @@ -491,7 +491,7 @@ bool ConfigurationService::commit() { return success; } -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationService.h b/src/MicroOcpp/Model/Configuration/ConfigurationService.h index edffe85c..55a26de9 100644 --- a/src/MicroOcpp/Model/Configuration/ConfigurationService.h +++ b/src/MicroOcpp/Model/Configuration/ConfigurationService.h @@ -17,7 +17,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { template struct ConfigurationValidator { @@ -81,7 +81,7 @@ class ConfigurationService : public MemoryManaged { bool commit(); }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp b/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp index b018e46c..af9e5038 100644 --- a/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp +++ b/src/MicroOcpp/Model/Diagnostics/Diagnostics.cpp @@ -80,7 +80,7 @@ MO_LogType mo_deserializeLogType(const char *v) { #if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { const char *serializeDiagnosticsStatus(DiagnosticsStatus status) { const char *statusCstr = ""; @@ -101,6 +101,6 @@ const char *serializeDiagnosticsStatus(DiagnosticsStatus status) { return statusCstr; } -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp/Model/Diagnostics/Diagnostics.h b/src/MicroOcpp/Model/Diagnostics/Diagnostics.h index b5fa040c..587b26c5 100644 --- a/src/MicroOcpp/Model/Diagnostics/Diagnostics.h +++ b/src/MicroOcpp/Model/Diagnostics/Diagnostics.h @@ -60,7 +60,7 @@ MO_LogType mo_deserializeLogType(const char *v); #if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { enum class DiagnosticsStatus { Idle, @@ -70,7 +70,7 @@ enum class DiagnosticsStatus { }; const char *serializeDiagnosticsStatus(DiagnosticsStatus status); -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 7702008d..4cc79102 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -117,7 +117,7 @@ bool DiagnosticsService::setup() { } context.getMessageService().registerOperation("GetDiagnostics", [] (Context& context) -> Operation* { - return new Ocpp16::GetDiagnostics(context, *context.getModel16().getDiagnosticsService());}); + return new v16::GetDiagnostics(context, *context.getModel16().getDiagnosticsService());}); context.getMessageService().registerOperation("GetLog", [] (Context& context) -> Operation* { return new GetLog(context, *context.getModel16().getDiagnosticsService());}); @@ -135,7 +135,7 @@ bool DiagnosticsService::setup() { rcService->addTriggerMessageHandler("DiagnosticsStatusNotification", [] (Context& context) -> Operation* { auto diagSvc = context.getModel16().getDiagnosticsService(); - return new Ocpp16::DiagnosticsStatusNotification(diagSvc->getUploadStatus16()); + return new v16::DiagnosticsStatusNotification(diagSvc->getUploadStatus16()); }); rcService->addTriggerMessageHandler("LogStatusNotification", [] (Context& context) -> Operation* { @@ -206,8 +206,8 @@ void DiagnosticsService::loop() { if (use16impl) { if (getUploadStatus16() != lastReportedUploadStatus16) { lastReportedUploadStatus16 = getUploadStatus16(); - if (lastReportedUploadStatus16 != Ocpp16::DiagnosticsStatus::Idle) { - uploadStatusNotification = new Ocpp16::DiagnosticsStatusNotification(lastReportedUploadStatus16); + if (lastReportedUploadStatus16 != v16::DiagnosticsStatus::Idle) { + uploadStatusNotification = new v16::DiagnosticsStatusNotification(lastReportedUploadStatus16); if (!uploadStatusNotification) { MO_DBG_ERR("OOM"); } @@ -524,28 +524,28 @@ MO_UploadLogStatus DiagnosticsService::getUploadStatus() { return status; } -Ocpp16::DiagnosticsStatus DiagnosticsService::getUploadStatus16() { +v16::DiagnosticsStatus DiagnosticsService::getUploadStatus16() { MO_UploadLogStatus status = getUploadStatus(); - auto res = Ocpp16::DiagnosticsStatus::Idle; + auto res = v16::DiagnosticsStatus::Idle; switch(status) { case MO_UploadLogStatus_Idle: - res = Ocpp16::DiagnosticsStatus::Idle; + res = v16::DiagnosticsStatus::Idle; break; case MO_UploadLogStatus_Uploaded: case MO_UploadLogStatus_AcceptedCanceled: - res = Ocpp16::DiagnosticsStatus::Uploaded; + res = v16::DiagnosticsStatus::Uploaded; break; case MO_UploadLogStatus_BadMessage: case MO_UploadLogStatus_NotSupportedOperation: case MO_UploadLogStatus_PermissionDenied: case MO_UploadLogStatus_UploadFailure: - res = Ocpp16::DiagnosticsStatus::UploadFailed; + res = v16::DiagnosticsStatus::UploadFailed; break; case MO_UploadLogStatus_Uploading: - res = Ocpp16::DiagnosticsStatus::Uploading; + res = v16::DiagnosticsStatus::Uploading; break; } return res; diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h index 3d3a6677..ac7ea3fe 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h @@ -97,7 +97,7 @@ class DiagnosticsService : public MemoryManaged { MO_UploadLogStatus lastReportedUploadStatus = MO_UploadLogStatus_Idle; #if MO_ENABLE_V16 - Ocpp16::DiagnosticsStatus lastReportedUploadStatus16 = Ocpp16::DiagnosticsStatus::Idle; + v16::DiagnosticsStatus lastReportedUploadStatus16 = v16::DiagnosticsStatus::Idle; #endif //MO_ENABLE_V16 bool uploadDiagnostics(); //prepare diags and start FTP upload @@ -163,7 +163,7 @@ class DiagnosticsService : public MemoryManaged { int getRequestId(); MO_UploadLogStatus getUploadStatus(); #if MO_ENABLE_V16 - Ocpp16::DiagnosticsStatus getUploadStatus16(); + v16::DiagnosticsStatus getUploadStatus16(); #endif //MO_ENABLE_V16 }; diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp index fe6190e7..d1786c23 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp @@ -26,7 +26,7 @@ #endif namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { #if MO_USE_FW_UPDATER != MO_FW_UPDATER_CUSTOM bool setupDefaultFwUpdater(FirmwareService *fwService); @@ -75,7 +75,7 @@ bool FirmwareService::setup() { buildNumber = nullptr; context.getMessageService().registerOperation("UpdateFirmware", [] (Context& context) -> Operation* { - return new Ocpp16::UpdateFirmware(context, *context.getModel16().getFirmwareService());}); + return new v16::UpdateFirmware(context, *context.getModel16().getFirmwareService());}); #if MO_ENABLE_MOCK_SERVER context.getMessageService().registerOperation("FirmwareStatusNotification", nullptr, nullptr, nullptr); @@ -88,7 +88,7 @@ bool FirmwareService::setup() { } rcService->addTriggerMessageHandler("FirmwareStatusNotification", [] (Context& context) -> Operation* { - return new Ocpp16::FirmwareStatusNotification(context.getModel16().getFirmwareService()->getFirmwareStatus());}); + return new v16::FirmwareStatusNotification(context.getModel16().getFirmwareService()->getFirmwareStatus());}); #if MO_USE_FW_UPDATER != MO_FW_UPDATER_CUSTOM if (!setupDefaultFwUpdater(this)) { @@ -401,7 +401,7 @@ std::unique_ptr FirmwareService::getFirmwareStatusNotification() { notifyFirmwareUpdate = false; lastReportedStatus = FirmwareStatus::Installed; - auto fwNotificationMsg = new Ocpp16::FirmwareStatusNotification(lastReportedStatus); + auto fwNotificationMsg = new v16::FirmwareStatusNotification(lastReportedStatus); auto fwNotification = makeRequest(context, fwNotificationMsg); return fwNotification; } @@ -409,7 +409,7 @@ std::unique_ptr FirmwareService::getFirmwareStatusNotification() { if (getFirmwareStatus() != lastReportedStatus) { lastReportedStatus = getFirmwareStatus(); if (lastReportedStatus != FirmwareStatus::Idle) { - auto fwNotificationMsg = new Ocpp16::FirmwareStatusNotification(lastReportedStatus); + auto fwNotificationMsg = new v16::FirmwareStatusNotification(lastReportedStatus); auto fwNotification = makeRequest(context, fwNotificationMsg); return fwNotification; } @@ -482,14 +482,14 @@ void FirmwareService::setFtpServerCert(const char *cert) { this->ftpServerCert = cert; } -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #if MO_USE_FW_UPDATER == MO_FW_UPDATER_BUILTIN_ESP32 #include -bool MicroOcpp::Ocpp16::setupDefaultFwUpdater(MicroOcpp::Ocpp16::FirmwareService *fwService) { +bool MicroOcpp::v16::setupDefaultFwUpdater(MicroOcpp::v16::FirmwareService *fwService) { fwService->setDownloadFileWriter( [fwService] (const unsigned char *data, size_t size) -> size_t { if (!Update.isRunning()) { @@ -562,7 +562,7 @@ bool MicroOcpp::Ocpp16::setupDefaultFwUpdater(MicroOcpp::Ocpp16::FirmwareService #include -bool MicroOcpp::Ocpp16::setupDefaultFwUpdater(MicroOcpp::Ocpp16::FirmwareService *fwService) { +bool MicroOcpp::v16::setupDefaultFwUpdater(MicroOcpp::v16::FirmwareService *fwService) { fwService->setOnInstall([fwService] (const char *location) { diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h index 54d78dc7..30ed413e 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h @@ -49,7 +49,7 @@ enum class InstallationStatus { InstallationFailed }; -namespace Ocpp16 { +namespace v16 { class FirmwareService : public MemoryManaged { private: @@ -137,7 +137,7 @@ class FirmwareService : public MemoryManaged { FirmwareStatus getFirmwareStatus(); }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h b/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h index 9894ddde..725b8093 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h @@ -10,7 +10,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { enum class FirmwareStatus { Downloaded, @@ -22,7 +22,7 @@ enum class FirmwareStatus { Installed }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h index 7418b6e3..4c2a6904 100644 --- a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h +++ b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.h @@ -16,13 +16,13 @@ namespace MicroOcpp { class Context; #if MO_ENABLE_V16 -namespace Ocpp16 { +namespace v16 { class ConfigurationService; class Configuration; } #endif #if MO_ENABLE_V201 -namespace Ocpp201 { +namespace v201 { class VariableService; class Variable; } @@ -35,12 +35,12 @@ class HeartbeatService : public MemoryManaged { int ocppVersion = -1; #if MO_ENABLE_V16 - Ocpp16::ConfigurationService *configService = nullptr; - Ocpp16::Configuration *heartbeatIntervalIntV16 = nullptr; + v16::ConfigurationService *configService = nullptr; + v16::Configuration *heartbeatIntervalIntV16 = nullptr; #endif #if MO_ENABLE_V201 - Ocpp201::VariableService *varService = nullptr; - Ocpp201::Variable *heartbeatIntervalIntV201 = nullptr; + v201::VariableService *varService = nullptr; + v201::Variable *heartbeatIntervalIntV201 = nullptr; #endif Timestamp lastHeartbeat; diff --git a/src/MicroOcpp/Model/Metering/MeterStore.cpp b/src/MicroOcpp/Model/Metering/MeterStore.cpp index d21bf360..dcf3d77b 100644 --- a/src/MicroOcpp/Model/Metering/MeterStore.cpp +++ b/src/MicroOcpp/Model/Metering/MeterStore.cpp @@ -14,7 +14,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; bool MeterStore::printTxMeterValueFName(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int mvIndex) { auto ret = snprintf(fname, MO_MAX_PATH_SIZE, "sd-%.*u-%.*u-%.*u.jsn", diff --git a/src/MicroOcpp/Model/Metering/MeterStore.h b/src/MicroOcpp/Model/Metering/MeterStore.h index 5499a1c6..85074f20 100644 --- a/src/MicroOcpp/Model/Metering/MeterStore.h +++ b/src/MicroOcpp/Model/Metering/MeterStore.h @@ -26,7 +26,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { namespace MeterStore { bool printTxMeterValueFName(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int mvIndex); @@ -36,7 +36,7 @@ FilesystemUtils::StoreStatus store(MO_FilesystemAdapter *filesystem, Context& co bool remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr); //removes tx meter values only } //namespace MeterStore -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index 0436bbcf..a4056eaa 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -290,12 +290,12 @@ bool validateSelectString(const char *selectString, void *user_data) { using namespace MicroOcpp; -Ocpp16::MeteringServiceEvse::MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int connectorId) +v16::MeteringServiceEvse::MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int connectorId) : MemoryManaged("v16.Metering.MeteringServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), mService(mService), connectorId(connectorId), meterData(makeVector(getMemoryTag())), meterInputs(makeVector(getMemoryTag())) { } -Ocpp16::MeteringServiceEvse::~MeteringServiceEvse() { +v16::MeteringServiceEvse::~MeteringServiceEvse() { for (size_t i = 0; i < meterData.size(); i++) { delete meterData[i]; } @@ -304,7 +304,7 @@ Ocpp16::MeteringServiceEvse::~MeteringServiceEvse() { meterDataFront = nullptr; } -bool Ocpp16::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { +bool v16::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { auto capacity = meterInputs.size() + 1; meterInputs.resize(capacity); if (meterInputs.capacity() < capacity) { @@ -317,22 +317,22 @@ bool Ocpp16::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { return true; } -Vector& Ocpp16::MeteringServiceEvse::getMeterInputs() { +Vector& v16::MeteringServiceEvse::getMeterInputs() { return meterInputs; } -bool Ocpp16::MeteringServiceEvse::setTxEnergyMeterInput(int32_t (*getInt)()) { +bool v16::MeteringServiceEvse::setTxEnergyMeterInput(int32_t (*getInt)()) { txEnergyMeterInput = getInt; return true; } -bool Ocpp16::MeteringServiceEvse::setTxEnergyMeterInput2(int32_t (*getInt2)(MO_ReadingContext readingContext, unsigned int evseId, void *user_data), void *user_data) { +bool v16::MeteringServiceEvse::setTxEnergyMeterInput2(int32_t (*getInt2)(MO_ReadingContext readingContext, unsigned int evseId, void *user_data), void *user_data) { txEnergyMeterInput2 = getInt2; txEnergyMeterInput2_user_data = user_data; return true; } -bool Ocpp16::MeteringServiceEvse::setup() { +bool v16::MeteringServiceEvse::setup() { context.getMessageService().addSendQueue(this); @@ -387,11 +387,11 @@ bool Ocpp16::MeteringServiceEvse::setup() { return true; } -MicroOcpp::MeterValue *Ocpp16::MeteringServiceEvse::takeMeterValue(MO_ReadingContext readingContext, uint8_t inputSelectFlag) { +MicroOcpp::MeterValue *v16::MeteringServiceEvse::takeMeterValue(MO_ReadingContext readingContext, uint8_t inputSelectFlag) { return MicroOcpp::takeMeterValue(context.getClock(), meterInputs, connectorId, readingContext, inputSelectFlag, getMemoryTag()); } -void Ocpp16::MeteringServiceEvse::loop() { +void v16::MeteringServiceEvse::loop() { bool txBreak = false; auto transaction = txSvcEvse->getTransaction(); @@ -511,7 +511,7 @@ void Ocpp16::MeteringServiceEvse::loop() { } } -Operation *Ocpp16::MeteringServiceEvse::createTriggeredMeterValues() { +Operation *v16::MeteringServiceEvse::createTriggeredMeterValues() { auto meterValue = takeMeterValue(MO_ReadingContext_Trigger, MO_FLAG_MeterValuesSampledData); if (!meterValue) { @@ -534,7 +534,7 @@ Operation *Ocpp16::MeteringServiceEvse::createTriggeredMeterValues() { return operation; } -int32_t Ocpp16::MeteringServiceEvse::readTxEnergyMeter(MO_ReadingContext readingContext) { +int32_t v16::MeteringServiceEvse::readTxEnergyMeter(MO_ReadingContext readingContext) { if (txEnergyMeterInput) { return txEnergyMeterInput(); } else if (txEnergyMeterInput2) { @@ -544,7 +544,7 @@ int32_t Ocpp16::MeteringServiceEvse::readTxEnergyMeter(MO_ReadingContext reading } } -bool Ocpp16::MeteringServiceEvse::addTxMeterData(Transaction& transaction, MeterValue *mv) { +bool v16::MeteringServiceEvse::addTxMeterData(Transaction& transaction, MeterValue *mv) { auto& txMeterValues = transaction.getTxMeterValues(); @@ -588,7 +588,7 @@ bool Ocpp16::MeteringServiceEvse::addTxMeterData(Transaction& transaction, Meter return false; } -bool Ocpp16::MeteringServiceEvse::beginTxMeterData(Transaction *transaction) { +bool v16::MeteringServiceEvse::beginTxMeterData(Transaction *transaction) { auto& txMeterValues = transaction->getTxMeterValues(); @@ -605,7 +605,7 @@ bool Ocpp16::MeteringServiceEvse::beginTxMeterData(Transaction *transaction) { return addTxMeterData(*transaction, sampleTxBegin); } -bool Ocpp16::MeteringServiceEvse::endTxMeterData(Transaction *transaction) { +bool v16::MeteringServiceEvse::endTxMeterData(Transaction *transaction) { auto& txMeterValues = transaction->getTxMeterValues(); @@ -622,7 +622,7 @@ bool Ocpp16::MeteringServiceEvse::endTxMeterData(Transaction *transaction) { return addTxMeterData(*transaction, sampleTxEnd); } -void Ocpp16::MeteringServiceEvse::updateInputSelectFlags() { +void v16::MeteringServiceEvse::updateInputSelectFlags() { uint16_t selectInputsWriteCount = 0; selectInputsWriteCount += mService.meterValuesSampledDataString->getWriteCount(); selectInputsWriteCount += mService.stopTxnSampledDataString->getWriteCount(); @@ -639,7 +639,7 @@ void Ocpp16::MeteringServiceEvse::updateInputSelectFlags() { } } -unsigned int Ocpp16::MeteringServiceEvse::getFrontRequestOpNr() { +unsigned int v16::MeteringServiceEvse::getFrontRequestOpNr() { if (!meterDataFront && !meterData.empty()) { MO_DBG_DEBUG("advance MV front"); meterDataFront = meterData.front(); //transfer ownership @@ -651,7 +651,7 @@ unsigned int Ocpp16::MeteringServiceEvse::getFrontRequestOpNr() { return NoOperation; } -std::unique_ptr Ocpp16::MeteringServiceEvse::fetchFrontRequest() { +std::unique_ptr v16::MeteringServiceEvse::fetchFrontRequest() { if (!mService.connection->isConnected()) { //offline behavior: pause sending messages and do not increment attempt counters @@ -725,19 +725,19 @@ std::unique_ptr Ocpp16::MeteringServiceEvse::fetchFrontRequest() { return meterValues; } -Ocpp16::MeteringService::MeteringService(Context& context) +v16::MeteringService::MeteringService(Context& context) : MemoryManaged("v16.Metering.MeteringService"), context(context) { } -Ocpp16::MeteringService::~MeteringService() { +v16::MeteringService::~MeteringService() { for (unsigned int i = 0; i < MO_NUM_EVSEID; i++) { delete evses[i]; evses[i] = nullptr; } } -bool Ocpp16::MeteringService::setup() { +bool v16::MeteringService::setup() { connection = context.getConnection(); if (!connection) { @@ -814,13 +814,13 @@ bool Ocpp16::MeteringService::setup() { return true; } -void Ocpp16::MeteringService::loop(){ +void v16::MeteringService::loop(){ for (unsigned int i = 0; i < numEvseId; i++){ evses[i]->loop(); } } -Ocpp16::MeteringServiceEvse *Ocpp16::MeteringService::getEvse(unsigned int evseId) { +v16::MeteringServiceEvse *v16::MeteringService::getEvse(unsigned int evseId) { if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; @@ -848,16 +848,16 @@ Ocpp16::MeteringServiceEvse *Ocpp16::MeteringService::getEvse(unsigned int evseI using namespace MicroOcpp; -Ocpp201::MeteringServiceEvse::MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int evseId) +v201::MeteringServiceEvse::MeteringServiceEvse(Context& context, MeteringService& mService, unsigned int evseId) : MemoryManaged("v201.MeterValues.MeteringServiceEvse"), context(context), mService(mService), evseId(evseId), meterInputs(makeVector(getMemoryTag())) { } -bool Ocpp201::MeteringServiceEvse::setup() { +bool v201::MeteringServiceEvse::setup() { return true; //nothing to be done here } -bool Ocpp201::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { +bool v201::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { auto capacity = meterInputs.size() + 1; meterInputs.resize(capacity); if (meterInputs.capacity() < capacity) { @@ -870,11 +870,11 @@ bool Ocpp201::MeteringServiceEvse::addMeterInput(MO_MeterInput meterInput) { return true; } -Vector& Ocpp201::MeteringServiceEvse::getMeterInputs() { +Vector& v201::MeteringServiceEvse::getMeterInputs() { return meterInputs; } -void Ocpp201::MeteringServiceEvse::updateInputSelectFlags() { +void v201::MeteringServiceEvse::updateInputSelectFlags() { uint16_t selectInputsWriteCount = 0; selectInputsWriteCount += mService.sampledDataTxStartedMeasurands->getWriteCount(); selectInputsWriteCount += mService.sampledDataTxUpdatedMeasurands->getWriteCount(); @@ -891,35 +891,35 @@ void Ocpp201::MeteringServiceEvse::updateInputSelectFlags() { } } -std::unique_ptr Ocpp201::MeteringServiceEvse::takeTxStartedMeterValue(MO_ReadingContext readingContext) { +std::unique_ptr v201::MeteringServiceEvse::takeTxStartedMeterValue(MO_ReadingContext readingContext) { updateInputSelectFlags(); return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, readingContext, MO_FLAG_SampledDataTxStarted, getMemoryTag())); } -std::unique_ptr Ocpp201::MeteringServiceEvse::takeTxUpdatedMeterValue(MO_ReadingContext readingContext) { +std::unique_ptr v201::MeteringServiceEvse::takeTxUpdatedMeterValue(MO_ReadingContext readingContext) { updateInputSelectFlags(); return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, readingContext, MO_FLAG_SampledDataTxUpdated, getMemoryTag())); } -std::unique_ptr Ocpp201::MeteringServiceEvse::takeTxEndedMeterValue(MO_ReadingContext readingContext) { +std::unique_ptr v201::MeteringServiceEvse::takeTxEndedMeterValue(MO_ReadingContext readingContext) { updateInputSelectFlags(); return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, readingContext, MO_FLAG_SampledDataTxEnded, getMemoryTag())); } -std::unique_ptr Ocpp201::MeteringServiceEvse::takeTriggeredMeterValues() { +std::unique_ptr v201::MeteringServiceEvse::takeTriggeredMeterValues() { updateInputSelectFlags(); return std::unique_ptr(MicroOcpp::takeMeterValue(context.getClock(), meterInputs, evseId, MO_ReadingContext_Trigger, MO_FLAG_AlignedData, getMemoryTag())); } -Ocpp201::MeteringService::MeteringService(Context& context) : MemoryManaged("v201.Metering.MeteringService"), context(context) { +v201::MeteringService::MeteringService(Context& context) : MemoryManaged("v201.Metering.MeteringService"), context(context) { } -Ocpp201::MeteringService::~MeteringService() { +v201::MeteringService::~MeteringService() { for (size_t i = 0; i < MO_NUM_EVSEID; i++) { delete evses[i]; evses[i] = nullptr; } } -Ocpp201::MeteringServiceEvse *Ocpp201::MeteringService::getEvse(unsigned int evseId) { +v201::MeteringServiceEvse *v201::MeteringService::getEvse(unsigned int evseId) { if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; @@ -936,7 +936,7 @@ Ocpp201::MeteringServiceEvse *Ocpp201::MeteringService::getEvse(unsigned int evs return evses[evseId]; } -bool Ocpp201::MeteringService::setup() { +bool v201::MeteringService::setup() { auto varService = context.getModel201().getVariableService(); if (!varService) { diff --git a/src/MicroOcpp/Model/Metering/MeteringService.h b/src/MicroOcpp/Model/Metering/MeteringService.h index 28fded6c..f24c54ba 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.h +++ b/src/MicroOcpp/Model/Metering/MeteringService.h @@ -29,7 +29,7 @@ class Clock; class Operation; class Request; -namespace Ocpp16 { +namespace v16 { class Model; class MeteringService; @@ -132,7 +132,7 @@ class MeteringService : public MemoryManaged { friend class MeteringServiceEvse; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 @@ -142,7 +142,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp201 { +namespace v201 { class MeteringService; class Variable; @@ -192,7 +192,7 @@ class MeteringService : public MemoryManaged { friend class MeteringServiceEvse; }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index f63ba6bd..dc1767b7 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -33,11 +33,11 @@ using namespace MicroOcpp; -Ocpp16::Model::Model(Context& context) : MemoryManaged("v16.Model"), context(context) { +v16::Model::Model(Context& context) : MemoryManaged("v16.Model"), context(context) { } -Ocpp16::Model::~Model() { +v16::Model::~Model() { delete bootService; bootService = nullptr; delete heartbeatService; @@ -92,7 +92,7 @@ Ocpp16::Model::~Model() { configurationService = nullptr; } -void Ocpp16::Model::updateSupportedStandardProfiles() { +void v16::Model::updateSupportedStandardProfiles() { auto supportedFeatureProfilesString = configurationService->declareConfiguration("SupportedFeatureProfiles", "", MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); @@ -158,7 +158,7 @@ void Ocpp16::Model::updateSupportedStandardProfiles() { MO_DBG_DEBUG("supported feature profiles: %s", buf.c_str()); } -void Ocpp16::Model::setNumEvseId(unsigned int numEvseId) { +void v16::Model::setNumEvseId(unsigned int numEvseId) { if (numEvseId >= MO_NUM_EVSEID) { MO_DBG_ERR("invalid arg"); return; @@ -166,27 +166,27 @@ void Ocpp16::Model::setNumEvseId(unsigned int numEvseId) { this->numEvseId = numEvseId; } -unsigned int Ocpp16::Model::getNumEvseId() { +unsigned int v16::Model::getNumEvseId() { return numEvseId; } -BootService *Ocpp16::Model::getBootService() { +BootService *v16::Model::getBootService() { if (!bootService) { bootService = new BootService(context); } return bootService; } -HeartbeatService *Ocpp16::Model::getHeartbeatService() { +HeartbeatService *v16::Model::getHeartbeatService() { if (!heartbeatService) { heartbeatService = new HeartbeatService(context); } return heartbeatService; } -Ocpp16::ConfigurationService *Ocpp16::Model::getConfigurationService() { +v16::ConfigurationService *v16::Model::getConfigurationService() { if (!configurationService) { - configurationService = new Ocpp16::ConfigurationService(context); + configurationService = new v16::ConfigurationService(context); // need extra init step so that other modules can declare configs before setup() if (configurationService && !configurationService->init()) { @@ -198,35 +198,35 @@ Ocpp16::ConfigurationService *Ocpp16::Model::getConfigurationService() { return configurationService; } -Ocpp16::TransactionService *Ocpp16::Model::getTransactionService() { +v16::TransactionService *v16::Model::getTransactionService() { if (!transactionService) { transactionService = new TransactionService(context); } return transactionService; } -Ocpp16::MeteringService* Ocpp16::Model::getMeteringService() { +v16::MeteringService* v16::Model::getMeteringService() { if (!meteringService) { meteringService = new MeteringService(context); } return meteringService; } -Ocpp16::ResetService *Ocpp16::Model::getResetService() { +v16::ResetService *v16::Model::getResetService() { if (!resetService) { resetService = new ResetService(context); } return resetService; } -Ocpp16::AvailabilityService *Ocpp16::Model::getAvailabilityService() { +v16::AvailabilityService *v16::Model::getAvailabilityService() { if (!availabilityService) { - availabilityService = new Ocpp16::AvailabilityService(context); + availabilityService = new v16::AvailabilityService(context); } return availabilityService; } -RemoteControlService *Ocpp16::Model::getRemoteControlService() { +RemoteControlService *v16::Model::getRemoteControlService() { if (!remoteControlService) { remoteControlService = new RemoteControlService(context); } @@ -234,7 +234,7 @@ RemoteControlService *Ocpp16::Model::getRemoteControlService() { } #if MO_ENABLE_FIRMWAREMANAGEMENT -Ocpp16::FirmwareService *Ocpp16::Model::getFirmwareService() { +v16::FirmwareService *v16::Model::getFirmwareService() { if (!firmwareService) { firmwareService = new FirmwareService(context); } @@ -243,7 +243,7 @@ Ocpp16::FirmwareService *Ocpp16::Model::getFirmwareService() { #endif //MO_ENABLE_FIRMWAREMANAGEMENT #if MO_ENABLE_DIAGNOSTICS -DiagnosticsService *Ocpp16::Model::getDiagnosticsService() { +DiagnosticsService *v16::Model::getDiagnosticsService() { if (!diagnosticsService) { diagnosticsService = new DiagnosticsService(context); } @@ -252,7 +252,7 @@ DiagnosticsService *Ocpp16::Model::getDiagnosticsService() { #endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_LOCAL_AUTH -Ocpp16::AuthorizationService *Ocpp16::Model::getAuthorizationService() { +v16::AuthorizationService *v16::Model::getAuthorizationService() { if (!authorizationService) { authorizationService = new AuthorizationService(context); } @@ -261,7 +261,7 @@ Ocpp16::AuthorizationService *Ocpp16::Model::getAuthorizationService() { #endif //MO_ENABLE_LOCAL_AUTH #if MO_ENABLE_RESERVATION -Ocpp16::ReservationService *Ocpp16::Model::getReservationService() { +v16::ReservationService *v16::Model::getReservationService() { if (!reservationService) { reservationService = new ReservationService(context); } @@ -270,7 +270,7 @@ Ocpp16::ReservationService *Ocpp16::Model::getReservationService() { #endif //MO_ENABLE_RESERVATION #if MO_ENABLE_SMARTCHARGING -SmartChargingService* Ocpp16::Model::getSmartChargingService() { +SmartChargingService* v16::Model::getSmartChargingService() { if (!smartChargingService) { smartChargingService = new SmartChargingService(context); } @@ -279,7 +279,7 @@ SmartChargingService* Ocpp16::Model::getSmartChargingService() { #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT -CertificateService *Ocpp16::Model::getCertificateService() { +CertificateService *v16::Model::getCertificateService() { if (!certService) { certService = new CertificateService(context); } @@ -288,7 +288,7 @@ CertificateService *Ocpp16::Model::getCertificateService() { #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_SECURITY_EVENT -SecurityEventService *Ocpp16::Model::getSecurityEventService() { +SecurityEventService *v16::Model::getSecurityEventService() { if (!secEventService) { secEventService = new SecurityEventService(context); } @@ -296,7 +296,7 @@ SecurityEventService *Ocpp16::Model::getSecurityEventService() { } #endif //MO_ENABLE_SECURITY_EVENT -bool Ocpp16::Model::setup() { +bool v16::Model::setup() { if (!getBootService() || !getBootService()->setup()) { MO_DBG_ERR("setup failure"); return false; @@ -391,12 +391,12 @@ bool Ocpp16::Model::setup() { // Register remainder of operations which don't have dedicated service context.getMessageService().registerOperation("DataTransfer", [] (Context&) -> Operation* { - return new Ocpp16::DataTransfer();}); + return new v16::DataTransfer();}); return true; } -void Ocpp16::Model::loop() { +void v16::Model::loop() { if (bootService) { bootService->loop(); @@ -457,11 +457,11 @@ void Ocpp16::Model::loop() { using namespace MicroOcpp; -Ocpp201::Model::Model(Context& context) : MemoryManaged("v201.Model"), context(context) { +v201::Model::Model(Context& context) : MemoryManaged("v201.Model"), context(context) { } -Ocpp201::Model::~Model() { +v201::Model::~Model() { delete bootService; bootService = nullptr; delete heartbeatService; @@ -501,7 +501,7 @@ Ocpp201::Model::~Model() { variableService = nullptr; } -void Ocpp201::Model::setNumEvseId(unsigned int numEvseId) { +void v201::Model::setNumEvseId(unsigned int numEvseId) { if (numEvseId >= MO_NUM_EVSEID) { MO_DBG_ERR("invalid arg"); return; @@ -509,27 +509,27 @@ void Ocpp201::Model::setNumEvseId(unsigned int numEvseId) { this->numEvseId = numEvseId; } -unsigned int Ocpp201::Model::getNumEvseId() { +unsigned int v201::Model::getNumEvseId() { return numEvseId; } -BootService *Ocpp201::Model::getBootService() { +BootService *v201::Model::getBootService() { if (!bootService) { bootService = new BootService(context); } return bootService; } -HeartbeatService *Ocpp201::Model::getHeartbeatService() { +HeartbeatService *v201::Model::getHeartbeatService() { if (!heartbeatService) { heartbeatService = new HeartbeatService(context); } return heartbeatService; } -Ocpp201::VariableService *Ocpp201::Model::getVariableService() { +v201::VariableService *v201::Model::getVariableService() { if (!variableService) { - variableService = new Ocpp201::VariableService(context); + variableService = new v201::VariableService(context); // need extra init step so that other modules can declare variables before setup() if (variableService && !variableService->init()) { @@ -541,35 +541,35 @@ Ocpp201::VariableService *Ocpp201::Model::getVariableService() { return variableService; } -Ocpp201::TransactionService *Ocpp201::Model::getTransactionService() { +v201::TransactionService *v201::Model::getTransactionService() { if (!transactionService) { transactionService = new TransactionService(context); } return transactionService; } -Ocpp201::MeteringService *Ocpp201::Model::getMeteringService() { +v201::MeteringService *v201::Model::getMeteringService() { if (!meteringService) { - meteringService = new Ocpp201::MeteringService(context); + meteringService = new v201::MeteringService(context); } return meteringService; } -Ocpp201::ResetService *Ocpp201::Model::getResetService() { +v201::ResetService *v201::Model::getResetService() { if (!resetService) { - resetService = new Ocpp201::ResetService(context); + resetService = new v201::ResetService(context); } return resetService; } -Ocpp201::AvailabilityService *Ocpp201::Model::getAvailabilityService() { +v201::AvailabilityService *v201::Model::getAvailabilityService() { if (!availabilityService) { - availabilityService = new Ocpp201::AvailabilityService(context); + availabilityService = new v201::AvailabilityService(context); } return availabilityService; } -RemoteControlService *Ocpp201::Model::getRemoteControlService() { +RemoteControlService *v201::Model::getRemoteControlService() { if (!remoteControlService) { remoteControlService = new RemoteControlService(context); } @@ -577,7 +577,7 @@ RemoteControlService *Ocpp201::Model::getRemoteControlService() { } #if MO_ENABLE_DIAGNOSTICS -DiagnosticsService *Ocpp201::Model::getDiagnosticsService() { +DiagnosticsService *v201::Model::getDiagnosticsService() { if (!diagnosticsService) { diagnosticsService = new DiagnosticsService(context); } @@ -586,7 +586,7 @@ DiagnosticsService *Ocpp201::Model::getDiagnosticsService() { #endif //MO_ENABLE_DIAGNOSTICS #if MO_ENABLE_SMARTCHARGING -SmartChargingService* Ocpp201::Model::getSmartChargingService() { +SmartChargingService* v201::Model::getSmartChargingService() { if (!smartChargingService) { smartChargingService = new SmartChargingService(context); } @@ -595,7 +595,7 @@ SmartChargingService* Ocpp201::Model::getSmartChargingService() { #endif //MO_ENABLE_SMARTCHARGING #if MO_ENABLE_CERT_MGMT -CertificateService *Ocpp201::Model::getCertificateService() { +CertificateService *v201::Model::getCertificateService() { if (!certService) { certService = new CertificateService(context); } @@ -604,7 +604,7 @@ CertificateService *Ocpp201::Model::getCertificateService() { #endif //MO_ENABLE_CERT_MGMT #if MO_ENABLE_SECURITY_EVENT -SecurityEventService *Ocpp201::Model::getSecurityEventService() { +SecurityEventService *v201::Model::getSecurityEventService() { if (!secEventService) { secEventService = new SecurityEventService(context); } @@ -612,7 +612,7 @@ SecurityEventService *Ocpp201::Model::getSecurityEventService() { } #endif //MO_ENABLE_SECURITY_EVENT -bool Ocpp201::Model::setup() { +bool v201::Model::setup() { if (!getBootService() || !getBootService()->setup()) { MO_DBG_ERR("setup failure"); return false; @@ -684,7 +684,7 @@ bool Ocpp201::Model::setup() { return true; } -void Ocpp201::Model::loop() { +void v201::Model::loop() { if (bootService) { bootService->loop(); diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 60baab40..35272ec4 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -37,7 +37,7 @@ class SecurityEventService; #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class ConfigurationService; class TransactionService; @@ -153,14 +153,14 @@ class Model : public MemoryManaged { void activateTasks() {runTasks = true;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class VariableService; class TransactionService; @@ -238,17 +238,17 @@ class Model : public MemoryManaged { void activateTasks() {runTasks = true;} }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #if MO_ENABLE_V16 && !MO_ENABLE_V201 namespace MicroOcpp { -using Model = Ocpp16::Model; +using Model = v16::Model; } #elif !MO_ENABLE_V16 && MO_ENABLE_V201 namespace MicroOcpp { -using Model = Ocpp201::Model; +using Model = v201::Model; } #endif diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h index f3ec8152..ad1fd7f3 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h @@ -50,7 +50,7 @@ enum class TriggerMessageStatus : uint8_t { #ifdef __cplusplus namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { enum class RemoteStartStopStatus : uint8_t { ERR_INTERNAL, @@ -65,7 +65,7 @@ enum class UnlockStatus : uint8_t { PENDING //MO-internal: unlock action not finished yet, result still unknown. Check later }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //__cplusplus @@ -75,7 +75,7 @@ enum class UnlockStatus : uint8_t { #ifdef __cplusplus namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { enum class RequestStartStopStatus : uint8_t { Accepted, @@ -90,7 +90,7 @@ enum class UnlockStatus : uint8_t { PENDING //MO-internal: unlock action not finished yet, result still unknown. Check later }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //__cplusplus #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index 24560bdb..d801f7e4 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -73,10 +73,10 @@ void RemoteControlServiceEvse::setOnUnlockConnector(MO_UnlockConnectorResult (*o } #if MO_ENABLE_V16 -Ocpp16::UnlockStatus RemoteControlServiceEvse::unlockConnector16() { +v16::UnlockStatus RemoteControlServiceEvse::unlockConnector16() { if (!onUnlockConnector) { - return Ocpp16::UnlockStatus::UnlockFailed; + return v16::UnlockStatus::UnlockFailed; } auto tx = txServiceEvse16->getTransaction(); @@ -88,28 +88,28 @@ Ocpp16::UnlockStatus RemoteControlServiceEvse::unlockConnector16() { auto status = onUnlockConnector(evseId, onUnlockConnectorUserData); switch (status) { case MO_UnlockConnectorResult_Pending: - return Ocpp16::UnlockStatus::PENDING; + return v16::UnlockStatus::PENDING; case MO_UnlockConnectorResult_Unlocked: - return Ocpp16::UnlockStatus::Unlocked; + return v16::UnlockStatus::Unlocked; case MO_UnlockConnectorResult_UnlockFailed: - return Ocpp16::UnlockStatus::UnlockFailed; + return v16::UnlockStatus::UnlockFailed; } MO_DBG_ERR("invalid onUnlockConnector result code"); - return Ocpp16::UnlockStatus::UnlockFailed; + return v16::UnlockStatus::UnlockFailed; } #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -Ocpp201::UnlockStatus RemoteControlServiceEvse::unlockConnector201() { +v201::UnlockStatus RemoteControlServiceEvse::unlockConnector201() { if (!onUnlockConnector) { - return Ocpp201::UnlockStatus::UnlockFailed; + return v201::UnlockStatus::UnlockFailed; } if (auto tx = txServiceEvse201->getTransaction()) { if (tx->started && !tx->stopped && tx->isAuthorized) { - return Ocpp201::UnlockStatus::OngoingAuthorizedTransaction; + return v201::UnlockStatus::OngoingAuthorizedTransaction; } else { txServiceEvse201->abortTransaction(MO_TxStoppedReason_Other,MO_TxEventTriggerReason_UnlockCommand); } @@ -118,15 +118,15 @@ Ocpp201::UnlockStatus RemoteControlServiceEvse::unlockConnector201() { auto status = onUnlockConnector(evseId, onUnlockConnectorUserData); switch (status) { case MO_UnlockConnectorResult_Pending: - return Ocpp201::UnlockStatus::PENDING; + return v201::UnlockStatus::PENDING; case MO_UnlockConnectorResult_Unlocked: - return Ocpp201::UnlockStatus::Unlocked; + return v201::UnlockStatus::Unlocked; case MO_UnlockConnectorResult_UnlockFailed: - return Ocpp201::UnlockStatus::UnlockFailed; + return v201::UnlockStatus::UnlockFailed; } MO_DBG_ERR("invalid onUnlockConnector result code"); - return Ocpp201::UnlockStatus::UnlockFailed; + return v201::UnlockStatus::UnlockFailed; } #endif //MO_ENABLE_V201 @@ -190,11 +190,11 @@ bool RemoteControlService::setup() { numEvseId = context.getModel16().getNumEvseId(); context.getMessageService().registerOperation("RemoteStartTransaction", [] (Context& context) -> Operation* { - return new Ocpp16::RemoteStartTransaction(context, *context.getModel16().getRemoteControlService());}); + return new v16::RemoteStartTransaction(context, *context.getModel16().getRemoteControlService());}); context.getMessageService().registerOperation("RemoteStopTransaction", [] (Context& context) -> Operation* { - return new Ocpp16::RemoteStopTransaction(context, *context.getModel16().getRemoteControlService());}); + return new v16::RemoteStopTransaction(context, *context.getModel16().getRemoteControlService());}); context.getMessageService().registerOperation("UnlockConnector", [] (Context& context) -> Operation* { - return new Ocpp16::UnlockConnector(context, *context.getModel16().getRemoteControlService());}); + return new v16::UnlockConnector(context, *context.getModel16().getRemoteControlService());}); } #endif #if MO_ENABLE_V201 @@ -220,13 +220,13 @@ bool RemoteControlService::setup() { numEvseId = context.getModel201().getNumEvseId(); context.getMessageService().registerOperation("RequestStartTransaction", [] (Context& context) -> Operation* { - return new Ocpp201::RequestStartTransaction(context, *context.getModel201().getRemoteControlService());}); + return new v201::RequestStartTransaction(context, *context.getModel201().getRemoteControlService());}); context.getMessageService().registerOperation("RequestStopTransaction", [] (Context& context) -> Operation* { - return new Ocpp201::RequestStopTransaction(*context.getModel201().getRemoteControlService());}); + return new v201::RequestStopTransaction(*context.getModel201().getRemoteControlService());}); #if MO_ENABLE_CONNECTOR_LOCK context.getMessageService().registerOperation("UnlockConnector", [] (Context& context) -> Operation* { - return new Ocpp201::UnlockConnector(context, *context.getModel201().getRemoteControlService());}); + return new v201::UnlockConnector(context, *context.getModel201().getRemoteControlService());}); #endif } #endif @@ -298,18 +298,18 @@ bool RemoteControlService::addTriggerMessageHandler(const char *operationType, T } #if MO_ENABLE_V16 -Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile) { +v16::RemoteStartStopStatus RemoteControlService::remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile) { auto configService = context.getModel16().getConfigurationService(); auto authorizeRemoteTxRequests = configService ? configService->declareConfiguration("AuthorizeRemoteTxRequests", false) : nullptr; if (!authorizeRemoteTxRequests) { MO_DBG_ERR("internal error"); - return Ocpp16::RemoteStartStopStatus::ERR_INTERNAL; + return v16::RemoteStartStopStatus::ERR_INTERNAL; } - auto status = Ocpp16::RemoteStartStopStatus::Rejected; + auto status = v16::RemoteStartStopStatus::Rejected; - Ocpp16::TransactionServiceEvse *selectEvse = nullptr; + v16::TransactionServiceEvse *selectEvse = nullptr; if (connectorId >= 1) { //connectorId specified for given connector, try to start Transaction here auto txSvcEvse = txService16->getEvse(connectorId); @@ -365,7 +365,7 @@ Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStartTransaction(int c } } else { MO_DBG_ERR("internal error"); - status = Ocpp16::RemoteStartStopStatus::ERR_INTERNAL; + status = v16::RemoteStartStopStatus::ERR_INTERNAL; success = false; } } @@ -375,21 +375,21 @@ Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStartTransaction(int c } if (success) { - status = Ocpp16::RemoteStartStopStatus::Accepted; + status = v16::RemoteStartStopStatus::Accepted; } else { - status = Ocpp16::RemoteStartStopStatus::Rejected; + status = v16::RemoteStartStopStatus::Rejected; } } else { MO_DBG_INFO("No connector to start transaction"); - status = Ocpp16::RemoteStartStopStatus::Rejected; + status = v16::RemoteStartStopStatus::Rejected; } return status; } -Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int transactionId) { +v16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int transactionId) { - auto status = Ocpp16::RemoteStartStopStatus::Rejected; + auto status = v16::RemoteStartStopStatus::Rejected; for (unsigned int eId = 0; eId < numEvseId; eId++) { auto txSvcEvse = txService16->getEvse(eId); @@ -400,7 +400,7 @@ Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int tr txSvcEvse->updateTxNotification(MO_TxNotification_RemoteStop); } txSvcEvse->endTransaction(nullptr, "Remote"); - status = Ocpp16::RemoteStartStopStatus::Accepted; + status = v16::RemoteStartStopStatus::Accepted; } } @@ -410,17 +410,17 @@ Ocpp16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int tr #if MO_ENABLE_V201 -Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize) { +v201::RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, v201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize) { if (!txService201) { MO_DBG_ERR("TxService uninitialized"); - return Ocpp201::RequestStartStopStatus::Rejected; + return v201::RequestStartStopStatus::Rejected; } auto evse = txService201->getEvse(evseId); if (!evse) { MO_DBG_ERR("EVSE not found"); - return Ocpp201::RequestStartStopStatus::Rejected; + return v201::RequestStartStopStatus::Rejected; } if (!evse->beginAuthorization(idToken, authorizeRemoteStart->getBool(), nullptr, /*commit*/ false)) { //only commit after storing the ChargingProfile @@ -429,15 +429,15 @@ Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(un auto ret = snprintf(transactionIdOut, transactionIdBufSize, "%s", tx->transactionId); if (ret < 0 || (size_t)ret >= transactionIdBufSize) { MO_DBG_ERR("internal error"); - return Ocpp201::RequestStartStopStatus::Rejected; + return v201::RequestStartStopStatus::Rejected; } } - return Ocpp201::RequestStartStopStatus::Rejected; + return v201::RequestStartStopStatus::Rejected; } int ret = -1; - Ocpp201::Transaction *tx = evse->getTransaction(); + v201::Transaction *tx = evse->getTransaction(); if (!tx) { goto fail; } @@ -476,7 +476,7 @@ Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(un } - return Ocpp201::RequestStartStopStatus::Accepted; + return v201::RequestStartStopStatus::Accepted; fail: evse->abortTransaction(); #if MO_ENABLE_SMARTCHARGING @@ -485,14 +485,14 @@ Ocpp201::RequestStartStopStatus RemoteControlService::requestStartTransaction(un scService->clearChargingProfile(chargingProfile->chargingProfileId,evseId,MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1); } #endif //MO_ENABLE_SMARTCHARGING - return Ocpp201::RequestStartStopStatus::Rejected; + return v201::RequestStartStopStatus::Rejected; } -Ocpp201::RequestStartStopStatus RemoteControlService::requestStopTransaction(const char *transactionId) { +v201::RequestStartStopStatus RemoteControlService::requestStopTransaction(const char *transactionId) { if (!txService201) { MO_DBG_ERR("TxService uninitialized"); - return Ocpp201::RequestStartStopStatus::Rejected; + return v201::RequestStartStopStatus::Rejected; } bool success = false; @@ -507,8 +507,8 @@ Ocpp201::RequestStartStopStatus RemoteControlService::requestStopTransaction(con } return success ? - Ocpp201::RequestStartStopStatus::Accepted : - Ocpp201::RequestStartStopStatus::Rejected; + v201::RequestStartStopStatus::Accepted : + v201::RequestStartStopStatus::Rejected; } #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h index 1bcf9bf7..5de2cc15 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -20,19 +20,19 @@ class Context; class RemoteControlService; #if MO_ENABLE_V16 -namespace Ocpp16 { +namespace v16 { class Configuration; class TransactionService; class TransactionServiceEvse; -} //namespace Ocpp16 +} //namespace v16 #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 -namespace Ocpp201 { +namespace v201 { class Variable; class TransactionService; class TransactionServiceEvse; -} //namespace Ocpp201 +} //namespace v201 #endif //MO_ENABLE_V201 class RemoteControlServiceEvse : public MemoryManaged { @@ -42,10 +42,10 @@ class RemoteControlServiceEvse : public MemoryManaged { const unsigned int evseId; #if MO_ENABLE_V16 - Ocpp16::TransactionServiceEvse *txServiceEvse16 = nullptr; + v16::TransactionServiceEvse *txServiceEvse16 = nullptr; #endif #if MO_ENABLE_V201 - Ocpp201::TransactionServiceEvse *txServiceEvse201 = nullptr; + v201::TransactionServiceEvse *txServiceEvse201 = nullptr; #endif #if MO_ENABLE_CONNECTOR_LOCK @@ -64,10 +64,10 @@ class RemoteControlServiceEvse : public MemoryManaged { #if MO_ENABLE_CONNECTOR_LOCK #if MO_ENABLE_V16 - Ocpp16::UnlockStatus unlockConnector16(); + v16::UnlockStatus unlockConnector16(); #endif #if MO_ENABLE_V201 - Ocpp201::UnlockStatus unlockConnector201(); + v201::UnlockStatus unlockConnector201(); #endif #endif //MO_ENABLE_CONNECTOR_LOCK }; @@ -79,12 +79,12 @@ class RemoteControlService : public MemoryManaged { unsigned int numEvseId = MO_NUM_EVSEID; #if MO_ENABLE_V16 - Ocpp16::TransactionService *txService16 = nullptr; - Ocpp16::Configuration *authorizeRemoteTxRequests = nullptr; + v16::TransactionService *txService16 = nullptr; + v16::Configuration *authorizeRemoteTxRequests = nullptr; #endif #if MO_ENABLE_V201 - Ocpp201::TransactionService *txService201 = nullptr; - Ocpp201::Variable *authorizeRemoteStart = nullptr; + v201::TransactionService *txService201 = nullptr; + v201::Variable *authorizeRemoteStart = nullptr; #endif struct OperationCreator { @@ -108,15 +108,15 @@ class RemoteControlService : public MemoryManaged { bool setup(); #if MO_ENABLE_V16 - Ocpp16::RemoteStartStopStatus remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile); + v16::RemoteStartStopStatus remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile); - Ocpp16::RemoteStartStopStatus remoteStopTransaction(int transactionId); + v16::RemoteStartStopStatus remoteStopTransaction(int transactionId); #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 - Ocpp201::RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, Ocpp201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize); //ChargingProfile, GroupIdToken not supported yet + v201::RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, v201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize); //ChargingProfile, GroupIdToken not supported yet - Ocpp201::RequestStartStopStatus requestStopTransaction(const char *transactionId); + v201::RequestStartStopStatus requestStopTransaction(const char *transactionId); #endif //MO_ENABLE_V201 TriggerMessageStatus triggerMessage(const char *requestedMessage, int evseId = -1); diff --git a/src/MicroOcpp/Model/Reservation/Reservation.cpp b/src/MicroOcpp/Model/Reservation/Reservation.cpp index dd7d2736..7474d965 100644 --- a/src/MicroOcpp/Model/Reservation/Reservation.cpp +++ b/src/MicroOcpp/Model/Reservation/Reservation.cpp @@ -14,7 +14,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_RESERVATION using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; Reservation::Reservation(Context& context, unsigned int slot) : MemoryManaged("v16.Reservation.Reservation"), context(context), slot(slot) { diff --git a/src/MicroOcpp/Model/Reservation/Reservation.h b/src/MicroOcpp/Model/Reservation/Reservation.h index e8e37eb0..969a216a 100644 --- a/src/MicroOcpp/Model/Reservation/Reservation.h +++ b/src/MicroOcpp/Model/Reservation/Reservation.h @@ -25,7 +25,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class Configuration; class ConfigurationContainer; @@ -75,7 +75,7 @@ class Reservation : public MemoryManaged { bool clear(); }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION #endif diff --git a/src/MicroOcpp/Model/Reservation/ReservationService.cpp b/src/MicroOcpp/Model/Reservation/ReservationService.cpp index 34ccb299..ec05ec48 100644 --- a/src/MicroOcpp/Model/Reservation/ReservationService.cpp +++ b/src/MicroOcpp/Model/Reservation/ReservationService.cpp @@ -17,7 +17,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_RESERVATION using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; ReservationService::ReservationService(Context& context) : MemoryManaged("v16.Reservation.ReservationService"), context(context) { diff --git a/src/MicroOcpp/Model/Reservation/ReservationService.h b/src/MicroOcpp/Model/Reservation/ReservationService.h index 1d698be0..5e67931f 100644 --- a/src/MicroOcpp/Model/Reservation/ReservationService.h +++ b/src/MicroOcpp/Model/Reservation/ReservationService.h @@ -18,7 +18,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class ReservationService : public MemoryManaged { private: @@ -54,7 +54,7 @@ class ReservationService : public MemoryManaged { bool updateReservation(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag = nullptr); }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION #endif diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index 62fcf56d..77f838fc 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -40,16 +40,16 @@ void (*defaultExecuteResetImpl)() = nullptr; using namespace MicroOcpp; -Ocpp16::ResetService::ResetService(Context& context) +v16::ResetService::ResetService(Context& context) : MemoryManaged("v16.Reset.ResetService"), context(context) { } -Ocpp16::ResetService::~ResetService() { +v16::ResetService::~ResetService() { } -bool Ocpp16::ResetService::setup() { +bool v16::ResetService::setup() { auto configService = context.getModel16().getConfigurationService(); if (!configService) { @@ -81,7 +81,7 @@ bool Ocpp16::ResetService::setup() { return true; } -void Ocpp16::ResetService::loop() { +void v16::ResetService::loop() { auto& clock = context.getClock(); @@ -125,29 +125,29 @@ void Ocpp16::ResetService::loop() { } } -void Ocpp16::ResetService::setNotifyReset(bool (*notifyResetCb)(bool isHard, void *userData), void *userData) { +void v16::ResetService::setNotifyReset(bool (*notifyResetCb)(bool isHard, void *userData), void *userData) { this->notifyResetCb = notifyResetCb; this->notifyResetUserData = userData; } -bool Ocpp16::ResetService::isPreResetDefined() { +bool v16::ResetService::isPreResetDefined() { return notifyResetCb != nullptr; } -bool Ocpp16::ResetService::notifyReset(bool isHard) { +bool v16::ResetService::notifyReset(bool isHard) { return notifyResetCb(isHard, notifyResetUserData); } -void Ocpp16::ResetService::setExecuteReset(bool (*executeReset)(bool isHard, void *userData), void *userData) { +void v16::ResetService::setExecuteReset(bool (*executeReset)(bool isHard, void *userData), void *userData) { this->executeResetCb = executeReset; this->executeResetUserData = userData; } -bool Ocpp16::ResetService::isExecuteResetDefined() { +bool v16::ResetService::isExecuteResetDefined() { return executeResetCb != nullptr; } -void Ocpp16::ResetService::initiateReset(bool isHard) { +void v16::ResetService::initiateReset(bool isHard) { for (unsigned int eId = 0; eId < context.getModel16().getNumEvseId(); eId++) { auto txSvc = context.getModel16().getTransactionService(); @@ -171,19 +171,19 @@ void Ocpp16::ResetService::initiateReset(bool isHard) { #if MO_ENABLE_V201 namespace MicroOcpp { -Ocpp201::ResetService::ResetService(Context& context) +v201::ResetService::ResetService(Context& context) : MemoryManaged("v201.Reset.ResetService"), context(context) { } -Ocpp201::ResetService::~ResetService() { +v201::ResetService::~ResetService() { for (unsigned int i = 0; i < numEvseId; i++) { delete evses[i]; evses[i] = 0; } } -bool Ocpp201::ResetService::setup() { +bool v201::ResetService::setup() { auto varService = context.getModel201().getVariableService(); if (!varService) { return false; @@ -222,11 +222,11 @@ bool Ocpp201::ResetService::setup() { return true; } -Ocpp201::ResetService::Evse::Evse(Context& context, ResetService& resetService, unsigned int evseId) : MemoryManaged("v201.Reset.ResetService"), context(context), resetService(resetService), evseId(evseId) { +v201::ResetService::Evse::Evse(Context& context, ResetService& resetService, unsigned int evseId) : MemoryManaged("v201.Reset.ResetService"), context(context), resetService(resetService), evseId(evseId) { } -bool Ocpp201::ResetService::Evse::setup() { +bool v201::ResetService::Evse::setup() { auto varService = context.getModel201().getVariableService(); if (!varService) { return false; @@ -235,7 +235,7 @@ bool Ocpp201::ResetService::Evse::setup() { return true; } -void Ocpp201::ResetService::Evse::loop() { +void v201::ResetService::Evse::loop() { auto& clock = context.getClock(); @@ -310,7 +310,7 @@ void Ocpp201::ResetService::Evse::loop() { } } -Ocpp201::ResetService::Evse *Ocpp201::ResetService::getEvse(unsigned int evseId) { +v201::ResetService::Evse *v201::ResetService::getEvse(unsigned int evseId) { if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; @@ -327,7 +327,7 @@ Ocpp201::ResetService::Evse *Ocpp201::ResetService::getEvse(unsigned int evseId) return evses[evseId]; } -void Ocpp201::ResetService::loop() { +void v201::ResetService::loop() { for (unsigned i = 0; i < numEvseId; i++) { if (evses[i]) { evses[i]->loop(); @@ -335,7 +335,7 @@ void Ocpp201::ResetService::loop() { } } -void Ocpp201::ResetService::setNotifyReset(unsigned int evseId, bool (*notifyReset)(MO_ResetType, unsigned int evseId, void *userData), void *userData) { +void v201::ResetService::setNotifyReset(unsigned int evseId, bool (*notifyReset)(MO_ResetType, unsigned int evseId, void *userData), void *userData) { Evse *evse = getEvse(evseId); if (!evse) { MO_DBG_ERR("evseId not found"); @@ -345,7 +345,7 @@ void Ocpp201::ResetService::setNotifyReset(unsigned int evseId, bool (*notifyRes evse->notifyResetUserData = userData; } -void Ocpp201::ResetService::setExecuteReset(unsigned int evseId, bool (*executeReset)(unsigned int evseId, void *userData), void *userData) { +void v201::ResetService::setExecuteReset(unsigned int evseId, bool (*executeReset)(unsigned int evseId, void *userData), void *userData) { Evse *evse = getEvse(evseId); if (!evse) { MO_DBG_ERR("evseId not found"); @@ -355,7 +355,7 @@ void Ocpp201::ResetService::setExecuteReset(unsigned int evseId, bool (*executeR evse->executeResetUserData = userData; } -ResetStatus Ocpp201::ResetService::initiateReset(MO_ResetType type, unsigned int evseId) { +ResetStatus v201::ResetService::initiateReset(MO_ResetType type, unsigned int evseId) { auto evse = getEvse(evseId); if (!evse) { MO_DBG_ERR("evseId not found"); diff --git a/src/MicroOcpp/Model/Reset/ResetService.h b/src/MicroOcpp/Model/Reset/ResetService.h index a3d2187c..e54855c9 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.h +++ b/src/MicroOcpp/Model/Reset/ResetService.h @@ -17,7 +17,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class Configuration; @@ -56,7 +56,7 @@ class ResetService : public MemoryManaged { void initiateReset(bool isHard); }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 @@ -64,7 +64,7 @@ class ResetService : public MemoryManaged { namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class Variable; @@ -115,7 +115,7 @@ class ResetService : public MemoryManaged { ResetStatus initiateReset(MO_ResetType type, unsigned int evseId = 0); }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h index 1a442b21..c088f8cf 100644 --- a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h +++ b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.h @@ -50,12 +50,12 @@ class Context; class Connection; #if MO_ENABLE_V16 -namespace Ocpp16 { +namespace v16 { class Configuration; } #endif #if MO_ENABLE_V201 -namespace Ocpp201 { +namespace v201 { class Variable; } #endif @@ -69,10 +69,10 @@ class SecurityEventService : public MemoryManaged { int ocppVersion = -1; #if MO_ENABLE_V16 - Ocpp16::Configuration *timeAdjustmentReportingThresholdIntV16 = nullptr; + v16::Configuration *timeAdjustmentReportingThresholdIntV16 = nullptr; #endif #if MO_ENABLE_V201 - Ocpp201::Variable *timeAdjustmentReportingThresholdIntV201 = nullptr; + v201::Variable *timeAdjustmentReportingThresholdIntV201 = nullptr; #endif Timestamp trackUptime; diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h index dcefe9f5..f02adf3b 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.h @@ -24,7 +24,7 @@ class Context; class SmartChargingService; #if MO_ENABLE_V201 -namespace Ocpp201 { +namespace v201 { class Variable; } #endif //MO_ENABLE_V201 @@ -104,7 +104,7 @@ class SmartChargingService : public MemoryManaged { int ocppVersion = -1; #if MO_ENABLE_V201 - Ocpp201::Variable *chargingProfileEntriesInt201 = nullptr; + v201::Variable *chargingProfileEntriesInt201 = nullptr; #endif //MO_ENABLE_V201 SmartChargingServiceEvse *getEvse(unsigned int evseId); diff --git a/src/MicroOcpp/Model/Transactions/Transaction.cpp b/src/MicroOcpp/Model/Transactions/Transaction.cpp index 039ac0fe..bbb8890c 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.cpp +++ b/src/MicroOcpp/Model/Transactions/Transaction.cpp @@ -10,21 +10,21 @@ using namespace MicroOcpp; -Ocpp16::Transaction::Transaction(unsigned int connectorId, unsigned int txNr, bool silent) : +v16::Transaction::Transaction(unsigned int connectorId, unsigned int txNr, bool silent) : MemoryManaged("v16.Transactions.Transaction"), connectorId(connectorId), txNr(txNr), silent(silent), meterValues(makeVector("v16.Transactions.TransactionMeterData")) { } -Ocpp16::Transaction::~Transaction() { +v16::Transaction::~Transaction() { for (size_t i = 0; i < meterValues.size(); i++) { delete meterValues[i]; } meterValues.clear(); } -void Ocpp16::Transaction::setTransactionId(int transactionId) { +void v16::Transaction::setTransactionId(int transactionId) { this->transactionId = transactionId; #if MO_ENABLE_V201 if (transactionId > 0) { @@ -35,22 +35,22 @@ void Ocpp16::Transaction::setTransactionId(int transactionId) { #endif //MO_ENABLE_V201 } -bool Ocpp16::Transaction::setIdTag(const char *idTag) { +bool v16::Transaction::setIdTag(const char *idTag) { auto ret = snprintf(this->idTag, MO_IDTAG_LEN_MAX + 1, "%s", idTag); return ret >= 0 && ret < MO_IDTAG_LEN_MAX + 1; } -bool Ocpp16::Transaction::setParentIdTag(const char *idTag) { +bool v16::Transaction::setParentIdTag(const char *idTag) { auto ret = snprintf(this->parentIdTag, MO_IDTAG_LEN_MAX + 1, "%s", idTag); return ret >= 0 && ret < MO_IDTAG_LEN_MAX + 1; } -bool Ocpp16::Transaction::setStopIdTag(const char *idTag) { +bool v16::Transaction::setStopIdTag(const char *idTag) { auto ret = snprintf(stop_idTag, MO_IDTAG_LEN_MAX + 1, "%s", idTag); return ret >= 0 && ret < MO_IDTAG_LEN_MAX + 1; } -bool Ocpp16::Transaction::setStopReason(const char *reason) { +bool v16::Transaction::setStopReason(const char *reason) { auto ret = snprintf(stop_reason, sizeof(stop_reason) + 1, "%s", reason); return ret >= 0 && (size_t)ret < sizeof(stop_reason) + 1; } @@ -60,7 +60,7 @@ bool Ocpp16::Transaction::setStopReason(const char *reason) { #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { const char *serializeTransactionStoppedReason(MO_TxStoppedReason stoppedReason) { const char *stoppedReasonCstr = nullptr; @@ -375,7 +375,7 @@ bool deserializeTransactionEventChargingState(const char *chargingStateCstr, Tra return true; } -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index a794b98b..5ac52758 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -25,7 +25,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { /* * A transaction is initiated by the client (charging station) and processed by the server (central system). @@ -189,7 +189,7 @@ class Transaction : public MemoryManaged { Vector& getTxMeterValues() {return meterValues;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 @@ -205,7 +205,7 @@ namespace MicroOcpp { class Clock; -namespace Ocpp201 { +namespace v201 { class Transaction : public MemoryManaged { private: @@ -366,7 +366,7 @@ const char *serializeTransactionEventChargingState(TransactionEventData::Chargin bool deserializeTransactionEventChargingState(const char *chargingStateCstr, TransactionEventData::ChargingState& chargingStateOut); -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index f477130c..178d2bbe 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -33,7 +33,7 @@ #endif using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; TransactionServiceEvse::TransactionServiceEvse(Context& context, TransactionService& cService, unsigned int evseId) : MemoryManaged("v16.Transactions.TransactionServiceEvse"), context(context), clock(context.getClock()), model(context.getModel16()), cService(cService), evseId(evseId) { diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.h b/src/MicroOcpp/Model/Transactions/TransactionService16.h index 7a77ad59..4c38022d 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.h @@ -32,7 +32,7 @@ class Clock; class Connection; class Operation; -namespace Ocpp16 { +namespace v16 { class Model; class Configuration; @@ -170,7 +170,7 @@ class TransactionService : public MemoryManaged { friend class TransactionServiceEvse; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index a15e4357..1c46bcf4 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -26,7 +26,7 @@ #endif using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; TransactionServiceEvse::TransactionServiceEvse(Context& context, TransactionService& txService, TransactionStoreEvse& txStore, unsigned int evseId) : MemoryManaged("v201.Transactions.TransactionServiceEvse"), diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.h b/src/MicroOcpp/Model/Transactions/TransactionService201.h index 98a9612c..af3a679b 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.h @@ -33,7 +33,7 @@ namespace MicroOcpp { class Context; class Clock; -namespace Ocpp201 { +namespace v201 { // TxStartStopPoint (2.6.4.1) enum class TxStartStopPoint : uint8_t { @@ -168,7 +168,7 @@ class TransactionService : public MemoryManaged { friend class TransactionServiceEvse; }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index ab899f00..df8b6696 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -14,7 +14,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { namespace TransactionStore { bool serializeSendStatus(Clock& clock, SendStatus& status, JsonObject out); @@ -24,12 +24,12 @@ bool serializeTransaction(Clock& clock, Transaction& tx, JsonDoc& out); bool deserializeTransaction(Clock& clock, Transaction& tx, JsonObject in); } //namespace TransactionStore -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp using namespace MicroOcpp; -bool Ocpp16::TransactionStore::printTxFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr) { +bool v16::TransactionStore::printTxFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr) { auto ret = snprintf(fname, size, "tx-%.*u-%.*u.json", MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr); if (ret < 0 || (size_t)ret >= size) { @@ -39,7 +39,7 @@ bool Ocpp16::TransactionStore::printTxFname(char *fname, size_t size, unsigned i return true; } -FilesystemUtils::LoadStatus Ocpp16::TransactionStore::load(MO_FilesystemAdapter *filesystem, Context& context, unsigned int evseId, unsigned int txNr, Transaction& transaction) { +FilesystemUtils::LoadStatus v16::TransactionStore::load(MO_FilesystemAdapter *filesystem, Context& context, unsigned int evseId, unsigned int txNr, Transaction& transaction) { char fname [MO_MAX_PATH_SIZE]; if (!printTxFname(fname, sizeof(fname), evseId, txNr)) { @@ -67,7 +67,7 @@ FilesystemUtils::LoadStatus Ocpp16::TransactionStore::load(MO_FilesystemAdapter return FilesystemUtils::LoadStatus::Success; } -FilesystemUtils::StoreStatus Ocpp16::TransactionStore::store(MO_FilesystemAdapter *filesystem, Context& context, Transaction& transaction) { +FilesystemUtils::StoreStatus v16::TransactionStore::store(MO_FilesystemAdapter *filesystem, Context& context, Transaction& transaction) { char fname [MO_MAX_PATH_SIZE]; if (!printTxFname(fname, sizeof(fname), transaction.getConnectorId(), transaction.getTxNr())) { @@ -91,7 +91,7 @@ FilesystemUtils::StoreStatus Ocpp16::TransactionStore::store(MO_FilesystemAdapte return ret; } -bool Ocpp16::TransactionStore::remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr) { +bool v16::TransactionStore::remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr) { char fname [MO_MAX_PATH_SIZE]; if (!printTxFname(fname, sizeof(fname), evseId, txNr)) { @@ -108,7 +108,7 @@ bool Ocpp16::TransactionStore::remove(MO_FilesystemAdapter *filesystem, unsigned return filesystem->remove(path); } -bool Ocpp16::TransactionStore::serializeSendStatus(Clock& clock, SendStatus& status, JsonObject out) { +bool v16::TransactionStore::serializeSendStatus(Clock& clock, SendStatus& status, JsonObject out) { if (status.isRequested()) { out["requested"] = true; } @@ -130,7 +130,7 @@ bool Ocpp16::TransactionStore::serializeSendStatus(Clock& clock, SendStatus& sta return true; } -bool Ocpp16::TransactionStore::deserializeSendStatus(Clock& clock, SendStatus& status, JsonObject in) { +bool v16::TransactionStore::deserializeSendStatus(Clock& clock, SendStatus& status, JsonObject in) { if (in["requested"] | false) { status.setRequested(); } @@ -153,7 +153,7 @@ bool Ocpp16::TransactionStore::deserializeSendStatus(Clock& clock, SendStatus& s return true; } -bool Ocpp16::TransactionStore::serializeTransaction(Clock& clock, Transaction& tx, JsonDoc& out) { +bool v16::TransactionStore::serializeTransaction(Clock& clock, Transaction& tx, JsonDoc& out) { out = initJsonDoc("v16.Transactions.TransactionDeserialize", 1024); JsonObject state = out.to(); @@ -247,7 +247,7 @@ bool Ocpp16::TransactionStore::serializeTransaction(Clock& clock, Transaction& t return true; } -bool Ocpp16::TransactionStore::deserializeTransaction(Clock& clock, Transaction& tx, JsonObject state) { +bool v16::TransactionStore::deserializeTransaction(Clock& clock, Transaction& tx, JsonObject state) { JsonObject sessionState = state["session"]; @@ -371,7 +371,7 @@ bool Ocpp16::TransactionStore::deserializeTransaction(Clock& clock, Transaction& using namespace MicroOcpp; -bool Ocpp201::TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJson) { +bool v201::TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJson) { if (tx.trackEvConnected) { txJson["trackEvConnected"] = tx.trackEvConnected; @@ -473,7 +473,7 @@ bool Ocpp201::TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonOb return true; } -bool Ocpp201::TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject txJson) { +bool v201::TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject txJson) { if (txJson.containsKey("trackEvConnected") && !txJson["trackEvConnected"].is()) { return false; @@ -616,7 +616,7 @@ bool Ocpp201::TransactionStoreEvse::deserializeTransaction(Transaction& tx, Json return true; } -bool Ocpp201::TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { +bool v201::TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { if (txEvent.eventType != TransactionEventData::Type::Updated) { txEventJson["eventType"] = serializeTransactionEventType(txEvent.eventType); @@ -688,7 +688,7 @@ bool Ocpp201::TransactionStoreEvse::serializeTransactionEvent(TransactionEventDa return true; } -bool Ocpp201::TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { +bool v201::TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { TransactionEventData::Type eventType; if (!deserializeTransactionEventType(txEventJson["eventType"] | "Updated", eventType)) { @@ -806,14 +806,14 @@ bool Ocpp201::TransactionStoreEvse::deserializeTransactionEvent(TransactionEvent return true; } -Ocpp201::TransactionStoreEvse::TransactionStoreEvse(Context& context, unsigned int evseId) : +v201::TransactionStoreEvse::TransactionStoreEvse(Context& context, unsigned int evseId) : MemoryManaged("v201.Transactions.TransactionStore"), context(context), evseId(evseId) { } -bool Ocpp201::TransactionStoreEvse::setup() { +bool v201::TransactionStoreEvse::setup() { filesystem = context.getFilesystem(); if (!filesystem) { MO_DBG_DEBUG("volatile mode"); @@ -821,7 +821,7 @@ bool Ocpp201::TransactionStoreEvse::setup() { return true; } -bool Ocpp201::TransactionStoreEvse::printTxEventFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int seqNo) { +bool v201::TransactionStoreEvse::printTxEventFname(char *fname, size_t size, unsigned int evseId, unsigned int txNr, unsigned int seqNo) { auto ret = snprintf(fname, size, "tx201-%.*u-%.*u-%.*u.json", MO_NUM_EVSEID_DIGITS, evseId, MO_TXNR_DIGITS, txNr, MO_TXEVENTRECORD_DIGITS, seqNo); if (ret < 0 || (size_t)ret >= size) { @@ -854,7 +854,7 @@ int loadSeqNoEntry(const char *fname, void* user_data) { } } //namespace MicroOcpp -std::unique_ptr Ocpp201::TransactionStoreEvse::loadTransaction(unsigned int txNr) { +std::unique_ptr v201::TransactionStoreEvse::loadTransaction(unsigned int txNr) { if (!filesystem) { MO_DBG_DEBUG("no FS adapter"); @@ -941,7 +941,7 @@ std::unique_ptr Ocpp201::TransactionStoreEvse::loadTransac return transaction; } -std::unique_ptr Ocpp201::TransactionStoreEvse::createTransaction(unsigned int txNr, const char *txId) { +std::unique_ptr v201::TransactionStoreEvse::createTransaction(unsigned int txNr, const char *txId) { //clean data which could still be here from a rolled-back transaction if (!remove(txNr)) { @@ -967,7 +967,7 @@ std::unique_ptr Ocpp201::TransactionStoreEvse::createTrans return transaction; } -std::unique_ptr Ocpp201::TransactionStoreEvse::createTransactionEvent(Transaction& tx) { +std::unique_ptr v201::TransactionStoreEvse::createTransactionEvent(Transaction& tx) { auto txEvent = std::unique_ptr(new TransactionEventData(&tx, tx.seqNoEnd)); if (!txEvent) { @@ -979,7 +979,7 @@ std::unique_ptr Ocpp201::TransactionStoreEvse::cr return txEvent; } -std::unique_ptr Ocpp201::TransactionStoreEvse::loadTransactionEvent(Transaction& tx, unsigned int seqNo) { +std::unique_ptr v201::TransactionStoreEvse::loadTransactionEvent(Transaction& tx, unsigned int seqNo) { if (!filesystem) { MO_DBG_DEBUG("no FS adapter"); @@ -1039,7 +1039,7 @@ std::unique_ptr Ocpp201::TransactionStoreEvse::lo return txEvent; } -bool Ocpp201::TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent) { +bool v201::TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent) { if (!filesystem) { MO_DBG_DEBUG("no FS: nothing to commit"); @@ -1123,15 +1123,15 @@ bool Ocpp201::TransactionStoreEvse::commit(Transaction& tx, TransactionEventData return true; } -bool Ocpp201::TransactionStoreEvse::commit(Transaction *transaction) { +bool v201::TransactionStoreEvse::commit(Transaction *transaction) { return commit(*transaction, nullptr); } -bool Ocpp201::TransactionStoreEvse::commit(TransactionEventData *txEvent) { +bool v201::TransactionStoreEvse::commit(TransactionEventData *txEvent) { return commit(*txEvent->transaction, txEvent); } -bool Ocpp201::TransactionStoreEvse::remove(unsigned int txNr) { +bool v201::TransactionStoreEvse::remove(unsigned int txNr) { if (!filesystem) { MO_DBG_DEBUG("no FS: nothing to remove"); @@ -1149,7 +1149,7 @@ bool Ocpp201::TransactionStoreEvse::remove(unsigned int txNr) { return FilesystemUtils::removeByPrefix(filesystem, fnPrefix); } -bool Ocpp201::TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) { +bool v201::TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) { if (tx.seqNos.empty()) { //nothing to do @@ -1244,18 +1244,18 @@ bool Ocpp201::TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) return success; } -Ocpp201::TransactionStore::TransactionStore(Context& context) : +v201::TransactionStore::TransactionStore(Context& context) : MemoryManaged{"v201.Transactions.TransactionStore"}, context(context) { } -Ocpp201::TransactionStore::~TransactionStore() { +v201::TransactionStore::~TransactionStore() { for (unsigned int evseId = 0; evseId < numEvseId; evseId++) { delete evses[evseId]; } } -bool Ocpp201::TransactionStore::setup() { +bool v201::TransactionStore::setup() { numEvseId = context.getModel201().getNumEvseId(); for (unsigned int i = 0; i < numEvseId; i++) { @@ -1268,7 +1268,7 @@ bool Ocpp201::TransactionStore::setup() { return true; } -Ocpp201::TransactionStoreEvse *Ocpp201::TransactionStore::getEvse(unsigned int evseId) { +v201::TransactionStoreEvse *v201::TransactionStore::getEvse(unsigned int evseId) { if (evseId >= numEvseId) { MO_DBG_ERR("evseId out of bound"); return nullptr; diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.h b/src/MicroOcpp/Model/Transactions/TransactionStore.h index dac4755f..d156fb7b 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.h +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.h @@ -25,7 +25,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class Transaction; @@ -38,7 +38,7 @@ FilesystemUtils::StoreStatus store(MO_FilesystemAdapter *filesystem, Context& co bool remove(MO_FilesystemAdapter *filesystem, unsigned int evseId, unsigned int txNr); } //namespace TransactionStore -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_OCPP_V16 @@ -56,7 +56,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp201 { +namespace v201 { class Transaction; class TransactionEventData; @@ -111,7 +111,7 @@ class TransactionStore : public MemoryManaged { TransactionStoreEvse *getEvse(unsigned int evseId); }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/Variable.cpp b/src/MicroOcpp/Model/Variables/Variable.cpp index f5c1d891..fbfa91a2 100644 --- a/src/MicroOcpp/Model/Variables/Variable.cpp +++ b/src/MicroOcpp/Model/Variables/Variable.cpp @@ -14,7 +14,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; ComponentId::ComponentId(const char *name) : name(name) { } ComponentId::ComponentId(const char *name, EvseId evse) : name(name), evse(evse) { } @@ -367,7 +367,7 @@ class VariableString : public Variable { } }; -std::unique_ptr MicroOcpp::Ocpp201::makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes) { +std::unique_ptr MicroOcpp::v201::makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes) { switch(dtype) { case Variable::InternalDataType::Int: if (supportAttributes.count() > 1) { diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index 596ce08c..8042168d 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -26,7 +26,7 @@ namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { // VariableCharacteristicsType (2.51) struct VariableCharacteristics : public MemoryManaged { @@ -221,7 +221,7 @@ class Variable : public MemoryManaged { std::unique_ptr makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes); -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.cpp b/src/MicroOcpp/Model/Variables/VariableContainer.cpp index d06021a5..b4a3f1e0 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.cpp +++ b/src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -15,7 +15,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; VariableContainer::~VariableContainer() { diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.h b/src/MicroOcpp/Model/Variables/VariableContainer.h index 339d8460..a0298c65 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.h +++ b/src/MicroOcpp/Model/Variables/VariableContainer.h @@ -19,7 +19,7 @@ #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class VariableContainer { public: @@ -70,7 +70,7 @@ class VariableContainerOwning : public VariableContainer, public MemoryManaged { bool commit() override; }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp index b9611eb9..7a8f51cc 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.cpp +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -25,7 +25,7 @@ #endif namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { template VariableValidator::VariableValidator(const ComponentId& component, const char *name, bool (*validateFn)(T, void*), void *userPtr) : @@ -551,6 +551,6 @@ GenericDeviceModelStatus VariableService::getBaseReport(int requestId, ReportBas return GenericDeviceModelStatus_Accepted; } -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Variables/VariableService.h b/src/MicroOcpp/Model/Variables/VariableService.h index 2641f3bb..4fc68c9d 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.h +++ b/src/MicroOcpp/Model/Variables/VariableService.h @@ -37,7 +37,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp201 { +namespace v201 { template struct VariableValidator { @@ -104,6 +104,6 @@ class VariableService : public MemoryManaged { }; } //namespace MicroOcpp -} //namespace Ocpp201 +} //namespace v201 #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/Authorize.cpp b/src/MicroOcpp/Operations/Authorize.cpp index 65d7ab81..f71a601d 100644 --- a/src/MicroOcpp/Operations/Authorize.cpp +++ b/src/MicroOcpp/Operations/Authorize.cpp @@ -12,7 +12,7 @@ using namespace MicroOcpp; #if MO_ENABLE_V16 -Ocpp16::Authorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged("v16.Operation.", "Authorize"), model(model) { +v16::Authorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged("v16.Operation.", "Authorize"), model(model) { if (idTagIn && strnlen(idTagIn, MO_IDTAG_LEN_MAX + 2) <= MO_IDTAG_LEN_MAX) { snprintf(idTag, MO_IDTAG_LEN_MAX + 1, "%s", idTagIn); } else { @@ -20,18 +20,18 @@ Ocpp16::Authorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged( } } -const char* Ocpp16::Authorize::getOperationType(){ +const char* v16::Authorize::getOperationType(){ return "Authorize"; } -std::unique_ptr Ocpp16::Authorize::createReq() { +std::unique_ptr v16::Authorize::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + (MO_IDTAG_LEN_MAX + 1)); JsonObject payload = doc->to(); payload["idTag"] = idTag; return doc; } -void Ocpp16::Authorize::processConf(JsonObject payload){ +void v16::Authorize::processConf(JsonObject payload){ const char *idTagInfo = payload["idTagInfo"]["status"] | "not specified"; if (!strcmp(idTagInfo, "Accepted")) { @@ -48,7 +48,7 @@ void Ocpp16::Authorize::processConf(JsonObject payload){ } #if MO_ENABLE_MOCK_SERVER -int Ocpp16::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { +int v16::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; (void)userData; return snprintf(buf, size, "{\"idTagInfo\":{\"status\":\"Accepted\"}}"); @@ -59,15 +59,15 @@ int Ocpp16::Authorize::writeMockConf(const char *operationType, char *buf, size_ #if MO_ENABLE_V201 -Ocpp201::Authorize::Authorize(Model& model, const IdToken& idToken) : MemoryManaged("v201.Operation.Authorize"), model(model) { +v201::Authorize::Authorize(Model& model, const IdToken& idToken) : MemoryManaged("v201.Operation.Authorize"), model(model) { this->idToken = idToken; } -const char* Ocpp201::Authorize::getOperationType(){ +const char* v201::Authorize::getOperationType(){ return "Authorize"; } -std::unique_ptr Ocpp201::Authorize::createReq() { +std::unique_ptr v201::Authorize::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2)); @@ -77,7 +77,7 @@ std::unique_ptr Ocpp201::Authorize::createReq() { return doc; } -void Ocpp201::Authorize::processConf(JsonObject payload){ +void v201::Authorize::processConf(JsonObject payload){ const char *idTagInfo = payload["idTokenInfo"]["status"] | "_Undefined"; if (!strcmp(idTagInfo, "Accepted")) { @@ -92,7 +92,7 @@ void Ocpp201::Authorize::processConf(JsonObject payload){ } #if MO_ENABLE_MOCK_SERVER -int Ocpp201::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { +int v201::Authorize::writeMockConf(const char *operationType, char *buf, size_t size, void *userStatus, void *userData) { (void)userStatus; (void)userData; return snprintf(buf, size, "{\"idTokenInfo\":{\"status\":\"Accepted\"}}"); diff --git a/src/MicroOcpp/Operations/Authorize.h b/src/MicroOcpp/Operations/Authorize.h index 5ae78a3c..86849140 100644 --- a/src/MicroOcpp/Operations/Authorize.h +++ b/src/MicroOcpp/Operations/Authorize.h @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class Model; @@ -35,7 +35,7 @@ class Authorize : public Operation, public MemoryManaged { }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 @@ -45,7 +45,7 @@ class Authorize : public Operation, public MemoryManaged { #include namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class Model; @@ -68,7 +68,7 @@ class Authorize : public Operation, public MemoryManaged { }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/CancelReservation.cpp b/src/MicroOcpp/Operations/CancelReservation.cpp index 6f331e5d..d3e17432 100644 --- a/src/MicroOcpp/Operations/CancelReservation.cpp +++ b/src/MicroOcpp/Operations/CancelReservation.cpp @@ -11,7 +11,7 @@ #include using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; CancelReservation::CancelReservation(ReservationService& reservationService) : MemoryManaged("v16.Operation.", "CancelReservation"), reservationService(reservationService) { diff --git a/src/MicroOcpp/Operations/CancelReservation.h b/src/MicroOcpp/Operations/CancelReservation.h index e2386f90..4610d8a9 100644 --- a/src/MicroOcpp/Operations/CancelReservation.h +++ b/src/MicroOcpp/Operations/CancelReservation.h @@ -12,7 +12,7 @@ #include namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class ReservationService; @@ -33,7 +33,7 @@ class CancelReservation : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION diff --git a/src/MicroOcpp/Operations/ChangeAvailability.cpp b/src/MicroOcpp/Operations/ChangeAvailability.cpp index 4bba4ac2..539192d2 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.cpp +++ b/src/MicroOcpp/Operations/ChangeAvailability.cpp @@ -11,15 +11,15 @@ using namespace MicroOcpp; #if MO_ENABLE_V16 -Ocpp16::ChangeAvailability::ChangeAvailability(Model& model) : MemoryManaged("v16.Operation.", "ChangeAvailability"), model(model) { +v16::ChangeAvailability::ChangeAvailability(Model& model) : MemoryManaged("v16.Operation.", "ChangeAvailability"), model(model) { } -const char* Ocpp16::ChangeAvailability::getOperationType(){ +const char* v16::ChangeAvailability::getOperationType(){ return "ChangeAvailability"; } -void Ocpp16::ChangeAvailability::processReq(JsonObject payload) { +void v16::ChangeAvailability::processReq(JsonObject payload) { int connectorIdRaw = payload["connectorId"] | -1; if (connectorIdRaw < 0) { errorCode = "FormationViolation"; @@ -69,7 +69,7 @@ void Ocpp16::ChangeAvailability::processReq(JsonObject payload) { } } -std::unique_ptr Ocpp16::ChangeAvailability::createConf(){ +std::unique_ptr v16::ChangeAvailability::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); if (!accepted) { @@ -87,15 +87,15 @@ std::unique_ptr Ocpp16::ChangeAvailability::createConf(){ #if MO_ENABLE_V201 -Ocpp201::ChangeAvailability::ChangeAvailability(AvailabilityService& availabilityService) : MemoryManaged("v201.Operation.", "ChangeAvailability"), availabilityService(availabilityService) { +v201::ChangeAvailability::ChangeAvailability(AvailabilityService& availabilityService) : MemoryManaged("v201.Operation.", "ChangeAvailability"), availabilityService(availabilityService) { } -const char* Ocpp201::ChangeAvailability::getOperationType(){ +const char* v201::ChangeAvailability::getOperationType(){ return "ChangeAvailability"; } -void Ocpp201::ChangeAvailability::processReq(JsonObject payload) { +void v201::ChangeAvailability::processReq(JsonObject payload) { unsigned int evseId = 0; @@ -135,7 +135,7 @@ void Ocpp201::ChangeAvailability::processReq(JsonObject payload) { status = availabilityEvse->changeAvailability(operative); } -std::unique_ptr Ocpp201::ChangeAvailability::createConf(){ +std::unique_ptr v201::ChangeAvailability::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); diff --git a/src/MicroOcpp/Operations/ChangeAvailability.h b/src/MicroOcpp/Operations/ChangeAvailability.h index ec1a54e1..28c3e4a8 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.h +++ b/src/MicroOcpp/Operations/ChangeAvailability.h @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class Model; @@ -34,7 +34,7 @@ class ChangeAvailability : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 @@ -45,7 +45,7 @@ class ChangeAvailability : public Operation, public MemoryManaged { #include namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class AvailabilityService; @@ -67,7 +67,7 @@ class ChangeAvailability : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/ChangeConfiguration.cpp b/src/MicroOcpp/Operations/ChangeConfiguration.cpp index bc913c2d..d99f865c 100644 --- a/src/MicroOcpp/Operations/ChangeConfiguration.cpp +++ b/src/MicroOcpp/Operations/ChangeConfiguration.cpp @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; ChangeConfiguration::ChangeConfiguration(ConfigurationService& configService) : MemoryManaged("v16.Operation.", "ChangeConfiguration"), configService(configService) { diff --git a/src/MicroOcpp/Operations/ChangeConfiguration.h b/src/MicroOcpp/Operations/ChangeConfiguration.h index c785f12c..6d14f90f 100644 --- a/src/MicroOcpp/Operations/ChangeConfiguration.h +++ b/src/MicroOcpp/Operations/ChangeConfiguration.h @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class ConfigurationService; @@ -35,7 +35,7 @@ class ChangeConfiguration : public Operation, public MemoryManaged { }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/ClearCache.cpp b/src/MicroOcpp/Operations/ClearCache.cpp index eb200f22..442bd131 100644 --- a/src/MicroOcpp/Operations/ClearCache.cpp +++ b/src/MicroOcpp/Operations/ClearCache.cpp @@ -9,7 +9,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; ClearCache::ClearCache(MO_FilesystemAdapter *filesystem) : MemoryManaged("v16.Operation.", "ClearCache"), filesystem(filesystem) { diff --git a/src/MicroOcpp/Operations/ClearCache.h b/src/MicroOcpp/Operations/ClearCache.h index 8ac0acba..2a03acac 100644 --- a/src/MicroOcpp/Operations/ClearCache.h +++ b/src/MicroOcpp/Operations/ClearCache.h @@ -13,7 +13,7 @@ namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class ClearCache : public Operation, public MemoryManaged { private: @@ -29,7 +29,7 @@ class ClearCache : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/DataTransfer.cpp b/src/MicroOcpp/Operations/DataTransfer.cpp index 08cf365f..f4b5c3ad 100644 --- a/src/MicroOcpp/Operations/DataTransfer.cpp +++ b/src/MicroOcpp/Operations/DataTransfer.cpp @@ -8,7 +8,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; DataTransfer::DataTransfer(const char *msg) : MemoryManaged("v16.Operation.", "DataTransfer") { if (msg) { diff --git a/src/MicroOcpp/Operations/DataTransfer.h b/src/MicroOcpp/Operations/DataTransfer.h index ff644de1..8c809a58 100644 --- a/src/MicroOcpp/Operations/DataTransfer.h +++ b/src/MicroOcpp/Operations/DataTransfer.h @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class DataTransfer : public Operation, public MemoryManaged { private: @@ -32,7 +32,7 @@ class DataTransfer : public Operation, public MemoryManaged { }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp index 85898387..e0f42194 100644 --- a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp +++ b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp @@ -10,7 +10,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; DiagnosticsStatusNotification::DiagnosticsStatusNotification(DiagnosticsStatus status) : MemoryManaged("v16.Operation.", "DiagnosticsStatusNotification"), status(status) { diff --git a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h index 1d1e021d..21ccb6be 100644 --- a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h +++ b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.h @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class DiagnosticsStatusNotification : public Operation, public MemoryManaged { private: @@ -27,7 +27,7 @@ class DiagnosticsStatusNotification : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS #endif diff --git a/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp b/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp index 5b49ffbd..dff71316 100644 --- a/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp +++ b/src/MicroOcpp/Operations/FirmwareStatusNotification.cpp @@ -10,7 +10,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; FirmwareStatusNotification::FirmwareStatusNotification(FirmwareStatus status) : MemoryManaged("v16.Operation.", "FirmwareStatusNotification"), status{status} { diff --git a/src/MicroOcpp/Operations/FirmwareStatusNotification.h b/src/MicroOcpp/Operations/FirmwareStatusNotification.h index c49ecfb7..eefac7fb 100644 --- a/src/MicroOcpp/Operations/FirmwareStatusNotification.h +++ b/src/MicroOcpp/Operations/FirmwareStatusNotification.h @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class FirmwareStatusNotification : public Operation, public MemoryManaged { private: @@ -28,7 +28,7 @@ class FirmwareStatusNotification : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/src/MicroOcpp/Operations/GetBaseReport.cpp b/src/MicroOcpp/Operations/GetBaseReport.cpp index 8fe0b1cb..cb13406f 100644 --- a/src/MicroOcpp/Operations/GetBaseReport.cpp +++ b/src/MicroOcpp/Operations/GetBaseReport.cpp @@ -9,7 +9,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; GetBaseReport::GetBaseReport(VariableService& variableService) : MemoryManaged("v201.Operation.", "GetBaseReport"), variableService(variableService) { diff --git a/src/MicroOcpp/Operations/GetBaseReport.h b/src/MicroOcpp/Operations/GetBaseReport.h index 61e5a6b1..a284901c 100644 --- a/src/MicroOcpp/Operations/GetBaseReport.h +++ b/src/MicroOcpp/Operations/GetBaseReport.h @@ -13,7 +13,7 @@ #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class VariableService; @@ -37,7 +37,7 @@ class GetBaseReport : public Operation, public MemoryManaged { }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/GetConfiguration.cpp b/src/MicroOcpp/Operations/GetConfiguration.cpp index 7e231c5d..ad11fcaf 100644 --- a/src/MicroOcpp/Operations/GetConfiguration.cpp +++ b/src/MicroOcpp/Operations/GetConfiguration.cpp @@ -9,7 +9,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; GetConfiguration::GetConfiguration(ConfigurationService& configService) : MemoryManaged("v16.Operation.", "GetConfiguration"), diff --git a/src/MicroOcpp/Operations/GetConfiguration.h b/src/MicroOcpp/Operations/GetConfiguration.h index a18e22da..c9b40917 100644 --- a/src/MicroOcpp/Operations/GetConfiguration.h +++ b/src/MicroOcpp/Operations/GetConfiguration.h @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class Configuration; class ConfigurationService; @@ -41,7 +41,7 @@ class GetConfiguration : public Operation, public MemoryManaged { }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/GetDiagnostics.cpp b/src/MicroOcpp/Operations/GetDiagnostics.cpp index 4adb6407..a3284cc6 100644 --- a/src/MicroOcpp/Operations/GetDiagnostics.cpp +++ b/src/MicroOcpp/Operations/GetDiagnostics.cpp @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; GetDiagnostics::GetDiagnostics(Context& context, DiagnosticsService& diagService) : MemoryManaged("v16.Operation.", "GetDiagnostics"), context(context), diagService(diagService) { diff --git a/src/MicroOcpp/Operations/GetDiagnostics.h b/src/MicroOcpp/Operations/GetDiagnostics.h index 16ed8546..86b96768 100644 --- a/src/MicroOcpp/Operations/GetDiagnostics.h +++ b/src/MicroOcpp/Operations/GetDiagnostics.h @@ -16,7 +16,7 @@ namespace MicroOcpp { class Context; class DiagnosticsService; -namespace Ocpp16 { +namespace v16 { class GetDiagnostics : public Operation, public MemoryManaged { private: @@ -37,7 +37,7 @@ class GetDiagnostics : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_DIAGNOSTICS #endif diff --git a/src/MicroOcpp/Operations/GetLocalListVersion.cpp b/src/MicroOcpp/Operations/GetLocalListVersion.cpp index 7de43dd6..1eb72d60 100644 --- a/src/MicroOcpp/Operations/GetLocalListVersion.cpp +++ b/src/MicroOcpp/Operations/GetLocalListVersion.cpp @@ -10,7 +10,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; GetLocalListVersion::GetLocalListVersion(Model& model) : MemoryManaged("v16.Operation.", "GetLocalListVersion"), model(model) { diff --git a/src/MicroOcpp/Operations/GetLocalListVersion.h b/src/MicroOcpp/Operations/GetLocalListVersion.h index c96be3d2..6fdd5dc4 100644 --- a/src/MicroOcpp/Operations/GetLocalListVersion.h +++ b/src/MicroOcpp/Operations/GetLocalListVersion.h @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class Model; @@ -28,7 +28,7 @@ class GetLocalListVersion : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Operations/GetVariables.cpp b/src/MicroOcpp/Operations/GetVariables.cpp index 627c19a0..a88de434 100644 --- a/src/MicroOcpp/Operations/GetVariables.cpp +++ b/src/MicroOcpp/Operations/GetVariables.cpp @@ -11,7 +11,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; GetVariableData::GetVariableData(const char *memory_tag) : componentName{makeString(memory_tag)}, variableName{makeString(memory_tag)} { diff --git a/src/MicroOcpp/Operations/GetVariables.h b/src/MicroOcpp/Operations/GetVariables.h index 2b7cc010..57e3960c 100644 --- a/src/MicroOcpp/Operations/GetVariables.h +++ b/src/MicroOcpp/Operations/GetVariables.h @@ -13,7 +13,7 @@ #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class VariableService; @@ -52,7 +52,7 @@ class GetVariables : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/Heartbeat.cpp b/src/MicroOcpp/Operations/Heartbeat.cpp index 878cf5d3..b753cb4a 100644 --- a/src/MicroOcpp/Operations/Heartbeat.cpp +++ b/src/MicroOcpp/Operations/Heartbeat.cpp @@ -10,7 +10,7 @@ #if MO_ENABLE_V16 || MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; Heartbeat::Heartbeat(Context& context) : MemoryManaged("v16.Operation.", "Heartbeat"), context(context) { diff --git a/src/MicroOcpp/Operations/MeterValues.cpp b/src/MicroOcpp/Operations/MeterValues.cpp index 58cb8a98..197ba2b8 100644 --- a/src/MicroOcpp/Operations/MeterValues.cpp +++ b/src/MicroOcpp/Operations/MeterValues.cpp @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; //can only be used for echo server debugging MeterValues::MeterValues(Context& context) : MemoryManaged("v16.Operation.", "MeterValues"), context(context) { diff --git a/src/MicroOcpp/Operations/MeterValues.h b/src/MicroOcpp/Operations/MeterValues.h index 321632ee..216bb74b 100644 --- a/src/MicroOcpp/Operations/MeterValues.h +++ b/src/MicroOcpp/Operations/MeterValues.h @@ -17,7 +17,7 @@ namespace MicroOcpp { class Context; struct MeterValue; -namespace Ocpp16 { +namespace v16 { class MeterValues : public Operation, public MemoryManaged { private: @@ -46,7 +46,7 @@ class MeterValues : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/NotifyReport.cpp b/src/MicroOcpp/Operations/NotifyReport.cpp index c21795fb..bcedbd0e 100644 --- a/src/MicroOcpp/Operations/NotifyReport.cpp +++ b/src/MicroOcpp/Operations/NotifyReport.cpp @@ -12,7 +12,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; NotifyReport::NotifyReport(Context& context, int requestId, const Timestamp& generatedAt, bool tbc, unsigned int seqNo, const Vector& reportData) : MemoryManaged("v201.Operation.", "NotifyReport"), context(context), requestId(requestId), generatedAt(generatedAt), tbc(tbc), seqNo(seqNo), reportData(reportData) { diff --git a/src/MicroOcpp/Operations/NotifyReport.h b/src/MicroOcpp/Operations/NotifyReport.h index a2ab1ffa..cd19fedf 100644 --- a/src/MicroOcpp/Operations/NotifyReport.h +++ b/src/MicroOcpp/Operations/NotifyReport.h @@ -16,7 +16,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp201 { +namespace v201 { class Variable; @@ -40,7 +40,7 @@ class NotifyReport : public Operation, public MemoryManaged { void processConf(JsonObject payload) override; }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp index 16f04c2c..3ba2e75e 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp @@ -17,7 +17,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; RemoteStartTransaction::RemoteStartTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "RemoteStartTransaction"), context(context), rcService(rcService) { @@ -82,7 +82,7 @@ void RemoteStartTransaction::processReq(JsonObject payload) { } status = rcService.remoteStartTransaction(connectorId, idTag, std::move(chargingProfile)); - if (status == Ocpp16::RemoteStartStopStatus::ERR_INTERNAL) { + if (status == v16::RemoteStartStopStatus::ERR_INTERNAL) { errorCode = "InternalError"; return; } @@ -91,7 +91,7 @@ void RemoteStartTransaction::processReq(JsonObject payload) { std::unique_ptr RemoteStartTransaction::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - if (status == Ocpp16::RemoteStartStopStatus::Accepted) { + if (status == v16::RemoteStartStopStatus::Accepted) { payload["status"] = "Accepted"; } else { payload["status"] = "Rejected"; diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.h b/src/MicroOcpp/Operations/RemoteStartTransaction.h index 15989043..8fadb12a 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.h +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.h @@ -17,7 +17,7 @@ class Context; class RemoteControlService; class ChargingProfile; -namespace Ocpp16 { +namespace v16 { class RemoteStartTransaction : public Operation, public MemoryManaged { private: @@ -41,7 +41,7 @@ class RemoteStartTransaction : public Operation, public MemoryManaged { const char *getErrorDescription() override {return errorDescription;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/RemoteStopTransaction.cpp b/src/MicroOcpp/Operations/RemoteStopTransaction.cpp index fa4a3f91..2ad4ecc9 100644 --- a/src/MicroOcpp/Operations/RemoteStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStopTransaction.cpp @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; RemoteStopTransaction::RemoteStopTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "RemoteStopTransaction"), context(context), rcService(rcService) { @@ -29,7 +29,7 @@ void RemoteStopTransaction::processReq(JsonObject payload) { int transactionId = payload["transactionId"]; status = rcService.remoteStopTransaction(transactionId); - if (status == Ocpp16::RemoteStartStopStatus::ERR_INTERNAL) { + if (status == v16::RemoteStartStopStatus::ERR_INTERNAL) { errorCode = "InternalError"; return; } @@ -38,7 +38,7 @@ void RemoteStopTransaction::processReq(JsonObject payload) { std::unique_ptr RemoteStopTransaction::createConf() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); - if (status == Ocpp16::RemoteStartStopStatus::Accepted) { + if (status == v16::RemoteStartStopStatus::Accepted) { payload["status"] = "Accepted"; } else { payload["status"] = "Rejected"; diff --git a/src/MicroOcpp/Operations/RemoteStopTransaction.h b/src/MicroOcpp/Operations/RemoteStopTransaction.h index bc8bc7ac..55f20f1d 100644 --- a/src/MicroOcpp/Operations/RemoteStopTransaction.h +++ b/src/MicroOcpp/Operations/RemoteStopTransaction.h @@ -16,7 +16,7 @@ namespace MicroOcpp { class Context; class RemoteControlService; -namespace Ocpp16 { +namespace v16 { class RemoteStopTransaction : public Operation, public MemoryManaged { private: @@ -38,7 +38,7 @@ class RemoteStopTransaction : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.cpp b/src/MicroOcpp/Operations/RequestStartTransaction.cpp index a517cf28..dc9b8a5f 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStartTransaction.cpp @@ -13,7 +13,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; RequestStartTransaction::RequestStartTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStartTransaction"), context(context), rcService(rcService) { diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.h b/src/MicroOcpp/Operations/RequestStartTransaction.h index 9a21749b..3e89622f 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.h +++ b/src/MicroOcpp/Operations/RequestStartTransaction.h @@ -18,7 +18,7 @@ namespace MicroOcpp { class Context; class RemoteControlService; -namespace Ocpp201 { +namespace v201 { class RequestStartTransaction : public Operation, public MemoryManaged { private: @@ -26,7 +26,7 @@ class RequestStartTransaction : public Operation, public MemoryManaged { RemoteControlService& rcService; RequestStartStopStatus status; - std::shared_ptr transaction; + std::shared_ptr transaction; char transactionId [MO_TXID_SIZE] = {'\0'}; const char *errorCode = nullptr; @@ -43,7 +43,7 @@ class RequestStartTransaction : public Operation, public MemoryManaged { }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.cpp b/src/MicroOcpp/Operations/RequestStopTransaction.cpp index 21569046..e4e07b99 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStopTransaction.cpp @@ -9,7 +9,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; RequestStopTransaction::RequestStopTransaction(RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStopTransaction"), rcService(rcService) { diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.h b/src/MicroOcpp/Operations/RequestStopTransaction.h index 2bcf70a5..815b752f 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.h +++ b/src/MicroOcpp/Operations/RequestStopTransaction.h @@ -15,7 +15,7 @@ namespace MicroOcpp { class RemoteControlService; -namespace Ocpp201 { +namespace v201 { class RequestStopTransaction : public Operation, public MemoryManaged { private: @@ -37,7 +37,7 @@ class RequestStopTransaction : public Operation, public MemoryManaged { }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/ReserveNow.cpp b/src/MicroOcpp/Operations/ReserveNow.cpp index c0808839..d8dd4b71 100644 --- a/src/MicroOcpp/Operations/ReserveNow.cpp +++ b/src/MicroOcpp/Operations/ReserveNow.cpp @@ -15,7 +15,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_RESERVATION using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; ReserveNow::ReserveNow(Context& context, ReservationService& rService) : MemoryManaged("v16.Operation.", "ReserveNow"), context(context), rService(rService) { diff --git a/src/MicroOcpp/Operations/ReserveNow.h b/src/MicroOcpp/Operations/ReserveNow.h index 3b430ae6..6b810eba 100644 --- a/src/MicroOcpp/Operations/ReserveNow.h +++ b/src/MicroOcpp/Operations/ReserveNow.h @@ -14,7 +14,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class ReservationService; @@ -38,7 +38,7 @@ class ReserveNow : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_RESERVATION #endif diff --git a/src/MicroOcpp/Operations/Reset.cpp b/src/MicroOcpp/Operations/Reset.cpp index cdaabe6a..5063448d 100644 --- a/src/MicroOcpp/Operations/Reset.cpp +++ b/src/MicroOcpp/Operations/Reset.cpp @@ -11,15 +11,15 @@ using namespace MicroOcpp; -Ocpp16::Reset::Reset(ResetService& resetService) : MemoryManaged("v16.Operation.", "Reset"), resetService(resetService) { +v16::Reset::Reset(ResetService& resetService) : MemoryManaged("v16.Operation.", "Reset"), resetService(resetService) { } -const char* Ocpp16::Reset::getOperationType(){ +const char* v16::Reset::getOperationType(){ return "Reset"; } -void Ocpp16::Reset::processReq(JsonObject payload) { +void v16::Reset::processReq(JsonObject payload) { /* * Process the application data here. Note: you have to implement the device reset procedure in your client code. You have to set * a onSendConfListener in which you initiate a reset (e.g. calling ESP.reset() ) @@ -37,7 +37,7 @@ void Ocpp16::Reset::processReq(JsonObject payload) { } } -std::unique_ptr Ocpp16::Reset::createConf() { +std::unique_ptr v16::Reset::createConf() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); payload["status"] = resetAccepted ? "Accepted" : "Rejected"; @@ -50,15 +50,15 @@ std::unique_ptr Ocpp16::Reset::createConf() { using namespace MicroOcpp; -Ocpp201::Reset::Reset(ResetService& resetService) : MemoryManaged("v201.Operation.", "Reset"), resetService(resetService) { +v201::Reset::Reset(ResetService& resetService) : MemoryManaged("v201.Operation.", "Reset"), resetService(resetService) { } -const char* Ocpp201::Reset::getOperationType(){ +const char* v201::Reset::getOperationType(){ return "Reset"; } -void Ocpp201::Reset::processReq(JsonObject payload) { +void v201::Reset::processReq(JsonObject payload) { MO_ResetType type; const char *typeCstr = payload["type"] | "_Undefined"; @@ -84,7 +84,7 @@ void Ocpp201::Reset::processReq(JsonObject payload) { status = resetService.initiateReset(type, evseId); } -std::unique_ptr Ocpp201::Reset::createConf() { +std::unique_ptr v201::Reset::createConf() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1)); JsonObject payload = doc->to(); diff --git a/src/MicroOcpp/Operations/Reset.h b/src/MicroOcpp/Operations/Reset.h index 0218d47b..19c6b4db 100644 --- a/src/MicroOcpp/Operations/Reset.h +++ b/src/MicroOcpp/Operations/Reset.h @@ -13,7 +13,7 @@ #if MO_ENABLE_V16 namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class ResetService; @@ -31,14 +31,14 @@ class Reset : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class ResetService; @@ -59,7 +59,7 @@ class Reset : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/SendLocalList.cpp b/src/MicroOcpp/Operations/SendLocalList.cpp index 8dcf5eaa..7fed1b22 100644 --- a/src/MicroOcpp/Operations/SendLocalList.cpp +++ b/src/MicroOcpp/Operations/SendLocalList.cpp @@ -9,7 +9,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; SendLocalList::SendLocalList(AuthorizationService& authService) : MemoryManaged("v16.Operation.", "SendLocalList"), authService(authService) { diff --git a/src/MicroOcpp/Operations/SendLocalList.h b/src/MicroOcpp/Operations/SendLocalList.h index 928091da..02881e33 100644 --- a/src/MicroOcpp/Operations/SendLocalList.h +++ b/src/MicroOcpp/Operations/SendLocalList.h @@ -11,7 +11,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH namespace MicroOcpp { -namespace Ocpp16 { +namespace v16 { class AuthorizationService; @@ -35,7 +35,7 @@ class SendLocalList : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_LOCAL_AUTH #endif diff --git a/src/MicroOcpp/Operations/SetVariables.cpp b/src/MicroOcpp/Operations/SetVariables.cpp index fa1477dd..37ce3f6f 100644 --- a/src/MicroOcpp/Operations/SetVariables.cpp +++ b/src/MicroOcpp/Operations/SetVariables.cpp @@ -9,7 +9,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; SetVariableData::SetVariableData(const char *memory_tag) : componentName{makeString(memory_tag)}, variableName{makeString(memory_tag)} { diff --git a/src/MicroOcpp/Operations/SetVariables.h b/src/MicroOcpp/Operations/SetVariables.h index 16e6f4e0..55cf9b2c 100644 --- a/src/MicroOcpp/Operations/SetVariables.h +++ b/src/MicroOcpp/Operations/SetVariables.h @@ -13,7 +13,7 @@ #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class VariableService; @@ -53,7 +53,7 @@ class SetVariables : public Operation, public MemoryManaged { }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Operations/StartTransaction.cpp b/src/MicroOcpp/Operations/StartTransaction.cpp index d3a5513b..897b3a2e 100644 --- a/src/MicroOcpp/Operations/StartTransaction.cpp +++ b/src/MicroOcpp/Operations/StartTransaction.cpp @@ -15,7 +15,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; StartTransaction::StartTransaction(Context& context, Transaction *transaction) : MemoryManaged("v16.Operation.", "StartTransaction"), context(context), transaction(transaction) { diff --git a/src/MicroOcpp/Operations/StartTransaction.h b/src/MicroOcpp/Operations/StartTransaction.h index fa29f9dc..9a15ae78 100644 --- a/src/MicroOcpp/Operations/StartTransaction.h +++ b/src/MicroOcpp/Operations/StartTransaction.h @@ -15,7 +15,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class Transaction; @@ -40,7 +40,7 @@ class StartTransaction : public Operation, public MemoryManaged { #endif }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/StatusNotification.cpp b/src/MicroOcpp/Operations/StatusNotification.cpp index 3d520811..cc368b2b 100644 --- a/src/MicroOcpp/Operations/StatusNotification.cpp +++ b/src/MicroOcpp/Operations/StatusNotification.cpp @@ -14,7 +14,7 @@ using namespace MicroOcpp; -Ocpp16::StatusNotification::StatusNotification(Context& context, int connectorId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp, MO_ErrorData errorData) +v16::StatusNotification::StatusNotification(Context& context, int connectorId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp, MO_ErrorData errorData) : MemoryManaged("v16.Operation.", "StatusNotification"), context(context), connectorId(connectorId), currentStatus(currentStatus), timestamp(timestamp), errorData(errorData) { if (currentStatus != MO_ChargePointStatus_UNDEFINED) { @@ -22,11 +22,11 @@ Ocpp16::StatusNotification::StatusNotification(Context& context, int connectorId } } -const char* Ocpp16::StatusNotification::getOperationType(){ +const char* v16::StatusNotification::getOperationType(){ return "StatusNotification"; } -std::unique_ptr Ocpp16::StatusNotification::createReq() { +std::unique_ptr v16::StatusNotification::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(7) + MO_JSONDATE_SIZE); JsonObject payload = doc->to(); @@ -63,7 +63,7 @@ std::unique_ptr Ocpp16::StatusNotification::createReq() { return doc; } -void Ocpp16::StatusNotification::processConf(JsonObject payload) { +void v16::StatusNotification::processConf(JsonObject payload) { /* * Empty payload */ @@ -72,14 +72,14 @@ void Ocpp16::StatusNotification::processConf(JsonObject payload) { /* * For debugging only */ -void Ocpp16::StatusNotification::processReq(JsonObject payload) { +void v16::StatusNotification::processReq(JsonObject payload) { } /* * For debugging only */ -std::unique_ptr Ocpp16::StatusNotification::createConf(){ +std::unique_ptr v16::StatusNotification::createConf(){ return createEmptyDocument(); } @@ -89,16 +89,16 @@ std::unique_ptr Ocpp16::StatusNotification::createConf(){ using namespace MicroOcpp; -Ocpp201::StatusNotification::StatusNotification(Context& context, EvseId evseId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp) +v201::StatusNotification::StatusNotification(Context& context, EvseId evseId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp) : MemoryManaged("v201.Operation.", "StatusNotification"), context(context), evseId(evseId), timestamp(timestamp), currentStatus(currentStatus) { } -const char* Ocpp201::StatusNotification::getOperationType(){ +const char* v201::StatusNotification::getOperationType(){ return "StatusNotification"; } -std::unique_ptr Ocpp201::StatusNotification::createReq() { +std::unique_ptr v201::StatusNotification::createReq() { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(4) + MO_JSONDATE_SIZE); JsonObject payload = doc->to(); @@ -116,7 +116,7 @@ std::unique_ptr Ocpp201::StatusNotification::createReq() { } -void Ocpp201::StatusNotification::processConf(JsonObject payload) { +void v201::StatusNotification::processConf(JsonObject payload) { /* * Empty payload */ @@ -125,14 +125,14 @@ void Ocpp201::StatusNotification::processConf(JsonObject payload) { /* * For debugging only */ -void Ocpp201::StatusNotification::processReq(JsonObject payload) { +void v201::StatusNotification::processReq(JsonObject payload) { } /* * For debugging only */ -std::unique_ptr Ocpp201::StatusNotification::createConf(){ +std::unique_ptr v201::StatusNotification::createConf(){ return createEmptyDocument(); } diff --git a/src/MicroOcpp/Operations/StatusNotification.h b/src/MicroOcpp/Operations/StatusNotification.h index 3af10e7e..f588c9ae 100644 --- a/src/MicroOcpp/Operations/StatusNotification.h +++ b/src/MicroOcpp/Operations/StatusNotification.h @@ -17,7 +17,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class StatusNotification : public Operation, public MemoryManaged { private: @@ -40,14 +40,14 @@ class StatusNotification : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 namespace MicroOcpp { -namespace Ocpp201 { +namespace v201 { class StatusNotification : public Operation, public MemoryManaged { private: @@ -69,7 +69,7 @@ class StatusNotification : public Operation, public MemoryManaged { std::unique_ptr createConf() override; }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/StopTransaction.cpp b/src/MicroOcpp/Operations/StopTransaction.cpp index 4a5d4753..c6c2d94b 100644 --- a/src/MicroOcpp/Operations/StopTransaction.cpp +++ b/src/MicroOcpp/Operations/StopTransaction.cpp @@ -16,7 +16,7 @@ #if MO_ENABLE_V16 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; StopTransaction::StopTransaction(Context& context, Transaction *transaction) : MemoryManaged("v16.Operation.", "StopTransaction"), context(context), transaction(transaction) { diff --git a/src/MicroOcpp/Operations/StopTransaction.h b/src/MicroOcpp/Operations/StopTransaction.h index 6844d778..8b1ce41b 100644 --- a/src/MicroOcpp/Operations/StopTransaction.h +++ b/src/MicroOcpp/Operations/StopTransaction.h @@ -16,7 +16,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class Transaction; @@ -39,7 +39,7 @@ class StopTransaction : public Operation, public MemoryManaged { #endif }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 #endif diff --git a/src/MicroOcpp/Operations/TransactionEvent.cpp b/src/MicroOcpp/Operations/TransactionEvent.cpp index 97ef2022..55e5d06d 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.cpp +++ b/src/MicroOcpp/Operations/TransactionEvent.cpp @@ -12,7 +12,7 @@ #if MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp201; +using namespace MicroOcpp::v201; TransactionEvent::TransactionEvent(Context& context, TransactionEventData *txEvent) : MemoryManaged("v201.Operation.", "TransactionEvent"), context(context), txEvent(txEvent) { diff --git a/src/MicroOcpp/Operations/TransactionEvent.h b/src/MicroOcpp/Operations/TransactionEvent.h index 7e513126..edea7fb2 100644 --- a/src/MicroOcpp/Operations/TransactionEvent.h +++ b/src/MicroOcpp/Operations/TransactionEvent.h @@ -14,7 +14,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp201 { +namespace v201 { class TransactionEventData; @@ -42,7 +42,7 @@ class TransactionEvent : public Operation, public MemoryManaged { #endif }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 #endif diff --git a/src/MicroOcpp/Operations/UnlockConnector.cpp b/src/MicroOcpp/Operations/UnlockConnector.cpp index fc17a219..e5769aa5 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.cpp +++ b/src/MicroOcpp/Operations/UnlockConnector.cpp @@ -12,15 +12,15 @@ using namespace MicroOcpp; -Ocpp16::UnlockConnector::UnlockConnector(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "UnlockConnector"), context(context), rcService(rcService) { +v16::UnlockConnector::UnlockConnector(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "UnlockConnector"), context(context), rcService(rcService) { } -const char* Ocpp16::UnlockConnector::getOperationType(){ +const char* v16::UnlockConnector::getOperationType(){ return "UnlockConnector"; } -void Ocpp16::UnlockConnector::processReq(JsonObject payload) { +void v16::UnlockConnector::processReq(JsonObject payload) { #if MO_ENABLE_CONNECTOR_LOCK { @@ -39,7 +39,7 @@ void Ocpp16::UnlockConnector::processReq(JsonObject payload) { #endif //MO_ENABLE_CONNECTOR_LOCK } -std::unique_ptr Ocpp16::UnlockConnector::createConf() { +std::unique_ptr v16::UnlockConnector::createConf() { #if MO_ENABLE_CONNECTOR_LOCK { @@ -87,15 +87,15 @@ std::unique_ptr Ocpp16::UnlockConnector::createConf() { using namespace MicroOcpp; -Ocpp201::UnlockConnector::UnlockConnector(Context& context, RemoteControlService& rcService) : MemoryManaged("v201.Operation.UnlockConnector"), context(context), rcService(rcService) { +v201::UnlockConnector::UnlockConnector(Context& context, RemoteControlService& rcService) : MemoryManaged("v201.Operation.UnlockConnector"), context(context), rcService(rcService) { } -const char* Ocpp201::UnlockConnector::getOperationType(){ +const char* v201::UnlockConnector::getOperationType(){ return "UnlockConnector"; } -void Ocpp201::UnlockConnector::processReq(JsonObject payload) { +void v201::UnlockConnector::processReq(JsonObject payload) { int evseId = payload["evseId"] | -1; int connectorId = payload["connectorId"] | -1; @@ -120,7 +120,7 @@ void Ocpp201::UnlockConnector::processReq(JsonObject payload) { timerStart = context.getClock().getUptime(); } -std::unique_ptr Ocpp201::UnlockConnector::createConf() { +std::unique_ptr v201::UnlockConnector::createConf() { int32_t dtTimerStart; context.getClock().delta(context.getClock().getUptime(), timerStart, dtTimerStart); diff --git a/src/MicroOcpp/Operations/UnlockConnector.h b/src/MicroOcpp/Operations/UnlockConnector.h index c8a0a2de..e76d4114 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.h +++ b/src/MicroOcpp/Operations/UnlockConnector.h @@ -20,7 +20,7 @@ class Context; class RemoteControlService; class RemoteControlServiceEvse; -namespace Ocpp16 { +namespace v16 { class UnlockConnector : public Operation, public MemoryManaged { private: @@ -44,7 +44,7 @@ class UnlockConnector : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 @@ -58,7 +58,7 @@ class Context; class RemoteControlService; class RemoteControlServiceEvse; -namespace Ocpp201 { +namespace v201 { class UnlockConnector : public Operation, public MemoryManaged { private: @@ -82,7 +82,7 @@ class UnlockConnector : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp201 +} //namespace v201 } //namespace MicroOcpp #endif //MO_ENABLE_V201 && MO_ENABLE_CONNECTOR_LOCK #endif diff --git a/src/MicroOcpp/Operations/UpdateFirmware.cpp b/src/MicroOcpp/Operations/UpdateFirmware.cpp index 24ac44c2..7832ac23 100644 --- a/src/MicroOcpp/Operations/UpdateFirmware.cpp +++ b/src/MicroOcpp/Operations/UpdateFirmware.cpp @@ -12,7 +12,7 @@ #if MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT using namespace MicroOcpp; -using namespace MicroOcpp::Ocpp16; +using namespace MicroOcpp::v16; UpdateFirmware::UpdateFirmware(Context& context, FirmwareService& fwService) : MemoryManaged("v16.Operation.", "UpdateFirmware"), context(context), fwService(fwService) { diff --git a/src/MicroOcpp/Operations/UpdateFirmware.h b/src/MicroOcpp/Operations/UpdateFirmware.h index 120c4d5e..fffde4ff 100644 --- a/src/MicroOcpp/Operations/UpdateFirmware.h +++ b/src/MicroOcpp/Operations/UpdateFirmware.h @@ -15,7 +15,7 @@ namespace MicroOcpp { class Context; -namespace Ocpp16 { +namespace v16 { class FirmwareService; @@ -37,7 +37,7 @@ class UpdateFirmware : public Operation, public MemoryManaged { const char *getErrorCode() override {return errorCode;} }; -} //namespace Ocpp16 +} //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 && MO_ENABLE_FIRMWAREMANAGEMENT #endif diff --git a/tests/Boot.cpp b/tests/Boot.cpp index 7e0a79d0..e5f2c2dc 100644 --- a/tests/Boot.cpp +++ b/tests/Boot.cpp @@ -66,7 +66,7 @@ TEST_CASE( "Boot Behavior" ) { getOcppContext()->getMessageService().registerOperation("BootNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("BootNotification", + return new v16::CustomOperation("BootNotification", [ &checkProcessed] (JsonObject payload) { //process req checkProcessed = true; @@ -115,7 +115,7 @@ TEST_CASE( "Boot Behavior" ) { getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { - return new Ocpp16::CustomOperation("BootNotification", + return new v16::CustomOperation("BootNotification", [] (JsonObject payload) { //ignore req }, @@ -143,7 +143,7 @@ TEST_CASE( "Boot Behavior" ) { bool checkProcessedHeartbeat = false; - auto heartbeat = makeRequest(new Ocpp16::CustomOperation( + auto heartbeat = makeRequest(new v16::CustomOperation( "Heartbeat", [] () { //create req @@ -204,7 +204,7 @@ TEST_CASE( "Boot Behavior" ) { getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { - return new Ocpp16::CustomOperation("BootNotification", + return new v16::CustomOperation("BootNotification", [] (JsonObject payload) { //ignore req }, @@ -256,7 +256,7 @@ TEST_CASE( "Boot Behavior" ) { getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { - return new Ocpp16::CustomOperation("BootNotification", + return new v16::CustomOperation("BootNotification", [] (JsonObject payload) { //ignore req }, @@ -289,7 +289,7 @@ TEST_CASE( "Boot Behavior" ) { getOcppContext()->getMessageService().registerOperation("BootNotification", [] () { - return new Ocpp16::CustomOperation("BootNotification", + return new v16::CustomOperation("BootNotification", [] (JsonObject payload) { //ignore req }, @@ -437,7 +437,7 @@ TEST_CASE( "Boot Behavior" ) { getOcppContext()->getMessageService().registerOperation("BootNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("BootNotification", + return new v16::CustomOperation("BootNotification", [ &checkProcessed] (JsonObject payload) { //process req checkProcessed = true; diff --git a/tests/Certificates.cpp b/tests/Certificates.cpp index 79d0b967..b87262ec 100644 --- a/tests/Certificates.cpp +++ b/tests/Certificates.cpp @@ -163,7 +163,7 @@ TEST_CASE( "M - Certificates" ) { SECTION("M05 InstallCertificate operation") { bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "InstallCertificate", [] () { //create req @@ -195,7 +195,7 @@ TEST_CASE( "M - Certificates" ) { REQUIRE(ret == InstallCertificateStatus_Accepted); bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "DeleteCertificate", [] () { //create req @@ -223,7 +223,7 @@ TEST_CASE( "M - Certificates" ) { REQUIRE(ret == InstallCertificateStatus_Accepted); bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "GetInstalledCertificateIds", [] () { //create req diff --git a/tests/ChargePointError.cpp b/tests/ChargePointError.cpp index 6c82b294..b6a80dbd 100644 --- a/tests/ChargePointError.cpp +++ b/tests/ChargePointError.cpp @@ -84,7 +84,7 @@ TEST_CASE( "ChargePointError" ) { getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("StatusNotification", + return new v16::CustomOperation("StatusNotification", [ &checkProcessed] (JsonObject payload) { //process req checkProcessed = true; @@ -112,7 +112,7 @@ TEST_CASE( "ChargePointError" ) { checkProcessed = false; getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("StatusNotification", + return new v16::CustomOperation("StatusNotification", [ &checkProcessed] (JsonObject payload) { //process req checkProcessed = true; @@ -210,7 +210,7 @@ TEST_CASE( "ChargePointError" ) { getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkErrorCode, &checkErrorInfo, &errorInfo, &errorCode] () { - return new Ocpp16::CustomOperation("StatusNotification", + return new v16::CustomOperation("StatusNotification", [&checkErrorCode, &checkErrorInfo, &errorInfo, &errorCode] (JsonObject payload) { //process req if (strcmp(errorInfo, "*")) { diff --git a/tests/ChargingSessions.cpp b/tests/ChargingSessions.cpp index 84d31ccc..ccf237bd 100644 --- a/tests/ChargingSessions.cpp +++ b/tests/ChargingSessions.cpp @@ -514,7 +514,7 @@ TEST_CASE( "Charging sessions" ) { int txId_confirm = txId_base; getOcppContext()->getMessageService().registerOperation("StartTransaction", [&txId_generate] () { - return new Ocpp16::CustomOperation("StartTransaction", + return new v16::CustomOperation("StartTransaction", [] (JsonObject payload) {}, //ignore req [&txId_generate] () { //create conf @@ -529,7 +529,7 @@ TEST_CASE( "Charging sessions" ) { });}); getOcppContext()->getMessageService().registerOperation("StopTransaction", [&txId_generate, &txId_confirm] () { - return new Ocpp16::CustomOperation("StopTransaction", + return new v16::CustomOperation("StopTransaction", [&txId_generate, &txId_confirm] (JsonObject payload) { //receive req REQUIRE( payload["transactionId"].as() == txId_generate ); @@ -594,7 +594,7 @@ TEST_CASE( "Charging sessions" ) { int txId_confirm = txId_base; getOcppContext()->getMessageService().registerOperation("StartTransaction", [&txId_generate] () { - return new Ocpp16::CustomOperation("StartTransaction", + return new v16::CustomOperation("StartTransaction", [] (JsonObject payload) {}, //ignore req [&txId_generate] () { //create conf @@ -609,7 +609,7 @@ TEST_CASE( "Charging sessions" ) { });}); getOcppContext()->getMessageService().registerOperation("StopTransaction", [&txId_generate, &txId_confirm] () { - return new Ocpp16::CustomOperation("StopTransaction", + return new v16::CustomOperation("StopTransaction", [&txId_generate, &txId_confirm] (JsonObject payload) { //receive req REQUIRE( payload["transactionId"].as() == txId_generate ); @@ -698,7 +698,7 @@ TEST_CASE( "Charging sessions" ) { getOcppContext()->getMessageService().registerOperation("StartTransaction", [&check_1, &check_2, &check_3, &check_4, tx1_idTag, tx2_idTag, tx3_idTag, tx4_idTag] () { - return new Ocpp16::CustomOperation("StartTransaction", + return new v16::CustomOperation("StartTransaction", [&check_1, &check_2, &check_3, &check_4, tx1_idTag, tx2_idTag, tx3_idTag, tx4_idTag] (JsonObject payload) { //process req @@ -757,7 +757,7 @@ TEST_CASE( "Charging sessions" ) { bool checkProcessed = false; - auto changeAvailability = makeRequest(new Ocpp16::CustomOperation( + auto changeAvailability = makeRequest(new v16::CustomOperation( "ChangeAvailability", [] () { //create req @@ -813,7 +813,7 @@ TEST_CASE( "Charging sessions" ) { bool checkProcessed = false; getOcppContext()->initiateRequest(makeRequest( - new MicroOcpp::Ocpp16::CustomOperation("UnlockConnector", + new MicroOcpp::v16::CustomOperation("UnlockConnector", [] () { //create req auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(1)); @@ -840,7 +840,7 @@ TEST_CASE( "Charging sessions" ) { checkProcessed = false; getOcppContext()->initiateRequest(makeRequest( - new MicroOcpp::Ocpp16::CustomOperation("UnlockConnector", + new MicroOcpp::v16::CustomOperation("UnlockConnector", [] () { //create req auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(1)); @@ -865,7 +865,7 @@ TEST_CASE( "Charging sessions" ) { checkProcessed = false; getOcppContext()->initiateRequest(makeRequest( - new MicroOcpp::Ocpp16::CustomOperation("UnlockConnector", + new MicroOcpp::v16::CustomOperation("UnlockConnector", [] () { //create req auto doc = makeJsonDoc("UnitTests", JSON_OBJECT_SIZE(1)); @@ -953,7 +953,7 @@ TEST_CASE( "Charging sessions" ) { */ getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId] () { - return new Ocpp16::CustomOperation("StartTransaction", + return new v16::CustomOperation("StartTransaction", [&checkProcessedStartTx] (JsonObject payload) { //receive req checkProcessedStartTx = true; @@ -970,7 +970,7 @@ TEST_CASE( "Charging sessions" ) { });}); getOcppContext()->getMessageService().registerOperation("StopTransaction", [&checkProcessedStopTx] () { - return new Ocpp16::CustomOperation("StopTransaction", + return new v16::CustomOperation("StopTransaction", [&checkProcessedStopTx] (JsonObject payload) { //receive req checkProcessedStopTx = true; @@ -1082,7 +1082,7 @@ TEST_CASE( "Charging sessions" ) { unsigned int attemptNr = 0; getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId, &attemptNr] () { - return new Ocpp16::CustomOperation("StartTransaction", + return new v16::CustomOperation("StartTransaction", [&attemptNr] (JsonObject payload) { //receive req attemptNr++; @@ -1150,7 +1150,7 @@ TEST_CASE( "Charging sessions" ) { getOcppContext()->getModel().getClock().setTime(BASE_TIME); getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkProcessedStartTx, &txId, &attemptNr] () { - return new Ocpp16::CustomOperation("StartTransaction", + return new v16::CustomOperation("StartTransaction", [&attemptNr] (JsonObject payload) { //receive req attemptNr++; @@ -1171,7 +1171,7 @@ TEST_CASE( "Charging sessions" ) { });}); getOcppContext()->getMessageService().registerOperation("StopTransaction", [&checkProcessedStopTx] () { - return new Ocpp16::CustomOperation("StopTransaction", + return new v16::CustomOperation("StopTransaction", [&checkProcessedStopTx] (JsonObject payload) { //receive req checkProcessedStopTx = true; diff --git a/tests/Configuration.cpp b/tests/Configuration.cpp index 91fa1e28..ac7ea503 100644 --- a/tests/Configuration.cpp +++ b/tests/Configuration.cpp @@ -284,7 +284,7 @@ TEST_CASE( "Configuration" ) { bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "GetConfiguration", [] () { //create req @@ -321,7 +321,7 @@ TEST_CASE( "Configuration" ) { checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "GetConfiguration", [] () { //create req @@ -375,7 +375,7 @@ TEST_CASE( "Configuration" ) { //update existing config bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeConfiguration", [] () { //create req @@ -397,7 +397,7 @@ TEST_CASE( "Configuration" ) { //try to update not existing key checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeConfiguration", [] () { //create req @@ -418,7 +418,7 @@ TEST_CASE( "Configuration" ) { //try to update config with malformatted value checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeConfiguration", [] () { //create req @@ -445,7 +445,7 @@ TEST_CASE( "Configuration" ) { //validation success checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeConfiguration", [] () { //create req @@ -467,7 +467,7 @@ TEST_CASE( "Configuration" ) { //validation failure checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeConfiguration", [] () { //create req diff --git a/tests/ConfigurationBehavior.cpp b/tests/ConfigurationBehavior.cpp index fc30b356..f0694707 100644 --- a/tests/ConfigurationBehavior.cpp +++ b/tests/ConfigurationBehavior.cpp @@ -239,7 +239,7 @@ TEST_CASE( "Configuration Behavior" ) { SECTION("set true") { configBool->setBool(true); - context->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + context->initiateRequest(makeRequest(new v16::CustomOperation( "RemoteStartTransaction", [] () { //create req @@ -261,7 +261,7 @@ TEST_CASE( "Configuration Behavior" ) { SECTION("set false") { configBool->setBool(false); - context->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + context->initiateRequest(makeRequest(new v16::CustomOperation( "RemoteStartTransaction", [] () { //create req diff --git a/tests/FirmwareManagement.cpp b/tests/FirmwareManagement.cpp index 99518028..eaec328d 100644 --- a/tests/FirmwareManagement.cpp +++ b/tests/FirmwareManagement.cpp @@ -52,7 +52,7 @@ TEST_CASE( "FirmwareManagement" ) { getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("FirmwareStatusNotification", + return new v16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { //process req checkProcessed = true; @@ -67,7 +67,7 @@ TEST_CASE( "FirmwareManagement" ) { }); }); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "UpdateFirmware", [] () { //create req @@ -92,7 +92,7 @@ TEST_CASE( "FirmwareManagement" ) { getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("FirmwareStatusNotification", + return new v16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { //process req if (checkProcessed == 0) { @@ -131,7 +131,7 @@ TEST_CASE( "FirmwareManagement" ) { } }); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "UpdateFirmware", [] () { //create req @@ -161,7 +161,7 @@ TEST_CASE( "FirmwareManagement" ) { getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("FirmwareStatusNotification", + return new v16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { //process req if (checkProcessed == 0) { @@ -198,7 +198,7 @@ TEST_CASE( "FirmwareManagement" ) { } }); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "UpdateFirmware", [] () { //create req @@ -228,7 +228,7 @@ TEST_CASE( "FirmwareManagement" ) { getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("FirmwareStatusNotification", + return new v16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { //process req if (checkProcessed == 0) { @@ -285,7 +285,7 @@ TEST_CASE( "FirmwareManagement" ) { } }); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "UpdateFirmware", [] () { //create req @@ -317,7 +317,7 @@ TEST_CASE( "FirmwareManagement" ) { getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("FirmwareStatusNotification", + return new v16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { //process req if (checkProcessed == 0) { @@ -369,7 +369,7 @@ TEST_CASE( "FirmwareManagement" ) { return true; }); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "UpdateFirmware", [] () { //create req @@ -400,7 +400,7 @@ TEST_CASE( "FirmwareManagement" ) { getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("FirmwareStatusNotification", + return new v16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { //process req if (checkProcessed == 0) { @@ -448,7 +448,7 @@ TEST_CASE( "FirmwareManagement" ) { } }); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "UpdateFirmware", [] () { //create req @@ -480,7 +480,7 @@ TEST_CASE( "FirmwareManagement" ) { getOcppContext()->getMessageService().registerOperation("FirmwareStatusNotification", [&checkProcessed] () { - return new Ocpp16::CustomOperation("FirmwareStatusNotification", + return new v16::CustomOperation("FirmwareStatusNotification", [ &checkProcessed] (JsonObject payload) { //process req if (checkProcessed == 0) { @@ -537,7 +537,7 @@ TEST_CASE( "FirmwareManagement" ) { } }); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "UpdateFirmware", [] () { //create req diff --git a/tests/LocalAuthList.cpp b/tests/LocalAuthList.cpp index 1253338b..e927b313 100644 --- a/tests/LocalAuthList.cpp +++ b/tests/LocalAuthList.cpp @@ -403,7 +403,7 @@ TEST_CASE( "LocalAuth" ) { //patch Authorize so it will reject all idTags getOcppContext()->getMessageService().registerOperation("Authorize", [] () { - return new Ocpp16::CustomOperation("Authorize", + return new v16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req [] () { //create conf @@ -446,7 +446,7 @@ TEST_CASE( "LocalAuth" ) { //patch Authorize so it will reject all idTags bool checkAuthorize = false; getOcppContext()->getMessageService().registerOperation("Authorize", [&checkAuthorize] () { - return new Ocpp16::CustomOperation("Authorize", + return new v16::CustomOperation("Authorize", [&checkAuthorize] (JsonObject) { checkAuthorize = true; }, @@ -461,7 +461,7 @@ TEST_CASE( "LocalAuth" ) { //patch StartTransaction so it will DeAuthorize all txs bool checkStartTx = false; getOcppContext()->getMessageService().registerOperation("StartTransaction", [&checkStartTx] () { - return new Ocpp16::CustomOperation("StartTransaction", + return new v16::CustomOperation("StartTransaction", [&checkStartTx] (JsonObject) { checkStartTx = true; }, @@ -478,7 +478,7 @@ TEST_CASE( "LocalAuth" ) { //check resulting StatusNotification message bool checkLocalListConflict = false; getOcppContext()->getMessageService().registerOperation("StatusNotification", [&checkLocalListConflict] () { - return new Ocpp16::CustomOperation("StatusNotification", + return new v16::CustomOperation("StatusNotification", [&checkLocalListConflict] (JsonObject payload) { if (payload["connectorId"] == 0 && !strcmp(payload["errorCode"] | "_Undefined", "LocalListConflict")) { @@ -645,7 +645,7 @@ TEST_CASE( "LocalAuth" ) { //Full update - happy path bool checkAccepted = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&listVersion, &listSize, &populatedEntryIdTag] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -688,7 +688,7 @@ TEST_CASE( "LocalAuth" ) { checkAccepted = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&listVersion, &listSize] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -714,7 +714,7 @@ TEST_CASE( "LocalAuth" ) { bool checkVersionMismatch = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&listVersion, &listSizeInvalid] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -741,7 +741,7 @@ TEST_CASE( "LocalAuth" ) { checkAccepted = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&listVersion, &listSize] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -767,7 +767,7 @@ TEST_CASE( "LocalAuth" ) { checkAccepted = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&listVersion, &listSize] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -794,7 +794,7 @@ TEST_CASE( "LocalAuth" ) { bool errOccurence = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&listVersionInvalid, &listSizeInvalid] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -830,7 +830,7 @@ TEST_CASE( "LocalAuth" ) { //Full update - happy path bool checkAccepted = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&i] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -868,7 +868,7 @@ TEST_CASE( "LocalAuth" ) { bool checkFailed = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SendLocalList", + new v16::CustomOperation("SendLocalList", [&listVersionInvalid] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -909,7 +909,7 @@ TEST_CASE( "LocalAuth" ) { int checkListVerion = -1; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("GetLocalListVersion", + new v16::CustomOperation("GetLocalListVersion", [] () { //create req return createEmptyDocument(); diff --git a/tests/Metering.cpp b/tests/Metering.cpp index 4fcb77a6..77786e16 100644 --- a/tests/Metering.cpp +++ b/tests/Metering.cpp @@ -635,7 +635,7 @@ TEST_CASE("Metering") { unsigned int attemptNr = 0; getOcppContext()->getMessageService().registerOperation("MeterValues", [&attemptNr] () { - return new Ocpp16::CustomOperation("MeterValues", + return new v16::CustomOperation("MeterValues", [&attemptNr] (JsonObject payload) { //receive req attemptNr++; diff --git a/tests/Reservation.cpp b/tests/Reservation.cpp index c2437a67..85ea307f 100644 --- a/tests/Reservation.cpp +++ b/tests/Reservation.cpp @@ -88,7 +88,7 @@ TEST_CASE( "Reservation" ) { rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag); REQUIRE( connector->getStatus() == ChargePointStatus_Reserved ); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "RemoteStartTransaction", [] () { //create req @@ -111,7 +111,7 @@ TEST_CASE( "Reservation" ) { rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag); REQUIRE( connector->getStatus() == ChargePointStatus_Reserved ); - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "RemoteStartTransaction", [idTag] () { //create req @@ -172,7 +172,7 @@ TEST_CASE( "Reservation" ) { bool checkProcessed = false; getOcppContext()->getMessageService().registerOperation("Authorize", [parentIdTag, &checkProcessed] () { - return new Ocpp16::CustomOperation("Authorize", + return new v16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req payload [parentIdTag, &checkProcessed] () { //create conf @@ -315,7 +315,7 @@ TEST_CASE( "Reservation" ) { //simple reservation bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ReserveNow", [reservationId, connectorId, expiryDate, idTag, parentIdTag] () { //create req @@ -351,7 +351,7 @@ TEST_CASE( "Reservation" ) { REQUIRE( connector->getStatus() == ChargePointStatus_Faulted ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ReserveNow", [reservationId, connectorId, expiryDate, idTag, parentIdTag] () { //create req @@ -386,7 +386,7 @@ TEST_CASE( "Reservation" ) { REQUIRE( connector->getStatus() == ChargePointStatus_Preparing ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ReserveNow", [reservationId, connectorId, expiryDate, idTag, parentIdTag] () { //create req @@ -423,7 +423,7 @@ TEST_CASE( "Reservation" ) { REQUIRE( connector->getStatus() == ChargePointStatus_Unavailable ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ReserveNow", [reservationId, connectorId, expiryDate, idTag, parentIdTag] () { //create req @@ -469,7 +469,7 @@ TEST_CASE( "Reservation" ) { //CancelReservation successfully bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "CancelReservation", [reservationId, connectorId, expiryDate, idTag, parentIdTag] () { //create req @@ -490,7 +490,7 @@ TEST_CASE( "Reservation" ) { //CancelReservation while no reservation exists checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "CancelReservation", [reservationId, connectorId, expiryDate, idTag, parentIdTag] () { //create req diff --git a/tests/Reset.cpp b/tests/Reset.cpp index 186771b3..90b8e96d 100644 --- a/tests/Reset.cpp +++ b/tests/Reset.cpp @@ -40,7 +40,7 @@ TEST_CASE( "Reset" ) { mocpp_set_timer(custom_timer_cb); getOcppContext()->getMessageService().registerOperation("Authorize", [] () { - return new Ocpp16::CustomOperation("Authorize", + return new v16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req [] () { //create conf @@ -51,7 +51,7 @@ TEST_CASE( "Reset" ) { });}); getOcppContext()->getMessageService().registerOperation("TransactionEvent", [] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [] (JsonObject) {}, //ignore req [] () { //create conf @@ -97,7 +97,7 @@ TEST_CASE( "Reset" ) { bool checkProcessed = false; - auto resetRequest = makeRequest(new Ocpp16::CustomOperation( + auto resetRequest = makeRequest(new v16::CustomOperation( "Reset", [] () { //create req @@ -150,7 +150,7 @@ TEST_CASE( "Reset" ) { bool checkProcessed = false; - auto resetRequest = makeRequest(new Ocpp16::CustomOperation( + auto resetRequest = makeRequest(new v16::CustomOperation( "Reset", [] () { //create req @@ -235,7 +235,7 @@ TEST_CASE( "Reset" ) { bool checkProcessedTx = false; getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkProcessedTx] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [&checkProcessedTx] (JsonObject payload) { //process req checkProcessedTx = true; @@ -251,7 +251,7 @@ TEST_CASE( "Reset" ) { bool checkProcessed = false; - auto resetRequest = makeRequest(new Ocpp16::CustomOperation( + auto resetRequest = makeRequest(new v16::CustomOperation( "Reset", [] () { //create req @@ -305,7 +305,7 @@ TEST_CASE( "Reset" ) { bool checkProcessed = false; - auto resetRequest = makeRequest(new Ocpp16::CustomOperation( + auto resetRequest = makeRequest(new v16::CustomOperation( "Reset", [] () { //create req @@ -337,7 +337,7 @@ TEST_CASE( "Reset" ) { bool checkProcessed = false; - auto resetRequest = makeRequest(new Ocpp16::CustomOperation( + auto resetRequest = makeRequest(new v16::CustomOperation( "Reset", [] () { //create req diff --git a/tests/Security.cpp b/tests/Security.cpp index 72483522..f7503e89 100644 --- a/tests/Security.cpp +++ b/tests/Security.cpp @@ -43,7 +43,7 @@ TEST_CASE( "Security" ) { MO_MEM_RESET(); - getOcppContext()->initiateRequest(makeRequest(new Ocpp201::SecurityEventNotification( + getOcppContext()->initiateRequest(makeRequest(new v201::SecurityEventNotification( "ReconfigurationOfSecurityParameters", getOcppContext()->getModel().getClock().now()))); diff --git a/tests/SmartCharging.cpp b/tests/SmartCharging.cpp index f26c4901..c0f16e9c 100644 --- a/tests/SmartCharging.cpp +++ b/tests/SmartCharging.cpp @@ -505,7 +505,7 @@ TEST_CASE( "SmartCharging" ) { bool checkProcessed = false; - auto getCompositeSchedule = makeRequest(new Ocpp16::CustomOperation( + auto getCompositeSchedule = makeRequest(new v16::CustomOperation( "GetCompositeSchedule", [] () { //create req @@ -581,7 +581,7 @@ TEST_CASE( "SmartCharging" ) { loop(); bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -599,7 +599,7 @@ TEST_CASE( "SmartCharging" ) { REQUIRE( checkProcessed ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -618,7 +618,7 @@ TEST_CASE( "SmartCharging" ) { REQUIRE( checkProcessed ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -641,7 +641,7 @@ TEST_CASE( "SmartCharging" ) { // replace existing profile - OK checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -663,7 +663,7 @@ TEST_CASE( "SmartCharging" ) { // try to install additional profile - not okay checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -687,7 +687,7 @@ TEST_CASE( "SmartCharging" ) { loop(); bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -706,7 +706,7 @@ TEST_CASE( "SmartCharging" ) { REQUIRE( checkProcessed ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -732,7 +732,7 @@ TEST_CASE( "SmartCharging" ) { loop(); bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -757,7 +757,7 @@ TEST_CASE( "SmartCharging" ) { REQUIRE( checkProcessed ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -793,7 +793,7 @@ TEST_CASE( "SmartCharging" ) { setSmartChargingPowerOutput([] (float) { }); bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -811,7 +811,7 @@ TEST_CASE( "SmartCharging" ) { REQUIRE( checkProcessed ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -833,7 +833,7 @@ TEST_CASE( "SmartCharging" ) { setSmartChargingCurrentOutput([] (float) { }); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req @@ -851,7 +851,7 @@ TEST_CASE( "SmartCharging" ) { REQUIRE( checkProcessed ); checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "SetChargingProfile", [] () { //create req diff --git a/tests/Transactions.cpp b/tests/Transactions.cpp index 8b85d972..9716e784 100644 --- a/tests/Transactions.cpp +++ b/tests/Transactions.cpp @@ -39,7 +39,7 @@ TEST_CASE( "Transactions" ) { mocpp_set_timer(custom_timer_cb); getOcppContext()->getMessageService().registerOperation("Authorize", [] () { - return new Ocpp16::CustomOperation("Authorize", + return new v16::CustomOperation("Authorize", [] (JsonObject) {}, //ignore req [] () { //create conf @@ -50,7 +50,7 @@ TEST_CASE( "Transactions" ) { });}); getOcppContext()->getMessageService().registerOperation("TransactionEvent", [] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [] (JsonObject) {}, //ignore req [] () { //create conf @@ -293,7 +293,7 @@ TEST_CASE( "Transactions" ) { bool checkReceivedStarted = false, checkReceivedEnded = false; getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded] (JsonObject request) { //process req const char *eventType = request["eventType"] | (const char*)nullptr; @@ -344,7 +344,7 @@ TEST_CASE( "Transactions" ) { size_t checkSeqNosSize = 0; getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, &checkSeqNosSize] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, &checkSeqNosSize] (JsonObject request) { //process req const char *eventType = request["eventType"] | (const char*)nullptr; @@ -417,7 +417,7 @@ TEST_CASE( "Transactions" ) { std::map> txEventRequests; getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&txEventRequests] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [&txEventRequests] (JsonObject request) { //process req const char *eventType = request["eventType"] | (const char*)nullptr; @@ -522,7 +522,7 @@ TEST_CASE( "Transactions" ) { bool checkProcessed = false; getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkProcessed, txId] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [&checkProcessed, txId] (JsonObject request) { //process req const char *eventType = request["eventType"] | (const char*)nullptr; @@ -608,7 +608,7 @@ TEST_CASE( "Transactions" ) { bool checkReceivedStarted = false, checkReceivedEnded = false; getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, txId, &checkSeqNosSize] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [&checkReceivedStarted, &checkReceivedEnded, txId, &checkSeqNosSize] (JsonObject request) { //process req const char *eventType = request["eventType"] | (const char*)nullptr; @@ -699,7 +699,7 @@ TEST_CASE( "Transactions" ) { getOcppContext()->getModel().getVariableService()->declareVariable("TxCtrlr", "TxStopPoint", "")->setString("Authorized"); getOcppContext()->getMessageService().registerOperation("TransactionEvent", [&txEventRequests] () { - return new Ocpp16::CustomOperation("TransactionEvent", + return new v16::CustomOperation("TransactionEvent", [&txEventRequests] (JsonObject request) { //process req const char *eventType = request["eventType"] | (const char*)nullptr; diff --git a/tests/Variables.cpp b/tests/Variables.cpp index 05f76b87..96885d58 100644 --- a/tests/Variables.cpp +++ b/tests/Variables.cpp @@ -252,7 +252,7 @@ TEST_CASE( "Variable" ) { bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "GetVariables", [] () { //create req @@ -289,7 +289,7 @@ TEST_CASE( "Variable" ) { checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "GetVariable", [] () { //create req @@ -343,7 +343,7 @@ TEST_CASE( "Variable" ) { //update existing config bool checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeVariable", [] () { //create req @@ -365,7 +365,7 @@ TEST_CASE( "Variable" ) { //try to update not existing key checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeVariable", [] () { //create req @@ -386,7 +386,7 @@ TEST_CASE( "Variable" ) { //try to update config with malformatted value checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeVariable", [] () { //create req @@ -413,7 +413,7 @@ TEST_CASE( "Variable" ) { //validation success checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeVariable", [] () { //create req @@ -435,7 +435,7 @@ TEST_CASE( "Variable" ) { //validation failure checkProcessed = false; - getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + getOcppContext()->initiateRequest(makeRequest(new v16::CustomOperation( "ChangeVariable", [] () { //create req @@ -504,7 +504,7 @@ TEST_CASE( "Variable" ) { bool checkProcessed = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("GetVariables", + new v16::CustomOperation("GetVariables", [] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -553,7 +553,7 @@ TEST_CASE( "Variable" ) { bool checkProcessed = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("SetVariables", + new v16::CustomOperation("SetVariables", [] () { //create req auto doc = makeJsonDoc("UnitTests", @@ -603,7 +603,7 @@ TEST_CASE( "Variable" ) { getOcppContext()->getMessageService().registerOperation("NotifyReport", [&checkProcessedNotification, &checkTimestamp] () { - return new Ocpp16::CustomOperation("NotifyReport", + return new v16::CustomOperation("NotifyReport", [ &checkProcessedNotification, &checkTimestamp] (JsonObject payload) { //process req checkProcessedNotification = true; @@ -629,7 +629,7 @@ TEST_CASE( "Variable" ) { bool checkProcessed = false; getOcppContext()->initiateRequest(makeRequest( - new Ocpp16::CustomOperation("GetBaseReport", + new v16::CustomOperation("GetBaseReport", [] () { //create req auto doc = makeJsonDoc("UnitTests", From 31985aa96be11ba248d8d86564aa0c9856778e16 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 6 Jul 2025 16:14:09 +0200 Subject: [PATCH 18/50] remove superfluous white spaces --- src/MicroOcpp.cpp | 14 +++--- src/MicroOcpp.h | 48 +++++++++---------- src/MicroOcpp/Context.cpp | 2 +- src/MicroOcpp/Core/Connection.cpp | 10 ++-- src/MicroOcpp/Core/Connection.h | 6 +-- src/MicroOcpp/Core/FilesystemAdapter.h | 4 +- src/MicroOcpp/Core/FilesystemUtils.h | 2 +- src/MicroOcpp/Core/FtpMbedTLS.cpp | 22 ++++----- src/MicroOcpp/Core/FtpMbedTLS.h | 6 +-- src/MicroOcpp/Core/Memory.h | 2 +- src/MicroOcpp/Core/MessageService.cpp | 22 ++++----- src/MicroOcpp/Core/MessageService.h | 2 +- src/MicroOcpp/Core/Operation.cpp | 2 +- src/MicroOcpp/Core/Operation.h | 14 +++--- src/MicroOcpp/Core/Request.cpp | 6 +-- src/MicroOcpp/Core/Request.h | 12 ++--- src/MicroOcpp/Core/Time.cpp | 8 ++-- src/MicroOcpp/Core/Time.h | 10 ++-- src/MicroOcpp/Core/UuidUtils.cpp | 2 +- src/MicroOcpp/Debug.cpp | 2 +- .../Model/Authorization/AuthorizationData.cpp | 4 +- .../Model/Authorization/AuthorizationList.cpp | 12 ++--- .../Authorization/AuthorizationService.cpp | 2 +- src/MicroOcpp/Model/Authorization/IdToken.cpp | 2 +- .../Model/Availability/AvailabilityDefs.h | 2 +- .../Availability/AvailabilityService.cpp | 8 ++-- .../Model/Availability/AvailabilityService.h | 2 +- .../Model/Boot/BootNotificationData.h | 6 +-- src/MicroOcpp/Model/Boot/BootService.cpp | 2 +- src/MicroOcpp/Model/Boot/BootService.h | 4 +- .../Model/Certificates/Certificate_c.cpp | 2 +- .../Model/Certificates/Certificate_c.h | 2 +- .../Configuration/ConfigurationContainer.cpp | 8 ++-- .../Configuration/ConfigurationService.cpp | 2 +- .../Configuration/ConfigurationService.h | 2 +- .../Model/Diagnostics/DiagnosticsService.cpp | 8 ++-- .../FirmwareManagement/FirmwareService.cpp | 6 +-- .../FirmwareManagement/FirmwareService.h | 2 +- src/MicroOcpp/Model/Metering/MeterValue.cpp | 10 ++-- src/MicroOcpp/Model/Metering/MeterValue.h | 2 +- .../Model/Metering/MeteringService.cpp | 2 +- .../Model/Metering/MeteringService.h | 2 +- src/MicroOcpp/Model/Model.cpp | 4 +- .../RemoteControl/RemoteControlService.cpp | 10 ++-- .../RemoteControl/RemoteControlService.h | 2 +- .../Model/Reservation/Reservation.cpp | 4 +- .../Model/Reservation/ReservationService.cpp | 6 +-- src/MicroOcpp/Model/Reset/ResetDefs.h | 2 +- src/MicroOcpp/Model/Reset/ResetService.cpp | 10 ++-- src/MicroOcpp/Model/Reset/ResetService.h | 4 +- .../SecurityEvent/SecurityEventService.cpp | 16 +++---- .../SmartCharging/SmartChargingModel.cpp | 4 +- .../Model/SmartCharging/SmartChargingModel.h | 4 +- .../SmartCharging/SmartChargingService.cpp | 20 ++++---- .../Model/Transactions/Transaction.cpp | 4 +- .../Model/Transactions/Transaction.h | 8 ++-- .../Model/Transactions/TransactionDefs.h | 2 +- .../Transactions/TransactionService16.cpp | 28 +++++------ .../Model/Transactions/TransactionService16.h | 4 +- .../Transactions/TransactionService201.cpp | 16 +++---- .../Model/Transactions/TransactionStore.cpp | 16 +++---- src/MicroOcpp/Model/Variables/Variable.h | 2 +- .../Model/Variables/VariableContainer.cpp | 4 +- .../Model/Variables/VariableContainer.h | 2 +- .../Model/Variables/VariableService.cpp | 10 ++-- .../Model/Variables/VariableService.h | 2 +- src/MicroOcpp/Operations/BootNotification.cpp | 6 +-- .../Operations/CancelReservation.cpp | 2 +- .../Operations/ChangeAvailability.cpp | 4 +- .../Operations/ChangeConfiguration.cpp | 2 +- src/MicroOcpp/Operations/ClearCache.cpp | 2 +- .../Operations/ClearChargingProfile.h | 2 +- src/MicroOcpp/Operations/CustomOperation.cpp | 2 +- src/MicroOcpp/Operations/DataTransfer.h | 2 +- .../DiagnosticsStatusNotification.cpp | 2 +- src/MicroOcpp/Operations/GetBaseReport.cpp | 2 +- .../Operations/GetCompositeSchedule.cpp | 2 +- src/MicroOcpp/Operations/GetConfiguration.cpp | 4 +- src/MicroOcpp/Operations/GetDiagnostics.cpp | 2 +- .../Operations/GetInstalledCertificateIds.cpp | 2 +- .../Operations/GetLocalListVersion.cpp | 2 +- src/MicroOcpp/Operations/GetVariables.cpp | 4 +- src/MicroOcpp/Operations/GetVariables.h | 2 +- src/MicroOcpp/Operations/Heartbeat.cpp | 4 +- src/MicroOcpp/Operations/MeterValues.cpp | 4 +- src/MicroOcpp/Operations/NotifyReport.cpp | 4 +- .../Operations/RemoteStartTransaction.cpp | 2 +- .../Operations/RemoteStartTransaction.h | 2 +- .../Operations/RemoteStopTransaction.cpp | 2 +- .../Operations/RequestStartTransaction.cpp | 2 +- .../Operations/RequestStopTransaction.cpp | 4 +- src/MicroOcpp/Operations/ReserveNow.cpp | 2 +- src/MicroOcpp/Operations/Reset.cpp | 6 +-- src/MicroOcpp/Operations/SendLocalList.cpp | 6 +-- src/MicroOcpp/Operations/SetVariables.cpp | 6 +-- src/MicroOcpp/Operations/StartTransaction.cpp | 8 ++-- .../Operations/StatusNotification.cpp | 2 +- src/MicroOcpp/Operations/TriggerMessage.cpp | 2 +- src/MicroOcpp/Operations/UnlockConnector.cpp | 6 +-- src/MicroOcpp/Platform.cpp | 4 +- 100 files changed, 291 insertions(+), 291 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index a7cb7b82..b5a51c05 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -1936,7 +1936,7 @@ void mo_setDiagnosticsReader(MO_Context *ctx, size_t (*readBytes)(char*, size_t, return; } auto context = mo_getContext2(ctx); - + MicroOcpp::DiagnosticsService *diagSvc = nullptr; #if MO_ENABLE_V16 @@ -2477,7 +2477,7 @@ bool mo_declareVarConfigInt(MO_Context *ctx, const char *component201, const cha MO_DBG_ERR("setup failure"); return false; } - + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::v201::Variable::AttributeTypeSet(), rebootRequired); } #endif @@ -2520,7 +2520,7 @@ bool mo_declareVarConfigBool(MO_Context *ctx, const char *component201, const ch MO_DBG_ERR("setup failure"); return false; } - + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::v201::Variable::AttributeTypeSet(), rebootRequired); } #endif @@ -2563,7 +2563,7 @@ bool mo_declareVarConfigString(MO_Context *ctx, const char *component201, const MO_DBG_ERR("setup failure"); return false; } - + success = varSvc->declareVariable(component201, name201, factoryDefault, convertMutability(mutability), persistent, MicroOcpp::v201::Variable::AttributeTypeSet(), rebootRequired); } #endif @@ -2919,7 +2919,7 @@ bool mo_setup2(MO_Context *ctx) { return false; } auto context = mo_getContext2(ctx); - + return context->setup(); } @@ -2952,7 +2952,7 @@ bool mo_sendRequest(MO_Context *ctx, const char *operationType, MicroOcpp::Operation *operation = nullptr; std::unique_ptr request; - + MicroOcpp::Context *context = nullptr; if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before @@ -2961,7 +2961,7 @@ bool mo_sendRequest(MO_Context *ctx, const char *operationType, context = mo_getContext2(ctx); if (auto customOperation = new MicroOcpp::CustomOperation()) { - + if (!customOperation->setupEvseInitiated(operationType, payloadJson, onResponse, userData)) { MO_DBG_ERR("create operation failure"); delete customOperation; diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index ed111b92..1d58b70d 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -48,8 +48,8 @@ void mo_setFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char * * WebSockets adapter (see examples folder). `backendUrl`, `chargeBoxId`, `authorizationKey` and * `CA_cert` are zero-copy and must remain valid until `mo_setup()`. `CA_cert` is zero-copy and must * outlive the MO lifecycle. - * - * If the connections fails, please refer to + * + * If the connections fails, please refer to * https://github.com/matth-x/MicroOcpp/issues/36#issuecomment-989716573 for recommendations on * how to track down the issue with the connection. */ @@ -66,7 +66,7 @@ bool mo_setWebsocketUrl( * of the passed `connection` object, i.e. it must be destroyed after `mo_deinitialize()` Please * refer to https://github.com/matth-x/MicroOcpp/tree/main/examples/ESP-TLS for an example how to * use it. - * + * * This GitHub project also delivers an Connection implementation based on links2004/WebSockets. If * you need another WebSockets implementation, you can subclass the Connection class and pass it to * this initialize() function. Please refer to @@ -93,10 +93,10 @@ bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData /* * Define the Inputs and Outputs of this library. - * + * * This library interacts with the hardware of your charger by Inputs and Outputs. Inputs and Outputs * are callbacks which read information from the EVSE or control the behavior of the EVSE. - * + * * An Input is a function which returns the current state of a variable of the EVSE. For example, if * the energy meter stores the energy register in the global variable `e_reg`, then you can allow * this library to read it by defining the following Input and passing it to the library. @@ -105,7 +105,7 @@ bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData * return e_reg; * }); * ``` - * + * * An Output is a callback which gets state value from the OCPP library and applies it to the EVSE. * For example, to let Smart Charging control the PWM signal of the Control Pilot, define the * following Output and pass it to the library. @@ -114,7 +114,7 @@ bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData * pwm = p_max / PWM_FACTOR; //(simplified example) * }); * ``` - * + * * Configure the library with Inputs and Outputs after mo_initialize() and before mo_setup(). */ @@ -156,7 +156,7 @@ void mo_loop(); /* * Transaction management. - * + * * OCPP 1.6 (2.0.1 see below): * Begin the transaction process and prepare it. When all conditions for the transaction are true, * eventually send a StartTransaction request to the OCPP server. @@ -168,12 +168,12 @@ void mo_loop(); * rules will apply as in the specification. * 4) the vehicle is already plugged or will be plugged soon (only applicable if the * ConnectorPlugged Input is set) - * + * * See beginTransaction_authorized for skipping steps 1) to 3) - * + * * Returns true if it was possible to create the transaction process. Returns * false if either another transaction process is still active or you need to try it again later. - * + * * OCPP 2.0.1: * Authorize a transaction. Like the OCPP 1.6 behavior, this should be called when the user swipes the * card to start charging, but the semantic is slightly different. This function begins the authorized @@ -213,32 +213,32 @@ bool mo_setTransactionAuthorized2(MO_Context *ctx, unsigned int evseId, const ch * matches with the parentIdTag of beginTransaction. * - [Planned, not released yet] If none of step 2) applies, then the OCPP lib will check * the authorization status via an Authorize request - * + * * See endTransaction_authorized for skipping the authorization check, i.e. step 2) - * + * * If the transaction is ended by swiping an RFID card, then idTag should contain its identifier. If * charging stops for a different reason than swiping the card, idTag should be null or empty. - * + * * Please refer to OCPP 1.6 Specification - Edition 2 p. 90 for a list of valid reasons. `reason` * can also be nullptr. - * + * * It is safe to call this function at any time, i.e. when no transaction runs or when the transaction * has already been ended. For example you can place * `endTransaction(nullptr, "Reboot");` * in the beginning of the program just to ensure that there is no transaction from a previous run. - * + * * If called with idTag=nullptr, this is functionally equivalent to * `endTransaction_authorized(nullptr, reason);` - * + * * Returns true if there is a transaction which could eventually be ended by this action - * + * * OCPP 2.0.1: * End the user authorization. Like when running with OCPP 1.6, this should be called when the user * swipes the card to stop charging. The difference between the 1.6/2.0.1 behavior is that in 1.6, * endTransaction always sets the transaction inactive so that it wants to stop. In 2.0.1, this only * revokes the user authorization which may terminate the transaction but doesn't have to if the * transaction stop point is set to EvConnected. - * + * * Note: the stop reason parameter is ignored when running with OCPP 2.0.1. It's always Local */ bool mo_endTransaction(const char *idTag, const char *reason); //idTag and reason can be NULL @@ -262,7 +262,7 @@ bool mo_abortTransaction(MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReas bool mo_abortTransaction2(MO_Context *ctx, unsigned int evseId, MO_TxStoppedReason stoppedReason, MO_TxEventTriggerReason stopTrigger); #endif //MO_ENABLE_V201 -/* +/* * Returns if according to OCPP, the EVSE is allowed to close provide charge now. * * If you integrate it into a J1772 charger, true means that the Control Pilot can send the PWM signal @@ -280,9 +280,9 @@ bool mo_ocppPermitsCharge2(MO_Context *ctx, unsigned int evseId); * - Running: transaction started and running * - Running/StopTxAwait: transaction still running but will end at the next possible time * - Finished: transaction stopped - * + * * isTransactionActive() and isTransactionRunning() give the status by combining them: - * + * * State | isTransactionActive() | isTransactionRunning() * --------------------+-----------------------+----------------------- * Preparing | true | false @@ -328,7 +328,7 @@ MO_ChargePointStatus mo_getChargePointStatus2(MO_Context *ctx, unsigned int evse /* * Define the Inputs and Outputs of this library. (Advanced) - * + * * These Inputs and Outputs are optional depending on the use case of your charger. Set before `mo_setup()` */ @@ -561,7 +561,7 @@ void mo_setTransactionMeterStop2(MO_Context *ctx, unsigned int evseId, int32_t m //Add new Config. `key` is zero-copy, i.e. must outlive the MO lifecycle. Add before `mo_setup()`. //At first boot, MO uses `factoryDefault`. At subsequent boots, it loads the values from flash //during `mo_setup()` -bool mo_declareConfigurationInt(const char *key, int factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); +bool mo_declareConfigurationInt(const char *key, int factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); bool mo_declareConfigurationBool(const char *key, bool factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); bool mo_declareConfigurationString(const char *key, const char *factoryDefault, MO_Mutability mutability, bool persistent, bool rebootRequired); diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index 6c79d597..655a43fe 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -265,7 +265,7 @@ bool Context::setup() { if (!ticksCb) { ticksCb = getDefaultTickCb(); - + if (!ticksCb) { MO_DBG_ERR("must set TicksCb connection before setup. See the examples in this repository"); return false; diff --git a/src/MicroOcpp/Core/Connection.cpp b/src/MicroOcpp/Core/Connection.cpp index 707a1251..233e81de 100644 --- a/src/MicroOcpp/Core/Connection.cpp +++ b/src/MicroOcpp/Core/Connection.cpp @@ -65,7 +65,7 @@ bool mo_connectionConfig_copy(MO_ConnectionConfig *dst, MO_ConnectionConfig *src size += src->backendUrl ? strlen(src->backendUrl) + 1 : 0; size += src->chargeBoxId ? strlen(src->chargeBoxId) + 1 : 0; size += src->authorizationKey ? strlen(src->authorizationKey) + 1 : 0; - + char *buf = static_cast(MO_MALLOC("WebSocketsClient", size)); if (!buf) { MO_DBG_ERR("OOM"); @@ -83,7 +83,7 @@ bool mo_connectionConfig_copy(MO_ConnectionConfig *dst, MO_ConnectionConfig *src } written += ret + 1; } - + if (src->chargeBoxId) { dst->chargeBoxId = buf + written; auto ret = snprintf(buf + written, size - written, "%s", src->chargeBoxId); @@ -92,7 +92,7 @@ bool mo_connectionConfig_copy(MO_ConnectionConfig *dst, MO_ConnectionConfig *src } written += ret + 1; } - + if (src->authorizationKey) { dst->authorizationKey = buf + written; auto ret = snprintf(buf + written, size - written, "%s", src->authorizationKey); @@ -247,7 +247,7 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { for (auto c = port_str.begin(); c != port_str.end(); c++) { if (*c < '0' || *c > '9') { MO_DBG_ERR("could not parse port: %s", url.c_str()); - return nullptr; + return nullptr; } auto p = port * 10U + (*c - '0'); if (p < port) { @@ -307,7 +307,7 @@ Connection *makeDefaultConnection(MO_ConnectionConfig config, int ocppVersion) { // consider connection disconnected if pong is not received 2 times wsock->enableHeartbeat(15000, 3000, 2); //comment this one out to for specific OCPP servers - + // add authentication data (optional) { size_t chargeBoxIdLen = config.chargeBoxId ? strlen(config.chargeBoxId) : 0; diff --git a/src/MicroOcpp/Core/Connection.h b/src/MicroOcpp/Core/Connection.h index dbb2871b..abc52c8e 100644 --- a/src/MicroOcpp/Core/Connection.h +++ b/src/MicroOcpp/Core/Connection.h @@ -16,7 +16,7 @@ #ifndef MO_WS_USE #if MO_PLATFORM == MO_PLATFORM_ARDUINO #define MO_WS_USE MO_WS_ARDUINO -#else +#else #define MO_WS_USE MO_WS_CUSTOM #endif #endif //MO_WS_USE @@ -58,11 +58,11 @@ class Connection { * NEW IN v1.1 * * Returns true if the connection is open; false if the charger is known to be offline. - * + * * This function determines if MO is in "offline mode". In offline mode, MO doesn't wait for Authorize responses * before performing fully local authorization. If the connection is disrupted but isConnected is still true, then * MO will first wait for a timeout to expire (20 seconds) before going into offline mode. - * + * * Returning true will have no further effects other than using the timeout-then-offline mechanism. If the * connection status is uncertain, it's best to return true by default. */ diff --git a/src/MicroOcpp/Core/FilesystemAdapter.h b/src/MicroOcpp/Core/FilesystemAdapter.h index 0a03d5a7..bfc0b326 100644 --- a/src/MicroOcpp/Core/FilesystemAdapter.h +++ b/src/MicroOcpp/Core/FilesystemAdapter.h @@ -130,9 +130,9 @@ typedef struct { * - Arduino SPIFFS * - ESP-IDF SPIFFS * - POSIX-like API (tested on Ubuntu 20.04) - * + * * You can add support for other file systems by passing a custom adapter to mocpp_initialize(...) - * + * * Returns null if platform is not supported or Filesystem is disabled */ diff --git a/src/MicroOcpp/Core/FilesystemUtils.h b/src/MicroOcpp/Core/FilesystemUtils.h index 61f21240..2073bc37 100644 --- a/src/MicroOcpp/Core/FilesystemUtils.h +++ b/src/MicroOcpp/Core/FilesystemUtils.h @@ -60,7 +60,7 @@ enum class StoreStatus : uint8_t { StoreStatus storeJson(MO_FilesystemAdapter *filesystem, const char *fname, const JsonDoc& doc); /* - * Removes files in the MO folder whose file names start with `fnamePrefix`. + * Removes files in the MO folder whose file names start with `fnamePrefix`. */ bool removeByPrefix(MO_FilesystemAdapter *filesystem, const char *fnamePrefix); diff --git a/src/MicroOcpp/Core/FtpMbedTLS.cpp b/src/MicroOcpp/Core/FtpMbedTLS.cpp index e5ae0e36..bc6f1b9a 100644 --- a/src/MicroOcpp/Core/FtpMbedTLS.cpp +++ b/src/MicroOcpp/Core/FtpMbedTLS.cpp @@ -61,7 +61,7 @@ class FtpTransferMbedTLS : public FtpUpload, public FtpDownload, public MemoryMa bool read_url_ctrl(const char *ftp_url); bool read_url_data(const char *data_url); - + std::function fileWriter; std::function fileReader; std::function onClose; @@ -97,7 +97,7 @@ class FtpTransferMbedTLS : public FtpUpload, public FtpDownload, public MemoryMa ~FtpTransferMbedTLS(); void loop() override; - + bool isActive() override; bool getFile(const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename @@ -144,7 +144,7 @@ void mo_mbedtls_log(void *user, int level, const char *file, int line, const cha * - 2 State change * - 3 Informational * - 4 Verbose - * + * * To change the debug level, use the build flag MO_DBG_LEVEL_MBEDTLS accordingly */ @@ -169,7 +169,7 @@ void mo_mbedtls_log(void *user, int level, const char *file, int line, const cha * FTP implementation */ -FtpTransferMbedTLS::FtpTransferMbedTLS(bool tls_only, const char *client_cert, const char *client_key) : +FtpTransferMbedTLS::FtpTransferMbedTLS(bool tls_only, const char *client_cert, const char *client_key) : MemoryManaged("FTP.TransferMbedTLS"), client_cert(client_cert), client_key(client_key), @@ -327,7 +327,7 @@ int FtpTransferMbedTLS::connect_data() { if (isSecure) { //reuse SSL session of ctrl conn - if (auto ret = mbedtls_ssl_set_session(&data_ssl, + if (auto ret = mbedtls_ssl_set_session(&data_ssl, mbedtls_ssl_get_session_pointer(&ctrl_ssl))) { MO_DBG_ERR("session reuse failure: %i", ret); return ret; @@ -421,7 +421,7 @@ void FtpTransferMbedTLS::send_cmd(const char *cmd, const char *arg, bool disable const size_t MSG_SIZE = 128; unsigned char msg [MSG_SIZE]; - auto len = snprintf((char*) msg, MSG_SIZE, "%s%s%s\r\n", + auto len = snprintf((char*) msg, MSG_SIZE, "%s%s%s\r\n", cmd, //cmd mandatory (e.g. "USER") arg ? " " : "", //line spacing if arg is provided arg ? arg : ""); //arg optional (e.g. "anonymous") @@ -430,7 +430,7 @@ void FtpTransferMbedTLS::send_cmd(const char *cmd, const char *arg, bool disable len = sprintf((char*) msg, "QUIT\r\n"); } else { //show outgoing traffic for debug, but shadow PASS - MO_DBG_DEBUG("SEND: %s %s", + MO_DBG_DEBUG("SEND: %s %s", cmd, !strncmp((char*) cmd, "PASS", strlen("PASS")) ? "***" : arg ? (char*) arg : ""); } @@ -464,7 +464,7 @@ bool FtpTransferMbedTLS::getFile(const char *ftp_url_raw, std::function fileReader, std::function onClose, const char *ca_cert) { - + if (method != Method::UNDEFINED) { MO_DBG_ERR("FTP Client reuse not supported"); return false; @@ -607,7 +607,7 @@ void FtpTransferMbedTLS::process_ctrl() { } //security policy met - + if (!strncmp("530", line, 3) // Not logged in || !strncmp("220", line, 3) // Service ready for new user || !strncmp("234", line, 3)) { // Just completed AUTH TLS handshake @@ -941,7 +941,7 @@ std::unique_ptr FtpClientMbedTLS::getFile(const char *ftp_url_raw, } std::unique_ptr FtpClientMbedTLS::postFile(const char *ftp_url_raw, std::function fileReader, std::function onClose, const char *ca_cert) { - + auto ftp_handle = std::unique_ptr(new FtpTransferMbedTLS(tls_only, client_cert, client_key)); if (!ftp_handle) { MO_DBG_ERR("OOM"); diff --git a/src/MicroOcpp/Core/FtpMbedTLS.h b/src/MicroOcpp/Core/FtpMbedTLS.h index 6dfc6512..d7572b1b 100644 --- a/src/MicroOcpp/Core/FtpMbedTLS.h +++ b/src/MicroOcpp/Core/FtpMbedTLS.h @@ -9,16 +9,16 @@ * Built-in FTP client (depends on MbedTLS) * * Moved from https://github.com/matth-x/MicroFtp - * + * * Currently, the compatibility with the following FTP servers has been tested: - * + * * | Server | FTP | FTPS | * | --------------------------------------------------------------------- | --- | ---- | * | [vsftp](https://security.appspot.com/vsftpd.html) | | x | * | [Rebex](https://www.rebex.net/) | x | x | * | [Windows Server 2022](https://www.microsoft.com/en-us/windows-server) | x | x | * | [SFTPGo](https://github.com/drakkan/sftpgo) | x | | - * + * */ #include diff --git a/src/MicroOcpp/Core/Memory.h b/src/MicroOcpp/Core/Memory.h index bca25a84..7647c2a7 100644 --- a/src/MicroOcpp/Core/Memory.h +++ b/src/MicroOcpp/Core/Memory.h @@ -389,7 +389,7 @@ namespace MicroOcpp { class MemoryManaged { protected: const char *getMemoryTag() const {return nullptr;} - void updateMemoryTag(const char*,const char*) { } + void updateMemoryTag(const char*,const char*) { } public: MemoryManaged() { } MemoryManaged(const char*) { } diff --git a/src/MicroOcpp/Core/MessageService.cpp b/src/MicroOcpp/Core/MessageService.cpp index 7732cf3e..ffdf84e9 100644 --- a/src/MicroOcpp/Core/MessageService.cpp +++ b/src/MicroOcpp/Core/MessageService.cpp @@ -21,7 +21,7 @@ using namespace MicroOcpp; MessageService::MessageService(Context& context) : MemoryManaged("MessageService"), context(context), - operationRegistry(makeVector(getMemoryTag())), + operationRegistry(makeVector(getMemoryTag())), operationRegistry2(makeVector(getMemoryTag())), receiveRequestListeners(makeVector(getMemoryTag())), sendConfListeners(makeVector(getMemoryTag())) { @@ -63,7 +63,7 @@ void MessageService::loop() { /** * Send and dequeue a pending confirmation message, if existing - * + * * If a message has been sent, terminate this loop() function. */ @@ -79,7 +79,7 @@ void MessageService::loop() { if (ret == Request::CreateResponseResult::Success) { auto out = makeString(getMemoryTag()); serializeJson(response, out); - + bool success = connection->sendTXT(out.c_str(), out.length()); if (success) { @@ -359,7 +359,7 @@ bool MessageService::receiveMessage(const char* payload, size_t length) { if (capacity > MO_MAX_JSON_CAPACITY) { capacity = MO_MAX_JSON_CAPACITY; } - + auto doc = initJsonDoc(getMemoryTag()); DeserializationError err = DeserializationError::NoMemory; @@ -378,7 +378,7 @@ bool MessageService::receiveMessage(const char* payload, size_t length) { int messageTypeId = doc[0] | -1; if (messageTypeId == MESSAGE_TYPE_CALL) { - receiveRequest(doc.as()); + receiveRequest(doc.as()); success = true; } else if (messageTypeId == MESSAGE_TYPE_CALLRESULT || messageTypeId == MESSAGE_TYPE_CALLERROR) { @@ -387,7 +387,7 @@ bool MessageService::receiveMessage(const char* payload, size_t length) { } else { MO_DBG_WARN("Invalid OCPP message! (though JSON has successfully been deserialized)"); } - break; + break; } case DeserializationError::InvalidInput: MO_DBG_WARN("Invalid input! Not a JSON"); @@ -431,7 +431,7 @@ bool MessageService::receiveMessage(const char* payload, size_t length) { /** * call conf() on each element of the queue. Start with first element. On successful message delivery, * delete the element from the list. Try all the pending OCPP Operations until the right one is found. - * + * * This function could result in improper behavior in Charging Stations, because messages are not * guaranteed to be received and therefore processed in the right order. */ @@ -464,13 +464,13 @@ void MessageService::receiveRequest(JsonArray json, std::unique_ptr req /* * Tries to recover the Ocpp-Operation header from a broken message. - * - * Example input: + * + * Example input: * [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {"key":"now the message breaks... - * + * * The Json library returns an error code when trying to deserialize that broken message. This * function searches for the first occurence of the character '{' and writes "}]" after it. - * + * * Example output: * [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {}] * diff --git a/src/MicroOcpp/Core/MessageService.h b/src/MicroOcpp/Core/MessageService.h index aa73696e..a34a92af 100644 --- a/src/MicroOcpp/Core/MessageService.h +++ b/src/MicroOcpp/Core/MessageService.h @@ -79,7 +79,7 @@ class MessageService : public MemoryManaged { // send a Request to the OCPP server bool sendRequest(std::unique_ptr request); //send an OCPP operation request to the server; adds request to default queue bool sendRequestPreBoot(std::unique_ptr request); //send an OCPP operation request to the server; adds request to preBootQueue - + // handle Requests from the OCPP Server bool registerOperation(const char *operationType, Operation* (*createOperationCb)(Context& context)); bool registerOperation(const char *operationType, void (*onRequest)(const char *operationType, const char *payloadJson, void **userStatus, void *userData), int (*writeResponse)(const char *operationType, char *buf, size_t size, void *userStatus, void *userData), void (*finally)(const char *operationType, void *userStatus, void *userData) = nullptr, void *userData = nullptr); diff --git a/src/MicroOcpp/Core/Operation.cpp b/src/MicroOcpp/Core/Operation.cpp index 1289381b..8c618090 100644 --- a/src/MicroOcpp/Core/Operation.cpp +++ b/src/MicroOcpp/Core/Operation.cpp @@ -11,7 +11,7 @@ using namespace MicroOcpp; Operation::Operation() {} Operation::~Operation() {} - + const char* Operation::getOperationType(){ MO_DBG_ERR("Unsupported operation: getOperationType() is not implemented"); return "CustomOperation"; diff --git a/src/MicroOcpp/Core/Operation.h b/src/MicroOcpp/Core/Operation.h index 7ffbb7a3..8dbe8f8c 100644 --- a/src/MicroOcpp/Core/Operation.h +++ b/src/MicroOcpp/Core/Operation.h @@ -6,10 +6,10 @@ * This framework considers OCPP operations to be a combination of two things: first, ensure that the message reaches its * destination properly (the "Remote procedure call" header, e.g. message Id). Second, transmit the application data * as specified in the OCPP 1.6 document. - * + * * The remote procedure call (RPC) part is implemented by the class Request. The application data part is implemented by * the respective Operation subclasses, e.g. BootNotification, StartTransaction, ect. - * + * * The resulting structure is that the RPC header (=instance of Request) holds a reference to the payload * message creator (=instance of BootNotification, StartTransaction, ...). Both objects working together give the complete * OCPP operation. @@ -31,14 +31,14 @@ class Operation { Operation(); virtual ~Operation(); - + virtual const char* getOperationType(); /** * Create the payload for the respective OCPP message - * + * * For instance operation Authorize: creates Authorize.req(idTag) - * + * * This function is usually called multiple times by the Arduino loop(). On first call, the request is initially sent. In the * succeeding calls, the implementers decide to either recreate the request, or do nothing as the operation is still pending. */ @@ -46,11 +46,11 @@ class Operation { virtual void processConf(JsonObject payload); - + virtual void processErr(const char *code, const char *description, JsonObject details) {} /** - * Processes the request in the JSON document. + * Processes the request in the JSON document. */ virtual void processReq(JsonObject payload); diff --git a/src/MicroOcpp/Core/Request.cpp b/src/MicroOcpp/Core/Request.cpp index 7a2ea5e4..6c34822b 100644 --- a/src/MicroOcpp/Core/Request.cpp +++ b/src/MicroOcpp/Core/Request.cpp @@ -201,15 +201,15 @@ bool Request::receiveRequest(JsonArray request) { MO_DBG_ERR("malformatted msgId"); return false; } - + setMessageID(request[1].as()); - + /* * Hand the payload over to the Request object */ JsonObject payload = request[3]; operation->processReq(payload); - + /* * Hand the payload over to the first Callback. It is a callback that notifies the client that request has been processed in the OCPP-library */ diff --git a/src/MicroOcpp/Core/Request.h b/src/MicroOcpp/Core/Request.h index db2d5492..78597611 100644 --- a/src/MicroOcpp/Core/Request.h +++ b/src/MicroOcpp/Core/Request.h @@ -38,7 +38,7 @@ class Request : public MemoryManaged { Timestamp timeoutStart; int32_t timeoutPeriod = 40; //in seconds bool timedOut = false; - + Timestamp debugRequestTime; bool requestSent = false; @@ -57,9 +57,9 @@ class Request : public MemoryManaged { /** * Sends the message(s) that belong to the OCPP Operation. This function puts a JSON message on the lower protocol layer. - * + * * For instance operation Authorize: sends Authorize.req(idTag) - * + * * This function is usually called multiple times by the Arduino loop(). On first call, the request is initially sent. In the * succeeding calls, the implementers decide to either resend the request, or do nothing as the operation is still pending. */ @@ -76,7 +76,7 @@ class Request : public MemoryManaged { /** * Processes the request in the JSON document. Returns true on success, false on error. - * + * * Returns false if the request doesn't belong to the corresponding operation instance */ bool receiveRequest(JsonArray json); @@ -104,11 +104,11 @@ class Request : public MemoryManaged { * The listener onAbort will be called whenever the engine stops trying to execute an operation normally which were initiated * on this device. This includes timeouts or if the ocpp counterpart sends an error (then it will be called in addition to * onTimeout or onReceiveError, respectively). Causes for onAbort: - * + * * - Cannot create OCPP payload * - Timeout * - Receives error msg instead of confirmation msg - * + * * The engine uses this listener in both modes: EVSE mode and Central system mode */ void setOnAbort(AbortListener onAbort); diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index b4e063d7..58560223 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -33,7 +33,7 @@ int noDays(int month, int year) { using namespace MicroOcpp; Timestamp::Timestamp() : MemoryManaged("Timestamp") { - + } Timestamp::Timestamp(const Timestamp& other) : MemoryManaged("Timestamp") { @@ -478,7 +478,7 @@ bool Clock::parseString(const char *src, Timestamp& dst) const { //ignore subsequent characters return false; } - + int year = (src[0] - '0') * 1000 + (src[1] - '0') * 100 + (src[2] - '0') * 10 + @@ -500,7 +500,7 @@ bool Clock::parseString(const char *src, Timestamp& dst) const { if (isdigit(src[20]) || //1 isdigit(src[21]) || //2 isdigit(src[22])) { - + ms = (src[20] - '0') * 100 + (src[21] - '0') * 10 + (src[22] - '0'); @@ -594,7 +594,7 @@ bool Clock::toUnixTime(const Timestamp& src, int32_t& dst) const { } bool Clock::fromUnixTime(Timestamp& dst, int32_t unixTimeInt) const { - + Timestamp t = unixTime; t.time = unixTimeInt; diff --git a/src/MicroOcpp/Core/Time.h b/src/MicroOcpp/Core/Time.h index ec91ed16..49d673ef 100644 --- a/src/MicroOcpp/Core/Time.h +++ b/src/MicroOcpp/Core/Time.h @@ -79,14 +79,14 @@ class Clock { /** * Expects a date string like * 2020-10-01T20:53:32.486Z - * + * * as generated in JavaScript by calling toJSON() on a Date object - * + * * Only processes the first 23 characters. The subsequent are ignored - * + * * Has a semi-sophisticated type check included. Will return true on successful time set and false if * the given string is not a JSON Date string. - * + * * jsonDateString: 0-terminated string */ bool setTime(const char* jsonDateString); @@ -136,7 +136,7 @@ class Clock { */ bool parseString(const char *src, Timestamp& dst) const; - /* Converts src into a unix time and writes result to dst. dst will equal to src if src is already + /* Converts src into a unix time and writes result to dst. dst will equal to src if src is already * a unix time. Returns true if successful and false if the unix time cannot be determined */ bool toUnixTime(const Timestamp& src, Timestamp& dst) const; bool toUnixTime(const Timestamp& src, int32_t& dstInt) const; diff --git a/src/MicroOcpp/Core/UuidUtils.cpp b/src/MicroOcpp/Core/UuidUtils.cpp index ca76aa1f..a8ea0ce8 100644 --- a/src/MicroOcpp/Core/UuidUtils.cpp +++ b/src/MicroOcpp/Core/UuidUtils.cpp @@ -10,7 +10,7 @@ bool generateUUID(uint32_t (*rng)(), char *uuidBuffer, size_t size) { if (!rng) { return false; } - + if (size < MO_UUID_STR_SIZE) { return false; diff --git a/src/MicroOcpp/Debug.cpp b/src/MicroOcpp/Debug.cpp index 5e9cdcd1..99f7f1fc 100644 --- a/src/MicroOcpp/Debug.cpp +++ b/src/MicroOcpp/Debug.cpp @@ -22,7 +22,7 @@ Debug debug; } //namespace MicroOcpp using namespace MicroOcpp; - + void Debug::setDebugCb(void (*debugCb)(const char *msg)) { this->debugCb = debugCb; } diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp index 43325673..c58201a6 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationData.cpp @@ -96,10 +96,10 @@ bool AuthorizationData::readJson(Clock& clock, JsonObject entry, bool internalFo size_t AuthorizationData::getJsonCapacity(bool internalFormat) const { return JSON_OBJECT_SIZE(2) + - (idTag[0] != '\0' ? + (idTag[0] != '\0' ? JSON_OBJECT_SIZE(1) : 0) + (expiryDate ? - JSON_OBJECT_SIZE(1) + + JSON_OBJECT_SIZE(1) + (internalFormat ? MO_INTERNALTIME_SIZE : MO_JSONDATE_SIZE) : 0) + diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp index 86c89869..dcda7c0b 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp @@ -22,11 +22,11 @@ AuthorizationList::~AuthorizationList() { } MicroOcpp::v16::AuthorizationData *AuthorizationList::get(const char *idTag) { - + if (!idTag) { return nullptr; } - + //binary search int l = 0; int r = ((int) localAuthorizationListSize) - 1; @@ -97,7 +97,7 @@ bool AuthorizationList::readJson(Clock& clock, JsonArray authlistJson, int listV return false; } - + AuthorizationData **updateList = nullptr; //list of newly allocated entries AuthorizationData **resList = nullptr; //resulting list after list update. Contains pointers to old auth list and updateList @@ -208,16 +208,16 @@ bool AuthorizationList::readJson(Clock& clock, JsonArray authlistJson, int listV resWritten++; } } - + qsort(resList, resListSize, sizeof(resList[0]), [] (const void* a,const void* b) -> int { return strcmp( reinterpret_cast(a)->getIdTag(), reinterpret_cast(b)->getIdTag()); }); - + // success - + this->listVersion = listVersion; if (resListSize == 0) { diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp index 1a2c940d..5521381a 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp @@ -76,7 +76,7 @@ bool AuthorizationService::loadLists() { } int listVersion = doc["listVersion"] | 0; - + if (!localAuthorizationList.readJson(context.getClock(), doc["localAuthorizationList"].as(), listVersion, /*differential*/ false, /*internalFormat*/ true)) { MO_DBG_ERR("list read failure"); return false; diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp index 06d9dc8d..7182d11c 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.cpp +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -24,7 +24,7 @@ IdToken::IdToken(const char *token, MO_IdTokenType type, const char *memoryTag) } } -IdToken::IdToken(const IdToken& other, const char *memoryTag) : IdToken(other.idToken, other.type, memoryTag ? memoryTag : other.getMemoryTag()) { +IdToken::IdToken(const IdToken& other, const char *memoryTag) : IdToken(other.idToken, other.type, memoryTag ? memoryTag : other.getMemoryTag()) { } diff --git a/src/MicroOcpp/Model/Availability/AvailabilityDefs.h b/src/MicroOcpp/Model/Availability/AvailabilityDefs.h index 179c26f5..cac93922 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityDefs.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityDefs.h @@ -47,7 +47,7 @@ extern "C" { #endif //__cplusplus typedef enum MO_ChargePointStatus { - MO_ChargePointStatus_UNDEFINED, //internal use only - no OCPP standard value + MO_ChargePointStatus_UNDEFINED, //internal use only - no OCPP standard value MO_ChargePointStatus_Available, MO_ChargePointStatus_Preparing, MO_ChargePointStatus_Charging, diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index 3796ed06..f48bd2c8 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -233,7 +233,7 @@ MO_ChargePointStatus v16::AvailabilityServiceEvse::getStatus() { if (connectorPluggedInput && !connectorPluggedInput(evseId, connectorPluggedInputUserData)) { //special case when StopTransactionOnEVSideDisconnect is false res = MO_ChargePointStatus_SuspendedEV; } else if (!txServiceEvse->ocppPermitsCharge() || - (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData))) { + (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData))) { res = MO_ChargePointStatus_SuspendedEVSE; } else if (evReadyInput && !evReadyInput(evseId, evReadyInputUserData)) { res = MO_ChargePointStatus_SuspendedEV; @@ -245,7 +245,7 @@ MO_ChargePointStatus v16::AvailabilityServiceEvse::getStatus() { else if (model.getReservationService() && model.getReservationService()->getReservation(evseId)) { res = MO_ChargePointStatus_Reserved; } - #endif + #endif else if ((!transaction) && //no transaction process occupying the connector (!connectorPluggedInput || !connectorPluggedInput(evseId, connectorPluggedInputUserData)) && //no vehicle plugged (!occupiedInput || !occupiedInput(evseId, occupiedInputUserData))) { //occupied override clear @@ -351,7 +351,7 @@ Operation *v16::AvailabilityServiceEvse::createTriggeredStatusNotification() { } v16::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v16.Availability.AvailabilityService"), context(context) { - + } v16::AvailabilityService::~AvailabilityService() { @@ -597,7 +597,7 @@ bool v201::AvailabilityServiceEvse::addFaultedInput(FaultedInput faultedInput) { } v201::AvailabilityService::AvailabilityService(Context& context) : MemoryManaged("v201.Availability.AvailabilityService"), context(context) { - + } v201::AvailabilityService::~AvailabilityService() { diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.h b/src/MicroOcpp/Model/Availability/AvailabilityService.h index 24b5543b..2c35c7e3 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.h @@ -86,7 +86,7 @@ class AvailabilityServiceEvse : public MemoryManaged { void loop(); bool setup(); - + MO_ChargePointStatus getStatus(); void setAvailability(bool available); diff --git a/src/MicroOcpp/Model/Boot/BootNotificationData.h b/src/MicroOcpp/Model/Boot/BootNotificationData.h index 543e18c9..feb38fc0 100644 --- a/src/MicroOcpp/Model/Boot/BootNotificationData.h +++ b/src/MicroOcpp/Model/Boot/BootNotificationData.h @@ -21,11 +21,11 @@ extern "C" { * // init * MO_BootNotificationData bnData; * mo_BootNotificationData_init(&bnData); - * + * * // set payload * bnData.chargePointVendor = "My Company Ltd."; * bnData.chargePointModel = "Demo Charger"; - * + * * // pass data to MO * mo_setBootNotificationData2(bnData); //copies data, i.e. bnData can be invalidated now */ @@ -45,7 +45,7 @@ typedef struct { void mo_BootNotificationData_init(MO_BootNotificationData *bnData); #ifdef __cplusplus -} //extern "C" +} //extern "C" #endif #endif //MO_ENABLE_V16 || MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index f59bc2e2..df6dad02 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -225,7 +225,7 @@ void BootService::loop() { if (!clock.delta(clock.getUptime(), lastBootNotification, dtLastBootNotification)) { dtLastBootNotification = 0; } - + if (lastBootNotification.isDefined() && dtLastBootNotification < interval_s) { return; } diff --git a/src/MicroOcpp/Model/Boot/BootService.h b/src/MicroOcpp/Model/Boot/BootService.h index 8f945b12..f9c7df59 100644 --- a/src/MicroOcpp/Model/Boot/BootService.h +++ b/src/MicroOcpp/Model/Boot/BootService.h @@ -37,7 +37,7 @@ class PreBootQueue : public VolatileRequestQueue { bool activatedPostBootCommunication = false; public: unsigned int getFrontRequestOpNr() override; //override FrontRequestOpNr behavior: in PreBoot mode, always return 0 to avoid other RequestQueues from sending msgs - + void activatePostBootCommunication(); //end PreBoot mode, now send Requests normally }; @@ -73,7 +73,7 @@ class BootService : public MemoryManaged { char *bnDataBuf = nullptr; int ocppVersion = -1; - + #if MO_ENABLE_V16 v16::Configuration *preBootTransactionsBoolV16 = nullptr; #endif diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp index 2f12da5f..f502ead1 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp @@ -37,7 +37,7 @@ class CertificateStoreC : public CertificateStore, public MemoryManaged { } bool err = false; - + for (mo_cert_chain_hash *it = cch; it && !err; it = it->next) { out.emplace_back(); auto &chd_el = out.back(); diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.h b/src/MicroOcpp/Model/Certificates/Certificate_c.h index 0dbee5ca..06399db3 100644 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.h +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.h @@ -22,7 +22,7 @@ typedef struct mo_cert_chain_hash { enum GetCertificateIdType certType; mo_cert_hash certHashData; - //mo_cert_hash *childCertificateHashData; + //mo_cert_hash *childCertificateHashData; struct mo_cert_chain_hash *next; //link to next list element if result of getCertificateIds diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp index bd613f02..1e611426 100644 --- a/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp +++ b/src/MicroOcpp/Model/Configuration/ConfigurationContainer.cpp @@ -111,13 +111,13 @@ bool ConfigurationContainerOwning::setFilename(const char *filename) { if (filename && *filename) { size_t fnsize = strlen(filename) + 1; - + this->filename = static_cast(MO_MALLOC(getMemoryTag(), fnsize)); if (!this->filename) { MO_DBG_ERR("OOM"); return false; } - + snprintf(this->filename, fnsize, "%s", filename); } @@ -175,7 +175,7 @@ bool ConfigurationContainerOwning::load() { MO_DBG_ERR("loaded configuration does not exist: %s, %s", filename, key); continue; } - + auto& configuration = *configurationPtr; switch (configuration.getType()) { @@ -229,7 +229,7 @@ bool ConfigurationContainerOwning::commit() { auto stored = configurationsJson.createNestedObject(); stored["key"] = configuration.getKey(); - + switch (configuration.getType()) { case Configuration::Type::Int: stored["value"] = configuration.getInt(); diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp b/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp index 45ea0e1b..9aaa3fe5 100644 --- a/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp +++ b/src/MicroOcpp/Model/Configuration/ConfigurationService.cpp @@ -348,7 +348,7 @@ ConfigurationService::~ConfigurationService() { delete containersInternal[i]; } } - + bool ConfigurationService::init() { containers.reserve(1); if (containers.capacity() < 1) { diff --git a/src/MicroOcpp/Model/Configuration/ConfigurationService.h b/src/MicroOcpp/Model/Configuration/ConfigurationService.h index 55a26de9..a1c37a38 100644 --- a/src/MicroOcpp/Model/Configuration/ConfigurationService.h +++ b/src/MicroOcpp/Model/Configuration/ConfigurationService.h @@ -80,7 +80,7 @@ class ConfigurationService : public MemoryManaged { bool commit(); }; - + } //namespace v16 } //namespace MicroOcpp #endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 4cc79102..0fb7bd16 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -93,7 +93,7 @@ bool DiagnosticsService::setup() { MO_FREE(customProtocols); customProtocols = nullptr; } - + ocppVersion = context.getOcppVersion(); #if MO_ENABLE_V16 @@ -340,9 +340,9 @@ void DiagnosticsService::loop() { #if MO_ENABLE_V16 bool DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned int retries, unsigned int retryInterval, Timestamp startTime, Timestamp stopTime, char filenameOut[MO_GETLOG_FNAME_SIZE]) { - + bool success = false; - + auto ret = getLog(MO_LogType_DiagnosticsLog, -1, retries, retryInterval, location, startTime, stopTime, filenameOut); switch (ret) { case MO_GetLogStatus_Accepted: @@ -952,7 +952,7 @@ bool DiagnosticsService::startFtpUpload() { } if (writeLen + written > size || //heading doesn't fit anymore, return with a bit unused buffer space and print heading the next time writeLen + written == size) { //filling the buffer up exactly would mean that no file payload is written and this head gets printed again - + MO_DBG_DEBUG("upload diag chunk (%zuB)", written); filesystem->close(file); return written; diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp index d1786c23..fa8ff6f3 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp @@ -363,7 +363,7 @@ FirmwareStatus FirmwareService::getFirmwareStatus() { if (stage == UpdateStage::Installed) { return FirmwareStatus::Installed; } else if (stage == UpdateStage::InternalError) { - return FirmwareStatus::InstallationFailed; + return FirmwareStatus::InstallationFailed; } if (installationIssued) { @@ -377,7 +377,7 @@ FirmwareStatus FirmwareService::getFirmwareStatus() { if (onInstall != nullptr) return FirmwareStatus::Installing; } - + if (downloadIssued) { if (downloadStatusInput != nullptr) { if (downloadStatusInput() == DownloadStatus::Downloaded) { @@ -565,7 +565,7 @@ bool MicroOcpp::v16::setupDefaultFwUpdater(MicroOcpp::v16::FirmwareService *fwSe bool MicroOcpp::v16::setupDefaultFwUpdater(MicroOcpp::v16::FirmwareService *fwService) { fwService->setOnInstall([fwService] (const char *location) { - + MO_DBG_WARN("Built-in updater for ESP8266 is only intended for demonstration purposes. HTTP support only"); WiFiClient client; diff --git a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h index 30ed413e..fc1a2e96 100644 --- a/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h +++ b/src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h @@ -110,7 +110,7 @@ class FirmwareService : public MemoryManaged { * next chunk of FW data and `size` being the chunk size. `firmwareWriter` must return the number of bytes written, whereas * the result can be between 1 and `size`, and 0 aborts the download. MO executes `onClose` with the reason why the connection * has been closed. If the download hasn't been successful, MO will abort the update routine in any case. - * + * * Note that this function only works if MO_ENABLE_MBEDTLS=1, or MO has been configured with a custom FTP client */ void setDownloadFileWriter(std::function firmwareWriter, std::function onClose); diff --git a/src/MicroOcpp/Model/Metering/MeterValue.cpp b/src/MicroOcpp/Model/Metering/MeterValue.cpp index a63a3153..e957c85a 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValue.cpp @@ -73,7 +73,7 @@ SampledValue::Type SampledValue::getType() { } MeterValue::MeterValue() : - MemoryManaged("v16.Metering.MeterValue"), + MemoryManaged("v16.Metering.MeterValue"), sampledValue(makeVector(getMemoryTag())) { } @@ -100,7 +100,7 @@ bool loadSignedValue(MO_SignedMeterValue201* signedValue, const char *signedMete signingMethodSize + encodingMethodSize + publicKeySize; - + char *buf = static_cast(MO_MALLOC(memoryTag, bufsize)); if (!buf) { MO_DBG_ERR("OOM"); @@ -116,7 +116,7 @@ bool loadSignedValue(MO_SignedMeterValue201* signedValue, const char *signedMete MO_DBG_ERR("snprintf: %i", ret); goto fail; } - + written += (size_t)ret + 1; } @@ -325,7 +325,7 @@ bool MeterValue::parseJson(Clock& clock, Vector& meterInputs, Jso } int MeterValue::getJsonCapacity(int ocppVersion, bool internalFormat) { - + size_t capacity = 0; capacity += JSON_OBJECT_SIZE(2); //timestamp, sampledValue @@ -566,7 +566,7 @@ bool MeterValue::toJson(Clock& clock, int ocppVersion, bool internalFormat, Json } else { unitJson = svJson.createNestedObject("unitOfMeasure"); } - + if (meterDevice.unit && strcmp(meterDevice.unit, "Wh")) { unitJson["unit"] = meterDevice.unit; } diff --git a/src/MicroOcpp/Model/Metering/MeterValue.h b/src/MicroOcpp/Model/Metering/MeterValue.h index f9e8bd2b..405f1048 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.h +++ b/src/MicroOcpp/Model/Metering/MeterValue.h @@ -80,7 +80,7 @@ typedef struct { #if MO_ENABLE_V201 //if signed value exists, fills `val` with the signature data and returns true. If no signed - //data exists, returns false. + //data exists, returns false. bool (*getSignedValue2)(MO_SignedMeterValue201* val, MO_ReadingContext readingContext, unsigned int evseId, void *user_data); #endif //MO_ENABLE_V201 }; diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index a4056eaa..92308b37 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -366,7 +366,7 @@ bool v16::MeteringServiceEvse::setup() { (!mInput.location || !strcmp(mInput.location, "Outlet")) && (!mInput.unit || !strcmp(mInput.unit, "Wh")) && (mInput.type == MO_MeterInputType_Int || mInput.type == MO_MeterInputType_IntWithArgs)) { - + if (mInput.type == MO_MeterInputType_Int) { txEnergyMeterInput = mInput.getInt; } else { diff --git a/src/MicroOcpp/Model/Metering/MeteringService.h b/src/MicroOcpp/Model/Metering/MeteringService.h index f24c54ba..49da11ed 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.h +++ b/src/MicroOcpp/Model/Metering/MeteringService.h @@ -152,7 +152,7 @@ class MeteringServiceEvse : public MemoryManaged { Context& context; MeteringService& mService; const unsigned int evseId; - + Vector meterInputs; uint16_t trackSelectInputs = -1; diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index dc1767b7..437e9cb4 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -316,7 +316,7 @@ bool v16::Model::setup() { MO_DBG_ERR("setup failure"); return false; } - + if (!getResetService() || !getResetService()->setup()) { MO_DBG_ERR("setup failure"); return false; @@ -392,7 +392,7 @@ bool v16::Model::setup() { // Register remainder of operations which don't have dedicated service context.getMessageService().registerOperation("DataTransfer", [] (Context&) -> Operation* { return new v16::DataTransfer();}); - + return true; } diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index d801f7e4..55f444ec 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -172,15 +172,15 @@ bool RemoteControlService::setup() { MO_DBG_ERR("setup failure"); return false; } - + configService->declareConfiguration("AuthorizeRemoteTxRequests", false); - + #if MO_ENABLE_CONNECTOR_LOCK configService->declareConfiguration("UnlockConnectorOnEVSideDisconnect", true); //read-write #else configService->declareConfiguration("UnlockConnectorOnEVSideDisconnect", false, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); //read-only because there is no connector lock #endif //MO_ENABLE_CONNECTOR_LOCK - + txService16 = context.getModel16().getTransactionService(); if (!txService16) { MO_DBG_ERR("setup failure"); @@ -359,7 +359,7 @@ v16::RemoteStartStopStatus RemoteControlService::remoteStartTransaction(int conn if (success) { if (auto transaction = selectEvse->getTransaction()) { selectEvse->updateTxNotification(MO_TxNotification_RemoteStart); - + if (chargingProfileId >= 0) { transaction->setTxProfileId(chargingProfileId); } @@ -411,7 +411,7 @@ v16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int trans #if MO_ENABLE_V201 v201::RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, v201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize) { - + if (!txService201) { MO_DBG_ERR("TxService uninitialized"); return v201::RequestStartStopStatus::Rejected; diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h index 5de2cc15..c5857524 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -108,7 +108,7 @@ class RemoteControlService : public MemoryManaged { bool setup(); #if MO_ENABLE_V16 - v16::RemoteStartStopStatus remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile); + v16::RemoteStartStopStatus remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile); v16::RemoteStartStopStatus remoteStopTransaction(int transactionId); #endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Model/Reservation/Reservation.cpp b/src/MicroOcpp/Model/Reservation/Reservation.cpp index 7474d965..82a9d826 100644 --- a/src/MicroOcpp/Model/Reservation/Reservation.cpp +++ b/src/MicroOcpp/Model/Reservation/Reservation.cpp @@ -59,7 +59,7 @@ bool Reservation::setup() { return false; } expiryDateRawString = configService->declareConfiguration(expiryDateRawKey, "", RESERVATION_FN, Mutability::None); - + ret = snprintf(idTagKey, sizeof(idTagKey), MO_RESERVATION_IDTAG_KEY "%u", slot); if (ret < 0 || (size_t)ret >= sizeof(idTagKey)) { return false; @@ -160,7 +160,7 @@ const char *Reservation::getParentIdTag() { bool Reservation::update(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag) { bool success = true; - + reservationIdInt->setInt(reservationId); connectorIdInt->setInt((int) connectorId); this->expiryDate = expiryDate; diff --git a/src/MicroOcpp/Model/Reservation/ReservationService.cpp b/src/MicroOcpp/Model/Reservation/ReservationService.cpp index ec05ec48..44029f60 100644 --- a/src/MicroOcpp/Model/Reservation/ReservationService.cpp +++ b/src/MicroOcpp/Model/Reservation/ReservationService.cpp @@ -99,7 +99,7 @@ void ReservationService::loop() { auto transaction = txSvcEvse ? txSvcEvse->getTransaction() : nullptr; if (transaction && transaction->isAuthorized()) { const char *idTag = transaction->getIdTag(); - if (transaction->getReservationId() == reservations[i]->getReservationId() || + if (transaction->getReservationId() == reservations[i]->getReservationId() || (idTag && !strcmp(idTag, reservations[i]->getIdTag()))) { reservations[i]->clear(); @@ -121,7 +121,7 @@ Reservation *ReservationService::getReservation(unsigned int connectorId) { return reservations[i]; } } - + return nullptr; } @@ -229,7 +229,7 @@ bool ReservationService::updateReservation(int reservationId, unsigned int conne } // Alternative condition: avoids that one idTag can make two reservations at a time. The specification doesn't -// mention that double-reservations should be possible but it seems to mean it. +// mention that double-reservations should be possible but it seems to mean it. if (auto reservation = getReservation(connectorId, nullptr, nullptr)) { // payload["idTag"], // payload.containsKey("parentIdTag") ? payload["parentIdTag"] : nullptr)) { diff --git a/src/MicroOcpp/Model/Reset/ResetDefs.h b/src/MicroOcpp/Model/Reset/ResetDefs.h index 154cb30d..78026435 100644 --- a/src/MicroOcpp/Model/Reset/ResetDefs.h +++ b/src/MicroOcpp/Model/Reset/ResetDefs.h @@ -21,4 +21,4 @@ typedef enum ResetStatus { } ResetStatus; #endif //MO_ENABLE_V201 -#endif +#endif diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index 77f838fc..4ecbb286 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -48,7 +48,7 @@ v16::ResetService::ResetService(Context& context) v16::ResetService::~ResetService() { } - + bool v16::ResetService::setup() { auto configService = context.getModel16().getConfigurationService(); @@ -77,7 +77,7 @@ bool v16::ResetService::setup() { context.getMessageService().registerOperation("Reset", [] (Context& context) -> Operation* { return new Reset(*context.getModel16().getResetService());}); - + return true; } @@ -87,7 +87,7 @@ void v16::ResetService::loop() { int32_t dtLastResetAttempt; if (!clock.delta(clock.getUptime(), lastResetAttempt, dtLastResetAttempt)) { - dtLastResetAttempt = MO_RESET_DELAY; + dtLastResetAttempt = MO_RESET_DELAY; } if (outstandingResetRetries > 0 && dtLastResetAttempt >= MO_RESET_DELAY) { @@ -218,7 +218,7 @@ bool v201::ResetService::setup() { context.getMessageService().registerOperation("Reset", [] (Context& context) -> Operation* { return new Reset(*context.getModel201().getResetService());}); - + return true; } @@ -263,7 +263,7 @@ void v201::ResetService::Evse::loop() { int32_t dtLastResetAttempt; if (!clock.delta(clock.getUptime(), lastResetAttempt, dtLastResetAttempt)) { - dtLastResetAttempt = MO_RESET_DELAY; + dtLastResetAttempt = MO_RESET_DELAY; } if (outstandingResetRetries && dtLastResetAttempt >= MO_RESET_DELAY) { diff --git a/src/MicroOcpp/Model/Reset/ResetService.h b/src/MicroOcpp/Model/Reset/ResetService.h index e54855c9..23f1de64 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.h +++ b/src/MicroOcpp/Model/Reset/ResetService.h @@ -45,7 +45,7 @@ class ResetService : public MemoryManaged { void setExecuteReset(bool (*executeReset)(bool isHard, void *userData), void *userData); bool setup(); - + void loop(); bool isPreResetDefined(); @@ -109,7 +109,7 @@ class ResetService : public MemoryManaged { void setExecuteReset(unsigned int evseId, bool (*executeReset)(unsigned int evseId, void *userData), void *userData); bool setup(); - + void loop(); ResetStatus initiateReset(MO_ResetType type, unsigned int evseId = 0); diff --git a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp index df6f9a7a..7df21053 100644 --- a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp +++ b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp @@ -31,7 +31,7 @@ * section. The first element of the historic section is the "begin", the first of the outstanding is the "front", * and the outstanding section is delimited by the "end" index which is one address higher than the last element. * This results in the following arithmetics: - * + * * front - begin: number of historic elements * front == begin: true if no historic elements exist * end - front: number of outstanding elements to be sent @@ -142,7 +142,7 @@ bool SecurityEventService::setup() { } if (status != FilesystemUtils::LoadStatus::Success) { - continue; + continue; } unsigned int nextEntryNrFront = (unsigned int)logFile.size(); @@ -188,7 +188,7 @@ void SecurityEventService::loop() { if (unixTime.isUnixTime() && trackUnixTime.isUnixTime()) { // Got initial time - check if clock drift exceeds timeAdjustmentReportingThreshold - + int32_t deltaUnixTime = 0; //unix time jumps over clock adjustments clock.delta(unixTime, trackUnixTime, deltaUnixTime); @@ -427,12 +427,12 @@ bool SecurityEventService::advanceSecurityEventFront() { if (status != FilesystemUtils::LoadStatus::Success) { continue; } - + if (logFile.size() == 0) { MO_DBG_ERR("invalid state: %s. Skpping logfile", fn); continue; } - + unsigned int nextEntryNrFront = (unsigned int)logFile.size(); unsigned int nextEntryNrEnd = (unsigned int)logFile.size(); @@ -444,7 +444,7 @@ bool SecurityEventService::advanceSecurityEventFront() { break; } } - + if (nextEntryNrFront == nextEntryNrEnd) { MO_DBG_ERR("invalid state: %s. Skpping logfile", fn); continue; @@ -526,7 +526,7 @@ bool SecurityEventService::triggerSecurityEvent(const char *eventType) { unsigned int historicFiles = (fileNrFront + MO_SECLOG_INDEX_MAX - fileNrBegin) % MO_SECLOG_INDEX_MAX; if (historicFiles + outstandingFiles > MO_SECLOG_MAX_FILES) { MO_DBG_ERR("Cleaning historic logfile"); - + char fn [MO_MAX_PATH_SIZE]; auto ret = snprintf(fn, sizeof(fn), MO_SECLOG_FN_PREFIX "%u.jsn", fileNrBegin); if (ret < 0 || (size_t)ret >= sizeof(fn)) { @@ -607,7 +607,7 @@ bool SecurityEventService::triggerSecurityEvent(const char *eventType) { // Successfully updated end of queue on flash. Update fileNrEnd (remains unchanged when SecurityEvent was appended to existing file) fileNrEnd = (fileNrBack + 1) % MO_SECLOG_INDEX_MAX; - + // If current front logfile was updated, then need to update cached SecurityEvent counter if (fileNrBack == fileNrFront) { entryNrEnd++; diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp index a2365bff..49f271d5 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp @@ -147,7 +147,7 @@ bool ChargingSchedule::calculateLimit(int32_t unixTime, int32_t sessionDurationS switch (chargingProfileKind) { case ChargingProfileKindType::Absolute: if (unixTime == 0) { - //cannot use Absolute profiles when + //cannot use Absolute profiles when MO_DBG_WARN("Need to set clock before using absolute profiles"); return false; } @@ -500,7 +500,7 @@ bool ChargingProfile::parseJson(Clock& clock, int ocppVersion, JsonObject json) chargingProfileId = json["id"] | -1; } #endif //MO_ENABLE_V201 - + if (chargingProfileId < 0) { MO_DBG_WARN("format violation"); return false; diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h index 3ef5db02..b7ac31e7 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h @@ -124,7 +124,7 @@ class ChargingSchedule : public MemoryManaged { /** * limit: output parameter * nextChange: output parameter - * + * * returns if charging profile defines a limit at time t * if true, limit and nextChange will be set according to this Schedule * if false, only nextChange will be set @@ -163,7 +163,7 @@ class ChargingProfile : public MemoryManaged { /** * limit: output parameter * nextChange: output parameter - * + * * returns if charging profile defines a limit at time t * if true, limit and nextChange will be set according to this Schedule * if false, only nextChange will be set diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index da0321e7..1a316499 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -29,14 +29,14 @@ namespace SmartChargingServiceUtils { bool printProfileFileName(char *out, size_t bufsize, unsigned int evseId, ChargingProfilePurposeType purpose, unsigned int stackLevel); bool storeProfile(MO_FilesystemAdapter *filesystem, Clock& clock, int ocppVersion, unsigned int evseId, ChargingProfile *chargingProfile); bool removeProfile(MO_FilesystemAdapter *filesystem, unsigned int evseId, ChargingProfilePurposeType purpose, unsigned int stackLevel); -} //namespace SmartChargingServiceUtils +} //namespace SmartChargingServiceUtils } //namespace MicroOcpp using namespace::MicroOcpp; SmartChargingServiceEvse::SmartChargingServiceEvse(Context& context, SmartChargingService& scService, unsigned int evseId) : MemoryManaged("v16/v201.SmartCharging.SmartChargingServiceEvse"), context(context), clock(context.getClock()), scService(scService), evseId(evseId) { - + mo_chargeRate_init(&trackLimitOutput); } @@ -330,7 +330,7 @@ void SmartChargingServiceEvse::setSmartChargingOutput(void (*limitOutput)(MO_Cha } bool SmartChargingServiceEvse::updateProfile(std::unique_ptr chargingProfile, bool updateFile) { - + ChargingProfile **stack = nullptr; switch (chargingProfile->chargingProfilePurpose) { @@ -348,14 +348,14 @@ bool SmartChargingServiceEvse::updateProfile(std::unique_ptr ch MO_DBG_ERR("invalid args"); return false; } - + if (updateFile && scService.filesystem) { if (!SmartChargingServiceUtils::storeProfile(scService.filesystem, clock, scService.ocppVersion, evseId, chargingProfile.get())) { MO_DBG_ERR("fs error"); return false; } } - + int stackLevel = chargingProfile->stackLevel; //already validated @@ -391,7 +391,7 @@ bool SmartChargingServiceEvse::clearChargingProfile(int chargingProfileId, Charg if (stackLevel >= 0 && (size_t)stackLevel != sLvl) { continue; } - + if (!stack[sLvl]) { // no profile installed at this stack and stackLevel continue; @@ -415,7 +415,7 @@ bool SmartChargingServiceEvse::clearChargingProfile(int chargingProfileId, Charg } std::unique_ptr SmartChargingServiceEvse::getCompositeSchedule(int duration, ChargingRateUnitType unit) { - + int32_t nowUnixTime; if (!clock.toUnixTime(clock.now(), nowUnixTime)) { MO_DBG_ERR("internal error"); @@ -845,7 +845,7 @@ size_t SmartChargingService::getChargingProfilesCount() { * validToOut: The begin of the next SmartCharging restriction after time t */ void SmartChargingService::calculateLimit(int32_t unixTime, MO_ChargeRate& limitOut, int32_t& nextChangeSecsOut){ - + //initialize output parameters with the default values mo_chargeRate_init(&limitOut); nextChangeSecsOut = 365 * 24 * 3600; @@ -995,12 +995,12 @@ bool SmartChargingService::clearChargingProfile(int chargingProfileId, int evseI } else if (purpose == ChargingProfilePurposeType::TxDefaultProfile) { stack = chargePointTxDefaultProfile; } - + for (size_t sLvl = 0; sLvl < MO_CHARGEPROFILESTACK_SIZE; sLvl++) { if (stackLevel >= 0 && (size_t)stackLevel != sLvl) { continue; } - + if (!stack[sLvl]) { // no profile installed at this stack and stackLevel continue; diff --git a/src/MicroOcpp/Model/Transactions/Transaction.cpp b/src/MicroOcpp/Model/Transactions/Transaction.cpp index bbb8890c..5239bf3a 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.cpp +++ b/src/MicroOcpp/Model/Transactions/Transaction.cpp @@ -10,9 +10,9 @@ using namespace MicroOcpp; -v16::Transaction::Transaction(unsigned int connectorId, unsigned int txNr, bool silent) : +v16::Transaction::Transaction(unsigned int connectorId, unsigned int txNr, bool silent) : MemoryManaged("v16.Transactions.Transaction"), - connectorId(connectorId), + connectorId(connectorId), txNr(txNr), silent(silent), meterValues(makeVector("v16.Transactions.TransactionMeterData")) { } diff --git a/src/MicroOcpp/Model/Transactions/Transaction.h b/src/MicroOcpp/Model/Transactions/Transaction.h index 5ac52758..ed92994e 100644 --- a/src/MicroOcpp/Model/Transactions/Transaction.h +++ b/src/MicroOcpp/Model/Transactions/Transaction.h @@ -31,8 +31,8 @@ namespace v16 { * A transaction is initiated by the client (charging station) and processed by the server (central system). * The client side of a transaction is all data that is generated or collected at the charging station. The * server side is all transaction data that is assigned by the central system. - * - * See OCPP 1.6 Specification - Edition 2, sections 3.6, 4.8, 4.10 and 5.11. + * + * See OCPP 1.6 Specification - Edition 2, sections 3.6, 4.8, 4.10 and 5.11. */ class TransactionStoreEvse; @@ -41,7 +41,7 @@ class SendStatus { private: bool requested = false; bool confirmed = false; - + unsigned int opNr = 0; unsigned int attemptNr = 0; Timestamp attemptTime; @@ -345,7 +345,7 @@ class TransactionEventData : public MemoryManaged { std::unique_ptr idToken; EvseId evse = -1; Vector> meterValue; - + unsigned int opNr = 0; unsigned int attemptNr = 0; Timestamp attemptTime; diff --git a/src/MicroOcpp/Model/Transactions/TransactionDefs.h b/src/MicroOcpp/Model/Transactions/TransactionDefs.h index 17dbdfc2..45d1024a 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionDefs.h +++ b/src/MicroOcpp/Model/Transactions/TransactionDefs.h @@ -91,6 +91,6 @@ typedef enum { #ifdef __cplusplus } //extern "C" -#endif +#endif #endif diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index 178d2bbe..f94f1d66 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -156,7 +156,7 @@ bool TransactionServiceEvse::ocppPermitsCharge() { } bool suspendDeAuthorizedIdTag = transaction && transaction->isIdTagDeauthorized(); //if idTag status is "DeAuthorized" and if charging should stop - + //check special case for DeAuthorized idTags: FreeVend mode if (suspendDeAuthorizedIdTag && cService.freeVendActiveBool->getBool()) { suspendDeAuthorizedIdTag = false; @@ -230,7 +230,7 @@ void TransactionServiceEvse::loop() { } if (transaction) { //begin exclusively transaction-related operations - + if (connectorPluggedInput) { if (transaction->isRunning() && transaction->isActive() && !connectorPluggedInput(evseId, connectorPluggedInputUserData)) { if (cService.stopTransactionOnEVSideDisconnectBool->getBool()) { @@ -264,7 +264,7 @@ void TransactionServiceEvse::loop() { transaction->isIdTagDeauthorized() && ( //transaction has been deAuthorized !transaction->isRunning() || //if transaction hasn't started yet, always end cService.stopTransactionOnInvalidIdBool->getBool())) { //if transaction is running, behavior depends on StopTransactionOnInvalidId - + MO_DBG_DEBUG("DeAuthorize session"); transaction->setStopReason("DeAuthorized"); transaction->setInactive(); @@ -322,7 +322,7 @@ void TransactionServiceEvse::loop() { //stop transaction MO_DBG_INFO("Session mngt: trigger StopTransaction"); - + if (transaction->getMeterStop() < 0) { auto meterStop = meteringServiceEvse->readTxEnergyMeter(MO_ReadingContext_TransactionEnd); transaction->setMeterStop(meterStop); @@ -368,7 +368,7 @@ void TransactionServiceEvse::loop() { freeVendTrackPlugged = connectorPluggedInput(evseId, connectorPluggedInputUserData); } - + return; } @@ -554,7 +554,7 @@ bool TransactionServiceEvse::beginTransaction(const char *idTag) { } MO_DBG_DEBUG("Begin transaction process (%s), prepare", idTag != nullptr ? idTag : ""); - + bool localAuthFound = false; const char *parentIdTag = nullptr; //locally stored parentIdTag bool offlineBlockedAuth = false; //if offline authorization will be blocked by local auth list entry @@ -731,8 +731,8 @@ bool TransactionServiceEvse::beginTransaction(const char *idTag) { //capture local auth and reservation check in for timeout handler authorize->setOnTimeout([this, txNr_capture, beginTimestamp_capture, - offlineBlockedAuth, - offlineBlockedResv, + offlineBlockedAuth, + offlineBlockedResv, localAuthFound, reservationId] () { @@ -799,7 +799,7 @@ bool TransactionServiceEvse::beginTransaction(const char *idTag) { } bool TransactionServiceEvse::beginTransaction_authorized(const char *idTag, const char *parentIdTag) { - + if (transaction) { MO_DBG_WARN("tx process still running. Please call endTransaction(...) before"); return false; @@ -824,7 +824,7 @@ bool TransactionServiceEvse::beginTransaction_authorized(const char *idTag, cons } transaction->setBeginTimestamp(clock.now()); - + MO_DBG_DEBUG("Begin transaction process (%s), already authorized", idTag != nullptr ? idTag : ""); transaction->setAuthorized(); @@ -864,7 +864,7 @@ bool TransactionServiceEvse::endTransaction(const char *idTag, const char *reaso // We have a parent ID tag, so we need to check if this new card also has one auto authorize = makeRequest(context, new Authorize(model, idTag)); authorize->setTimeout(cService.authorizationTimeoutInt->getInt() > 0 ? cService.authorizationTimeoutInt->getInt(): 20); - + if (!connection->isConnected()) { //WebSockt unconnected. Enter offline mode immediately authorize->setTimeout(1); @@ -954,7 +954,7 @@ bool TransactionServiceEvse::endTransaction(const char *idTag, const char *reaso MO_DBG_INFO("endTransaction: idTag doesn't match"); return false; } - + return false; } @@ -967,11 +967,11 @@ bool TransactionServiceEvse::endTransaction_authorized(const char *idTag, const MO_DBG_DEBUG("End session started by idTag %s", transaction->getIdTag()); - + if (idTag && *idTag != '\0') { transaction->setStopIdTag(idTag); } - + if (reason) { transaction->setStopReason(reason); } diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.h b/src/MicroOcpp/Model/Transactions/TransactionService16.h index 4c38022d..68b17672 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.h +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.h @@ -90,7 +90,7 @@ class TransactionServiceEvse : public RequestQueue, public MemoryManaged { /* * beginTransaction begins the transaction process which eventually leads to a StartTransaction * request in the normal case. - * + * * Returns true if the transaction process begins successfully * Returns false if no transaction process begins due to this call (e.g. other transaction still running) */ @@ -107,7 +107,7 @@ class TransactionServiceEvse : public RequestQueue, public MemoryManaged { Transaction *getTransaction(); - Transaction *allocateTransaction(); + Transaction *allocateTransaction(); void setConnectorPluggedInput(bool (*connectorPlugged)(unsigned int, void*), void *userData); void setEvReadyInput(bool (*evReady)(unsigned int, void*), void *userData); diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index 1c46bcf4..5c2460b4 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -244,7 +244,7 @@ TransactionEventData::ChargingState TransactionServiceEvse::getChargingState() { res = TransactionEventData::ChargingState::Idle; } else if (!transaction || !transaction->isAuthorizationActive || !transaction->isAuthorized) { res = TransactionEventData::ChargingState::EVConnected; - } else if (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData)) { + } else if (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData)) { res = TransactionEventData::ChargingState::SuspendedEVSE; } else if (evReadyInput && !evReadyInput(evseId, evReadyInputUserData)) { res = TransactionEventData::ChargingState::SuspendedEV; @@ -305,7 +305,7 @@ void TransactionServiceEvse::loop() { !transaction->started && (txService.isTxStartPoint(TxStartStopPoint::Authorized) || txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) || txService.isTxStopPoint(TxStartStopPoint::Authorized) || txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed))) { - + MO_DBG_INFO("Session mngt: Deauthorized before start"); endTransaction(MO_TxStoppedReason_DeAuthorized, MO_TxEventTriggerReason_Deauthorized); } @@ -386,8 +386,8 @@ void TransactionServiceEvse::loop() { txEvent->eventType = TransactionEventData::Type::Ended; txEvent->triggerReason = triggerReason; } - } - + } + if (!txStopCondition) { // start tx? @@ -774,7 +774,7 @@ bool TransactionServiceEvse::endAuthorization(IdToken idToken, bool validateIdTo MO_DBG_DEBUG("End session started by idTag %s", transaction->idToken.get()); - + if (transaction->idToken.equals(idToken)) { // use same idToken like tx start transaction->isAuthorizationActive = false; @@ -1250,7 +1250,7 @@ bool TransactionService::setup() { if (!txSvcEvse) { return TriggerMessageStatus::ERR_INTERNAL; } - + auto ret = txSvcEvse->triggerTransactionEvent(); bool abortLoop = false; @@ -1292,7 +1292,7 @@ bool TransactionService::setup() { evses[0]->evReadyInput = disabledInput; evses[0]->evseReadyInput = disabledInput; } - + return true; } @@ -1314,7 +1314,7 @@ void TransactionService::loop() { //pending tx on evseId 0 if (evses[0]->transaction->active) { for (unsigned int evseId = 1; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) { - if (!evses[evseId]->getTransaction() && + if (!evses[evseId]->getTransaction() && (!evses[evseId]->connectorPluggedInput || evses[evseId]->connectorPluggedInput(evseId, evses[evseId]->connectorPluggedInputUserData))) { MO_DBG_INFO("assign tx to evse %u", evseId); evses[0]->transaction->notifyEvseId = true; diff --git a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp index df8b6696..a79dc3e9 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionStore.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionStore.cpp @@ -68,7 +68,7 @@ FilesystemUtils::LoadStatus v16::TransactionStore::load(MO_FilesystemAdapter *fi } FilesystemUtils::StoreStatus v16::TransactionStore::store(MO_FilesystemAdapter *filesystem, Context& context, Transaction& transaction) { - + char fname [MO_MAX_PATH_SIZE]; if (!printTxFname(fname, sizeof(fname), transaction.getConnectorId(), transaction.getTxNr())) { MO_DBG_ERR("fname error %u %u", transaction.getConnectorId(), transaction.getTxNr()); @@ -531,7 +531,7 @@ bool v201::TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObj if (txJson.containsKey("idToken")) { IdToken idToken; if (!idToken.parseCstr( - txJson["idToken"]["idToken"] | (const char*)nullptr, + txJson["idToken"]["idToken"] | (const char*)nullptr, txJson["idToken"]["type"] | (const char*)nullptr)) { return false; } @@ -590,7 +590,7 @@ bool v201::TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObj return false; } if (!stopIdToken->parseCstr( - txJson["stopIdToken"]["idToken"] | (const char*)nullptr, + txJson["stopIdToken"]["idToken"] | (const char*)nullptr, txJson["stopIdToken"]["type"] | (const char*)nullptr)) { return false; } @@ -617,7 +617,7 @@ bool v201::TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObj } bool v201::TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) { - + if (txEvent.eventType != TransactionEventData::Type::Updated) { txEventJson["eventType"] = serializeTransactionEventType(txEvent.eventType); } @@ -633,7 +633,7 @@ bool v201::TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& if (serializeTxEventTriggerReason(txEvent.triggerReason)) { txEventJson["triggerReason"] = serializeTxEventTriggerReason(txEvent.triggerReason); } - + if (txEvent.offline) { txEventJson["offline"] = true; } @@ -676,7 +676,7 @@ bool v201::TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEventJson["opNr"] = txEvent.opNr; txEventJson["attemptNr"] = txEvent.attemptNr; - + if (txEvent.attemptTime.isDefined()) { char timeStr [MO_JSONDATE_SIZE] = {'\0'}; if (!context.getClock().toInternalString(txEvent.attemptTime, timeStr, sizeof(timeStr))) { @@ -707,7 +707,7 @@ bool v201::TransactionStoreEvse::deserializeTransactionEvent(TransactionEventDat return false; } txEvent.triggerReason = triggerReason; - + if (txEventJson.containsKey("offline") && !txEventJson["offline"].is()) { return false; } @@ -758,7 +758,7 @@ bool v201::TransactionStoreEvse::deserializeTransactionEvent(TransactionEventDat return false; } if (!idToken->parseCstr( - txEventJson["idToken"]["idToken"] | (const char*)nullptr, + txEventJson["idToken"]["idToken"] | (const char*)nullptr, txEventJson["idToken"]["type"] | (const char*)nullptr)) { return false; } diff --git a/src/MicroOcpp/Model/Variables/Variable.h b/src/MicroOcpp/Model/Variables/Variable.h index 8042168d..1c5c576f 100644 --- a/src/MicroOcpp/Model/Variables/Variable.h +++ b/src/MicroOcpp/Model/Variables/Variable.h @@ -215,7 +215,7 @@ class Variable : public MemoryManaged { bool isConstant(); //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) }; diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.cpp b/src/MicroOcpp/Model/Variables/VariableContainer.cpp index b4a3f1e0..66649644 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.cpp +++ b/src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -146,7 +146,7 @@ bool VariableContainerOwning::load() { MO_DBG_ERR("failed to load %s", filename); return false; } - + JsonArray variablesJson = doc["variables"]; for (JsonObject stored : variablesJson) { @@ -259,7 +259,7 @@ bool VariableContainerOwning::commit() { stored["evseId"] = variable.getComponentId().evse.id; } stored["name"] = variable.getName(); - + switch (variable.getInternalDataType()) { case Variable::InternalDataType::Int: if (variable.hasAttribute(Variable::AttributeType::Actual)) stored["valActual"] = variable.getInt(Variable::AttributeType::Actual); diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.h b/src/MicroOcpp/Model/Variables/VariableContainer.h index a0298c65..9a5cd26c 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.h +++ b/src/MicroOcpp/Model/Variables/VariableContainer.h @@ -65,7 +65,7 @@ class VariableContainerOwning : public VariableContainer, public MemoryManaged { bool add(std::unique_ptr variable); - bool enablePersistency(MO_FilesystemAdapter *filesystem, const char *filename); + bool enablePersistency(MO_FilesystemAdapter *filesystem, const char *filename); bool load(); //load variables from flash bool commit() override; }; diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp index 7a8f51cc..2da8e777 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.cpp +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -133,7 +133,7 @@ VariableService::VariableService(Context& context) : getBaseReportVars(makeVector(getMemoryTag())) { } - + bool VariableService::init() { containers.reserve(MO_VARIABLESTORE_BUCKETS + 1); if (containers.capacity() < MO_VARIABLESTORE_BUCKETS + 1) { @@ -206,13 +206,13 @@ void VariableService::loop() { } auto notifyReport = makeRequest(context, new NotifyReport( - context, + context, getBaseReportRequestId, context.getClock().now(), !getBaseReportVars.empty(), // tbc: to be continued getBaseReportSeqNo, variablesChunk)); - + if (!notifyReport) { MO_DBG_ERR("OOM"); getBaseReportVars.clear(); @@ -374,7 +374,7 @@ SetVariableStatus VariableService::setVariable(Variable::AttributeType attrType, if (foundComponent) { return SetVariableStatus::UnknownVariable; } else { - return SetVariableStatus::UnknownComponent; + return SetVariableStatus::UnknownComponent; } } @@ -512,7 +512,7 @@ GetVariableStatus VariableService::getVariable(Variable::AttributeType attrType, if (foundComponent) { return GetVariableStatus::UnknownVariable; } else { - return GetVariableStatus::UnknownComponent; + return GetVariableStatus::UnknownComponent; } } diff --git a/src/MicroOcpp/Model/Variables/VariableService.h b/src/MicroOcpp/Model/Variables/VariableService.h index 4fc68c9d..7ffe65ab 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.h +++ b/src/MicroOcpp/Model/Variables/VariableService.h @@ -76,7 +76,7 @@ class VariableService : public MemoryManaged { bool init(); //Get Variable. If not existent, create Variable owned by MO and return - template + template Variable *declareVariable(const ComponentId& component, const char *name, T factoryDefault, Mutability mutability = Mutability::ReadWrite, bool persistent = true, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false); bool addVariable(Variable *variable); //Add Variable without transferring ownership diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index 6105d1cb..d60cd7e1 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -18,7 +18,7 @@ using namespace MicroOcpp; BootNotification::BootNotification(Context& context, BootService& bootService, HeartbeatService *heartbeatService, const MO_BootNotificationData& bnData) : MemoryManaged("v16/v201.Operation.", "BootNotification"), context(context), bootService(bootService), heartbeatService(heartbeatService), bnData(bnData), ocppVersion(context.getOcppVersion()) { - + } const char* BootNotification::getOperationType(){ @@ -81,7 +81,7 @@ void BootNotification::processConf(JsonObject payload) { errorCode = "FormationViolation"; return; } - + int interval = payload["interval"] | -1; if (interval < 0) { errorCode = "FormationViolation"; @@ -89,7 +89,7 @@ void BootNotification::processConf(JsonObject payload) { } RegistrationStatus status = deserializeRegistrationStatus(payload["status"] | "Invalid"); - + if (status == RegistrationStatus::UNDEFINED) { errorCode = "FormationViolation"; return; diff --git a/src/MicroOcpp/Operations/CancelReservation.cpp b/src/MicroOcpp/Operations/CancelReservation.cpp index d3e17432..6782ac79 100644 --- a/src/MicroOcpp/Operations/CancelReservation.cpp +++ b/src/MicroOcpp/Operations/CancelReservation.cpp @@ -14,7 +14,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; CancelReservation::CancelReservation(ReservationService& reservationService) : MemoryManaged("v16.Operation.", "CancelReservation"), reservationService(reservationService) { - + } const char* CancelReservation::getOperationType() { diff --git a/src/MicroOcpp/Operations/ChangeAvailability.cpp b/src/MicroOcpp/Operations/ChangeAvailability.cpp index 539192d2..60e68835 100644 --- a/src/MicroOcpp/Operations/ChangeAvailability.cpp +++ b/src/MicroOcpp/Operations/ChangeAvailability.cpp @@ -79,7 +79,7 @@ std::unique_ptr v16::ChangeAvailability::createConf(){ } else { payload["status"] = "Accepted"; } - + return doc; } @@ -98,7 +98,7 @@ const char* v201::ChangeAvailability::getOperationType(){ void v201::ChangeAvailability::processReq(JsonObject payload) { unsigned int evseId = 0; - + if (payload.containsKey("evse")) { int evseIdRaw = payload["evse"]["id"] | -1; if (evseIdRaw < 0) { diff --git a/src/MicroOcpp/Operations/ChangeConfiguration.cpp b/src/MicroOcpp/Operations/ChangeConfiguration.cpp index d99f865c..8ab1034b 100644 --- a/src/MicroOcpp/Operations/ChangeConfiguration.cpp +++ b/src/MicroOcpp/Operations/ChangeConfiguration.cpp @@ -14,7 +14,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; ChangeConfiguration::ChangeConfiguration(ConfigurationService& configService) : MemoryManaged("v16.Operation.", "ChangeConfiguration"), configService(configService) { - + } const char* ChangeConfiguration::getOperationType(){ diff --git a/src/MicroOcpp/Operations/ClearCache.cpp b/src/MicroOcpp/Operations/ClearCache.cpp index 442bd131..eac4257c 100644 --- a/src/MicroOcpp/Operations/ClearCache.cpp +++ b/src/MicroOcpp/Operations/ClearCache.cpp @@ -12,7 +12,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; ClearCache::ClearCache(MO_FilesystemAdapter *filesystem) : MemoryManaged("v16.Operation.", "ClearCache"), filesystem(filesystem) { - + } const char* ClearCache::getOperationType(){ diff --git a/src/MicroOcpp/Operations/ClearChargingProfile.h b/src/MicroOcpp/Operations/ClearChargingProfile.h index 41c736da..cea9f755 100644 --- a/src/MicroOcpp/Operations/ClearChargingProfile.h +++ b/src/MicroOcpp/Operations/ClearChargingProfile.h @@ -29,7 +29,7 @@ class ClearChargingProfile : public Operation, public MemoryManaged { void processReq(JsonObject payload) override; std::unique_ptr createConf() override; - + const char *getErrorCode() override {return errorCode;} }; diff --git a/src/MicroOcpp/Operations/CustomOperation.cpp b/src/MicroOcpp/Operations/CustomOperation.cpp index c5b8b5b1..c682ea1b 100644 --- a/src/MicroOcpp/Operations/CustomOperation.cpp +++ b/src/MicroOcpp/Operations/CustomOperation.cpp @@ -16,7 +16,7 @@ std::unique_ptr makeDeserializedJson(const char *memoryTag, size_t capacity = MO_MAX_JSON_CAPACITY / 8; DeserializationError err = DeserializationError::NoMemory; - + while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) { doc = makeJsonDoc(memoryTag, capacity); diff --git a/src/MicroOcpp/Operations/DataTransfer.h b/src/MicroOcpp/Operations/DataTransfer.h index 8c809a58..3a64c4dc 100644 --- a/src/MicroOcpp/Operations/DataTransfer.h +++ b/src/MicroOcpp/Operations/DataTransfer.h @@ -29,7 +29,7 @@ class DataTransfer : public Operation, public MemoryManaged { void processReq(JsonObject payload) override; std::unique_ptr createConf() override; - + }; } //namespace v16 diff --git a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp index e0f42194..14ff2eda 100644 --- a/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp +++ b/src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp @@ -13,7 +13,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; DiagnosticsStatusNotification::DiagnosticsStatusNotification(DiagnosticsStatus status) : MemoryManaged("v16.Operation.", "DiagnosticsStatusNotification"), status(status) { - + } std::unique_ptr DiagnosticsStatusNotification::createReq() { diff --git a/src/MicroOcpp/Operations/GetBaseReport.cpp b/src/MicroOcpp/Operations/GetBaseReport.cpp index cb13406f..080cf34c 100644 --- a/src/MicroOcpp/Operations/GetBaseReport.cpp +++ b/src/MicroOcpp/Operations/GetBaseReport.cpp @@ -12,7 +12,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v201; GetBaseReport::GetBaseReport(VariableService& variableService) : MemoryManaged("v201.Operation.", "GetBaseReport"), variableService(variableService) { - + } const char* GetBaseReport::getOperationType(){ diff --git a/src/MicroOcpp/Operations/GetCompositeSchedule.cpp b/src/MicroOcpp/Operations/GetCompositeSchedule.cpp index f93b1ea0..ce9687cd 100644 --- a/src/MicroOcpp/Operations/GetCompositeSchedule.cpp +++ b/src/MicroOcpp/Operations/GetCompositeSchedule.cpp @@ -37,7 +37,7 @@ void GetCompositeSchedule::processReq(JsonObject payload) { numEvseId = context.getModel201().getNumEvseId(); } #endif //MO_ENABLE_V201 - + duration = payload["duration"] | -1; if (evseId < 0 || duration < 0) { diff --git a/src/MicroOcpp/Operations/GetConfiguration.cpp b/src/MicroOcpp/Operations/GetConfiguration.cpp index ad11fcaf..2cbfc64e 100644 --- a/src/MicroOcpp/Operations/GetConfiguration.cpp +++ b/src/MicroOcpp/Operations/GetConfiguration.cpp @@ -91,7 +91,7 @@ std::unique_ptr GetConfiguration::createConf(){ } jcapacity += JSON_ARRAY_SIZE(unknownKeys.size()); - + MO_DBG_DEBUG("GetConfiguration capacity: %zu", jcapacity); std::unique_ptr doc; @@ -111,7 +111,7 @@ std::unique_ptr GetConfiguration::createConf(){ } JsonObject payload = doc->to(); - + JsonArray jsonConfigurationKey = payload.createNestedArray("configurationKey"); for (size_t i = 0; i < configurations.size(); i++) { auto config = configurations[i]; diff --git a/src/MicroOcpp/Operations/GetDiagnostics.cpp b/src/MicroOcpp/Operations/GetDiagnostics.cpp index a3284cc6..3b5ddbfb 100644 --- a/src/MicroOcpp/Operations/GetDiagnostics.cpp +++ b/src/MicroOcpp/Operations/GetDiagnostics.cpp @@ -26,7 +26,7 @@ void GetDiagnostics::processReq(JsonObject payload) { errorCode = "FormationViolation"; return; } - + int retries = payload["retries"] | 1; int retryInterval = payload["retryInterval"] | 180; if (retries < 0 || retryInterval < 0) { diff --git a/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp index 5a525b3a..c3ed29a3 100644 --- a/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp +++ b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp @@ -117,7 +117,7 @@ std::unique_ptr GetInstalledCertificateIds::createConf() { mo_cert_print_serialNumber(&chainElem.certificateHashData, buf, sizeof(buf)); certHashJson["certificateHashData"]["serialNumber"] = buf; - + if (!chainElem.childCertificateHashData.empty()) { MO_DBG_ERR("only sole root certs supported"); } diff --git a/src/MicroOcpp/Operations/GetLocalListVersion.cpp b/src/MicroOcpp/Operations/GetLocalListVersion.cpp index 1eb72d60..f9459a2e 100644 --- a/src/MicroOcpp/Operations/GetLocalListVersion.cpp +++ b/src/MicroOcpp/Operations/GetLocalListVersion.cpp @@ -13,7 +13,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; GetLocalListVersion::GetLocalListVersion(Model& model) : MemoryManaged("v16.Operation.", "GetLocalListVersion"), model(model) { - + } const char* GetLocalListVersion::getOperationType(){ diff --git a/src/MicroOcpp/Operations/GetVariables.cpp b/src/MicroOcpp/Operations/GetVariables.cpp index a88de434..f7dbbf19 100644 --- a/src/MicroOcpp/Operations/GetVariables.cpp +++ b/src/MicroOcpp/Operations/GetVariables.cpp @@ -84,7 +84,7 @@ std::unique_ptr GetVariables::createConf(){ for (auto& query : queries) { query.attributeStatus = variableService.getVariable( query.attributeType, - ComponentId(query.componentName.c_str(), + ComponentId(query.componentName.c_str(), EvseId(query.componentEvseId, query.componentEvseConnectorId)), query.variableName.c_str(), &query.variable); @@ -120,7 +120,7 @@ std::unique_ptr GetVariables::createConf(){ } } - capacity += + capacity += JSON_OBJECT_SIZE(5) + // getVariableResult valueCapacity + // capacity needed for storing the value JSON_OBJECT_SIZE(2) + // component diff --git a/src/MicroOcpp/Operations/GetVariables.h b/src/MicroOcpp/Operations/GetVariables.h index 57e3960c..9e3e7cd4 100644 --- a/src/MicroOcpp/Operations/GetVariables.h +++ b/src/MicroOcpp/Operations/GetVariables.h @@ -12,7 +12,7 @@ #if MO_ENABLE_V201 -namespace MicroOcpp { +namespace MicroOcpp { namespace v201 { class VariableService; diff --git a/src/MicroOcpp/Operations/Heartbeat.cpp b/src/MicroOcpp/Operations/Heartbeat.cpp index b753cb4a..aed8b131 100644 --- a/src/MicroOcpp/Operations/Heartbeat.cpp +++ b/src/MicroOcpp/Operations/Heartbeat.cpp @@ -13,7 +13,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; Heartbeat::Heartbeat(Context& context) : MemoryManaged("v16.Operation.", "Heartbeat"), context(context) { - + } const char* Heartbeat::getOperationType(){ @@ -25,7 +25,7 @@ std::unique_ptr Heartbeat::createReq() { } void Heartbeat::processConf(JsonObject payload) { - + const char* currentTime = payload["currentTime"] | "Invalid"; if (strcmp(currentTime, "Invalid")) { if (context.getClock().setTime(currentTime)) { diff --git a/src/MicroOcpp/Operations/MeterValues.cpp b/src/MicroOcpp/Operations/MeterValues.cpp index 197ba2b8..8b44427a 100644 --- a/src/MicroOcpp/Operations/MeterValues.cpp +++ b/src/MicroOcpp/Operations/MeterValues.cpp @@ -16,7 +16,7 @@ using namespace MicroOcpp::v16; //can only be used for echo server debugging MeterValues::MeterValues(Context& context) : MemoryManaged("v16.Operation.", "MeterValues"), context(context) { - + } MeterValues::MeterValues(Context& context, unsigned int connectorId, int transactionId, MeterValue *meterValue, bool transferOwnership) : @@ -26,7 +26,7 @@ MeterValues::MeterValues(Context& context, unsigned int connectorId, int transac isMeterValueOwner(transferOwnership), connectorId(connectorId), transactionId(transactionId) { - + } MeterValues::~MeterValues(){ diff --git a/src/MicroOcpp/Operations/NotifyReport.cpp b/src/MicroOcpp/Operations/NotifyReport.cpp index bcedbd0e..e86baf32 100644 --- a/src/MicroOcpp/Operations/NotifyReport.cpp +++ b/src/MicroOcpp/Operations/NotifyReport.cpp @@ -34,7 +34,7 @@ std::unique_ptr NotifyReport::createReq() { Variable::AttributeType::MaxSet }; - size_t capacity = + size_t capacity = JSON_OBJECT_SIZE(5) + //total of 5 fields MO_JSONDATE_SIZE; //timestamp string @@ -228,7 +228,7 @@ std::unique_ptr NotifyReport::createReq() { break; default: MO_DBG_ERR("internal error"); - break; + break; } variableCharacteristics["dataType"] = dataTypeCstr; diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp index 3ba2e75e..c2111a31 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.cpp @@ -20,7 +20,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; RemoteStartTransaction::RemoteStartTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "RemoteStartTransaction"), context(context), rcService(rcService) { - + } const char* RemoteStartTransaction::getOperationType() { diff --git a/src/MicroOcpp/Operations/RemoteStartTransaction.h b/src/MicroOcpp/Operations/RemoteStartTransaction.h index 8fadb12a..58ce3089 100644 --- a/src/MicroOcpp/Operations/RemoteStartTransaction.h +++ b/src/MicroOcpp/Operations/RemoteStartTransaction.h @@ -25,7 +25,7 @@ class RemoteStartTransaction : public Operation, public MemoryManaged { RemoteControlService& rcService; RemoteStartStopStatus status = RemoteStartStopStatus::Rejected; - + const char *errorCode {nullptr}; const char *errorDescription = ""; public: diff --git a/src/MicroOcpp/Operations/RemoteStopTransaction.cpp b/src/MicroOcpp/Operations/RemoteStopTransaction.cpp index 2ad4ecc9..c7e9bb42 100644 --- a/src/MicroOcpp/Operations/RemoteStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RemoteStopTransaction.cpp @@ -14,7 +14,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; RemoteStopTransaction::RemoteStopTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "RemoteStopTransaction"), context(context), rcService(rcService) { - + } const char* RemoteStopTransaction::getOperationType() { diff --git a/src/MicroOcpp/Operations/RequestStartTransaction.cpp b/src/MicroOcpp/Operations/RequestStartTransaction.cpp index dc9b8a5f..95e009a6 100644 --- a/src/MicroOcpp/Operations/RequestStartTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStartTransaction.cpp @@ -16,7 +16,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v201; RequestStartTransaction::RequestStartTransaction(Context& context, RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStartTransaction"), context(context), rcService(rcService) { - + } const char* RequestStartTransaction::getOperationType(){ diff --git a/src/MicroOcpp/Operations/RequestStopTransaction.cpp b/src/MicroOcpp/Operations/RequestStopTransaction.cpp index e4e07b99..0b3229ed 100644 --- a/src/MicroOcpp/Operations/RequestStopTransaction.cpp +++ b/src/MicroOcpp/Operations/RequestStopTransaction.cpp @@ -12,7 +12,7 @@ using namespace MicroOcpp; using namespace MicroOcpp::v201; RequestStopTransaction::RequestStopTransaction(RemoteControlService& rcService) : MemoryManaged("v201.Operation.", "RequestStopTransaction"), rcService(rcService) { - + } const char* RequestStopTransaction::getOperationType(){ @@ -21,7 +21,7 @@ const char* RequestStopTransaction::getOperationType(){ void RequestStopTransaction::processReq(JsonObject payload) { - if (!payload.containsKey("transactionId") || + if (!payload.containsKey("transactionId") || !payload["transactionId"].is() || strlen(payload["transactionId"].as()) + 1 > MO_TXID_SIZE) { errorCode = "FormationViolation"; diff --git a/src/MicroOcpp/Operations/ReserveNow.cpp b/src/MicroOcpp/Operations/ReserveNow.cpp index d8dd4b71..84203c51 100644 --- a/src/MicroOcpp/Operations/ReserveNow.cpp +++ b/src/MicroOcpp/Operations/ReserveNow.cpp @@ -134,7 +134,7 @@ std::unique_ptr ReserveNow::createConf(){ MO_DBG_ERR("didn't set reservationStatus"); payload["status"] = "Rejected"; } - + return doc; } diff --git a/src/MicroOcpp/Operations/Reset.cpp b/src/MicroOcpp/Operations/Reset.cpp index 5063448d..3d07acf5 100644 --- a/src/MicroOcpp/Operations/Reset.cpp +++ b/src/MicroOcpp/Operations/Reset.cpp @@ -12,7 +12,7 @@ using namespace MicroOcpp; v16::Reset::Reset(ResetService& resetService) : MemoryManaged("v16.Operation.", "Reset"), resetService(resetService) { - + } const char* v16::Reset::getOperationType(){ @@ -51,7 +51,7 @@ std::unique_ptr v16::Reset::createConf() { using namespace MicroOcpp; v201::Reset::Reset(ResetService& resetService) : MemoryManaged("v201.Operation.", "Reset"), resetService(resetService) { - + } const char* v201::Reset::getOperationType(){ @@ -62,7 +62,7 @@ void v201::Reset::processReq(JsonObject payload) { MO_ResetType type; const char *typeCstr = payload["type"] | "_Undefined"; - + if (!strcmp(typeCstr, "Immediate")) { type = MO_ResetType_Immediate; } else if (!strcmp(typeCstr, "OnIdle")) { diff --git a/src/MicroOcpp/Operations/SendLocalList.cpp b/src/MicroOcpp/Operations/SendLocalList.cpp index 7fed1b22..469437e7 100644 --- a/src/MicroOcpp/Operations/SendLocalList.cpp +++ b/src/MicroOcpp/Operations/SendLocalList.cpp @@ -12,11 +12,11 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; SendLocalList::SendLocalList(AuthorizationService& authService) : MemoryManaged("v16.Operation.", "SendLocalList"), authService(authService) { - + } SendLocalList::~SendLocalList() { - + } const char* SendLocalList::getOperationType(){ @@ -65,7 +65,7 @@ std::unique_ptr SendLocalList::createConf(){ } else { payload["status"] = "Accepted"; } - + return doc; } diff --git a/src/MicroOcpp/Operations/SetVariables.cpp b/src/MicroOcpp/Operations/SetVariables.cpp index 37ce3f6f..7e407477 100644 --- a/src/MicroOcpp/Operations/SetVariables.cpp +++ b/src/MicroOcpp/Operations/SetVariables.cpp @@ -16,7 +16,7 @@ SetVariableData::SetVariableData(const char *memory_tag) : componentName{makeStr } SetVariables::SetVariables(VariableService& variableService) : MemoryManaged("v201.Operation.", "SetVariables"), variableService(variableService), queries(makeVector(getMemoryTag())) { - + } const char* SetVariables::getOperationType(){ @@ -84,7 +84,7 @@ void SetVariables::processReq(JsonObject payload) { query.attributeStatus = variableService.setVariable( query.attributeType, query.attributeValue, - ComponentId(query.componentName.c_str(), + ComponentId(query.componentName.c_str(), EvseId(query.componentEvseId, query.componentEvseConnectorId)), query.variableName.c_str()); } @@ -99,7 +99,7 @@ void SetVariables::processReq(JsonObject payload) { std::unique_ptr SetVariables::createConf(){ size_t capacity = JSON_ARRAY_SIZE(queries.size()); for (const auto& data : queries) { - capacity += + capacity += JSON_OBJECT_SIZE(5) + // setVariableResult JSON_OBJECT_SIZE(2) + // component data.componentName.length() + 1 + diff --git a/src/MicroOcpp/Operations/StartTransaction.cpp b/src/MicroOcpp/Operations/StartTransaction.cpp index 897b3a2e..2df04446 100644 --- a/src/MicroOcpp/Operations/StartTransaction.cpp +++ b/src/MicroOcpp/Operations/StartTransaction.cpp @@ -18,11 +18,11 @@ using namespace MicroOcpp; using namespace MicroOcpp::v16; StartTransaction::StartTransaction(Context& context, Transaction *transaction) : MemoryManaged("v16.Operation.", "StartTransaction"), context(context), transaction(transaction) { - + } StartTransaction::~StartTransaction() { - + } const char* StartTransaction::getOperationType() { @@ -32,9 +32,9 @@ const char* StartTransaction::getOperationType() { std::unique_ptr StartTransaction::createReq() { auto doc = makeJsonDoc(getMemoryTag(), - JSON_OBJECT_SIZE(6) + + JSON_OBJECT_SIZE(6) + MO_JSONDATE_SIZE); - + JsonObject payload = doc->to(); payload["connectorId"] = transaction->getConnectorId(); diff --git a/src/MicroOcpp/Operations/StatusNotification.cpp b/src/MicroOcpp/Operations/StatusNotification.cpp index cc368b2b..3660b05b 100644 --- a/src/MicroOcpp/Operations/StatusNotification.cpp +++ b/src/MicroOcpp/Operations/StatusNotification.cpp @@ -16,7 +16,7 @@ using namespace MicroOcpp; v16::StatusNotification::StatusNotification(Context& context, int connectorId, MO_ChargePointStatus currentStatus, const Timestamp ×tamp, MO_ErrorData errorData) : MemoryManaged("v16.Operation.", "StatusNotification"), context(context), connectorId(connectorId), currentStatus(currentStatus), timestamp(timestamp), errorData(errorData) { - + if (currentStatus != MO_ChargePointStatus_UNDEFINED) { MO_DBG_INFO("New status: %s (connectorId %d)", mo_serializeChargePointStatus(currentStatus), connectorId); } diff --git a/src/MicroOcpp/Operations/TriggerMessage.cpp b/src/MicroOcpp/Operations/TriggerMessage.cpp index cb73450a..4ea639f2 100644 --- a/src/MicroOcpp/Operations/TriggerMessage.cpp +++ b/src/MicroOcpp/Operations/TriggerMessage.cpp @@ -53,7 +53,7 @@ void TriggerMessage::processReq(JsonObject payload) { } } #endif //MO_ENABLE_V201 - + if (evseId >= 0 && (unsigned int)evseId >= numEvseId) { errorCode = "PropertyConstraintViolation"; return; diff --git a/src/MicroOcpp/Operations/UnlockConnector.cpp b/src/MicroOcpp/Operations/UnlockConnector.cpp index e5769aa5..e728860b 100644 --- a/src/MicroOcpp/Operations/UnlockConnector.cpp +++ b/src/MicroOcpp/Operations/UnlockConnector.cpp @@ -13,7 +13,7 @@ using namespace MicroOcpp; v16::UnlockConnector::UnlockConnector(Context& context, RemoteControlService& rcService) : MemoryManaged("v16.Operation.", "UnlockConnector"), context(context), rcService(rcService) { - + } const char* v16::UnlockConnector::getOperationType(){ @@ -45,10 +45,10 @@ std::unique_ptr v16::UnlockConnector::createConf() { { int32_t dtTimerStart; context.getClock().delta(context.getClock().getUptime(), timerStart, dtTimerStart); - + if (rcEvse && status == UnlockStatus::PENDING && dtTimerStart < MO_UNLOCK_TIMEOUT) { status = rcEvse->unlockConnector16(); - + if (status == UnlockStatus::PENDING) { return nullptr; //no result yet - delay confirmation response } diff --git a/src/MicroOcpp/Platform.cpp b/src/MicroOcpp/Platform.cpp index 5541ff7f..51a1dfd0 100644 --- a/src/MicroOcpp/Platform.cpp +++ b/src/MicroOcpp/Platform.cpp @@ -28,7 +28,7 @@ uint32_t defaultTickCbImpl() { #include "freertos/task.h" namespace MicroOcpp { - + void defaultDebugCbImpl(const char *msg) { printf("%s", msg); } @@ -49,7 +49,7 @@ uint32_t defaultTickCbImpl() { #include namespace MicroOcpp { - + void defaultDebugCbImpl(const char *msg) { printf("%s", msg); } From 74c96f33a39578a0c9d4d9790d5a13b213c31ec4 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:43:18 +0200 Subject: [PATCH 19/50] add common Model class for interoperable modules --- src/MicroOcpp.cpp | 97 +--- src/MicroOcpp/Context.cpp | 23 + src/MicroOcpp/Context.h | 4 + src/MicroOcpp/Model/Boot/BootService.cpp | 21 +- src/MicroOcpp/Model/Boot/BootService.h | 3 - .../Model/Certificates/CertificateService.cpp | 26 +- .../Model/Diagnostics/DiagnosticsService.cpp | 73 +-- .../Model/Heartbeat/HeartbeatService.cpp | 2 +- .../Model/Metering/MeteringService.cpp | 15 +- src/MicroOcpp/Model/Model.cpp | 501 +++++++----------- src/MicroOcpp/Model/Model.h | 171 +++--- .../RemoteControl/RemoteControlService.cpp | 20 +- .../SmartCharging/SmartChargingService.cpp | 31 +- .../Operations/GetCompositeSchedule.cpp | 6 +- .../Operations/SetChargingProfile.cpp | 17 +- src/MicroOcpp/Operations/TriggerMessage.cpp | 6 +- tests/SmartCharging.cpp | 13 +- 17 files changed, 352 insertions(+), 677 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index b5a51c05..ec90fb70 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -196,19 +196,7 @@ bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData } #endif - MicroOcpp::BootService *bootService = nullptr; - - #if MO_ENABLE_V16 - if (context->getOcppVersion() == MO_OCPP_V16) { - bootService = context->getModel16().getBootService(); - } - #endif - #if MO_ENABLE_V201 - if (context->getOcppVersion() == MO_OCPP_V201) { - bootService = context->getModel201().getBootService(); - } - #endif - + MicroOcpp::BootService *bootService = context->getModelCommon().getBootService(); if (!bootService) { MO_DBG_ERR("OOM"); return false; @@ -338,19 +326,7 @@ bool mo_setSmartChargingOutput(MO_Context *ctx, unsigned int evseId, void (*char } auto context = mo_getContext2(ctx); - MicroOcpp::SmartChargingService *scService = nullptr; - - #if MO_ENABLE_V16 - if (context->getOcppVersion() == MO_OCPP_V16) { - scService = context->getModel16().getSmartChargingService(); - } - #endif - #if MO_ENABLE_V201 - if (context->getOcppVersion() == MO_OCPP_V201) { - scService = context->getModel201().getSmartChargingService(); - } - #endif - + MicroOcpp::SmartChargingService *scService = context->getModelCommon().getSmartChargingService(); if (!scService) { MO_DBG_ERR("OOM"); return false; @@ -1513,28 +1489,13 @@ void mo_setOnUnlockConnector2(MO_Context *ctx, unsigned int evseId, MO_UnlockCon } auto context = mo_getContext2(ctx); - #if MO_ENABLE_V16 - if (context->getOcppVersion() == MO_OCPP_V16) { - auto rmtSvc = context->getModel16().getRemoteControlService(); - auto rmtSvcEvse = rmtSvc ? rmtSvc->getEvse(evseId) : nullptr; - if (!rmtSvcEvse) { - MO_DBG_ERR("init failure"); - return; - } - rmtSvcEvse->setOnUnlockConnector(onUnlockConnector2, userData); - } - #endif - #if MO_ENABLE_V201 - if (context->getOcppVersion() == MO_OCPP_V201) { - auto rmtSvc = context->getModel201().getRemoteControlService(); - auto rmtSvcEvse = rmtSvc ? rmtSvc->getEvse(evseId) : nullptr; - if (!rmtSvcEvse) { - MO_DBG_ERR("init failure"); - return; - } - rmtSvcEvse->setOnUnlockConnector(onUnlockConnector2, userData); + auto rmtSvc = context->getModelCommon().getRemoteControlService(); + auto rmtSvcEvse = rmtSvc ? rmtSvc->getEvse(evseId) : nullptr; + if (!rmtSvcEvse) { + MO_DBG_ERR("init failure"); + return; } - #endif + rmtSvcEvse->setOnUnlockConnector(onUnlockConnector2, userData); } #endif //MO_ENABLE_CONNECTOR_LOCK @@ -1606,19 +1567,7 @@ bool mo_isAcceptedByCsms2(MO_Context *ctx) { } auto context = mo_getContext2(ctx); - MicroOcpp::BootService *bootService = nullptr; - - #if MO_ENABLE_V16 - if (context->getOcppVersion() == MO_OCPP_V16) { - bootService = context->getModel16().getBootService(); - } - #endif - #if MO_ENABLE_V201 - if (context->getOcppVersion() == MO_OCPP_V201) { - bootService = context->getModel201().getBootService(); - } - #endif - + MicroOcpp::BootService *bootService = context->getModelCommon().getBootService(); if (!bootService) { MO_DBG_ERR("OOM"); return false; @@ -1937,19 +1886,7 @@ void mo_setDiagnosticsReader(MO_Context *ctx, size_t (*readBytes)(char*, size_t, } auto context = mo_getContext2(ctx); - MicroOcpp::DiagnosticsService *diagSvc = nullptr; - - #if MO_ENABLE_V16 - if (context->getOcppVersion() == MO_OCPP_V16) { - diagSvc = context->getModel16().getDiagnosticsService(); - } - #endif - #if MO_ENABLE_V201 - if (context->getOcppVersion() == MO_OCPP_V201) { - diagSvc = context->getModel201().getDiagnosticsService(); - } - #endif - + MicroOcpp::DiagnosticsService *diagSvc = context->getModelCommon().getDiagnosticsService(); if (!diagSvc) { MO_DBG_ERR("init failure"); return; @@ -1965,19 +1902,7 @@ void mo_setDiagnosticsFtpServerCert(MO_Context *ctx, const char *cert) { } auto context = mo_getContext2(ctx); - MicroOcpp::DiagnosticsService *diagSvc = nullptr; - - #if MO_ENABLE_V16 - if (context->getOcppVersion() == MO_OCPP_V16) { - diagSvc = context->getModel16().getDiagnosticsService(); - } - #endif - #if MO_ENABLE_V201 - if (context->getOcppVersion() == MO_OCPP_V201) { - diagSvc = context->getModel201().getDiagnosticsService(); - } - #endif - + MicroOcpp::DiagnosticsService *diagSvc = context->getModelCommon().getDiagnosticsService(); if (!diagSvc) { MO_DBG_ERR("init failure"); return; diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index 655a43fe..30281783 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -246,6 +246,29 @@ v201::Model& Context::getModel201() { } #endif +#if MO_ENABLE_V16 || MO_ENABLE_V201 +ModelCommon& Context::getModelCommon() { + #if MO_ENABLE_V16 && MO_ENABLE_V201 + if (ocppVersion == MO_OCPP_V16) { + return static_cast(modelV16); + } else if (ocppVersion == MO_OCPP_V201) { + return static_cast(modelV201); + } else { + MO_DBG_ERR("OCPP version undefined"); + return static_cast(modelV16); + } + #elif MO_ENABLE_V16 + { + return static_cast(modelV16); + } + #elif MO_ENABLE_V201 + { + return static_cast(modelV201); + } + #endif +} +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 + #if MO_ENABLE_V16 && !MO_ENABLE_V201 Model& Context::getModel() { return getModel16(); diff --git a/src/MicroOcpp/Context.h b/src/MicroOcpp/Context.h index ab9d9b11..39d62a9f 100644 --- a/src/MicroOcpp/Context.h +++ b/src/MicroOcpp/Context.h @@ -119,6 +119,10 @@ class Context : public MemoryManaged { v201::Model& getModel201(); #endif +#if MO_ENABLE_V16 || MO_ENABLE_V201 + ModelCommon& getModelCommon(); //subset of Model16 / Model201 with modules which are shared between v16 and v201 implementation +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 + // eliminate OCPP version specifiers if charger supports just one version anyway #if MO_ENABLE_V16 && !MO_ENABLE_V201 Model& getModel(); diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index df6dad02..173aef8d 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -84,8 +84,6 @@ bool BootService::setup() { return false; } - heartbeatService = context.getModel16().getHeartbeatService(); //optional - RemoteControlService *rcService = context.getModel16().getRemoteControlService(); if (!rcService) { MO_DBG_ERR("initialization error"); @@ -115,9 +113,7 @@ bool BootService::setup() { return false; } - heartbeatService = context.getModel201().getHeartbeatService(); //optional - - RemoteControlService *rcService = context.getModel16().getRemoteControlService(); + RemoteControlService *rcService = context.getModel201().getRemoteControlService(); if (!rcService) { MO_DBG_ERR("initialization error"); return false; @@ -202,18 +198,7 @@ void BootService::loop() { #endif //MO_ENABLE_V201 if (!activatedModel && (status == RegistrationStatus::Accepted || preBootTransactions)) { - - #if MO_ENABLE_V16 - if (ocppVersion == MO_OCPP_V16) { - context.getModel16().activateTasks(); - } - #endif //MO_ENABLE_V16 - #if MO_ENABLE_V201 - if (ocppVersion == MO_OCPP_V201) { - context.getModel201().activateTasks(); - } - #endif //MO_ENABLE_V201 - + context.getModelCommon().activateTasks(); activatedModel = true; } @@ -234,7 +219,7 @@ void BootService::loop() { * Create BootNotification. The BootNotifaction object will fetch its paremeters from * this class and notify this class about the response */ - auto bootNotification = makeRequest(context, new BootNotification(context, *this, heartbeatService, getBootNotificationData())); + auto bootNotification = makeRequest(context, new BootNotification(context, *this, context.getModelCommon().getHeartbeatService(), getBootNotificationData())); bootNotification->setTimeout(interval_s); context.getMessageService().sendRequestPreBoot(std::move(bootNotification)); diff --git a/src/MicroOcpp/Model/Boot/BootService.h b/src/MicroOcpp/Model/Boot/BootService.h index f9c7df59..1325460c 100644 --- a/src/MicroOcpp/Model/Boot/BootService.h +++ b/src/MicroOcpp/Model/Boot/BootService.h @@ -42,7 +42,6 @@ class PreBootQueue : public VolatileRequestQueue { }; class Context; -class HeartbeatService; #if MO_ENABLE_V16 namespace v16 { @@ -62,8 +61,6 @@ class BootService : public MemoryManaged { PreBootQueue preBootQueue; - HeartbeatService *heartbeatService = nullptr; - int32_t interval_s = MO_BOOT_INTERVAL_DEFAULT; Timestamp lastBootNotification; diff --git a/src/MicroOcpp/Model/Certificates/CertificateService.cpp b/src/MicroOcpp/Model/Certificates/CertificateService.cpp index 1deda36d..c9b30774 100644 --- a/src/MicroOcpp/Model/Certificates/CertificateService.cpp +++ b/src/MicroOcpp/Model/Certificates/CertificateService.cpp @@ -26,26 +26,12 @@ bool CertificateService::setup() { return true; // not a critical failure } - #if MO_ENABLE_V16 - if (context.getOcppVersion() == MO_OCPP_V16) { - context.getMessageService().registerOperation("DeleteCertificate", [] (Context& context) -> Operation* { - return new DeleteCertificate(*context.getModel16().getCertificateService());}); - context.getMessageService().registerOperation("GetInstalledCertificateIds", [] (Context& context) -> Operation* { - return new GetInstalledCertificateIds(*context.getModel16().getCertificateService());}); - context.getMessageService().registerOperation("InstallCertificate", [] (Context& context) -> Operation* { - return new InstallCertificate(*context.getModel16().getCertificateService());}); - } - #endif - #if MO_ENABLE_V201 - if (context.getOcppVersion() == MO_OCPP_V201) { - context.getMessageService().registerOperation("DeleteCertificate", [] (Context& context) -> Operation* { - return new DeleteCertificate(*context.getModel201().getCertificateService());}); - context.getMessageService().registerOperation("GetInstalledCertificateIds", [] (Context& context) -> Operation* { - return new GetInstalledCertificateIds(*context.getModel201().getCertificateService());}); - context.getMessageService().registerOperation("InstallCertificate", [] (Context& context) -> Operation* { - return new InstallCertificate(*context.getModel201().getCertificateService());}); - } - #endif + context.getMessageService().registerOperation("DeleteCertificate", [] (Context& context) -> Operation* { + return new DeleteCertificate(*context.getModelCommon().getCertificateService());}); + context.getMessageService().registerOperation("GetInstalledCertificateIds", [] (Context& context) -> Operation* { + return new GetInstalledCertificateIds(*context.getModelCommon().getCertificateService());}); + context.getMessageService().registerOperation("InstallCertificate", [] (Context& context) -> Operation* { + return new InstallCertificate(*context.getModelCommon().getCertificateService());}); return true; } diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 0fb7bd16..0df003d4 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -94,6 +94,11 @@ bool DiagnosticsService::setup() { customProtocols = nullptr; } + auto rcService = context.getModelCommon().getRemoteControlService(); + if (!rcService) { + MO_DBG_ERR("initialization error"); + return false; + } ocppVersion = context.getOcppVersion(); #if MO_ENABLE_V16 @@ -119,28 +124,14 @@ bool DiagnosticsService::setup() { context.getMessageService().registerOperation("GetDiagnostics", [] (Context& context) -> Operation* { return new v16::GetDiagnostics(context, *context.getModel16().getDiagnosticsService());}); - context.getMessageService().registerOperation("GetLog", [] (Context& context) -> Operation* { - return new GetLog(context, *context.getModel16().getDiagnosticsService());}); - #if MO_ENABLE_MOCK_SERVER context.getMessageService().registerOperation("DiagnosticsStatusNotification", nullptr, nullptr); - context.getMessageService().registerOperation("LogStatusNotification", nullptr, nullptr); #endif //MO_ENABLE_MOCK_SERVER - auto rcService = context.getModel16().getRemoteControlService(); - if (!rcService) { - MO_DBG_ERR("initialization error"); - return false; - } - rcService->addTriggerMessageHandler("DiagnosticsStatusNotification", [] (Context& context) -> Operation* { auto diagSvc = context.getModel16().getDiagnosticsService(); return new v16::DiagnosticsStatusNotification(diagSvc->getUploadStatus16()); }); - - rcService->addTriggerMessageHandler("LogStatusNotification", [] (Context& context) -> Operation* { - auto diagService = context.getModel16().getDiagnosticsService(); - return new LogStatusNotification(diagService->getUploadStatus(), diagService->getRequestId());}); } #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 @@ -163,25 +154,19 @@ bool DiagnosticsService::setup() { } else if (ftpClient) { fileTransferProtocols->setString("FTP,FTPS"); } + } + #endif //MO_ENABLE_V201 - context.getMessageService().registerOperation("GetLog", [] (Context& context) -> Operation* { - return new GetLog(context, *context.getModel201().getDiagnosticsService());}); - - #if MO_ENABLE_MOCK_SERVER - context.getMessageService().registerOperation("LogStatusNotification", nullptr, nullptr); - #endif //MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("GetLog", [] (Context& context) -> Operation* { + return new GetLog(context, *context.getModelCommon().getDiagnosticsService());}); - auto rcService = context.getModel16().getRemoteControlService(); - if (!rcService) { - MO_DBG_ERR("initialization error"); - return false; - } + rcService->addTriggerMessageHandler("LogStatusNotification", [] (Context& context) -> Operation* { + auto diagService = context.getModelCommon().getDiagnosticsService(); + return new LogStatusNotification(diagService->getUploadStatus(), diagService->getRequestId());}); - rcService->addTriggerMessageHandler("LogStatusNotification", [] (Context& context) -> Operation* { - auto diagService = context.getModel201().getDiagnosticsService(); - return new LogStatusNotification(diagService->getUploadStatus(), diagService->getRequestId());}); - } - #endif //MO_ENABLE_V201 + #if MO_ENABLE_MOCK_SERVER + context.getMessageService().registerOperation("LogStatusNotification", nullptr, nullptr); + #endif //MO_ENABLE_MOCK_SERVER MO_FREE(customProtocols); //not needed anymore customProtocols = nullptr; @@ -619,22 +604,10 @@ bool DiagnosticsService::uploadDiagnostics() { diagPostambleLen = 0; diagPostambleTransferred = 0; - BootService *bootService = nullptr; - #if MO_ENABLE_V16 - if (ocppVersion == MO_OCPP_V16) { - bootService = context.getModel16().getBootService(); - } - #endif //MO_ENABLE_V16 - #if MO_ENABLE_V201 - if (ocppVersion == MO_OCPP_V201) { - bootService = context.getModel201().getBootService(); - } - #endif //MO_ENABLE_V201 - const char *cpModel = nullptr; const char *fwVersion = nullptr; - if (bootService) { + if (auto bootService = context.getModelCommon().getBootService()) { auto bnData = bootService->getBootNotificationData(); cpModel = bnData.chargePointModel; fwVersion = bnData.firmwareVersion; @@ -798,22 +771,10 @@ bool DiagnosticsService::uploadSecurityLog() { diagReaderHasData = false; - BootService *bootService = nullptr; - #if MO_ENABLE_V16 - if (ocppVersion == MO_OCPP_V16) { - bootService = context.getModel16().getBootService(); - } - #endif //MO_ENABLE_V16 - #if MO_ENABLE_V201 - if (ocppVersion == MO_OCPP_V201) { - bootService = context.getModel201().getBootService(); - } - #endif //MO_ENABLE_V201 - auto cpModel = makeString(getMemoryTag()); auto fwVersion = makeString(getMemoryTag()); - if (bootService) { + if (auto bootService = context.getModelCommon().getBootService()) { auto bnData = bootService->getBootNotificationData(); cpModel = bnData.chargePointModel; fwVersion = bnData.firmwareVersion; diff --git a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp index f8c2addd..5254c253 100644 --- a/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp +++ b/src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp @@ -75,7 +75,7 @@ bool HeartbeatService::setup() { context.getMessageService().registerOperation("Heartbeat", nullptr, Heartbeat::writeMockConf, nullptr, reinterpret_cast(&context)); #endif //MO_ENABLE_MOCK_SERVER - auto rcService = context.getModel16().getRemoteControlService(); + auto rcService = context.getModelCommon().getRemoteControlService(); if (!rcService) { MO_DBG_ERR("initialization error"); return false; diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index 92308b37..81c351c3 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -198,19 +198,6 @@ void updateInputSelectFlag(const char *selectString, uint8_t flag, Vector(user_data); - unsigned int numEvseId = 0; - - #if MO_ENABLE_V16 - if (context.getOcppVersion() == MO_OCPP_V16) { - numEvseId = context.getModel16().getNumEvseId(); - } - #endif //MO_ENABLE_V16 - #if MO_ENABLE_V201 - if (context.getOcppVersion() == MO_OCPP_V201) { - numEvseId = context.getModel201().getNumEvseId(); - } - #endif //MO_ENABLE_V201 - bool isValid = true; const char *l = selectString; //the beginning of an entry of the comma-separated list const char *r = l; //one place after the last character of the entry beginning with l @@ -229,7 +216,7 @@ bool validateSelectString(const char *selectString, void *user_data) { //check if measurand exists in MeterInputs. Search through all EVSEs bool found = false; - for (unsigned int evseId = 0; evseId < numEvseId; evseId++) { + for (unsigned int evseId = 0; evseId < context.getModelCommon().getNumEvseId(); evseId++) { Vector *meterInputsPtr = nullptr; diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index 437e9cb4..55f50569 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -29,19 +29,197 @@ #include #include -#if MO_ENABLE_V16 +#if MO_ENABLE_V16 || MO_ENABLE_V201 using namespace MicroOcpp; -v16::Model::Model(Context& context) : MemoryManaged("v16.Model"), context(context) { +ModelCommon::ModelCommon(Context& context) : context(context) { } -v16::Model::~Model() { +ModelCommon::~ModelCommon() { delete bootService; bootService = nullptr; delete heartbeatService; heartbeatService = nullptr; + delete remoteControlService; + remoteControlService = nullptr; + +#if MO_ENABLE_DIAGNOSTICS + delete diagnosticsService; + diagnosticsService = nullptr; +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING + delete smartChargingService; + smartChargingService = nullptr; +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT + delete certService; + certService = nullptr; +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT + delete secEventService; + secEventService = nullptr; +#endif //MO_ENABLE_SECURITY_EVENT + +} + +bool ModelCommon::setupCommon() { + + if (!getBootService() || !getBootService()->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } + + if (!getHeartbeatService() || !getHeartbeatService()->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } + + if (!getRemoteControlService() || !getRemoteControlService()->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } + +#if MO_ENABLE_DIAGNOSTICS + if (!getDiagnosticsService() || !getDiagnosticsService()->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING + if (!getSmartChargingService() || !getSmartChargingService()->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT + if (!getCertificateService() || !getCertificateService()->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT + if (!getSecurityEventService() || !getSecurityEventService()->setup()) { + MO_DBG_ERR("setup failure"); + return false; + } +#endif //MO_ENABLE_SECURITY_EVENT + + return true; +} + +void ModelCommon::loopCommon() { + + if (bootService) { + bootService->loop(); + } + + if (!runTasks) { + return; + } + + if (heartbeatService) { + heartbeatService->loop(); + } + +#if MO_ENABLE_DIAGNOSTICS + if (diagnosticsService) { + diagnosticsService->loop(); + } +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING + if (smartChargingService) { + smartChargingService->loop(); + } +#endif //MO_ENABLE_SMARTCHARGING +} + +void ModelCommon::setNumEvseId(unsigned int numEvseId) { + if (numEvseId >= MO_NUM_EVSEID) { + MO_DBG_ERR("invalid arg"); + return; + } + this->numEvseId = numEvseId; +} + +unsigned int ModelCommon::getNumEvseId() { + return numEvseId; +} + +BootService *ModelCommon::getBootService() { + if (!bootService) { + bootService = new BootService(context); + } + return bootService; +} + +HeartbeatService *ModelCommon::getHeartbeatService() { + if (!heartbeatService) { + heartbeatService = new HeartbeatService(context); + } + return heartbeatService; +} + +RemoteControlService *ModelCommon::getRemoteControlService() { + if (!remoteControlService) { + remoteControlService = new RemoteControlService(context); + } + return remoteControlService; +} + +#if MO_ENABLE_DIAGNOSTICS +DiagnosticsService *ModelCommon::getDiagnosticsService() { + if (!diagnosticsService) { + diagnosticsService = new DiagnosticsService(context); + } + return diagnosticsService; +} +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING +SmartChargingService* ModelCommon::getSmartChargingService() { + if (!smartChargingService) { + smartChargingService = new SmartChargingService(context); + } + return smartChargingService; +} +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT +CertificateService *ModelCommon::getCertificateService() { + if (!certService) { + certService = new CertificateService(context); + } + return certService; +} +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT +SecurityEventService *ModelCommon::getSecurityEventService() { + if (!secEventService) { + secEventService = new SecurityEventService(context); + } + return secEventService; +} +#endif //MO_ENABLE_SECURITY_EVENT + +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 + +#if MO_ENABLE_V16 + +v16::Model::Model(Context& context) : MemoryManaged("v16.Model"), ModelCommon(context), context(context) { + +} + +v16::Model::~Model() { delete transactionService; transactionService = nullptr; delete meteringService; @@ -50,19 +228,12 @@ v16::Model::~Model() { resetService = nullptr; delete availabilityService; availabilityService = nullptr; - delete remoteControlService; - remoteControlService = nullptr; #if MO_ENABLE_FIRMWAREMANAGEMENT delete firmwareService; firmwareService = nullptr; #endif //MO_ENABLE_FIRMWAREMANAGEMENT -#if MO_ENABLE_DIAGNOSTICS - delete diagnosticsService; - diagnosticsService = nullptr; -#endif //MO_ENABLE_DIAGNOSTICS - #if MO_ENABLE_LOCAL_AUTH delete authorizationService; authorizationService = nullptr; @@ -73,21 +244,6 @@ v16::Model::~Model() { reservationService = nullptr; #endif //MO_ENABLE_RESERVATION -#if MO_ENABLE_SMARTCHARGING - delete smartChargingService; - smartChargingService = nullptr; -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - delete certService; - certService = nullptr; -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - delete secEventService; - secEventService = nullptr; -#endif //MO_ENABLE_SECURITY_EVENT - delete configurationService; configurationService = nullptr; } @@ -106,9 +262,9 @@ void v16::Model::updateSupportedStandardProfiles() { if (transactionService && availabilityService && - remoteControlService && - heartbeatService && - bootService) { + getRemoteControlService() && + getHeartbeatService() && + getBootService()) { if (!strstr(supportedFeatureProfilesString->getString(), "Core")) { if (!buf.empty()) buf += ','; buf += "Core"; @@ -116,7 +272,7 @@ void v16::Model::updateSupportedStandardProfiles() { } if (firmwareService || - diagnosticsService) { + getDiagnosticsService()) { if (!strstr(supportedFeatureProfilesString->getString(), "FirmwareManagement")) { if (!buf.empty()) buf += ','; buf += "FirmwareManagement"; @@ -141,7 +297,7 @@ void v16::Model::updateSupportedStandardProfiles() { } #endif //MO_ENABLE_RESERVATION - if (smartChargingService) { + if (getSmartChargingService()) { if (!strstr(supportedFeatureProfilesString->getString(), "SmartCharging")) { if (!buf.empty()) buf += ','; buf += "SmartCharging"; @@ -158,32 +314,6 @@ void v16::Model::updateSupportedStandardProfiles() { MO_DBG_DEBUG("supported feature profiles: %s", buf.c_str()); } -void v16::Model::setNumEvseId(unsigned int numEvseId) { - if (numEvseId >= MO_NUM_EVSEID) { - MO_DBG_ERR("invalid arg"); - return; - } - this->numEvseId = numEvseId; -} - -unsigned int v16::Model::getNumEvseId() { - return numEvseId; -} - -BootService *v16::Model::getBootService() { - if (!bootService) { - bootService = new BootService(context); - } - return bootService; -} - -HeartbeatService *v16::Model::getHeartbeatService() { - if (!heartbeatService) { - heartbeatService = new HeartbeatService(context); - } - return heartbeatService; -} - v16::ConfigurationService *v16::Model::getConfigurationService() { if (!configurationService) { configurationService = new v16::ConfigurationService(context); @@ -226,13 +356,6 @@ v16::AvailabilityService *v16::Model::getAvailabilityService() { return availabilityService; } -RemoteControlService *v16::Model::getRemoteControlService() { - if (!remoteControlService) { - remoteControlService = new RemoteControlService(context); - } - return remoteControlService; -} - #if MO_ENABLE_FIRMWAREMANAGEMENT v16::FirmwareService *v16::Model::getFirmwareService() { if (!firmwareService) { @@ -242,15 +365,6 @@ v16::FirmwareService *v16::Model::getFirmwareService() { } #endif //MO_ENABLE_FIRMWAREMANAGEMENT -#if MO_ENABLE_DIAGNOSTICS -DiagnosticsService *v16::Model::getDiagnosticsService() { - if (!diagnosticsService) { - diagnosticsService = new DiagnosticsService(context); - } - return diagnosticsService; -} -#endif //MO_ENABLE_DIAGNOSTICS - #if MO_ENABLE_LOCAL_AUTH v16::AuthorizationService *v16::Model::getAuthorizationService() { if (!authorizationService) { @@ -269,40 +383,9 @@ v16::ReservationService *v16::Model::getReservationService() { } #endif //MO_ENABLE_RESERVATION -#if MO_ENABLE_SMARTCHARGING -SmartChargingService* v16::Model::getSmartChargingService() { - if (!smartChargingService) { - smartChargingService = new SmartChargingService(context); - } - return smartChargingService; -} -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT -CertificateService *v16::Model::getCertificateService() { - if (!certService) { - certService = new CertificateService(context); - } - return certService; -} -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT -SecurityEventService *v16::Model::getSecurityEventService() { - if (!secEventService) { - secEventService = new SecurityEventService(context); - } - return secEventService; -} -#endif //MO_ENABLE_SECURITY_EVENT - bool v16::Model::setup() { - if (!getBootService() || !getBootService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } - if (!getHeartbeatService() || !getHeartbeatService()->setup()) { + if (!setupCommon()) { MO_DBG_ERR("setup failure"); return false; } @@ -327,11 +410,6 @@ bool v16::Model::setup() { return false; } - if (!getRemoteControlService() || !getRemoteControlService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } - #if MO_ENABLE_FIRMWAREMANAGEMENT if (!getFirmwareService() || !getFirmwareService()->setup()) { MO_DBG_ERR("setup failure"); @@ -339,13 +417,6 @@ bool v16::Model::setup() { } #endif //MO_ENABLE_FIRMWAREMANAGEMENT -#if MO_ENABLE_DIAGNOSTICS - if (!getDiagnosticsService() || !getDiagnosticsService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_DIAGNOSTICS - #if MO_ENABLE_LOCAL_AUTH if (!getAuthorizationService() || !getAuthorizationService()->setup()) { MO_DBG_ERR("setup failure"); @@ -360,27 +431,6 @@ bool v16::Model::setup() { } #endif //MO_ENABLE_RESERVATION -#if MO_ENABLE_SMARTCHARGING - if (!getSmartChargingService() || !getSmartChargingService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - if (!getCertificateService() || !getCertificateService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - if (!getSecurityEventService() || !getSecurityEventService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_SECURITY_EVENT - // Ensure this is set up last. ConfigurationService::setup() loads the persistent config values from flash if (!getConfigurationService() || !getConfigurationService()->setup()) { MO_DBG_ERR("setup failure"); @@ -398,18 +448,12 @@ bool v16::Model::setup() { void v16::Model::loop() { - if (bootService) { - bootService->loop(); - } + loopCommon(); if (!runTasks) { return; } - if (heartbeatService) { - heartbeatService->loop(); - } - if (transactionService) { transactionService->loop(); } @@ -432,23 +476,11 @@ void v16::Model::loop() { } #endif //MO_ENABLE_FIRMWAREMANAGEMENT -#if MO_ENABLE_DIAGNOSTICS - if (diagnosticsService) { - diagnosticsService->loop(); - } -#endif //MO_ENABLE_DIAGNOSTICS - #if MO_ENABLE_RESERVATION if (reservationService) { reservationService->loop(); } #endif //MO_ENABLE_RESERVATION - -#if MO_ENABLE_SMARTCHARGING - if (smartChargingService) { - smartChargingService->loop(); - } -#endif //MO_ENABLE_SMARTCHARGING } #endif //MO_ENABLE_V16 @@ -457,15 +489,11 @@ void v16::Model::loop() { using namespace MicroOcpp; -v201::Model::Model(Context& context) : MemoryManaged("v201.Model"), context(context) { +v201::Model::Model(Context& context) : MemoryManaged("v201.Model"), ModelCommon(context), context(context) { } v201::Model::~Model() { - delete bootService; - bootService = nullptr; - delete heartbeatService; - heartbeatService = nullptr; delete transactionService; transactionService = nullptr; delete meteringService; @@ -474,59 +502,10 @@ v201::Model::~Model() { resetService = nullptr; delete availabilityService; availabilityService = nullptr; - delete remoteControlService; - remoteControlService = nullptr; - -#if MO_ENABLE_DIAGNOSTICS - delete diagnosticsService; - diagnosticsService = nullptr; -#endif //MO_ENABLE_DIAGNOSTICS - -#if MO_ENABLE_SMARTCHARGING - delete smartChargingService; - smartChargingService = nullptr; -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - delete certService; - certService = nullptr; -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - delete secEventService; - secEventService = nullptr; -#endif //MO_ENABLE_SECURITY_EVENT - delete variableService; variableService = nullptr; } -void v201::Model::setNumEvseId(unsigned int numEvseId) { - if (numEvseId >= MO_NUM_EVSEID) { - MO_DBG_ERR("invalid arg"); - return; - } - this->numEvseId = numEvseId; -} - -unsigned int v201::Model::getNumEvseId() { - return numEvseId; -} - -BootService *v201::Model::getBootService() { - if (!bootService) { - bootService = new BootService(context); - } - return bootService; -} - -HeartbeatService *v201::Model::getHeartbeatService() { - if (!heartbeatService) { - heartbeatService = new HeartbeatService(context); - } - return heartbeatService; -} - v201::VariableService *v201::Model::getVariableService() { if (!variableService) { variableService = new v201::VariableService(context); @@ -569,56 +548,9 @@ v201::AvailabilityService *v201::Model::getAvailabilityService() { return availabilityService; } -RemoteControlService *v201::Model::getRemoteControlService() { - if (!remoteControlService) { - remoteControlService = new RemoteControlService(context); - } - return remoteControlService; -} - -#if MO_ENABLE_DIAGNOSTICS -DiagnosticsService *v201::Model::getDiagnosticsService() { - if (!diagnosticsService) { - diagnosticsService = new DiagnosticsService(context); - } - return diagnosticsService; -} -#endif //MO_ENABLE_DIAGNOSTICS - -#if MO_ENABLE_SMARTCHARGING -SmartChargingService* v201::Model::getSmartChargingService() { - if (!smartChargingService) { - smartChargingService = new SmartChargingService(context); - } - return smartChargingService; -} -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT -CertificateService *v201::Model::getCertificateService() { - if (!certService) { - certService = new CertificateService(context); - } - return certService; -} -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT -SecurityEventService *v201::Model::getSecurityEventService() { - if (!secEventService) { - secEventService = new SecurityEventService(context); - } - return secEventService; -} -#endif //MO_ENABLE_SECURITY_EVENT - bool v201::Model::setup() { - if (!getBootService() || !getBootService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } - if (!getHeartbeatService() || !getHeartbeatService()->setup()) { + if (!setupCommon()) { MO_DBG_ERR("setup failure"); return false; } @@ -637,44 +569,11 @@ bool v201::Model::setup() { return false; } - if (!getRemoteControlService() || !getRemoteControlService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } - if (!getResetService() || !getResetService()->setup()) { MO_DBG_ERR("setup failure"); return false; } -#if MO_ENABLE_DIAGNOSTICS - if (!getDiagnosticsService() || !getDiagnosticsService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_DIAGNOSTICS - -#if MO_ENABLE_SMARTCHARGING - if (!getSmartChargingService() || !getSmartChargingService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - if (!getCertificateService() || !getCertificateService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - if (!getSecurityEventService() || !getSecurityEventService()->setup()) { - MO_DBG_ERR("setup failure"); - return false; - } -#endif //MO_ENABLE_SECURITY_EVENT - // Ensure this is set up last. VariableService::setup() loads the persistent variable values from flash if (!getVariableService() || !getVariableService()->setup()) { MO_DBG_ERR("setup failure"); @@ -686,9 +585,7 @@ bool v201::Model::setup() { void v201::Model::loop() { - if (bootService) { - bootService->loop(); - } + loopCommon(); if (variableService) { variableService->loop(); @@ -698,10 +595,6 @@ void v201::Model::loop() { return; } - if (heartbeatService) { - heartbeatService->loop(); - } - if (transactionService) { transactionService->loop(); } @@ -713,18 +606,6 @@ void v201::Model::loop() { if (availabilityService) { availabilityService->loop(); } - -#if MO_ENABLE_DIAGNOSTICS - if (diagnosticsService) { - diagnosticsService->loop(); - } -#endif //MO_ENABLE_DIAGNOSTICS - -#if MO_ENABLE_SMARTCHARGING - if (smartChargingService) { - smartChargingService->loop(); - } -#endif //MO_ENABLE_SMARTCHARGING } #endif //MO_ENABLE_V201 diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 35272ec4..e579e69a 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -12,6 +12,13 @@ namespace MicroOcpp { class Context; + +} //namespace MicroOcpp + +#if MO_ENABLE_V16 || MO_ENABLE_V201 + +namespace MicroOcpp { + class BootService; class HeartbeatService; class RemoteControlService; @@ -32,8 +39,76 @@ class CertificateService; class SecurityEventService; #endif //MO_ENABLE_SECURITY_EVENT +//For Model modules which can be used with OCPP 1.6 and OCPP 2.0.1 +class ModelCommon { +private: + Context& context; + + BootService *bootService = nullptr; + HeartbeatService *heartbeatService = nullptr; + RemoteControlService *remoteControlService = nullptr; + +#if MO_ENABLE_DIAGNOSTICS + DiagnosticsService *diagnosticsService = nullptr; +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING + SmartChargingService *smartChargingService = nullptr; +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT + CertificateService *certService = nullptr; +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT + SecurityEventService *secEventService = nullptr; +#endif //MO_ENABLE_SECURITY_EVENT + +protected: + + unsigned int numEvseId = MO_NUM_EVSEID; + bool runTasks = false; + + ModelCommon(Context& context); + ~ModelCommon(); + + bool setupCommon(); + void loopCommon(); + +public: + + // Set number of EVSE IDs (including 0). On a charger with one physical connector, numEvseId is 2. Default value is MO_NUM_EVSEID + void setNumEvseId(unsigned int numEvseId); + unsigned int getNumEvseId(); + + BootService *getBootService(); + HeartbeatService *getHeartbeatService(); + RemoteControlService *getRemoteControlService(); + +#if MO_ENABLE_DIAGNOSTICS + DiagnosticsService *getDiagnosticsService(); +#endif //MO_ENABLE_DIAGNOSTICS + +#if MO_ENABLE_SMARTCHARGING + SmartChargingService *getSmartChargingService(); +#endif //MO_ENABLE_SMARTCHARGING + +#if MO_ENABLE_CERT_MGMT + CertificateService *getCertificateService(); +#endif //MO_ENABLE_CERT_MGMT + +#if MO_ENABLE_SECURITY_EVENT + SecurityEventService *getSecurityEventService(); +#endif //MO_ENABLE_SECURITY_EVENT + + void activateTasks() {runTasks = true;} + +}; + } //namespace MicroOcpp +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 + #if MO_ENABLE_V16 namespace MicroOcpp { @@ -57,27 +132,20 @@ class AuthorizationService; class ReservationService; #endif //MO_ENABLE_RESERVATION -class Model : public MemoryManaged { +class Model : public MemoryManaged, public ModelCommon { private: Context& context; - BootService *bootService = nullptr; - HeartbeatService *heartbeatService = nullptr; ConfigurationService *configurationService = nullptr; TransactionService *transactionService = nullptr; MeteringService *meteringService = nullptr; ResetService *resetService = nullptr; AvailabilityService *availabilityService = nullptr; - RemoteControlService *remoteControlService = nullptr; #if MO_ENABLE_FIRMWAREMANAGEMENT FirmwareService *firmwareService = nullptr; #endif //MO_ENABLE_FIRMWAREMANAGEMENT -#if MO_ENABLE_DIAGNOSTICS - DiagnosticsService *diagnosticsService = nullptr; -#endif //MO_ENABLE_DIAGNOSTICS - #if MO_ENABLE_LOCAL_AUTH AuthorizationService *authorizationService = nullptr; #endif //MO_ENABLE_LOCAL_AUTH @@ -86,48 +154,22 @@ class Model : public MemoryManaged { ReservationService *reservationService = nullptr; #endif //MO_ENABLE_RESERVATION -#if MO_ENABLE_SMARTCHARGING - SmartChargingService *smartChargingService = nullptr; -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - CertificateService *certService = nullptr; -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - SecurityEventService *secEventService = nullptr; -#endif //MO_ENABLE_SECURITY_EVENT - - unsigned int numEvseId = MO_NUM_EVSEID; - bool runTasks = false; - void updateSupportedStandardProfiles(); public: Model(Context& context); ~Model(); - // Set number of EVSE IDs (including 0). On a charger with one physical connector, numEvseId is 2. Default value is MO_NUM_EVSEID - void setNumEvseId(unsigned int numEvseId); - unsigned int getNumEvseId(); - - BootService *getBootService(); - HeartbeatService *getHeartbeatService(); ConfigurationService *getConfigurationService(); TransactionService *getTransactionService(); MeteringService *getMeteringService(); ResetService *getResetService(); AvailabilityService *getAvailabilityService(); - RemoteControlService *getRemoteControlService(); #if MO_ENABLE_FIRMWAREMANAGEMENT FirmwareService *getFirmwareService(); #endif //MO_ENABLE_FIRMWAREMANAGEMENT -#if MO_ENABLE_DIAGNOSTICS - DiagnosticsService *getDiagnosticsService(); -#endif //MO_ENABLE_DIAGNOSTICS - #if MO_ENABLE_LOCAL_AUTH AuthorizationService *getAuthorizationService(); #endif //MO_ENABLE_LOCAL_AUTH @@ -136,21 +178,8 @@ class Model : public MemoryManaged { ReservationService *getReservationService(); #endif //MO_ENABLE_RESERVATION -#if MO_ENABLE_SMARTCHARGING - SmartChargingService *getSmartChargingService(); -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - CertificateService *getCertificateService(); -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - SecurityEventService *getSecurityEventService(); -#endif //MO_ENABLE_SECURITY_EVENT - bool setup(); void loop(); - void activateTasks() {runTasks = true;} }; } //namespace v16 @@ -168,74 +197,28 @@ class MeteringService; class ResetService; class AvailabilityService; -class Model : public MemoryManaged { +class Model : public MemoryManaged, public ModelCommon { private: Context& context; - BootService *bootService = nullptr; - HeartbeatService *heartbeatService = nullptr; VariableService *variableService = nullptr; TransactionService *transactionService = nullptr; MeteringService *meteringService = nullptr; ResetService *resetService = nullptr; AvailabilityService *availabilityService = nullptr; - RemoteControlService *remoteControlService = nullptr; - -#if MO_ENABLE_DIAGNOSTICS - DiagnosticsService *diagnosticsService = nullptr; -#endif //MO_ENABLE_DIAGNOSTICS - -#if MO_ENABLE_SMARTCHARGING - SmartChargingService *smartChargingService = nullptr; -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - CertificateService *certService = nullptr; -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - SecurityEventService *secEventService = nullptr; -#endif //MO_ENABLE_SECURITY_EVENT - - unsigned int numEvseId = MO_NUM_EVSEID; - bool runTasks = false; public: Model(Context& context); ~Model(); - // Set number of EVSE IDs (including 0). On a charger with one physical connector, numEvseId is 2. Default value is MO_NUM_EVSEID - void setNumEvseId(unsigned int numEvseId); - unsigned int getNumEvseId(); - - BootService *getBootService(); - HeartbeatService *getHeartbeatService(); VariableService *getVariableService(); TransactionService *getTransactionService(); MeteringService *getMeteringService(); ResetService *getResetService(); AvailabilityService *getAvailabilityService(); - RemoteControlService *getRemoteControlService(); - -#if MO_ENABLE_DIAGNOSTICS - DiagnosticsService *getDiagnosticsService(); -#endif //MO_ENABLE_DIAGNOSTICS - -#if MO_ENABLE_SMARTCHARGING - SmartChargingService *getSmartChargingService(); -#endif //MO_ENABLE_SMARTCHARGING - -#if MO_ENABLE_CERT_MGMT - CertificateService *getCertificateService(); -#endif //MO_ENABLE_CERT_MGMT - -#if MO_ENABLE_SECURITY_EVENT - SecurityEventService *getSecurityEventService(); -#endif //MO_ENABLE_SECURITY_EVENT bool setup(); void loop(); - void activateTasks() {runTasks = true;} }; } //namespace v201 diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index 55f444ec..58e064a5 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -162,8 +162,6 @@ RemoteControlServiceEvse *RemoteControlService::getEvse(unsigned int evseId) { bool RemoteControlService::setup() { - unsigned int numEvseId = MO_NUM_EVSEID; - #if MO_ENABLE_V16 if (context.getOcppVersion() == MO_OCPP_V16) { @@ -187,8 +185,6 @@ bool RemoteControlService::setup() { return false; } - numEvseId = context.getModel16().getNumEvseId(); - context.getMessageService().registerOperation("RemoteStartTransaction", [] (Context& context) -> Operation* { return new v16::RemoteStartTransaction(context, *context.getModel16().getRemoteControlService());}); context.getMessageService().registerOperation("RemoteStopTransaction", [] (Context& context) -> Operation* { @@ -217,8 +213,6 @@ bool RemoteControlService::setup() { return false; } - numEvseId = context.getModel201().getNumEvseId(); - context.getMessageService().registerOperation("RequestStartTransaction", [] (Context& context) -> Operation* { return new v201::RequestStartTransaction(context, *context.getModel201().getRemoteControlService());}); context.getMessageService().registerOperation("RequestStopTransaction", [] (Context& context) -> Operation* { @@ -232,19 +226,9 @@ bool RemoteControlService::setup() { #endif context.getMessageService().registerOperation("TriggerMessage", [] (Context& context) -> Operation* { - RemoteControlService *rcSvc = nullptr; - #if MO_ENABLE_V16 - if (context.getOcppVersion() == MO_OCPP_V16) { - rcSvc = context.getModel16().getRemoteControlService(); - } - #endif //MO_ENABLE_V16 - #if MO_ENABLE_V201 - if (context.getOcppVersion() == MO_OCPP_V201) { - rcSvc = context.getModel201().getRemoteControlService(); - } - #endif //MO_ENABLE_V201 - return new TriggerMessage(context, *rcSvc);}); + return new TriggerMessage(context, *context.getModelCommon().getRemoteControlService());}); + numEvseId = context.getModelCommon().getNumEvseId(); for (unsigned int i = 0; i < numEvseId; i++) { if (!getEvse(i) || !getEvse(i)->setup()) { MO_DBG_ERR("setup failure"); diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index 1a316499..f0dbbdd1 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -552,8 +552,6 @@ bool SmartChargingService::setup() { if (phases3to1Supported) { configService->declareConfiguration("ConnectorSwitch3to1PhaseSupported", phases3to1Supported, MO_CONFIGURATION_VOLATILE, Mutability::ReadOnly); } - - numEvseId = context.getModel16().getNumEvseId(); } #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 @@ -592,32 +590,17 @@ bool SmartChargingService::setup() { varService->declareVariable("SmartChargingCtrlr", "Phases3to1", phases3to1Supported, Mutability::ReadOnly, false); } varService->declareVariable("SmartChargingCtrlr", "SmartChargingAvailable", true, Mutability::ReadOnly, false); - - numEvseId = context.getModel201().getNumEvseId(); } #endif //MO_ENABLE_V201 - #if MO_ENABLE_V16 - if (ocppVersion == MO_OCPP_V16) { - context.getMessageService().registerOperation("ClearChargingProfile", [] (Context& context) -> Operation* { - return new ClearChargingProfile(*context.getModel16().getSmartChargingService(), MO_OCPP_V16);}); - context.getMessageService().registerOperation("GetCompositeSchedule", [] (Context& context) -> Operation* { - return new GetCompositeSchedule(context, *context.getModel16().getSmartChargingService());}); - context.getMessageService().registerOperation("SetChargingProfile", [] (Context& context) -> Operation* { - return new SetChargingProfile(context, *context.getModel16().getSmartChargingService());}); - } - #endif //MO_ENABLE_V16 - #if MO_ENABLE_V201 - if (ocppVersion == MO_OCPP_V201) { - context.getMessageService().registerOperation("ClearChargingProfile", [] (Context& context) -> Operation* { - return new ClearChargingProfile(*context.getModel201().getSmartChargingService(), MO_OCPP_V201);}); - context.getMessageService().registerOperation("GetCompositeSchedule", [] (Context& context) -> Operation* { - return new GetCompositeSchedule(context, *context.getModel201().getSmartChargingService());}); - context.getMessageService().registerOperation("SetChargingProfile", [] (Context& context) -> Operation* { - return new SetChargingProfile(context, *context.getModel201().getSmartChargingService());}); - } - #endif //MO_ENABLE_V201 + context.getMessageService().registerOperation("ClearChargingProfile", [] (Context& context) -> Operation* { + return new ClearChargingProfile(*context.getModelCommon().getSmartChargingService(), MO_OCPP_V16);}); + context.getMessageService().registerOperation("GetCompositeSchedule", [] (Context& context) -> Operation* { + return new GetCompositeSchedule(context, *context.getModelCommon().getSmartChargingService());}); + context.getMessageService().registerOperation("SetChargingProfile", [] (Context& context) -> Operation* { + return new SetChargingProfile(context, *context.getModelCommon().getSmartChargingService());}); + numEvseId = context.getModelCommon().getNumEvseId(); for (unsigned int i = 1; i < numEvseId; i++) { //evseId 0 won't be populated if (!getEvse(i)) { MO_DBG_ERR("OOM"); diff --git a/src/MicroOcpp/Operations/GetCompositeSchedule.cpp b/src/MicroOcpp/Operations/GetCompositeSchedule.cpp index ce9687cd..b12ed4b1 100644 --- a/src/MicroOcpp/Operations/GetCompositeSchedule.cpp +++ b/src/MicroOcpp/Operations/GetCompositeSchedule.cpp @@ -23,18 +23,14 @@ const char* GetCompositeSchedule::getOperationType() { void GetCompositeSchedule::processReq(JsonObject payload) { - unsigned int numEvseId = MO_NUM_EVSEID; - #if MO_ENABLE_V16 if (ocppVersion == MO_OCPP_V16) { evseId = payload["connectorId"] | -1; - numEvseId = context.getModel16().getNumEvseId(); } #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 if (ocppVersion == MO_OCPP_V201) { evseId = payload["evseId"] | -1; - numEvseId = context.getModel201().getNumEvseId(); } #endif //MO_ENABLE_V201 @@ -45,7 +41,7 @@ void GetCompositeSchedule::processReq(JsonObject payload) { return; } - if ((unsigned int) evseId >= numEvseId) { + if ((unsigned int) evseId >= context.getModelCommon().getNumEvseId()) { errorCode = "PropertyConstraintViolation"; } diff --git a/src/MicroOcpp/Operations/SetChargingProfile.cpp b/src/MicroOcpp/Operations/SetChargingProfile.cpp index fefdd434..aba8350c 100644 --- a/src/MicroOcpp/Operations/SetChargingProfile.cpp +++ b/src/MicroOcpp/Operations/SetChargingProfile.cpp @@ -39,12 +39,6 @@ void SetChargingProfile::processReq(JsonObject payload) { errorCode = "FormationViolation"; return; } - - if ((unsigned int) evseId >= context.getModel16().getNumEvseId()) { - errorCode = "PropertyConstraintViolation"; - return; - } - chargingProfileJson = payload["csChargingProfiles"]; } #endif //MO_ENABLE_V16 @@ -55,16 +49,15 @@ void SetChargingProfile::processReq(JsonObject payload) { errorCode = "FormationViolation"; return; } - - if ((unsigned int) evseId >= context.getModel201().getNumEvseId()) { - errorCode = "PropertyConstraintViolation"; - return; - } - chargingProfileJson = payload["chargingProfile"]; } #endif //MO_ENABLE_V201 + if ((unsigned int) evseId >= context.getModelCommon().getNumEvseId()) { + errorCode = "PropertyConstraintViolation"; + return; + } + auto chargingProfile = std::unique_ptr(new ChargingProfile()); if (!chargingProfile) { MO_DBG_ERR("OOM"); diff --git a/src/MicroOcpp/Operations/TriggerMessage.cpp b/src/MicroOcpp/Operations/TriggerMessage.cpp index 4ea639f2..e86b572b 100644 --- a/src/MicroOcpp/Operations/TriggerMessage.cpp +++ b/src/MicroOcpp/Operations/TriggerMessage.cpp @@ -34,17 +34,13 @@ void TriggerMessage::processReq(JsonObject payload) { const char *requestedMessage = payload["requestedMessage"]; int evseId = -1; - unsigned int numEvseId = MO_NUM_EVSEID; - #if MO_ENABLE_V16 if (context.getOcppVersion() == MO_OCPP_V16) { - numEvseId = context.getModel16().getNumEvseId(); evseId = payload["connectorId"] | -1; } #endif //MO_ENABLE_V16 #if MO_ENABLE_V201 if (context.getOcppVersion() == MO_OCPP_V201) { - numEvseId = context.getModel201().getNumEvseId(); evseId = payload["evse"]["id"] | -1; if ((payload["evse"]["connectorId"] | 1) != 1) { @@ -54,7 +50,7 @@ void TriggerMessage::processReq(JsonObject payload) { } #endif //MO_ENABLE_V201 - if (evseId >= 0 && (unsigned int)evseId >= numEvseId) { + if (evseId >= 0 && (unsigned int)evseId >= context.getModelCommon().getNumEvseId()) { errorCode = "PropertyConstraintViolation"; return; } diff --git a/tests/SmartCharging.cpp b/tests/SmartCharging.cpp index c0f16e9c..8d5036ad 100644 --- a/tests/SmartCharging.cpp +++ b/tests/SmartCharging.cpp @@ -105,12 +105,7 @@ TEST_CASE( "SmartCharging" ) { loop(); - MicroOcpp::SmartChargingService *scService = nullptr; - if (ocppVersion == MO_OCPP_V16) { - scService = mo_getContext()->getModel16().getSmartChargingService(); - } else if (ocppVersion == MO_OCPP_V201) { - scService = mo_getContext()->getModel201().getSmartChargingService(); - } + MicroOcpp::SmartChargingService *scService = mo_getContext()->getModelCommon().getSmartChargingService(); REQUIRE(scService != nullptr); SECTION("Clean up files") { @@ -151,11 +146,7 @@ TEST_CASE( "SmartCharging" ) { mo_getContext()->setConnection(&loopback); mo_setOcppVersion(ocppVersion); mo_setup(); - if (ocppVersion == MO_OCPP_V16) { - scService = mo_getContext()->getModel16().getSmartChargingService(); - } else if (ocppVersion == MO_OCPP_V201) { - scService = mo_getContext()->getModel201().getSmartChargingService(); - } + scService = mo_getContext()->getModelCommon().getSmartChargingService(); REQUIRE(scService->getChargingProfilesCount() == 1); REQUIRE(scService->clearChargingProfile(-1, -1, MicroOcpp::ChargingProfilePurposeType::UNDEFINED, -1)); From 14e9f80b7558173541c052a805aaf6b4add336ff Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 6 Jul 2025 19:38:29 +0200 Subject: [PATCH 20/50] API cleanup --- src/MicroOcpp.cpp | 32 ++++++++--------- src/MicroOcpp.h | 10 +++--- src/MicroOcpp/Context.cpp | 16 +++++++-- src/MicroOcpp/Context.h | 2 +- src/MicroOcpp/Core/FilesystemAdapter.cpp | 35 +++++++++++++++++++ src/MicroOcpp/Core/FilesystemAdapter.h | 7 ++++ .../Authorization/AuthorizationService.cpp | 4 +-- .../Model/Availability/AvailabilityDefs.cpp | 6 ++-- .../Model/Availability/AvailabilityDefs.h | 6 ++-- .../Availability/AvailabilityService.cpp | 4 +-- .../Model/Boot/BootNotificationData.h | 4 +-- src/MicroOcpp/Model/Boot/BootService.cpp | 2 +- src/MicroOcpp/Model/Metering/MeterValue.cpp | 8 ++--- src/MicroOcpp/Model/Metering/MeterValue.h | 8 ++--- src/MicroOcpp/Model/Reset/ResetService.cpp | 4 +-- .../SecurityEvent/SecurityEventService.cpp | 2 +- .../Transactions/TransactionService16.cpp | 15 -------- 17 files changed, 101 insertions(+), 64 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index ec90fb70..d7034cd7 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -103,7 +103,7 @@ void mo_setFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char * auto context = mo_getContext2(ctx); MO_FilesystemConfig filesystemConfig; - memset(&filesystemConfig, 0, sizeof(filesystemConfig)); + mo_filesystemConfig_init(&filesystemConfig); filesystemConfig.opt = opt; filesystemConfig.path_prefix = pathPrefix; @@ -171,7 +171,7 @@ void mo_setOcppVersion2(MO_Context *ctx, int ocppVersion) { //Set BootNotification fields bool mo_setBootNotificationData(const char *chargePointModel, const char *chargePointVendor) { MO_BootNotificationData bnData; - mo_BootNotificationData_init(&bnData); + mo_bootNotificationData_init(&bnData); bnData.chargePointModel = chargePointModel; bnData.chargePointVendor = chargePointVendor; @@ -264,7 +264,7 @@ void mo_setConnectorPluggedInput2(MO_Context *ctx, unsigned int evseId, bool (*c //Input of the electricity meter register in Wh bool mo_setEnergyMeterInput(int32_t (*energyInput)()) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_Int); + mo_meterInput_init(&mInput, MO_MeterInputType_Int); mInput.getInt = energyInput; mInput.measurand = "Energy.Active.Import.Register"; mInput.unit = "Wh"; @@ -273,7 +273,7 @@ bool mo_setEnergyMeterInput(int32_t (*energyInput)()) { bool mo_setEnergyMeterInput2(MO_Context *ctx, unsigned int evseId, int32_t (*energyInput2)(MO_ReadingContext, unsigned int, void*), void *userData) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_IntWithArgs); + mo_meterInput_init(&mInput, MO_MeterInputType_IntWithArgs); mInput.getInt2 = energyInput2; mInput.measurand = "Energy.Active.Import.Register"; mInput.unit = "Wh"; @@ -284,7 +284,7 @@ bool mo_setEnergyMeterInput2(MO_Context *ctx, unsigned int evseId, int32_t (*ene //Input of the power meter reading in W bool mo_setPowerMeterInput(float (*powerInput)()) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_Float); + mo_meterInput_init(&mInput, MO_MeterInputType_Float); mInput.getFloat = powerInput; mInput.measurand = "Power.Active.Import"; mInput.unit = "W"; @@ -293,7 +293,7 @@ bool mo_setPowerMeterInput(float (*powerInput)()) { bool mo_setPowerMeterInput2(MO_Context *ctx, unsigned int evseId, float (*powerInput2)(MO_ReadingContext, unsigned int, void*), void *userData) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_Float); + mo_meterInput_init(&mInput, MO_MeterInputType_Float); mInput.getFloat2 = powerInput2; mInput.measurand = "Power.Active.Import"; mInput.unit = "W"; @@ -1095,14 +1095,14 @@ bool mo_addErrorCodeInput(const char* (*errorCodeInput)()) { } MO_ErrorDataInput errorDataInput; - mo_ErrorDataInput_init(&errorDataInput); + mo_errorDataInput_init(&errorDataInput); errorDataInput.userData = reinterpret_cast(errorCodeInput); errorDataInput.getErrorData = [] (unsigned int, void *userData) { auto errorCodeInput = reinterpret_cast(userData); MO_ErrorData errorData; - mo_ErrorData_init(&errorData); - mo_ErrorData_setErrorCode(&errorData, errorCodeInput()); + mo_errorData_init(&errorData); + mo_errorData_setErrorCode(&errorData, errorCodeInput()); return errorData; }; @@ -1155,7 +1155,7 @@ bool mo_v16_addErrorDataInput(MO_Context *ctx, unsigned int evseId, MO_ErrorData } MO_ErrorDataInput errorDataInput; - mo_ErrorDataInput_init(&errorDataInput); + mo_errorDataInput_init(&errorDataInput); errorDataInput.getErrorData = errorData; errorDataInput.userData = userData; @@ -1212,7 +1212,7 @@ bool mo_v201_addFaultedInput(MO_Context *ctx, unsigned int evseId, bool (*faulte bool mo_addMeterValueInputInt(int32_t (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_Int); + mo_meterInput_init(&mInput, MO_MeterInputType_Int); mInput.getInt = meterInput; mInput.measurand = measurand; mInput.unit = unit; @@ -1223,7 +1223,7 @@ bool mo_addMeterValueInputInt(int32_t (*meterInput)(), const char *measurand, co bool mo_addMeterValueInputInt2(MO_Context *ctx, unsigned int evseId, int32_t (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_IntWithArgs); + mo_meterInput_init(&mInput, MO_MeterInputType_IntWithArgs); mInput.getInt2 = meterInput; mInput.measurand = measurand; mInput.unit = unit; @@ -1235,7 +1235,7 @@ bool mo_addMeterValueInputInt2(MO_Context *ctx, unsigned int evseId, int32_t (*m bool mo_addMeterValueInputFloat(float (*meterInput)(), const char *measurand, const char *unit, const char *location, const char *phase) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_Float); + mo_meterInput_init(&mInput, MO_MeterInputType_Float); mInput.getFloat = meterInput; mInput.measurand = measurand; mInput.unit = unit; @@ -1246,7 +1246,7 @@ bool mo_addMeterValueInputFloat(float (*meterInput)(), const char *measurand, co bool mo_addMeterValueInputFloat2(MO_Context *ctx, unsigned int evseId, float (*meterInput)(MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_FloatWithArgs); + mo_meterInput_init(&mInput, MO_MeterInputType_FloatWithArgs); mInput.getFloat2 = meterInput; mInput.measurand = measurand; mInput.unit = unit; @@ -1258,7 +1258,7 @@ bool mo_addMeterValueInputFloat2(MO_Context *ctx, unsigned int evseId, float (*m bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), const char *measurand, const char *unit, const char *location, const char *phase) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_String); + mo_meterInput_init(&mInput, MO_MeterInputType_String); mInput.getString = meterInput; mInput.measurand = measurand; mInput.unit = unit; @@ -1268,7 +1268,7 @@ bool mo_addMeterValueInputString(int (*meterInput)(char *buf, size_t size), cons } bool mo_addMeterValueInputString2(MO_Context *ctx, unsigned int evseId, int (*meterInput)(char *buf, size_t size, MO_ReadingContext, unsigned int, void*), const char *measurand, const char *unit, const char *location, const char *phase, void *userData) { MO_MeterInput mInput; - mo_MeterInput_init(&mInput, MO_MeterInputType_StringWithArgs); + mo_meterInput_init(&mInput, MO_MeterInputType_StringWithArgs); mInput.getString2 = meterInput; mInput.measurand = measurand; mInput.unit = unit; diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index 1d58b70d..e8d96093 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -45,9 +45,7 @@ void mo_setFilesystemConfig2(MO_Context *ctx, MO_FilesystemOpt opt, const char * #if MO_WS_USE == MO_WS_ARDUINO /* * Setup MO with links2004/WebSockets library. Only available on Arduino, for other platforms set custom - * WebSockets adapter (see examples folder). `backendUrl`, `chargeBoxId`, `authorizationKey` and - * `CA_cert` are zero-copy and must remain valid until `mo_setup()`. `CA_cert` is zero-copy and must - * outlive the MO lifecycle. + * WebSockets adapter (see examples folder). `CA_cert` is zero-copy and must outlive the MO lifecycle. * * If the connections fails, please refer to * https://github.com/matth-x/MicroOcpp/issues/36#issuecomment-989716573 for recommendations on @@ -57,7 +55,7 @@ bool mo_setWebsocketUrl( const char *backendUrl, //e.g. "wss://example.com:8443/steve/websocket/CentralSystemService". Must be defined const char *chargeBoxId, //e.g. "charger001". Can be NULL const char *authorizationKey, //authorizationKey present in the websocket message header. Can be NULL. Set this to enable OCPP Security Profile 2 - const char *CA_cert); //TLS certificate. Can be NULL. Set this to enable OCPP Security Profile 2 + const char *CA_cert); //TLS certificate. Can be NULL. Zero-copy, must outlive MO. Set this to enable OCPP Security Profile 2 #endif #if __cplusplus @@ -101,7 +99,7 @@ bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData * the energy meter stores the energy register in the global variable `e_reg`, then you can allow * this library to read it by defining the following Input and passing it to the library. * ``` - * setEnergyMeterInput([] () { + * mo_setEnergyMeterInput([] () { * return e_reg; * }); * ``` @@ -110,7 +108,7 @@ bool mo_setBootNotificationData2(MO_Context *ctx, MO_BootNotificationData bnData * For example, to let Smart Charging control the PWM signal of the Control Pilot, define the * following Output and pass it to the library. * ``` - * setSmartChargingPowerOutput([] (float p_max) { + * mo_setSmartChargingPowerOutput([] (float p_max) { * pwm = p_max / PWM_FACTOR; //(simplified example) * }); * ``` diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index 30281783..cfa88239 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -37,6 +37,11 @@ Context::~Context() { setFtpClient(nullptr); setCertificateStore(nullptr); +#if MO_USE_FILEAPI != MO_CUSTOM_FS + mo_filesystemConfig_deinit(&filesystemConfig); + filesystemConfigDefined = false; +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + #if MO_WS_USE != MO_WS_CUSTOM mo_connectionConfig_deinit(&connectionConfig); #endif //MO_WS_USE != MO_WS_CUSTOM @@ -77,9 +82,13 @@ uint32_t (*Context::getRngCb())() { } #if MO_USE_FILEAPI != MO_CUSTOM_FS -void Context::setFilesystemConfig(MO_FilesystemConfig filesystemConfig) { - this->filesystemConfig = filesystemConfig; +bool Context::setFilesystemConfig(MO_FilesystemConfig filesystemConfig) { + if (!mo_filesystemConfig_copy(&this->filesystemConfig, &filesystemConfig)) { + MO_DBG_ERR("OOM"); + return false; + } filesystemConfigDefined = true; + return true; } #endif //MO_USE_FILEAPI != MO_CUSTOM_FS @@ -104,6 +113,8 @@ MO_FilesystemAdapter *Context::getFilesystem() { return nullptr; } isFilesystemOwner = true; + mo_filesystemConfig_deinit(&filesystemConfig); + filesystemConfigDefined = false; } #endif //MO_USE_FILEAPI != MO_CUSTOM_FS @@ -303,6 +314,7 @@ bool Context::setup() { #if MO_USE_FILEAPI != MO_CUSTOM_FS if (!filesystemConfigDefined) { //set defaults + mo_filesystemConfig_init(&filesystemConfig); filesystemConfig.opt = MO_FS_OPT_USE_MOUNT; filesystemConfig.path_prefix = MO_FILENAME_PREFIX; filesystemConfigDefined = true; diff --git a/src/MicroOcpp/Context.h b/src/MicroOcpp/Context.h index 39d62a9f..ea2f9f5a 100644 --- a/src/MicroOcpp/Context.h +++ b/src/MicroOcpp/Context.h @@ -85,7 +85,7 @@ class Context : public MemoryManaged { uint32_t (*getRngCb())(); #if MO_USE_FILEAPI != MO_CUSTOM_FS - void setFilesystemConfig(MO_FilesystemConfig filesystemConfig); + bool setFilesystemConfig(MO_FilesystemConfig filesystemConfig); #endif //MO_USE_FILEAPI != MO_CUSTOM_FS void setFilesystem(MO_FilesystemAdapter *filesystem); MO_FilesystemAdapter *getFilesystem(); diff --git a/src/MicroOcpp/Core/FilesystemAdapter.cpp b/src/MicroOcpp/Core/FilesystemAdapter.cpp index 1d1f3f3d..ad5bfadb 100644 --- a/src/MicroOcpp/Core/FilesystemAdapter.cpp +++ b/src/MicroOcpp/Core/FilesystemAdapter.cpp @@ -398,6 +398,41 @@ MO_FilesystemAdapter *resetIndex(MO_FilesystemAdapter *decorator) { #endif //MO_ENABLE_FILE_INDEX +#if MO_USE_FILEAPI != MO_CUSTOM_FS + +void mo_filesystemConfig_init(MO_FilesystemConfig *config) { + memset(config, 0, sizeof(*config)); +} + +bool mo_filesystemConfig_copy(MO_FilesystemConfig *dst, MO_FilesystemConfig *src) { + + char *prefix_copy = nullptr; + + const char *prefix = src->path_prefix; + size_t prefix_len = prefix ? strlen(prefix) : 0; + if (prefix_len > 0) { + size_t prefix_size = prefix_len + 1; + prefix_copy = static_cast(MO_MALLOC("Filesystem", prefix_size)); + if (!prefix_copy) { + MO_DBG_ERR("OOM"); + return false; + } + (void)snprintf(prefix_copy, prefix_size, "%s", prefix); + } + + dst->internalBuf = prefix_copy; + dst->path_prefix = dst->internalBuf; + dst->opt = src->opt; + return true; +} + +void mo_filesystemConfig_deinit(MO_FilesystemConfig *config) { + MO_FREE(config->internalBuf); + memset(config, 0, sizeof(*config)); +} + +#endif //MO_USE_FILEAPI != MO_CUSTOM_FS + #if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS || MO_USE_FILEAPI == MO_ARDUINO_SPIFFS #if MO_USE_FILEAPI == MO_ARDUINO_LITTLEFS diff --git a/src/MicroOcpp/Core/FilesystemAdapter.h b/src/MicroOcpp/Core/FilesystemAdapter.h index bfc0b326..86078475 100644 --- a/src/MicroOcpp/Core/FilesystemAdapter.h +++ b/src/MicroOcpp/Core/FilesystemAdapter.h @@ -152,7 +152,14 @@ typedef struct { * a filename to the end of path_prefix. Can be empty if the filesystem implementation accepts the MO filenames as * path directly. MO doesn't use sub-directories. */ const char *path_prefix; + + char *internalBuf; //used by MO internally } MO_FilesystemConfig; + +void mo_filesystemConfig_init(MO_FilesystemConfig *config); +bool mo_filesystemConfig_copy(MO_FilesystemConfig *dst, MO_FilesystemConfig *src); +void mo_filesystemConfig_deinit(MO_FilesystemConfig *config); + MO_FilesystemAdapter *mo_makeDefaultFilesystemAdapter(MO_FilesystemConfig config); void mo_freeDefaultFilesystemAdapter(MO_FilesystemAdapter *filesystem); diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp index 5521381a..00e0ddf1 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationService.cpp @@ -201,8 +201,8 @@ void AuthorizationService::notifyAuthorization(const char *idTag, JsonObject idT auto cpStatus = availSvcCp ? availSvcCp->getStatus() : MO_ChargePointStatus_UNDEFINED; MO_ErrorData errorCode; - mo_ErrorData_init(&errorCode); - mo_ErrorData_setErrorCode(&errorCode, "LocalListConflict"); + mo_errorData_init(&errorCode); + mo_errorData_setErrorCode(&errorCode, "LocalListConflict"); auto statusNotification = makeRequest(context, new StatusNotification( context, diff --git a/src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp b/src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp index 17bcf6ae..001fb194 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityDefs.cpp @@ -8,12 +8,12 @@ #if MO_ENABLE_V16 -void mo_ErrorData_init(MO_ErrorData *errorData) { +void mo_errorData_init(MO_ErrorData *errorData) { memset(errorData, 0, sizeof(*errorData)); errorData->severity = 1; } -void mo_ErrorData_setErrorCode(MO_ErrorData *errorData, const char *errorCode) { +void mo_errorData_setErrorCode(MO_ErrorData *errorData, const char *errorCode) { errorData->errorCode = errorCode; if (errorCode) { errorData->isError = true; @@ -21,7 +21,7 @@ void mo_ErrorData_setErrorCode(MO_ErrorData *errorData, const char *errorCode) { } } -void mo_ErrorDataInput_init(MO_ErrorDataInput *errorDataInput) { +void mo_errorDataInput_init(MO_ErrorDataInput *errorDataInput) { memset(errorDataInput, 0, sizeof(*errorDataInput)); } diff --git a/src/MicroOcpp/Model/Availability/AvailabilityDefs.h b/src/MicroOcpp/Model/Availability/AvailabilityDefs.h index cac93922..65c0f2a8 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityDefs.h +++ b/src/MicroOcpp/Model/Availability/AvailabilityDefs.h @@ -25,15 +25,15 @@ typedef struct { const char *vendorErrorCode; //vendor-specific error code } MO_ErrorData; -void mo_ErrorData_init(MO_ErrorData *errorData); -void mo_ErrorData_setErrorCode(MO_ErrorData *errorData, const char *errorCode); +void mo_errorData_init(MO_ErrorData *errorData); +void mo_errorData_setErrorCode(MO_ErrorData *errorData, const char *errorCode); typedef struct { MO_ErrorData (*getErrorData)(unsigned int evseId, void *userData); void *userData; } MO_ErrorDataInput; -void mo_ErrorDataInput_init(MO_ErrorDataInput *errorDataInput); +void mo_errorDataInput_init(MO_ErrorDataInput *errorDataInput); #ifdef __cplusplus } //extern "C" diff --git a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp index f48bd2c8..e5224579 100644 --- a/src/MicroOcpp/Model/Availability/AvailabilityService.cpp +++ b/src/MicroOcpp/Model/Availability/AvailabilityService.cpp @@ -74,7 +74,7 @@ void v16::AvailabilityServiceEvse::loop() { } MO_ErrorData errorData; - mo_ErrorData_init(&errorData); + mo_errorData_init(&errorData); errorData.severity = 0; int errorDataIndex = -1; @@ -326,7 +326,7 @@ bool v16::AvailabilityServiceEvse::isOperative() { Operation *v16::AvailabilityServiceEvse::createTriggeredStatusNotification() { MO_ErrorData errorData; - mo_ErrorData_init(&errorData); + mo_errorData_init(&errorData); errorData.severity = 0; if (reportedErrorIndex >= 0) { diff --git a/src/MicroOcpp/Model/Boot/BootNotificationData.h b/src/MicroOcpp/Model/Boot/BootNotificationData.h index feb38fc0..fff15184 100644 --- a/src/MicroOcpp/Model/Boot/BootNotificationData.h +++ b/src/MicroOcpp/Model/Boot/BootNotificationData.h @@ -20,7 +20,7 @@ extern "C" { * ``` * // init * MO_BootNotificationData bnData; - * mo_BootNotificationData_init(&bnData); + * mo_bootNotificationData_init(&bnData); * * // set payload * bnData.chargePointVendor = "My Company Ltd."; @@ -42,7 +42,7 @@ typedef struct { const char *imsi; } MO_BootNotificationData; -void mo_BootNotificationData_init(MO_BootNotificationData *bnData); +void mo_bootNotificationData_init(MO_BootNotificationData *bnData); #ifdef __cplusplus } //extern "C" diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index 173aef8d..e0500890 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -22,7 +22,7 @@ using namespace MicroOcpp; -void mo_BootNotificationData_init(MO_BootNotificationData *bnData) { +void mo_bootNotificationData_init(MO_BootNotificationData *bnData) { memset(bnData, 0, sizeof(*bnData)); } diff --git a/src/MicroOcpp/Model/Metering/MeterValue.cpp b/src/MicroOcpp/Model/Metering/MeterValue.cpp index e957c85a..c298f526 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValue.cpp @@ -8,15 +8,15 @@ #if MO_ENABLE_V16 || MO_ENABLE_V201 -void mo_MeterInput_init(MO_MeterInput *mInput, MO_MeterInputType type) { +void mo_meterInput_init(MO_MeterInput *mInput, MO_MeterInputType type) { memset(mInput, 0, sizeof(*mInput)); - mo_MeterInput_setType(mInput, type); + mo_meterInput_setType(mInput, type); } -MO_MeterInputType mo_MeterInput_getType(MO_MeterInput *mInput) { +MO_MeterInputType mo_meterInput_getType(MO_MeterInput *mInput) { return static_cast(mInput->type); } -void mo_MeterInput_setType(MO_MeterInput *mInput, MO_MeterInputType type) { +void mo_meterInput_setType(MO_MeterInput *mInput, MO_MeterInputType type) { mInput->type = static_casttype)>(type); } diff --git a/src/MicroOcpp/Model/Metering/MeterValue.h b/src/MicroOcpp/Model/Metering/MeterValue.h index 405f1048..21571374 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.h +++ b/src/MicroOcpp/Model/Metering/MeterValue.h @@ -85,7 +85,7 @@ typedef struct { #endif //MO_ENABLE_V201 }; - uint8_t type; //MO_MeterInputType in dense representation. Use `mo_MeterInput_setType()` to define + uint8_t type; //MO_MeterInputType in dense representation. Use `mo_meterInput_setType()` to define uint8_t mo_flags; //flags which MO uses internally #if MO_ENABLE_V201 @@ -100,9 +100,9 @@ typedef struct { const char *unit; } MO_MeterInput; -void mo_MeterInput_init(MO_MeterInput *mInput, MO_MeterInputType type); -MO_MeterInputType mo_MeterInput_getType(MO_MeterInput *mInput); -void mo_MeterInput_setType(MO_MeterInput *mInput, MO_MeterInputType type); +void mo_meterInput_init(MO_MeterInput *mInput, MO_MeterInputType type); +MO_MeterInputType mo_meterInput_getType(MO_MeterInput *mInput); +void mo_meterInput_setType(MO_MeterInput *mInput, MO_MeterInputType type); #ifdef __cplusplus } //extern "C" diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index 4ecbb286..ea835e6d 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -110,8 +110,8 @@ void v16::ResetService::loop() { auto cpStatus = availSvcCp ? availSvcCp->getStatus() : MO_ChargePointStatus_UNDEFINED; MO_ErrorData errorCode; - mo_ErrorData_init(&errorCode); - mo_ErrorData_setErrorCode(&errorCode, "ResetFailure"); + mo_errorData_init(&errorCode); + mo_errorData_setErrorCode(&errorCode, "ResetFailure"); auto statusNotification = makeRequest(context, new StatusNotification( context, diff --git a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp index 7df21053..3addbd25 100644 --- a/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp +++ b/src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp @@ -48,7 +48,7 @@ using namespace MicroOcpp; -SecurityEventService::SecurityEventService(Context& context) : MemoryManaged("v201.Security.SecurityEventService"), context(context) { +SecurityEventService::SecurityEventService(Context& context) : MemoryManaged("v16/v201.Security.SecurityEventService"), context(context) { } diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index f94f1d66..efd8d12b 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -994,21 +994,6 @@ void TransactionServiceEvse::setEvReadyInput(bool (*evReady)(unsigned int, void* this->evReadyInputUserData = userData; } -/* -void TransactionServiceEvse::addErrorCodeInput(const char* (*errorCodeInput)(unsigned int)) { - addErrorDataInput([] (unsigned int evseId, void *userData) -> MO_ErrorData { - auto errorCodeInput = reinterpret_cast(userData); - - MO_ErrorData res; - mo_ErrorData_init(&res); - mo_ErrorData_setErrorCode(&res, errorCodeInput(evseId)); - return res; - }, reinterpret_cast(errorCodeInput)); - addErrorDataInput([connectorErrorCode] () -> ErrorData { - return ErrorData(connectorErrorCode()); - }); -}*/ - void TransactionServiceEvse::setStartTxReadyInput(bool (*startTxReady)(unsigned int, void*), void *userData) { this->startTxReadyInput = startTxReady; this->startTxReadyInputUserData = userData; From 83918e504b978e93033e97e48bdbddf7b9a6c596 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 7 Jul 2025 01:11:53 +0200 Subject: [PATCH 21/50] add README section --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 9434aec7..65e3d0fa 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,19 @@ OCPP 1.6 / 2.0.1 client for microcontrollers. Portable C/C++. Compatible with Es Reference usage: [OpenEVSE](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/ocpp.cpp) | Technical introduction: [Docs](https://matth-x.github.io/MicroOcpp/intro-tech) | Website: [www.micro-ocpp.com](https://www.micro-ocpp.com) +## AI-friendly code + +In 2025, the code base was largely restructured to work better with AI models. Chat bots can explain large parts of the code, write code snippets and help with the integration into the main charger firmware. Use your favorite AI tools with MicroOCPP and save hours of development time. + +The best practices for AI-friendly code are not fully clear yet. The following was considered a good start: + +- Consistency in architecture, variable names, algorithmic approaches +- More granular functions to make complexities explicit on the caller end +- Break down complexity and prefer duplicate code over deep abstraction +- Strictly define resource ownership in comments + +If your tools have issues working with something in MicroOCPP, please open an issue. Any ideas how to further optimize the code base are also highly appreciated. + ## Tester / Demo App *Main repository: [MicroOcppSimulator](https://github.com/matth-x/MicroOcppSimulator)* From 6a5b2c8fe9bbaee18843fa732f22653db54943a8 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:22:24 +0200 Subject: [PATCH 22/50] fix measure_heap script --- src/MicroOcpp/Core/MessageService.cpp | 4 +- tests/benchmarks/scripts/measure_heap.py | 90 ++++++++++++++++-------- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/MicroOcpp/Core/MessageService.cpp b/src/MicroOcpp/Core/MessageService.cpp index ffdf84e9..c9bffacf 100644 --- a/src/MicroOcpp/Core/MessageService.cpp +++ b/src/MicroOcpp/Core/MessageService.cpp @@ -128,7 +128,9 @@ void MessageService::loop() { bool success = connection->sendTXT(out.c_str(), out.length()); if (success) { - MO_DBG_DEBUG("Send %s", out.c_str()); + #ifdef MO_TRAFFIC_OUT + MO_DBG_INFO("Send %s", out.c_str()); + #endif sendReqFront->setRequestSent(); //mask as sent and wait for response / timeout } diff --git a/tests/benchmarks/scripts/measure_heap.py b/tests/benchmarks/scripts/measure_heap.py index d921c49e..2a08cdc9 100644 --- a/tests/benchmarks/scripts/measure_heap.py +++ b/tests/benchmarks/scripts/measure_heap.py @@ -107,6 +107,10 @@ max_memory_total = 0 min_memory_base = 1000 * 1000 * 1000 +watchdog_timer = 0 +exit_watchdog = False +watchdog_triggered = False + # Ensure Simulator is still running despite Reset requests via OCPP or the rmt_ctrl interface run_simulator = False exit_run_simulator_process = False @@ -115,6 +119,7 @@ def run_simulator_process(): global run_simulator global exit_run_simulator_process + global watchdog_triggered track_run_simulator = run_simulator iterations_since_last_check = 0 @@ -129,7 +134,7 @@ def run_simulator_process(): iterations_since_last_check = 0 - if not track_run_simulator and run_simulator: + if not track_run_simulator and run_simulator and not watchdog_triggered: print('Starting simulator') os.system(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator') + ' &') track_run_simulator = True @@ -190,11 +195,44 @@ def setup_simulator(): print(' - done') +def cleanup_test_driver(): + try: + print("Stop Test Driver") + response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', + headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) + print(f'Test Driver /session/stop:\n > {response.status_code}') + #print(json.dumps(response.json(), indent=4)) + except: + print('Error stopping Test Driver') + traceback.print_exc() + +def watchdog_thread(): + + global watchdog_timer + global exit_watchdog + global watchdog_triggered + + while not exit_watchdog and watchdog_timer < 120: + watchdog_timer += 1 + time.sleep(1) + + if exit_watchdog: + # watchdog has been exited gracefully + return + + watchdog_triggered = True + cleanup_test_driver() + cleanup_simulator() + print("\nTest execution timeout - terminate") + os._exit(1) + def run_measurements(): global max_memory_total global min_memory_base global run_simulator + global watchdog_timer + global watchdog_triggered print("Fetch TCs from Test Driver") @@ -249,6 +287,11 @@ def run_measurements(): print('Test case already executed - skip') continue + if watchdog_triggered: + break + + watchdog_timer = 0 + setup_simulator() time.sleep(1) @@ -293,13 +336,7 @@ def run_measurements(): # print('Test failure, abort') # break - print("Stop Test Driver") - - response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) - print(f'Test Driver /session/stop:\n > {response.status_code}') - #print(json.dumps(response.json(), indent=4)) - + cleanup_test_driver() cleanup_simulator() print('Store test results') @@ -336,6 +373,7 @@ def run_measurements(): def run_measurements_and_retry(): global exit_run_simulator_process + global exit_watchdog if ( 'TEST_DRIVER_URL' not in os.environ or 'TEST_DRIVER_CONFIG' not in os.environ or @@ -346,6 +384,9 @@ def run_measurements_and_retry(): 'MO_SIM_RMT_CTRL_CERT' not in os.environ): sys.exit('\nCould not read environment variables') + m_watchdog_thread = threading.Thread(target=watchdog_thread) + m_watchdog_thread.start() + n_tries = 3 for i in range(n_tries): @@ -367,25 +408,18 @@ def run_measurements_and_retry(): traceback.print_exc() - try: - print("Stop Test Driver") - response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', - headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']}) - print(f'Test Driver /session/stop:\n > {response.status_code}') - #print(json.dumps(response.json(), indent=4)) - except: - print('Error stopping Test Driver') - traceback.print_exc() - - cleanup_simulator() - - exit_run_simulator_process = True - run_simulator_thread.join() - - if i + 1 < n_tries: - print('Retry test cases') - else: - print('\n **Test case execution aborted**') - sys.exit('\nError running test cases') + cleanup_test_driver() + cleanup_simulator() + + exit_run_simulator_process = True + run_simulator_thread.join() + + if i + 1 < n_tries: + print('Retry test cases') + else: + print('\n **Test case execution aborted**') + + exit_watchdog = True # terminate watchdog thread + m_watchdog_thread.join() run_measurements_and_retry() From b67946b4a34d89ac5e0f975ab72d45194fb7c8ca Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:03:22 +0200 Subject: [PATCH 23/50] fix build pipeline --- .github/workflows/documentation.yml | 106 ++--- src/MicroOcpp.h | 6 +- .../Model/Diagnostics/DiagnosticsService.cpp | 2 + .../RemoteControl/RemoteControlService.h | 1 + src/MicroOcpp/Model/Reset/ResetService.cpp | 6 +- .../SmartCharging/SmartChargingService.cpp | 2 +- .../Transactions/TransactionService16.cpp | 4 + src/MicroOcpp/Operations/Heartbeat.cpp | 1 - tests/benchmarks/firmware_size/main.cpp | 119 ++++-- tests/benchmarks/firmware_size/platformio.ini | 26 +- .../benchmarks/scripts/eval_firmware_size.py | 361 +++++------------- 11 files changed, 278 insertions(+), 356 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7fc6acd6..e1a14c66 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -13,8 +13,8 @@ permissions: contents: write jobs: - build_simulator: - name: Build Simulator + measure_heap: + name: Heap measurements runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -34,7 +34,7 @@ jobs: with: repository: matth-x/MicroOcppSimulator path: MicroOcppSimulator - ref: 2cb07cdbe53954a694a29336ab31eac2d2b48673 + ref: 65b1c2e9c8a3f40907a823a0bc2f657423ebef65 submodules: 'recursive' - name: Clean MicroOcpp submodule run: | @@ -47,32 +47,8 @@ jobs: run: cmake -S ./MicroOcppSimulator -B ./MicroOcppSimulator/build -DCMAKE_CXX_FLAGS="-DMO_OVERRIDE_ALLOCATION=1 -DMO_ENABLE_HEAP_PROFILER=1" - name: Compile run: cmake --build ./MicroOcppSimulator/build -j 32 --target mo_simulator - - name: Upload Simulator executable - uses: actions/upload-artifact@v4 - with: - name: Simulator executable - path: | - MicroOcppSimulator/build/mo_simulator - MicroOcppSimulator/public/bundle.html.gz - if-no-files-found: error - retention-days: 1 - - measure_heap: - needs: build_simulator - name: Heap measurements - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.x - name: Install Python dependencies run: pip install requests paramiko pandas - - name: Get Simulator - uses: actions/download-artifact@v4 - with: - name: Simulator executable - path: MicroOcppSimulator - name: Measure heap and create reports run: | mkdir -p docs/assets/tables @@ -81,13 +57,12 @@ jobs: TEST_DRIVER_URL: ${{ secrets.TEST_DRIVER_URL }} TEST_DRIVER_CONFIG: ${{ secrets.TEST_DRIVER_CONFIG }} TEST_DRIVER_KEY: ${{ secrets.TEST_DRIVER_KEY }} + TEST_DRIVER_CERT: ${{ secrets.TEST_DRIVER_CERT }} MO_SIM_CONFIG: ${{ secrets.MO_SIM_CONFIG }} MO_SIM_OCPP_SERVER: ${{ secrets.MO_SIM_OCPP_SERVER }} - MO_SIM_API_CERT: ${{ secrets.MO_SIM_API_CERT }} - MO_SIM_API_KEY: ${{ secrets.MO_SIM_API_KEY }} - MO_SIM_API_CONFIG: ${{ secrets.MO_SIM_API_CONFIG }} - SSH_LOCAL_PRIV: ${{ secrets.SSH_LOCAL_PRIV }} - SSH_HOST_PUB: ${{ secrets.SSH_HOST_PUB }} + MO_SIM_OCPP_CERT: ${{ secrets.MO_SIM_OCPP_CERT }} + MO_SIM_RMT_CTRL_CONFIG: ${{ secrets.MO_SIM_RMT_CTRL_CONFIG }} + MO_SIM_RMT_CTRL_CERT: ${{ secrets.MO_SIM_RMT_CTRL_CERT }} - name: Upload reports uses: actions/upload-artifact@v4 with: @@ -95,9 +70,12 @@ jobs: path: docs/assets/tables if-no-files-found: error - build_firmware_size: + build_firmware: name: Build firmware runs-on: ubuntu-latest + strategy: + matrix: + part: [v16, v201, v16_v201] steps: - uses: actions/checkout@v3 - name: Cache pip @@ -119,35 +97,28 @@ jobs: python -m pip install --upgrade pip pip install --upgrade platformio - name: Run PlatformIO - run: pio ci --lib="." --build-dir="${{ github.workspace }}/../build" --keep-build-dir --project-conf="./tests/benchmarks/firmware_size/platformio.ini" ./tests/benchmarks/firmware_size/main.cpp - - name: Move firmware files # change path to location without parent dir ('..') statement (to make upload-artifact happy) + run: pio ci --lib="." --build-dir="${{ github.workspace }}/../build" --keep-build-dir --project-conf="./tests/benchmarks/firmware_size/platformio.ini" -e ${{ matrix.part }} ./tests/benchmarks/firmware_size/main.cpp + - name: Move firmware file # change path to location without parent dir ('..') statement (to make upload-artifact happy) run: | mkdir firmware - mv "${{ github.workspace }}/../build/.pio/build/v16/firmware.elf" firmware/firmware_v16.elf - mv "${{ github.workspace }}/../build/.pio/build/v201/firmware.elf" firmware/firmware_v201.elf - - name: Upload firmware linker files + mv "${{ github.workspace }}/../build/.pio/build/${{ matrix.part }}/firmware.elf" "firmware/firmware_${{ matrix.part }}.elf" + - name: Upload firmware linker file uses: actions/upload-artifact@v4 with: - name: Firmware linker files + name: Firmware linker file ${{ matrix.part }} path: firmware if-no-files-found: error retention-days: 1 - evaluate_firmware: - needs: build_firmware_size - name: Static firmware analysis + build_bloaty: + name: Build Google bloaty runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.x - uses: actions/cache@v2 with: key: ${{ github.ref }} path: .cache - - name: Install Python dependencies - run: pip install pandas - name: Get build tools run: | sudo apt update @@ -165,16 +136,53 @@ jobs: run: | cmake -B tools/bloaty/build -G Ninja -S tools/bloaty cmake --build tools/bloaty/build -j 32 - - name: Get firmware linker files + - name: Upload bloaty executable + uses: actions/upload-artifact@v4 + with: + name: Google bloaty + path: tools/bloaty/build/bloaty + if-no-files-found: error + retention-days: 1 + + evaluate_firmware: + needs: [build_firmware, build_bloaty] + name: Static firmware analysis + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v2 + with: + key: ${{ github.ref }} + path: .cache + - name: Set up Python + uses: actions/setup-python@v4 + - name: Install Python dependencies + run: pip install pandas + - name: Get firmware linker file + uses: actions/download-artifact@v4 + with: + name: Firmware linker file v16 + path: firmware + - name: Get firmware linker file + uses: actions/download-artifact@v4 + with: + name: Firmware linker file v201 + path: firmware + - name: Get firmware linker file uses: actions/download-artifact@v4 with: - name: Firmware linker files + name: Firmware linker file v16_v201 path: firmware + - name: Get bloaty executable + uses: actions/download-artifact@v4 + with: + name: Google bloaty + path: tools/bloaty/build/bloaty - name: Run bloaty run: | mkdir -p docs/assets/tables tools/bloaty/build/bloaty firmware/firmware_v16.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16.csv tools/bloaty/build/bloaty firmware/firmware_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v201.csv + tools/bloaty/build/bloaty firmware/firmware_v16_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16_v201.csv - name: Evaluate and create reports run: python tests/benchmarks/scripts/eval_firmware_size.py - name: Upload reports diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index e8d96093..0dc92d98 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -186,16 +186,12 @@ bool mo_beginTransaction_authorized2(MO_Context *ctx, unsigned int evseId, const #if MO_ENABLE_V201 //Same as mo_beginTransaction, but function name fits better for OCPP 2.0.1 and more options for 2.0.1. -//Attempt to authorize a pending transaction, or create new transactino to authorize. If local +//Attempt to authorize a pending transaction, or create new transaction to authorize. If local //authorization is enabled, will search the local whitelist if server response takes too long. //Backwards-compatible: if MO is initialized with 1.6, then this is the same as `mo_beginTransaction()` bool mo_authorizeTransaction(const char *idToken); //idTokenType ISO14443 is assumed bool mo_authorizeTransaction2(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type); bool mo_authorizeTransaction3(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type, bool validateIdToken, const char *groupIdToken); - -//Same as `mo_beginAuthorization()`, but skip authorization. `groupIdToken` can be NULL -bool mo_setTransactionAuthorized(const char *idToken, const char *groupIdToken); //idTokenType ISO14443 is assumed -bool mo_setTransactionAuthorized2(MO_Context *ctx, unsigned int evseId, const char *idToken, MO_IdTokenType type, const char *groupIdToken); #endif //MO_ENABLE_V201 /* diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 0df003d4..87920399 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -509,6 +509,7 @@ MO_UploadLogStatus DiagnosticsService::getUploadStatus() { return status; } +#if MO_ENABLE_V16 v16::DiagnosticsStatus DiagnosticsService::getUploadStatus16() { MO_UploadLogStatus status = getUploadStatus(); @@ -535,6 +536,7 @@ v16::DiagnosticsStatus DiagnosticsService::getUploadStatus16() { } return res; } +#endif //MO_ENABLE_V16 void DiagnosticsService::setDiagnosticsReader(size_t (*diagnosticsReader)(char *buf, size_t size, void *user_data), void(*onClose)(void *user_data), void *user_data) { this->diagnosticsReader = diagnosticsReader; diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h index c5857524..410720fd 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.h @@ -5,6 +5,7 @@ #ifndef MO_REMOTECONTROLSERVICE_H #define MO_REMOTECONTROLSERVICE_H +#include #include #include #include diff --git a/src/MicroOcpp/Model/Reset/ResetService.cpp b/src/MicroOcpp/Model/Reset/ResetService.cpp index ea835e6d..ebde91be 100644 --- a/src/MicroOcpp/Model/Reset/ResetService.cpp +++ b/src/MicroOcpp/Model/Reset/ResetService.cpp @@ -15,7 +15,7 @@ #include #include -#if MO_ENABLE_V16 +#if MO_ENABLE_V16 || MO_ENABLE_V201 #ifndef MO_RESET_DELAY #define MO_RESET_DELAY 10 @@ -38,6 +38,10 @@ void (*defaultExecuteResetImpl)() = nullptr; #endif //MO_PLATFORM +#endif //MO_ENABLE_V16 || MO_ENABLE_V201 + +#if MO_ENABLE_V16 + using namespace MicroOcpp; v16::ResetService::ResetService(Context& context) diff --git a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp index f0dbbdd1..e249e455 100644 --- a/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp +++ b/src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp @@ -232,7 +232,7 @@ void SmartChargingServiceEvse::trackTransaction() { } } else { //check if transaction has just been completed - if (trackTxRmtProfileId >= 0 || trackTxStart.isDefined() || trackTransactionId16 >= 0) { + if (trackTxRmtProfileId >= 0 || trackTxStart.isDefined() || *trackTransactionId201) { //yes, clear data update = true; trackTxRmtProfileId = -1; diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index efd8d12b..074f4c5e 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -28,6 +28,8 @@ #include #include +#if MO_ENABLE_V16 + #ifndef MO_TX_CLEAN_ABORTED #define MO_TX_CLEAN_ABORTED 1 #endif @@ -1499,3 +1501,5 @@ TransactionServiceEvse* TransactionService::getEvse(unsigned int evseId) { return evses[evseId]; } + +#endif //MO_ENABLE_V16 diff --git a/src/MicroOcpp/Operations/Heartbeat.cpp b/src/MicroOcpp/Operations/Heartbeat.cpp index aed8b131..e1956801 100644 --- a/src/MicroOcpp/Operations/Heartbeat.cpp +++ b/src/MicroOcpp/Operations/Heartbeat.cpp @@ -10,7 +10,6 @@ #if MO_ENABLE_V16 || MO_ENABLE_V201 using namespace MicroOcpp; -using namespace MicroOcpp::v16; Heartbeat::Heartbeat(Context& context) : MemoryManaged("v16.Operation.", "Heartbeat"), context(context) { diff --git a/tests/benchmarks/firmware_size/main.cpp b/tests/benchmarks/firmware_size/main.cpp index b296c1ce..8450bb35 100644 --- a/tests/benchmarks/firmware_size/main.cpp +++ b/tests/benchmarks/firmware_size/main.cpp @@ -1,46 +1,119 @@ // matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 +// Copyright Matthias Akstaller 2019 - 2025 // MIT License #include +/* + * Dummy invokations of all functions in MicroOcpp.h to avoid link-time optimization. This + * file is only useful for firmware size analysis. The function invokations should not be + * taken as an example. + */ + void setup() { + // Basic lifecycle mo_deinitialize(); - mo_initialize(); - mo_setup(); + mo_isInitialized(); + MO_Context *ctx = mo_getApiContext(); - mo_beginTransaction(""); - mo_beginTransaction_authorized("",""); - mo_endTransaction("",""); - mo_endTransaction_authorized("",""); - mo_isTransactionActive(); - mo_isTransactionRunning(); - mo_getTransactionIdTag(); - mo_v16_getTransactionId(); - mo_v201_getTransactionId(); - mo_ocppPermitsCharge(); - mo_getChargePointStatus(); +#if MO_USE_FILEAPI != MO_CUSTOM_FS + mo_setFilesystemConfig(MO_FS_OPT_DISABLE); + mo_setFilesystemConfig2(ctx, MO_FS_OPT_DISABLE, "/tmp"); +#endif + +#if MO_WS_USE == MO_WS_ARDUINO + mo_setWebsocketUrl("wss://example.com", "chargeBoxId", "authKey", nullptr); +#endif + + // Connection + mo_setConnection(nullptr); + mo_setConnection2(ctx, nullptr); + + // OCPP version + mo_setOcppVersion(MO_OCPP_V16); + mo_setOcppVersion2(ctx, MO_OCPP_V16); + + // BootNotification + mo_setBootNotificationData("Model", "Vendor"); + MO_BootNotificationData bnData; + mo_bootNotificationData_init(&bnData); + mo_setBootNotificationData2(ctx, bnData); + + // Inputs/Outputs mo_setConnectorPluggedInput([] () {return false;}); + mo_setConnectorPluggedInput2(ctx, 1, [] (unsigned int, void*) {return false;}, nullptr); mo_setEnergyMeterInput([] () {return 0;}); + mo_setEnergyMeterInput2(ctx, 1, [] (MO_ReadingContext, unsigned int, void*) {return 0;}, nullptr); mo_setPowerMeterInput([] () {return 0.f;}); + mo_setPowerMeterInput2(ctx, 1, [] (MO_ReadingContext, unsigned int, void*) {return 0.f;}, nullptr); +#if MO_ENABLE_SMARTCHARGING mo_setSmartChargingPowerOutput([] (float) {}); mo_setSmartChargingCurrentOutput([] (float) {}); + mo_setSmartChargingOutput(ctx, 1, [] (MO_ChargeRate, unsigned int, void*) {}, true, true, true, true, nullptr); +#endif mo_setEvReadyInput([] () {return false;}); + mo_setEvReadyInput2(ctx, 1, [] (unsigned int, void*) {return false;}, nullptr); mo_setEvseReadyInput([] () {return false;}); + mo_setEvseReadyInput2(ctx, 1, [] (unsigned int, void*) {return false;}, nullptr); mo_addErrorCodeInput([] () {return (const char*)nullptr;}); - mo_v16_addErrorDataInput(mo_getApiContext(), 0, [] (unsigned int, void*) {return MO_ErrorData();}, NULL); - mo_addMeterValueInputFloat([] () {return 0.f;}, NULL, NULL, NULL, NULL); - mo_setOccupiedInput([] () {return false;}); - mo_setStartTxReadyInput([] () {return false;}); - mo_setStopTxReadyInput([] () {return false;}); - mo_setTxNotificationOutput([] (MO_TxNotification) {}); - -#if MO_ENABLE_CONNECTOR_LOCK - mo_setOnUnlockConnector([] () {return MO_UnlockConnectorResult_UnlockFailed;}); +#if MO_ENABLE_V16 + mo_v16_addErrorDataInput(ctx, 1, [] (unsigned int, void*) {MO_ErrorData e; mo_errorData_init(&e); return e;}, nullptr); +#endif +#if MO_ENABLE_V211 + mo_v201_addFaultedInput(ctx, 1, [] (unsigned int, void*) {return false;}, nullptr); +#endif + mo_addMeterValueInputInt([] () {return 0;}, nullptr, nullptr, nullptr, nullptr); + mo_addMeterValueInputInt2(ctx, 1, [] (MO_ReadingContext, unsigned int, void*) {return 0;}, nullptr, nullptr, nullptr, nullptr, nullptr); + mo_addMeterValueInputFloat([] () {return 0.f;}, nullptr, nullptr, nullptr, nullptr); + mo_addMeterValueInputFloat2(ctx, 1, [] (MO_ReadingContext, unsigned int, void*) {return 0.f;}, nullptr, nullptr, nullptr, nullptr, nullptr); + mo_addMeterValueInputString([] (char *buf, size_t size) {if (size > 0) {buf[0] = '\0'; return 0;} return 0;}, nullptr, nullptr, nullptr, nullptr); + + // Transaction management + mo_beginTransaction(""); + mo_beginTransaction2(ctx, 1, ""); + mo_beginTransaction_authorized("", ""); + mo_beginTransaction_authorized2(ctx, 1, "", ""); +#if MO_ENABLE_V201 + mo_authorizeTransaction(""); + mo_authorizeTransaction2(ctx, 1, "", MO_IdTokenType_ISO14443); + mo_authorizeTransaction3(ctx, 1, "", MO_IdTokenType_ISO14443, false, ""); #endif + mo_endTransaction(nullptr, nullptr); + mo_endTransaction2(ctx, 1, nullptr, nullptr); + mo_endTransaction_authorized(nullptr, nullptr); + mo_endTransaction_authorized2(ctx, 1, nullptr, nullptr); +#if MO_ENABLE_V201 + mo_deauthorizeTransaction(nullptr); + mo_deauthorizeTransaction2(ctx, 1, nullptr, MO_IdTokenType_ISO14443); + mo_deauthorizeTransaction3(ctx, 1, nullptr, MO_IdTokenType_ISO14443, false, nullptr); + mo_abortTransaction(MO_TxStoppedReason_Other, MO_TxEventTriggerReason_AbnormalCondition); + mo_abortTransaction2(ctx, 1, MO_TxStoppedReason_Other, MO_TxEventTriggerReason_AbnormalCondition); +#endif + + // Status and info + mo_ocppPermitsCharge(); + mo_ocppPermitsCharge2(ctx, 1); + mo_isTransactionActive(); + mo_isTransactionActive2(ctx, 1); + mo_isTransactionRunning(); + mo_isTransactionRunning2(ctx, 1); + mo_getTransactionIdTag(); + mo_getTransactionIdTag2(ctx, 1); +#if MO_ENABLE_V201 + mo_getTransactionId(); + mo_getTransactionId2(ctx, 1); +#endif +#if MO_ENABLE_V16 + mo_v16_getTransactionId(); + mo_v16_getTransactionId2(ctx, 1); +#endif + mo_getChargePointStatus(); + mo_getChargePointStatus2(ctx, 1); + + mo_setOnUnlockConnector([] () {return MO_UnlockConnectorResult_UnlockFailed;}); mo_isOperative(); mo_v16_setOnResetNotify([] (bool) {return false;}); diff --git a/tests/benchmarks/firmware_size/platformio.ini b/tests/benchmarks/firmware_size/platformio.ini index 9873f36f..146aade1 100644 --- a/tests/benchmarks/firmware_size/platformio.ini +++ b/tests/benchmarks/firmware_size/platformio.ini @@ -9,8 +9,11 @@ framework = arduino lib_deps = bblanchon/ArduinoJson@6.20.1 build_flags= - -D MO_DBG_LEVEL=MO_DL_NONE ; don't take debug messages into account + -D MO_DBG_LEVEL=MO_DL_WARN ; don't take debug messages into account -D MO_WS_USE=MO_WS_CUSTOM + -D MO_ENABLE_MBEDTLS=1 + -D MO_ENABLE_CERT_MGMT=1 + -D MO_ENABLE_SECURITY_EVENT=1 [env:v16] platform = ${common.platform} @@ -19,8 +22,8 @@ framework = ${common.framework} lib_deps = ${common.lib_deps} build_flags = ${common.build_flags} - -D MO_ENABLE_MBEDTLS=1 - -D MO_ENABLE_CERT_MGMT=1 + -D MO_ENABLE_V16=1 + -D MO_ENABLE_V201=0 -D MO_ENABLE_RESERVATION=1 -D MO_ENABLE_LOCAL_AUTH=1 -D MO_REPORT_NOERROR=1 @@ -33,6 +36,19 @@ framework = ${common.framework} lib_deps = ${common.lib_deps} build_flags = ${common.build_flags} + -D MO_ENABLE_V16=0 -D MO_ENABLE_V201=1 - -D MO_ENABLE_MBEDTLS=1 - -D MO_ENABLE_CERT_MGMT=1 + +[env:v16_v201] +platform = ${common.platform} +board = ${common.board} +framework = ${common.framework} +lib_deps = ${common.lib_deps} +build_flags = + ${common.build_flags} + -D MO_ENABLE_V16=1 + -D MO_ENABLE_V201=1 + -D MO_ENABLE_RESERVATION=1 + -D MO_ENABLE_LOCAL_AUTH=1 + -D MO_REPORT_NOERROR=1 + -D MO_ENABLE_CONNECTOR_LOCK=1 diff --git a/tests/benchmarks/scripts/eval_firmware_size.py b/tests/benchmarks/scripts/eval_firmware_size.py index 44cbc558..77802763 100755 --- a/tests/benchmarks/scripts/eval_firmware_size.py +++ b/tests/benchmarks/scripts/eval_firmware_size.py @@ -4,330 +4,164 @@ # load data -COLUMN_BINSIZE = 'Binary size (Bytes)' +COLUMN_CUNITS = 'Compile Unit' +COLUMN_SIZE_V16 = 'OCPP 1.6 (Bytes)' +COLUMN_SIZE_V201 = 'OCPP 2.0.1 (Bytes)' +COLUMN_SIZE_V16_V201 = 'Both enabled (Bytes)' def load_compilation_units(fn): - df = pd.read_csv(fn, index_col="compileunits").filter(like="lib/MicroOcpp/src/MicroOcpp", axis=0).filter(['Module','v16','v201','vmsize'], axis=1).sort_index() - df.index.names = ['Compile Unit'] + df = pd.read_csv(fn, index_col="compileunits").filter(like="src/MicroOcpp", axis=0).filter(['Module','vmsize'], axis=1).sort_index() + df.index.names = [COLUMN_CUNITS] df.index = df.index.map(lambda s: s[len("lib/MicroOcpp/src/"):] if s.startswith("lib/MicroOcpp/src/") else s) + df.index = df.index.map(lambda s: s[len("src/MicroOcpp/"):] if s.startswith("src/MicroOcpp/") else s) + df.index = df.index.map(lambda s: s[len("src/"):] if s.startswith("src/") else s) df.index = df.index.map(lambda s: s[len("MicroOcpp/"):] if s.startswith("MicroOcpp/") else s) - df.rename(columns={'vmsize': COLUMN_BINSIZE}, inplace=True) return df cunits_v16 = load_compilation_units('docs/assets/tables/bloaty_v16.csv') +cunits_v16.rename(columns={'vmsize': COLUMN_SIZE_V16}, inplace=True) cunits_v201 = load_compilation_units('docs/assets/tables/bloaty_v201.csv') +cunits_v201.rename(columns={'vmsize': COLUMN_SIZE_V201}, inplace=True) +cunits_v16_v201 = load_compilation_units('docs/assets/tables/bloaty_v16_v201.csv') +cunits_v16_v201.rename(columns={'vmsize': COLUMN_SIZE_V16_V201}, inplace=True) + +cunits = pd.merge(cunits_v16, cunits_v201, on=COLUMN_CUNITS, how='outer') +cunits = pd.merge(cunits, cunits_v16_v201, on=COLUMN_CUNITS, how='outer') # categorize data def categorize_table(df): - df["v16"] = ' ' - df["v201"] = ' ' df["Module"] = '' - TICK = 'x' - - MODULE_GENERAL = 'General' - MODULE_HAL = 'General - Hardware Abstraction Layer' - MODULE_RPC = 'General - RPC framework' - MODULE_API = 'General - API' - MODULE_CORE = 'Core' - MODULE_CONFIGURATION = 'Configuration' - MODULE_FW_MNGT = 'Firmware Management' - MODULE_TRIGGERMESSAGE = 'TriggerMessage' + MODULE_MO = ' General library functions' MODULE_SECURITY = 'A - Security' - MODULE_PROVISIONING = 'B - Provisioning' - MODULE_PROVISIONING_VARS = 'B - Provisioning - Variables' + MODULE_PROVISIONING_CONF = 'B - Provisioning - Configuration (v16)' + MODULE_PROVISIONING_VARS = 'B - Provisioning - Variables (v201)' + MODULE_PROVISIONING = 'B - Provisioning - Other' MODULE_AUTHORIZATION = 'C - Authorization' - MODULE_LOCALAUTH = 'D - Local Authorization List Management' + MODULE_LOCALAUTH = 'D - LocalAuthorizationListManagement' MODULE_TX = 'E - Transactions' MODULE_REMOTECONTROL = 'F - RemoteControl' MODULE_AVAILABILITY = 'G - Availability' MODULE_RESERVATION = 'H - Reservation' MODULE_METERVALUES = 'J - MeterValues' MODULE_SMARTCHARGING = 'K - SmartCharging' - MODULE_CERTS = 'M - Certificate Management' - - df.at['MicroOcpp.cpp', 'v16'] = TICK - df.at['MicroOcpp.cpp', 'v201'] = TICK - df.at['MicroOcpp.cpp', 'Module'] = MODULE_API - df.at['Core/Configuration.cpp', 'v16'] = TICK - df.at['Core/Configuration.cpp', 'v201'] = TICK - df.at['Core/Configuration.cpp', 'Module'] = MODULE_CONFIGURATION - if 'Core/Configuration_c.cpp' in df.index: - df.at['Core/Configuration_c.cpp', 'v16'] = TICK - df.at['Core/Configuration_c.cpp', 'v201'] = TICK - df.at['Core/Configuration_c.cpp', 'Module'] = MODULE_CONFIGURATION - df.at['Core/ConfigurationContainer.cpp', 'v16'] = TICK - df.at['Core/ConfigurationContainer.cpp', 'v201'] = TICK - df.at['Core/ConfigurationContainer.cpp', 'Module'] = MODULE_CONFIGURATION - df.at['Core/ConfigurationContainerFlash.cpp', 'v16'] = TICK - df.at['Core/ConfigurationContainerFlash.cpp', 'v201'] = TICK - df.at['Core/ConfigurationContainerFlash.cpp', 'Module'] = MODULE_CONFIGURATION - df.at['Core/ConfigurationKeyValue.cpp', 'v16'] = TICK - df.at['Core/ConfigurationKeyValue.cpp', 'v201'] = TICK - df.at['Core/ConfigurationKeyValue.cpp', 'Module'] = MODULE_CONFIGURATION - df.at['Core/Connection.cpp', 'v16'] = TICK - df.at['Core/Connection.cpp', 'v201'] = TICK - df.at['Core/Connection.cpp', 'Module'] = MODULE_HAL - df.at['Core/Context.cpp', 'v16'] = TICK - df.at['Core/Context.cpp', 'v201'] = TICK - df.at['Core/Context.cpp', 'Module'] = MODULE_GENERAL - df.at['Core/FilesystemAdapter.cpp', 'v16'] = TICK - df.at['Core/FilesystemAdapter.cpp', 'v201'] = TICK - df.at['Core/FilesystemAdapter.cpp', 'Module'] = MODULE_HAL - df.at['Core/FilesystemUtils.cpp', 'v16'] = TICK - df.at['Core/FilesystemUtils.cpp', 'v201'] = TICK - df.at['Core/FilesystemUtils.cpp', 'Module'] = MODULE_GENERAL - df.at['Core/FtpMbedTLS.cpp', 'v16'] = TICK - df.at['Core/FtpMbedTLS.cpp', 'v201'] = TICK - df.at['Core/FtpMbedTLS.cpp', 'Module'] = MODULE_GENERAL - df.at['Core/Memory.cpp', 'v16'] = TICK - df.at['Core/Memory.cpp', 'v201'] = TICK - df.at['Core/Memory.cpp', 'Module'] = MODULE_GENERAL - df.at['Core/Operation.cpp', 'v16'] = TICK - df.at['Core/Operation.cpp', 'v201'] = TICK - df.at['Core/Operation.cpp', 'Module'] = MODULE_RPC - df.at['Core/OperationRegistry.cpp', 'v16'] = TICK - df.at['Core/OperationRegistry.cpp', 'v201'] = TICK - df.at['Core/OperationRegistry.cpp', 'Module'] = MODULE_RPC - df.at['Core/Request.cpp', 'v16'] = TICK - df.at['Core/Request.cpp', 'v201'] = TICK - df.at['Core/Request.cpp', 'Module'] = MODULE_RPC - df.at['Core/RequestQueue.cpp', 'v16'] = TICK - df.at['Core/RequestQueue.cpp', 'v201'] = TICK - df.at['Core/RequestQueue.cpp', 'Module'] = MODULE_RPC - df.at['Core/Time.cpp', 'v16'] = TICK - df.at['Core/Time.cpp', 'v201'] = TICK - df.at['Core/Time.cpp', 'Module'] = MODULE_GENERAL - df.at['Core/UuidUtils.cpp', 'v16'] = TICK - df.at['Core/UuidUtils.cpp', 'v201'] = TICK - df.at['Core/UuidUtils.cpp', 'Module'] = MODULE_GENERAL - if 'Debug.cpp' in df.index: - df.at['Debug.cpp', 'v16'] = TICK - df.at['Debug.cpp', 'v201'] = TICK - df.at['Debug.cpp', 'Module'] = MODULE_HAL - if 'Platform.cpp' in df.index: - df.at['Platform.cpp', 'v16'] = TICK - df.at['Platform.cpp', 'v201'] = TICK - df.at['Platform.cpp', 'Module'] = MODULE_HAL - df.at['Model/Authorization/AuthorizationData.cpp', 'v16'] = TICK + MODULE_FW_MNGT = 'L - FirmwareManagement' + MODULE_CERTS = 'M - CertificateManagement' + MODULE_DIAG = 'N - Diagnostics' + MODULE_DATATRANSFER = 'P - DataTransfer' + + df.at['Context.cpp', 'Module'] = MODULE_MO + df.at['Core/Connection.cpp', 'Module'] = MODULE_MO + df.at['Core/FilesystemAdapter.cpp', 'Module'] = MODULE_MO + df.at['Core/FilesystemUtils.cpp', 'Module'] = MODULE_MO + df.at['Core/FtpMbedTLS.cpp', 'Module'] = MODULE_MO + df.at['Core/Memory.cpp', 'Module'] = MODULE_MO + df.at['Core/MessageService.cpp', 'Module'] = MODULE_MO + df.at['Core/Operation.cpp', 'Module'] = MODULE_MO + df.at['Core/PersistencyUtils.cpp', 'Module'] = MODULE_MO + df.at['Core/Request.cpp', 'Module'] = MODULE_MO + df.at['Core/RequestQueue.cpp', 'Module'] = MODULE_MO + df.at['Core/Time.cpp', 'Module'] = MODULE_MO + df.at['Core/UuidUtils.cpp', 'Module'] = MODULE_MO + df.at['Debug.cpp', 'Module'] = MODULE_MO + df.at['MicroOcpp.cpp', 'Module'] = MODULE_MO df.at['Model/Authorization/AuthorizationData.cpp', 'Module'] = MODULE_LOCALAUTH - df.at['Model/Authorization/AuthorizationList.cpp', 'v16'] = TICK df.at['Model/Authorization/AuthorizationList.cpp', 'Module'] = MODULE_LOCALAUTH - df.at['Model/Authorization/AuthorizationService.cpp', 'v16'] = TICK df.at['Model/Authorization/AuthorizationService.cpp', 'Module'] = MODULE_LOCALAUTH - if 'Model/Authorization/IdToken.cpp' in df.index: - df.at['Model/Authorization/IdToken.cpp', 'v201'] = TICK - df.at['Model/Authorization/IdToken.cpp', 'Module'] = MODULE_AUTHORIZATION - if 'Model/Availability/AvailabilityService.cpp' in df.index: - df.at['Model/Availability/AvailabilityService.cpp', 'v16'] = TICK - df.at['Model/Availability/AvailabilityService.cpp', 'v201'] = TICK - df.at['Model/Availability/AvailabilityService.cpp', 'Module'] = MODULE_AVAILABILITY - df.at['Model/Boot/BootService.cpp', 'v16'] = TICK - df.at['Model/Boot/BootService.cpp', 'v201'] = TICK + df.at['Model/Authorization/IdToken.cpp', 'Module'] = MODULE_AUTHORIZATION + df.at['Model/Availability/AvailabilityDefs.cpp', 'Module'] = MODULE_AVAILABILITY + df.at['Model/Availability/AvailabilityService.cpp', 'Module'] = MODULE_AVAILABILITY df.at['Model/Boot/BootService.cpp', 'Module'] = MODULE_PROVISIONING - df.at['Model/Certificates/Certificate.cpp', 'v16'] = TICK - df.at['Model/Certificates/Certificate.cpp', 'v201'] = TICK df.at['Model/Certificates/Certificate.cpp', 'Module'] = MODULE_CERTS - df.at['Model/Certificates/CertificateMbedTLS.cpp', 'v16'] = TICK - df.at['Model/Certificates/CertificateMbedTLS.cpp', 'v201'] = TICK df.at['Model/Certificates/CertificateMbedTLS.cpp', 'Module'] = MODULE_CERTS - if 'Model/Certificates/Certificate_c.cpp' in df.index: - df.at['Model/Certificates/Certificate_c.cpp', 'v16'] = TICK - df.at['Model/Certificates/Certificate_c.cpp', 'v201'] = TICK - df.at['Model/Certificates/Certificate_c.cpp', 'Module'] = MODULE_CERTS - df.at['Model/Certificates/CertificateService.cpp', 'v16'] = TICK - df.at['Model/Certificates/CertificateService.cpp', 'v201'] = TICK df.at['Model/Certificates/CertificateService.cpp', 'Module'] = MODULE_CERTS - df.at['Model/ConnectorBase/Connector.cpp', 'v16'] = TICK - df.at['Model/ConnectorBase/Connector.cpp', 'Module'] = MODULE_CORE - df.at['Model/ConnectorBase/ConnectorService.cpp', 'v16'] = TICK - df.at['Model/ConnectorBase/ConnectorService.cpp', 'Module'] = MODULE_CORE - df.at['Model/Diagnostics/DiagnosticsService.cpp', 'v16'] = TICK - df.at['Model/Diagnostics/DiagnosticsService.cpp', 'Module'] = MODULE_FW_MNGT - df.at['Model/FirmwareManagement/FirmwareService.cpp', 'v16'] = TICK + df.at['Model/Certificates/Certificate_c.cpp', 'Module'] = MODULE_CERTS + df.at['Model/Configuration/Configuration.cpp', 'Module'] = MODULE_PROVISIONING_CONF + df.at['Model/Configuration/ConfigurationContainer.cpp', 'Module'] = MODULE_PROVISIONING_CONF + df.at['Model/Configuration/ConfigurationService.cpp', 'Module'] = MODULE_PROVISIONING_CONF + df.at['Model/Diagnostics/Diagnostics.cpp', 'Module'] = MODULE_DIAG + df.at['Model/Diagnostics/DiagnosticsService.cpp', 'Module'] = MODULE_DIAG df.at['Model/FirmwareManagement/FirmwareService.cpp', 'Module'] = MODULE_FW_MNGT - df.at['Model/Heartbeat/HeartbeatService.cpp', 'v16'] = TICK - df.at['Model/Heartbeat/HeartbeatService.cpp', 'v201'] = TICK df.at['Model/Heartbeat/HeartbeatService.cpp', 'Module'] = MODULE_AVAILABILITY - df.at['Model/Metering/MeteringService.cpp', 'v16'] = TICK df.at['Model/Metering/MeteringService.cpp', 'Module'] = MODULE_METERVALUES - df.at['Model/Metering/MeterStore.cpp', 'v16'] = TICK df.at['Model/Metering/MeterStore.cpp', 'Module'] = MODULE_METERVALUES - df.at['Model/Metering/MeterValue.cpp', 'v16'] = TICK df.at['Model/Metering/MeterValue.cpp', 'Module'] = MODULE_METERVALUES - if 'Model/Metering/MeterValuesV201.cpp' in df.index: - df.at['Model/Metering/MeterValuesV201.cpp', 'v201'] = TICK - df.at['Model/Metering/MeterValuesV201.cpp', 'Module'] = MODULE_METERVALUES - if 'Model/Metering/ReadingContext.cpp' in df.index: - df.at['Model/Metering/ReadingContext.cpp', 'v201'] = TICK - df.at['Model/Metering/ReadingContext.cpp', 'Module'] = MODULE_METERVALUES - df.at['Model/Metering/SampledValue.cpp', 'v16'] = TICK - df.at['Model/Metering/SampledValue.cpp', 'Module'] = MODULE_METERVALUES - if 'Model/RemoteControl/RemoteControlService.cpp' in df.index: - df.at['Model/RemoteControl/RemoteControlService.cpp', 'v201'] = TICK - df.at['Model/RemoteControl/RemoteControlService.cpp', 'Module'] = MODULE_REMOTECONTROL - df.at['Model/Model.cpp', 'v16'] = TICK - df.at['Model/Model.cpp', 'v201'] = TICK - df.at['Model/Model.cpp', 'Module'] = MODULE_GENERAL - df.at['Model/Reservation/Reservation.cpp', 'v16'] = TICK + df.at['Model/Metering/ReadingContext.cpp', 'Module'] = MODULE_METERVALUES + df.at['Model/Model.cpp', 'Module'] = MODULE_MO + df.at['Model/RemoteControl/RemoteControlService.cpp', 'Module'] = MODULE_REMOTECONTROL df.at['Model/Reservation/Reservation.cpp', 'Module'] = MODULE_RESERVATION - df.at['Model/Reservation/ReservationService.cpp', 'v16'] = TICK df.at['Model/Reservation/ReservationService.cpp', 'Module'] = MODULE_RESERVATION - df.at['Model/Reset/ResetService.cpp', 'v16'] = TICK - df.at['Model/Reset/ResetService.cpp', 'v201'] = TICK df.at['Model/Reset/ResetService.cpp', 'Module'] = MODULE_PROVISIONING - df.at['Model/SmartCharging/SmartChargingModel.cpp', 'v16'] = TICK + df.at['Model/SecurityEvent/SecurityEventService.cpp', 'Module'] = MODULE_SECURITY df.at['Model/SmartCharging/SmartChargingModel.cpp', 'Module'] = MODULE_SMARTCHARGING - df.at['Model/SmartCharging/SmartChargingService.cpp', 'v16'] = TICK df.at['Model/SmartCharging/SmartChargingService.cpp', 'Module'] = MODULE_SMARTCHARGING - df.at['Model/Transactions/Transaction.cpp', 'v16'] = TICK - df.at['Model/Transactions/Transaction.cpp', 'v201'] = TICK df.at['Model/Transactions/Transaction.cpp', 'Module'] = MODULE_TX - df.at['Model/Transactions/TransactionDeserialize.cpp', 'v16'] = TICK - df.at['Model/Transactions/TransactionDeserialize.cpp', 'Module'] = MODULE_TX - if 'Model/Transactions/TransactionService.cpp' in df.index: - df.at['Model/Transactions/TransactionService.cpp', 'v201'] = TICK - df.at['Model/Transactions/TransactionService.cpp', 'Module'] = MODULE_TX - df.at['Model/Transactions/TransactionStore.cpp', 'v16'] = TICK + df.at['Model/Transactions/TransactionService16.cpp', 'Module'] = MODULE_TX + df.at['Model/Transactions/TransactionService201.cpp', 'Module'] = MODULE_TX df.at['Model/Transactions/TransactionStore.cpp', 'Module'] = MODULE_TX - if 'Model/Variables/Variable.cpp' in df.index: - df.at['Model/Variables/Variable.cpp', 'v201'] = TICK - df.at['Model/Variables/Variable.cpp', 'Module'] = MODULE_PROVISIONING_VARS - if 'Model/Variables/VariableContainer.cpp' in df.index: - df.at['Model/Variables/VariableContainer.cpp', 'v201'] = TICK - df.at['Model/Variables/VariableContainer.cpp', 'Module'] = MODULE_PROVISIONING_VARS - if 'Model/Variables/VariableService.cpp' in df.index: - df.at['Model/Variables/VariableService.cpp', 'v201'] = TICK - df.at['Model/Variables/VariableService.cpp', 'Module'] = MODULE_PROVISIONING_VARS - df.at['Operations/Authorize.cpp', 'v16'] = TICK - df.at['Operations/Authorize.cpp', 'v201'] = TICK + df.at['Model/Variables/Variable.cpp', 'Module'] = MODULE_PROVISIONING_VARS + df.at['Model/Variables/VariableContainer.cpp', 'Module'] = MODULE_PROVISIONING_VARS + df.at['Model/Variables/VariableService.cpp', 'Module'] = MODULE_PROVISIONING_VARS df.at['Operations/Authorize.cpp', 'Module'] = MODULE_AUTHORIZATION - df.at['Operations/BootNotification.cpp', 'v16'] = TICK - df.at['Operations/BootNotification.cpp', 'v201'] = TICK df.at['Operations/BootNotification.cpp', 'Module'] = MODULE_PROVISIONING - df.at['Operations/CancelReservation.cpp', 'v16'] = TICK df.at['Operations/CancelReservation.cpp', 'Module'] = MODULE_RESERVATION - df.at['Operations/ChangeAvailability.cpp', 'v16'] = TICK df.at['Operations/ChangeAvailability.cpp', 'Module'] = MODULE_AVAILABILITY - df.at['Operations/ChangeConfiguration.cpp', 'v16'] = TICK - df.at['Operations/ChangeConfiguration.cpp', 'Module'] = MODULE_CONFIGURATION - df.at['Operations/ClearCache.cpp', 'v16'] = TICK - df.at['Operations/ClearCache.cpp', 'Module'] = MODULE_CORE - df.at['Operations/ClearChargingProfile.cpp', 'v16'] = TICK + df.at['Operations/ChangeConfiguration.cpp', 'Module'] = MODULE_PROVISIONING_CONF + df.at['Operations/ClearCache.cpp', 'Module'] = MODULE_AUTHORIZATION df.at['Operations/ClearChargingProfile.cpp', 'Module'] = MODULE_SMARTCHARGING - df.at['Operations/CustomOperation.cpp', 'v16'] = TICK - df.at['Operations/CustomOperation.cpp', 'v201'] = TICK - df.at['Operations/CustomOperation.cpp', 'Module'] = MODULE_RPC - df.at['Operations/DataTransfer.cpp', 'v16'] = TICK - df.at['Operations/DataTransfer.cpp', 'Module'] = MODULE_CORE - df.at['Operations/DeleteCertificate.cpp', 'v16'] = TICK - df.at['Operations/DeleteCertificate.cpp', 'v201'] = TICK + df.at['Operations/CustomOperation.cpp', 'Module'] = MODULE_MO + df.at['Operations/DataTransfer.cpp', 'Module'] = MODULE_DATATRANSFER df.at['Operations/DeleteCertificate.cpp', 'Module'] = MODULE_CERTS - df.at['Operations/DiagnosticsStatusNotification.cpp', 'v16'] = TICK - df.at['Operations/DiagnosticsStatusNotification.cpp', 'Module'] = MODULE_FW_MNGT - df.at['Operations/FirmwareStatusNotification.cpp', 'v16'] = TICK + df.at['Operations/DiagnosticsStatusNotification.cpp', 'Module'] = MODULE_DIAG df.at['Operations/FirmwareStatusNotification.cpp', 'Module'] = MODULE_FW_MNGT - if 'Operations/GetBaseReport.cpp' in df.index: - df.at['Operations/GetBaseReport.cpp', 'v201'] = TICK - df.at['Operations/GetBaseReport.cpp', 'Module'] = MODULE_PROVISIONING_VARS - df.at['Operations/GetCompositeSchedule.cpp', 'v16'] = TICK + df.at['Operations/GetBaseReport.cpp', 'Module'] = MODULE_PROVISIONING_VARS df.at['Operations/GetCompositeSchedule.cpp', 'Module'] = MODULE_SMARTCHARGING - df.at['Operations/GetConfiguration.cpp', 'v16'] = TICK - df.at['Operations/GetConfiguration.cpp', 'v201'] = TICK - df.at['Operations/GetConfiguration.cpp', 'Module'] = MODULE_CONFIGURATION - df.at['Operations/GetDiagnostics.cpp', 'v16'] = TICK - df.at['Operations/GetDiagnostics.cpp', 'Module'] = MODULE_FW_MNGT - df.at['Operations/GetInstalledCertificateIds.cpp', 'v16'] = TICK - df.at['Operations/GetInstalledCertificateIds.cpp', 'Module'] = MODULE_SMARTCHARGING - df.at['Operations/GetLocalListVersion.cpp', 'v16'] = TICK + df.at['Operations/GetConfiguration.cpp', 'Module'] = MODULE_PROVISIONING_CONF + df.at['Operations/GetDiagnostics.cpp', 'Module'] = MODULE_DIAG + df.at['Operations/GetInstalledCertificateIds.cpp', 'Module'] = MODULE_CERTS df.at['Operations/GetLocalListVersion.cpp', 'Module'] = MODULE_LOCALAUTH - if 'Operations/GetVariables.cpp' in df.index: - df.at['Operations/GetVariables.cpp', 'v201'] = TICK - df.at['Operations/GetVariables.cpp', 'Module'] = MODULE_PROVISIONING_VARS - df.at['Operations/Heartbeat.cpp', 'v16'] = TICK - df.at['Operations/Heartbeat.cpp', 'v201'] = TICK + df.at['Operations/GetLog.cpp', 'Module'] = MODULE_DIAG + df.at['Operations/GetVariables.cpp', 'Module'] = MODULE_PROVISIONING_VARS df.at['Operations/Heartbeat.cpp', 'Module'] = MODULE_AVAILABILITY - df.at['Operations/InstallCertificate.cpp', 'v16'] = TICK - df.at['Operations/InstallCertificate.cpp', 'v201'] = TICK df.at['Operations/InstallCertificate.cpp', 'Module'] = MODULE_CERTS - df.at['Operations/MeterValues.cpp', 'v16'] = TICK + df.at['Operations/LogStatusNotification.cpp', 'Module'] = MODULE_DIAG df.at['Operations/MeterValues.cpp', 'Module'] = MODULE_METERVALUES - if 'Operations/NotifyReport.cpp' in df.index: - df.at['Operations/NotifyReport.cpp', 'v201'] = TICK - df.at['Operations/NotifyReport.cpp', 'Module'] = MODULE_PROVISIONING_VARS - df.at['Operations/RemoteStartTransaction.cpp', 'v16'] = TICK + df.at['Operations/NotifyReport.cpp', 'Module'] = MODULE_PROVISIONING_VARS df.at['Operations/RemoteStartTransaction.cpp', 'Module'] = MODULE_TX - df.at['Operations/RemoteStopTransaction.cpp', 'v16'] = TICK df.at['Operations/RemoteStopTransaction.cpp', 'Module'] = MODULE_TX - if 'Operations/RequestStartTransaction.cpp' in df.index: - df.at['Operations/RequestStartTransaction.cpp', 'v201'] = TICK - df.at['Operations/RequestStartTransaction.cpp', 'Module'] = MODULE_TX - if 'Operations/RequestStopTransaction.cpp' in df.index: - df.at['Operations/RequestStopTransaction.cpp', 'v201'] = TICK - df.at['Operations/RequestStopTransaction.cpp', 'Module'] = MODULE_TX - df.at['Operations/ReserveNow.cpp', 'v16'] = TICK + df.at['Operations/RequestStartTransaction.cpp', 'Module'] = MODULE_TX + df.at['Operations/RequestStopTransaction.cpp', 'Module'] = MODULE_TX df.at['Operations/ReserveNow.cpp', 'Module'] = MODULE_RESERVATION - df.at['Operations/Reset.cpp', 'v16'] = TICK - df.at['Operations/Reset.cpp', 'v201'] = TICK df.at['Operations/Reset.cpp', 'Module'] = MODULE_PROVISIONING - if 'Operations/SecurityEventNotification.cpp' in df.index: - df.at['Operations/SecurityEventNotification.cpp', 'v201'] = TICK - df.at['Operations/SecurityEventNotification.cpp', 'Module'] = MODULE_SECURITY - df.at['Operations/SendLocalList.cpp', 'v16'] = TICK + df.at['Operations/SecurityEventNotification.cpp', 'Module'] = MODULE_SECURITY df.at['Operations/SendLocalList.cpp', 'Module'] = MODULE_LOCALAUTH - df.at['Operations/SetChargingProfile.cpp', 'v16'] = TICK df.at['Operations/SetChargingProfile.cpp', 'Module'] = MODULE_SMARTCHARGING - if 'Operations/SetVariables.cpp' in df.index: - df.at['Operations/SetVariables.cpp', 'v201'] = TICK - df.at['Operations/SetVariables.cpp', 'Module'] = MODULE_PROVISIONING_VARS - df.at['Operations/StartTransaction.cpp', 'v16'] = TICK + df.at['Operations/SetVariables.cpp', 'Module'] = MODULE_PROVISIONING_VARS df.at['Operations/StartTransaction.cpp', 'Module'] = MODULE_TX - df.at['Operations/StatusNotification.cpp', 'v16'] = TICK - df.at['Operations/StatusNotification.cpp', 'v201'] = TICK df.at['Operations/StatusNotification.cpp', 'Module'] = MODULE_AVAILABILITY - df.at['Operations/StopTransaction.cpp', 'v16'] = TICK df.at['Operations/StopTransaction.cpp', 'Module'] = MODULE_TX - if 'Operations/TransactionEvent.cpp' in df.index: - df.at['Operations/TransactionEvent.cpp', 'v201'] = TICK - df.at['Operations/TransactionEvent.cpp', 'Module'] = MODULE_TX - df.at['Operations/TriggerMessage.cpp', 'v16'] = TICK - df.at['Operations/TriggerMessage.cpp', 'Module'] = MODULE_TRIGGERMESSAGE - df.at['Operations/UnlockConnector.cpp', 'v16'] = TICK - df.at['Operations/UnlockConnector.cpp', 'Module'] = MODULE_CORE - df.at['Operations/UpdateFirmware.cpp', 'v16'] = TICK + df.at['Operations/TransactionEvent.cpp', 'Module'] = MODULE_TX + df.at['Operations/TriggerMessage.cpp', 'Module'] = MODULE_REMOTECONTROL + df.at['Operations/UnlockConnector.cpp', 'Module'] = MODULE_REMOTECONTROL df.at['Operations/UpdateFirmware.cpp', 'Module'] = MODULE_FW_MNGT - if 'MicroOcpp_c.cpp' in df.index: - df.at['MicroOcpp_c.cpp', 'v16'] = TICK - df.at['MicroOcpp_c.cpp', 'v201'] = TICK - df.at['MicroOcpp_c.cpp', 'Module'] = MODULE_API + df.at['Platform.cpp', 'Module'] = MODULE_MO print(df) -categorize_table(cunits_v16) -categorize_table(cunits_v201) +categorize_table(cunits) categorize_success = True -if cunits_v16[COLUMN_BINSIZE].isnull().any(): - print('Error: categorized the following compilation units erroneously (v16):\n') - print(cunits_v16.loc[cunits_v16[COLUMN_BINSIZE].isnull()]) +if (cunits[[COLUMN_SIZE_V16, COLUMN_SIZE_V201, COLUMN_SIZE_V16_V201]].isna().all(axis=1)).any(): + print('Error: categorized non-existing compile units:\n') + print(cunits.loc[cunits[[COLUMN_SIZE_V16, COLUMN_SIZE_V201, COLUMN_SIZE_V16_V201]].isna().all(axis=1)]) categorize_success = False -if cunits_v201[COLUMN_BINSIZE].isnull().any(): - print('Error: categorized the following compilation units erroneously (v201):\n') - print(cunits_v201.loc[cunits_v201[COLUMN_BINSIZE].isnull()]) - categorize_success = False - -if (cunits_v16['Module'].values == '').sum() > 0: +if (cunits['Module'].values == '').sum() > 0: print('Error: did not categorize the following compilation units (v16):\n') - print(cunits_v16.loc[cunits_v16['Module'].values == '']) - categorize_success = False - -if (cunits_v201['Module'].values == '').sum() > 0: - print('Error: did not categorize the following compilation units (v201):\n') - print(cunits_v201.loc[cunits_v201['Module'].values == '']) + print(cunits.loc[cunits['Module'].values == '']) categorize_success = False if not categorize_success: @@ -335,28 +169,13 @@ def categorize_table(df): # store csv with all details -print('Uncategorized compile units (v16): ', (cunits_v16['Module'].values == '').sum()) -print('Uncategorized compile units (v201): ', (cunits_v201['Module'].values == '').sum()) - -cunits_v16.to_csv("docs/assets/tables/compile_units_v16.csv") -cunits_v201.to_csv("docs/assets/tables/compile_units_v201.csv") +cunits.to_csv("docs/assets/tables/compile_units.csv") # store csv with size by Module for v16 -modules_v16 = cunits_v16.loc[cunits_v16['v16'].values == 'x'].sort_index() -modules_v16_by_module = modules_v16[['Module', COLUMN_BINSIZE]].groupby('Module').sum() -modules_v16_by_module.loc['**Total**'] = [modules_v16_by_module[COLUMN_BINSIZE].sum()] - -print(modules_v16_by_module) - -modules_v16_by_module.to_csv('docs/assets/tables/modules_v16.csv') - -# store csv with size by Module for v201 - -modules_v201 = cunits_v201.loc[cunits_v201['v201'].values == 'x'].sort_index() -modules_v201_by_module = modules_v201[['Module', COLUMN_BINSIZE]].groupby('Module').sum() -modules_v201_by_module.loc['**Total**'] = [modules_v201_by_module[COLUMN_BINSIZE].sum()] +modules = cunits[['Module', COLUMN_SIZE_V16, COLUMN_SIZE_V201, COLUMN_SIZE_V16_V201]].groupby('Module').sum() +modules.loc['**Total**'] = [modules[COLUMN_SIZE_V16].sum(), modules[COLUMN_SIZE_V201].sum(), modules[COLUMN_SIZE_V16_V201].sum()] -print(modules_v201_by_module) +print(modules) -modules_v201_by_module.to_csv('docs/assets/tables/modules_v201.csv') +modules.to_csv('docs/assets/tables/modules.csv') From 11ab396cffd431f70ce1bd2c8d2120f9f955ebd8 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:16:42 +0200 Subject: [PATCH 24/50] fix workflows --- .github/workflows/documentation.yml | 12 ++++++------ .github/workflows/pio.yml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index e1a14c66..48087b69 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: 3.x - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: key: ${{ github.ref }} path: .cache @@ -79,14 +79,14 @@ jobs: steps: - uses: actions/checkout@v3 - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} @@ -115,7 +115,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: key: ${{ github.ref }} path: .cache @@ -149,7 +149,7 @@ jobs: name: Static firmware analysis steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: key: ${{ github.ref }} path: .cache @@ -202,7 +202,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: 3.x - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: key: ${{ github.ref }} path: .cache diff --git a/.github/workflows/pio.yml b/.github/workflows/pio.yml index ec777853..f533fb1e 100644 --- a/.github/workflows/pio.yml +++ b/.github/workflows/pio.yml @@ -22,14 +22,14 @@ jobs: steps: - uses: actions/checkout@v2 - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} From 6db63c13d6e53e995e77e0ff8da88e8486c0bc9f Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:20:18 +0200 Subject: [PATCH 25/50] fix workflows --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 48087b69..7617a75a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -147,6 +147,7 @@ jobs: evaluate_firmware: needs: [build_firmware, build_bloaty] name: Static firmware analysis + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/cache@v4 From 62704d2ad34dfb154a7b4fabd2ee8a899d24cd48 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:23:05 +0200 Subject: [PATCH 26/50] fix workflows --- tests/benchmarks/firmware_size/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/benchmarks/firmware_size/main.cpp b/tests/benchmarks/firmware_size/main.cpp index 8450bb35..549061fc 100644 --- a/tests/benchmarks/firmware_size/main.cpp +++ b/tests/benchmarks/firmware_size/main.cpp @@ -116,7 +116,9 @@ void setup() { mo_setOnUnlockConnector([] () {return MO_UnlockConnectorResult_UnlockFailed;}); mo_isOperative(); +#if MO_ENABLE_V16 mo_v16_setOnResetNotify([] (bool) {return false;}); +#endif mo_setOnResetExecute([] () {}); mo_getContext(); From b238d6b69a09cc7d967aa2b769a4adcb01dc63ed Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:45:15 +0200 Subject: [PATCH 27/50] fix workflows --- tests/benchmarks/scripts/measure_heap.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/benchmarks/scripts/measure_heap.py b/tests/benchmarks/scripts/measure_heap.py index 2a08cdc9..ca02d33b 100644 --- a/tests/benchmarks/scripts/measure_heap.py +++ b/tests/benchmarks/scripts/measure_heap.py @@ -166,7 +166,8 @@ def cleanup_simulator(): time.sleep(1) print(' - clean state') - os.system('rm -rf ' + os.path.join('MicroOcppSimulator', 'mo_store', '*')) + os.system('rm -rf ' + os.path.join('mo_store')) + os.system('mkdir ' + os.path.join('mo_store')) print(' - done') @@ -179,13 +180,13 @@ def setup_simulator(): print(' - set credentials') - with open(os.path.join('MicroOcppSimulator', 'mo_store', 'simulator.jsn'), 'w') as f: + with open(os.path.join('mo_store', 'simulator.jsn'), 'w') as f: f.write(os.environ['MO_SIM_CONFIG']) - with open(os.path.join('MicroOcppSimulator', 'mo_store', 'ws-conn-v201.jsn'), 'w') as f: + with open(os.path.join('mo_store', 'ws-conn-v201.jsn'), 'w') as f: f.write(os.environ['MO_SIM_OCPP_SERVER']) - with open(os.path.join('MicroOcppSimulator', 'mo_store', 'rmt_ctrl.jsn'), 'w') as f: + with open(os.path.join('mo_store', 'rmt_ctrl.jsn'), 'w') as f: f.write(os.environ['MO_SIM_RMT_CTRL_CONFIG']) - with open(os.path.join('MicroOcppSimulator', 'mo_store', 'rmt_ctrl.pem'), 'w') as f: + with open(os.path.join('mo_store', 'rmt_ctrl.pem'), 'w') as f: f.write(os.environ['MO_SIM_RMT_CTRL_CERT']) print(' - start Simulator') @@ -370,10 +371,13 @@ def run_measurements(): print('Stored test results to CSV') +run_measurements_success = False + def run_measurements_and_retry(): global exit_run_simulator_process global exit_watchdog + global run_measurements_success if ( 'TEST_DRIVER_URL' not in os.environ or 'TEST_DRIVER_CONFIG' not in os.environ or @@ -402,6 +406,7 @@ def run_measurements_and_retry(): run_measurements() print('\n **Test cases executed successfully**') + run_measurements_success = True break except: print(f'Error detected ({i+1})') @@ -418,8 +423,14 @@ def run_measurements_and_retry(): print('Retry test cases') else: print('\n **Test case execution aborted**') + run_measurements_success = False exit_watchdog = True # terminate watchdog thread m_watchdog_thread.join() run_measurements_and_retry() + +if run_measurements_success: + sys.exit(0) +else: + sys.exit(1) From 8c9363c149960e1c742302eb96370bf3fedc55e2 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:57:21 +0200 Subject: [PATCH 28/50] fix workflows --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7617a75a..3b992a0c 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -177,7 +177,7 @@ jobs: uses: actions/download-artifact@v4 with: name: Google bloaty - path: tools/bloaty/build/bloaty + path: tools/bloaty/build - name: Run bloaty run: | mkdir -p docs/assets/tables From 60f2d5649076ac29b3bc41f2a4e727c89b9964b2 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:05:55 +0200 Subject: [PATCH 29/50] fix workflows --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3b992a0c..98ad77d6 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -181,6 +181,7 @@ jobs: - name: Run bloaty run: | mkdir -p docs/assets/tables + chmod +x tools/bloaty/build/bloaty tools/bloaty/build/bloaty firmware/firmware_v16.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16.csv tools/bloaty/build/bloaty firmware/firmware_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v201.csv tools/bloaty/build/bloaty firmware/firmware_v16_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16_v201.csv From 3dd94713316c1ada5efbd894f1942ca3a9503699 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:33:37 +0200 Subject: [PATCH 30/50] fix workflows --- .github/workflows/documentation.yml | 4 ++++ tests/benchmarks/scripts/eval_firmware_size.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 98ad77d6..ab51645a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -178,6 +178,8 @@ jobs: with: name: Google bloaty path: tools/bloaty/build + - name: Debug + run: ls docs/assets/tables - name: Run bloaty run: | mkdir -p docs/assets/tables @@ -185,6 +187,8 @@ jobs: tools/bloaty/build/bloaty firmware/firmware_v16.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16.csv tools/bloaty/build/bloaty firmware/firmware_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v201.csv tools/bloaty/build/bloaty firmware/firmware_v16_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16_v201.csv + - name: Debug + run: ls docs/assets/tables - name: Evaluate and create reports run: python tests/benchmarks/scripts/eval_firmware_size.py - name: Upload reports diff --git a/tests/benchmarks/scripts/eval_firmware_size.py b/tests/benchmarks/scripts/eval_firmware_size.py index 77802763..9fe770c7 100755 --- a/tests/benchmarks/scripts/eval_firmware_size.py +++ b/tests/benchmarks/scripts/eval_firmware_size.py @@ -28,6 +28,15 @@ def load_compilation_units(fn): cunits = pd.merge(cunits_v16, cunits_v201, on=COLUMN_CUNITS, how='outer') cunits = pd.merge(cunits, cunits_v16_v201, on=COLUMN_CUNITS, how='outer') +print("cunits_v16") +print(cunits_v16) +print("cunits_v201") +print(cunits_v201) +print("cunits_v16_v201") +print(cunits_v16_v201) +print("cunits") +print(cunits) + # categorize data def categorize_table(df): From ab7b266a097ca219995e6d0815589ab1097bc6f4 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:53:58 +0200 Subject: [PATCH 31/50] fix workflows --- .github/workflows/documentation.yml | 2 +- src/MicroOcpp/Core/Time.cpp | 4 ---- src/MicroOcpp/Core/Time.h | 3 ++- src/MicroOcpp/Model/Authorization/IdToken.cpp | 7 +++++++ src/MicroOcpp/Model/Authorization/IdToken.h | 2 ++ src/MicroOcpp/Model/Boot/BootService.cpp | 2 +- src/MicroOcpp/Model/Boot/BootService.h | 2 +- src/MicroOcpp/Model/Metering/MeterValue.cpp | 1 + src/MicroOcpp/Platform.cpp | 2 +- 9 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index ab51645a..40cf9c3a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -179,7 +179,7 @@ jobs: name: Google bloaty path: tools/bloaty/build - name: Debug - run: ls docs/assets/tables + run: ls firmware - name: Run bloaty run: | mkdir -p docs/assets/tables diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index 58560223..fd7e1309 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -36,10 +36,6 @@ Timestamp::Timestamp() : MemoryManaged("Timestamp") { } -Timestamp::Timestamp(const Timestamp& other) : MemoryManaged("Timestamp") { - *this = other; -} - bool Timestamp::isUnixTime() const { return time >= MO_MIN_TIME && time <= MO_MAX_TIME; } diff --git a/src/MicroOcpp/Core/Time.h b/src/MicroOcpp/Core/Time.h index 49d673ef..d694fff4 100644 --- a/src/MicroOcpp/Core/Time.h +++ b/src/MicroOcpp/Core/Time.h @@ -45,7 +45,8 @@ class Timestamp : public MemoryManaged { public: Timestamp(); - Timestamp(const Timestamp& other); + Timestamp(const Timestamp& other) = default; + MicroOcpp::Timestamp& operator=(const Timestamp& other) = default; bool isUnixTime() const; bool isUptime() const; diff --git a/src/MicroOcpp/Model/Authorization/IdToken.cpp b/src/MicroOcpp/Model/Authorization/IdToken.cpp index 7182d11c..fa28718f 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.cpp +++ b/src/MicroOcpp/Model/Authorization/IdToken.cpp @@ -28,6 +28,13 @@ IdToken::IdToken(const IdToken& other, const char *memoryTag) : IdToken(other.id } +IdToken& IdToken::operator=(const IdToken& other) { + snprintf(this->idToken, sizeof(this->idToken), "%s", other.idToken); + this->type = other.type; + updateMemoryTag(other.getMemoryTag(), nullptr); + return *this; +} + bool IdToken::parseCstr(const char *token, const char *typeCstr) { if (!token || !typeCstr) { return false; diff --git a/src/MicroOcpp/Model/Authorization/IdToken.h b/src/MicroOcpp/Model/Authorization/IdToken.h index 2b9504f7..a38ac8f2 100644 --- a/src/MicroOcpp/Model/Authorization/IdToken.h +++ b/src/MicroOcpp/Model/Authorization/IdToken.h @@ -53,6 +53,8 @@ class IdToken : public MemoryManaged { IdToken(const IdToken& other, const char *memoryTag = nullptr); + IdToken& operator=(const IdToken& other); + bool parseCstr(const char *token, const char *typeCstr); const char *get() const; diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index e0500890..13c1646b 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -52,7 +52,7 @@ RegistrationStatus MicroOcpp::deserializeRegistrationStatus(const char *serializ } BootService::BootService(Context& context) : MemoryManaged("v16/v201.Boot.BootService"), context(context) { - + mo_bootNotificationData_init(&bnData); } BootService::~BootService() { diff --git a/src/MicroOcpp/Model/Boot/BootService.h b/src/MicroOcpp/Model/Boot/BootService.h index 1325460c..cdc02027 100644 --- a/src/MicroOcpp/Model/Boot/BootService.h +++ b/src/MicroOcpp/Model/Boot/BootService.h @@ -66,7 +66,7 @@ class BootService : public MemoryManaged { RegistrationStatus status = RegistrationStatus::Pending; - MO_BootNotificationData bnData {0}; + MO_BootNotificationData bnData; char *bnDataBuf = nullptr; int ocppVersion = -1; diff --git a/src/MicroOcpp/Model/Metering/MeterValue.cpp b/src/MicroOcpp/Model/Metering/MeterValue.cpp index c298f526..72708166 100644 --- a/src/MicroOcpp/Model/Metering/MeterValue.cpp +++ b/src/MicroOcpp/Model/Metering/MeterValue.cpp @@ -35,6 +35,7 @@ SampledValue::~SampledValue() { case Type::String: MO_FREE(valueString); valueString = nullptr; + break; #if MO_ENABLE_V201 case Type::SignedValue: valueSigned->onDestroy(valueSigned->user_data); diff --git a/src/MicroOcpp/Platform.cpp b/src/MicroOcpp/Platform.cpp index 51a1dfd0..e087367c 100644 --- a/src/MicroOcpp/Platform.cpp +++ b/src/MicroOcpp/Platform.cpp @@ -70,7 +70,7 @@ uint32_t defaultTickCbImpl() { } //namespace MicroOcpp #else namespace MicroOcpp { -void (*defaultDebugCbImpl)(const char*) = nullptr +void (*defaultDebugCbImpl)(const char*) = nullptr; uint32_t (*defaultTickCbImpl)() = nullptr; } //namespace MicroOcpp #endif From 44f05c301f8d7d2969b2cff3a01700b5935a76db Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 17:35:18 +0200 Subject: [PATCH 32/50] fix workflows --- .github/workflows/pio.yml | 3 ++- src/MicroOcpp/Context.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pio.yml b/.github/workflows/pio.yml index f533fb1e..90983518 100644 --- a/.github/workflows/pio.yml +++ b/.github/workflows/pio.yml @@ -18,6 +18,7 @@ jobs: strategy: matrix: example: [examples/ESP/main.cpp, examples/ESP-TLS/main.cpp] + pio_platform: [nodemcuv2, esp32-development-board] steps: - uses: actions/checkout@v2 @@ -42,6 +43,6 @@ jobs: - name: Install library dependencies run: pio pkg install - name: Run PlatformIO - run: pio ci --lib="." --project-conf=platformio.ini ${{ matrix.dashboard-extra }} + run: pio ci --lib="." --project-conf=platformio.ini -e ${{ matrix.pio_platform }} ${{ matrix.dashboard-extra }} env: PLATFORMIO_CI_SRC: ${{ matrix.example }} diff --git a/src/MicroOcpp/Context.cpp b/src/MicroOcpp/Context.cpp index cfa88239..f6890930 100644 --- a/src/MicroOcpp/Context.cpp +++ b/src/MicroOcpp/Context.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -209,7 +210,7 @@ void Context::setCertificateStore(CertificateStore *certStore) { CertificateStore *Context::getCertificateStore() { #if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS if (!certStore && filesystem) { - certStore = makeCertificateStoreMbedTLS(filesystem); + certStore = makeCertificateStoreMbedTLS(filesystem).release(); if (!certStore) { MO_DBG_ERR("OOM"); return nullptr; From 1c80d131dd5d3be36fd4d3bc889eb4663992e603 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:03:46 +0200 Subject: [PATCH 33/50] fix workflows --- examples/ESP/main.cpp | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/examples/ESP/main.cpp b/examples/ESP/main.cpp index 306e700b..294e59e8 100644 --- a/examples/ESP/main.cpp +++ b/examples/ESP/main.cpp @@ -24,7 +24,7 @@ ESP8266WiFiMulti WiFiMulti; // // Settings which worked for my SteVe instance: // -//#define OCPP_BACKEND_URL "ws://192.168.178.100:8180/steve/websocket/CentralSystemService" +//#define OCPP_BACKEND_URL "ws://192.168.178.100:8080/steve/websocket/CentralSystemService" //#define OCPP_CHARGE_BOX_ID "esp-charger" void setup() { @@ -58,22 +58,32 @@ void setup() { /* * Initialize the OCPP library */ - mocpp_initialize(OCPP_BACKEND_URL, OCPP_CHARGE_BOX_ID, "My Charging Station", "My company name"); + mo_initialize(); + + mo_setWebsocketUrl( + OCPP_BACKEND_URL, //OCPP backend URL + OCPP_CHARGE_BOX_ID, //ChargeBoxId, assigned by OCPP backend + nullptr, //Authorization key (`nullptr` disables Basic Auth) + nullptr); //OCPP backend TLS certificate (`nullptr` disables certificate check) + + mo_setBootNotificationData("My Charging Station", "My company name"); + + mo_setOcppVersion(MO_OCPP_V16); //Run OCPP 1.6. For v2.0.1, pass argument `MO_OCPP_V201` /* * Integrate OCPP functionality. You can leave out the following part if your EVSE doesn't need it. */ - setEnergyMeterInput([]() { + mo_setEnergyMeterInput([]() { //take the energy register of the main electricity meter and return the value in watt-hours - return 0.f; + return 0; }); - setSmartChargingCurrentOutput([](float limit) { + mo_setSmartChargingCurrentOutput([](float limit) { //set the SAE J1772 Control Pilot value here Serial.printf("[main] Smart Charging allows maximum charge rate: %.0f\n", limit); }); - setConnectorPluggedInput([]() { + mo_setConnectorPluggedInput([]() { //return true if an EV is plugged to this EVSE return false; }); @@ -86,12 +96,12 @@ void loop() { /* * Do all OCPP stuff (process WebSocket input, send recorded meter values to Central System, etc.) */ - mocpp_loop(); + mo_loop(); /* * Energize EV plug if OCPP transaction is up and running */ - if (ocppPermitsCharge()) { + if (mo_ocppPermitsCharge()) { //OCPP set up and transaction running. Energize the EV plug here } else { //No transaction running at the moment. De-energize EV plug @@ -103,7 +113,7 @@ void loop() { if (/* RFID chip detected? */ false) { String idTag = "0123456789ABCD"; //e.g. idTag = RFID.readIdTag(); - if (!getTransaction()) { + if (!mo_getTransactionIdTag()) { //no transaction running or preparing. Begin a new transaction Serial.printf("[main] Begin Transaction with idTag %s\n", idTag.c_str()); @@ -112,7 +122,7 @@ void loop() { * and listen to the ConnectorPlugged Input. When the Authorization succeeds and an EV * is plugged, the OCPP lib will send the StartTransaction */ - auto ret = beginTransaction(idTag.c_str()); + auto ret = mo_beginTransaction(idTag.c_str()); if (ret) { Serial.println(F("[main] Transaction initiated. OCPP lib will send a StartTransaction when" \ @@ -122,15 +132,10 @@ void loop() { } } else { - //Transaction already initiated. Check if to stop current Tx by RFID card - if (idTag.equals(getTransactionIdTag())) { - //card matches -> user can stop Tx - Serial.println(F("[main] End transaction by RFID card")); - - endTransaction(idTag.c_str()); - } else { - Serial.println(F("[main] Cannot end transaction by RFID card (different card?)")); - } + //Transaction already initiated. Attempt to stop current Tx by RFID card. If idTag is the same, + //or in the same group, then MO will stop the transaction and `mo_ocppPermitsCharge()` becomes + //false + mo_endTransaction(idTag.c_str(), nullptr); //`idTag` and `reason` can be nullptr } } From 95100410cccb1a3eb227627cbf73e8a191914bde Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:18:37 +0200 Subject: [PATCH 34/50] fix workflows --- tests/benchmarks/firmware_size/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/benchmarks/firmware_size/main.cpp b/tests/benchmarks/firmware_size/main.cpp index 549061fc..66acbbf2 100644 --- a/tests/benchmarks/firmware_size/main.cpp +++ b/tests/benchmarks/firmware_size/main.cpp @@ -121,6 +121,11 @@ void setup() { #endif mo_setOnResetExecute([] () {}); + mo_sendRequest(ctx, "", "", [] (const char*,void*) {}, [] (void*) {}, nullptr); + mo_setRequestHandler(ctx, "", [] (const char*,const char*,void**,void*) {}, [] (const char*,char*,size_t,void*,void*) {return 0;}, [] (const char*,void*,void*) {}, nullptr); + mo_setOnReceiveRequest(ctx, "", [] (const char*,const char*,void*) {}, nullptr); + mo_setOnSendConf(ctx, "", [] (const char*, const char*, void*) {}, nullptr); + mo_getContext(); } From 1959de22590018f71724628d3744aed49f84220b Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:32:41 +0200 Subject: [PATCH 35/50] fix workflows --- CMakeLists.txt | 1 - .../Model/Certificates/Certificate_c.cpp | 77 ------------------- .../Model/Certificates/Certificate_c.h | 54 ------------- .../benchmarks/scripts/eval_firmware_size.py | 1 - 4 files changed, 133 deletions(-) delete mode 100644 src/MicroOcpp/Model/Certificates/Certificate_c.cpp delete mode 100644 src/MicroOcpp/Model/Certificates/Certificate_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c801fc22..6436d964 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,6 @@ set(MO_SRC src/MicroOcpp/Model/Reservation/Reservation.cpp src/MicroOcpp/Model/SecurityEvent/SecurityEventService.cpp src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp - src/MicroOcpp/Model/Certificates/Certificate_c.cpp src/MicroOcpp/Model/Certificates/Certificate.cpp src/MicroOcpp/Model/Certificates/CertificateService.cpp src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp deleted file mode 100644 index f502ead1..00000000 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#include - -#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT - -#include -#include - -#include - -namespace MicroOcpp { - -/* - * C++ wrapper for the C-style certificate interface - */ -class CertificateStoreC : public CertificateStore, public MemoryManaged { -private: - mo_cert_store *certstore = nullptr; -public: - CertificateStoreC(mo_cert_store *certstore) : MemoryManaged("v201.Certificates.CertificateStoreC"), certstore(certstore) { - - } - - ~CertificateStoreC() = default; - - GetInstalledCertificateStatus getCertificateIds(const Vector& certificateType, Vector& out) override { - out.clear(); - - mo_cert_chain_hash *cch; - - auto ret = certstore->getCertificateIds(certstore->user_data, &certificateType[0], certificateType.size(), &cch); - if (ret == GetInstalledCertificateStatus_NotFound || !cch) { - return GetInstalledCertificateStatus_NotFound; - } - - bool err = false; - - for (mo_cert_chain_hash *it = cch; it && !err; it = it->next) { - out.emplace_back(); - auto &chd_el = out.back(); - chd_el.certificateType = it->certType; - memcpy(&chd_el.certificateHashData, &it->certHashData, sizeof(mo_cert_hash)); - } - - while (cch) { - mo_cert_chain_hash *el = cch; - cch = cch->next; - el->invalidate(el); - } - - if (err) { - out.clear(); - } - - return out.empty() ? - GetInstalledCertificateStatus_NotFound : - GetInstalledCertificateStatus_Accepted; - } - - DeleteCertificateStatus deleteCertificate(const CertificateHash& hash) override { - return certstore->deleteCertificate(certstore->user_data, &hash); - } - - InstallCertificateStatus installCertificate(InstallCertificateType certificateType, const char *certificate) override { - return certstore->installCertificate(certstore->user_data, certificateType, certificate); - } -}; - -std::unique_ptr makeCertificateStoreCwrapper(mo_cert_store *certstore) { - return std::unique_ptr(new CertificateStoreC(certstore)); -} - -} //namespace MicroOcpp -#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.h b/src/MicroOcpp/Model/Certificates/Certificate_c.h deleted file mode 100644 index 06399db3..00000000 --- a/src/MicroOcpp/Model/Certificates/Certificate_c.h +++ /dev/null @@ -1,54 +0,0 @@ -// matth-x/MicroOcpp -// Copyright Matthias Akstaller 2019 - 2024 -// MIT License - -#ifndef MO_CERTIFICATE_C_H -#define MO_CERTIFICATE_C_H - -#include - -#if (MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT - -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct mo_cert_chain_hash { - void *user_data; //set this at your choice. MO passes it back to the functions below - - enum GetCertificateIdType certType; - mo_cert_hash certHashData; - //mo_cert_hash *childCertificateHashData; - - struct mo_cert_chain_hash *next; //link to next list element if result of getCertificateIds - - void (*invalidate)(void *user_data); //free resources here. Guaranteed to be called -} mo_cert_chain_hash; - -typedef struct mo_cert_store { - void *user_data; //set this at your choice. MO passes it back to the functions below - - enum GetInstalledCertificateStatus (*getCertificateIds)(void *user_data, const enum GetCertificateIdType certType [], size_t certTypeLen, mo_cert_chain_hash **out); - enum DeleteCertificateStatus (*deleteCertificate)(void *user_data, const mo_cert_hash *hash); - enum InstallCertificateStatus (*installCertificate)(void *user_data, enum InstallCertificateType certType, const char *cert); -} mo_cert_store; - -#ifdef __cplusplus -} //extern "C" - -#include - -namespace MicroOcpp { - -std::unique_ptr makeCertificateStoreCwrapper(mo_cert_store *certstore); - -} //namespace MicroOcpp - -#endif //__cplusplus - -#endif //(MO_ENABLE_V16 || MO_ENABLE_V201) && MO_ENABLE_CERT_MGMT -#endif diff --git a/tests/benchmarks/scripts/eval_firmware_size.py b/tests/benchmarks/scripts/eval_firmware_size.py index 9fe770c7..84a33b8e 100755 --- a/tests/benchmarks/scripts/eval_firmware_size.py +++ b/tests/benchmarks/scripts/eval_firmware_size.py @@ -86,7 +86,6 @@ def categorize_table(df): df.at['Model/Certificates/Certificate.cpp', 'Module'] = MODULE_CERTS df.at['Model/Certificates/CertificateMbedTLS.cpp', 'Module'] = MODULE_CERTS df.at['Model/Certificates/CertificateService.cpp', 'Module'] = MODULE_CERTS - df.at['Model/Certificates/Certificate_c.cpp', 'Module'] = MODULE_CERTS df.at['Model/Configuration/Configuration.cpp', 'Module'] = MODULE_PROVISIONING_CONF df.at['Model/Configuration/ConfigurationContainer.cpp', 'Module'] = MODULE_PROVISIONING_CONF df.at['Model/Configuration/ConfigurationService.cpp', 'Module'] = MODULE_PROVISIONING_CONF From 7a35feafbf25caaad33f48ad0fac2bb37aa83442 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:38:34 +0200 Subject: [PATCH 36/50] fix workflows --- src/MicroOcpp/Core/Time.cpp | 5 +++++ src/MicroOcpp/Core/Time.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index fd7e1309..fd67f8c0 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -36,6 +36,11 @@ Timestamp::Timestamp() : MemoryManaged("Timestamp") { } +Timestamp::Timestamp(const Timestamp& other) { + *this = other; + updateMemoryTag(other.getMemoryTag()); +} + bool Timestamp::isUnixTime() const { return time >= MO_MIN_TIME && time <= MO_MAX_TIME; } diff --git a/src/MicroOcpp/Core/Time.h b/src/MicroOcpp/Core/Time.h index d694fff4..2124bbfe 100644 --- a/src/MicroOcpp/Core/Time.h +++ b/src/MicroOcpp/Core/Time.h @@ -45,7 +45,7 @@ class Timestamp : public MemoryManaged { public: Timestamp(); - Timestamp(const Timestamp& other) = default; + Timestamp(const Timestamp& other); MicroOcpp::Timestamp& operator=(const Timestamp& other) = default; bool isUnixTime() const; From 0acd1978bcaa6340d181bb2e17c4f7a3320f7832 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:42:37 +0200 Subject: [PATCH 37/50] update debug output --- .github/workflows/documentation.yml | 4 ---- tests/benchmarks/scripts/eval_firmware_size.py | 9 --------- 2 files changed, 13 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 40cf9c3a..98ad77d6 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -178,8 +178,6 @@ jobs: with: name: Google bloaty path: tools/bloaty/build - - name: Debug - run: ls firmware - name: Run bloaty run: | mkdir -p docs/assets/tables @@ -187,8 +185,6 @@ jobs: tools/bloaty/build/bloaty firmware/firmware_v16.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16.csv tools/bloaty/build/bloaty firmware/firmware_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v201.csv tools/bloaty/build/bloaty firmware/firmware_v16_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16_v201.csv - - name: Debug - run: ls docs/assets/tables - name: Evaluate and create reports run: python tests/benchmarks/scripts/eval_firmware_size.py - name: Upload reports diff --git a/tests/benchmarks/scripts/eval_firmware_size.py b/tests/benchmarks/scripts/eval_firmware_size.py index 84a33b8e..c9f62683 100755 --- a/tests/benchmarks/scripts/eval_firmware_size.py +++ b/tests/benchmarks/scripts/eval_firmware_size.py @@ -28,15 +28,6 @@ def load_compilation_units(fn): cunits = pd.merge(cunits_v16, cunits_v201, on=COLUMN_CUNITS, how='outer') cunits = pd.merge(cunits, cunits_v16_v201, on=COLUMN_CUNITS, how='outer') -print("cunits_v16") -print(cunits_v16) -print("cunits_v201") -print(cunits_v201) -print("cunits_v16_v201") -print(cunits_v16_v201) -print("cunits") -print(cunits) - # categorize data def categorize_table(df): From 14051a515a9e6a408ba43d5e7266fec414a6de10 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:22:15 +0200 Subject: [PATCH 38/50] fix workflows --- src/MicroOcpp/Core/Time.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index fd67f8c0..e5f3f4aa 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -38,7 +38,7 @@ Timestamp::Timestamp() : MemoryManaged("Timestamp") { Timestamp::Timestamp(const Timestamp& other) { *this = other; - updateMemoryTag(other.getMemoryTag()); + updateMemoryTag(other.getMemoryTag(), nullptr); } bool Timestamp::isUnixTime() const { From 18e06c41132a97a266a049c38bddf818985fb379 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:12:42 +0200 Subject: [PATCH 39/50] fix log upload --- src/MicroOcpp.cpp | 2 +- src/MicroOcpp.h | 2 +- .../Model/Diagnostics/DiagnosticsService.cpp | 16 +++++++++++----- src/MicroOcpp/Operations/GetLog.cpp | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index d7034cd7..a4156f5f 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -1868,7 +1868,7 @@ MO_FilesystemAdapter *mo_getFilesystem2(MO_Context *ctx) { } #if MO_ENABLE_MBEDTLS -void mo_setFtpConfig(MO_Context *ctx, MO_FTPConfig ftpConfig) { +void mo_setFtpConfig2(MO_Context *ctx, MO_FTPConfig ftpConfig) { if (!ctx) { MO_DBG_ERR("OCPP uninitialized"); //need to call mocpp_initialize before return; diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index 0dc92d98..1e19359e 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -473,7 +473,7 @@ MO_FilesystemAdapter *mo_getFilesystem2(MO_Context *ctx); #if MO_ENABLE_MBEDTLS //Set FTP security parameters (e.g. client cert). See "FtpMbedTLS.h" for all options //To use a custom FTP client, subclass `FtpClient` (see "Ftp.h") and pass to C++ `Context` object -void mo_setFtpConfig(MO_Context *ctx, MO_FTPConfig ftpConfig); +void mo_setFtpConfig2(MO_Context *ctx, MO_FTPConfig ftpConfig); #if MO_ENABLE_DIAGNOSTICS //Provide MO with diagnostics data for the "GetDiagnostics" operation. Should contain human-readable text which diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 87920399..eede0414 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -347,7 +347,7 @@ bool DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned } #endif //MO_ENABLE_V16 -MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int retries, unsigned int retryInterval, const char *remoteLocation, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]) { +MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int retries, unsigned int retryInterval, const char *location, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]) { if (runCustomUpload || this->retries > 0) { MO_DBG_INFO("upload still running"); @@ -405,13 +405,21 @@ MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int r MO_FREE(this->location); this->location = nullptr; - size_t locationSize = strlen(location); + size_t locationSize = strlen(location) + 1; this->location = static_cast(MO_MALLOC(getMemoryTag(), locationSize)); if (!this->location) { MO_DBG_ERR("OOM"); goto fail; } + { + int ret = snprintf(this->location, locationSize, "%s", location); + if (ret < 0 || (size_t)ret >= locationSize) { + MO_DBG_ERR("snprintf: %i", ret); + goto fail; + } + } + MO_FREE(this->filename); this->filename = nullptr; @@ -636,6 +644,7 @@ bool DiagnosticsService::uploadDiagnostics() { } diagPreambleLen += (size_t)ret; + ret = 0; #if MO_ENABLE_V16 if (ocppVersion == MO_OCPP_V16) { @@ -654,8 +663,6 @@ bool DiagnosticsService::uploadDiagnostics() { auto availSvcEvse1 = availSvc ? availSvc->getEvse(1) : nullptr; auto availSvcEvse2 = availSvc ? availSvc->getEvse(2) : nullptr; - ret = 0; - if (ret >= 0 && (size_t)ret + diagPostambleLen < MO_DIAG_POSTAMBLE_SIZE) { diagPostambleLen += (size_t)ret; ret = snprintf(diagPostamble + diagPostambleLen, MO_DIAG_POSTAMBLE_SIZE - diagPostambleLen, @@ -923,7 +930,6 @@ bool DiagnosticsService::startFtpUpload() { memcpy(buf + written, fileHeading, (size_t)writeLen); written += (size_t)writeLen; - filesystem->close(file); } filesystem->seek(file, diagFilesBackTransferred); diff --git a/src/MicroOcpp/Operations/GetLog.cpp b/src/MicroOcpp/Operations/GetLog.cpp index d1d00e79..fa7dfe75 100644 --- a/src/MicroOcpp/Operations/GetLog.cpp +++ b/src/MicroOcpp/Operations/GetLog.cpp @@ -70,7 +70,7 @@ std::unique_ptr GetLog::createConf(){ auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2)); JsonObject payload = doc->to(); payload["status"] = mo_serializeGetLogStatus(status); - payload["fileName"] = (const char*)filename; //force zero-copy + payload["filename"] = (const char*)filename; //force zero-copy return doc; } From 76be884ce11911f6678e24b764156478351141bc Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:47:17 +0200 Subject: [PATCH 40/50] fix GetLog retry behavior --- .../Model/Diagnostics/DiagnosticsService.cpp | 28 ++++++++----------- .../Model/Diagnostics/DiagnosticsService.h | 2 +- src/MicroOcpp/Operations/GetDiagnostics.cpp | 2 +- src/MicroOcpp/Operations/GetLog.cpp | 2 +- tests/benchmarks/scripts/measure_heap.py | 2 ++ 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index eede0414..8560a11d 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -238,7 +238,7 @@ void DiagnosticsService::loop() { dtNextTry = -1; } - if (retries > 0 && dtNextTry >= 0) { + if (retries >= 0 && dtNextTry >= 0) { if (!uploadIssued) { @@ -261,7 +261,7 @@ void DiagnosticsService::loop() { uploadFailure = false; } else { MO_DBG_ERR("cannot upload via FTP. Abort"); - retries = 0; + retries = -1; uploadIssued = false; uploadFailure = true; cleanUploadData(); @@ -274,7 +274,7 @@ void DiagnosticsService::loop() { //success! MO_DBG_DEBUG("end upload routine (by status)"); uploadIssued = false; - retries = 0; + retries = -1; cleanUploadData(); cleanGetLogData(); } @@ -303,17 +303,12 @@ void DiagnosticsService::loop() { //either we have UploadFailed status or (NotUploaded + timeout) here MO_DBG_WARN("Upload timeout or failed"); cleanUploadData(); + uploadIssued = false; - const int TRANSITION_DELAY = 10; - if (retryInterval <= UPLOAD_TIMEOUT + TRANSITION_DELAY) { - nextTry = clock.getUptime(); - clock.add(nextTry, TRANSITION_DELAY); //wait for another 10 seconds - } else { - clock.add(nextTry, retryInterval); - } + clock.add(nextTry, retryInterval); retries--; - if (retries == 0) { + if (retries < 0) { MO_DBG_DEBUG("end upload routine (no more retry)"); uploadFailure = true; cleanGetLogData(); @@ -349,7 +344,7 @@ bool DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int retries, unsigned int retryInterval, const char *location, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]) { - if (runCustomUpload || this->retries > 0) { + if (runCustomUpload || this->retries >= 0) { MO_DBG_INFO("upload still running"); return MO_GetLogStatus_Rejected; } @@ -444,8 +439,8 @@ MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int r snprintf(this->filename, filenameSize, "%s", filenameOut); this->logType = type; - this->retries = retries > 0 ? retries : 1; - this->retryInterval = retryInterval > 30 ? retryInterval : 30; + this->retries = retries >= 0 ? retries : 0; + this->retryInterval = retryInterval >= 0 ? retryInterval : 30; this->oldestTimestamp = oldestTimestamp; this->latestTimestamp = latestTimestamp; @@ -475,7 +470,6 @@ MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int r #endif nextTry = clock.now(); - clock.add(nextTry, 5); //wait for 5s before upload uploadIssued = false; uploadFailure = false; @@ -496,7 +490,7 @@ MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int r } int DiagnosticsService::getRequestId() { - if (runCustomUpload || this->retries > 0) { + if (runCustomUpload || this->retries >= 0) { return requestId; } else { return -1; @@ -1000,7 +994,7 @@ void DiagnosticsService::cleanGetLogData() { location = nullptr; MO_FREE(filename); filename = nullptr; - retries = 0; + retries = -1; retryInterval = 0; oldestTimestamp = Timestamp(); latestTimestamp = Timestamp(); diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h index ac7ea3fe..d0c34eec 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h @@ -67,7 +67,7 @@ class DiagnosticsService : public MemoryManaged { int requestId = -1; char *location = nullptr; char *filename = nullptr; - int retries = 0; + int retries = -1; int retryInterval = 0; Timestamp oldestTimestamp; Timestamp latestTimestamp; diff --git a/src/MicroOcpp/Operations/GetDiagnostics.cpp b/src/MicroOcpp/Operations/GetDiagnostics.cpp index 3b5ddbfb..4eadf374 100644 --- a/src/MicroOcpp/Operations/GetDiagnostics.cpp +++ b/src/MicroOcpp/Operations/GetDiagnostics.cpp @@ -27,7 +27,7 @@ void GetDiagnostics::processReq(JsonObject payload) { return; } - int retries = payload["retries"] | 1; + int retries = payload["retries"] | 0; int retryInterval = payload["retryInterval"] | 180; if (retries < 0 || retryInterval < 0) { errorCode = "PropertyConstraintViolation"; diff --git a/src/MicroOcpp/Operations/GetLog.cpp b/src/MicroOcpp/Operations/GetLog.cpp index fa7dfe75..cd8a24c1 100644 --- a/src/MicroOcpp/Operations/GetLog.cpp +++ b/src/MicroOcpp/Operations/GetLog.cpp @@ -30,7 +30,7 @@ void GetLog::processReq(JsonObject payload) { return; } - int retries = payload["retries"] | 1; + int retries = payload["retries"] | 0; int retryInterval = payload["retryInterval"] | 180; if (retries < 0 || retryInterval < 0) { errorCode = "PropertyConstraintViolation"; diff --git a/tests/benchmarks/scripts/measure_heap.py b/tests/benchmarks/scripts/measure_heap.py index ca02d33b..e4fe50dd 100644 --- a/tests/benchmarks/scripts/measure_heap.py +++ b/tests/benchmarks/scripts/measure_heap.py @@ -98,6 +98,8 @@ 'TC_J_08_CS', 'TC_J_09_CS', 'TC_J_10_CS', + 'TC_N_25_CS', + 'TC_N_26_CS', ] # Result data set From fa36f78442121858ab49c9eeefab0b8422df4253 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:57:32 +0200 Subject: [PATCH 41/50] fix upload security log --- .../Model/Diagnostics/DiagnosticsService.cpp | 12 ++++++------ tests/benchmarks/scripts/measure_heap.py | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index 8560a11d..dd5cc3d4 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -626,7 +626,7 @@ bool DiagnosticsService::uploadDiagnostics() { int ret; ret = snprintf(diagPreamble, MO_DIAG_PREAMBLE_SIZE, - "### %s Security Log%s%s\n%s\n", + "### %s Hardware Diagnostics%s%s\n%s\n", cpModel ? cpModel : "Charger", fwVersion ? " - v. " : "", fwVersion ? fwVersion : "", jsonDate); @@ -774,8 +774,8 @@ bool DiagnosticsService::uploadSecurityLog() { diagReaderHasData = false; - auto cpModel = makeString(getMemoryTag()); - auto fwVersion = makeString(getMemoryTag()); + const char *cpModel = nullptr; + const char *fwVersion = nullptr; if (auto bootService = context.getModelCommon().getBootService()) { auto bnData = bootService->getBootNotificationData(); @@ -792,9 +792,9 @@ bool DiagnosticsService::uploadSecurityLog() { int ret; ret = snprintf(diagPreamble, MO_DIAG_PREAMBLE_SIZE, - "### %s Hardware Diagnostics%s%s\n%s\n", - cpModel.c_str(), - fwVersion.empty() ? "" : " - v. ", fwVersion.c_str(), + "### %s Security Log%s%s\n%s\n", + cpModel ? cpModel : "Charger", + fwVersion ? " - v. " : "", fwVersion ? fwVersion : "", jsonDate); if (ret < 0 || (size_t)ret >= MO_DIAG_PREAMBLE_SIZE) { diff --git a/tests/benchmarks/scripts/measure_heap.py b/tests/benchmarks/scripts/measure_heap.py index e4fe50dd..0c0fa95f 100644 --- a/tests/benchmarks/scripts/measure_heap.py +++ b/tests/benchmarks/scripts/measure_heap.py @@ -100,6 +100,7 @@ 'TC_J_10_CS', 'TC_N_25_CS', 'TC_N_26_CS', + 'TC_N_35_CS', ] # Result data set From 294e5b944da52a84b8106feb03f5b19550321e82 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:56:04 +0200 Subject: [PATCH 42/50] fix BootNotification test cases --- src/MicroOcpp/Model/Boot/BootService.cpp | 7 ++++--- .../Model/RemoteControl/RemoteControlService.cpp | 14 ++++++++++++++ src/MicroOcpp/Model/Variables/VariableService.cpp | 2 +- src/MicroOcpp/Operations/BootNotification.cpp | 4 ++-- src/MicroOcpp/Operations/BootNotification.h | 3 ++- tests/benchmarks/scripts/measure_heap.py | 3 +++ 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/MicroOcpp/Model/Boot/BootService.cpp b/src/MicroOcpp/Model/Boot/BootService.cpp index 13c1646b..f29bf47e 100644 --- a/src/MicroOcpp/Model/Boot/BootService.cpp +++ b/src/MicroOcpp/Model/Boot/BootService.cpp @@ -93,7 +93,7 @@ bool BootService::setup() { rcService->addTriggerMessageHandler("BootNotification", [] (Context& context) -> Operation* { auto& model = context.getModel16(); auto& bootSvc = *model.getBootService(); - return new BootNotification(context, bootSvc, model.getHeartbeatService(), bootSvc.getBootNotificationData()); + return new BootNotification(context, bootSvc, model.getHeartbeatService(), bootSvc.getBootNotificationData(), nullptr); }); } #endif //MO_ENABLE_V16 @@ -128,7 +128,7 @@ bool BootService::setup() { //F06.FR.17: if previous BootNotification was accepted, don't allow triggering a new one return TriggerMessageStatus::Rejected; } else { - auto operation = new BootNotification(context, bootSvc, model.getHeartbeatService(), bootSvc.getBootNotificationData()); + auto operation = new BootNotification(context, bootSvc, model.getHeartbeatService(), bootSvc.getBootNotificationData(), "Triggered"); if (!operation) { MO_DBG_ERR("OOM"); return TriggerMessageStatus::ERR_INTERNAL; @@ -219,7 +219,7 @@ void BootService::loop() { * Create BootNotification. The BootNotifaction object will fetch its paremeters from * this class and notify this class about the response */ - auto bootNotification = makeRequest(context, new BootNotification(context, *this, context.getModelCommon().getHeartbeatService(), getBootNotificationData())); + auto bootNotification = makeRequest(context, new BootNotification(context, *this, context.getModelCommon().getHeartbeatService(), getBootNotificationData(), nullptr)); bootNotification->setTimeout(interval_s); context.getMessageService().sendRequestPreBoot(std::move(bootNotification)); @@ -313,6 +313,7 @@ void BootService::setRetryInterval(int interval_s) { this->interval_s = MO_BOOT_INTERVAL_DEFAULT; } else { this->interval_s = interval_s; + this->interval_s += 1; //TC_B_03_CS expects retry after at least interval_s secs and doesn't tolerate network jitter } lastBootNotification = context.getClock().getUptime(); } diff --git a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp index 58e064a5..252acc89 100644 --- a/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp +++ b/src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp @@ -284,6 +284,13 @@ bool RemoteControlService::addTriggerMessageHandler(const char *operationType, T #if MO_ENABLE_V16 v16::RemoteStartStopStatus RemoteControlService::remoteStartTransaction(int connectorId, const char *idTag, std::unique_ptr chargingProfile) { + if (auto bSvc = context.getModel16().getBootService()) { + if (bSvc->getRegistrationStatus() != RegistrationStatus::Accepted) { + MO_DBG_WARN("BootNotofication not accepted"); + return v16::RemoteStartStopStatus::Rejected; + } + } + auto configService = context.getModel16().getConfigurationService(); auto authorizeRemoteTxRequests = configService ? configService->declareConfiguration("AuthorizeRemoteTxRequests", false) : nullptr; if (!authorizeRemoteTxRequests) { @@ -396,6 +403,13 @@ v16::RemoteStartStopStatus RemoteControlService::remoteStopTransaction(int trans v201::RequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, v201::IdToken idToken, std::unique_ptr chargingProfile, char *transactionIdOut, size_t transactionIdBufSize) { + if (auto bSvc = context.getModel201().getBootService()) { + if (bSvc->getRegistrationStatus() != RegistrationStatus::Accepted) { + MO_DBG_WARN("BootNotofication not accepted"); + return v201::RequestStartStopStatus::Rejected; + } + } + if (!txService201) { MO_DBG_ERR("TxService uninitialized"); return v201::RequestStartStopStatus::Rejected; diff --git a/src/MicroOcpp/Model/Variables/VariableService.cpp b/src/MicroOcpp/Model/Variables/VariableService.cpp index 2da8e777..77c07284 100644 --- a/src/MicroOcpp/Model/Variables/VariableService.cpp +++ b/src/MicroOcpp/Model/Variables/VariableService.cpp @@ -230,7 +230,7 @@ void VariableService::loop() { notifyReportInProgress = true; - context.getMessageService().sendRequest(std::move(notifyReport)); + context.getMessageService().sendRequestPreBoot(std::move(notifyReport)); } template diff --git a/src/MicroOcpp/Operations/BootNotification.cpp b/src/MicroOcpp/Operations/BootNotification.cpp index d60cd7e1..dcf69388 100644 --- a/src/MicroOcpp/Operations/BootNotification.cpp +++ b/src/MicroOcpp/Operations/BootNotification.cpp @@ -17,7 +17,7 @@ using namespace MicroOcpp; -BootNotification::BootNotification(Context& context, BootService& bootService, HeartbeatService *heartbeatService, const MO_BootNotificationData& bnData) : MemoryManaged("v16/v201.Operation.", "BootNotification"), context(context), bootService(bootService), heartbeatService(heartbeatService), bnData(bnData), ocppVersion(context.getOcppVersion()) { +BootNotification::BootNotification(Context& context, BootService& bootService, HeartbeatService *heartbeatService, const MO_BootNotificationData& bnData, const char *reason201) : MemoryManaged("v16/v201.Operation.", "BootNotification"), context(context), bootService(bootService), heartbeatService(heartbeatService), bnData(bnData), reason201(reason201), ocppVersion(context.getOcppVersion()) { } @@ -47,7 +47,7 @@ std::unique_ptr BootNotification::createReq() { if (ocppVersion == MO_OCPP_V201) { auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(2)); JsonObject payload = doc->to(); - payload["reason"] = "Unknown"; + payload["reason"] = reason201 ? reason201 : "Unknown"; JsonObject chargingStation = payload.createNestedObject("chargingStation"); if (bnData.chargePointSerialNumber) {chargingStation["serialNumber"] = bnData.chargePointSerialNumber;} if (bnData.chargePointModel) {chargingStation["model"] = bnData.chargePointModel;} diff --git a/src/MicroOcpp/Operations/BootNotification.h b/src/MicroOcpp/Operations/BootNotification.h index 043a3500..774ae66f 100644 --- a/src/MicroOcpp/Operations/BootNotification.h +++ b/src/MicroOcpp/Operations/BootNotification.h @@ -23,10 +23,11 @@ class BootNotification : public Operation, public MemoryManaged { BootService& bootService; HeartbeatService *heartbeatService = nullptr; const MO_BootNotificationData& bnData; + const char *reason201 = nullptr; int ocppVersion = -1; const char *errorCode = nullptr; public: - BootNotification(Context& context, BootService& bootService, HeartbeatService *heartbeatService, const MO_BootNotificationData& bnData); + BootNotification(Context& context, BootService& bootService, HeartbeatService *heartbeatService, const MO_BootNotificationData& bnData, const char *reason201); ~BootNotification() = default; diff --git a/tests/benchmarks/scripts/measure_heap.py b/tests/benchmarks/scripts/measure_heap.py index 0c0fa95f..9cf19518 100644 --- a/tests/benchmarks/scripts/measure_heap.py +++ b/tests/benchmarks/scripts/measure_heap.py @@ -16,6 +16,9 @@ # Test case selection (commented out a few to simplify testing for now) testcase_name_list = [ + 'TC_B_01_CS', + 'TC_B_02_CS', + 'TC_B_03_CS', 'TC_B_06_CS', 'TC_B_07_CS', 'TC_B_09_CS', From 7aa118f7ab11535a66da7a511848f0a9cde3dbfc Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 20 Jul 2025 17:38:58 +0200 Subject: [PATCH 43/50] fix test cases --- src/MicroOcpp/Model/Transactions/TransactionService201.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index 5c2460b4..0aade978 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -402,6 +402,8 @@ void TransactionServiceEvse::loop() { txStartCondition = true; if (transaction->remoteStartId >= 0) { triggerReason = MO_TxEventTriggerReason_RemoteStart; + } else if (!transaction->trackAuthorized) { + triggerReason = MO_TxEventTriggerReason_Authorized; } else { triggerReason = MO_TxEventTriggerReason_CablePluggedIn; } From c913e5102aadd82c43f624d9c56ab2d66b8f858c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 20 Jul 2025 17:41:03 +0200 Subject: [PATCH 44/50] update test case build ref --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 98ad77d6..4ae8205b 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -34,7 +34,7 @@ jobs: with: repository: matth-x/MicroOcppSimulator path: MicroOcppSimulator - ref: 65b1c2e9c8a3f40907a823a0bc2f657423ebef65 + ref: 491ff2f3b48d5b668a327b21cf5080e818f42a22 submodules: 'recursive' - name: Clean MicroOcpp submodule run: | From 46afc933331954574018c34777f8e725a64ec377 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 20 Jul 2025 18:02:28 +0200 Subject: [PATCH 45/50] fix compilation --- src/MicroOcpp/Core/Time.cpp | 3 +-- src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp | 4 ++-- src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h | 4 ++-- src/MicroOcpp/Model/Transactions/TransactionService201.cpp | 5 ++--- src/MicroOcpp/Operations/GetDiagnostics.cpp | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/MicroOcpp/Core/Time.cpp b/src/MicroOcpp/Core/Time.cpp index e5f3f4aa..1d5eaee4 100644 --- a/src/MicroOcpp/Core/Time.cpp +++ b/src/MicroOcpp/Core/Time.cpp @@ -36,9 +36,8 @@ Timestamp::Timestamp() : MemoryManaged("Timestamp") { } -Timestamp::Timestamp(const Timestamp& other) { +Timestamp::Timestamp(const Timestamp& other) : MemoryManaged(other.getMemoryTag()) { *this = other; - updateMemoryTag(other.getMemoryTag(), nullptr); } bool Timestamp::isUnixTime() const { diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp index dd5cc3d4..2579feb4 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp @@ -319,7 +319,7 @@ void DiagnosticsService::loop() { } #if MO_ENABLE_V16 -bool DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned int retries, unsigned int retryInterval, Timestamp startTime, Timestamp stopTime, char filenameOut[MO_GETLOG_FNAME_SIZE]) { +bool DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned int retries, int retryInterval, Timestamp startTime, Timestamp stopTime, char filenameOut[MO_GETLOG_FNAME_SIZE]) { bool success = false; @@ -342,7 +342,7 @@ bool DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned } #endif //MO_ENABLE_V16 -MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int retries, unsigned int retryInterval, const char *location, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]) { +MO_GetLogStatus DiagnosticsService::getLog(MO_LogType type, int requestId, int retries, int retryInterval, const char *location, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]) { if (runCustomUpload || this->retries >= 0) { MO_DBG_INFO("upload still running"); diff --git a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h index d0c34eec..c566197b 100644 --- a/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h +++ b/src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h @@ -154,10 +154,10 @@ class DiagnosticsService : public MemoryManaged { //timestamps before year 2021 will be treated as "undefined" //returns empty std::string if onUpload is missing or upload cannot be scheduled for another reason //returns fileName of diagnostics file to be uploaded if upload has been scheduled - bool requestDiagnosticsUpload(const char *location, unsigned int retries, unsigned int retryInterval, Timestamp startTime, Timestamp stopTime, char filenameOut[MO_GETLOG_FNAME_SIZE]); + bool requestDiagnosticsUpload(const char *location, unsigned int retries, int retryInterval, Timestamp startTime, Timestamp stopTime, char filenameOut[MO_GETLOG_FNAME_SIZE]); #endif //MO_ENABLE_V16 - MO_GetLogStatus getLog(MO_LogType type, int requestId, int retries, unsigned int retryInterval, const char *remoteLocation, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]); + MO_GetLogStatus getLog(MO_LogType type, int requestId, int retries, int retryInterval, const char *remoteLocation, Timestamp oldestTimestamp, Timestamp latestTimestamp, char filenameOut[MO_GETLOG_FNAME_SIZE]); //for TriggerMessage int getRequestId(); diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index 0aade978..296dcf28 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -1032,10 +1032,9 @@ std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { return nullptr; } - int32_t dtLastAttempt; - if (!clock.delta(clock.now(), txEventFront->attemptTime, dtLastAttempt)) { + int32_t dtLastAttempt = MO_MAX_TIME; + if (txEventFront->attemptTime.isDefined() && !clock.delta(clock.now(), txEventFront->attemptTime, dtLastAttempt)) { MO_DBG_ERR("internal error"); - dtLastAttempt = MO_MAX_TIME; } if (dtLastAttempt < (int)txEventFront->attemptNr * std::max(0, txService.messageAttemptIntervalTransactionEventInt->getInt())) { diff --git a/src/MicroOcpp/Operations/GetDiagnostics.cpp b/src/MicroOcpp/Operations/GetDiagnostics.cpp index 4eadf374..ad3f25e7 100644 --- a/src/MicroOcpp/Operations/GetDiagnostics.cpp +++ b/src/MicroOcpp/Operations/GetDiagnostics.cpp @@ -52,7 +52,7 @@ void GetDiagnostics::processReq(JsonObject payload) { } } - (void)diagService.requestDiagnosticsUpload(location, (unsigned int) retries, (unsigned int) retryInterval, startTime, stopTime, filename); + (void)diagService.requestDiagnosticsUpload(location, (unsigned int) retries, retryInterval, startTime, stopTime, filename); } std::unique_ptr GetDiagnostics::createConf(){ From 6bc0562b82e2598adca3e3baf25956f5b322f617 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 20 Jul 2025 18:22:21 +0200 Subject: [PATCH 46/50] update docs --- docs/benchmarks.md | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 665930c6..3112c02e 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -8,7 +8,7 @@ In general, microcontrollers have three relevant hardware constraints: - Limited memory size - Limited flash size -For OCPP, the relevant bottlenecks are especially the memory and flash size. The processing speed is no concern, since OCPP is not computationally complex and does not include any extensive planning algorithms on the charger size. A previous [benchmark on the ESP-IDF](https://github.com/matth-x/MicroOcpp-benchmark) showed that the processing times are in the lower milliseconds range and are probably outweighed by IO times and network round trip times. +For OCPP, the relevant bottlenecks are especially the memory and flash size. The processing speed is no concern, since OCPP is not computationally complex and does not include any extensive planning algorithms on the charger side. A previous [benchmark on the ESP-IDF](https://github.com/matth-x/MicroOcpp-benchmark) showed that the processing times are in the lower milliseconds range and are probably outweighed by IO times and network round trip times. However, the memory and flash requirements are important figures, because the device model of OCPP has a significant size. The microcontroller needs to keep the model data in the heap memory for the largest part and the firmware which covers the corresponding processing routines needs to have sufficient space on flash. @@ -20,19 +20,11 @@ When compiling a firmware with MicroOCPP, the resulting binary will contain func For the flash benchmark, the profiler compiles a [dummy OCPP firmware](https://github.com/matth-x/MicroOcpp/tree/main/tests/benchmarks/firmware_size/main.cpp), analyzes the size of the compilation units using [bloaty](https://github.com/google/bloaty) and evaluates the bloaty report using a [Python script](https://github.com/matth-x/MicroOcpp/tree/main/tests/benchmarks/scripts/eval_firmware_size.py). To give realistic results, the firwmare is compiled with `-Os`, no RTTI or exceptions and newlib as the standard C library. The following tables show the results. -### OCPP 1.6 - -The following table shows the cumulated size of the objects files per module. The Module category consists of the OCPP 2.0.1 functional blocks, OCPP 1.6 feature profiles and general functionality which is shared accross the library. If a feature of the implementation falls under both an OCPP 2.0.1 functional block and OCPP 1.6 feature profile definition, it is preferrably assigned to the OCPP 2.0.1 category. This allows for better comparability between both OCPP versions. +The following table shows the firmware size of each functional block. Their scope is defined in OCPP 2.0.1 and the 1.6 implementation follows the same scope. Since some code is reused between 1.6 and 2.0.1, the firmware size was taken in three configurations, one to only support 1.6, one for 2.0.1 only and one to enable both OCPP versions (the firmware can select the OCPP version during runtime). These configurations are shown per separate columns. **Table 1: Firmware size per Module** -{{ read_csv('modules_v16.csv') }} - -### OCPP 2.0.1 - -**Table 2: Firmware size per Module** - -{{ read_csv('modules_v201.csv') }} +{{ read_csv('modules.csv') }} ## Memory usage @@ -42,7 +34,7 @@ Another important figure is the base level which is much closer to the average h The following table shows the dynamic heap usage for a variety of test cases, followed by the base level and resulting maximum memory occupation of MicroOCPP. At the time being, the measurements are limited to only OCPP 2.0.1 and a narrow set of test cases. They will be gradually extended over time. -**Table 3: Memory usage per use case and total** +**Table 2: Memory usage per use case and total** {{ read_csv('heap_v201.csv') }} @@ -50,10 +42,6 @@ The following table shows the dynamic heap usage for a variety of test cases, fo This section contains the raw data which is the basis for the evaluations above. -**Table 4: All compilation units for OCPP 1.6 firmware** - -{{ read_csv('compile_units_v16.csv') }} - -**Table 5: All compilation units for OCPP 2.0.1 firmware** +**Table 3: All compilation units for OCPP 1.6 firmware** -{{ read_csv('compile_units_v201.csv') }} +{{ read_csv('compile_units.csv') }} From ff4dc9ca1bc8bbfb8a70c09cd72a2f44ca0e4fb3 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 20 Jul 2025 20:28:24 +0200 Subject: [PATCH 47/50] bugfixes --- src/MicroOcpp.cpp | 5 +++-- src/MicroOcpp/Model/Metering/MeteringService.cpp | 6 +++--- .../Model/Transactions/TransactionService16.cpp | 12 ++++++------ src/MicroOcpp/Platform.h | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index a4156f5f..3a802e48 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -278,7 +278,7 @@ bool mo_setEnergyMeterInput2(MO_Context *ctx, unsigned int evseId, int32_t (*ene mInput.measurand = "Energy.Active.Import.Register"; mInput.unit = "Wh"; mInput.user_data = userData; - return mo_addMeterValueInput(ctx, EVSE_ID_1, mInput); + return mo_addMeterValueInput(ctx, evseId, mInput); } //Input of the power meter reading in W @@ -297,7 +297,8 @@ bool mo_setPowerMeterInput2(MO_Context *ctx, unsigned int evseId, float (*powerI mInput.getFloat2 = powerInput2; mInput.measurand = "Power.Active.Import"; mInput.unit = "W"; - return mo_addMeterValueInput(ctx, EVSE_ID_1, mInput); + mInput.user_data = userData; + return mo_addMeterValueInput(ctx, evseId, mInput); } //Smart Charging Output diff --git a/src/MicroOcpp/Model/Metering/MeteringService.cpp b/src/MicroOcpp/Model/Metering/MeteringService.cpp index 81c351c3..98934d6b 100644 --- a/src/MicroOcpp/Model/Metering/MeteringService.cpp +++ b/src/MicroOcpp/Model/Metering/MeteringService.cpp @@ -661,10 +661,10 @@ std::unique_ptr v16::MeteringServiceEvse::fetchFrontRequest() { return nullptr; } - int32_t dtLastAttempt; - if (!clock.delta(clock.getUptime(), meterDataFront->attemptTime, dtLastAttempt)) { + int32_t dtLastAttempt = MO_MAX_TIME; + if (meterDataFront->attemptTime.isDefined() && + !clock.delta(clock.getUptime(), meterDataFront->attemptTime, dtLastAttempt)) { MO_DBG_ERR("internal error"); - dtLastAttempt = 0; } if (dtLastAttempt < (int)meterDataFront->attemptNr * std::max(0, mService.transactionMessageRetryIntervalInt->getInt())) { diff --git a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp index 074f4c5e..7dc80844 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService16.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService16.cpp @@ -1191,10 +1191,10 @@ std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { return nullptr; } - int32_t dtLastAttempt; - if (!clock.delta(clock.now(), transactionFront->getStartSync().getAttemptTime(), dtLastAttempt)) { + int32_t dtLastAttempt = MO_MAX_TIME; + if (transactionFront->getStartSync().getAttemptTime().isDefined() && + !clock.delta(clock.now(), transactionFront->getStartSync().getAttemptTime(), dtLastAttempt)) { MO_DBG_ERR("internal error"); - dtLastAttempt = 0; } if (dtLastAttempt < (int)transactionFront->getStartSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { @@ -1318,10 +1318,10 @@ std::unique_ptr TransactionServiceEvse::fetchFrontRequest() { } } - int32_t dtLastAttempt; - if (!clock.delta(clock.now(), transactionFront->getStopSync().getAttemptTime(), dtLastAttempt)) { + int32_t dtLastAttempt = MO_MAX_TIME; + if (transactionFront->getStopSync().getAttemptTime().isDefined() && + !clock.delta(clock.now(), transactionFront->getStopSync().getAttemptTime(), dtLastAttempt)) { MO_DBG_ERR("internal error"); - dtLastAttempt = 0; } if (dtLastAttempt < (int)transactionFront->getStopSync().getAttemptNr() * std::max(0, cService.transactionMessageRetryIntervalInt->getInt())) { diff --git a/src/MicroOcpp/Platform.h b/src/MicroOcpp/Platform.h index 95d1ed92..b594cf79 100644 --- a/src/MicroOcpp/Platform.h +++ b/src/MicroOcpp/Platform.h @@ -28,7 +28,7 @@ uint32_t (*getDefaultRngCb())(); #ifndef MO_MAX_JSON_CAPACITY #if MO_PLATFORM == MO_PLATFORM_UNIX -#define MO_MAX_JSON_CAPACITY 5120 +#define MO_MAX_JSON_CAPACITY 8192 #else #define MO_MAX_JSON_CAPACITY 4096 #endif From 9672e79a61ef00e0678aa975c8fb79005290f27e Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 20 Jul 2025 23:53:25 +0200 Subject: [PATCH 48/50] bugfixes --- .../Model/Authorization/AuthorizationList.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp index dcda7c0b..7c937367 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp @@ -174,7 +174,7 @@ bool AuthorizationList::readJson(Clock& clock, JsonArray authlistJson, int listV // Insert new entries for (size_t i = 0; i < authlistJson.size(); i++) { - if (!authlistJson[i].containsKey(AUTHDATA_KEY_IDTAGINFO)) { + if (!internalFormat && !authlistJson[i].containsKey(AUTHDATA_KEY_IDTAGINFO)) { // remove already handled above continue; } @@ -209,6 +209,20 @@ bool AuthorizationList::readJson(Clock& clock, JsonArray authlistJson, int listV } } + // sanity check 1 + if (resWritten != resListSize) { + MO_DBG_ERR("internal error"); + goto fail; + } + + // sanity check 2 + for (size_t i = 0; i < resListSize; i++) { + if (!resList[i]) { + MO_DBG_ERR("internal error"); + goto fail; + } + } + qsort(resList, resListSize, sizeof(resList[0]), [] (const void* a,const void* b) -> int { return strcmp( From d00028b02ad114a95c1ce52372adb1fe3b15bd22 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 22 Jul 2025 10:40:34 +0530 Subject: [PATCH 49/50] fix test cases --- .github/workflows/documentation.yml | 2 +- src/MicroOcpp/Model/Authorization/AuthorizationList.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 4ae8205b..30171296 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -34,7 +34,7 @@ jobs: with: repository: matth-x/MicroOcppSimulator path: MicroOcppSimulator - ref: 491ff2f3b48d5b668a327b21cf5080e818f42a22 + ref: 643743d4bb7ca267e710114bb8a8c0396196384f submodules: 'recursive' - name: Clean MicroOcpp submodule run: | diff --git a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp index 7c937367..9a0c57d5 100644 --- a/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp +++ b/src/MicroOcpp/Model/Authorization/AuthorizationList.cpp @@ -226,8 +226,8 @@ bool AuthorizationList::readJson(Clock& clock, JsonArray authlistJson, int listV qsort(resList, resListSize, sizeof(resList[0]), [] (const void* a,const void* b) -> int { return strcmp( - reinterpret_cast(a)->getIdTag(), - reinterpret_cast(b)->getIdTag()); + (*reinterpret_cast(a))->getIdTag(), + (*reinterpret_cast(b))->getIdTag()); }); // success From d155bf0f9640532ba1a430e6f26f1573c64378d5 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 26 Jul 2025 17:59:46 +0530 Subject: [PATCH 50/50] fix test cases --- .github/workflows/documentation.yml | 2 +- src/MicroOcpp/Model/Transactions/TransactionService201.cpp | 7 ++++--- src/MicroOcpp/Model/Variables/VariableContainer.cpp | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 30171296..54440598 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -34,7 +34,7 @@ jobs: with: repository: matth-x/MicroOcppSimulator path: MicroOcppSimulator - ref: 643743d4bb7ca267e710114bb8a8c0396196384f + ref: 741733550b801d29261f81c3b7fb74bfc0b6faec submodules: 'recursive' - name: Clean MicroOcpp submodule run: | diff --git a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp index 296dcf28..6537b851 100644 --- a/src/MicroOcpp/Model/Transactions/TransactionService201.cpp +++ b/src/MicroOcpp/Model/Transactions/TransactionService201.cpp @@ -242,14 +242,15 @@ TransactionEventData::ChargingState TransactionServiceEvse::getChargingState() { auto res = TransactionEventData::ChargingState::Idle; if (connectorPluggedInput && !connectorPluggedInput(evseId, connectorPluggedInputUserData)) { res = TransactionEventData::ChargingState::Idle; - } else if (!transaction || !transaction->isAuthorizationActive || !transaction->isAuthorized) { + } else if (!transaction || !transaction->isAuthorizationActive || !transaction->isAuthorized || + (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData))) { res = TransactionEventData::ChargingState::EVConnected; - } else if (evseReadyInput && !evseReadyInput(evseId, evseReadyInputUserData)) { - res = TransactionEventData::ChargingState::SuspendedEVSE; } else if (evReadyInput && !evReadyInput(evseId, evReadyInputUserData)) { res = TransactionEventData::ChargingState::SuspendedEV; } else if (ocppPermitsCharge()) { res = TransactionEventData::ChargingState::Charging; + } else { + res = TransactionEventData::ChargingState::SuspendedEVSE; } return res; } diff --git a/src/MicroOcpp/Model/Variables/VariableContainer.cpp b/src/MicroOcpp/Model/Variables/VariableContainer.cpp index 66649644..7713fedf 100644 --- a/src/MicroOcpp/Model/Variables/VariableContainer.cpp +++ b/src/MicroOcpp/Model/Variables/VariableContainer.cpp @@ -245,7 +245,7 @@ bool VariableContainerOwning::commit() { JsonArray variablesJson = doc.createNestedArray("variables"); - for (size_t i = 0; i < variableCapacity; i++) { + for (size_t i = 0; i < variables.size(); i++) { auto& variable = *variables[i]; if (!variable.isPersistent()) {