From 569240fb6f8fd43571012f252b651e11a7b6000e Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 9 Mar 2022 18:01:00 +0100 Subject: [PATCH 001/549] further configuration keys --- .../Tasks/ChargePointStatus/ChargePointStatusService.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp index 22aaa061..c0cbe8cf 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp @@ -42,6 +42,12 @@ ChargePointStatusService::ChargePointStatusService(OcppEngine& context, unsigned fProfilePlus += fpIdRTrigger; fProfile->setValue(fProfilePlus.c_str(), fProfilePlus.length() + 1); } + + /* + * Further configuration keys which correspond to the Core profile + */ + declareConfiguration("AuthorizeRemoteTxRequests","false",CONFIGURATION_VOLATILE,false,true,false,false); + declareConfiguration("GetConfigurationMaxKeys",30,CONFIGURATION_VOLATILE,false,true,false,false); } ChargePointStatusService::~ChargePointStatusService() { From d36ccd5cdc5fb260e05e5e3f5513f45c61b7c6fc Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 16 Mar 2022 18:28:44 +0100 Subject: [PATCH 002/549] add generic MeterValue support --- src/ArduinoOcpp.cpp | 33 +++- src/ArduinoOcpp.h | 3 + src/ArduinoOcpp/MessagesV16/MeterValues.cpp | 71 +++------ src/ArduinoOcpp/MessagesV16/MeterValues.h | 8 +- .../MessagesV16/StartTransaction.cpp | 2 +- .../MessagesV16/StartTransaction.h | 2 +- .../MessagesV16/StopTransaction.cpp | 2 +- src/ArduinoOcpp/MessagesV16/StopTransaction.h | 2 +- .../Metering/ConnectorMeterValuesRecorder.cpp | 87 +++++------ .../Metering/ConnectorMeterValuesRecorder.h | 19 ++- src/ArduinoOcpp/Tasks/Metering/MeterValue.h | 61 ++++++++ .../Tasks/Metering/MeteringService.cpp | 10 +- .../Tasks/Metering/MeteringService.h | 5 +- src/ArduinoOcpp/Tasks/Metering/SampledValue.h | 143 ++++++++++++++++++ 14 files changed, 327 insertions(+), 121 deletions(-) create mode 100644 src/ArduinoOcpp/Tasks/Metering/MeterValue.h create mode 100644 src/ArduinoOcpp/Tasks/Metering/SampledValue.h diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 60f8b841..b32ae275 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -168,7 +168,16 @@ void setPowerActiveImportSampler(std::function power) { model.setMeteringSerivce(std::unique_ptr( new MeteringService(*ocppEngine, OCPP_NUMCONNECTORS))); } - model.getMeteringService()->setPowerSampler(OCPP_ID_OF_CONNECTOR, power); //connectorId=1 + SampledValueProperties meterProperties; + meterProperties.setMeasurand("Power.Active.Import"); + meterProperties.setUnit("W"); + auto mvs = std::unique_ptr>>( + new SampledValueSamplerConcrete>( + meterProperties, + power + )); + model.getMeteringService()->addMeterValueSampler(OCPP_ID_OF_CONNECTOR, std::move(mvs)); //connectorId=1 + model.getMeteringService()->setPowerSampler(OCPP_ID_OF_CONNECTOR, power); } void setEnergyActiveImportSampler(std::function energy) { @@ -181,7 +190,27 @@ void setEnergyActiveImportSampler(std::function energy) { model.setMeteringSerivce(std::unique_ptr( new MeteringService(*ocppEngine, OCPP_NUMCONNECTORS))); } - model.getMeteringService()->setEnergySampler(OCPP_ID_OF_CONNECTOR, energy); //connectorId=1 + SampledValueProperties meterProperties; + meterProperties.setMeasurand("Energy.Active.Import.Register"); + meterProperties.setUnit("Wh"); + auto mvs = std::unique_ptr>>( + new SampledValueSamplerConcrete>( + meterProperties, energy)); + model.getMeteringService()->addMeterValueSampler(OCPP_ID_OF_CONNECTOR, std::move(mvs)); //connectorId=1 + model.getMeteringService()->setEnergySampler(OCPP_ID_OF_CONNECTOR, energy); +} + +void addMeterValueSampler(std::unique_ptr meterValueSampler) { + if (!ocppEngine) { + AO_DBG_ERR("Please call OCPP_initialize before"); + return; + } + auto& model = ocppEngine->getOcppModel(); + if (!model.getMeteringService()) { + model.setMeteringSerivce(std::unique_ptr( + new MeteringService(*ocppEngine, OCPP_NUMCONNECTORS))); + } + model.getMeteringService()->addMeterValueSampler(OCPP_ID_OF_CONNECTOR, std::move(meterValueSampler)); //connectorId=1 } void setEvRequestsEnergySampler(std::function evRequestsEnergy) { diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index aba69669..65ca8ca7 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -13,6 +13,7 @@ #include #include #include +#include using ArduinoOcpp::OnReceiveConfListener; using ArduinoOcpp::OnReceiveReqListener; @@ -50,6 +51,8 @@ void setPowerActiveImportSampler(std::function power); void setEnergyActiveImportSampler(std::function energy); +void addMeterValueSampler(std::unique_ptr meterValueSampler); + void setEvRequestsEnergySampler(std::function evRequestsEnergy); void setConnectorEnergizedSampler(std::function connectorEnergized); diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp index 4f04992b..26c60593 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include using ArduinoOcpp::Ocpp16::MeterValues; @@ -14,14 +15,12 @@ MeterValues::MeterValues() { } -MeterValues::MeterValues(const std::vector *sampleTime, const std::vector *energy, const std::vector *power, int connectorId, int transactionId) +MeterValues::MeterValues(const std::vector>& meterValue, int connectorId, int transactionId) : connectorId{connectorId}, transactionId{transactionId} { - if (sampleTime) - this->sampleTime = std::vector(*sampleTime); - if (energy) - this->energy = std::vector(*energy); - if (power) - this->power = std::vector(*power); + + for (auto value = meterValue.begin(); value != meterValue.end(); value++) { + this->meterValue.push_back(std::unique_ptr(new MeterValue(**value))); + } } MeterValues::~MeterValues(){ @@ -34,49 +33,22 @@ const char* MeterValues::getOcppOperationType(){ std::unique_ptr MeterValues::createReq() { - int numEntries = sampleTime.size(); - - const size_t VALUE_MAXPRECISION = 10; - const size_t VALUE_MAXSIZE = VALUE_MAXPRECISION + 7; - char value_str [VALUE_MAXSIZE] = {'\0'}; - - auto doc = std::unique_ptr(new DynamicJsonDocument( - JSON_OBJECT_SIZE(3) //connectorID, transactionId, meterValue entry - + JSON_ARRAY_SIZE(numEntries) //metervalue array - + numEntries * JSON_OBJECT_SIZE(1) //sampledValue entry - + numEntries * (JSON_OBJECT_SIZE(1) + (JSONDATE_LENGTH + 1)) //timestamp - + numEntries * JSON_ARRAY_SIZE(2) //sampledValue - + 2 * numEntries * (JSON_OBJECT_SIZE(1) + VALUE_MAXSIZE) //value - + 2 * numEntries * JSON_OBJECT_SIZE(1) //measurand - + 2 * numEntries * JSON_OBJECT_SIZE(1) //unit - + 230)); //"safety space" - JsonObject payload = doc->to(); + size_t capacity = 0; - payload["connectorId"] = connectorId; - JsonArray meterValues = payload.createNestedArray("meterValue"); - for (size_t i = 0; i < sampleTime.size(); i++) { - JsonObject meterValue = meterValues.createNestedObject(); - char timestamp[JSONDATE_LENGTH + 1] = {'\0'}; - OcppTimestamp otimestamp = sampleTime.at(i); - otimestamp.toJsonString(timestamp, JSONDATE_LENGTH + 1); - meterValue["timestamp"] = timestamp; - JsonArray sampledValue = meterValue.createNestedArray("sampledValue"); - if (energy.size() >= i + 1) { - JsonObject sampledValue_1 = sampledValue.createNestedObject(); - snprintf(value_str, VALUE_MAXSIZE, "%.*g", VALUE_MAXPRECISION, energy.at(i)); - sampledValue_1["value"] = value_str; - sampledValue_1["measurand"] = "Energy.Active.Import.Register"; - sampledValue_1["unit"] = "Wh"; - } - if (power.size() >= i + 1) { - JsonObject sampledValue_2 = sampledValue.createNestedObject(); - snprintf(value_str, VALUE_MAXSIZE, "%.*g", VALUE_MAXPRECISION, power.at(i)); - sampledValue_2["value"] = value_str; - sampledValue_2["measurand"] = "Power.Active.Import"; - sampledValue_2["unit"] = "W"; - } + std::vector> entries; + for (auto value = meterValue.begin(); value != meterValue.end(); value++) { + auto entry = (*value)->toJson(); + capacity += entry->capacity(); + entries.push_back(std::move(entry)); } + capacity += JSON_OBJECT_SIZE(3); + capacity += JSON_ARRAY_SIZE(entries.size()); + + auto doc = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space + auto payload = doc->to(); + payload["connectorId"] = connectorId; + if (ocppModel && ocppModel->getConnectorStatus(connectorId)) { auto connector = ocppModel->getConnectorStatus(connectorId); if (connector->getTransactionIdSync() >= 0) { @@ -84,6 +56,11 @@ std::unique_ptr MeterValues::createReq() { } } + auto meterValueJson = payload.createNestedArray("meterValue"); + for (auto entry = entries.begin(); entry != entries.end(); entry++) { + meterValueJson.add(**entry); + } + return doc; } diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.h b/src/ArduinoOcpp/MessagesV16/MeterValues.h index 35160909..2e0bfcab 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.h +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.h @@ -7,22 +7,20 @@ #include #include +#include namespace ArduinoOcpp { namespace Ocpp16 { class MeterValues : public OcppMessage { private: - - std::vector sampleTime; - std::vector power; - std::vector energy; + std::vector> meterValue; int connectorId = 0; int transactionId = -1; public: - MeterValues(const std::vector *sampleTime, const std::vector *energy, const std::vector *power, int connectorId, int transactionId); + MeterValues(const std::vector>& meterValue, int connectorId, int transactionId); MeterValues(); //for debugging only. Make this for the server pendant diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp index 62e05590..c6941c51 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp @@ -28,7 +28,7 @@ const char* StartTransaction::getOcppOperationType(){ void StartTransaction::initiate() { if (ocppModel && ocppModel->getMeteringService()) { auto meteringService = ocppModel->getMeteringService(); - meterStart = (int) meteringService->readEnergyActiveImportRegister(connectorId); + meterStart = meteringService->readEnergyActiveImportRegister(connectorId); } if (ocppModel) { diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.h b/src/ArduinoOcpp/MessagesV16/StartTransaction.h index b756cdec..6390a0f1 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.h @@ -15,7 +15,7 @@ namespace Ocpp16 { class StartTransaction : public OcppMessage { private: int connectorId = 1; - int meterStart = -1; + int32_t meterStart = -1; OcppTimestamp otimestamp; char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; uint16_t transactionRev = 0; diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp index 094888c3..52f91627 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp @@ -22,7 +22,7 @@ void StopTransaction::initiate() { if (ocppModel && ocppModel->getMeteringService()) { auto meteringService = ocppModel->getMeteringService(); - meterStop = (int) meteringService->readEnergyActiveImportRegister(connectorId); + meterStop = meteringService->readEnergyActiveImportRegister(connectorId); } if (ocppModel) { diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.h b/src/ArduinoOcpp/MessagesV16/StopTransaction.h index a1b861bd..d662cf4d 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.h @@ -14,7 +14,7 @@ namespace Ocpp16 { class StopTransaction : public OcppMessage { private: int connectorId = 1; - int meterStop = -1; + int32_t meterStop = -1; OcppTimestamp otimestamp; public: diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index 659bd265..1d71648f 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -15,27 +15,27 @@ using namespace ArduinoOcpp::Ocpp16; ConnectorMeterValuesRecorder::ConnectorMeterValuesRecorder(OcppModel& context, int connectorId) : context(context), connectorId{connectorId} { - sampleTimestamp = std::vector(); - energy = std::vector(); - power = std::vector(); MeterValueSampleInterval = declareConfiguration("MeterValueSampleInterval", 60); MeterValuesSampledDataMaxLength = declareConfiguration("MeterValuesSampledDataMaxLength", 4, CONFIGURATION_VOLATILE, false, true, false, false); } void ConnectorMeterValuesRecorder::takeSample() { - if (energySampler != nullptr || powerSampler != nullptr) { - if (!context.getOcppTime().isValid()) return; - sampleTimestamp.push_back(context.getOcppTime().getOcppTimestampNow()); - } + if (meterValueSamplers.empty()) return; - if (energySampler != nullptr) { - energy.push_back(energySampler()); + std::unique_ptr sample; + if (context.getOcppTime().isValid()) { + sample.reset(new MeterValue(context.getOcppTime().getOcppTimestampNow())); + } + if (!sample) { + return; } - if (powerSampler != nullptr) { - power.push_back(powerSampler()); + for (auto mvs = meterValueSamplers.begin(); mvs != meterValueSamplers.end(); mvs++) { + sample->addSampledValue((*mvs)->takeValue()); } + + meterValue.push_back(std::move(sample)); } OcppMessage *ConnectorMeterValuesRecorder::loop() { @@ -73,7 +73,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { /* * Is the value buffer already full? If yes, return MeterValues message */ - if (((int) sampleTimestamp.size()) >= (int) *MeterValuesSampledDataMaxLength) { + if (((int) meterValue.size()) >= (int) *MeterValuesSampledDataMaxLength) { auto result = toMeterValues(); return result; } @@ -82,59 +82,35 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { } OcppMessage *ConnectorMeterValuesRecorder::toMeterValues() { - if (sampleTimestamp.size() == 0) { + if (meterValue.empty()) { AO_DBG_DEBUG("Checking if to send MeterValues ... No"); clear(); return nullptr; - } - - //decide which measurands to send. If a measurand is missing at at least one point in time, omit that measurand completely - - if (energy.size() == sampleTimestamp.size() && power.size() == sampleTimestamp.size()) { - auto result = new MeterValues(&sampleTimestamp, &energy, &power, connectorId, lastTransactionId); - clear(); - return result; - } - - if (energy.size() == sampleTimestamp.size() && power.size() != sampleTimestamp.size()) { - auto result = new MeterValues(&sampleTimestamp, &energy, nullptr, connectorId, lastTransactionId); - clear(); - return result; - } - - if (energy.size() != sampleTimestamp.size() && power.size() == sampleTimestamp.size()) { - auto result = new MeterValues(&sampleTimestamp, nullptr, &power, connectorId, lastTransactionId); + } else { + auto result = new MeterValues(meterValue, connectorId, lastTransactionId); clear(); return result; } - - //Maybe the energy sampler or power sampler was set during recording. Discard recorded data. - AO_DBG_WARN("Invalid data set. Discard data set and restart recording"); - clear(); - - return nullptr; } OcppMessage *ConnectorMeterValuesRecorder::takeMeterValuesNow() { - if (!energySampler && !powerSampler) { + if (meterValueSamplers.empty()) { return nullptr; } - decltype(sampleTimestamp) t_now; - decltype(energy) e_now; - decltype(power) p_now; + std::unique_ptr value; if (context.getOcppTime().isValid()) { - t_now.push_back(context.getOcppTime().getOcppTimestampNow()); + value.reset(new MeterValue(context.getOcppTime().getOcppTimestampNow())); } - if (energySampler) { - e_now.push_back(energySampler()); + if (!value) { + return nullptr; } - if (powerSampler) { - p_now.push_back(powerSampler()); + for (auto mvs = meterValueSamplers.begin(); mvs != meterValueSamplers.end(); mvs++) { + value->addSampledValue((*mvs)->takeValue()); } int txId_now = -1; @@ -143,24 +119,29 @@ OcppMessage *ConnectorMeterValuesRecorder::takeMeterValuesNow() { txId_now = connector->getTransactionId(); } - return new MeterValues(&t_now, &e_now, &p_now, connectorId, txId_now); + decltype(meterValue) mv_now; + mv_now.push_back(std::move(value)); + + return new MeterValues(mv_now, connectorId, txId_now); } void ConnectorMeterValuesRecorder::clear() { - sampleTimestamp.clear(); - energy.clear(); - power.clear(); + meterValue.clear(); } void ConnectorMeterValuesRecorder::setPowerSampler(PowerSampler ps){ - this->powerSampler = ps; + this->powerSampler = ps; } void ConnectorMeterValuesRecorder::setEnergySampler(EnergySampler es){ - this->energySampler = es; + this->energySampler = es; +} + +void ConnectorMeterValuesRecorder::addMeterValueSampler(std::unique_ptr meterValueSampler) { + meterValueSamplers.push_back(std::move(meterValueSampler)); } -float ConnectorMeterValuesRecorder::readEnergyActiveImportRegister() { +int32_t ConnectorMeterValuesRecorder::readEnergyActiveImportRegister() { if (energySampler != nullptr) { return energySampler(); } else { diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h index 9976067d..d65de801 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h @@ -12,6 +12,7 @@ #include #include +#include #include namespace ArduinoOcpp { @@ -29,18 +30,18 @@ class ConnectorMeterValuesRecorder { const int connectorId; - std::vector sampleTimestamp; - std::vector energy; - std::vector power; + std::vector> meterValue; + ulong lastSampleTime = 0; //0 means not charging right now float lastPower; int lastTransactionId = -1; - PowerSampler powerSampler = NULL; - EnergySampler energySampler = NULL; + PowerSampler powerSampler = nullptr; + EnergySampler energySampler = nullptr; + std::vector> meterValueSamplers; - std::shared_ptr> MeterValueSampleInterval = NULL; - std::shared_ptr> MeterValuesSampledDataMaxLength = NULL; + std::shared_ptr> MeterValueSampleInterval = nullptr; + std::shared_ptr> MeterValuesSampledDataMaxLength = nullptr; void takeSample(); OcppMessage *toMeterValues(); @@ -54,7 +55,9 @@ class ConnectorMeterValuesRecorder { void setEnergySampler(EnergySampler energySampler); - float readEnergyActiveImportRegister(); + void addMeterValueSampler(std::unique_ptr meterValueSampler); + + int32_t readEnergyActiveImportRegister(); OcppMessage *takeMeterValuesNow(); }; diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h new file mode 100644 index 00000000..6945fa8c --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h @@ -0,0 +1,61 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef METERVALUE_H +#define METERVALUE_H + +#include +#include +#include +#include + +namespace ArduinoOcpp { + +class MeterValue { +private: + OcppTimestamp timestamp; + std::vector> sampledValue; +public: + MeterValue(OcppTimestamp timestamp) : timestamp(timestamp) { } + MeterValue(const MeterValue& other) { + timestamp = other.timestamp; + for (auto value = other.sampledValue.begin(); value != other.sampledValue.end(); value++) { + sampledValue.push_back(std::unique_ptr((*value)->clone())); + } + } + + void addSampledValue(std::unique_ptr sample) {sampledValue.push_back(std::move(sample));} + + std::unique_ptr toJson() { + size_t capacity = 0; + std::vector> entries; + for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) { + auto json = (*sample)->toJson(); + capacity += json->capacity(); + entries.push_back(std::move(json)); + } + + capacity += JSON_ARRAY_SIZE(entries.size()); + capacity += JSONDATE_LENGTH + 1; + capacity += JSON_OBJECT_SIZE(2); + + auto result = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space + auto jsonPayload = result->to(); + + char timestampStr [JSONDATE_LENGTH + 1] = {'\0'}; + if (!timestamp.toJsonString(timestampStr, JSONDATE_LENGTH + 1)) { + return nullptr; + } + jsonPayload["timestamp"] = timestampStr; + auto jsonMeterValue = jsonPayload.createNestedArray("sampledValue"); + for (auto entry = entries.begin(); entry != entries.end(); entry++) { + jsonMeterValue.add(**entry); + } + return std::move(result); + } +}; + +} + +#endif diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp index 6260a55c..6232aa34 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp @@ -45,7 +45,15 @@ void MeteringService::setEnergySampler(int connectorId, EnergySampler es){ connectors[connectorId]->setEnergySampler(es); } -float MeteringService::readEnergyActiveImportRegister(int connectorId) { +void MeteringService::addMeterValueSampler(int connectorId, std::unique_ptr meterValueSampler) { + if (connectorId < 0 || connectorId >= connectors.size()) { + AO_DBG_ERR("connectorId is out of bounds"); + return; + } + connectors[connectorId]->addMeterValueSampler(std::move(meterValueSampler)); +} + +int32_t MeteringService::readEnergyActiveImportRegister(int connectorId) { if (connectorId < 0 || connectorId >= connectors.size()) { AO_DBG_ERR("connectorId is out of bounds"); return 0.f; diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h index 035db5f6..526629fb 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h @@ -8,6 +8,7 @@ #include #include +#include namespace ArduinoOcpp { @@ -31,7 +32,9 @@ class MeteringService { void setEnergySampler(int connectorId, EnergySampler energySampler); - float readEnergyActiveImportRegister(int connectorId); + void addMeterValueSampler(int connectorId, std::unique_ptr meterValueSampler); + + int32_t readEnergyActiveImportRegister(int connectorId); std::unique_ptr takeMeterValuesNow(int connectorId); //snapshot of all meters now diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h new file mode 100644 index 00000000..f2a3adb9 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h @@ -0,0 +1,143 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef SAMPLEDVALUE_H +#define SAMPLEDVALUE_H + +#include +#include + +namespace ArduinoOcpp { + +template +class SampledValueDeSerializer { +public: + static T deserialize(const char *str); + static std::string serialize(const T& val); + static int32_t toInteger(const T& val); +}; + +template <> +class SampledValueDeSerializer { +public: + static int32_t deserialize(const char *str) {return 42;} + static std::string serialize(const int32_t& val) { + char str [12] = {'\0'}; + snprintf(str, 12, "%d", val); + return std::string(str); + } + static int32_t toInteger(const int32_t& val) {return val;} +}; + +class SampledValueProperties { +private: + std::string format; + std::string measurand; + std::string phase; + std::string location; + std::string unit; + + const std::string& getFormat() const {return format;} + const std::string& getMeasurand() const {return measurand;} + const std::string& getPhase() const {return phase;} + const std::string& getLocation() const {return location;} + const std::string& getUnit() const {return unit;} + friend class SampledValue; //will be able to retreive these parameters + +public: + 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;} + void setMeasurand(const char *measurand) {this->measurand = measurand;} + void setPhase(const char *phase) {this->phase = phase;} + void setLocation(const char *location) {this->location = location;} + void setUnit(const char *unit) {this->unit = unit;} +}; + +class SampledValue { +protected: + const SampledValueProperties& properties; + virtual std::string serializeValue() = 0; +public: + SampledValue(const SampledValueProperties& properties) : properties(properties) { } + SampledValue(const SampledValue& other) : properties(other.properties) { } + virtual ~SampledValue() = default; + + std::unique_ptr toJson() { + auto value = serializeValue(); + size_t capacity = 0; + capacity += JSON_OBJECT_SIZE(7); + capacity += value.length() + 1 + + properties.getFormat().length() + 1 + + properties.getMeasurand().length() + 1 + + properties.getPhase().length() + 1 + + properties.getLocation().length() + 1 + + properties.getUnit().length() + 1; + auto result = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space + auto payload = result->to(); + payload["value"] = value; + if (!properties.getFormat().empty()) + payload["format"] = properties.getFormat(); + if (!properties.getMeasurand().empty()) + payload["measurand"] = properties.getMeasurand(); + if (!properties.getPhase().empty()) + payload["phase"] = properties.getPhase(); + if (!properties.getLocation().empty()) + payload["location"] = properties.getLocation(); + if (!properties.getUnit().empty()) + payload["unit"] = properties.getUnit(); + return std::move(result); + } + + virtual std::unique_ptr clone() = 0; + + virtual int32_t toInteger() = 0; +}; + +template +class SampledValueConcrete : public SampledValue { +private: + const T value; +public: + SampledValueConcrete(const SampledValueProperties& properties, const T&& value) : SampledValue(properties), value(value) { } + SampledValueConcrete(const SampledValueConcrete& other) : SampledValue(other), value(other.value) { } + ~SampledValueConcrete() = default; + + std::string serializeValue() override {return DeSerializer::serialize(value);} + + std::unique_ptr clone() override {return std::unique_ptr>(new SampledValueConcrete(*this));} + + 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() = 0; +}; + +template +class SampledValueSamplerConcrete : public SampledValueSampler { +private: + std::function sampler; +public: + SampledValueSamplerConcrete(SampledValueProperties properties, std::function sampler) : SampledValueSampler(properties), sampler(sampler) { } + std::unique_ptr takeValue() override { + return std::unique_ptr>(new SampledValueConcrete(properties, sampler())); + } +}; + +} + +#endif From 9b0ca14d931d016cf945bdfdb3d98c850da93107 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 4 Apr 2022 11:42:39 +0200 Subject: [PATCH 003/549] initial commit --- CMakeLists.txt | 59 ++++++++++ README.md | 102 +----------------- src/ArduinoOcpp.cpp | 4 +- src/ArduinoOcpp/Core/Configuration.cpp | 9 +- .../Core/ConfigurationContainerFlash.cpp | 2 + .../Core/ConfigurationKeyValue.cpp | 8 -- src/ArduinoOcpp/Core/OcppConnection.cpp | 10 +- src/ArduinoOcpp/Core/OcppOperation.cpp | 14 +-- src/ArduinoOcpp/Core/OcppOperation.h | 14 +-- src/ArduinoOcpp/Core/OcppTime.cpp | 1 + .../MessagesV16/StatusNotification.cpp | 1 + src/ArduinoOcpp/Platform.h | 2 + .../SimpleOcppOperationFactory.cpp | 2 +- .../ChargePointStatus/ConnectorStatus.cpp | 2 +- .../SmartCharging/SmartChargingModel.cpp | 1 + .../SmartCharging/SmartChargingService.cpp | 4 +- src/ao_opts.h | 31 ++++++ src/ao_opts_impl.c | 29 +++++ 18 files changed, 154 insertions(+), 141 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 src/ao_opts.h create mode 100644 src/ao_opts_impl.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..be8c56fc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,59 @@ +set(AO_SRC + src/ArduinoOcpp/Core/Configuration.cpp + src/ArduinoOcpp/Core/ConfigurationContainer.cpp + src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp + src/ArduinoOcpp/Core/ConfigurationKeyValue.cpp + src/ArduinoOcpp/Core/OcppConnection.cpp + src/ArduinoOcpp/Core/OcppEngine.cpp + src/ArduinoOcpp/Core/OcppMessage.cpp + src/ArduinoOcpp/Core/OcppModel.cpp + src/ArduinoOcpp/Core/OcppOperation.cpp + src/ArduinoOcpp/Core/OcppOperationTimeout.cpp + src/ArduinoOcpp/Core/OcppServer.cpp + src/ArduinoOcpp/Core/OcppSocket.cpp + src/ArduinoOcpp/Core/OcppTime.cpp + src/ArduinoOcpp/MessagesV16/Authorize.cpp + src/ArduinoOcpp/MessagesV16/BootNotification.cpp + src/ArduinoOcpp/MessagesV16/ChangeAvailability.cpp + src/ArduinoOcpp/MessagesV16/ChangeConfiguration.cpp + src/ArduinoOcpp/MessagesV16/ClearCache.cpp + src/ArduinoOcpp/MessagesV16/ClearChargingProfile.cpp + src/ArduinoOcpp/MessagesV16/DataTransfer.cpp + src/ArduinoOcpp/MessagesV16/DiagnosticsStatusNotification.cpp + src/ArduinoOcpp/MessagesV16/FirmwareStatusNotification.cpp + src/ArduinoOcpp/MessagesV16/GetConfiguration.cpp + src/ArduinoOcpp/MessagesV16/GetDiagnostics.cpp + src/ArduinoOcpp/MessagesV16/Heartbeat.cpp + src/ArduinoOcpp/MessagesV16/MeterValues.cpp + src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp + src/ArduinoOcpp/MessagesV16/RemoteStopTransaction.cpp + src/ArduinoOcpp/MessagesV16/Reset.cpp + src/ArduinoOcpp/MessagesV16/SetChargingProfile.cpp + src/ArduinoOcpp/MessagesV16/StartTransaction.cpp + src/ArduinoOcpp/MessagesV16/StatusNotification.cpp + src/ArduinoOcpp/MessagesV16/StopTransaction.cpp + src/ArduinoOcpp/MessagesV16/TriggerMessage.cpp + src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp + src/ArduinoOcpp/MessagesV16/UpdateFirmware.cpp + src/ArduinoOcpp/Platform.cpp + src/ArduinoOcpp/SimpleOcppOperationFactory.cpp + src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp + src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp + src/ArduinoOcpp/Tasks/Diagnostics/DiagnosticsService.cpp + src/ArduinoOcpp/Tasks/FirmwareManagement/FirmwareService.cpp + src/ArduinoOcpp/Tasks/Heartbeat/HeartbeatService.cpp + src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp + src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp + src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp + src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp + src/ArduinoOcpp.cpp + src/ao_opts_impl.c +) + +idf_component_register(SRCS ${AO_SRC} + INCLUDE_DIRS "./src") + +target_compile_options(${COMPONENT_TARGET} PUBLIC + -DAO_CUSTOM_WS + -DAO_CUSTOM_CONSOLE + -DAO_DEACTIVATE_FLASH) diff --git a/README.md b/README.md index 5329de96..35e19a87 100644 --- a/README.md +++ b/README.md @@ -32,107 +32,7 @@ For simple chargers, the necessary hardware and internet integration is usually ## Usage guide -Please take `examples/ESP/main.cpp` as the starting point for your first project. It is a minimal example which shows how to establish an OCPP connection and how to start and stop charging sessions. In this guide, I give a brief overview of the key concepts. - -- To get the library running, you have to install all dependencies (see the list below). - - - In case you use PlatformIO, you can just add `matth-x/ArduinoOcpp` to your project using the PIO library manager. - -- In your project's `main` file, include `ArduinoOcpp.h`. This gives you a simple access to all functions. - -- Before establishing an OCPP connection you have to ensure that your device has access to a Wi-Fi access point. All debug messages are printed on the standard serial (i.e. `Serial.print("debug msg")`). To redirect debug messages, please refer to `src/ArduinoOcpp/Platform.h`. - -- To connect to your OCPP Central System, call `OCPP_initialize(String OCPP_HOST, uint16_t OCPP_PORT, String OCPP_URL)`. You need to insert the address parameters according to the configuration of your central system. Internally, the library passes these parameters to the WebSocket object without further alteration. - - To secure the connection with TLS, you have to configure the WebSocket. Please take `examples/SECC/main.cpp` as an example. - -- In your `setup()` function, you can add the configuration functions from `ArduinoOcpp.h` to properly integrate your hardware. All configuration functions are documented in `ArduinoOcpp.h`. For example, to integrate the energy meter of your EVSE, add - -```cpp -setEnergyActiveImportSampler([]() { - return yourEVSE_readEnergyMeter(); -}); -``` - -- Add `OCPP_loop()` to your `loop()` function. - -### Sending OCPP operations - -There are a couple of OCPP operations you can initialize on your EVSE. For example, to send a `Boot Notification`, use the function -```cpp -void bootNotification(const char *chargePointModel, const char *chargePointVendor, OnReceiveConfListener onConf = nullptr, ...)` -``` - -In practice, it looks like this: - -```cpp -void setup() { - - ... //other code including the initialization of Wi-Fi and OCPP - - bootNotification("My CP model name", "My company name", [] (JsonObject confMsg) { - //This callback is executed when the .conf() response from the central system arrives - Serial.print(F("BootNotification was answered. Central System clock: ")); - Serial.println(confMsg["currentTime"].as()); //"currentTime" is a field of the central system response - - //Notify your hardare that the BootNotification.conf() has arrived. E.g.: - //evseIsBooted = true; - }); - - ... //rest of setup() function; executed immediately as bootNotification() is non-blocking -} -``` - -The parameters `chargePointModel` and `chargePointVendor` are equivalent to the parameters in the `Boot Notification` as defined by the OCPP specification. The last parameter `OnReceiveConfListener onConf` is a callback function which the library executes when the central system has processed the operation and the ESP has received the `.conf()` response. Here you can add your device-specific behavior, e.g. flash a confirmation LED or unlock the connectors. If you don't need it, the last parameter is optional. - -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 following example shows the correct usage of all listeners. - -```cpp -authorize(idTag, [](JsonObject conf) { - //onReceiveConfListener (optional but very likely necessary for your integration) - successfullyAuthorized = true; //example client code - ... //further client code, e.g. beginSession(idTag); -}, []() { - //onAbortListener (optional) - Serial.print(F("[EVSE] Could not authorize charging session. Aborted\n")); //flash error light etc. -}, []() { - //onTimeoutListener (optional) - Serial.print(F("[EVSE] Could not authorize charging session. Reason: timeout\n")); -}, [](const char *code, const char *description, JsonObject details) { - //onReceiveErrorListener (optional) - Serial.print(F("[EVSE] Could not authorize charging session. Reason: received OCPP error: ")); - Serial.println(code); -}); - -``` - -### Receiving OCPP operations - -The library also reacts on CS-initiated operations. You can add your own behavior there too. For example, to flash a LED on receipt of a `Set Charging Profile` request, use the following function. - -```cpp -setOnSetChargingProfileRequest([] (JsonObject payload) { - //... -}); -``` - -You can also process the original payload from the CS using the `payload` object. - -*To get started quickly with or without EVSE hardware, you can flash the sketch in `examples/SECC` onto your ESP. That example mimics a full OCPP communications controller as it would look like in a real charging station. You can build a charger prototype based on that example or just view the internal state using the device monitor.* - -## Dependencies - -- [bblanchon/ArduinoJSON](https://github.com/bblanchon/ArduinoJson) (please upgrade to version `6.19.1`) -- [Links2004/arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) (please upgrade to version `2.3.6`) - -In case you use PlatformIO, you can copy all dependencies from `platformio.ini` into your own configuration file. Alternatively, you can install the full library with dependencies by adding `matth-x/ArduinoOcpp` in the PIO library manager. +**This feature branch is WIP. A usage guide will follow.** ## Supported operations diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 60f8b841..e90985cb 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -37,7 +37,7 @@ float voltage_eff {230.f}; #define OCPP_NUMCONNECTORS 2 #define OCPP_ID_OF_CONNECTOR 1 #define OCPP_ID_OF_CP 0 -boolean OCPP_booted = false; //if BootNotification succeeded +bool OCPP_booted = false; //if BootNotification succeeded } //end namespace ArduinoOcpp::Facade } //end namespace ArduinoOcpp @@ -137,7 +137,7 @@ void OCPP_deinitialize() { void OCPP_loop() { if (!ocppEngine) { AO_DBG_WARN("Please call OCPP_initialize before"); - delay(200); //Prevent this message from flooding the Serial monitor. + //delay(200); //Prevent this message from flooding the Serial monitor. return; } diff --git a/src/ArduinoOcpp/Core/Configuration.cpp b/src/ArduinoOcpp/Core/Configuration.cpp index 21e27f0e..cbd75a0a 100644 --- a/src/ArduinoOcpp/Core/Configuration.cpp +++ b/src/ArduinoOcpp/Core/Configuration.cpp @@ -7,16 +7,9 @@ #include #include +#include #include -#if defined(ESP32) && !defined(AO_DEACTIVATE_FLASH) -#include -#define USE_FS LITTLEFS -#else -#include -#define USE_FS SPIFFS -#endif - namespace ArduinoOcpp { FilesystemOpt configurationFilesystemOpt = FilesystemOpt::Use_Mount_FormatOnFail; diff --git a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp index d10adbf3..4d0a260d 100644 --- a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp +++ b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp @@ -11,6 +11,7 @@ #define USE_FS SPIFFS #endif +#ifndef AO_DEACTIVATE_FLASH #if USE_FS == LITTLEFS #include #elif USE_FS == SPIFFS @@ -18,6 +19,7 @@ #else #error "FS not supported" #endif +#endif #define MAX_FILE_SIZE 4000 #define MAX_CONFIGURATIONS 50 diff --git a/src/ArduinoOcpp/Core/ConfigurationKeyValue.cpp b/src/ArduinoOcpp/Core/ConfigurationKeyValue.cpp index 0cee24e3..f25abddb 100644 --- a/src/ArduinoOcpp/Core/ConfigurationKeyValue.cpp +++ b/src/ArduinoOcpp/Core/ConfigurationKeyValue.cpp @@ -9,14 +9,6 @@ #include #include -#if defined(ESP32) && !defined(AO_DEACTIVATE_FLASH) -#include -#define USE_FS LITTLEFS -#else -#include -#define USE_FS SPIFFS -#endif - #define KEY_MAXLEN 60 #define STRING_VAL_MAXLEN 2000 //allow TLS certificates in ... diff --git a/src/ArduinoOcpp/Core/OcppConnection.cpp b/src/ArduinoOcpp/Core/OcppConnection.cpp index 204a8288..15c10ce3 100644 --- a/src/ArduinoOcpp/Core/OcppConnection.cpp +++ b/src/ArduinoOcpp/Core/OcppConnection.cpp @@ -35,7 +35,7 @@ void OcppConnection::loop(OcppSocket& ocppSock) { auto operation = initiatedOcppOperations.begin(); while (operation != initiatedOcppOperations.end()){ - boolean timeout = (*operation)->sendReq(ocppSock); //The only reason to dequeue elements here is when a timeout occurs. Normally + bool timeout = (*operation)->sendReq(ocppSock); //The only reason to dequeue elements here is when a timeout occurs. Normally if (timeout){ //the Conf msg processing routine dequeues finished elements operation = initiatedOcppOperations.erase(operation); } else { @@ -76,7 +76,7 @@ void OcppConnection::loop(OcppSocket& ocppSock) { operation = receivedOcppOperations.begin(); while (operation != receivedOcppOperations.end()){ - boolean success = (*operation)->sendConf(ocppSock); + bool success = (*operation)->sendConf(ocppSock); if (success){ operation = receivedOcppOperations.erase(operation); } else { @@ -102,7 +102,7 @@ void OcppConnection::initiateOcppOperation(std::unique_ptr o){ bool OcppConnection::processOcppSocketInputTXT(const char* payload, size_t length) { - boolean deserializationSuccess = false; + bool deserializationSuccess = false; auto doc = std::unique_ptr{nullptr}; size_t capacity = length + 100; @@ -187,7 +187,7 @@ bool OcppConnection::processOcppSocketInputTXT(const char* payload, size_t lengt */ void OcppConnection::handleConfMessage(JsonDocument& json) { for (auto operation = initiatedOcppOperations.begin(); operation != initiatedOcppOperations.end(); ++operation) { - boolean success = (*operation)->receiveConf(json); //maybe rename to "consumed"? + bool success = (*operation)->receiveConf(json); //maybe rename to "consumed"? if (success) { initiatedOcppOperations.erase(operation); return; @@ -219,7 +219,7 @@ void OcppConnection::handleReqMessage(JsonDocument& json, std::unique_ptrreceiveError(json); //maybe rename to "consumed"? + bool discardOperation = (*operation)->receiveError(json); //maybe rename to "consumed"? if (discardOperation) { initiatedOcppOperations.erase(operation); return; diff --git a/src/ArduinoOcpp/Core/OcppOperation.cpp b/src/ArduinoOcpp/Core/OcppOperation.cpp index 52a9f7e2..035ac8c5 100644 --- a/src/ArduinoOcpp/Core/OcppOperation.cpp +++ b/src/ArduinoOcpp/Core/OcppOperation.cpp @@ -73,7 +73,7 @@ const std::string *OcppOperation::getMessageID() { return &messageID; } -boolean OcppOperation::sendReq(OcppSocket& ocppSocket){ +bool OcppOperation::sendReq(OcppSocket& ocppSocket){ /* * timeout behaviour @@ -151,7 +151,7 @@ boolean OcppOperation::sendReq(OcppSocket& ocppSocket){ return false; } -boolean OcppOperation::receiveConf(JsonDocument& confJson){ +bool OcppOperation::receiveConf(JsonDocument& confJson){ /* * check if messageIDs match. If yes, continue with this function. If not, return false for message not consumed */ @@ -176,7 +176,7 @@ boolean OcppOperation::receiveConf(JsonDocument& confJson){ return true; } -boolean OcppOperation::receiveError(JsonDocument& confJson){ +bool OcppOperation::receiveError(JsonDocument& confJson){ /* * check if messageIDs match. If yes, continue with this function. If not, return false for message not consumed */ @@ -205,7 +205,7 @@ boolean OcppOperation::receiveError(JsonDocument& confJson){ return abortOperation; } -boolean OcppOperation::receiveReq(JsonDocument& reqJson){ +bool OcppOperation::receiveReq(JsonDocument& reqJson){ std::string reqId = reqJson[1]; setMessageID(reqId); @@ -229,7 +229,7 @@ boolean OcppOperation::receiveReq(JsonDocument& reqJson){ return true; //true because everything was successful. If there will be an error check in future, this value becomes more reasonable } -boolean OcppOperation::sendConf(OcppSocket& ocppSocket){ +bool OcppOperation::sendConf(OcppSocket& ocppSocket){ if (!reqExecuted) { //wait until req has been executed @@ -290,7 +290,7 @@ boolean OcppOperation::sendConf(OcppSocket& ocppSocket){ */ std::string out {}; serializeJson(*confJson, out); - boolean wsSuccess = ocppSocket.sendTXT(out); + bool wsSuccess = ocppSocket.sendTXT(out); if (wsSuccess) { if (operationSuccess) { @@ -346,7 +346,7 @@ void OcppOperation::setOnAbortListener(OnAbortListener onAbort) { onAbortListener = onAbort; } -boolean OcppOperation::isFullyConfigured(){ +bool OcppOperation::isFullyConfigured(){ return ocppMessage != nullptr; } diff --git a/src/ArduinoOcpp/Core/OcppOperation.h b/src/ArduinoOcpp/Core/OcppOperation.h index fd93e96a..4ca7b5ad 100644 --- a/src/ArduinoOcpp/Core/OcppOperation.h +++ b/src/ArduinoOcpp/Core/OcppOperation.h @@ -31,7 +31,7 @@ class OcppOperation { OnTimeoutListener onTimeoutListener = [] () {}; OnReceiveErrorListener onReceiveErrorListener = [] (const char *code, const char *description, JsonObject details) {}; OnAbortListener onAbortListener = [] () {}; - boolean reqExecuted = false; + bool reqExecuted = false; std::unique_ptr timeout{new OfflineSensitiveTimeout(40000)}; @@ -67,7 +67,7 @@ class OcppOperation { * the operation is completed (for example when conf() has been called), return true. When the operation is still pending, return * false. */ - boolean sendReq(OcppSocket& ocppSocket); + bool sendReq(OcppSocket& ocppSocket); /** * Decides if message belongs to this operation instance and if yes, proccesses it. For example, multiple instances of an @@ -75,28 +75,28 @@ class OcppOperation { * * Returns true if JSON object has been consumed, false otherwise. */ - boolean receiveConf(JsonDocument& json); + bool receiveConf(JsonDocument& json); /** * Decides if message belongs to this operation instance and if yes, notifies the OcppMessage object about the CallError. * * Returns true if JSON object has been consumed, false otherwise. */ - boolean receiveError(JsonDocument& json); + bool receiveError(JsonDocument& json); /** * 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 */ - boolean receiveReq(JsonDocument& json); + bool receiveReq(JsonDocument& json); /** * After processing a request sent by the communication counterpart, this function sends a confirmation * message. Returns true on success, false otherwise. Returns also true if a CallError has successfully * been sent */ - boolean sendConf(OcppSocket& ocppSocket); + bool sendConf(OcppSocket& ocppSocket); void setInitiated(); @@ -126,7 +126,7 @@ class OcppOperation { */ void setOnAbortListener(OnAbortListener onAbort); - boolean isFullyConfigured(); + bool isFullyConfigured(); void print_debug(); }; diff --git a/src/ArduinoOcpp/Core/OcppTime.cpp b/src/ArduinoOcpp/Core/OcppTime.cpp index 3c58e4ba..d44dc4f8 100644 --- a/src/ArduinoOcpp/Core/OcppTime.cpp +++ b/src/ArduinoOcpp/Core/OcppTime.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace ArduinoOcpp { diff --git a/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp b/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp index 72cdff6a..8776e60e 100644 --- a/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp +++ b/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp @@ -36,6 +36,7 @@ const char *cstrFromOcppEveState(OcppEvseState state) { return "Faulted"; default: AO_DBG_ERR("OcppEvseState not specified"); + __attribute__ ((fallthrough)); case (OcppEvseState::NOT_SET): return "NOT_SET"; } diff --git a/src/ArduinoOcpp/Platform.h b/src/ArduinoOcpp/Platform.h index 25f9f322..66cb0d97 100644 --- a/src/ArduinoOcpp/Platform.h +++ b/src/ArduinoOcpp/Platform.h @@ -5,6 +5,8 @@ #ifndef AO_PLATFORM_H #define AO_PLATFORM_H +#include + #ifdef AO_CUSTOM_CONSOLE #ifndef AO_CUSTOM_CONSOLE_MAXMSGSIZE diff --git a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp index d4deeb38..1b1ca459 100644 --- a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp +++ b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp @@ -31,7 +31,7 @@ #include #include - +#include #include namespace ArduinoOcpp { diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index 532aecd5..05504484 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -38,7 +38,7 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) AO_DBG_ERR("Cannot declare sessionIdTag, transactionId or availability"); } if (sIdTag->getBuffsize() > 0 && (*sIdTag)[0] != '\0') { - snprintf(idTag, min((size_t) (IDTAG_LEN_MAX + 1), sIdTag->getBuffsize()), "%s", ((const char *) *sIdTag)); + snprintf(idTag, std::min((size_t) (IDTAG_LEN_MAX + 1), sIdTag->getBuffsize()), "%s", ((const char *) *sIdTag)); session = true; connectionTimeOutTimestamp = ao_tick_ms(); connectionTimeOutListen = true; diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp index fd34dd7c..8a326cfe 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp @@ -6,6 +6,7 @@ #include #include +#include using namespace ArduinoOcpp; diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp index b4cd67ce..8b28483e 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp @@ -9,13 +9,15 @@ #include #include -#if defined(ESP32) && !defined(AO_DEACTIVATE_FLASH) +#ifndef AO_DEACTIVATE_FLASH +#if defined(ESP32) #include #define USE_FS LITTLEFS #else #include #define USE_FS SPIFFS #endif +#endif #define SINGLE_CONNECTOR_ID 1 diff --git a/src/ao_opts.h b/src/ao_opts.h new file mode 100644 index 00000000..a95f1cad --- /dev/null +++ b/src/ao_opts.h @@ -0,0 +1,31 @@ +#ifndef AOOPTS_H +#define AOOPTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +long ao_tick_ms_impl(); +//unsigned int32_t ao_avail_heap_impl(); + +#ifdef __cplusplus +} +#endif + +#ifndef ao_tick_ms +#define ao_tick_ms ao_tick_ms_impl +#endif + +#ifndef ao_avail_heap +#define ao_avail_heap() 20000 +#endif + +//#ifndef AO_CONSOLE_PRINTF +//#define AO_CONSOLE_PRINTF(...) ESP_LOGI("[ocpp]", __VA_ARGS__) +//#endif + +#ifndef AO_CUSTOM_CONSOLE_MAXMSGSIZE +#define AO_CUSTOM_CONSOLE_MAXMSGSIZE 500 +#endif + +#endif diff --git a/src/ao_opts_impl.c b/src/ao_opts_impl.c new file mode 100644 index 00000000..61c0dae8 --- /dev/null +++ b/src/ao_opts_impl.c @@ -0,0 +1,29 @@ +#include "ao_opts.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +long ao_tick_ms_impl() { + return xTaskGetTickCount() / configTICK_RATE_HZ; +} + +#ifdef _cplusplus +} +#endif From b36ff888e5693fa41e46fdbea57d504abba9acdd Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 5 Apr 2022 17:30:14 +0200 Subject: [PATCH 004/549] support StopTxOnEvUnplug, -InvalidId, field reason --- src/ArduinoOcpp/MessagesV16/CiStrings.h | 4 ++ .../MessagesV16/RemoteStopTransaction.cpp | 2 +- src/ArduinoOcpp/MessagesV16/Reset.cpp | 4 +- .../MessagesV16/StartTransaction.cpp | 15 +++--- .../MessagesV16/StopTransaction.cpp | 12 +++-- src/ArduinoOcpp/MessagesV16/StopTransaction.h | 4 +- .../MessagesV16/UnlockConnector.cpp | 4 +- .../ChargePointStatus/ConnectorStatus.cpp | 52 +++++++++++++++---- .../Tasks/ChargePointStatus/ConnectorStatus.h | 22 +++++--- 9 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/ArduinoOcpp/MessagesV16/CiStrings.h b/src/ArduinoOcpp/MessagesV16/CiStrings.h index a6a13209..2d84cdd7 100644 --- a/src/ArduinoOcpp/MessagesV16/CiStrings.h +++ b/src/ArduinoOcpp/MessagesV16/CiStrings.h @@ -15,7 +15,11 @@ #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 CiString25TypeLen + #endif diff --git a/src/ArduinoOcpp/MessagesV16/RemoteStopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/RemoteStopTransaction.cpp index 761b486d..461398b8 100644 --- a/src/ArduinoOcpp/MessagesV16/RemoteStopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/RemoteStopTransaction.cpp @@ -32,7 +32,7 @@ std::unique_ptr RemoteStopTransaction::createConf(){ auto connIter = cpStatusService->getConnector(i); if (connIter->getTransactionId() == transactionId) { canStopTransaction = true; - connIter->endSession(); + connIter->endSession("Remote"); } } } diff --git a/src/ArduinoOcpp/MessagesV16/Reset.cpp b/src/ArduinoOcpp/MessagesV16/Reset.cpp index 04de2f51..9e751ca6 100644 --- a/src/ArduinoOcpp/MessagesV16/Reset.cpp +++ b/src/ArduinoOcpp/MessagesV16/Reset.cpp @@ -21,7 +21,7 @@ void 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() ) */ - //const char *type = payload["type"] | "Invalid"; + bool isHard = !strcmp(payload["type"] | "undefined", "Hard"); if (ocppModel && ocppModel->getChargePointStatusService()) { auto cpsService = ocppModel->getChargePointStatusService(); @@ -29,7 +29,7 @@ void Reset::processReq(JsonObject payload) { for (int i = 0; i < cpsService->getNumConnectors(); i++) { auto connector = cpsService->getConnector(connId); if (connector) { - connector->endSession(); + connector->endSession(isHard ? "HardReset" : "SoftReset"); } } } diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp index 62e05590..d05c908f 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp @@ -21,7 +21,7 @@ StartTransaction::StartTransaction(int connectorId, const char *idTag) : connect AO_DBG_ERR("Format violation"); } -const char* StartTransaction::getOcppOperationType(){ +const char* StartTransaction::getOcppOperationType() { return "StartTransaction"; } @@ -95,17 +95,18 @@ void StartTransaction::processConf(JsonObject payload) { if (ocppModel) connector = ocppModel->getConnectorStatus(connectorId); - if (connector){ + if (connector) { if (transactionRev == connector->getTransactionWriteCount()) { - + if (!strcmp(idTagInfoStatus, "Accepted")) { AO_DBG_INFO("Request has been accepted"); - connector->setTransactionId(transactionId); } else { AO_DBG_INFO("Request has been denied. Reason: %s", idTagInfoStatus); - //connector->setTransactionId(-1); - connector->endSession(); //something is wrong with the idTag. Abort session + AO_DBG_DEBUG("Set txId despite rejection"); + connector->setIdTagInvalidated(); } + + connector->setTransactionId(transactionId); } connector->setTransactionIdSync(transactionId); @@ -123,7 +124,7 @@ void StartTransaction::processReq(JsonObject payload) { } -std::unique_ptr StartTransaction::createConf(){ +std::unique_ptr StartTransaction::createConf() { auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2))); JsonObject payload = doc->to(); diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp index 094888c3..17af6b60 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp @@ -10,8 +10,10 @@ using ArduinoOcpp::Ocpp16::StopTransaction; -StopTransaction::StopTransaction(int connectorId) : connectorId(connectorId) { - +StopTransaction::StopTransaction(int connectorId, const char *reason) : connectorId(connectorId) { + if (reason) { + snprintf(this->reason, REASON_LEN_MAX, "%s", reason); + } } const char* StopTransaction::getOcppOperationType(){ @@ -44,7 +46,7 @@ void StopTransaction::initiate() { } std::unique_ptr StopTransaction::createReq() { - auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(4) + (JSONDATE_LENGTH + 1))); + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(5) + (JSONDATE_LENGTH + 1) + (REASON_LEN_MAX + 1))); JsonObject payload = doc->to(); if (meterStop >= 0) @@ -61,6 +63,10 @@ std::unique_ptr StopTransaction::createReq() { payload["transactionId"] = connector->getTransactionIdSync(); } + if (reason[0] != '\0') { + payload["reason"] = reason; + } + return doc; } diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.h b/src/ArduinoOcpp/MessagesV16/StopTransaction.h index a1b861bd..58dbf9b3 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.h @@ -7,6 +7,7 @@ #include #include +#include namespace ArduinoOcpp { namespace Ocpp16 { @@ -16,9 +17,10 @@ class StopTransaction : public OcppMessage { int connectorId = 1; int meterStop = -1; OcppTimestamp otimestamp; + char reason [REASON_LEN_MAX] {'\0'}; public: - StopTransaction(int connectorId); + StopTransaction(int connectorId, const char *reason = nullptr); const char* getOcppOperationType(); diff --git a/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp b/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp index 9b464087..a7418c44 100644 --- a/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp +++ b/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp @@ -28,6 +28,8 @@ void UnlockConnector::processReq(JsonObject payload) { auto connector = ocppModel->getConnectorStatus(connectorId); + connector->endSession("UnlockCommand"); + std::function unlockConnector = connector->getOnUnlockConnector(); if (unlockConnector != nullptr) { cbDefined = true; @@ -38,8 +40,6 @@ void UnlockConnector::processReq(JsonObject payload) { } cbUnlockSuccessful = unlockConnector(); - - //success } std::unique_ptr UnlockConnector::createConf(){ diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index cf304af9..6d27b36d 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -35,6 +35,8 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) connectionTimeOut = declareConfiguration("ConnectionTimeOut", 30, CONFIGURATION_FN, true, true, true, false); minimumStatusDuration = declareConfiguration("MinimumStatusDuration", 0, CONFIGURATION_FN, true, true, true, false); + stopTransactionOnInvalidId = declareConfiguration("StopTransactionOnInvalidId", "true", CONFIGURATION_FN, true, true, false, false); + stopTransactionOnEVSideDisconnect = declareConfiguration("StopTransactionOnEVSideDisconnect", "true", CONFIGURATION_FN, true, true, false, false); if (!sIdTag || !transactionId || !availability) { AO_DBG_ERR("Cannot declare sessionIdTag, transactionId or availability"); @@ -89,12 +91,13 @@ OcppEvseState ConnectorStatus::inferenceStatus() { return OcppEvseState::Preparing; } else { //Transaction is currently running + if ((connectorEnergizedSampler && !connectorEnergizedSampler()) || + idTagInvalidated) { + return OcppEvseState::SuspendedEVSE; + } if (evRequestsEnergySampler && !evRequestsEnergySampler()) { return OcppEvseState::SuspendedEV; } - if (connectorEnergizedSampler && !connectorEnergizedSampler()) { - return OcppEvseState::SuspendedEVSE; - } return OcppEvseState::Charging; } } @@ -105,6 +108,10 @@ bool ConnectorStatus::ocppPermitsCharge() { return false; } + if (idTagInvalidated) { + return false; + } + OcppEvseState state = inferenceStatus(); return state == OcppEvseState::Charging || @@ -117,6 +124,14 @@ OcppMessage *ConnectorStatus::loop() { *availability = AVAILABILITY_INOPERATIVE; saveState(); } + + if (connectorPluggedSampler) { + if (getTransactionId() >= 0 && !connectorPluggedSampler()) { + if (!*stopTransactionOnEVSideDisconnect || strcmp(*stopTransactionOnEVSideDisconnect, "false")) { + endSession("EVDisconnected"); + } + } + } /* * Check conditions for start or stop transaction @@ -124,12 +139,11 @@ OcppMessage *ConnectorStatus::loop() { if (connectorPluggedSampler) { //only supported with connectorPluggedSampler if (getTransactionId() >= 0) { //check condition for StopTransaction - if (!connectorPluggedSampler() || - !session) { + if (!session) { AO_DBG_DEBUG("Session mngt: txId=%i, connectorPlugged=%d, session=%d", getTransactionId(), connectorPluggedSampler(), session); AO_DBG_INFO("Session mngt: trigger StopTransaction"); - return new StopTransaction(connectorId); + return new StopTransaction(connectorId, endReason[0] != '\0' ? endReason : nullptr); } } else { //check condition for StartTransaction @@ -163,7 +177,7 @@ OcppMessage *ConnectorStatus::loop() { if (inferencedStatus != currentStatus) { currentStatus = inferencedStatus; t_statusTransition = ao_tick_ms(); - AO_DBG_DEBUG("Status changed%s", *minimumStatusDuration > 0 ? ", will report delayed", ""); + AO_DBG_DEBUG("Status changed%s", *minimumStatusDuration ? ", will report delayed" : ""); } if (reportedStatus != currentStatus && @@ -200,13 +214,18 @@ void ConnectorStatus::beginSession(const char *sessionIdTag) { sIdTag->setValue(idTag, IDTAG_LEN_MAX + 1); saveState(); session = true; + idTagInvalidated = false; + + memset(endReason, '\0', REASON_LEN_MAX + 1); connectionTimeOutListen = true; connectionTimeOutTimestamp = ao_tick_ms(); } -void ConnectorStatus::endSession() { - AO_DBG_DEBUG("End session with idTag %s", idTag); +void ConnectorStatus::endSession(const char *reason) { + AO_DBG_DEBUG("End session with idTag %s for reason %s, %s previous reason", + idTag, reason ? reason : "undefined", + endReason[0] == '\0' ? "no" : "overruled by"); if (session) { memset(idTag, '\0', IDTAG_LEN_MAX + 1); *sIdTag = ""; @@ -214,9 +233,24 @@ void ConnectorStatus::endSession() { } session = false; + if (reason && endReason[0] == '\0') { + snprintf(endReason, REASON_LEN_MAX + 1, "%s", reason); + } + connectionTimeOutListen = false; } +void ConnectorStatus::setIdTagInvalidated() { + if (session) { + idTagInvalidated = true; + if (!*stopTransactionOnInvalidId || strcmp(*stopTransactionOnInvalidId, "false")) { + endSession("DeAuthorized"); + } + } else { + AO_DBG_WARN("Cannot invalidate IdTag outside of session"); + } +} + const char *ConnectorStatus::getSessionIdTag() { return session ? idTag : nullptr; } diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h index 6a1879dc..fb9d3ff0 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h @@ -28,15 +28,17 @@ class ConnectorStatus { const int connectorId; - std::shared_ptr> availability {nullptr}; + std::shared_ptr> availability; bool session = false; char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; - std::shared_ptr> sIdTag {nullptr}; - std::shared_ptr> transactionId {nullptr}; + bool idTagInvalidated {false}; //if StartTransaction.conf() has status != "Accepted" + std::shared_ptr> sIdTag; + std::shared_ptr> transactionId; int transactionIdSync = -1; + char endReason [REASON_LEN_MAX + 1] = {'\0'}; - std::shared_ptr> connectionTimeOut {nullptr}; //in seconds + std::shared_ptr> connectionTimeOut; //in seconds bool connectionTimeOutListen {false}; ulong connectionTimeOutTimestamp {0}; //in milliseconds @@ -47,11 +49,17 @@ class ConnectorStatus { const char *getErrorCode(); OcppEvseState currentStatus = OcppEvseState::NOT_SET; - std::shared_ptr> minimumStatusDuration {nullptr}; //in seconds + std::shared_ptr> minimumStatusDuration; //in seconds OcppEvseState reportedStatus = OcppEvseState::NOT_SET; ulong t_statusTransition = 0; + //std::function()> startTransactionBehavior; + //std::function(const char* stopReason)> stopTransactionBehavior; + std::function onUnlockConnector {nullptr}; + + std::shared_ptr> stopTransactionOnInvalidId; + std::shared_ptr> stopTransactionOnEVSideDisconnect; public: ConnectorStatus(OcppModel& context, int connectorId); @@ -66,7 +74,8 @@ class ConnectorStatus { * (given by ConnectorPluggedSampler and no error code) */ void beginSession(const char *idTag); - void endSession(); + void endSession(const char *reason = nullptr); + void setIdTagInvalidated(); //if StartTransaction.conf() has status != "Accepted" const char *getSessionIdTag(); int getTransactionId(); int getTransactionIdSync(); @@ -74,7 +83,6 @@ class ConnectorStatus { void setTransactionId(int id); void setTransactionIdSync(int id); - int getAvailability(); void setAvailability(bool available); void setAuthorizationProvider(std::function authorization); From 38bdce6d91ee1060100be84c6cea53bb4acaa166 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:09:31 +0200 Subject: [PATCH 005/549] begin with c facade --- CMakeLists.txt | 3 ++- README.md | 4 ++++ src/ArduinoOcpp_c.cpp | 22 ++++++++++++++++++++++ src/ArduinoOcpp_c.h | 18 ++++++++++++++++++ src/ao_opts_impl.c | 2 +- 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 src/ArduinoOcpp_c.cpp create mode 100644 src/ArduinoOcpp_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index be8c56fc..176999e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,11 +47,12 @@ set(AO_SRC src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp src/ArduinoOcpp.cpp + src/ArduinoOcpp_c.cpp src/ao_opts_impl.c ) idf_component_register(SRCS ${AO_SRC} - INCLUDE_DIRS "./src") + INCLUDE_DIRS "./src" "${PROJECT_DIR}/include") target_compile_options(${COMPONENT_TARGET} PUBLIC -DAO_CUSTOM_WS diff --git a/README.md b/README.md index 35e19a87..facb1c3c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,10 @@ For simple chargers, the necessary hardware and internet integration is usually **This feature branch is WIP. A usage guide will follow.** +## Dependencies + +- [bblanchon/ArduinoJson](https://github.com/bblanchon/ArduinoJson) + ## Supported operations | Operation name | supported | in progress | not supported | diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp new file mode 100644 index 00000000..7400cc89 --- /dev/null +++ b/src/ArduinoOcpp_c.cpp @@ -0,0 +1,22 @@ +#include "ArduinoOcpp_c.h" +#include "ArduinoOcpp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void ao_initialize() { + //OCPP_initialize("echo.websocket.events", 80, "ws://echo.websocket.events/"); +} + +void ao_loop() { + OCPP_loop(); +} + +void ao_bootNotification() { + bootNotification("model", "vendor"); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h new file mode 100644 index 00000000..54eedd24 --- /dev/null +++ b/src/ArduinoOcpp_c.h @@ -0,0 +1,18 @@ +#ifndef ARDUINOOCPP_C_H +#define ARDUINOOCPP_C_H + +#ifdef __cplusplus +extern "C" { +#endif + +void ao_initialize(); + +void ao_loop(); + +void ao_bootNotification(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ao_opts_impl.c b/src/ao_opts_impl.c index 61c0dae8..41839036 100644 --- a/src/ao_opts_impl.c +++ b/src/ao_opts_impl.c @@ -24,6 +24,6 @@ long ao_tick_ms_impl() { return xTaskGetTickCount() / configTICK_RATE_HZ; } -#ifdef _cplusplus +#ifdef __cplusplus } #endif From 0f1f23616dc4d631b9b634febfe68b8e67478db3 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:04:24 +0200 Subject: [PATCH 006/549] add ocpp socket type and callbacks in c adapter --- src/ArduinoOcpp/Platform.h | 2 +- .../SimpleOcppOperationFactory.cpp | 6 --- src/ArduinoOcpp/SimpleOcppOperationFactory.h | 1 - src/ArduinoOcpp_c.cpp | 37 +++++++++++++++---- src/ArduinoOcpp_c.h | 16 +++++++- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/ArduinoOcpp/Platform.h b/src/ArduinoOcpp/Platform.h index 66cb0d97..319239d1 100644 --- a/src/ArduinoOcpp/Platform.h +++ b/src/ArduinoOcpp/Platform.h @@ -23,7 +23,7 @@ void ao_console_out(const char *msg); char msg [AO_CUSTOM_CONSOLE_MAXMSGSIZE]; \ snprintf(msg, AO_CUSTOM_CONSOLE_MAXMSGSIZE, X, ##__VA_ARGS__); \ sprintf(msg + AO_CUSTOM_CONSOLE_MAXMSGSIZE - 7, " [...]"); \ - ao_console_out(msg); \ + ArduinoOcpp::ao_console_out(msg); \ } while (0) #else #define ao_set_console_out(X) \ diff --git a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp index 1b1ca459..590818e5 100644 --- a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp +++ b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp @@ -69,12 +69,6 @@ void setOnBootNotificationRequestListener(OnReceiveReqListener listener){ deinit_afterwards(onBootNotificationRequest); } -OnReceiveReqListener onTargetValuesRequest; -void setOnTargetValuesRequestListener(OnReceiveReqListener listener) { - onTargetValuesRequest = listener; - deinit_afterwards(onTargetValuesRequest); -} - OnReceiveReqListener onSetChargingProfileRequest; void setOnSetChargingProfileRequestListener(OnReceiveReqListener listener){ onSetChargingProfileRequest = listener; diff --git a/src/ArduinoOcpp/SimpleOcppOperationFactory.h b/src/ArduinoOcpp/SimpleOcppOperationFactory.h index c9ad0726..875b90ad 100644 --- a/src/ArduinoOcpp/SimpleOcppOperationFactory.h +++ b/src/ArduinoOcpp/SimpleOcppOperationFactory.h @@ -26,7 +26,6 @@ void registerCustomOcppMessage(const char *messageType, OcppMessageCreator ocppM void setOnAuthorizeRequestListener(OnReceiveReqListener onReceiveReq); void setOnBootNotificationRequestListener(OnReceiveReqListener onReceiveReq); -void setOnTargetValuesRequestListener(OnReceiveReqListener onReceiveReq); void setOnSetChargingProfileRequestListener(OnReceiveReqListener onReceiveReq); void setOnStartTransactionRequestListener(OnReceiveReqListener onReceiveReq); void setOnTriggerMessageRequestListener(OnReceiveReqListener onReceiveReq); diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 7400cc89..11f9969d 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -1,22 +1,43 @@ #include "ArduinoOcpp_c.h" #include "ArduinoOcpp.h" -#ifdef __cplusplus -extern "C" { -#endif +#include + +ArduinoOcpp::OcppSocket *ocppSocket = nullptr; -void ao_initialize() { +extern "C" void ao_initialize(AO_OcppSocket *osock) { //OCPP_initialize("echo.websocket.events", 80, "ws://echo.websocket.events/"); + if (!osock) { + AO_DBG_ERR("osock is null"); + } + + ocppSocket = reinterpret_cast(osock); + + OCPP_initialize(*ocppSocket); } -void ao_loop() { +extern "C" void ao_loop() { OCPP_loop(); } -void ao_bootNotification() { +extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation) { bootNotification("model", "vendor"); } -#ifdef __cplusplus -} +#ifndef AO_RECEIVE_PAYLOAD_BUFSIZE +#define AO_RECEIVE_PAYLOAD_BUFSIZE 1024 #endif + +char ao_recv_payload_buff [AO_RECEIVE_PAYLOAD_BUFSIZE] = {'\0'}; + +extern "C" void ao_onResetRequest(OnOcppMessage onRequest) { + OnReceiveReqListener cb = [onRequest] (JsonObject payload) { + auto len = serializeJson(payload, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); + if (len <= 0) { + AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); + } + onRequest(len > 0 ? ao_recv_payload_buff : nullptr, len); + }; + setOnResetReceiveReq(cb); +} + diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index 54eedd24..5faf07a9 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -1,15 +1,27 @@ #ifndef ARDUINOOCPP_C_H #define ARDUINOOCPP_C_H +#include + +struct AO_OcppSocket; +typedef struct AO_OcppSocket AO_OcppSocket; + +typedef void (*OnOcppMessage) (const char *payload, size_t len); +typedef void (*OnOcppAbort) (); +typedef void (*OnOcppTimeout) (); +typedef void (*OnOcppError) (const char *code, const char *description, const char *details_json, size_t details_len); + #ifdef __cplusplus extern "C" { #endif -void ao_initialize(); +void ao_initialize(AO_OcppSocket *osock); void ao_loop(); -void ao_bootNotification(); +void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation); + +void ao_onResetRequest(OnOcppMessage onRequest); #ifdef __cplusplus } From 7d451a2dc9c58b61fec3e494e352b0597bee8ac3 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 18 Apr 2022 09:45:10 +0200 Subject: [PATCH 007/549] added functions --- src/ArduinoOcpp_c.cpp | 4 ++-- src/ArduinoOcpp_c.h | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 11f9969d..ad0f0701 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -5,7 +5,7 @@ ArduinoOcpp::OcppSocket *ocppSocket = nullptr; -extern "C" void ao_initialize(AO_OcppSocket *osock) { +extern "C" void ao_initialize(AOcppSocket *osock) { //OCPP_initialize("echo.websocket.events", 80, "ws://echo.websocket.events/"); if (!osock) { AO_DBG_ERR("osock is null"); @@ -20,7 +20,7 @@ extern "C" void ao_loop() { OCPP_loop(); } -extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation) { +extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { bootNotification("model", "vendor"); } diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index 5faf07a9..7efb3878 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -3,8 +3,8 @@ #include -struct AO_OcppSocket; -typedef struct AO_OcppSocket AO_OcppSocket; +struct AOcppSocket; +typedef struct AOcppSocket AOcppSocket; typedef void (*OnOcppMessage) (const char *payload, size_t len); typedef void (*OnOcppAbort) (); @@ -15,11 +15,17 @@ typedef void (*OnOcppError) (const char *code, const char *description, const extern "C" { #endif -void ao_initialize(AO_OcppSocket *osock); +void ao_initialize(AOcppSocket *osock); void ao_loop(); -void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation); +void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +void ao_stopTransaction(OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); void ao_onResetRequest(OnOcppMessage onRequest); From 4af1e7908764304a06daeac2b8b2c6cebb852673 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 19 Apr 2022 14:59:14 +0200 Subject: [PATCH 008/549] All function declarations for c-facade --- src/ArduinoOcpp_c.h | 73 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index 7efb3878..fb74c201 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -11,14 +11,65 @@ typedef void (*OnOcppAbort) (); typedef void (*OnOcppTimeout) (); typedef void (*OnOcppError) (const char *code, const char *description, const char *details_json, size_t details_len); +typedef float (*SamplerFloat)(); +typedef int (*SamplerInt)(); +typedef bool (*SamplerBool)(); +typedef const char* (*SamplerString)(); + #ifdef __cplusplus extern "C" { #endif void ao_initialize(AOcppSocket *osock); +void ao_deinitialize(); + void ao_loop(); +/* + * Feed lib with HW related data + */ + +void ao_setPowerActiveImportSampler(SamplerFloat power); + +void ao_setEnergyActiveImportSampler(SamplerFloat energy); + +void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy); + +void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized); + +void ao_setConnectorPluggedSampler(SamplerBool connectorPlugged); + +//void setConnectorFaultedSampler(SamplerBool connectorFailed); + +void ao_addConnectorErrorCodeSampler(SamplerString connectorErrorCode); + +/* + * Execute HW related operations on EVSE + */ + +void ao_onChargingRateLimitChange(void (*chargingRateChanged)(float)); + +void ao_onUnlockConnector(SamplerBool unlockConnector); //true: success, false: failure + +/* + * Generic listeners for OCPP operations initiated by Central System + */ + +void ao_onSetChargingProfileRequest(OnOcppMessage onRequest); //optional + +void ao_onRemoteStartTransactionSendConf(OnOcppMessage onSendConf); //important, energize the power plug here and capture the idTag + +void ao_onRemoteStopTransactionSendConf(OnOcppMessage onSendConf); //important, de-energize the power plug here +void ao_onRemoteStopTransactionRequest(OnOcppMessage onRequest); //optional, to de-energize the power plug immediately + +void ao_onResetSendConf(OnOcppMessage onSendConf); //important, reset your device here (i.e. call ESP.reset();) +void ao_onResetRequest(OnOcppMessage onRequest); //alternative: start reset timer here + +/* + * Initiate OCPP operations + */ + void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); @@ -27,7 +78,27 @@ void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcpp void ao_stopTransaction(OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); -void ao_onResetRequest(OnOcppMessage onRequest); +/* + * Access OCPP state + */ + +int ao_getTransactionId(); //returns the ID of the current transaction. Returns -1 if called before or after an transaction + +bool ao_ocppPermitsCharge(); + +bool ao_isAvailable(); //if the charge point is operative or inoperative + +/* + * Charging session management + */ + +void ao_beginSession(const char *idTag); + +void ao_endSession(); + +bool ao_isInSession(); + +const char *ao_getSessionIdTag(); #ifdef __cplusplus } From 5bd8b4c9f328e9beddbc669807f651865a8f2598 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Fri, 29 Apr 2022 17:56:05 +0200 Subject: [PATCH 009/549] debug out fixes --- CMakeLists.txt | 3 ++- src/ArduinoOcpp/Platform.h | 7 ++++--- src/ArduinoOcpp_c.cpp | 4 ++++ src/ArduinoOcpp_c.h | 2 ++ src/ao_opts.h | 2 +- src/ao_opts_impl.c | 3 ++- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 176999e2..210f3d11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,4 +57,5 @@ idf_component_register(SRCS ${AO_SRC} target_compile_options(${COMPONENT_TARGET} PUBLIC -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE - -DAO_DEACTIVATE_FLASH) + -DAO_DEACTIVATE_FLASH + -DAO_DBG_LEVEL=AO_DL_DEBUG) diff --git a/src/ArduinoOcpp/Platform.h b/src/ArduinoOcpp/Platform.h index 319239d1..f6dfb757 100644 --- a/src/ArduinoOcpp/Platform.h +++ b/src/ArduinoOcpp/Platform.h @@ -10,7 +10,7 @@ #ifdef AO_CUSTOM_CONSOLE #ifndef AO_CUSTOM_CONSOLE_MAXMSGSIZE -#define AO_CUSTOM_CONSOLE_MAXMSGSIZE 128 +#define AO_CUSTOM_CONSOLE_MAXMSGSIZE 196 #endif void ao_set_console_out(void (*console_out)(const char *msg)); @@ -21,8 +21,9 @@ void ao_console_out(const char *msg); #define AO_CONSOLE_PRINTF(X, ...) \ do { \ char msg [AO_CUSTOM_CONSOLE_MAXMSGSIZE]; \ - snprintf(msg, AO_CUSTOM_CONSOLE_MAXMSGSIZE, X, ##__VA_ARGS__); \ - sprintf(msg + AO_CUSTOM_CONSOLE_MAXMSGSIZE - 7, " [...]"); \ + if (snprintf(msg, AO_CUSTOM_CONSOLE_MAXMSGSIZE, X, ##__VA_ARGS__) < 0) { \ + sprintf(msg + AO_CUSTOM_CONSOLE_MAXMSGSIZE - 7, " [...]"); \ + } \ ArduinoOcpp::ao_console_out(msg); \ } while (0) #else diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index ad0f0701..4511f1ac 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -20,6 +20,10 @@ extern "C" void ao_loop() { OCPP_loop(); } +extern "C" void ao_set_console_out_c(void (*console_out)(const char *msg)) { + ao_set_console_out(console_out); +} + extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { bootNotification("model", "vendor"); } diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index fb74c201..c44db5fc 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -26,6 +26,8 @@ void ao_deinitialize(); void ao_loop(); +void ao_set_console_out_c(void (*console_out)(const char *msg)); + /* * Feed lib with HW related data */ diff --git a/src/ao_opts.h b/src/ao_opts.h index a95f1cad..8f76097f 100644 --- a/src/ao_opts.h +++ b/src/ao_opts.h @@ -25,7 +25,7 @@ long ao_tick_ms_impl(); //#endif #ifndef AO_CUSTOM_CONSOLE_MAXMSGSIZE -#define AO_CUSTOM_CONSOLE_MAXMSGSIZE 500 +#define AO_CUSTOM_CONSOLE_MAXMSGSIZE 192 #endif #endif diff --git a/src/ao_opts_impl.c b/src/ao_opts_impl.c index 41839036..5238ad01 100644 --- a/src/ao_opts_impl.c +++ b/src/ao_opts_impl.c @@ -21,7 +21,8 @@ extern "C" { #endif long ao_tick_ms_impl() { - return xTaskGetTickCount() / configTICK_RATE_HZ; + //return xTaskGetTickCount() / configTICK_RATE_HZ; + return xTaskGetTickCount() * (1000 / configTICK_RATE_HZ); } #ifdef __cplusplus From 1d8946dcaf4eb8a62ab3cbf96fbebfc46c3a6ad7 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 1 May 2022 21:54:35 +0200 Subject: [PATCH 010/549] adapt c-style callback functions for AO --- src/ArduinoOcpp_c.cpp | 32 ++++++++++++++++++++++++++++---- src/ao_opts.h | 2 +- src/ao_opts_impl.c | 4 ++-- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 4511f1ac..7b50aa69 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -24,16 +24,40 @@ extern "C" void ao_set_console_out_c(void (*console_out)(const char *msg)) { ao_set_console_out(console_out); } -extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { - bootNotification("model", "vendor"); -} - #ifndef AO_RECEIVE_PAYLOAD_BUFSIZE #define AO_RECEIVE_PAYLOAD_BUFSIZE 1024 #endif char ao_recv_payload_buff [AO_RECEIVE_PAYLOAD_BUFSIZE] = {'\0'}; +std::function wrapCstyleOcppCb(OnOcppMessage cb) { + return [cb] (JsonObject payload) { + auto len = serializeJson(payload, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); + if (len <= 0) { + AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); + } + cb(len > 0 ? ao_recv_payload_buff : nullptr, len); + }; +} + +std::function wrapCstyleOcppCb(void (*cb)()) { + return cb; +} + +ArduinoOcpp::OnReceiveErrorListener wrapCstyleOcppCb(OnOcppError cb) { + return [cb] (const char *code, const char *description, JsonObject details) { + auto len = serializeJson(details, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); + if (len <= 0) { + AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); + } + cb(code, description, len > 0 ? ao_recv_payload_buff : "", len); + }; +} + +extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + bootNotification("model", "vendor", wrapCstyleOcppCb(onConfirmation), wrapCstyleOcppCb(onAbort), wrapCstyleOcppCb(onTimeout), wrapCstyleOcppCb(onError)); +} + extern "C" void ao_onResetRequest(OnOcppMessage onRequest) { OnReceiveReqListener cb = [onRequest] (JsonObject payload) { auto len = serializeJson(payload, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); diff --git a/src/ao_opts.h b/src/ao_opts.h index 8f76097f..5a19cede 100644 --- a/src/ao_opts.h +++ b/src/ao_opts.h @@ -5,7 +5,7 @@ extern "C" { #endif -long ao_tick_ms_impl(); +unsigned long ao_tick_ms_impl(); //unsigned int32_t ao_avail_heap_impl(); #ifdef __cplusplus diff --git a/src/ao_opts_impl.c b/src/ao_opts_impl.c index 5238ad01..ce1b6559 100644 --- a/src/ao_opts_impl.c +++ b/src/ao_opts_impl.c @@ -20,9 +20,9 @@ extern "C" { #endif -long ao_tick_ms_impl() { +unsigned long ao_tick_ms_impl() { //return xTaskGetTickCount() / configTICK_RATE_HZ; - return xTaskGetTickCount() * (1000 / configTICK_RATE_HZ); + return (xTaskGetTickCount() * 1000UL) / configTICK_RATE_HZ; } #ifdef __cplusplus From 8eb95d06dfc90d26af8daeebea3f0a9653fa5c84 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 3 May 2022 16:00:00 +0200 Subject: [PATCH 011/549] Customizable meter values format --- src/ArduinoOcpp/MessagesV16/MeterValues.cpp | 7 +- src/ArduinoOcpp/MessagesV16/MeterValues.h | 2 +- .../MessagesV16/TriggerMessage.cpp | 4 +- .../Metering/ConnectorMeterValuesRecorder.cpp | 228 +++++++++++------- .../Metering/ConnectorMeterValuesRecorder.h | 34 ++- src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp | 108 +++++++++ src/ArduinoOcpp/Tasks/Metering/MeterValue.h | 52 ++-- .../Tasks/Metering/MeteringService.cpp | 4 +- .../Tasks/Metering/MeteringService.h | 2 +- .../Tasks/Metering/SampledValue.cpp | 67 +++++ src/ArduinoOcpp/Tasks/Metering/SampledValue.h | 56 ++--- 11 files changed, 389 insertions(+), 175 deletions(-) create mode 100644 src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp create mode 100644 src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp index 26c60593..97863a93 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp @@ -15,12 +15,9 @@ MeterValues::MeterValues() { } -MeterValues::MeterValues(const std::vector>& meterValue, int connectorId, int transactionId) - : connectorId{connectorId}, transactionId{transactionId} { +MeterValues::MeterValues(std::vector>&& meterValue, int connectorId, int transactionId) + : meterValue{std::move(meterValue)}, connectorId{connectorId}, transactionId{transactionId} { - for (auto value = meterValue.begin(); value != meterValue.end(); value++) { - this->meterValue.push_back(std::unique_ptr(new MeterValue(**value))); - } } MeterValues::~MeterValues(){ diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.h b/src/ArduinoOcpp/MessagesV16/MeterValues.h index 5fba8009..24f4be0e 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.h +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.h @@ -22,7 +22,7 @@ class MeterValues : public OcppMessage { int transactionId = -1; public: - MeterValues(const std::vector>& meterValue, int connectorId, int transactionId); + MeterValues(std::vector>&& meterValue, int connectorId, int transactionId); MeterValues(); //for debugging only. Make this for the server pendant diff --git a/src/ArduinoOcpp/MessagesV16/TriggerMessage.cpp b/src/ArduinoOcpp/MessagesV16/TriggerMessage.cpp index 3ab778be..3673a589 100644 --- a/src/ArduinoOcpp/MessagesV16/TriggerMessage.cpp +++ b/src/ArduinoOcpp/MessagesV16/TriggerMessage.cpp @@ -31,10 +31,10 @@ void TriggerMessage::processReq(JsonObject payload) { if (connectorId < 0) { auto nConnectors = mService->getNumConnectors(); for (decltype(nConnectors) i = 0; i < nConnectors; i++) { - triggeredOperations.push_back(mService->takeMeterValuesNow(i)); + triggeredOperations.push_back(mService->takeTriggeredMeterValues(i)); } } else if (connectorId < mService->getNumConnectors()) { - triggeredOperations.push_back(mService->takeMeterValuesNow(connectorId)); + triggeredOperations.push_back(mService->takeTriggeredMeterValues(connectorId)); } else { errorCode = "PropertyConstraintViolation"; } diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index 1d71648f..ffc6b91f 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -16,117 +16,162 @@ using namespace ArduinoOcpp::Ocpp16; ConnectorMeterValuesRecorder::ConnectorMeterValuesRecorder(OcppModel& context, int connectorId) : context(context), connectorId{connectorId} { - MeterValueSampleInterval = declareConfiguration("MeterValueSampleInterval", 60); + auto MeterValuesSampledData = declareConfiguration( + "MeterValuesSampledData", + "Energy.Active.Import.Register,Power.Active.Import", + CONFIGURATION_FN, + true,true,true,false + ); MeterValuesSampledDataMaxLength = declareConfiguration("MeterValuesSampledDataMaxLength", 4, CONFIGURATION_VOLATILE, false, true, false, false); -} - -void ConnectorMeterValuesRecorder::takeSample() { - if (meterValueSamplers.empty()) return; - - std::unique_ptr sample; - if (context.getOcppTime().isValid()) { - sample.reset(new MeterValue(context.getOcppTime().getOcppTimestampNow())); - } - if (!sample) { - return; - } - - for (auto mvs = meterValueSamplers.begin(); mvs != meterValueSamplers.end(); mvs++) { - sample->addSampledValue((*mvs)->takeValue()); - } - - meterValue.push_back(std::move(sample)); + MeterValueSampleInterval = declareConfiguration("MeterValueSampleInterval", 60); + + auto StopTxnSampledData = declareConfiguration( + "StopTxnSampledData", + "", + CONFIGURATION_FN, + true,true,true,false + ); + StopTxnSampledDataMaxLength = declareConfiguration("StopTxnSampledDataMaxLength", 4, CONFIGURATION_VOLATILE, false, true, false, false); + + auto MeterValuesAlignedData = declareConfiguration( + "MeterValuesAlignedData", + "Energy.Active.Import.Register,Power.Active.Import", + CONFIGURATION_FN, + true,true,true,false + ); + MeterValuesAlignedDataMaxLength = declareConfiguration("MeterValuesAlignedDataMaxLength", 4, CONFIGURATION_VOLATILE, false, true, false, false); + ClockAlignedDataInterval = declareConfiguration("ClockAlignedDataInterval", 0); + + auto StopTxnAlignedData = declareConfiguration( + "StopTxnAlignedData", + "", + CONFIGURATION_FN, + true,true,true,false + ); + StopTxnAlignedDataMaxLength = declareConfiguration("StopTxnAlignedDataMaxLength", 4, CONFIGURATION_VOLATILE, false, true, false, false); + + sampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, MeterValuesSampledData)); AO_DBG_DEBUG("After MeterValuesSampledData"); + alignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, MeterValuesAlignedData)); AO_DBG_DEBUG("After MeterValuesAlignedData"); + stopTxnSampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, StopTxnSampledData)); AO_DBG_DEBUG("After StopTxnSampledData"); + stopTxnAlignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, StopTxnAlignedData)); AO_DBG_DEBUG("After StopTxnAlignedData"); } OcppMessage *ConnectorMeterValuesRecorder::loop() { - if (*MeterValueSampleInterval < 1) { - //Metering off by definition - clear(); - return nullptr; - } - - /* - * First: check if there was a transaction break (i.e. transaction either started or stopped; transactionId changed) - */ - auto connector = context.getConnectorStatus(connectorId); - if (connector && connector->getTransactionId() != lastTransactionId) { - //transaction break occured! - auto result = toMeterValues(); - lastTransactionId = connector->getTransactionId(); - return result; - } - - /* - * Calculate energy consumption which finally should be reportet to the Central Station in a MeterValues.req. - * This code uses the EVSE's own energy register, if available (i.e. if energySampler is set). Otherwise it - * uses the power sampler. - * If no powerSampler is available, estimate the energy consumption taking the Charging Schedule and CP Status - * into account. - */ - if (ao_tick_ms() - lastSampleTime >= (ulong) (*MeterValueSampleInterval * 1000)) { - takeSample(); - lastSampleTime = ao_tick_ms(); + if (*ClockAlignedDataInterval >= 1) { + + if (alignedData.size() >= *MeterValuesAlignedDataMaxLength) { + auto meterValues = new MeterValues(std::move(alignedData), connectorId, -1); + alignedData.clear(); + return meterValues; + } + + auto& timestampNow = context.getOcppTime().getOcppTimestampNow(); + auto dt = nextAlignedTime - timestampNow; + if (dt <= 0 || //normal case: interval elapsed + dt > *ClockAlignedDataInterval) { //special case: clock has been adjusted or first run + + AO_DBG_DEBUG("Clock aligned measurement %ds: %s", dt, + abs(dt) <= 60 ? + "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); abs(-123); + if (abs(dt) <= 60) { //is measurement still "clock-aligned"? + auto alignedMeterValues = alignedDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::SampleClock); + if (alignedMeterValues) { + alignedData.push_back(std::move(alignedMeterValues)); + } + + if (stopTxnAlignedData.size() + 1 < (size_t) (*StopTxnAlignedDataMaxLength)) { + //ensure that collection keeps one free data slot for final value at StopTransaction + auto alignedStopTx = stopTxnAlignedDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::SampleClock); + if (alignedStopTx) { + stopTxnAlignedData.push_back(std::move(alignedStopTx)); + } + } + } + + OcppTimestamp midnightBase = OcppTimestamp(2010,0,0,0,0,0); + auto intervall = timestampNow - midnightBase; + intervall %= 3600 * 24; + OcppTimestamp midnight = timestampNow - intervall; + intervall += *ClockAlignedDataInterval; + if (intervall >= 3600 * 24) { + //next measurement is tomorrow; set to precisely 00:00 + nextAlignedTime = midnight; + nextAlignedTime += 3600 * 24; + } else { + intervall /= *ClockAlignedDataInterval; + nextAlignedTime = midnight + (intervall * *ClockAlignedDataInterval); + } + } + } else { + alignedData.clear(); + stopTxnAlignedData.clear(); } + if (*MeterValueSampleInterval >= 1) { + //record periodic tx data + + if (sampledData.size() >= *MeterValuesSampledDataMaxLength) { + auto meterValues = new MeterValues(std::move(sampledData), connectorId, lastTransactionId); + sampledData.clear(); + return meterValues; + } + + auto connector = context.getConnectorStatus(connectorId); + if (connector && connector->getTransactionId() != lastTransactionId) { + //transaction break + MeterValues *meterValues = nullptr; + if (!sampledData.empty()) { + meterValues = new MeterValues(std::move(sampledData), connectorId, lastTransactionId); + sampledData.clear(); + } + lastTransactionId = connector->getTransactionId(); + lastSampleTime = ao_tick_ms(); + return meterValues; + } + + if (ao_tick_ms() - lastSampleTime >= (ulong) (*MeterValueSampleInterval * 1000)) { + auto sampleMeterValues = sampledDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::SamplePeriodic); + if (sampleMeterValues) { + sampledData.push_back(std::move(sampleMeterValues)); + } + + if (stopTxnSampledData.size() + 1 < (size_t) (*StopTxnSampledDataMaxLength)) { + //ensure that collection keeps one free data slot for final value at StopTransaction + auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::SamplePeriodic); + if (sampleStopTx) { + stopTxnSampledData.push_back(std::move(sampleStopTx)); + } + } + lastSampleTime = ao_tick_ms(); + } - /* - * Is the value buffer already full? If yes, return MeterValues message - */ - if (((int) meterValue.size()) >= (int) *MeterValuesSampledDataMaxLength) { - auto result = toMeterValues(); - return result; + } else { + sampledData.clear(); + stopTxnSampledData.clear(); } return nullptr; //successful method completition. Currently there is no reason to send a MeterValues Msg. } -OcppMessage *ConnectorMeterValuesRecorder::toMeterValues() { - if (meterValue.empty()) { - AO_DBG_DEBUG("Checking if to send MeterValues ... No"); - clear(); - return nullptr; - } else { - auto result = new MeterValues(meterValue, connectorId, lastTransactionId); - clear(); - return result; - } -} - -OcppMessage *ConnectorMeterValuesRecorder::takeMeterValuesNow() { - - if (meterValueSamplers.empty()) { - return nullptr; - } +OcppMessage *ConnectorMeterValuesRecorder::takeTriggeredMeterValues() { - std::unique_ptr value; + auto sample = sampledDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::Trigger); - if (context.getOcppTime().isValid()) { - value.reset(new MeterValue(context.getOcppTime().getOcppTimestampNow())); - } - - if (!value) { + if (!sample) { return nullptr; } - for (auto mvs = meterValueSamplers.begin(); mvs != meterValueSamplers.end(); mvs++) { - value->addSampledValue((*mvs)->takeValue()); - } - int txId_now = -1; auto connector = context.getConnectorStatus(connectorId); if (connector) { txId_now = connector->getTransactionId(); } - decltype(meterValue) mv_now; - mv_now.push_back(std::move(value)); - - return new MeterValues(mv_now, connectorId, txId_now); -} + decltype(sampledData) mv_now; + mv_now.push_back(std::move(sample)); -void ConnectorMeterValuesRecorder::clear() { - meterValue.clear(); + return new MeterValues(std::move(mv_now), connectorId, txId_now); } void ConnectorMeterValuesRecorder::setPowerSampler(PowerSampler ps){ @@ -138,14 +183,17 @@ void ConnectorMeterValuesRecorder::setEnergySampler(EnergySampler es){ } void ConnectorMeterValuesRecorder::addMeterValueSampler(std::unique_ptr meterValueSampler) { - meterValueSamplers.push_back(std::move(meterValueSampler)); + if (!meterValueSampler->getMeasurand().compare("Energy.Active.Import.Register")) { + energySamplerIndex = samplers.size(); + } + samplers.push_back(std::move(meterValueSampler)); } int32_t ConnectorMeterValuesRecorder::readEnergyActiveImportRegister() { - if (energySampler != nullptr) { - return energySampler(); + if (energySamplerIndex >= 0 && energySamplerIndex < samplers.size()) { + return samplers[energySamplerIndex]->takeValue(ReadingContext::NOT_SET)->toInteger(); } else { AO_DBG_DEBUG("Called readEnergyActiveImportRegister(), but no energySampler or handling strategy set"); - return 0.f; + return 0; } } diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h index 1148ee7e..502724d1 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h @@ -30,23 +30,39 @@ class ConnectorMeterValuesRecorder { OcppModel& context; const int connectorId; + + std::vector> sampledData; + std::vector> alignedData; + std::vector> stopTxnSampledData; + std::vector> stopTxnAlignedData; + + std::unique_ptr sampledDataBuilder; + std::unique_ptr alignedDataBuilder; + std::unique_ptr stopTxnSampledDataBuilder; + std::unique_ptr stopTxnAlignedDataBuilder; - std::vector> meterValue; + std::shared_ptr> sampledDataSelect; + std::shared_ptr> alignedDataSelect; + std::shared_ptr> stopTxnSampledDataSelect; + std::shared_ptr> stopTxnAlignedDataSelect; ulong lastSampleTime = 0; //0 means not charging right now + OcppTimestamp nextAlignedTime; float lastPower; int lastTransactionId = -1; PowerSampler powerSampler = nullptr; EnergySampler energySampler = nullptr; - std::vector> meterValueSamplers; + std::vector> samplers; + int energySamplerIndex {-1}; - std::shared_ptr> MeterValueSampleInterval = nullptr; - std::shared_ptr> MeterValuesSampledDataMaxLength = nullptr; + std::shared_ptr> MeterValueSampleInterval; + std::shared_ptr> MeterValuesSampledDataMaxLength; + std::shared_ptr> StopTxnSampledDataMaxLength; - void takeSample(); - OcppMessage *toMeterValues(); - void clear(); + std::shared_ptr> ClockAlignedDataInterval; + std::shared_ptr> MeterValuesAlignedDataMaxLength; + std::shared_ptr> StopTxnAlignedDataMaxLength; public: ConnectorMeterValuesRecorder(OcppModel& context, int connectorId); @@ -60,7 +76,9 @@ class ConnectorMeterValuesRecorder { int32_t readEnergyActiveImportRegister(); - OcppMessage *takeMeterValuesNow(); + OcppMessage *takeTriggeredMeterValues(); + + OcppMessage *getStopTransactionData(); }; } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp new file mode 100644 index 00000000..e46edd1d --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp @@ -0,0 +1,108 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include +#include + +using ArduinoOcpp::MeterValue; +using ArduinoOcpp::MeterValueBuilder; + +MeterValue::MeterValue(const MeterValue& other) { + timestamp = other.timestamp; + for (auto value = other.sampledValue.begin(); value != other.sampledValue.end(); value++) { + sampledValue.push_back(std::unique_ptr((*value)->clone())); + } +} + +std::unique_ptr MeterValue::toJson() { + size_t capacity = 0; + std::vector> entries; + for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) { + auto json = (*sample)->toJson(); + capacity += json->capacity(); + entries.push_back(std::move(json)); + } + + capacity += JSON_ARRAY_SIZE(entries.size()); + capacity += JSONDATE_LENGTH + 1; + capacity += JSON_OBJECT_SIZE(2); + + auto result = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space + auto jsonPayload = result->to(); + + char timestampStr [JSONDATE_LENGTH + 1] = {'\0'}; + if (!timestamp.toJsonString(timestampStr, JSONDATE_LENGTH + 1)) { + return nullptr; + } + jsonPayload["timestamp"] = timestampStr; + auto jsonMeterValue = jsonPayload.createNestedArray("sampledValue"); + for (auto entry = entries.begin(); entry != entries.end(); entry++) { + jsonMeterValue.add(**entry); + } + return std::move(result); +} + +MeterValueBuilder::MeterValueBuilder(const std::vector> &samplers, + std::shared_ptr> samplers_select) : + samplers(samplers), + select(samplers_select) { + + updateObservedSamplers(); + select_observe = select->getValueRevision(); +} + +void MeterValueBuilder::updateObservedSamplers() { + + if (select_mask.size() != samplers.size()) { + select_mask.resize(samplers.size(), false); + select_n = 0; + } + + auto selectStr = select->operator const char *(); + size_t sl = 0, sr = 0; + while (selectStr && sl < select->getBuffsize()) { + while (sr < select->getBuffsize()) { + if (selectStr[sr] == ',') { + break; + } + sr++; + } + + if (sr != sl + 1) { + for (size_t i = 0; i < samplers.size(); i++) { + if (!strncmp(samplers[i]->getMeasurand().c_str(), selectStr + sl, sr - sl)) { + select_mask[i] = true; + select_n++; + } + } + } + + sr++; + sl = sr; + } +} + +std::unique_ptr MeterValueBuilder::takeSample(const OcppTimestamp& timestamp, const ReadingContext& context) { + if (select_observe != select->getValueRevision() || //OCPP server has changed configuration about which measurands to take + samplers.size() != select_mask.size()) { //Client has added another Measurand; synchronize lists + AO_DBG_DEBUG("Updating observed samplers due to config change or samplers added"); + updateObservedSamplers(); + select_observe = select->getValueRevision(); + } + + if (select_n == 0) { + return nullptr; + } + + auto sample = std::unique_ptr(new MeterValue(timestamp)); + + for (size_t i = 0; i < select_mask.size(); i++) { + if (select_mask[i]) { + sample->addSampledValue(samplers[i]->takeValue(context)); + } + } + + return sample; +} diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h index 6945fa8c..d9ba7a2b 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -18,42 +19,27 @@ class MeterValue { std::vector> sampledValue; public: MeterValue(OcppTimestamp timestamp) : timestamp(timestamp) { } - MeterValue(const MeterValue& other) { - timestamp = other.timestamp; - for (auto value = other.sampledValue.begin(); value != other.sampledValue.end(); value++) { - sampledValue.push_back(std::unique_ptr((*value)->clone())); - } - } + MeterValue(const MeterValue& other); void addSampledValue(std::unique_ptr sample) {sampledValue.push_back(std::move(sample));} - std::unique_ptr toJson() { - size_t capacity = 0; - std::vector> entries; - for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) { - auto json = (*sample)->toJson(); - capacity += json->capacity(); - entries.push_back(std::move(json)); - } - - capacity += JSON_ARRAY_SIZE(entries.size()); - capacity += JSONDATE_LENGTH + 1; - capacity += JSON_OBJECT_SIZE(2); - - auto result = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space - auto jsonPayload = result->to(); - - char timestampStr [JSONDATE_LENGTH + 1] = {'\0'}; - if (!timestamp.toJsonString(timestampStr, JSONDATE_LENGTH + 1)) { - return nullptr; - } - jsonPayload["timestamp"] = timestampStr; - auto jsonMeterValue = jsonPayload.createNestedArray("sampledValue"); - for (auto entry = entries.begin(); entry != entries.end(); entry++) { - jsonMeterValue.add(**entry); - } - return std::move(result); - } + std::unique_ptr toJson(); +}; + +class MeterValueBuilder { +private: + const std::vector> &samplers; + std::shared_ptr> select; + std::vector select_mask; + unsigned int select_n {0}; + decltype(select->getValueRevision()) select_observe; + + void updateObservedSamplers(); +public: + MeterValueBuilder(const std::vector> &samplers, + std::shared_ptr> samplers_select); + + std::unique_ptr takeSample(const OcppTimestamp& timestamp, const ReadingContext& context); }; } diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp index c8e95419..97c2a792 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp @@ -61,14 +61,14 @@ int32_t MeteringService::readEnergyActiveImportRegister(int connectorId) { return connectors[connectorId]->readEnergyActiveImportRegister(); } -std::unique_ptr MeteringService::takeMeterValuesNow(int connectorId) { +std::unique_ptr MeteringService::takeTriggeredMeterValues(int connectorId) { if (connectorId < 0 || connectorId >= (int) connectors.size()) { AO_DBG_ERR("connectorId out of bounds. Ignore"); return nullptr; } auto& connector = connectors.at(connectorId); if (connector.get()) { - auto msg = connector->takeMeterValuesNow(); + auto msg = connector->takeTriggeredMeterValues(); if (msg) { auto meterValues = makeOcppOperation(msg); meterValues->setTimeout(std::unique_ptr{new FixedTimeout(120000)}); diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h index 25814b37..f82700a3 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h @@ -38,7 +38,7 @@ class MeteringService { int32_t readEnergyActiveImportRegister(int connectorId); - std::unique_ptr takeMeterValuesNow(int connectorId); //snapshot of all meters now + std::unique_ptr takeTriggeredMeterValues(int connectorId); //snapshot of all meters now int getNumConnectors() {return connectors.size();} }; diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp new file mode 100644 index 00000000..75227b11 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp @@ -0,0 +1,67 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include + +using ArduinoOcpp::SampledValue; + +//helper function +namespace ArduinoOcpp { +namespace Ocpp16 { +const char *cstrFromReadingContext(ReadingContext context) { + switch (context) { + case (ReadingContext::InterruptionBegin): + return "Interruption.Begin"; + case (ReadingContext::InterruptionEnd): + return "Interruption.End"; + case (ReadingContext::Other): + return "Other"; + case (ReadingContext::SampleClock): + return "Sample.Clock"; + case (ReadingContext::SamplePeriodic): + return "Sample.Periodic"; + case (ReadingContext::TransactionBegin): + return "Transaction.Begin"; + case (ReadingContext::TransactionEnd): + return "Transaction.End"; + case (ReadingContext::Trigger): + return "Trigger"; + default: + AO_DBG_ERR("ReadingContext not specified"); + case (ReadingContext::NOT_SET): + return nullptr; + } +} + +}} //end namespaces + +std::unique_ptr SampledValue::toJson() { + auto value = serializeValue(); + size_t capacity = 0; + capacity += JSON_OBJECT_SIZE(8); + capacity += value.length() + 1 + + properties.getFormat().length() + 1 + + properties.getMeasurand().length() + 1 + + properties.getPhase().length() + 1 + + properties.getLocation().length() + 1 + + properties.getUnit().length() + 1; + auto result = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space + auto payload = result->to(); + payload["value"] = value; + auto context_cstr = Ocpp16::cstrFromReadingContext(context); + if (context_cstr) + payload["context"] = context_cstr; + if (!properties.getFormat().empty()) + payload["format"] = properties.getFormat(); + if (!properties.getMeasurand().empty()) + payload["measurand"] = properties.getMeasurand(); + if (!properties.getPhase().empty()) + payload["phase"] = properties.getPhase(); + if (!properties.getLocation().empty()) + payload["location"] = properties.getLocation(); + if (!properties.getUnit().empty()) + payload["unit"] = properties.getUnit(); + return std::move(result); +} diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h index f2a3adb9..c6a4d7ec 100644 --- a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h @@ -39,7 +39,6 @@ class SampledValueProperties { std::string unit; const std::string& getFormat() const {return format;} - const std::string& getMeasurand() const {return measurand;} const std::string& getPhase() const {return phase;} const std::string& getLocation() const {return location;} const std::string& getUnit() const {return unit;} @@ -57,45 +56,35 @@ class SampledValueProperties { void setFormat(const char *format) {this->format = format;} void setMeasurand(const char *measurand) {this->measurand = measurand;} + const std::string& getMeasurand() const {return measurand;} void setPhase(const char *phase) {this->phase = phase;} void setLocation(const char *location) {this->location = location;} void setUnit(const char *unit) {this->unit = unit;} }; +enum class ReadingContext { + InterruptionBegin, + InterruptionEnd, + Other, + SampleClock, + SamplePeriodic, + TransactionBegin, + TransactionEnd, + Trigger, + NOT_SET +}; + class SampledValue { protected: const SampledValueProperties& properties; + const ReadingContext context; virtual std::string serializeValue() = 0; public: - SampledValue(const SampledValueProperties& properties) : properties(properties) { } - SampledValue(const SampledValue& other) : properties(other.properties) { } + 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() { - auto value = serializeValue(); - size_t capacity = 0; - capacity += JSON_OBJECT_SIZE(7); - capacity += value.length() + 1 - + properties.getFormat().length() + 1 - + properties.getMeasurand().length() + 1 - + properties.getPhase().length() + 1 - + properties.getLocation().length() + 1 - + properties.getUnit().length() + 1; - auto result = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space - auto payload = result->to(); - payload["value"] = value; - if (!properties.getFormat().empty()) - payload["format"] = properties.getFormat(); - if (!properties.getMeasurand().empty()) - payload["measurand"] = properties.getMeasurand(); - if (!properties.getPhase().empty()) - payload["phase"] = properties.getPhase(); - if (!properties.getLocation().empty()) - payload["location"] = properties.getLocation(); - if (!properties.getUnit().empty()) - payload["unit"] = properties.getUnit(); - return std::move(result); - } + std::unique_ptr toJson(); virtual std::unique_ptr clone() = 0; @@ -107,7 +96,7 @@ class SampledValueConcrete : public SampledValue { private: const T value; public: - SampledValueConcrete(const SampledValueProperties& properties, const T&& value) : SampledValue(properties), value(value) { } + SampledValueConcrete(const SampledValueProperties& properties, ReadingContext context, const T&& value) : SampledValue(properties, context), value(value) { } SampledValueConcrete(const SampledValueConcrete& other) : SampledValue(other), value(other.value) { } ~SampledValueConcrete() = default; @@ -124,7 +113,8 @@ class SampledValueSampler { public: SampledValueSampler(SampledValueProperties properties) : properties(properties) { } virtual ~SampledValueSampler() = default; - virtual std::unique_ptr takeValue() = 0; + virtual std::unique_ptr takeValue(ReadingContext context) = 0; + const std::string& getMeasurand() {return properties.getMeasurand();}; }; template @@ -133,11 +123,11 @@ class SampledValueSamplerConcrete : public SampledValueSampler { std::function sampler; public: SampledValueSamplerConcrete(SampledValueProperties properties, std::function sampler) : SampledValueSampler(properties), sampler(sampler) { } - std::unique_ptr takeValue() override { - return std::unique_ptr>(new SampledValueConcrete(properties, sampler())); + std::unique_ptr takeValue(ReadingContext context) override { + return std::unique_ptr>(new SampledValueConcrete(properties, context, sampler())); } }; -} +} //end namespace ArduinoOcpp #endif From 0a3d89b7ff4e79561cb2cacc58b8336008978b80 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 8 May 2022 18:52:58 +0200 Subject: [PATCH 012/549] complete C facade definitions --- src/ArduinoOcpp.h | 2 +- src/ArduinoOcpp_c.cpp | 107 +++++++++++++++++++++++++++++++++++++----- src/ArduinoOcpp_c.h | 4 -- 3 files changed, 95 insertions(+), 18 deletions(-) diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index 6fbaec92..18882796 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -30,7 +30,7 @@ void OCPP_initialize(const char *CS_hostname, uint16_t CS_port, const char *CS_u #endif //Lets you use your own WebSocket implementation -void OCPP_initialize(ArduinoOcpp::OcppSocket& ocppSocket, float V_eff = 230.f /*German grid*/, ArduinoOcpp::FilesystemOpt fsOpt = ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail, ArduinoOcpp::OcppClock system_time = ArduinoOcpp::Clocks::DEFAULT_CLOCK); +void OCPP_initialize(ArduinoOcpp::OcppSocket& ocppSocket, float V_eff = 230.f /*European grid*/, ArduinoOcpp::FilesystemOpt fsOpt = ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail, ArduinoOcpp::OcppClock system_time = ArduinoOcpp::Clocks::DEFAULT_CLOCK); //experimental; More testing required (help needed: it would be awesome if you can you publish your evaluation results on the GitHub page) void OCPP_deinitialize(); diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 7b50aa69..b44dceb4 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -10,6 +10,7 @@ extern "C" void ao_initialize(AOcppSocket *osock) { if (!osock) { AO_DBG_ERR("osock is null"); } + AO_DBG_ERR("no error"); ocppSocket = reinterpret_cast(osock); @@ -30,7 +31,7 @@ extern "C" void ao_set_console_out_c(void (*console_out)(const char *msg)) { char ao_recv_payload_buff [AO_RECEIVE_PAYLOAD_BUFSIZE] = {'\0'}; -std::function wrapCstyleOcppCb(OnOcppMessage cb) { +std::function adaptCb(OnOcppMessage cb) { return [cb] (JsonObject payload) { auto len = serializeJson(payload, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); if (len <= 0) { @@ -40,11 +41,11 @@ std::function wrapCstyleOcppCb(OnOcppMessage cb) { }; } -std::function wrapCstyleOcppCb(void (*cb)()) { +std::function adaptCb(void (*cb)()) { return cb; } -ArduinoOcpp::OnReceiveErrorListener wrapCstyleOcppCb(OnOcppError cb) { +ArduinoOcpp::OnReceiveErrorListener adaptCb(OnOcppError cb) { return [cb] (const char *code, const char *description, JsonObject details) { auto len = serializeJson(details, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); if (len <= 0) { @@ -54,18 +55,98 @@ ArduinoOcpp::OnReceiveErrorListener wrapCstyleOcppCb(OnOcppError cb) { }; } -extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { - bootNotification("model", "vendor", wrapCstyleOcppCb(onConfirmation), wrapCstyleOcppCb(onAbort), wrapCstyleOcppCb(onTimeout), wrapCstyleOcppCb(onError)); +std::function adaptCb(SamplerBool cb) { + return cb; +} + +std::function adaptCb(SamplerString cb) { + return cb; +} + +void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy) { + setEvRequestsEnergySampler(adaptCb(evRequestsEnergy)); +} + +void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized) { + setConnectorEnergizedSampler(adaptCb(connectorEnergized)); +} + +void ao_setConnectorPluggedSampler(SamplerBool connectorPlugged) { + setConnectorPluggedSampler(adaptCb(connectorPlugged)); +} + +void ao_addConnectorErrorCodeSampler(SamplerString connectorErrorCode) { + addConnectorErrorCodeSampler(adaptCb(connectorErrorCode)); +} + +void ao_onChargingRateLimitChange(void (*chargingRateChanged)(float)) { + setOnChargingRateLimitChange(chargingRateChanged); +} + +void ao_onUnlockConnector(SamplerBool unlockConnector) { + setOnUnlockConnector(adaptCb(unlockConnector)); +} + +void ao_onRemoteStartTransactionSendConf(OnOcppMessage onSendConf) { + setOnRemoteStopTransactionSendConf(adaptCb(onSendConf)); +} + +void ao_onRemoteStopTransactionSendConf(OnOcppMessage onSendConf) { + setOnRemoteStopTransactionSendConf(adaptCb(onSendConf)); +} + +void ao_onRemoteStopTransactionRequest(OnOcppMessage onRequest) { + setOnRemoteStopTransactionReceiveReq(adaptCb(onRequest)); +} + +void ao_onResetSendConf(OnOcppMessage onSendConf) { + setOnResetSendConf(adaptCb(onSendConf)); } extern "C" void ao_onResetRequest(OnOcppMessage onRequest) { - OnReceiveReqListener cb = [onRequest] (JsonObject payload) { - auto len = serializeJson(payload, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); - if (len <= 0) { - AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); - } - onRequest(len > 0 ? ao_recv_payload_buff : nullptr, len); - }; - setOnResetReceiveReq(cb); + setOnResetReceiveReq(adaptCb(onRequest)); +} + +extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + bootNotification("model", "vendor", adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + authorize(idTag, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + startTransaction(idTag, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +void ao_stopTransaction(OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + stopTransaction(adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); } +int ao_getTransactionId() { + return getTransactionId(); +} + +bool ao_ocppPermitsCharge() { + return ocppPermitsCharge(); +} + +bool ao_isAvailable() { + return isAvailable(); +} + +void ao_beginSession(const char *idTag) { + return beginSession(idTag); +} + +void ao_endSession() { + return endSession(); +} + +bool ao_isInSession() { + return isInSession(); +} + +const char *ao_getSessionIdTag() { + return getSessionIdTag(); +} diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index c44db5fc..b36af90a 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -42,8 +42,6 @@ void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized); void ao_setConnectorPluggedSampler(SamplerBool connectorPlugged); -//void setConnectorFaultedSampler(SamplerBool connectorFailed); - void ao_addConnectorErrorCodeSampler(SamplerString connectorErrorCode); /* @@ -58,8 +56,6 @@ void ao_onUnlockConnector(SamplerBool unlockConnector); //true: success, false: * Generic listeners for OCPP operations initiated by Central System */ -void ao_onSetChargingProfileRequest(OnOcppMessage onRequest); //optional - void ao_onRemoteStartTransactionSendConf(OnOcppMessage onSendConf); //important, energize the power plug here and capture the idTag void ao_onRemoteStopTransactionSendConf(OnOcppMessage onSendConf); //important, de-energize the power plug here From c22495379dbcba7a2a0e06ed4e74a5e68466cafc Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 18 May 2022 16:40:26 +0200 Subject: [PATCH 013/549] Async meter measurments and connector unlock --- src/ArduinoOcpp.cpp | 8 +-- src/ArduinoOcpp.h | 3 +- src/ArduinoOcpp/Core/OcppOperation.cpp | 15 +++--- src/ArduinoOcpp/Core/PollResult.h | 51 +++++++++++++++++++ src/ArduinoOcpp/MessagesV16/MeterValues.cpp | 3 ++ .../MessagesV16/StartTransaction.cpp | 10 +++- .../MessagesV16/StartTransaction.h | 3 +- .../MessagesV16/StopTransaction.cpp | 11 +++- src/ArduinoOcpp/MessagesV16/StopTransaction.h | 3 +- .../MessagesV16/UnlockConnector.cpp | 25 +++++---- src/ArduinoOcpp/MessagesV16/UnlockConnector.h | 5 +- .../ChargePointStatus/ConnectorStatus.cpp | 4 +- .../Tasks/ChargePointStatus/ConnectorStatus.h | 7 +-- .../Metering/ConnectorMeterValuesRecorder.cpp | 12 ++--- .../Metering/ConnectorMeterValuesRecorder.h | 2 +- src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp | 10 ++-- src/ArduinoOcpp/Tasks/Metering/MeterValue.h | 2 +- .../Tasks/Metering/MeteringService.cpp | 4 +- .../Tasks/Metering/MeteringService.h | 2 +- .../Tasks/Metering/SampledValue.cpp | 3 ++ src/ArduinoOcpp/Tasks/Metering/SampledValue.h | 33 +++++++----- 21 files changed, 151 insertions(+), 65 deletions(-) create mode 100644 src/ArduinoOcpp/Core/PollResult.h diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index b32ae275..d8ffe4d1 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -174,7 +174,7 @@ void setPowerActiveImportSampler(std::function power) { auto mvs = std::unique_ptr>>( new SampledValueSamplerConcrete>( meterProperties, - power + [power] (ReadingContext) {return power();} )); model.getMeteringService()->addMeterValueSampler(OCPP_ID_OF_CONNECTOR, std::move(mvs)); //connectorId=1 model.getMeteringService()->setPowerSampler(OCPP_ID_OF_CONNECTOR, power); @@ -195,7 +195,9 @@ void setEnergyActiveImportSampler(std::function energy) { meterProperties.setUnit("Wh"); auto mvs = std::unique_ptr>>( new SampledValueSamplerConcrete>( - meterProperties, energy)); + meterProperties, + [energy] (ReadingContext) {return energy();} + )); model.getMeteringService()->addMeterValueSampler(OCPP_ID_OF_CONNECTOR, std::move(mvs)); //connectorId=1 model.getMeteringService()->setEnergySampler(OCPP_ID_OF_CONNECTOR, energy); } @@ -278,7 +280,7 @@ void setOnChargingRateLimitChange(std::function chargingRateChanged model.getSmartChargingService()->setOnLimitChange(chargingRateChanged); } -void setOnUnlockConnector(std::function unlockConnector) { +void setOnUnlockConnector(std::function()> unlockConnector) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index 59953387..15b3ee91 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -14,6 +14,7 @@ #include #include #include +#include #include using ArduinoOcpp::OnReceiveConfListener; @@ -76,7 +77,7 @@ void addConnectorErrorCodeSampler(std::function connectorErrorCo void setOnChargingRateLimitChange(std::function chargingRateChanged); -void setOnUnlockConnector(std::function unlockConnector); //true: success, false: failure +void setOnUnlockConnector(std::function()> unlockConnector); //true: success, false: failure /* * React on CS-initiated operations diff --git a/src/ArduinoOcpp/Core/OcppOperation.cpp b/src/ArduinoOcpp/Core/OcppOperation.cpp index 52a9f7e2..9f651caa 100644 --- a/src/ArduinoOcpp/Core/OcppOperation.cpp +++ b/src/ArduinoOcpp/Core/OcppOperation.cpp @@ -104,8 +104,7 @@ boolean OcppOperation::sendReq(OcppSocket& ocppSocket){ */ auto requestPayload = ocppMessage->createReq(); if (!requestPayload) { - onAbortListener(); - return true; + return false; } /* @@ -240,12 +239,16 @@ boolean OcppOperation::sendConf(OcppSocket& ocppSocket){ * Create the OCPP message */ std::unique_ptr confJson = nullptr; - std::unique_ptr confPayload = std::unique_ptr(ocppMessage->createConf()); + std::unique_ptr confPayload = ocppMessage->createConf(); std::unique_ptr errorDetails = nullptr; - bool operationSuccess = ocppMessage->getErrorCode() == nullptr && confPayload != nullptr; + bool operationFailure = ocppMessage->getErrorCode() != nullptr; + + if (!operationFailure && !confPayload) { + return false; //confirmation message still pending + } - if (operationSuccess) { + if (!operationFailure) { /* * Create OCPP-J Remote Procedure Call header @@ -293,7 +296,7 @@ boolean OcppOperation::sendConf(OcppSocket& ocppSocket){ boolean wsSuccess = ocppSocket.sendTXT(out); if (wsSuccess) { - if (operationSuccess) { + if (!operationFailure) { AO_DBG_TRAFFIC_OUT(out.c_str()); onSendConfListener(confPayload->as()); } else { diff --git a/src/ArduinoOcpp/Core/PollResult.h b/src/ArduinoOcpp/Core/PollResult.h new file mode 100644 index 00000000..a21324f8 --- /dev/null +++ b/src/ArduinoOcpp/Core/PollResult.h @@ -0,0 +1,51 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef POLLRESULT_H +#define POLLRESULT_H + +#include +#include + +namespace ArduinoOcpp { + +template +class PollResult { +private: + bool ready; + T value; +public: + PollResult() : ready(false) {} + PollResult(T&& value) : ready(true), value(value) {} + PollResult(const PollResult&) = delete; + PollResult& operator =(const PollResult&) = delete; + PollResult& operator =(const PollResult&& other) { + ready = other.ready; + value = std::move(other.value); + return *this; + } + PollResult(PollResult&& other) : ready(other.ready), value(std::move(other.value)) {} + T&& toValue() { + if (!ready) { + AO_DBG_ERR("Not ready"); + (void)0; + } + ready = false; + return std::move(value); + } + T& getValue() const { + if (!ready) { + AO_DBG_ERR("Not ready"); + (void)0; + } + return *value; + } + operator bool() const {return ready;} + + static PollResult Await() {return PollResult();} +}; + +} + +#endif diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp index 97863a93..87559f96 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp @@ -35,6 +35,9 @@ std::unique_ptr MeterValues::createReq() { std::vector> entries; for (auto value = meterValue.begin(); value != meterValue.end(); value++) { auto entry = (*value)->toJson(); + if (!entry) { + return nullptr; + } capacity += entry->capacity(); entries.push_back(std::move(entry)); } diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp index 339aa8b1..f7f3ec57 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp @@ -67,12 +67,18 @@ void StartTransaction::initiate() { } std::unique_ptr StartTransaction::createReq() { + + if (meterStart && !*meterStart) { + //meterStart not ready yet + return nullptr; + } + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(5) + (JSONDATE_LENGTH + 1) + (IDTAG_LEN_MAX + 1))); JsonObject payload = doc->to(); payload["connectorId"] = connectorId; - if (meterStart >= 0) { - payload["meterStart"] = meterStart; + if (meterStart && *meterStart) { + payload["meterStart"] = meterStart->toInteger(); } if (otimestamp > MIN_TIME) { diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.h b/src/ArduinoOcpp/MessagesV16/StartTransaction.h index 6390a0f1..153da753 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace ArduinoOcpp { namespace Ocpp16 { @@ -15,7 +16,7 @@ namespace Ocpp16 { class StartTransaction : public OcppMessage { private: int connectorId = 1; - int32_t meterStart = -1; + std::unique_ptr meterStart {nullptr}; OcppTimestamp otimestamp; char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; uint16_t transactionRev = 0; diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp index 1f5c002c..6466a85c 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp @@ -46,11 +46,18 @@ void StopTransaction::initiate() { } std::unique_ptr StopTransaction::createReq() { + + if (meterStop && !*meterStop) { + //meterStop not ready yet + return nullptr; + } + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(5) + (JSONDATE_LENGTH + 1) + (REASON_LEN_MAX + 1))); JsonObject payload = doc->to(); - if (meterStop >= 0) - payload["meterStop"] = meterStop; //TODO meterStart is required to be in Wh, but measuring unit is probably inconsistent in implementation + if (meterStop && *meterStop) { + payload["meterStop"] = meterStop->toInteger(); + } if (otimestamp > MIN_TIME) { char timestamp[JSONDATE_LENGTH + 1] = {'\0'}; diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.h b/src/ArduinoOcpp/MessagesV16/StopTransaction.h index 6268b8f4..08a91957 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace ArduinoOcpp { namespace Ocpp16 { @@ -15,7 +16,7 @@ namespace Ocpp16 { class StopTransaction : public OcppMessage { private: int connectorId = 1; - int32_t meterStop = -1; + std::unique_ptr meterStop {nullptr}; OcppTimestamp otimestamp; char reason [REASON_LEN_MAX] {'\0'}; public: diff --git a/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp b/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp index a7418c44..e883c89c 100644 --- a/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp +++ b/src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp @@ -19,7 +19,7 @@ const char* UnlockConnector::getOcppOperationType(){ void UnlockConnector::processReq(JsonObject payload) { - int connectorId = payload["connectorId"] | -1; + auto connectorId = payload["connectorId"] | -1; if (!ocppModel || !ocppModel->getConnectorStatus(connectorId)) { err = true; @@ -30,24 +30,29 @@ void UnlockConnector::processReq(JsonObject payload) { connector->endSession("UnlockCommand"); - std::function unlockConnector = connector->getOnUnlockConnector(); + unlockConnector = connector->getOnUnlockConnector(); if (unlockConnector != nullptr) { - cbDefined = true; + cbUnlockResult = unlockConnector(); } else { - cbDefined = false; AO_DBG_WARN("Unlock CB undefined"); - return; } - - cbUnlockSuccessful = unlockConnector(); } -std::unique_ptr UnlockConnector::createConf(){ +std::unique_ptr UnlockConnector::createConf() { + if (unlockConnector) { + if (!cbUnlockResult) { + cbUnlockResult = unlockConnector(); + if (!cbUnlockResult) { + return nullptr; //no result yet - delay confirmation response + } + } + } + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); JsonObject payload = doc->to(); - if (err || !cbDefined) { + if (err || !unlockConnector) { payload["status"] = "NotSupported"; - } else if (cbUnlockSuccessful) { + } else if (cbUnlockResult.toValue()) { payload["status"] = "Unlocked"; } else { payload["status"] = "UnlockFailed"; diff --git a/src/ArduinoOcpp/MessagesV16/UnlockConnector.h b/src/ArduinoOcpp/MessagesV16/UnlockConnector.h index d65dc559..2659fd2b 100644 --- a/src/ArduinoOcpp/MessagesV16/UnlockConnector.h +++ b/src/ArduinoOcpp/MessagesV16/UnlockConnector.h @@ -6,6 +6,7 @@ #define UNLOCKCONNECTOR_H #include +#include namespace ArduinoOcpp { namespace Ocpp16 { @@ -13,8 +14,8 @@ namespace Ocpp16 { class UnlockConnector : public OcppMessage { private: bool err = false; - bool cbDefined = false; - bool cbUnlockSuccessful = false; + std::function ()> unlockConnector; + PollResult cbUnlockResult; public: UnlockConnector(); diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index 6d27b36d..185ff4b5 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -315,10 +315,10 @@ void ConnectorStatus::saveState() { configuration_save(); } -void ConnectorStatus::setOnUnlockConnector(std::function unlockConnector) { +void ConnectorStatus::setOnUnlockConnector(std::function()> unlockConnector) { this->onUnlockConnector = unlockConnector; } -std::function ConnectorStatus::getOnUnlockConnector() { +std::function()> ConnectorStatus::getOnUnlockConnector() { return this->onUnlockConnector; } diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h index fb9d3ff0..8c9a8962 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -56,7 +57,7 @@ class ConnectorStatus { //std::function()> startTransactionBehavior; //std::function(const char* stopReason)> stopTransactionBehavior; - std::function onUnlockConnector {nullptr}; + std::function()> onUnlockConnector {nullptr}; std::shared_ptr> stopTransactionOnInvalidId; std::shared_ptr> stopTransactionOnEVSideDisconnect; @@ -100,8 +101,8 @@ class ConnectorStatus { bool ocppPermitsCharge(); - void setOnUnlockConnector(std::function unlockConnector); - std::function getOnUnlockConnector(); + void setOnUnlockConnector(std::function()> unlockConnector); + std::function()> getOnUnlockConnector(); }; } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index ffc6b91f..56620cd5 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -50,10 +50,10 @@ ConnectorMeterValuesRecorder::ConnectorMeterValuesRecorder(OcppModel& context, i ); StopTxnAlignedDataMaxLength = declareConfiguration("StopTxnAlignedDataMaxLength", 4, CONFIGURATION_VOLATILE, false, true, false, false); - sampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, MeterValuesSampledData)); AO_DBG_DEBUG("After MeterValuesSampledData"); - alignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, MeterValuesAlignedData)); AO_DBG_DEBUG("After MeterValuesAlignedData"); - stopTxnSampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, StopTxnSampledData)); AO_DBG_DEBUG("After StopTxnSampledData"); - stopTxnAlignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, StopTxnAlignedData)); AO_DBG_DEBUG("After StopTxnAlignedData"); + sampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, MeterValuesSampledData)); + alignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, MeterValuesAlignedData)); + stopTxnSampledDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, StopTxnSampledData)); + stopTxnAlignedDataBuilder = std::unique_ptr(new MeterValueBuilder(samplers, StopTxnAlignedData)); } OcppMessage *ConnectorMeterValuesRecorder::loop() { @@ -189,9 +189,9 @@ void ConnectorMeterValuesRecorder::addMeterValueSampler(std::unique_ptr ConnectorMeterValuesRecorder::readEnergyActiveImportRegister() { if (energySamplerIndex >= 0 && energySamplerIndex < samplers.size()) { - return samplers[energySamplerIndex]->takeValue(ReadingContext::NOT_SET)->toInteger(); + return samplers[energySamplerIndex]->takeValue(ReadingContext::NOT_SET); } else { AO_DBG_DEBUG("Called readEnergyActiveImportRegister(), but no energySampler or handling strategy set"); return 0; diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h index 502724d1..a145438b 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h @@ -74,7 +74,7 @@ class ConnectorMeterValuesRecorder { void addMeterValueSampler(std::unique_ptr meterValueSampler); - int32_t readEnergyActiveImportRegister(); + std::unique_ptr readEnergyActiveImportRegister(); OcppMessage *takeTriggeredMeterValues(); diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp index e46edd1d..27fe8904 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp @@ -9,18 +9,14 @@ using ArduinoOcpp::MeterValue; using ArduinoOcpp::MeterValueBuilder; -MeterValue::MeterValue(const MeterValue& other) { - timestamp = other.timestamp; - for (auto value = other.sampledValue.begin(); value != other.sampledValue.end(); value++) { - sampledValue.push_back(std::unique_ptr((*value)->clone())); - } -} - std::unique_ptr MeterValue::toJson() { size_t capacity = 0; std::vector> entries; 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)); } diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h index d9ba7a2b..450f5d5b 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h @@ -19,7 +19,7 @@ class MeterValue { std::vector> sampledValue; public: MeterValue(OcppTimestamp timestamp) : timestamp(timestamp) { } - MeterValue(const MeterValue& other); + MeterValue(const MeterValue& other) = delete; void addSampledValue(std::unique_ptr sample) {sampledValue.push_back(std::move(sample));} diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp index 97c2a792..c5522f78 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp @@ -53,10 +53,10 @@ void MeteringService::addMeterValueSampler(int connectorId, std::unique_ptraddMeterValueSampler(std::move(meterValueSampler)); } -int32_t MeteringService::readEnergyActiveImportRegister(int connectorId) { +std::unique_ptr MeteringService::readEnergyActiveImportRegister(int connectorId) { if (connectorId < 0 || connectorId >= connectors.size()) { AO_DBG_ERR("connectorId is out of bounds"); - return 0.f; + return nullptr; } return connectors[connectorId]->readEnergyActiveImportRegister(); } diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h index f82700a3..4b51defe 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h @@ -36,7 +36,7 @@ class MeteringService { void addMeterValueSampler(int connectorId, std::unique_ptr meterValueSampler); - int32_t readEnergyActiveImportRegister(int connectorId); + std::unique_ptr readEnergyActiveImportRegister(int connectorId); std::unique_ptr takeTriggeredMeterValues(int connectorId); //snapshot of all meters now diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp index 75227b11..4a570829 100644 --- a/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp @@ -39,6 +39,9 @@ const char *cstrFromReadingContext(ReadingContext context) { 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 diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h index c6a4d7ec..7debf013 100644 --- a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h @@ -14,20 +14,22 @@ template class SampledValueDeSerializer { public: static T deserialize(const char *str); - static std::string serialize(const T& val); - static int32_t toInteger(const T& val); + static bool ready(T& val); + static std::string serialize(T& val); + static int32_t toInteger(T& val); }; template <> -class SampledValueDeSerializer { +class SampledValueDeSerializer { // example class public: - static int32_t deserialize(const char *str) {return 42;} - static std::string serialize(const int32_t& val) { + static int32_t deserialize(const char *str) {return strtol(str, nullptr,10);} + static bool ready(int32_t& val) {return true;} //int32_t is always valid + static std::string serialize(int32_t& val) { char str [12] = {'\0'}; snprintf(str, 12, "%d", val); return std::string(str); } - static int32_t toInteger(const int32_t& val) {return val;} + static int32_t toInteger(int32_t& val) {return val;} }; class SampledValueProperties { @@ -74,6 +76,10 @@ enum class ReadingContext { NOT_SET }; +namespace Ocpp16 { +const char *cstrFromReadingContext(ReadingContext context); +} + class SampledValue { protected: const SampledValueProperties& properties; @@ -86,23 +92,22 @@ class SampledValue { std::unique_ptr toJson(); - virtual std::unique_ptr clone() = 0; - + virtual operator bool() = 0; virtual int32_t toInteger() = 0; }; template class SampledValueConcrete : public SampledValue { private: - const T value; + T value; public: SampledValueConcrete(const SampledValueProperties& properties, ReadingContext context, const T&& value) : SampledValue(properties, context), value(value) { } SampledValueConcrete(const SampledValueConcrete& other) : SampledValue(other), value(other.value) { } ~SampledValueConcrete() = default; - std::string serializeValue() override {return DeSerializer::serialize(value);} + operator bool() override {return DeSerializer::ready(value);} - std::unique_ptr clone() override {return std::unique_ptr>(new SampledValueConcrete(*this));} + std::string serializeValue() override {return DeSerializer::serialize(value);} int32_t toInteger() override { return DeSerializer::toInteger(value);} }; @@ -120,11 +125,11 @@ class SampledValueSampler { template class SampledValueSamplerConcrete : public SampledValueSampler { private: - std::function sampler; + std::function sampler; public: - SampledValueSamplerConcrete(SampledValueProperties properties, std::function sampler) : SampledValueSampler(properties), sampler(sampler) { } + SampledValueSamplerConcrete(SampledValueProperties properties, std::function sampler) : SampledValueSampler(properties), sampler(sampler) { } std::unique_ptr takeValue(ReadingContext context) override { - return std::unique_ptr>(new SampledValueConcrete(properties, context, sampler())); + return std::unique_ptr>(new SampledValueConcrete(properties, context, sampler(context))); } }; From 9ef142b334b30328249ccbe8805fafa11e2c4611 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 19 May 2022 16:23:48 +0200 Subject: [PATCH 014/549] Continuous Integration --- .github/workflows/pio.yaml | 47 +++++++++++++++++++ README.md | 3 ++ .../Core/ConfigurationContainerFlash.cpp | 12 ++--- 3 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/pio.yaml diff --git a/.github/workflows/pio.yaml b/.github/workflows/pio.yaml new file mode 100644 index 00000000..ab6248b7 --- /dev/null +++ b/.github/workflows/pio.yaml @@ -0,0 +1,47 @@ +name: PlatformIO CI + +on: + push: + branches: + - develop + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + example: [examples/ESP/main.cpp, examples/ESP-TLS/main.cpp, examples/SECC/main.cpp] + include: + - example: examples/SECC/main.cpp + dashboard-extra: --lib="/tmp/tzapu/WiFiManager" + + steps: + - uses: actions/checkout@v2 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Cache PlatformIO + uses: actions/cache@v2 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + - name: Install library dependencies + run: pio pkg install + - name: Extra dependencies for SECC example + if: ${{ matrix.dashboard-extra }} + run: git clone https://github.com/tzapu/WiFiManager.git /tmp/tzapu/WiFiManager + - name: Run PlatformIO + run: pio ci --lib="." --project-conf=platformio.ini ${{ matrix.dashboard-extra }} + env: + PLATFORMIO_CI_SRC: ${{ matrix.example }} \ No newline at end of file diff --git a/README.md b/README.md index e485854a..44b2988e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # Icon   ArduinoOcpp + +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/matth-x/ArduinoOcpp/PlatformIO%20CI?logo=github)](https://github.com/matth-x/ArduinoOcpp/actions) + OCPP-J 1.6 client for the ESP8266 and the ESP32 (more coming soon) Reference usage: [OpenEVSE](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/ocpp.cpp) diff --git a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp index d10adbf3..f8740bff 100644 --- a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp +++ b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp @@ -5,18 +5,12 @@ #include #include -#if defined(ESP32) +#if defined(ESP32) && !defined(AO_DEACTIVATE_FLASH) +#include #define USE_FS LITTLEFS #else -#define USE_FS SPIFFS -#endif - -#if USE_FS == LITTLEFS -#include -#elif USE_FS == SPIFFS #include -#else -#error "FS not supported" +#define USE_FS SPIFFS #endif #define MAX_FILE_SIZE 4000 From 786bc975c52248287f4fef6d3fc64e62217e9424 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 26 May 2022 20:59:31 +0200 Subject: [PATCH 015/549] Decoupled filesystem driver --- CMakeLists.txt | 8 +- src/ArduinoOcpp.cpp | 6 +- src/ArduinoOcpp/Core/Configuration.cpp | 68 ++--- src/ArduinoOcpp/Core/Configuration.h | 7 +- .../Core/ConfigurationContainerFlash.cpp | 180 ++++++------ .../Core/ConfigurationContainerFlash.h | 7 +- src/ArduinoOcpp/Core/FilesystemAdapter.cpp | 274 ++++++++++++++++++ src/ArduinoOcpp/Core/FilesystemAdapter.h | 103 +++++++ src/ArduinoOcpp/Core/OcppConnection.cpp | 2 +- .../MessagesV16/GetConfiguration.cpp | 6 +- 10 files changed, 520 insertions(+), 141 deletions(-) create mode 100644 src/ArduinoOcpp/Core/FilesystemAdapter.cpp create mode 100644 src/ArduinoOcpp/Core/FilesystemAdapter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 210f3d11..f0307395 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,13 +49,17 @@ set(AO_SRC src/ArduinoOcpp.cpp src/ArduinoOcpp_c.cpp src/ao_opts_impl.c + src/ArduinoOcpp/Core/FilesystemAdapter.cpp ) idf_component_register(SRCS ${AO_SRC} - INCLUDE_DIRS "./src" "${PROJECT_DIR}/include") + INCLUDE_DIRS "./src" "${PROJECT_DIR}/include" + PRIV_REQUIRES spiffs) target_compile_options(${COMPONENT_TARGET} PUBLIC -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE -DAO_DEACTIVATE_FLASH - -DAO_DBG_LEVEL=AO_DL_DEBUG) + -DAO_USE_FILEAPI=ESPIDF_SPIFFS + -DAO_DBG_LEVEL=AO_DL_DEBUG + -DAO_TRAFFIC_OUT) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index e90985cb..c96f7985 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -83,8 +84,11 @@ void OCPP_initialize(OcppSocket& ocppSocket, float V_eff, ArduinoOcpp::Filesyste voltage_eff = V_eff; fileSystemOpt = fsOpt; + + std::shared_ptr filesystem = EspWiFi::makeDefaultFilesystemAdapter(fileSystemOpt); + AO_DBG_DEBUG("filesystem %s", filesystem ? "loaded" : "error"); - configuration_init(fileSystemOpt); //call before each other library call + configuration_init(filesystem); //call before each other library call ocppEngine = new OcppEngine(ocppSocket, system_time); auto& model = ocppEngine->getOcppModel(); diff --git a/src/ArduinoOcpp/Core/Configuration.cpp b/src/ArduinoOcpp/Core/Configuration.cpp index cbd75a0a..4dfe8bde 100644 --- a/src/ArduinoOcpp/Core/Configuration.cpp +++ b/src/ArduinoOcpp/Core/Configuration.cpp @@ -12,7 +12,7 @@ namespace ArduinoOcpp { -FilesystemOpt configurationFilesystemOpt = FilesystemOpt::Use_Mount_FormatOnFail; +std::shared_ptr filesystem; template std::shared_ptr> createConfiguration(const char *key, T value) { @@ -46,16 +46,16 @@ std::shared_ptr> createConfiguration(const char *key return configuration; } -std::shared_ptr createConfigurationContainer(const char *filename) { +std::unique_ptr createConfigurationContainer(const char *filename) { //create non-persistent Configuration store (i.e. lives only in RAM) if // - Flash FS usage is switched off OR // - Filename starts with "/volatile" - if (!configurationFilesystemOpt.accessAllowed() || + if (!filesystem || !strncmp(filename, CONFIGURATION_VOLATILE, strlen(CONFIGURATION_VOLATILE))) { - return std::static_pointer_cast(std::make_shared(filename)); + return std::unique_ptr(new ConfigurationContainerVolatile(filename)); } else { - //create persistent Configuration store. This is the normal case - return std::static_pointer_cast(std::make_shared(filename)); + //create persistent Configuration store. This is the normal caseS + return std::unique_ptr(new ConfigurationContainerFlash(filesystem, filename)); } } @@ -154,8 +154,10 @@ std::shared_ptr getConfiguration(const char *key) { return nullptr; } -std::shared_ptr>> getAllConfigurations() { //TODO maybe change to iterator? - auto result = std::make_shared>>(); +std::unique_ptr>> getAllConfigurations() { //TODO maybe change to iterator? + auto result = std::unique_ptr>>( + new std::vector>() + ); for (auto container = configurationContainers.begin(); container != configurationContainers.end(); container++) { for (auto config = (*container)->configurationsIteratorBegin(); config != (*container)->configurationsIteratorEnd(); config++) { @@ -172,33 +174,17 @@ std::shared_ptr>> getAllConfi bool configuration_inited = false; -bool configuration_init(FilesystemOpt fsOpt) { +bool configuration_init(std::shared_ptr _filesystem) { if (configuration_inited) return true; //configuration_init() already called; tolerate multiple calls so user can use this store for //credentials outside ArduinoOcpp which need to be loaded before OCPP_initialize() - bool loadRoutineSuccessful = true; -#ifndef AO_DEACTIVATE_FLASH - - configurationFilesystemOpt = fsOpt; + + filesystem = _filesystem; - if (fsOpt.mustMount()) { -#if defined(ESP32) - if(!LITTLEFS.begin(fsOpt.formatOnFail())) { - AO_DBG_ERR("Error while mounting LITTLEFS"); - loadRoutineSuccessful = false; - } -#else - //ESP8266 - SPIFFSConfig cfg; - cfg.setAutoFormat(fsOpt.formatOnFail()); - SPIFFS.setConfig(cfg); - - if (!SPIFFS.begin()) { - AO_DBG_ERR("Unable to initialize: unable to mount SPIFFS"); - loadRoutineSuccessful = false; - } -#endif - } //end fs mount + if (!filesystem) { + configuration_inited = true; + return true; //no filesystem, nothing can go wrong + } std::shared_ptr containerDefault = nullptr; for (auto container = configurationContainers.begin(); container != configurationContainers.end(); container++) { @@ -208,29 +194,28 @@ bool configuration_init(FilesystemOpt fsOpt) { } } + bool success = true; + if (containerDefault) { - AO_DBG_DEBUG("Found default container before calling configuration_init(). If you added\n" \ - " the container manually, please ensure to call load(). If not, it is a hint\n" \ - " that declareConfiguration() was called too early\n"); + AO_DBG_DEBUG("Found default container before calling configuration_init(). If you added"); + AO_DBG_DEBUG(" > the container manually, please ensure to call load(). If not, it is a hint"); + AO_DBG_DEBUG(" > that declareConfiguration() was called too early"); + (void)0; } else { containerDefault = createConfigurationContainer(CONFIGURATION_FN); if (!containerDefault->load()) { AO_DBG_ERR("Loading default configurations file failed"); - loadRoutineSuccessful = false; + success = false; } configurationContainers.push_back(containerDefault); } - -#endif //ndef AO_DEACTIVATE_FLASH - configuration_inited = loadRoutineSuccessful; - return loadRoutineSuccessful; + configuration_inited = success; + return success; } bool configuration_save() { bool success = true; -#ifndef AO_DEACTIVATE_FLASH - for (auto container = configurationContainers.begin(); container != configurationContainers.end(); container++) { if (!(*container)->save()) { @@ -238,7 +223,6 @@ bool configuration_save() { } } -#endif //ndef AO_DEACTIVATE_FLASH return success; } diff --git a/src/ArduinoOcpp/Core/Configuration.h b/src/ArduinoOcpp/Core/Configuration.h index 4097bc1a..e7d9f911 100644 --- a/src/ArduinoOcpp/Core/Configuration.h +++ b/src/ArduinoOcpp/Core/Configuration.h @@ -8,11 +8,12 @@ #include #include #include +#include #include #include -#define CONFIGURATION_FN "/arduino-ocpp.cnf" +#define CONFIGURATION_FN (AO_FILENAME_PREFIX "/arduino-ocpp.cnf") #define CONFIGURATION_VOLATILE "/volatile" namespace ArduinoOcpp { @@ -28,10 +29,10 @@ std::vector>::iterator getConfigurationC namespace Ocpp16 { std::shared_ptr getConfiguration(const char *key); - std::shared_ptr>> getAllConfigurations(); + std::unique_ptr>> getAllConfigurations(); } -bool configuration_init(FilesystemOpt fsOpt = FilesystemOpt::Use_Mount_FormatOnFail); +bool configuration_init(std::shared_ptr filesytem); bool configuration_save(); } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp index 4d0a260d..b656144d 100644 --- a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp +++ b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp @@ -5,6 +5,8 @@ #include #include +#include + #if defined(ESP32) #define USE_FS LITTLEFS #else @@ -23,95 +25,85 @@ #define MAX_FILE_SIZE 4000 #define MAX_CONFIGURATIONS 50 +#define MAX_CONFJSON_CAPACITY 4000 namespace ArduinoOcpp { bool ConfigurationContainerFlash::load() { -#ifndef AO_DEACTIVATE_FLASH + + if (!filesystem) { + return false; + } if (configurations.size() > 0) { AO_DBG_ERR("Error: declared configurations before calling container->load(). " \ "All previously declared values won't be written back"); + (void)0; } - if (!USE_FS.exists(getFilename())) { + size_t file_size = 0; + if (filesystem->stat(getFilename(), &file_size) != 0 // file does not exist + || file_size == 0) { // file exists, but empty AO_DBG_DEBUG("Populate FS: create configuration file"); - return true; + return save(); } - File file = USE_FS.open(getFilename(), "r"); + if (file_size > MAX_FILE_SIZE) { + AO_DBG_ERR("Unable to initialize: filesize is too long"); + return false; + } + + auto file = filesystem->open(getFilename(), "r"); if (!file) { AO_DBG_ERR("Unable to initialize: could not open configuration file %s", getFilename()); return false; } - if (!file.available()) { - AO_DBG_DEBUG("Populate FS: create configuration file"); - file.close(); - return true; - } + auto jsonCapacity = std::max(file_size, (size_t) 256); + DynamicJsonDocument doc {0}; + DeserializationError err = DeserializationError::NoMemory; - int file_size = file.size(); + while (err == DeserializationError::NoMemory) { + if (jsonCapacity > MAX_CONFJSON_CAPACITY) { + AO_DBG_ERR("JSON capacity exceeded"); + return false; + } - if (file_size < 2) { - AO_DBG_ERR("Unable to initialize: too short for json"); - file.close(); - return false; - } else if (file_size > MAX_FILE_SIZE) { - AO_DBG_ERR("Unable to initialize: filesize is too long"); - file.close(); - return false; - } + AO_DBG_DEBUG("Configs JSON capacity: %zu", jsonCapacity); - String token = file.readStringUntil('\n'); - if (!token.equals("content-type:arduino-ocpp_configuration_file")) { - AO_DBG_ERR("Unable to initialize: unrecognized configuration file format"); - file.close(); - return false; + doc = DynamicJsonDocument(jsonCapacity); + ArduinoJsonFileAdapter file_adapt {file.get()}; + err = deserializeJson(doc, file_adapt); + + jsonCapacity *= 3; + jsonCapacity /= 2; + file->seek(0); } - token = file.readStringUntil('\n'); - if (!token.equals("version:1.0")) { - AO_DBG_ERR("Unable to initialize: unsupported version"); - file.close(); + if (err) { + AO_DBG_ERR("Unable to initialize: config file deserialization failed: %s", err.c_str()); return false; } - token = file.readStringUntil(':'); - if (!token.equals("configurations_len")) { - AO_DBG_ERR("Unable to initialize: missing length statement"); - file.close(); + JsonObject configHeader = doc["head"]; + + if (strcmp(configHeader["content-type"] | "Invalid", "ao_configuration_file")) { + AO_DBG_ERR("Unable to initialize: unrecognized configuration file format"); return false; } - token = file.readStringUntil('\n'); - int configurations_len = token.toInt(); - if (configurations_len <= 0) { - AO_DBG_ERR("Unable to initialize: empty configuration"); - file.close(); - return true; - } - if (configurations_len > MAX_CONFIGURATIONS) { - AO_DBG_ERR("Unable to initialize: configurations_len is too big"); - file.close(); + if (strcmp(configHeader["version"] | "Invalid", "1.1")) { + AO_DBG_ERR("Unable to initialize: unsupported version"); return false; } - - size_t jsonCapacity = file_size + JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(configurations_len) + configurations_len * JSON_OBJECT_SIZE(5); - - AO_DBG_DEBUG("Config capacity = %zu", jsonCapacity); - - DynamicJsonDocument configDoc(jsonCapacity); - - DeserializationError error = deserializeJson(configDoc, file); - if (error) { - AO_DBG_ERR("Unable to initialize: config file deserialization failed: %s", error.c_str()); - file.close(); + + JsonArray configurationsArray = doc["configurations"]; + if (configurationsArray.size() > MAX_CONFIGURATIONS) { + AO_DBG_ERR("Unable to initialize: configurations_len is too big (=%zu)", configurationsArray.size()); return false; } - JsonArray configurationsArray = configDoc["configurations"]; for (JsonObject config : configurationsArray) { const char *type = config["type"] | "Undefined"; @@ -132,49 +124,46 @@ bool ConfigurationContainerFlash::load() { } } - file.close(); - configurationsUpdated(); - AO_DBG_DEBUG("Initialization successful"); -#endif //ndef AO_DEACTIVATE_FLASH + AO_DBG_DEBUG("Initialization finished"); return true; } bool ConfigurationContainerFlash::save() { -#ifndef AO_DEACTIVATE_FLASH + + if (!filesystem) { + return false; + } if (!configurationsUpdated()) { return true; //nothing to be done } - if (USE_FS.exists(getFilename())) { - USE_FS.remove(getFilename()); + size_t file_size = 0; + if (filesystem->stat(getFilename(), &file_size) == 0) { + filesystem->remove(getFilename()); } - File file = USE_FS.open(getFilename(), "w"); - + auto file = filesystem->open(getFilename(), "w"); if (!file) { AO_DBG_ERR("Unable to save: could not open configuration file %s", getFilename()); return false; } - size_t jsonCapacity = JSON_OBJECT_SIZE(1); //configurations - - size_t numEntries = configurations.size(); - - file.print("content-type:arduino-ocpp_configuration_file\n"); - file.print("version:1.0\n"); - file.print("configurations_len:"); - file.print(numEntries, DEC); - file.print("\n"); + size_t jsonCapacity = 2 * JSON_OBJECT_SIZE(2); //head + configurations + head payload std::vector> entries; for (auto config = configurations.begin(); config != configurations.end(); config++) { std::shared_ptr entry = (*config)->toJsonStorageEntry(); - if (entry) + if (entry) { entries.push_back(entry); + } + if (entries.size() >= MAX_CONFIGURATIONS) { + AO_DBG_ERR("Max No of configratuions exceeded. Crop configs file (by FCFS)"); + break; + } } jsonCapacity += JSON_ARRAY_SIZE(entries.size()); //length of configurations @@ -182,26 +171,43 @@ bool ConfigurationContainerFlash::save() { jsonCapacity += (*entry)->capacity(); } - DynamicJsonDocument configDoc(jsonCapacity); + jsonCapacity = std::max(jsonCapacity, (size_t) 256); + DynamicJsonDocument doc {0}; + bool jsonDocOverflow = true; - JsonArray configurationsArray = configDoc.createNestedArray("configurations"); + while (jsonDocOverflow) { + if (jsonCapacity > MAX_CONFJSON_CAPACITY) { + AO_DBG_ERR("JSON capacity exceeded"); + return false; + } - for (auto entry = entries.begin(); entry != entries.end(); entry++) { - configurationsArray.add((*entry)->as()); - } + file->seek(0); - // Serialize JSON to file - if (serializeJson(configDoc, file) == 0) { - AO_DBG_ERR("Unable to save: Could not serialize JSON"); - file.close(); - return false; + doc = DynamicJsonDocument(jsonCapacity); + JsonObject head = doc.createNestedObject("head"); + head["content-type"] = "ao_configuration_file"; + head["version"] = "1.1"; + + JsonArray configurationsArray = doc.createNestedArray("configurations"); + for (auto entry = entries.begin(); entry != entries.end(); entry++) { + configurationsArray.add((*entry)->as()); + } + + ArduinoJsonFileAdapter file_adapt {file.get()}; + size_t written = serializeJson(doc, file_adapt); + + jsonCapacity *= 3; + jsonCapacity /= 2; + jsonDocOverflow = doc.overflowed(); + + if (!jsonDocOverflow && written < 20) { //plausibility check + AO_DBG_ERR("Config serialization: unkown error for file %s", getFilename()); + return false; + } } //success - file.close(); - AO_DBG_DEBUG("Saving configDoc successful"); - -#endif //ndef AO_DEACTIVATE_FLASH + AO_DBG_DEBUG("Saving configurations finished"); return true; } diff --git a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.h b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.h index e401ab94..865756de 100644 --- a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.h +++ b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.h @@ -6,14 +6,15 @@ #define CONFIGURATIONCONTAINERFLASH_H #include +#include namespace ArduinoOcpp { class ConfigurationContainerFlash : public ConfigurationContainer { - - + std::shared_ptr filesystem; public: - ConfigurationContainerFlash(const char *filename) : ConfigurationContainer(filename) { } + ConfigurationContainerFlash(std::shared_ptr filesystem, const char *filename) : + ConfigurationContainer(filename), filesystem(filesystem) { } ~ConfigurationContainerFlash() = default; diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp new file mode 100644 index 00000000..d35789be --- /dev/null +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp @@ -0,0 +1,274 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include //FilesystemOpt +#include + +//#ifndef AO_DEACTIVATE_FLASH +#if 1 + +//Set default parameters; assume usage with Arduino if no build flags are present +#ifndef AO_USE_FILEAPI +#if defined(ESP32) +#define AO_USE_FILEAPI ARDUINO_LITTLEFS +#else +#define AO_USE_FILEAPI ARDUINO_SPIFFS +#endif +#endif //ndef AO_USE_FILEAPI + +/* + * Platform specific implementations. Currently supported: + * - Arduino LittleFs + * - Arduino SPIFFS + * - ESP-IDF SPIFFS + * + * You can add support for any file system by passing custom adapters to the initialize + * function of ArduinoOcpp + */ + +#if AO_USE_FILEAPI == ARDUINO_LITTLEFS +#include +#define USE_FS LITTLEFS +#elif AO_USE_FILEAPI == ARDUINO_SPIFFS +#include +#define USE_FS SPIFFS +#elif AO_USE_FILEAPI == ESPIDF_SPIFFS +#include +#include "esp_spiffs.h" +#endif + + +#if AO_USE_FILEAPI == ARDUINO_LITTLEFS || AO_USE_FILEAPI == ARDUINO_SPIFFS + +namespace ArduinoOcpp { +namespace EspWiFi { + +class ArduinoFileAdapter : public FileAdapter { + File file; +public: + ArduinoFileAdapter(File&& file) : file(file) {} + + ~ArduinoFileAdapter() { + if (file) { + file.close(); + } + } + + int read() override; + size_t read(char *buf, size_t len) override; + size_t write(const char *buf, size_t len) override; + size_t seek(size_t offset) override; +}; + +class ArduinoFilesystemAdapter : public FilesystemAdapter { +private: + bool valid = false; + FilesystemOpt config; +public: + ArduinoFilesystemAdapter(FilesystemOpt config) : config(config) { + valid = true; + + if (config.mustMount()) { +#if AO_USE_FILEAPI == ARDUINO_LITTLEFS + if(!USE_FS.begin(config.formatOnFail())) { + AO_DBG_ERR("Error while mounting LITTLEFS"); + valid = false; + } +#elif AO_USE_FILEAPI == ARDUINO_SPIFFS + //ESP8266 + SPIFFSConfig cfg; + cfg.setAutoFormat(config.formatOnFail()); + SPIFFS.setConfig(cfg); + + if (!SPIFFS.begin()) { + AO_DBG_ERR("Unable to initialize: unable to mount SPIFFS"); + valid = false; + } +#else +#error +#endif + } //end if mustMount() + } + + ~ArduinoFilesystemAdapter() { + if (config.mustMount()) { + USE_FS.end(); + } + } + + operator bool() {return valid;} + + int stat(const char *path, size_t *size) override { + if (!USE_FS.exists(path)) { + return -1; + } + File f = USE_FS.open(path, "r"); + if (!f) { + return -1; + } + + int status = -1; + if (f.isFile()) { + size = f.size(); + status = 0; + } else { + //fetch more information for directory when ArduinoOcpp also uses them + //status = 0; + } + + f.close(); + return status; + } + + std::unique_ptr open(const char *fn, const char *mode) override { + File file = USE_FS.open(fn, mode); + if (file && file.isFile()) { + return std::unique_ptr(new ArduinoFileAdapter(std::move(file))); + } else { + return nullptr; + } + } + bool remove(const char *fn) override { + return USE_FS.remove(fn); + }; +}; + +std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { + + if (!config.accessAllowed()) { + AO_DBG_DEBUG("Access to Arduino FS not allowed by config"); + return nullptr; + } + + auto fs = std::unique_ptr( + new ArduinoFilesystemAdapter(config) + ); + + if (*fs) { + return fs; + } else { + return nullptr; + } +} + +} //end namespace EspWiFi +} //end namespace ArduinoOcpp + +#elif AO_USE_FILEAPI == ESPIDF_SPIFFS + +namespace ArduinoOcpp { +namespace EspWiFi { + +class EspIdfFileAdapter : public FileAdapter { + FILE *file {nullptr}; +public: + EspIdfFileAdapter(FILE *file) : file(file) {} + + ~EspIdfFileAdapter() { + 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); + } + + size_t seek(size_t offset) override { + return fseek(file, offset, SEEK_SET); + } + + int read() { + return fgetc(file); + } +}; + +class EspIdfFilesystemAdapter : public FilesystemAdapter { +public: + FilesystemOpt config; +public: + EspIdfFilesystemAdapter(FilesystemOpt config) : config(config) { } + + ~EspIdfFilesystemAdapter() { + if (config.mustMount()) { + esp_vfs_spiffs_unregister("ao"); //partition label + AO_DBG_DEBUG("SPIFFS unmounted"); + } + } + + 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; + } + + 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 { + AO_DBG_DEBUG("Failed to open file path %s", fn); + return nullptr; + } + } + + bool remove(const char *fn) override { + return unlink(fn) == 0; + } +}; + +std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { + + if (!config.accessAllowed()) { + AO_DBG_DEBUG("Access to ESP-IDF SPIFFS not allowed by config"); + return nullptr; + } + + bool mounted = true; + + if (config.mustMount()) { + mounted = false; + + esp_vfs_spiffs_conf_t conf = { + .base_path = AO_FILENAME_PREFIX, + .partition_label = "ao", //also see deconstructor + .max_files = 5, + .format_if_mount_failed = config.formatOnFail() + }; + + esp_err_t ret = esp_vfs_spiffs_register(&conf); + + if (ret == ESP_OK) { + mounted = true; + AO_DBG_DEBUG("SPIFFS mounted"); + } else { + if (ret == ESP_FAIL) { + AO_DBG_ERR("Failed to mount or format filesystem"); + } else if (ret == ESP_ERR_NOT_FOUND) { + AO_DBG_ERR("Failed to find SPIFFS partition"); + } else { + AO_DBG_ERR("Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); + } + } + } + + if (mounted) { + return std::unique_ptr(new EspIdfFilesystemAdapter(config)); + } else { + return nullptr; + } +} + +} //end namespace EspWiFi +} //end namespace ArduinoOcpp + +#endif + +#endif diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.h b/src/ArduinoOcpp/Core/FilesystemAdapter.h new file mode 100644 index 00000000..e32f7779 --- /dev/null +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.h @@ -0,0 +1,103 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef AO_FILESYSTEMADAPTER_H +#define AO_FILESYSTEMADAPTER_H + +#ifndef AO_FILENAME_PREFIX +#define AO_FILENAME_PREFIX "/ao_store" +#endif + +#define ARDUINO_LITTLEFS 1 +#define ARDUINO_SPIFFS 2 +#define ESPIDF_SPIFFS 3 + +#include + +namespace ArduinoOcpp { + +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; + // virtual void close() = 0; implemented in deconstructor + + virtual int read() = 0; +}; + +class ArduinoJsonFileAdapter { +private: + FileAdapter *file; +public: + ArduinoJsonFileAdapter(FileAdapter *file) : file(file) { } + + size_t readBytes(char *buf, size_t len) { + return file->read(buf, len); + } + + int read() { + return file->read(); + } + + size_t write(const uint8_t *buf, size_t len) { + return file->write((const char*) buf, len); + } + + size_t write(uint8_t c) { + return file->write((const char*) &c, 1); + } +}; + +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; +}; + +} //end namespace ArduinoOcpp + +//#ifndef AO_DEACTIVATE_FLASH +#if 1 + +//Set default parameters; assume usage with Arduino if no build flags are present +#ifndef AO_USE_FILEAPI +#if defined(ESP32) +#define AO_USE_FILEAPI ARDUINO_LITTLEFS +#else +#define AO_USE_FILEAPI ARDUINO_SPIFFS +#endif +#endif //ndef AO_USE_FILEAPI + +/* + * Platform specific implementations. Currently supported: + * - Arduino LittleFs + * - Arduino SPIFFS + * - ESP-IDF SPIFFS + * + * You can add support for any file system by passing custom adapters to the initialize + * function of ArduinoOcpp + */ + +#if AO_USE_FILEAPI == ARDUINO_LITTLEFS || \ + AO_USE_FILEAPI == ARDUINO_SPIFFS || \ + AO_USE_FILEAPI == ESPIDF_SPIFFS + +#include + +namespace ArduinoOcpp { +namespace EspWiFi { + +std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt config); + +} //end namespace EspWiFi +} //end namespace ArduinoOcpp +#endif + +#endif //ndef AO_DEACTIVATE_FLASH + +#endif diff --git a/src/ArduinoOcpp/Core/OcppConnection.cpp b/src/ArduinoOcpp/Core/OcppConnection.cpp index 15c10ce3..310f5d0c 100644 --- a/src/ArduinoOcpp/Core/OcppConnection.cpp +++ b/src/ArduinoOcpp/Core/OcppConnection.cpp @@ -112,8 +112,8 @@ bool OcppConnection::processOcppSocketInputTXT(const char* payload, size_t lengt doc = std::unique_ptr(new DynamicJsonDocument(capacity)); err = deserializeJson(*doc, payload, length); - capacity /= 2; capacity *= 3; + capacity /= 2; } //TODO insert validateRpcHeader at suitable position diff --git a/src/ArduinoOcpp/MessagesV16/GetConfiguration.cpp b/src/ArduinoOcpp/MessagesV16/GetConfiguration.cpp index ccd4c57f..3989137a 100644 --- a/src/ArduinoOcpp/MessagesV16/GetConfiguration.cpp +++ b/src/ArduinoOcpp/MessagesV16/GetConfiguration.cpp @@ -26,13 +26,15 @@ void GetConfiguration::processReq(JsonObject payload) { std::unique_ptr GetConfiguration::createConf(){ - std::shared_ptr>> configurationKeys; + std::unique_ptr>> configurationKeys; std::vector unknownKeys; if (keys.size() == 0){ //return all existing keys configurationKeys = getAllConfigurations(); } else { //only return keys that were searched using the "key" parameter - configurationKeys = std::make_shared>>(); + configurationKeys = std::unique_ptr>>( + new std::vector>() + ); for (size_t i = 0; i < keys.size(); i++) { std::shared_ptr entry = getConfiguration(keys.at(i).c_str()); if (entry) From 6c02f43bd50ca59149c6de8f2a0bfeffa6c38d91 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 1 Jun 2022 10:07:54 +0200 Subject: [PATCH 016/549] Add loopback OCPP socket --- src/ArduinoOcpp/Core/OcppSocket.h | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/ArduinoOcpp/Core/OcppSocket.h b/src/ArduinoOcpp/Core/OcppSocket.h index f0c9c087..f1fb6db7 100644 --- a/src/ArduinoOcpp/Core/OcppSocket.h +++ b/src/ArduinoOcpp/Core/OcppSocket.h @@ -24,6 +24,23 @@ class OcppSocket { virtual void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) = 0; //ReceiveTXTcallback is defined in OcppServer.h }; +class OcppEchoSocket : public OcppSocket { +private: + ReceiveTXTcallback receiveTXT; +public: + void loop() override { } + bool sendTXT(std::string &out) override { + if (receiveTXT) { + return receiveTXT(out.c_str(), out.length()); + } else { + return false; + } + } + void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) override { + this->receiveTXT = receiveTXT; + } +}; + } //end namespace ArduinoOcpp #ifndef AO_CUSTOM_WS @@ -36,10 +53,8 @@ namespace EspWiFi { class OcppClientSocket : public OcppSocket { private: - //std::shared_ptr wsock; WebSocketsClient *wsock; public: - //OcppClientSocket(ReceiveTXTcallback &receiveTXT, std::shared_ptr wsock); OcppClientSocket(WebSocketsClient *wsock); void loop(); From a1bcd53f84096af4747bef5274f0331d31023b57 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 1 Jun 2022 10:20:37 +0200 Subject: [PATCH 017/549] OCMF support, connector lock integration --- src/ArduinoOcpp.cpp | 26 +++ src/ArduinoOcpp.h | 21 ++- .../MessagesV16/StartTransaction.cpp | 3 +- .../ChargePointStatus/ConnectorStatus.cpp | 177 +++++++++++++----- .../Tasks/ChargePointStatus/ConnectorStatus.h | 27 ++- .../TransactionPrerequisites.h | 27 +++ 6 files changed, 227 insertions(+), 54 deletions(-) create mode 100644 src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index d8ffe4d1..f488c258 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -293,6 +293,32 @@ void setOnUnlockConnector(std::function()> unlockConnector) { connector->setOnUnlockConnector(unlockConnector); } +void setConnectorLock(std::function lockConnector) { + if (!ocppEngine) { + AO_DBG_ERR("Please call OCPP_initialize before"); + return; + } + auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); + if (!connector) { + AO_DBG_ERR("Could not find connector. Ignore"); + return; + } + connector->setConnectorLock(lockConnector); +} + +void setTxBasedMeterUpdate(std::function updateTxState) { + if (!ocppEngine) { + AO_DBG_ERR("Please call OCPP_initialize before"); + return; + } + auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); + if (!connector) { + AO_DBG_ERR("Could not find connector. Ignore"); + return; + } + connector->setTxBasedMeterUpdate(updateTxState); +} + void setOnSetChargingProfileRequest(OnReceiveReqListener onReceiveReq) { setOnSetChargingProfileRequestListener(onReceiveReq); } diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index 15b3ee91..c22b2088 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -15,6 +15,7 @@ #include #include #include +#include #include using ArduinoOcpp::OnReceiveConfListener; @@ -77,7 +78,25 @@ void addConnectorErrorCodeSampler(std::function connectorErrorCo void setOnChargingRateLimitChange(std::function chargingRateChanged); -void setOnUnlockConnector(std::function()> unlockConnector); //true: success, false: failure +//Set a Cb to mechanically unlock the connector. Called for the OCPP operation "UnlockConnector" +//Return values: true on success, false on failure, PollResult::Await if not known yet +//Continues to call the Cb as long as it returns PollResult::Await +void setOnUnlockConnector(std::function()> unlockConnector); + +//Set a Cb for setting the state of the connector lock. Called in the course of normal transactions +//Return values: - TxEnableState::Active if connector is locked and ready for transaction +// - TxEnableState::Inactive if connector lock is released +// - TxEnableState::Pending otherwise, e.g. if transitioning between the states +//Called periodically +void setConnectorLock(std::function lockConnector); + +//Set a Cb to update transaction-based energy measurements with the most recent transaction state. +//This allows energy meters (e.g. based on OCMF) to take their measruements right before and after a transaction +//Return values: - TxEnableState::Active if the energy meter confirmed to be in the transaction-state +// - TxEnableState::Inactive if the energy meter has transitioned into a non-transaction-state +// - TxEnableState::Pending otherwise, e.g. if transitioning between the states +//Called periodically +void setTxBasedMeterUpdate(std::function updateTxState); /* * React on CS-initiated operations diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp index f7f3ec57..bee6d6cf 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp @@ -136,7 +136,8 @@ std::unique_ptr StartTransaction::createConf() { JsonObject idTagInfo = payload.createNestedObject("idTagInfo"); idTagInfo["status"] = "Accepted"; - payload["transactionId"] = 123456; //sample data for debug purpose + static int uniqueTxId = 1000; + payload["transactionId"] = uniqueTxId++; //sample data for debug purpose return doc; } diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index 185ff4b5..eb90a08f 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -37,6 +37,8 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) minimumStatusDuration = declareConfiguration("MinimumStatusDuration", 0, CONFIGURATION_FN, true, true, true, false); stopTransactionOnInvalidId = declareConfiguration("StopTransactionOnInvalidId", "true", CONFIGURATION_FN, true, true, false, false); stopTransactionOnEVSideDisconnect = declareConfiguration("StopTransactionOnEVSideDisconnect", "true", CONFIGURATION_FN, true, true, false, false); + localAuthorizeOffline = declareConfiguration("LocalAuthorizeOffline", "false", CONFIGURATION_FN, true, true, false, false); + localPreAuthorize = declareConfiguration("LocalPreAuthorize", "false", CONFIGURATION_FN, true, true, false, false); if (!sIdTag || !transactionId || !availability) { AO_DBG_ERR("Cannot declare sessionIdTag, transactionId or availability"); @@ -49,6 +51,29 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) AO_DBG_DEBUG("Load session idTag at initialization"); } transactionIdSync = *transactionId; + + /* + * Initialize standard EVSE behavior. + * By default, transactions are triggered by a valid IdTag (+ connected plug as soon as set) + * The default necessary steps before starting a transaction are + * - lock the connector (if handler is set) + * - instruct the OCMF meter to begin a transaction (if OCMF meter handler is set) + */ + txTriggerConditions.push_back([this] () -> TxCondition { + return getSessionIdTag() == nullptr ? TxCondition::Inactive : TxCondition::Active; + }); + txEnableSequence.push_back([this] (TxCondition cond) -> TxEnableState { + if (onOcmfMeterPollTx) { + return onOcmfMeterPollTx(cond); + } + return cond == TxCondition::Active ? TxEnableState::Active : TxEnableState::Inactive; + }); + txEnableSequence.push_back([this] (TxCondition cond) -> TxEnableState { + if (onConnectorLockPollTx) { + return onConnectorLockPollTx(cond); + } + return cond == TxCondition::Active ? TxEnableState::Active : TxEnableState::Inactive; + }); } OcppEvseState ConnectorStatus::inferenceStatus() { @@ -65,31 +90,16 @@ OcppEvseState ConnectorStatus::inferenceStatus() { } } -// auto cpStatusService = context.getChargePointStatusService(); -// -// if (!authorized && !getChargePointStatusService()->existsUnboundAuthorization()) { -// return OcppEvseState::Available; -// } else if (((int) *transactionId) < 0) { -// return OcppEvseState::Preparing; - //if (connectorFaultedSampler != nullptr && connectorFaultedSampler()) { if (getErrorCode() != nullptr) { return OcppEvseState::Faulted; } else if (*availability == AVAILABILITY_INOPERATIVE) { return OcppEvseState::Unavailable; - } else if (!session && - getTransactionId() < 0 && - (connectorPluggedSampler == nullptr || !connectorPluggedSampler()) ) { - return OcppEvseState::Available; - } else if (getTransactionId() <= 0) { - if (connectorPluggedSampler != nullptr && connectorPluggedSampler() && - (currentStatus == OcppEvseState::Finishing || - currentStatus == OcppEvseState::Charging || - currentStatus == OcppEvseState::SuspendedEV || - currentStatus == OcppEvseState::SuspendedEVSE)) { - return OcppEvseState::Finishing; - } - return OcppEvseState::Preparing; - } else { + } else if (getTransactionId() == 0 && // i.e. Tx pending or EVSE offline. Check if offline Tx is OFF + !(*localAuthorizeOffline && strcmp(*localAuthorizeOffline, "false")) && + !(*localPreAuthorize && strcmp(*localPreAuthorize, "false"))) { + //All modes for offline Tx are off + return OcppEvseState::Preparing; //see other Preparing case + } else if (getTransactionId() >= 0) { //Transaction is currently running if ((connectorEnergizedSampler && !connectorEnergizedSampler()) || idTagInvalidated) { @@ -99,7 +109,32 @@ OcppEvseState ConnectorStatus::inferenceStatus() { return OcppEvseState::SuspendedEV; } return OcppEvseState::Charging; + } else if (txEnable == TxEnableState::Inactive) { + return OcppEvseState::Available; + } else if (txEnable == TxEnableState::Pending || + txEnable == TxEnableState::Active) { //reached if Tx init is delayed + + if (txEnable == TxEnableState::Active) { // TODO verify if actually possible + AO_DBG_VERBOSE("Infered Active"); // + (void)0; // + } // + + /* + * Either in Preparing or Finishing state. Only way to know is from previous state + */ + const auto previous = currentStatus; + if (previous == OcppEvseState::Finishing || + previous == OcppEvseState::Charging || + previous == OcppEvseState::SuspendedEV || + previous == OcppEvseState::SuspendedEVSE) { + return OcppEvseState::Finishing; + } else { + return OcppEvseState::Preparing; + } } + + AO_DBG_VERBOSE("Cannot infere status"); + return OcppEvseState::Faulted; //internal error } bool ConnectorStatus::ocppPermitsCharge() { @@ -120,7 +155,7 @@ bool ConnectorStatus::ocppPermitsCharge() { } OcppMessage *ConnectorStatus::loop() { - if (getTransactionId() <= 0 && *availability == AVAILABILITY_INOPERATIVE_SCHEDULED) { + if (getTransactionId() < 0 && *availability == AVAILABILITY_INOPERATIVE_SCHEDULED) { *availability = AVAILABILITY_INOPERATIVE; saveState(); } @@ -133,29 +168,72 @@ OcppMessage *ConnectorStatus::loop() { } } + auto txTrigger = txTriggerConditions.empty() ? TxCondition::Inactive : TxCondition::Active; + txEnable = TxEnableState::Inactive; + + if (*availability == AVAILABILITY_INOPERATIVE) { + txTrigger = TxCondition::Inactive; + } + + if (txTrigger == TxCondition::Active) { + for (auto trigger = txTriggerConditions.begin(); trigger != txTriggerConditions.end(); trigger++) { + auto result = trigger->operator()(); + if (result == TxCondition::Active) { + txEnable = TxEnableState::Pending; + } else { + txTrigger = TxCondition::Inactive; + } + } + } + + if (txTrigger == TxCondition::Active) { + txEnable = TxEnableState::Active; + + for (auto step = txEnableSequence.rbegin(); step != txEnableSequence.rend(); step++) { + auto result = step->operator()(TxCondition::Active); + if (result != TxEnableState::Active) { + txEnable = TxEnableState::Pending; + break; + } + } + } else { + for (auto step = txEnableSequence.begin(); step != txEnableSequence.end(); step++) { + auto result = step->operator()(TxCondition::Inactive); + if (result != TxEnableState::Inactive) { + txEnable = TxEnableState::Pending; + break; + } + } + } + + /* * Check conditions for start or stop transaction */ - if (connectorPluggedSampler) { //only supported with connectorPluggedSampler + if (txEnable == TxEnableState::Active) { + //check if not in transaction yet + if (getTransactionId() < 0 && + !getErrorCode()) { + //start Transaction + + AO_DBG_DEBUG("Session mngt: txId=%i, connectorPlugged = %s, session=%d", + getTransactionId(), + connectorPluggedSampler ? (connectorPluggedSampler() ? "plugged" : "unplugged") : "undefined", + session); + AO_DBG_INFO("Session mngt: trigger StartTransaction"); + return new StartTransaction(connectorId); + } + } else { + //check if still in transaction if (getTransactionId() >= 0) { - //check condition for StopTransaction - if (!session) { - AO_DBG_DEBUG("Session mngt: txId=%i, connectorPlugged=%d, session=%d", - getTransactionId(), connectorPluggedSampler(), session); - AO_DBG_INFO("Session mngt: trigger StopTransaction"); - return new StopTransaction(connectorId, endReason[0] != '\0' ? endReason : nullptr); - } - } else { - //check condition for StartTransaction - if (connectorPluggedSampler() && - session && - !getErrorCode() && - *availability == AVAILABILITY_OPERATIVE) { - AO_DBG_DEBUG("Session mngt: txId=%i, connectorPlugged=%d, session=%d", - getTransactionId(), connectorPluggedSampler(), session); - AO_DBG_INFO("Session mngt: trigger StartTransaction"); - return new StartTransaction(connectorId); - } + //stop transaction + + AO_DBG_DEBUG("Session mngt: txId=%i, connectorPlugged = %s, session=%d", + getTransactionId(), + connectorPluggedSampler ? (connectorPluggedSampler() ? "plugged" : "unplugged") : "undefined", + session); + AO_DBG_INFO("Session mngt: trigger StopTransaction"); + return new StopTransaction(connectorId, endReason[0] != '\0' ? endReason : nullptr); } } @@ -172,10 +250,10 @@ OcppMessage *ConnectorStatus::loop() { } } - auto inferencedStatus = inferenceStatus(); + auto inferedStatus = inferenceStatus(); - if (inferencedStatus != currentStatus) { - currentStatus = inferencedStatus; + if (inferedStatus != currentStatus) { + currentStatus = inferedStatus; t_statusTransition = ao_tick_ms(); AO_DBG_DEBUG("Status changed%s", *minimumStatusDuration ? ", will report delayed" : ""); } @@ -297,6 +375,9 @@ void ConnectorStatus::setAvailability(bool available) { void ConnectorStatus::setConnectorPluggedSampler(std::function connectorPlugged) { this->connectorPluggedSampler = connectorPlugged; + txTriggerConditions.push_back([this] () -> TxCondition { + return connectorPluggedSampler() ? TxCondition::Active : TxCondition::Inactive; + }); } void ConnectorStatus::setEvRequestsEnergySampler(std::function evRequestsEnergy) { @@ -322,3 +403,11 @@ void ConnectorStatus::setOnUnlockConnector(std::function()> unl std::function()> ConnectorStatus::getOnUnlockConnector() { return this->onUnlockConnector; } + +void ConnectorStatus::setConnectorLock(std::function onConnectorLockPollTx) { + this->onConnectorLockPollTx = onConnectorLockPollTx; +} + +void ConnectorStatus::setTxBasedMeterUpdate(std::function onOcmfMeterPollTx) { + this->onOcmfMeterPollTx = onOcmfMeterPollTx; +} diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h index 8c9a8962..940e1d35 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h @@ -2,10 +2,11 @@ // Copyright Matthias Akstaller 2019 - 2022 // MIT License -#ifndef CONNECTOR_STATUS -#define CONNECTOR_STATUS +#ifndef CONNECTORSTATUS_H +#define CONNECTORSTATUS_H #include +#include #include #include #include @@ -43,9 +44,9 @@ class ConnectorStatus { bool connectionTimeOutListen {false}; ulong connectionTimeOutTimestamp {0}; //in milliseconds - std::function connectorPluggedSampler {nullptr}; - std::function evRequestsEnergySampler {nullptr}; - std::function connectorEnergizedSampler {nullptr}; + std::function connectorPluggedSampler; + std::function evRequestsEnergySampler; + std::function connectorEnergizedSampler; std::vector> connectorErrorCodeSamplers; const char *getErrorCode(); @@ -54,13 +55,20 @@ class ConnectorStatus { OcppEvseState reportedStatus = OcppEvseState::NOT_SET; ulong t_statusTransition = 0; - //std::function()> startTransactionBehavior; - //std::function(const char* stopReason)> stopTransactionBehavior; + std::function()> onUnlockConnector; - std::function()> onUnlockConnector {nullptr}; + std::function onConnectorLockPollTx; + std::function onOcmfMeterPollTx; + + std::vector> txTriggerConditions; + std::vector> txEnableSequence; + TxEnableState txEnable {TxEnableState::Inactive}; // = Result of Trigger and Enable Sequence std::shared_ptr> stopTransactionOnInvalidId; std::shared_ptr> stopTransactionOnEVSideDisconnect; + std::shared_ptr> unlockConnectorOnEVSideDisconnect; + std::shared_ptr> localAuthorizeOffline; + std::shared_ptr> localPreAuthorize; public: ConnectorStatus(OcppModel& context, int connectorId); @@ -103,6 +111,9 @@ class ConnectorStatus { void setOnUnlockConnector(std::function()> unlockConnector); std::function()> getOnUnlockConnector(); + + void setConnectorLock(std::function lockConnector); + void setTxBasedMeterUpdate(std::function updateTxBasedMeter); }; } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h new file mode 100644 index 00000000..d73b3310 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h @@ -0,0 +1,27 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef TXPREREQUISITES_H +#define TXPREREQUISITES_H + +namespace ArduinoOcpp { + +/* + * Type definitions for extending the transaction initiation process + */ + +enum class TxCondition { + Active, + Inactive +}; + +enum class TxEnableState { + Active, + Inactive, + Pending +}; + +} + +#endif From 322a7c85d16dac2e4f5bc9348b6f2f1adbb69217 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 7 Jun 2022 17:41:23 +0200 Subject: [PATCH 018/549] add energy and power meter cb --- src/ArduinoOcpp_c.cpp | 18 ++++++++++++++++++ src/ArduinoOcpp_c.h | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index b44dceb4..7a027a88 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -63,6 +63,24 @@ std::function adaptCb(SamplerString cb) { return cb; } +std::function adaptCb(SamplerFloat cb) { + return cb; +} + +std::function adaptCb(SamplerInt cb) { + return cb; +} + +void ao_setPowerActiveImportSampler(SamplerFloat power) { + setPowerActiveImportSampler(adaptCb(power)); +} + +void ao_setEnergyActiveImportSampler(SamplerInt energy) { + setEnergyActiveImportSampler([energy] () -> float { + return (float) energy(); + }); +} + void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy) { setEvRequestsEnergySampler(adaptCb(evRequestsEnergy)); } diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index b36af90a..568cfa7a 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -34,7 +34,7 @@ void ao_set_console_out_c(void (*console_out)(const char *msg)); void ao_setPowerActiveImportSampler(SamplerFloat power); -void ao_setEnergyActiveImportSampler(SamplerFloat energy); +void ao_setEnergyActiveImportSampler(SamplerInt energy); void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy); From b01683658c82c0e3093a520ba83953b36ea7f9df Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 8 Jun 2022 10:25:15 +0200 Subject: [PATCH 019/549] explicit custom updater --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0307395..862c2398 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ idf_component_register(SRCS ${AO_SRC} target_compile_options(${COMPONENT_TARGET} PUBLIC -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE + -DAO_CUSTOM_UPDATER -DAO_DEACTIVATE_FLASH -DAO_USE_FILEAPI=ESPIDF_SPIFFS -DAO_DBG_LEVEL=AO_DL_DEBUG From 597d04ac7fa707f7f42b2e551661626ad1345fc9 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Fri, 17 Jun 2022 14:44:24 +0200 Subject: [PATCH 020/549] BootNotification with all fields --- src/ArduinoOcpp_c.cpp | 11 +++++++++++ src/ArduinoOcpp_c.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 7a027a88..855689c0 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -129,6 +129,17 @@ extern "C" void ao_bootNotification(const char *chargePointModel, const char *ch bootNotification("model", "vendor", adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); } +void ao_bootNotification_full(const char *payloadJson, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + DynamicJsonDocument *payload = new DynamicJsonDocument(JSON_OBJECT_SIZE(9) + 230 + 9); // BootNotification has at most 9 attributes with at most 230 chars + null terminators + auto err = deserializeJson(*payload, payloadJson); + if (err) { + AO_DBG_ERR("Could not process input: %s", err.c_str()); + (void)0; + } + + bootNotification(payload, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { authorize(idTag, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); } diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index 568cfa7a..16e45b39 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -70,6 +70,8 @@ void ao_onResetRequest(OnOcppMessage onRequest); //alternative: start reset time void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); +void ao_bootNotification_full(const char *payloadJson, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); From eca45824a47c7a5513b2104e06d3720adcf70c3a Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 18 Jun 2022 11:53:27 +0200 Subject: [PATCH 021/549] update examples --- examples/ESP-TLS/main.cpp | 2 +- examples/SECC/README.md | 2 +- examples/SECC/main.cpp | 116 ++++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/examples/ESP-TLS/main.cpp b/examples/ESP-TLS/main.cpp index 5e9be62e..0bb11e53 100644 --- a/examples/ESP-TLS/main.cpp +++ b/examples/ESP-TLS/main.cpp @@ -87,8 +87,8 @@ void setup() { #elif defined(ESP32) WiFi.begin(STASSID, STAPSK); while (!WiFi.isConnected()) { - Serial.print('.'); delay(1000); + Serial.print('.'); } #endif diff --git a/examples/SECC/README.md b/examples/SECC/README.md index 4d9eeaa5..e1f7a7b5 100644 --- a/examples/SECC/README.md +++ b/examples/SECC/README.md @@ -15,7 +15,7 @@ You can find the interface descriptions in `main.ino`. Please be aware that alth - Copy the `main.ino` from this example folder into your source directory. Adapt the pinout settings if needed. - Finished. You should be able to compile and upload the sketch onto your ESP. -When booting, the ESP opens up the Wi-Fi configuration portal. Please connect your PC to the network with the SSID `EVSE_Maintenance_Portal` within the first 30s of the boot routine of the ESP (the portal has a timeout). The passphrase is `myEvseController`. +When booting, the ESP opens up the Wi-Fi configuration portal. Please connect your PC to the network with the SSID `EVSE-Config` within the first 30s of the boot routine of the ESP (the portal has a timeout). The passphrase is `evse1234`. ## Standalone mode diff --git a/examples/SECC/main.cpp b/examples/SECC/main.cpp index 40160cab..446dc6fa 100644 --- a/examples/SECC/main.cpp +++ b/examples/SECC/main.cpp @@ -74,11 +74,13 @@ #define CHARGE_PERMISSION_OFF HIGH #endif -#if DEBUG_OUT +#if !defined(SECC_NO_DEBUG) #define PRINT(...) Serial.print(__VA_ARGS__) +#define PRINTF(...) Serial.printf(__VA_ARGS__) #define PRINTLN(...) Serial.println(__VA_ARGS__) #else #define PRINT(...) +#define PRINTF(...) #define PRINTLN(...) #endif @@ -92,14 +94,15 @@ ulong scheduleReboot = 0; //0 = no reboot scheduled; otherwise reboot scheduled ulong reboot_timestamp = 0; //timestamp of the triggering event; if scheduleReboot=0, the timestamp has no meaning // ============ CAPTIVE PORTAL -#define CAPTIVE_PORTAL_TIMEOUT 30000 +#define CAPTIVE_PORTAL_TIMEOUT 60 //in seconds +#define WIFI_CONNECTION_TIMEOUT 30 //in seconds struct Ocpp_URL { bool isTLS = true; String host = String('\0'); uint16_t port = 443; String url = String('\0'); - bool parse(String &url); + bool parse(String& url); }; Ocpp_URL ocppUrlParsed = Ocpp_URL(); @@ -113,7 +116,9 @@ void setup() { * Initialize peripherals */ Serial.begin(115200); +#if !defined(SECC_NO_DEBUG) Serial.setDebugOutput(true); +#endif pinMode(EV_PLUG_PIN, INPUT); pinMode(EV_CHARGE_PIN, INPUT); pinMode(EVSE_GROUND_FAULT_PIN, INPUT); @@ -174,12 +179,10 @@ void setup() { ESP.restart(); } - PRINT(F("[main] host, port, URL: ")); - PRINT(ocppUrlParsed.host); - PRINT(F(", ")); - PRINT(ocppUrlParsed.port); - PRINT(F(", ")); - PRINTLN(ocppUrlParsed.url); + PRINTF("[main] host, port, URL: %s, %hu, %s\n", + ocppUrlParsed.host.isEmpty() ? "undefined" : ocppUrlParsed.host.c_str(), + ocppUrlParsed.port, + ocppUrlParsed.url.isEmpty() ? "undefined" : ocppUrlParsed.url.c_str()); /* * Initialize ArduinoOcpp framework. @@ -202,21 +205,19 @@ void setup() { PRINT('.'); now = time(nullptr); } - PRINT(F(" finished. Unix timestamp is ")); - PRINTLN(now); + PRINTF(" finished. Unix timestamp is %lu\n", now); wSock.beginSslWithCA(ocppUrlParsed.host.c_str(), ocppUrlParsed.port, ocppUrlParsed.url.c_str(), *CA_cert, "ocpp1.6"); } else { - wSock.beginSSL(ocppUrlParsed.host.c_str(), ocppUrlParsed.port, ocppUrlParsed.url.c_str(), NULL, "ocpp1.6"); + wSock.beginSSL(ocppUrlParsed.host.c_str(), ocppUrlParsed.port, ocppUrlParsed.url.c_str(), nullptr, "ocpp1.6"); } } else { wSock.begin(ocppUrlParsed.host, ocppUrlParsed.port, ocppUrlParsed.url, "ocpp1.6"); } OCPP_initialize(oSock, - /* Grid voltage */ 230.f, - ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail, - ArduinoOcpp::Clocks::DEFAULT_CLOCK); + 230.f, //European grid voltage + ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail); /* * Integrate OCPP functionality. You can leave out the following part if your EVSE doesn't need it. @@ -224,24 +225,26 @@ void setup() { setEnergyActiveImportSampler([]() { //read the energy input register of the EVSE here and return the value in Wh /* - * Approximated value. TODO: Replace with real reading + * Approximated value. Replace with real reading */ static ulong lastSampled = millis(); static float energyMeter = 0.f; - if (getTransactionId() > 0 && digitalRead(EV_CHARGE_PIN) == EV_CHARGING) - energyMeter += ((float) millis() - lastSampled) * 0.003f; //increase by 0.003Wh per ms (~ 10.8kWh per h) + if (getTransactionId() > 0) + energyMeter += ((float) (millis() - lastSampled)) * 0.003f; //increase by 0.003Wh per ms (~ 10.8kWh per h) lastSampled = millis(); return energyMeter; }); setOnChargingRateLimitChange([](float limit) { //set the SAE J1772 Control Pilot value here - PRINT(F("[main] Smart Charging allows maximum charge rate: ")); - PRINTLN(limit); - float amps = limit / 230.f; + const float voltage = 230.f; // European grid + const uint nPhases = 1; //one, two or three phase charging + float amps = limit / (voltage * (float) nPhases); if (amps > 51.f) amps = 51.f; + PRINTF("[main] Smart Charging allows maximum charge rate: %iW; convert to Control Pilot amerage: %.2fA\n", (int) limit, amps); + int pwmVal; if (amps < 6.f) { pwmVal = 256; // = constant +3.3V DC @@ -253,7 +256,6 @@ void setup() { ledcWrite(AMPERAGE_PIN, pwmVal); #elif defined(ESP8266) analogWrite(AMPERAGE_PIN, pwmVal); -#else #endif }); @@ -263,19 +265,20 @@ void setup() { }); addConnectorErrorCodeSampler([] () { -// if (digitalRead(EVSE_GROUND_FAULT_PIN) != EVSE_GROUND_CLEAR) { -// return "GroundFault"; -// } else { - return (const char *) NULL; -// } + //Uncomment if Ground fault pin is used + //if (digitalRead(EVSE_GROUND_FAULT_PIN) != EVSE_GROUND_CLEAR) { + // return "GroundFault"; + //} + return (const char *) nullptr; }); - setOnResetSendConf([] (JsonObject payload) { + setOnResetSendConf([] (JsonObject confirmation) { if (getTransactionId() >= 0) stopTransaction(); + PRINTLN(F("[main] Execute reset command")); reboot_timestamp = millis(); - scheduleReboot = 5000; + scheduleReboot = 5000; //reboot will be executed in loop() booted = false; }); @@ -284,15 +287,14 @@ void setup() { /* * Notify the Central System that this station is ready */ - bootNotification("My Charging Station", "My company name", [] (JsonObject payload) { - const char *status = payload["status"] | "INVALID"; - if (!strcmp(status, "Accepted")) { + bootNotification("My Charging Station", "My company name", [] (JsonObject response) { + if (response["status"].as().equals("Accepted")) { booted = true; digitalWrite(SERVER_CONNECT_LED, SERVER_CONNECT_ON); } else { - //retry sending the BootNotification - delay(60000); - ESP.restart(); + //Wait for the connection retry + reboot_timestamp = millis(); + scheduleReboot = 60000; //wait for 60s until reboot; reboot will be executed in loop() } }); } @@ -304,47 +306,49 @@ void loop() { */ OCPP_loop(); - /* - * Detect if something physical happened at your EVSE and trigger the corresponding OCPP messages - */ + //NFC reader integration example if (/* RFID chip detected? */ false) { const char *idTag = "my-id-tag"; //e.g. idTag = RFID.readIdTag(); authorize(idTag); } if (ocppPermitsCharge()) { + //EVSE is in a charging session and charging is permitted by the OCPP server digitalWrite(OCPP_CHARGE_PERMISSION_PIN, OCPP_CHARGE_PERMITTED); digitalWrite(CHARGE_PERMISSION_LED, CHARGE_PERMISSION_ON); } else { + //Charging is not allowed due to OCPP rules digitalWrite(OCPP_CHARGE_PERMISSION_PIN, OCPP_CHARGE_FORBIDDEN); digitalWrite(CHARGE_PERMISSION_LED, CHARGE_PERMISSION_OFF); } - if (!booted) - return; if (scheduleReboot > 0 && millis() - reboot_timestamp >= scheduleReboot) { ESP.restart(); } - if (digitalRead(EV_PLUG_PIN) == EV_PLUGGED && evPlugged == EV_UNPLUGGED && getTransactionId() >= 0) { - //transition unplugged -> plugged; Case A: transaction has already been initiated - evPlugged = EV_PLUGGED; - } else if (digitalRead(EV_PLUG_PIN) == EV_PLUGGED && evPlugged == EV_UNPLUGGED && isAvailable()) { - //transition unplugged -> plugged; Case B: no transaction running; start transaction - evPlugged = EV_PLUGGED; + if (!booted) { + return; + } + auto readEvPlugged = digitalRead(EV_PLUG_PIN); + if (evPlugged == EV_UNPLUGGED && readEvPlugged == EV_PLUGGED //transition from unplugged to plugged + && getTransactionId() < 0 //no transaction yet + && isAvailable()) { //EVSE is in operative mode startTransaction("my-id-tag"); - } else if (digitalRead(EV_PLUG_PIN) == EV_UNPLUGGED && evPlugged == EV_PLUGGED) { - //transition plugged -> unplugged - evPlugged = EV_UNPLUGGED; - - if (getTransactionId() >= 0) - stopTransaction(); + } else if (evPlugged == EV_PLUGGED && readEvPlugged == EV_UNPLUGGED //transition from plugged to unplugged + && getTransactionId() >= 0) { //need to stop transaction + stopTransaction(); } + evPlugged = readEvPlugged; //... see ArduinoOcpp.h for more possibilities } + + +// ### End of the OCPP integration. The following code integrates the +// ### WiFi-Manager into this example sketch. + bool runWiFiManager() { /* @@ -416,16 +420,16 @@ bool runWiFiManager() { wifiManager.setDarkMode(true); //wifiManager.setConfigPortalTimeout(CAPATITIVE_PORTAL_TIMEOUT / 1000); //if nobody logs in to the portal, continue after timeout - wifiManager.setTimeout(CAPTIVE_PORTAL_TIMEOUT / 1000); //if nobody logs in to the portal, continue after timeout - wifiManager.setConnectTimeout(CAPTIVE_PORTAL_TIMEOUT / 1000); + wifiManager.setTimeout(CAPTIVE_PORTAL_TIMEOUT); //if nobody logs in to the portal, continue after timeout + wifiManager.setConnectTimeout(WIFI_CONNECTION_TIMEOUT); //wifiManager.setSaveConnect(true); wifiManager.setAPClientCheck(true); // avoid timeout if client connected to softap PRINTLN(F("[main] Start capatitive portal")); - if (wifiManager.startConfigPortal("EVSE_Maintenance_Portal", "myEvseController")) { + if (wifiManager.startConfigPortal("EVSE-Config", "evse1234")) { return true; } else { - return wifiManager.autoConnect("EVSE_Maintenance_Portal", "myEvseController"); + return wifiManager.autoConnect("EVSE-Config", "evse1234"); } } From f2956cbb7fbf5b240c07cca372cca785c8c63416 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:18:19 +0200 Subject: [PATCH 022/549] update Status documentation --- .../MessagesV16/StatusNotification.cpp | 2 +- .../ChargePointStatus/ConnectorStatus.cpp | 1 + .../Tasks/ChargePointStatus/OcppEvseState.h | 20 +++++++++---------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp b/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp index 72cdff6a..89350484 100644 --- a/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp +++ b/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp @@ -45,7 +45,7 @@ const char *cstrFromOcppEveState(OcppEvseState state) { StatusNotification::StatusNotification(int connectorId, OcppEvseState currentStatus, const OcppTimestamp &otimestamp, const char *errorCode) : connectorId(connectorId), currentStatus(currentStatus), otimestamp(otimestamp), errorCode(errorCode) { - AO_DBG_INFO("New status: %s", cstrFromOcppEveState(currentStatus)); + AO_DBG_INFO("New status: %s (connectorId %d)", cstrFromOcppEveState(currentStatus), connectorId); } const char* StatusNotification::getOcppOperationType(){ diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index eb90a08f..0a8d67fa 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -37,6 +37,7 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) minimumStatusDuration = declareConfiguration("MinimumStatusDuration", 0, CONFIGURATION_FN, true, true, true, false); stopTransactionOnInvalidId = declareConfiguration("StopTransactionOnInvalidId", "true", CONFIGURATION_FN, true, true, false, false); stopTransactionOnEVSideDisconnect = declareConfiguration("StopTransactionOnEVSideDisconnect", "true", CONFIGURATION_FN, true, true, false, false); + unlockConnectorOnEVSideDisconnect = declareConfiguration("UnlockConnectorOnEVSideDisconnect", "true", CONFIGURATION_FN, true, true, false, false); localAuthorizeOffline = declareConfiguration("LocalAuthorizeOffline", "false", CONFIGURATION_FN, true, true, false, false); localPreAuthorize = declareConfiguration("LocalPreAuthorize", "false", CONFIGURATION_FN, true, true, false, false); diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/OcppEvseState.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/OcppEvseState.h index 00a49152..a3d9964a 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/OcppEvseState.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/OcppEvseState.h @@ -8,16 +8,16 @@ namespace ArduinoOcpp { enum class OcppEvseState { - Available, - Preparing, - Charging, - SuspendedEVSE, - SuspendedEV, - Finishing, //not supported by this client - Reserved, //not supported by this client - Unavailable, - Faulted, - NOT_SET //not part of OCPP 1.6 + Available, + Preparing, + Charging, + SuspendedEVSE, + SuspendedEV, + Finishing, + Reserved, + Unavailable, + Faulted, + NOT_SET //internal value for "undefined" }; } //end namespace ArduinoOcpp From a45d3a822c031394e50b52324a9c291feb4a2e01 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 18 Jun 2022 23:07:39 +0200 Subject: [PATCH 023/549] more Smart Charging Configuration keys --- src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp | 3 +++ src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp index b4cd67ce..0dd66ba8 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp @@ -44,6 +44,9 @@ SmartChargingService::SmartChargingService(OcppEngine& context, float chargeLimi TxProfile[i] = NULL; } declareConfiguration("ChargeProfileMaxStackLevel", CHARGEPROFILEMAXSTACKLEVEL, CONFIGURATION_VOLATILE, false, true, false, false); + declareConfiguration("ChargingScheduleAllowedChargingRateUnit ", "Power", CONFIGURATION_VOLATILE, false, true, false, false); + declareConfiguration("ChargingScheduleMaxPeriods", CHARGINGSCHEDULEMAXPERIODS, CONFIGURATION_VOLATILE, false, true, false, false); + declareConfiguration("MaxChargingProfilesInstalled", MAXCHARGINGPROFILESINSTALLED, CONFIGURATION_VOLATILE, false, true, false, false); const char *fpId = "SmartCharging"; auto fProfile = declareConfiguration("SupportedFeatureProfiles",fpId, CONFIGURATION_VOLATILE, false, true, true, false); diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h index 53546204..8dd10dc6 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h @@ -5,7 +5,9 @@ #ifndef SMARTCHARGINGSERVICE_H #define SMARTCHARGINGSERVICE_H -#define CHARGEPROFILEMAXSTACKLEVEL 20 +#define CHARGEPROFILEMAXSTACKLEVEL 8 +#define CHARGINGSCHEDULEMAXPERIODS 24 +#define MAXCHARGINGPROFILESINSTALLED 10 #include #include From 5f3a729d62f0337aaba20905596d91d56f09b088 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 19 Jun 2022 11:16:49 +0200 Subject: [PATCH 024/549] cache BootNotification credentials --- src/ArduinoOcpp.cpp | 10 ++- .../MessagesV16/BootNotification.cpp | 74 +++++++++---------- .../MessagesV16/BootNotification.h | 17 ++--- .../ChargePointStatusService.cpp | 23 ++++++ .../ChargePointStatusService.h | 5 ++ 5 files changed, 78 insertions(+), 51 deletions(-) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index f488c258..9add110d 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -374,8 +374,14 @@ void bootNotification(const char *chargePointModel, const char *chargePointVendo AO_DBG_ERR("Please call OCPP_initialize before"); return; } + + auto credentials = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(2) + strlen(chargePointModel) + strlen(chargePointVendor) + 2)); + (*credentials)["chargePointModel"] = (char*) chargePointModel; + (*credentials)["chargePointVendor"] = (char*) chargePointVendor; + auto bootNotification = makeOcppOperation( - new BootNotification(chargePointModel, chargePointVendor)); + new BootNotification(std::move(credentials))); if (onConf) bootNotification->setOnReceiveConfListener(onConf); if (onAbort) @@ -397,7 +403,7 @@ void bootNotification(DynamicJsonDocument *payload, OnReceiveConfListener onConf return; } auto bootNotification = makeOcppOperation( - new BootNotification(payload)); + new BootNotification(std::unique_ptr(payload))); if (onConf) bootNotification->setOnReceiveConfListener(onConf); if (onAbort) diff --git a/src/ArduinoOcpp/MessagesV16/BootNotification.cpp b/src/ArduinoOcpp/MessagesV16/BootNotification.cpp index ab5a2309..3bae171e 100644 --- a/src/ArduinoOcpp/MessagesV16/BootNotification.cpp +++ b/src/ArduinoOcpp/MessagesV16/BootNotification.cpp @@ -13,56 +13,56 @@ using ArduinoOcpp::Ocpp16::BootNotification; BootNotification::BootNotification() { - -} - -BootNotification::BootNotification(const char *cpModel, const char *cpVendor) { - snprintf(chargePointModel, CP_MODEL_LEN_MAX + 1, "%s", cpModel); - snprintf(chargePointVendor, CP_VENDOR_LEN_MAX + 1, "%s", cpVendor); -} - -BootNotification::BootNotification(const char *cpModel, const char *cpSerialNumber, const char *cpVendor, const char *fwVersion) { - snprintf(chargePointModel, CP_MODEL_LEN_MAX + 1, "%s", cpModel); - snprintf(chargePointSerialNumber, CP_SERIALNUMBER_LEN_MAX + 1, "%s", cpSerialNumber); - snprintf(chargePointVendor, CP_VENDOR_LEN_MAX + 1, "%s", cpVendor); - snprintf(firmwareVersion, FW_VERSION_LEN_MAX + 1, "%s", fwVersion); -} - -BootNotification::BootNotification(DynamicJsonDocument *payload) { - this->overridePayload = payload; + } -BootNotification::~BootNotification() { - if (overridePayload != nullptr) - delete overridePayload; +BootNotification::BootNotification(std::unique_ptr payload) : credentials(std::move(payload)) { + } const char* BootNotification::getOcppOperationType(){ return "BootNotification"; } +void BootNotification::initiate() { + if (credentials && + ocppModel && ocppModel->getChargePointStatusService()) { + auto cpStatus = ocppModel->getChargePointStatusService(); + cpStatus->setChargePointCredentials(*credentials); + credentials.release(); + } +} + std::unique_ptr BootNotification::createReq() { - if (overridePayload != nullptr) { - auto result = std::unique_ptr(new DynamicJsonDocument(*overridePayload)); - return result; - } + if (ocppModel && ocppModel->getChargePointStatusService()) { + auto cpStatus = ocppModel->getChargePointStatusService(); + const auto& cpCredentials = cpStatus->getChargePointCredentials(); + + std::unique_ptr doc; + size_t capacity = JSON_OBJECT_SIZE(9) + cpCredentials.size(); + DeserializationError err = DeserializationError::NoMemory; + while (err == DeserializationError::NoMemory) { + doc.reset(new DynamicJsonDocument(capacity)); + err = deserializeJson(*doc, cpCredentials); + + capacity *= 3; + capacity /= 2; + } - auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(4) - + strlen(chargePointModel) + 1 - + strlen(chargePointVendor) + 1 - + strlen(chargePointSerialNumber) + 1 - + strlen(firmwareVersion) + 1)); - JsonObject payload = doc->to(); - payload["chargePointModel"] = chargePointModel; - if (chargePointSerialNumber[0]) { - payload["chargePointSerialNumber"] = chargePointSerialNumber; + if (!err) { + return doc; + } else { + AO_DBG_ERR("could not parse stored credentials: %s", err.c_str()); + } } - payload["chargePointVendor"] = chargePointVendor; - if (firmwareVersion[0]) { - payload["firmwareVersion"] = firmwareVersion; + + if (credentials) { + return std::unique_ptr(new DynamicJsonDocument(*credentials)); } - return doc; + + AO_DBG_ERR("payload undefined"); + return createEmptyDocument(); } void BootNotification::processConf(JsonObject payload){ diff --git a/src/ArduinoOcpp/MessagesV16/BootNotification.h b/src/ArduinoOcpp/MessagesV16/BootNotification.h index 54449802..5f33364a 100644 --- a/src/ArduinoOcpp/MessagesV16/BootNotification.h +++ b/src/ArduinoOcpp/MessagesV16/BootNotification.h @@ -18,25 +18,18 @@ namespace Ocpp16 { class BootNotification : public OcppMessage { private: - char chargePointModel [CP_MODEL_LEN_MAX + 1] = {'\0'}; - char chargePointSerialNumber [CP_SERIALNUMBER_LEN_MAX + 1] = {'\0'}; - char chargePointVendor [CP_VENDOR_LEN_MAX + 1] = {'\0'}; - char firmwareVersion [FW_VERSION_LEN_MAX + 1] = {'\0'}; - - DynamicJsonDocument *overridePayload = NULL; + std::unique_ptr credentials; public: BootNotification(); - ~BootNotification(); - - BootNotification(const char *chargePointModel, const char *chargePointVendor); + ~BootNotification() = default; - BootNotification(const char *chargePointModel, const char *chargePointSerialNumber, const char *chargePointVendor, const char *firmwareVersion); - - BootNotification(DynamicJsonDocument *payload); + BootNotification(std::unique_ptr payload); const char* getOcppOperationType(); + void initiate(); + std::unique_ptr createReq(); void processConf(JsonObject payload); diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp index 7a43ac50..ce616d72 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp @@ -86,3 +86,26 @@ bool ChargePointStatusService::isBooted() { int ChargePointStatusService::getNumConnectors() { return connectors.size(); } + +void ChargePointStatusService::setChargePointCredentials(DynamicJsonDocument &credentials) { + if (!credentials.is()) { + AO_DBG_ERR("Payload must be JSON object"); + cpCredentials.clear(); + return; + } + auto written = serializeJson(credentials, cpCredentials); + if (written <= 2) { + AO_DBG_ERR("Could not parse CP credentials: %s", written == 2 ? "format violation" : "invalid JSON"); + cpCredentials.clear(); + return; + } + //success +} + +std::string& ChargePointStatusService::getChargePointCredentials() { + if (cpCredentials.size() <= 2) { + cpCredentials = "{}"; + } + + return cpCredentials; +} diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h index ce95f59e..7526d352 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h @@ -21,6 +21,8 @@ class ChargePointStatusService { bool booted = false; + std::string cpCredentials; + public: ChargePointStatusService(OcppEngine& context, unsigned int numConnectors); @@ -33,6 +35,9 @@ class ChargePointStatusService { ConnectorStatus *getConnector(int connectorId); int getNumConnectors(); + + void setChargePointCredentials(DynamicJsonDocument &credentials); + std::string& getChargePointCredentials(); }; } //end namespace ArduinoOcpp From 0d870bb448af3147e8e50c3517ff6b5ab6f01b2a Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 20 Jun 2022 23:24:23 +0200 Subject: [PATCH 025/549] fix ClearChargingProfile not removed --- src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h | 6 +++--- .../Tasks/SmartCharging/SmartChargingService.cpp | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h index d909e6a7..9a107e8c 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h @@ -94,9 +94,9 @@ class ChargingProfile { int chargingProfileId = -1; int transactionId = -1; int stackLevel = 0; - ChargingProfilePurposeType chargingProfilePurpose; - ChargingProfileKindType chargingProfileKind; //copied to ChargingSchedule to increase cohesion of limit inferencing methods - RecurrencyKindType recurrencyKind; // copied to ChargingSchedule to increase cohesion + ChargingProfilePurposeType chargingProfilePurpose {ChargingProfilePurposeType::TxProfile}; + ChargingProfileKindType chargingProfileKind {ChargingProfileKindType::Relative}; //copied to ChargingSchedule to increase cohesion of limit inferencing methods + RecurrencyKindType recurrencyKind {RecurrencyKindType::NOT_SET}; // copied to ChargingSchedule to increase cohesion OcppTimestamp validFrom; OcppTimestamp validTo; std::unique_ptr chargingSchedule; diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp index 0dd66ba8..08e752ae 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp @@ -330,6 +330,7 @@ bool SmartChargingService::clearChargingProfile(const std::function Date: Mon, 20 Jun 2022 23:27:08 +0200 Subject: [PATCH 026/549] fix SuspendedEVSE state --- src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index 0a8d67fa..f61b5f91 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -386,7 +386,7 @@ void ConnectorStatus::setEvRequestsEnergySampler(std::function evRequest } void ConnectorStatus::setConnectorEnergizedSampler(std::function connectorEnergized) { - this->connectorEnergizedSampler = connectorEnergizedSampler; + this->connectorEnergizedSampler = connectorEnergized; } void ConnectorStatus::addConnectorErrorCodeSampler(std::function connectorErrorCode) { From d29698ec281a7e6e3620a1c7b08ad2cd94e4af3f Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 20 Jun 2022 23:27:27 +0200 Subject: [PATCH 027/549] update BootNotification facade call --- src/ArduinoOcpp.cpp | 20 +++----------------- src/ArduinoOcpp.h | 2 +- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 9add110d..c2cd64a2 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -380,30 +380,16 @@ void bootNotification(const char *chargePointModel, const char *chargePointVendo (*credentials)["chargePointModel"] = (char*) chargePointModel; (*credentials)["chargePointVendor"] = (char*) chargePointVendor; - auto bootNotification = makeOcppOperation( - new BootNotification(std::move(credentials))); - if (onConf) - bootNotification->setOnReceiveConfListener(onConf); - if (onAbort) - bootNotification->setOnAbortListener(onAbort); - if (onTimeout) - bootNotification->setOnTimeoutListener(onTimeout); - if (onError) - bootNotification->setOnReceiveErrorListener(onError); - if (timeout) - bootNotification->setTimeout(std::move(timeout)); - else - bootNotification->setTimeout(std::unique_ptr (new SuppressedTimeout())); - ocppEngine->initiateOperation(std::move(bootNotification)); + bootNotification(std::move(credentials), onConf, onAbort, onTimeout, onError, std::move(timeout)); } -void bootNotification(DynamicJsonDocument *payload, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { +void bootNotification(std::unique_ptr payload, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); return; } auto bootNotification = makeOcppOperation( - new BootNotification(std::unique_ptr(payload))); + new BootNotification(std::move(payload))); if (onConf) bootNotification->setOnReceiveConfListener(onConf); if (onAbort) diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index c22b2088..c24ed0f4 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -145,7 +145,7 @@ void authorize(const char *idTag, OnReceiveConfListener onConf = nullptr, OnAbor void bootNotification(const char *chargePointModel, const char *chargePointVendor, OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); //The OCPP operation will include the given payload without modifying it. The library will delete the payload object after successful transmission. -void bootNotification(DynamicJsonDocument *payload, OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); +void bootNotification(std::unique_ptr payload, OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); void startTransaction(const char *idTag, OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); From 235202fcd1c774c8f41ac178bd32d0e0d55dc3fb Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 25 Jun 2022 00:32:46 +0200 Subject: [PATCH 028/549] improved Reset integration --- src/ArduinoOcpp.cpp | 4 ++ src/ArduinoOcpp/MessagesV16/Reset.cpp | 20 ++++-- src/ArduinoOcpp/MessagesV16/Reset.h | 2 + .../ChargePointStatusService.cpp | 65 ++++++++++++++++++- .../ChargePointStatusService.h | 31 +++++++++ .../ChargePointStatus/ConnectorStatus.cpp | 12 ++++ .../Tasks/ChargePointStatus/ConnectorStatus.h | 3 + 7 files changed, 129 insertions(+), 8 deletions(-) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index c2cd64a2..7f5333fc 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -110,6 +110,10 @@ void OCPP_initialize(OcppSocket& ocppSocket, float V_eff, ArduinoOcpp::Filesyste new DiagnosticsService(*ocppEngine))); #endif +#if !defined(AO_CUSTOM_RESET) + model.getChargePointStatusService()->setExecuteReset(EspWiFi::makeDefaultResetFn()); +#endif + ocppEngine->setRunOcppTasks(false); //prevent OCPP classes from doing anything while booting } diff --git a/src/ArduinoOcpp/MessagesV16/Reset.cpp b/src/ArduinoOcpp/MessagesV16/Reset.cpp index 9e751ca6..0e54e8ef 100644 --- a/src/ArduinoOcpp/MessagesV16/Reset.cpp +++ b/src/ArduinoOcpp/MessagesV16/Reset.cpp @@ -25,19 +25,25 @@ void Reset::processReq(JsonObject payload) { if (ocppModel && ocppModel->getChargePointStatusService()) { auto cpsService = ocppModel->getChargePointStatusService(); - int connId = 0; - for (int i = 0; i < cpsService->getNumConnectors(); i++) { - auto connector = cpsService->getConnector(connId); - if (connector) { - connector->endSession(isHard ? "HardReset" : "SoftReset"); + if (!cpsService->getPreReset() || cpsService->getPreReset()(isHard) || isHard) { + resetAccepted = true; + cpsService->initiateReset(isHard); + int connId = 0; + for (int i = 0; i < cpsService->getNumConnectors(); i++) { + auto connector = cpsService->getConnector(connId); + if (connector) { + connector->endSession(isHard ? "HardReset" : "SoftReset"); + } } } + } else { + resetAccepted = true; //assume that onReceiveReset is set } } -std::unique_ptr Reset::createConf(){ +std::unique_ptr Reset::createConf() { auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); JsonObject payload = doc->to(); - payload["status"] = "Accepted"; + payload["status"] = resetAccepted ? "Accepted" : "Rejected"; return doc; } diff --git a/src/ArduinoOcpp/MessagesV16/Reset.h b/src/ArduinoOcpp/MessagesV16/Reset.h index e7dcfa83..7693dddb 100644 --- a/src/ArduinoOcpp/MessagesV16/Reset.h +++ b/src/ArduinoOcpp/MessagesV16/Reset.h @@ -11,6 +11,8 @@ namespace ArduinoOcpp { namespace Ocpp16 { class Reset : public OcppMessage { +private: + bool resetAccepted {false}; public: Reset(); diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp index ce616d72..7ac60c4a 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp @@ -12,6 +12,8 @@ #include #include +#define RESET_DELAY 15000 + using namespace ArduinoOcpp; ChargePointStatusService::ChargePointStatusService(OcppEngine& context, unsigned int numConn) @@ -20,7 +22,6 @@ ChargePointStatusService::ChargePointStatusService(OcppEngine& context, unsigned for (unsigned int i = 0; i < numConn; i++) { connectors.push_back(std::unique_ptr(new ConnectorStatus(context.getOcppModel(), i))); } - std::shared_ptr> numberOfConnectors = declareConfiguration("NumberOfConnectors", numConn >= 1 ? numConn - 1 : 0, CONFIGURATION_VOLATILE, false, true, false, false); @@ -44,6 +45,8 @@ ChargePointStatusService::ChargePointStatusService(OcppEngine& context, unsigned fProfile->setValue(fProfilePlus.c_str(), fProfilePlus.length() + 1); } + resetRetries = declareConfiguration("ResetRetries", 2, CONFIGURATION_FN, true, true, false, false); + /* * Further configuration keys which correspond to the Core profile */ @@ -64,6 +67,25 @@ void ChargePointStatusService::loop() { context.initiateOperation(std::move(statusNotification)); } } + + if (outstandingResetRetries > 0 && ao_tick_ms() - t_resetRetry >= RESET_DELAY) { + t_resetRetry = ao_tick_ms(); + outstandingResetRetries--; + if (executeReset) { + AO_DBG_INFO("Reset device"); + executeReset(isHardReset); + } else { + AO_DBG_ERR("No Reset function set! Abort"); + outstandingResetRetries = 0; + } + AO_DBG_ERR("Reset device failure. %s", outstandingResetRetries == 0 ? "Abort" : "Retry"); + + if (outstandingResetRetries <= 0) { + for (auto connector = connectors.begin(); connector != connectors.end(); connector++) { + (*connector)->setRebooting(false); + } + } + } } ConnectorStatus *ChargePointStatusService::getConnector(int connectorId) { @@ -109,3 +131,44 @@ std::string& ChargePointStatusService::getChargePointCredentials() { return cpCredentials; } + +void ChargePointStatusService::setPreReset(std::function preReset) { + this->preReset = preReset; +} + +std::function ChargePointStatusService::getPreReset() { + return this->preReset; +} + +void ChargePointStatusService::setExecuteReset(std::function executeReset) { + this->executeReset = executeReset; +} + +std::function ChargePointStatusService::getExecuteReset() { + return this->executeReset; +} + +void ChargePointStatusService::initiateReset(bool isHard) { + isHardReset = isHard; + outstandingResetRetries = 1 + *resetRetries; //one initial try + no. of retries + if (outstandingResetRetries > 5) { + AO_DBG_ERR("no. of reset trials exceeds 5"); + outstandingResetRetries = 5; + } + t_resetRetry = ao_tick_ms(); + + for (auto connector = connectors.begin(); connector != connectors.end(); connector++) { + (*connector)->setRebooting(true); + } +} + +#if !defined(AO_CUSTOM_RESET) +#if defined(ESP32) || defined(ESP8266) +std::function ArduinoOcpp::EspWiFi::makeDefaultResetFn() { + return [] (bool isHard) { + AO_DBG_DEBUG("Perform ESP reset"); + ESP.restart(); + }; +} +#endif //defined(ESP32) || defined(ESP8266) +#endif //!defined(AO_CUSTOM_UPDATER) && !defined(AO_CUSTOM_WS) diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h index 7526d352..2add4e7c 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.h @@ -23,6 +23,14 @@ class ChargePointStatusService { std::string cpCredentials; + std::function preReset; //true: reset is possible; false: reject reset; Await: need more time to determine + std::function executeReset; //please disconnect WebSocket (AO remains initialized), shut down device and restart with normal initialization routine; on failure reconnect WebSocket + uint outstandingResetRetries = 0; //0 = do not reset device + bool isHardReset = false; + ulong t_resetRetry; + + std::shared_ptr> resetRetries; + public: ChargePointStatusService(OcppEngine& context, unsigned int numConnectors); @@ -38,7 +46,30 @@ class ChargePointStatusService { void setChargePointCredentials(DynamicJsonDocument &credentials); std::string& getChargePointCredentials(); + + void setPreReset(std::function preReset); + std::function getPreReset(); + + void setExecuteReset(std::function executeReset); + std::function getExecuteReset(); + + void initiateReset(bool isHard); }; } //end namespace ArduinoOcpp + +#if !defined(AO_CUSTOM_RESET) +#if defined(ESP32) || defined(ESP8266) + +namespace ArduinoOcpp { +namespace EspWiFi { + +std::function makeDefaultResetFn(); + +} +} + +#endif //defined(ESP32) || defined(ESP8266) +#endif //!defined(AO_CUSTOM_UPDATER) && !defined(AO_CUSTOM_WS) + #endif diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index f61b5f91..e4682138 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -86,6 +86,8 @@ OcppEvseState ConnectorStatus::inferenceStatus() { return OcppEvseState::Faulted; } else if (*availability == AVAILABILITY_INOPERATIVE) { return OcppEvseState::Unavailable; + } else if (rebooting) { + return OcppEvseState::Unavailable; } else { return OcppEvseState::Available; } @@ -95,6 +97,8 @@ OcppEvseState ConnectorStatus::inferenceStatus() { return OcppEvseState::Faulted; } else if (*availability == AVAILABILITY_INOPERATIVE) { return OcppEvseState::Unavailable; + } else if (rebooting && getTransactionId() < 0) { + return OcppEvseState::Unavailable; } else if (getTransactionId() == 0 && // i.e. Tx pending or EVSE offline. Check if offline Tx is OFF !(*localAuthorizeOffline && strcmp(*localAuthorizeOffline, "false")) && !(*localPreAuthorize && strcmp(*localPreAuthorize, "false"))) { @@ -334,6 +338,10 @@ const char *ConnectorStatus::getSessionIdTag() { return session ? idTag : nullptr; } +uint16_t ConnectorStatus::getSessionWriteCount() { + return sIdTag->getValueRevision(); +} + int ConnectorStatus::getTransactionId() { return *transactionId; } @@ -374,6 +382,10 @@ void ConnectorStatus::setAvailability(bool available) { saveState(); } +void ConnectorStatus::setRebooting(bool rebooting) { + this->rebooting = rebooting; +} + void ConnectorStatus::setConnectorPluggedSampler(std::function connectorPlugged) { this->connectorPluggedSampler = connectorPlugged; txTriggerConditions.push_back([this] () -> TxCondition { diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h index 940e1d35..ec5492ba 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h @@ -31,6 +31,7 @@ class ConnectorStatus { const int connectorId; std::shared_ptr> availability; + bool rebooting = false; //report connector inoperative and reject new charging sessions bool session = false; char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; @@ -86,6 +87,7 @@ class ConnectorStatus { void endSession(const char *reason = nullptr); void setIdTagInvalidated(); //if StartTransaction.conf() has status != "Accepted" const char *getSessionIdTag(); + uint16_t getSessionWriteCount(); int getTransactionId(); int getTransactionIdSync(); uint16_t getTransactionWriteCount(); @@ -94,6 +96,7 @@ class ConnectorStatus { int getAvailability(); void setAvailability(bool available); + void setRebooting(bool rebooting); void setAuthorizationProvider(std::function authorization); void setConnectorPluggedSampler(std::function connectorPlugged); void setEvRequestsEnergySampler(std::function evRequestsEnergy); From 0041934bc22da9448f1f89b4b2aa1dd47435203c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 26 Jun 2022 17:53:44 +0200 Subject: [PATCH 029/549] RmtTx charging profile support --- .../MessagesV16/RemoteStartTransaction.cpp | 55 ++++++++++-- .../MessagesV16/RemoteStartTransaction.h | 5 ++ .../MessagesV16/SetChargingProfile.cpp | 2 +- .../SmartCharging/SmartChargingModel.cpp | 35 +++++--- .../Tasks/SmartCharging/SmartChargingModel.h | 4 +- .../SmartCharging/SmartChargingService.cpp | 88 +++++++++++++++---- .../SmartCharging/SmartChargingService.h | 14 ++- 7 files changed, 158 insertions(+), 45 deletions(-) diff --git a/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp index 4100b291..39835d7a 100644 --- a/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp @@ -5,7 +5,9 @@ #include #include +#include #include +#include #include using ArduinoOcpp::Ocpp16::RemoteStartTransaction; @@ -27,20 +29,28 @@ void RemoteStartTransaction::processReq(JsonObject payload) { snprintf(idTag, IDTAG_LEN_MAX + 1, "%s", idTagIn); } + if (*idTag == '\0') { + AO_DBG_WARN("idTag format violation"); + errorCode = "FormationViolation"; + } + if (payload.containsKey("chargingProfile")) { - AO_DBG_WARN("chargingProfile via RmtStartTransaction not supported yet"); + AO_DBG_INFO("Setting Charging profile via RemoteStartTransaction"); + + JsonObject chargingProfile = payload["chargingProfile"]; + if ((chargingProfile["chargingProfileId"] | -1) < 0) { + AO_DBG_WARN("RemoteStartTx profile requires non-negative chargingProfileId"); + errorCode = chargingProfile.containsKey("chargingProfileId") ? + "PropertyConstraintViolation" : "FormationViolation"; + } + chargingProfileDoc = DynamicJsonDocument(chargingProfile.memoryUsage()); //copy TxProfile + chargingProfileDoc.set(chargingProfile); } } std::unique_ptr RemoteStartTransaction::createConf(){ auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); JsonObject payload = doc->to(); - - if (*idTag == '\0') { - AO_DBG_WARN("idTag format violation"); - payload["status"] = "Rejected"; - return doc; - } bool canStartTransaction = false; if (connectorId >= 1) { @@ -70,13 +80,42 @@ std::unique_ptr RemoteStartTransaction::createConf(){ } } - if (canStartTransaction){ + if (canStartTransaction) { + + auto sRmtProfileId = declareConfiguration("AO_SRMTPROFILEID_CONN_1", -1, CONFIGURATION_FN, false, false, true, false); + + if (ocppModel && ocppModel->getSmartChargingService()) { + auto scService = ocppModel->getSmartChargingService(); + + if (*sRmtProfileId >= 0) { + int clearProfileId = *sRmtProfileId; + bool ret = scService->clearChargingProfile([clearProfileId](int id, int, ChargingProfilePurposeType, int) { + return id == clearProfileId; + }); + + *sRmtProfileId = -1; + AO_DBG_DEBUG("Cleared Charging Profile from previous RemoteStartTx: %s", ret ? "success" : "already cleared"); + configuration_save(); + } + } + if (ocppModel && ocppModel->getConnectorStatus(connectorId)) { auto connector = ocppModel->getConnectorStatus(connectorId); connector->beginSession(idTag); } + if (!chargingProfileDoc.isNull() + && (ocppModel && ocppModel->getSmartChargingService())) { + auto scService = ocppModel->getSmartChargingService(); + + JsonObject chargingProfile = chargingProfileDoc.as(); + scService->setChargingProfile(chargingProfile); + *sRmtProfileId = chargingProfile["chargingProfileId"].as(); + AO_DBG_DEBUG("Charging Profile from RemoteStartTx set"); + configuration_save(); + } + payload["status"] = "Accepted"; } else { AO_DBG_INFO("No connector to start transaction"); diff --git a/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.h b/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.h index f8026d6b..f3d077b6 100644 --- a/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.h @@ -15,6 +15,9 @@ class RemoteStartTransaction : public OcppMessage { private: int connectorId; char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; + DynamicJsonDocument chargingProfileDoc {0}; + + const char *errorCode {nullptr}; public: RemoteStartTransaction(); @@ -27,6 +30,8 @@ class RemoteStartTransaction : public OcppMessage { void processReq(JsonObject payload); std::unique_ptr createConf(); + + const char *getErrorCode() {return errorCode;} }; } //end namespace Ocpp16 diff --git a/src/ArduinoOcpp/MessagesV16/SetChargingProfile.cpp b/src/ArduinoOcpp/MessagesV16/SetChargingProfile.cpp index da4c18d0..5006130a 100644 --- a/src/ArduinoOcpp/MessagesV16/SetChargingProfile.cpp +++ b/src/ArduinoOcpp/MessagesV16/SetChargingProfile.cpp @@ -34,7 +34,7 @@ void SetChargingProfile::processReq(JsonObject payload) { if (ocppModel && ocppModel->getSmartChargingService()) { auto smartChargingService = ocppModel->getSmartChargingService(); - smartChargingService->updateChargingProfile(&csChargingProfiles); + smartChargingService->setChargingProfile(csChargingProfiles); } } diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp index fd34dd7c..1ddf7c94 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp @@ -46,7 +46,7 @@ int ChargingSchedulePeriod::getNumberPhases(){ } void ChargingSchedulePeriod::printPeriod(){ - AO_DBG_INFO("CHARGING SCHEDULE PERIOD:\n" \ + AO_DBG_VERBOSE("CHARGING SCHEDULE PERIOD:\n" \ " startPeriod: %i\n" \ " limit: %f\n" \ " numberPhases: %i\n", @@ -267,7 +267,7 @@ void ChargingSchedule::printSchedule(){ char tmp[JSONDATE_LENGTH + 1] = {'\0'}; startSchedule.toJsonString(tmp, JSONDATE_LENGTH + 1); - AO_DBG_INFO("CHARGING SCHEDULE:\n" \ + AO_DBG_VERBOSE("CHARGING SCHEDULE:\n" \ " duration: %i\n" \ " startSchedule: %s\n" \ " chargingRateUnit: %s\n" \ @@ -351,15 +351,28 @@ bool ChargingProfile::inferenceLimit(const OcppTimestamp &t, float *limit, OcppT return inferenceLimit(t, MAX_TIME, limit, nextChange); } -bool ChargingProfile::checkTransactionId(int chargingSessionTransactionID) { - if (transactionId >= 0 && chargingSessionTransactionID >= 0){ - //Transaction IDs are valid - if (chargingProfilePurpose == ChargingProfilePurposeType::TxProfile //only then a transactionId can restrict the limits - && transactionId != chargingSessionTransactionID) { - return false; - } +bool ChargingProfile::checkTransactionAssignment(int txId, int profileId) { + if (chargingProfilePurpose != ChargingProfilePurposeType::TxProfile) { + AO_DBG_ERR("assignment only exists for TxProfiles"); + return true; //does not apply to this profile -> no restriction } - return true; + + if (txId <= 0 && profileId < 0) { + //no search parameters set + return true; + } + + if (chargingProfileId >= 0 && profileId >= 0) { //profileIDs are valid + return chargingProfileId == profileId; //return if they match (case of remote charging profiles) + } + + if (transactionId > 0 && txId > 0) { //txIDs are valid + return transactionId == txId; //return if they do match + } + + AO_DBG_ERR("Check error"); + //neither txIds nor profileIDs apply + return false; } int ChargingProfile::getStackLevel(){ @@ -381,7 +394,7 @@ void ChargingProfile::printProfile(){ char tmp2[JSONDATE_LENGTH + 1] = {'\0'}; validTo.toJsonString(tmp2, JSONDATE_LENGTH + 1); - AO_DBG_INFO("CHARGING PROFILE:\n" \ + AO_DBG_VERBOSE("CHARGING PROFILE:\n" \ " chargingProfileId: %i\n" \ " transactionId: %i\n" \ " stackLevel: %i\n" \ diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h index 9a107e8c..2cb8b37d 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.h @@ -119,9 +119,9 @@ class ChargingProfile { bool inferenceLimit(const OcppTimestamp &t, float *limit, OcppTimestamp *nextChange); /* - * Check if this profile belongs to transaction with ID transId + * Check if this profile belongs to transaction with ID txId or idTag alternatively */ - bool checkTransactionId(int transId); + bool checkTransactionAssignment(int txId, int profileId); int getStackLevel(); diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp index 08e752ae..52e3cd04 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp @@ -37,7 +37,11 @@ SmartChargingService::SmartChargingService(OcppEngine& context, float chargeLimi limitBeforeChange = -1.0f; nextChange = MIN_TIME; chargingSessionStart = MAX_TIME; + char max_timestamp [JSONDATE_LENGTH + 1] = {'\0'}; + chargingSessionStart.toJsonString(max_timestamp, JSONDATE_LENGTH + 1); + txStartTime = declareConfiguration("AO_TXSTARTTIME_CONN_1", max_timestamp, CONFIGURATION_FN, false, false, true, false); chargingSessionTransactionID = -1; + sRmtProfileId = declareConfiguration("AO_SRMTPROFILEID_CONN_1", -1, CONFIGURATION_FN, false, false, true, false); for (int i = 0; i < CHARGEPROFILEMAXSTACKLEVEL; i++) { ChargePointMaxProfile[i] = NULL; TxDefaultProfile[i] = NULL; @@ -126,9 +130,11 @@ void SmartChargingService::inferenceLimit(const OcppTimestamp &t, float *limitOu //evaluate limit from TxProfiles float limit_tx = 0.0f; bool limit_defined_tx = false; - for (int i = CHARGEPROFILEMAXSTACKLEVEL - 1; i >= 0; i--){ - if (TxProfile[i] == NULL) continue; - if (!TxProfile[i]->checkTransactionId(chargingSessionTransactionID)) continue; + for (int i = CHARGEPROFILEMAXSTACKLEVEL - 1; i >= 0; i--) { + if (!TxProfile[i]) + continue; + if (!TxProfile[i]->checkTransactionAssignment(chargingSessionTransactionID, *sRmtProfileId)) + continue; OcppTimestamp nextChange = MAX_TIME; limit_defined_tx = TxProfile[i]->inferenceLimit(t, chargingSessionStart, &limit_tx, &nextChange); if (nextChange < validToMin) @@ -144,7 +150,6 @@ void SmartChargingService::inferenceLimit(const OcppTimestamp &t, float *limitOu bool limit_defined_txdef = false; for (int i = CHARGEPROFILEMAXSTACKLEVEL - 1; i >= 0; i--){ if (TxDefaultProfile[i] == NULL) continue; - //if (!TxDefaultProfile[i]->checkTransactionId(chargingSessionTransactionID)) continue; //this doesn't do anything on TxDefaultProfiles and could be deleted OcppTimestamp nextChange = MAX_TIME; limit_defined_txdef = TxDefaultProfile[i]->inferenceLimit(t, chargingSessionStart, &limit_txdef, &nextChange); if (nextChange < validToMin) @@ -160,7 +165,6 @@ void SmartChargingService::inferenceLimit(const OcppTimestamp &t, float *limitOu bool limit_defined_cpmax = false; for (int i = CHARGEPROFILEMAXSTACKLEVEL - 1; i >= 0; i--){ if (ChargePointMaxProfile[i] == NULL) continue; - //if (!ChargePointMaxProfile[i]->checkTransactionId(chargingSessionTransactionID)) continue; //this doesn't do anything on ChargePointMaxProfiles and could be deleted OcppTimestamp nextChange = MAX_TIME; limit_defined_cpmax = ChargePointMaxProfile[i]->inferenceLimit(t, chargingSessionStart, &limit_cpmax, &nextChange); if (nextChange < validToMin) @@ -219,34 +223,80 @@ ChargingSchedule *SmartChargingService::getCompositeSchedule(int connectorId, ot } void SmartChargingService::refreshChargingSessionState() { - int currentTxId = -1; - if (context.getOcppModel().getConnectorStatus(SINGLE_CONNECTOR_ID)) { - auto connector = context.getOcppModel().getConnectorStatus(SINGLE_CONNECTOR_ID); - currentTxId = connector->getTransactionId(); + if (!context.getOcppModel().getConnectorStatus(SINGLE_CONNECTOR_ID)) { + return; //charging session state does not apply + } + + auto connector = context.getOcppModel().getConnectorStatus(SINGLE_CONNECTOR_ID); + + if (!chargingSessionStateInitialized) { + chargingSessionStateInitialized = true; + + chargingSessionStart.setTime(*txStartTime); + chargingSessionTransactionID = connector->getTransactionId(); + sessionIdTagRev = connector->getSessionWriteCount(); + sRmtProfileIdRev = sRmtProfileId->getValueRevision(); + + //fuzzy check if session engaged at reboot (during first loop run) + auto chargingSessionStartCheck = MAX_TIME; + chargingSessionStartCheck -= 1000000; + if (chargingSessionStart >= chargingSessionStartCheck) { + //charging session start lies in future -> null-value -> no charging session before reboot + chargingSessionTransactionID = -1; + } } - if (currentTxId != chargingSessionTransactionID) { + if (connector->getTransactionId() != chargingSessionTransactionID) { //transition! - if (chargingSessionTransactionID != 0 && currentTxId >= 0) { + bool txStartUpdated = false; + if (chargingSessionTransactionID != 0 && connector->getTransactionId() >= 0) { chargingSessionStart = context.getOcppModel().getOcppTime().getOcppTimestampNow(); - } else if (chargingSessionTransactionID >= 0 && currentTxId < 0) { + txStartUpdated = true; + } else if (chargingSessionTransactionID >= 0 && connector->getTransactionId() < 0) { chargingSessionStart = MAX_TIME; + txStartUpdated = true; + } + + if (txStartUpdated) { + char timestamp [JSONDATE_LENGTH + 1] = {'\0'}; + chargingSessionStart.toJsonString(timestamp, JSONDATE_LENGTH + 1); + *txStartTime = timestamp; + configuration_save(); } nextChange = context.getOcppModel().getOcppTime().getOcppTimestampNow(); - chargingSessionTransactionID = currentTxId; } + + if (*sRmtProfileId >= 0 && //Remote profile set? Check if to delete + (!connector->getSessionIdTag() //Always delete Rmt profile if there is no session + || (sessionIdTagRev != connector->getSessionWriteCount() && sRmtProfileIdRev == sRmtProfileId->getValueRevision()))) { + //Alternaternively delete if session state has been overwritten + + //after RemoteTx session expired, clean charging profile + int clearProfileId = *sRmtProfileId; + bool ret = clearChargingProfile([clearProfileId] (int id, int, ChargingProfilePurposeType, int) { + return id == clearProfileId; + }); + + AO_DBG_DEBUG("Clearing RmtTx Charging Profile after session expiry: %s", ret ? "success" : "already cleared"); + + *sRmtProfileId = -1; + } + + chargingSessionTransactionID = connector->getTransactionId(); + sessionIdTagRev = connector->getSessionWriteCount(); + sRmtProfileIdRev = sRmtProfileId->getValueRevision(); } -void SmartChargingService::updateChargingProfile(JsonObject *json) { +void SmartChargingService::setChargingProfile(JsonObject json) { ChargingProfile *pointer = updateProfileStack(json); if (pointer) writeProfileToFlash(json, pointer); } -ChargingProfile *SmartChargingService::updateProfileStack(JsonObject *json){ - ChargingProfile *chargingProfile = new ChargingProfile(*json); +ChargingProfile *SmartChargingService::updateProfileStack(JsonObject json){ + ChargingProfile *chargingProfile = new ChargingProfile(json); if (AO_DBG_LEVEL >= AO_DL_INFO) { AO_DBG_INFO("Charging Profile internal model:"); @@ -344,7 +394,7 @@ bool SmartChargingService::clearChargingProfile(const std::function 0; } -bool SmartChargingService::writeProfileToFlash(JsonObject *json, ChargingProfile *chargingProfile) { +bool SmartChargingService::writeProfileToFlash(JsonObject json, ChargingProfile *chargingProfile) { #ifndef AO_DEACTIVATE_FLASH if (!filesystemOpt.accessAllowed()) { @@ -378,7 +428,7 @@ bool SmartChargingService::writeProfileToFlash(JsonObject *json, ChargingProfile } // Serialize JSON to file - if (serializeJson(*json, file) == 0) { + if (serializeJson(json, file) == 0) { AO_DBG_ERR("Unable to save: could not serialize JSON for profile: %s", fn); file.close(); return false; @@ -496,7 +546,7 @@ bool SmartChargingService::loadProfiles() { } JsonObject profileJson = profileDoc.as(); - updateProfileStack(&profileJson); + updateProfileStack(profileJson); profileDoc.clear(); break; diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h index 8dd10dc6..ef288ee6 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.h @@ -13,7 +13,7 @@ #include #include -#include +#include #include namespace ArduinoOcpp { @@ -34,18 +34,24 @@ class SmartChargingService { OnLimitChange onLimitChange = NULL; float limitBeforeChange; OcppTimestamp nextChange; + + bool chargingSessionStateInitialized {false}; + std::shared_ptr> txStartTime; OcppTimestamp chargingSessionStart; int chargingSessionTransactionID; + std::shared_ptr> sRmtProfileId; + uint16_t sRmtProfileIdRev {0}; + uint16_t sessionIdTagRev {0}; void refreshChargingSessionState(); - ChargingProfile *updateProfileStack(JsonObject *json); + ChargingProfile *updateProfileStack(JsonObject json); FilesystemOpt filesystemOpt; - bool writeProfileToFlash(JsonObject *json, ChargingProfile *chargingProfile); + bool writeProfileToFlash(JsonObject json, ChargingProfile *chargingProfile); bool loadProfiles(); public: SmartChargingService(OcppEngine& context, float chargeLimit, float V_eff, int numConnectors, FilesystemOpt filesystemOpt = FilesystemOpt::Use_Mount_FormatOnFail); - void updateChargingProfile(JsonObject *json); + void setChargingProfile(JsonObject json); bool clearChargingProfile(const std::function& filter); void inferenceLimit(const OcppTimestamp &t, float *limit, OcppTimestamp *validTo); float inferenceLimitNow(); From 9bb7a703b204607f2a11d23632f18c82e23537ef Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 26 Jun 2022 19:28:47 +0200 Subject: [PATCH 030/549] retain C facade --- CMakeLists.txt | 66 --------------- README.md | 66 ++++++++++++++- src/ArduinoOcpp_c.cpp | 181 ------------------------------------------ src/ArduinoOcpp_c.h | 107 ------------------------- src/ao_opts.h | 31 -------- src/ao_opts_impl.c | 30 ------- 6 files changed, 64 insertions(+), 417 deletions(-) delete mode 100644 CMakeLists.txt delete mode 100644 src/ArduinoOcpp_c.cpp delete mode 100644 src/ArduinoOcpp_c.h delete mode 100644 src/ao_opts.h delete mode 100644 src/ao_opts_impl.c diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 862c2398..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,66 +0,0 @@ -set(AO_SRC - src/ArduinoOcpp/Core/Configuration.cpp - src/ArduinoOcpp/Core/ConfigurationContainer.cpp - src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp - src/ArduinoOcpp/Core/ConfigurationKeyValue.cpp - src/ArduinoOcpp/Core/OcppConnection.cpp - src/ArduinoOcpp/Core/OcppEngine.cpp - src/ArduinoOcpp/Core/OcppMessage.cpp - src/ArduinoOcpp/Core/OcppModel.cpp - src/ArduinoOcpp/Core/OcppOperation.cpp - src/ArduinoOcpp/Core/OcppOperationTimeout.cpp - src/ArduinoOcpp/Core/OcppServer.cpp - src/ArduinoOcpp/Core/OcppSocket.cpp - src/ArduinoOcpp/Core/OcppTime.cpp - src/ArduinoOcpp/MessagesV16/Authorize.cpp - src/ArduinoOcpp/MessagesV16/BootNotification.cpp - src/ArduinoOcpp/MessagesV16/ChangeAvailability.cpp - src/ArduinoOcpp/MessagesV16/ChangeConfiguration.cpp - src/ArduinoOcpp/MessagesV16/ClearCache.cpp - src/ArduinoOcpp/MessagesV16/ClearChargingProfile.cpp - src/ArduinoOcpp/MessagesV16/DataTransfer.cpp - src/ArduinoOcpp/MessagesV16/DiagnosticsStatusNotification.cpp - src/ArduinoOcpp/MessagesV16/FirmwareStatusNotification.cpp - src/ArduinoOcpp/MessagesV16/GetConfiguration.cpp - src/ArduinoOcpp/MessagesV16/GetDiagnostics.cpp - src/ArduinoOcpp/MessagesV16/Heartbeat.cpp - src/ArduinoOcpp/MessagesV16/MeterValues.cpp - src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp - src/ArduinoOcpp/MessagesV16/RemoteStopTransaction.cpp - src/ArduinoOcpp/MessagesV16/Reset.cpp - src/ArduinoOcpp/MessagesV16/SetChargingProfile.cpp - src/ArduinoOcpp/MessagesV16/StartTransaction.cpp - src/ArduinoOcpp/MessagesV16/StatusNotification.cpp - src/ArduinoOcpp/MessagesV16/StopTransaction.cpp - src/ArduinoOcpp/MessagesV16/TriggerMessage.cpp - src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp - src/ArduinoOcpp/MessagesV16/UpdateFirmware.cpp - src/ArduinoOcpp/Platform.cpp - src/ArduinoOcpp/SimpleOcppOperationFactory.cpp - src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp - src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp - src/ArduinoOcpp/Tasks/Diagnostics/DiagnosticsService.cpp - src/ArduinoOcpp/Tasks/FirmwareManagement/FirmwareService.cpp - src/ArduinoOcpp/Tasks/Heartbeat/HeartbeatService.cpp - src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp - src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp - src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp - src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp - src/ArduinoOcpp.cpp - src/ArduinoOcpp_c.cpp - src/ao_opts_impl.c - src/ArduinoOcpp/Core/FilesystemAdapter.cpp -) - -idf_component_register(SRCS ${AO_SRC} - INCLUDE_DIRS "./src" "${PROJECT_DIR}/include" - PRIV_REQUIRES spiffs) - -target_compile_options(${COMPONENT_TARGET} PUBLIC - -DAO_CUSTOM_WS - -DAO_CUSTOM_CONSOLE - -DAO_CUSTOM_UPDATER - -DAO_DEACTIVATE_FLASH - -DAO_USE_FILEAPI=ESPIDF_SPIFFS - -DAO_DBG_LEVEL=AO_DL_DEBUG - -DAO_TRAFFIC_OUT) diff --git a/README.md b/README.md index facb1c3c..44b2988e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # Icon   ArduinoOcpp + +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/matth-x/ArduinoOcpp/PlatformIO%20CI?logo=github)](https://github.com/matth-x/ArduinoOcpp/actions) + OCPP-J 1.6 client for the ESP8266 and the ESP32 (more coming soon) Reference usage: [OpenEVSE](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/ocpp.cpp) @@ -32,11 +35,70 @@ For simple chargers, the necessary hardware and internet integration is usually ## Usage guide -**This feature branch is WIP. A usage guide will follow.** +Please take `examples/ESP/main.cpp` as the starting point for your first project. It is a minimal example which shows how to establish an OCPP connection and how to start and stop charging sessions. This guide explains the concepts for a minimal integration. + +- To get the library running, you have to install all dependencies (see the list below). In case you use PlatformIO, you can just add `matth-x/ArduinoOcpp` to your project using the PIO library manager. + +- In your project's `main` file, include `ArduinoOcpp.h`. + +- Before establishing an OCPP connection you have to ensure that your device has access to a Wi-Fi access point. All debug messages are printed on the standard serial (i.e. `Serial.print("debug msg")`). To redirect debug messages, please refer to `src/ArduinoOcpp/Platform.h`. + +- To connect to your OCPP Central System, call `OCPP_initialize(String OCPP_HOST, uint16_t OCPP_PORT, String OCPP_URL)`. You need to insert the address parameters according to the configuration of your central system. Internally, the library passes these parameters to the WebSocket object without further alteration. + - To secure the connection with TLS, you have to configure the WebSocket. Please take `examples/ESP-TLS/main.cpp` as an example. + +- In your `setup()` function, you can add the configuration functions from `ArduinoOcpp.h` to properly integrate your hardware. For example, the library needs access to the energy meter. All configuration functions are documented in `ArduinoOcpp.h`. + +- Add `OCPP_loop()` to your `loop()` function. + +**Sending OCPP operations** + +There are a couple of OCPP operations you can initialize on your EVSE. For example, to send a `Boot Notification`, use the function +```cpp +void bootNotification(const char *chargePointModel, const char *chargePointVendor, OnReceiveConfListener onConf = nullptr, ...)` +``` + +In practice, it looks like this: + +```cpp +void setup() { + + ... //other code including the initialization of Wi-Fi and OCPP + + bootNotification("My CP model name", "My company name", [] (JsonObject confMsg) { + //This callback is executed when the .conf() response from the central system arrives + Serial.print(F("BootNotification was answered. Central System clock: ")); + Serial.println(confMsg["currentTime"].as()); //"currentTime" is a field of the central system response + + //Notify your hardare that the BootNotification.conf() has arrived. E.g.: + //evseIsBooted = true; + }); + + ... //rest of setup() function; executed immediately as bootNotification() is non-blocking +} +``` + +The parameters `chargePointModel` and `chargePointVendor` are equivalent to the parameters in the `Boot Notification` as defined by the OCPP specification. The last parameter `OnReceiveConfListener onConf` is a callback function which the library executes when the central system has processed the operation and the ESP has received the `.conf()` response. Here you can add your device-specific behavior, e.g. flash a confirmation LED or unlock the connectors. If you don't need it, the last parameter is optional. + +**Receiving OCPP operations** + +The library also reacts on CS-initiated operations. You can add your own behavior there too. For example, to flash a LED on receipt of a `Set Charging Profile` request, use the following function. + +```cpp +setOnSetChargingProfileRequest([] (JsonObject payload) { + //... +}); +``` + +You can also process the original payload from the CS using the `payload` object. + +*To get started quickly with or without EVSE hardware, you can flash the sketch in `examples/SECC` onto your ESP. That example mimics a full OCPP communications controller as it would look like in a real charging station. You can build a charger prototype based on that example or just view the internal state using the device monitor.* ## Dependencies -- [bblanchon/ArduinoJson](https://github.com/bblanchon/ArduinoJson) +- [bblanchon/ArduinoJSON](https://github.com/bblanchon/ArduinoJson) (please upgrade to version `6.19.1`) +- [Links2004/arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) (please upgrade to version `2.3.6`) + +In case you use PlatformIO, you can copy all dependencies from `platformio.ini` into your own configuration file. Alternatively, you can install the full library with dependencies by adding `matth-x/ArduinoOcpp` in the PIO library manager. ## Supported operations diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp deleted file mode 100644 index 855689c0..00000000 --- a/src/ArduinoOcpp_c.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "ArduinoOcpp_c.h" -#include "ArduinoOcpp.h" - -#include - -ArduinoOcpp::OcppSocket *ocppSocket = nullptr; - -extern "C" void ao_initialize(AOcppSocket *osock) { - //OCPP_initialize("echo.websocket.events", 80, "ws://echo.websocket.events/"); - if (!osock) { - AO_DBG_ERR("osock is null"); - } - AO_DBG_ERR("no error"); - - ocppSocket = reinterpret_cast(osock); - - OCPP_initialize(*ocppSocket); -} - -extern "C" void ao_loop() { - OCPP_loop(); -} - -extern "C" void ao_set_console_out_c(void (*console_out)(const char *msg)) { - ao_set_console_out(console_out); -} - -#ifndef AO_RECEIVE_PAYLOAD_BUFSIZE -#define AO_RECEIVE_PAYLOAD_BUFSIZE 1024 -#endif - -char ao_recv_payload_buff [AO_RECEIVE_PAYLOAD_BUFSIZE] = {'\0'}; - -std::function adaptCb(OnOcppMessage cb) { - return [cb] (JsonObject payload) { - auto len = serializeJson(payload, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); - if (len <= 0) { - AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); - } - cb(len > 0 ? ao_recv_payload_buff : nullptr, len); - }; -} - -std::function adaptCb(void (*cb)()) { - return cb; -} - -ArduinoOcpp::OnReceiveErrorListener adaptCb(OnOcppError cb) { - return [cb] (const char *code, const char *description, JsonObject details) { - auto len = serializeJson(details, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); - if (len <= 0) { - AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); - } - cb(code, description, len > 0 ? ao_recv_payload_buff : "", len); - }; -} - -std::function adaptCb(SamplerBool cb) { - return cb; -} - -std::function adaptCb(SamplerString cb) { - return cb; -} - -std::function adaptCb(SamplerFloat cb) { - return cb; -} - -std::function adaptCb(SamplerInt cb) { - return cb; -} - -void ao_setPowerActiveImportSampler(SamplerFloat power) { - setPowerActiveImportSampler(adaptCb(power)); -} - -void ao_setEnergyActiveImportSampler(SamplerInt energy) { - setEnergyActiveImportSampler([energy] () -> float { - return (float) energy(); - }); -} - -void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy) { - setEvRequestsEnergySampler(adaptCb(evRequestsEnergy)); -} - -void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized) { - setConnectorEnergizedSampler(adaptCb(connectorEnergized)); -} - -void ao_setConnectorPluggedSampler(SamplerBool connectorPlugged) { - setConnectorPluggedSampler(adaptCb(connectorPlugged)); -} - -void ao_addConnectorErrorCodeSampler(SamplerString connectorErrorCode) { - addConnectorErrorCodeSampler(adaptCb(connectorErrorCode)); -} - -void ao_onChargingRateLimitChange(void (*chargingRateChanged)(float)) { - setOnChargingRateLimitChange(chargingRateChanged); -} - -void ao_onUnlockConnector(SamplerBool unlockConnector) { - setOnUnlockConnector(adaptCb(unlockConnector)); -} - -void ao_onRemoteStartTransactionSendConf(OnOcppMessage onSendConf) { - setOnRemoteStopTransactionSendConf(adaptCb(onSendConf)); -} - -void ao_onRemoteStopTransactionSendConf(OnOcppMessage onSendConf) { - setOnRemoteStopTransactionSendConf(adaptCb(onSendConf)); -} - -void ao_onRemoteStopTransactionRequest(OnOcppMessage onRequest) { - setOnRemoteStopTransactionReceiveReq(adaptCb(onRequest)); -} - -void ao_onResetSendConf(OnOcppMessage onSendConf) { - setOnResetSendConf(adaptCb(onSendConf)); -} - -extern "C" void ao_onResetRequest(OnOcppMessage onRequest) { - setOnResetReceiveReq(adaptCb(onRequest)); -} - -extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { - bootNotification("model", "vendor", adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); -} - -void ao_bootNotification_full(const char *payloadJson, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { - DynamicJsonDocument *payload = new DynamicJsonDocument(JSON_OBJECT_SIZE(9) + 230 + 9); // BootNotification has at most 9 attributes with at most 230 chars + null terminators - auto err = deserializeJson(*payload, payloadJson); - if (err) { - AO_DBG_ERR("Could not process input: %s", err.c_str()); - (void)0; - } - - bootNotification(payload, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); -} - -void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { - authorize(idTag, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); -} - -void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { - startTransaction(idTag, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); -} - -void ao_stopTransaction(OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { - stopTransaction(adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); -} - -int ao_getTransactionId() { - return getTransactionId(); -} - -bool ao_ocppPermitsCharge() { - return ocppPermitsCharge(); -} - -bool ao_isAvailable() { - return isAvailable(); -} - -void ao_beginSession(const char *idTag) { - return beginSession(idTag); -} - -void ao_endSession() { - return endSession(); -} - -bool ao_isInSession() { - return isInSession(); -} - -const char *ao_getSessionIdTag() { - return getSessionIdTag(); -} diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h deleted file mode 100644 index 16e45b39..00000000 --- a/src/ArduinoOcpp_c.h +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef ARDUINOOCPP_C_H -#define ARDUINOOCPP_C_H - -#include - -struct AOcppSocket; -typedef struct AOcppSocket AOcppSocket; - -typedef void (*OnOcppMessage) (const char *payload, size_t len); -typedef void (*OnOcppAbort) (); -typedef void (*OnOcppTimeout) (); -typedef void (*OnOcppError) (const char *code, const char *description, const char *details_json, size_t details_len); - -typedef float (*SamplerFloat)(); -typedef int (*SamplerInt)(); -typedef bool (*SamplerBool)(); -typedef const char* (*SamplerString)(); - -#ifdef __cplusplus -extern "C" { -#endif - -void ao_initialize(AOcppSocket *osock); - -void ao_deinitialize(); - -void ao_loop(); - -void ao_set_console_out_c(void (*console_out)(const char *msg)); - -/* - * Feed lib with HW related data - */ - -void ao_setPowerActiveImportSampler(SamplerFloat power); - -void ao_setEnergyActiveImportSampler(SamplerInt energy); - -void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy); - -void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized); - -void ao_setConnectorPluggedSampler(SamplerBool connectorPlugged); - -void ao_addConnectorErrorCodeSampler(SamplerString connectorErrorCode); - -/* - * Execute HW related operations on EVSE - */ - -void ao_onChargingRateLimitChange(void (*chargingRateChanged)(float)); - -void ao_onUnlockConnector(SamplerBool unlockConnector); //true: success, false: failure - -/* - * Generic listeners for OCPP operations initiated by Central System - */ - -void ao_onRemoteStartTransactionSendConf(OnOcppMessage onSendConf); //important, energize the power plug here and capture the idTag - -void ao_onRemoteStopTransactionSendConf(OnOcppMessage onSendConf); //important, de-energize the power plug here -void ao_onRemoteStopTransactionRequest(OnOcppMessage onRequest); //optional, to de-energize the power plug immediately - -void ao_onResetSendConf(OnOcppMessage onSendConf); //important, reset your device here (i.e. call ESP.reset();) -void ao_onResetRequest(OnOcppMessage onRequest); //alternative: start reset timer here - -/* - * Initiate OCPP operations - */ - -void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); - -void ao_bootNotification_full(const char *payloadJson, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); - -void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); - -void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); - -void ao_stopTransaction(OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); - -/* - * Access OCPP state - */ - -int ao_getTransactionId(); //returns the ID of the current transaction. Returns -1 if called before or after an transaction - -bool ao_ocppPermitsCharge(); - -bool ao_isAvailable(); //if the charge point is operative or inoperative - -/* - * Charging session management - */ - -void ao_beginSession(const char *idTag); - -void ao_endSession(); - -bool ao_isInSession(); - -const char *ao_getSessionIdTag(); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/ao_opts.h b/src/ao_opts.h deleted file mode 100644 index 5a19cede..00000000 --- a/src/ao_opts.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef AOOPTS_H -#define AOOPTS_H - -#ifdef __cplusplus -extern "C" { -#endif - -unsigned long ao_tick_ms_impl(); -//unsigned int32_t ao_avail_heap_impl(); - -#ifdef __cplusplus -} -#endif - -#ifndef ao_tick_ms -#define ao_tick_ms ao_tick_ms_impl -#endif - -#ifndef ao_avail_heap -#define ao_avail_heap() 20000 -#endif - -//#ifndef AO_CONSOLE_PRINTF -//#define AO_CONSOLE_PRINTF(...) ESP_LOGI("[ocpp]", __VA_ARGS__) -//#endif - -#ifndef AO_CUSTOM_CONSOLE_MAXMSGSIZE -#define AO_CUSTOM_CONSOLE_MAXMSGSIZE 192 -#endif - -#endif diff --git a/src/ao_opts_impl.c b/src/ao_opts_impl.c deleted file mode 100644 index ce1b6559..00000000 --- a/src/ao_opts_impl.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "ao_opts.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -unsigned long ao_tick_ms_impl() { - //return xTaskGetTickCount() / configTICK_RATE_HZ; - return (xTaskGetTickCount() * 1000UL) / configTICK_RATE_HZ; -} - -#ifdef __cplusplus -} -#endif From 8be2cdbf7a39a692dcc713eddcee2b650ea1662c Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 26 Jun 2022 21:33:08 +0200 Subject: [PATCH 031/549] fix compilation errors --- src/ArduinoOcpp/Core/FilesystemAdapter.cpp | 24 ++++++++++++------- .../MessagesV16/StatusNotification.cpp | 3 ++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp index d35789be..e8dad634 100644 --- a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp @@ -56,10 +56,18 @@ class ArduinoFileAdapter : public FileAdapter { } } - int read() override; - size_t read(char *buf, size_t len) override; - size_t write(const char *buf, size_t len) override; - size_t seek(size_t offset) override; + int read() override { + return file.read(); + }; + size_t read(char *buf, size_t len) override { + return file.readBytes(buf, len); + } + size_t write(const char *buf, size_t len) override { + return file.printf("%.*s", len, buf); + } + size_t seek(size_t offset) override { + return file.seek(offset); + } }; class ArduinoFilesystemAdapter : public FilesystemAdapter { @@ -110,8 +118,8 @@ class ArduinoFilesystemAdapter : public FilesystemAdapter { } int status = -1; - if (f.isFile()) { - size = f.size(); + if (!f.isDirectory()) { + *size = f.size(); status = 0; } else { //fetch more information for directory when ArduinoOcpp also uses them @@ -124,7 +132,7 @@ class ArduinoFilesystemAdapter : public FilesystemAdapter { std::unique_ptr open(const char *fn, const char *mode) override { File file = USE_FS.open(fn, mode); - if (file && file.isFile()) { + if (file && !file.isDirectory()) { return std::unique_ptr(new ArduinoFileAdapter(std::move(file))); } else { return nullptr; @@ -146,7 +154,7 @@ std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt co new ArduinoFilesystemAdapter(config) ); - if (*fs) { + if (fs) { return fs; } else { return nullptr; diff --git a/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp b/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp index c563a8dd..9c405a33 100644 --- a/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp +++ b/src/ArduinoOcpp/MessagesV16/StatusNotification.cpp @@ -36,7 +36,8 @@ const char *cstrFromOcppEveState(OcppEvseState state) { return "Faulted"; default: AO_DBG_ERR("OcppEvseState not specified"); - __attribute__ ((fallthrough)); + (void)0; + /* fall through */ case (OcppEvseState::NOT_SET): return "NOT_SET"; } From bbab921fca8feb69b4733088bec72d3885ebcc30 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 26 Jun 2022 21:45:27 +0200 Subject: [PATCH 032/549] remove includes --- .../Core/ConfigurationContainerFlash.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp index 3aa078e8..0eea713d 100644 --- a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp +++ b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp @@ -7,21 +7,6 @@ #include -#if defined(ESP32) -#define USE_FS LITTLEFS -#else -#define USE_FS SPIFFS -#endif - -#ifndef AO_DEACTIVATE_FLASH -#if USE_FS == LITTLEFS -#include -#elif USE_FS == SPIFFS -#include -#define USE_FS SPIFFS -#endif -#endif - #define MAX_FILE_SIZE 4000 #define MAX_CONFIGURATIONS 50 #define MAX_CONFJSON_CAPACITY 4000 From 07f6c0caeda1f73c6c5b51385d0138dc2fe11046 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 27 Jun 2022 00:10:49 +0200 Subject: [PATCH 033/549] fix filesystem access --- examples/SECC/main.cpp | 7 ++++++- src/ArduinoOcpp.cpp | 3 ++- src/ArduinoOcpp/Core/FilesystemAdapter.cpp | 9 +++++---- src/ArduinoOcpp/Core/FilesystemAdapter.h | 2 +- .../Tasks/SmartCharging/SmartChargingService.cpp | 4 ++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/SECC/main.cpp b/examples/SECC/main.cpp index 446dc6fa..1ca19e35 100644 --- a/examples/SECC/main.cpp +++ b/examples/SECC/main.cpp @@ -151,8 +151,13 @@ void setup() { /* * You can use ArduinoOcpp's internal configurations store for credentials other than those which are * specified by OCPP. To use it before ArduinoOcpp is initialized, you need to call configuration_init() + * + * This snippet also shows how to integrate a custom filesystem. Just subclass FilesystemAdapter and pass + * it to the library */ - ArduinoOcpp::configuration_init(ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail); + std::shared_ptr filesystem = ArduinoOcpp::EspWiFi::makeDefaultFilesystemAdapter(ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail); + ArduinoOcpp::configuration_init(filesystem); + filesystem = nullptr; /* * WiFiManager opens a captive portal, lets the user enter the WiFi credentials and provides a settings diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 4a9b6bf1..781fc9c1 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -32,6 +32,7 @@ OcppSocket *ocppSocket {nullptr}; #endif OcppEngine *ocppEngine {nullptr}; +std::shared_ptr filesystem; FilesystemOpt fileSystemOpt {}; float voltage_eff {230.f}; @@ -85,7 +86,7 @@ void OCPP_initialize(OcppSocket& ocppSocket, float V_eff, ArduinoOcpp::Filesyste voltage_eff = V_eff; fileSystemOpt = fsOpt; - std::shared_ptr filesystem = EspWiFi::makeDefaultFilesystemAdapter(fileSystemOpt); + filesystem = EspWiFi::makeDefaultFilesystemAdapter(fileSystemOpt); AO_DBG_DEBUG("filesystem %s", filesystem ? "loaded" : "error"); configuration_init(filesystem); //call before each other library call diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp index e8dad634..1d3d720e 100644 --- a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp @@ -83,6 +83,8 @@ class ArduinoFilesystemAdapter : public FilesystemAdapter { if(!USE_FS.begin(config.formatOnFail())) { AO_DBG_ERR("Error while mounting LITTLEFS"); valid = false; + } else { + AO_DBG_DEBUG("LittleFS mount success"); } #elif AO_USE_FILEAPI == ARDUINO_SPIFFS //ESP8266 @@ -150,11 +152,10 @@ std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt co return nullptr; } - auto fs = std::unique_ptr( - new ArduinoFilesystemAdapter(config) - ); + auto fs_concrete = new ArduinoFilesystemAdapter(config); + auto fs = std::unique_ptr(fs_concrete); - if (fs) { + if (*fs_concrete) { return fs; } else { return nullptr; diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.h b/src/ArduinoOcpp/Core/FilesystemAdapter.h index e32f7779..93c6af27 100644 --- a/src/ArduinoOcpp/Core/FilesystemAdapter.h +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.h @@ -6,7 +6,7 @@ #define AO_FILESYSTEMADAPTER_H #ifndef AO_FILENAME_PREFIX -#define AO_FILENAME_PREFIX "/ao_store" +#define AO_FILENAME_PREFIX "" #endif #define ARDUINO_LITTLEFS 1 diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp index 79c564d4..b2dbbe84 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp @@ -300,8 +300,8 @@ void SmartChargingService::setChargingProfile(JsonObject json) { ChargingProfile *SmartChargingService::updateProfileStack(JsonObject json){ ChargingProfile *chargingProfile = new ChargingProfile(json); - if (AO_DBG_LEVEL >= AO_DL_INFO) { - AO_DBG_INFO("Charging Profile internal model:"); + if (AO_DBG_LEVEL >= AO_DL_VERBOSE) { + AO_DBG_VERBOSE("Charging Profile internal model:"); chargingProfile->printProfile(); } From 0e5dab47de87b08ff4ff7fe356bbe8f5dc489e4d Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 27 Jun 2022 11:35:31 +0200 Subject: [PATCH 034/549] restore files --- .github/workflows/pio.yaml | 47 ---------- CMakeLists.txt | 67 ++++++++++++++ README.md | 66 +------------- src/ArduinoOcpp/Platform.h | 2 + src/ArduinoOcpp_c.cpp | 181 +++++++++++++++++++++++++++++++++++++ src/ArduinoOcpp_c.h | 107 ++++++++++++++++++++++ src/ao_opts.h | 31 +++++++ src/ao_opts_impl.c | 30 ++++++ 8 files changed, 420 insertions(+), 111 deletions(-) delete mode 100644 .github/workflows/pio.yaml create mode 100644 CMakeLists.txt create mode 100644 src/ArduinoOcpp_c.cpp create mode 100644 src/ArduinoOcpp_c.h create mode 100644 src/ao_opts.h create mode 100644 src/ao_opts_impl.c diff --git a/.github/workflows/pio.yaml b/.github/workflows/pio.yaml deleted file mode 100644 index ab6248b7..00000000 --- a/.github/workflows/pio.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: PlatformIO CI - -on: - push: - branches: - - develop - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - example: [examples/ESP/main.cpp, examples/ESP-TLS/main.cpp, examples/SECC/main.cpp] - include: - - example: examples/SECC/main.cpp - dashboard-extra: --lib="/tmp/tzapu/WiFiManager" - - steps: - - uses: actions/checkout@v2 - - name: Cache pip - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Cache PlatformIO - uses: actions/cache@v2 - with: - path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - - name: Set up Python - uses: actions/setup-python@v2 - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - name: Install library dependencies - run: pio pkg install - - name: Extra dependencies for SECC example - if: ${{ matrix.dashboard-extra }} - run: git clone https://github.com/tzapu/WiFiManager.git /tmp/tzapu/WiFiManager - - name: Run PlatformIO - run: pio ci --lib="." --project-conf=platformio.ini ${{ matrix.dashboard-extra }} - env: - PLATFORMIO_CI_SRC: ${{ matrix.example }} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..56345c10 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +set(AO_SRC + src/ArduinoOcpp/Core/Configuration.cpp + src/ArduinoOcpp/Core/ConfigurationContainer.cpp + src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp + src/ArduinoOcpp/Core/ConfigurationKeyValue.cpp + src/ArduinoOcpp/Core/OcppConnection.cpp + src/ArduinoOcpp/Core/OcppEngine.cpp + src/ArduinoOcpp/Core/OcppMessage.cpp + src/ArduinoOcpp/Core/OcppModel.cpp + src/ArduinoOcpp/Core/OcppOperation.cpp + src/ArduinoOcpp/Core/OcppOperationTimeout.cpp + src/ArduinoOcpp/Core/OcppServer.cpp + src/ArduinoOcpp/Core/OcppSocket.cpp + src/ArduinoOcpp/Core/OcppTime.cpp + src/ArduinoOcpp/MessagesV16/Authorize.cpp + src/ArduinoOcpp/MessagesV16/BootNotification.cpp + src/ArduinoOcpp/MessagesV16/ChangeAvailability.cpp + src/ArduinoOcpp/MessagesV16/ChangeConfiguration.cpp + src/ArduinoOcpp/MessagesV16/ClearCache.cpp + src/ArduinoOcpp/MessagesV16/ClearChargingProfile.cpp + src/ArduinoOcpp/MessagesV16/DataTransfer.cpp + src/ArduinoOcpp/MessagesV16/DiagnosticsStatusNotification.cpp + src/ArduinoOcpp/MessagesV16/FirmwareStatusNotification.cpp + src/ArduinoOcpp/MessagesV16/GetConfiguration.cpp + src/ArduinoOcpp/MessagesV16/GetDiagnostics.cpp + src/ArduinoOcpp/MessagesV16/Heartbeat.cpp + src/ArduinoOcpp/MessagesV16/MeterValues.cpp + src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp + src/ArduinoOcpp/MessagesV16/RemoteStopTransaction.cpp + src/ArduinoOcpp/MessagesV16/Reset.cpp + src/ArduinoOcpp/MessagesV16/SetChargingProfile.cpp + src/ArduinoOcpp/MessagesV16/StartTransaction.cpp + src/ArduinoOcpp/MessagesV16/StatusNotification.cpp + src/ArduinoOcpp/MessagesV16/StopTransaction.cpp + src/ArduinoOcpp/MessagesV16/TriggerMessage.cpp + src/ArduinoOcpp/MessagesV16/UnlockConnector.cpp + src/ArduinoOcpp/MessagesV16/UpdateFirmware.cpp + src/ArduinoOcpp/Platform.cpp + src/ArduinoOcpp/SimpleOcppOperationFactory.cpp + src/ArduinoOcpp/Tasks/ChargePointStatus/ChargePointStatusService.cpp + src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp + src/ArduinoOcpp/Tasks/Diagnostics/DiagnosticsService.cpp + src/ArduinoOcpp/Tasks/FirmwareManagement/FirmwareService.cpp + src/ArduinoOcpp/Tasks/Heartbeat/HeartbeatService.cpp + src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp + src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp + src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp + src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp + src/ArduinoOcpp.cpp + src/ArduinoOcpp_c.cpp + src/ao_opts_impl.c + src/ArduinoOcpp/Core/FilesystemAdapter.cpp +) + +idf_component_register(SRCS ${AO_SRC} + INCLUDE_DIRS "./src" "${PROJECT_DIR}/include" + PRIV_REQUIRES spiffs) + +target_compile_options(${COMPONENT_TARGET} PUBLIC + -DAO_CUSTOM_WS + -DAO_CUSTOM_CONSOLE + -DAO_CUSTOM_UPDATER + -DAO_DEACTIVATE_FLASH + -DAO_USE_FILEAPI=ESPIDF_SPIFFS + -DAO_DBG_LEVEL=AO_DL_DEBUG + -DAO_TRAFFIC_OUT + -DAO_FILENAME_PREFIX="/ao_store") diff --git a/README.md b/README.md index 44b2988e..facb1c3c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ # Icon   ArduinoOcpp - -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/matth-x/ArduinoOcpp/PlatformIO%20CI?logo=github)](https://github.com/matth-x/ArduinoOcpp/actions) - OCPP-J 1.6 client for the ESP8266 and the ESP32 (more coming soon) Reference usage: [OpenEVSE](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/ocpp.cpp) @@ -35,70 +32,11 @@ For simple chargers, the necessary hardware and internet integration is usually ## Usage guide -Please take `examples/ESP/main.cpp` as the starting point for your first project. It is a minimal example which shows how to establish an OCPP connection and how to start and stop charging sessions. This guide explains the concepts for a minimal integration. - -- To get the library running, you have to install all dependencies (see the list below). In case you use PlatformIO, you can just add `matth-x/ArduinoOcpp` to your project using the PIO library manager. - -- In your project's `main` file, include `ArduinoOcpp.h`. - -- Before establishing an OCPP connection you have to ensure that your device has access to a Wi-Fi access point. All debug messages are printed on the standard serial (i.e. `Serial.print("debug msg")`). To redirect debug messages, please refer to `src/ArduinoOcpp/Platform.h`. - -- To connect to your OCPP Central System, call `OCPP_initialize(String OCPP_HOST, uint16_t OCPP_PORT, String OCPP_URL)`. You need to insert the address parameters according to the configuration of your central system. Internally, the library passes these parameters to the WebSocket object without further alteration. - - To secure the connection with TLS, you have to configure the WebSocket. Please take `examples/ESP-TLS/main.cpp` as an example. - -- In your `setup()` function, you can add the configuration functions from `ArduinoOcpp.h` to properly integrate your hardware. For example, the library needs access to the energy meter. All configuration functions are documented in `ArduinoOcpp.h`. - -- Add `OCPP_loop()` to your `loop()` function. - -**Sending OCPP operations** - -There are a couple of OCPP operations you can initialize on your EVSE. For example, to send a `Boot Notification`, use the function -```cpp -void bootNotification(const char *chargePointModel, const char *chargePointVendor, OnReceiveConfListener onConf = nullptr, ...)` -``` - -In practice, it looks like this: - -```cpp -void setup() { - - ... //other code including the initialization of Wi-Fi and OCPP - - bootNotification("My CP model name", "My company name", [] (JsonObject confMsg) { - //This callback is executed when the .conf() response from the central system arrives - Serial.print(F("BootNotification was answered. Central System clock: ")); - Serial.println(confMsg["currentTime"].as()); //"currentTime" is a field of the central system response - - //Notify your hardare that the BootNotification.conf() has arrived. E.g.: - //evseIsBooted = true; - }); - - ... //rest of setup() function; executed immediately as bootNotification() is non-blocking -} -``` - -The parameters `chargePointModel` and `chargePointVendor` are equivalent to the parameters in the `Boot Notification` as defined by the OCPP specification. The last parameter `OnReceiveConfListener onConf` is a callback function which the library executes when the central system has processed the operation and the ESP has received the `.conf()` response. Here you can add your device-specific behavior, e.g. flash a confirmation LED or unlock the connectors. If you don't need it, the last parameter is optional. - -**Receiving OCPP operations** - -The library also reacts on CS-initiated operations. You can add your own behavior there too. For example, to flash a LED on receipt of a `Set Charging Profile` request, use the following function. - -```cpp -setOnSetChargingProfileRequest([] (JsonObject payload) { - //... -}); -``` - -You can also process the original payload from the CS using the `payload` object. - -*To get started quickly with or without EVSE hardware, you can flash the sketch in `examples/SECC` onto your ESP. That example mimics a full OCPP communications controller as it would look like in a real charging station. You can build a charger prototype based on that example or just view the internal state using the device monitor.* +**This feature branch is WIP. A usage guide will follow.** ## Dependencies -- [bblanchon/ArduinoJSON](https://github.com/bblanchon/ArduinoJson) (please upgrade to version `6.19.1`) -- [Links2004/arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) (please upgrade to version `2.3.6`) - -In case you use PlatformIO, you can copy all dependencies from `platformio.ini` into your own configuration file. Alternatively, you can install the full library with dependencies by adding `matth-x/ArduinoOcpp` in the PIO library manager. +- [bblanchon/ArduinoJson](https://github.com/bblanchon/ArduinoJson) ## Supported operations diff --git a/src/ArduinoOcpp/Platform.h b/src/ArduinoOcpp/Platform.h index 58cdd372..f6dfb757 100644 --- a/src/ArduinoOcpp/Platform.h +++ b/src/ArduinoOcpp/Platform.h @@ -5,6 +5,8 @@ #ifndef AO_PLATFORM_H #define AO_PLATFORM_H +#include + #ifdef AO_CUSTOM_CONSOLE #ifndef AO_CUSTOM_CONSOLE_MAXMSGSIZE diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp new file mode 100644 index 00000000..855689c0 --- /dev/null +++ b/src/ArduinoOcpp_c.cpp @@ -0,0 +1,181 @@ +#include "ArduinoOcpp_c.h" +#include "ArduinoOcpp.h" + +#include + +ArduinoOcpp::OcppSocket *ocppSocket = nullptr; + +extern "C" void ao_initialize(AOcppSocket *osock) { + //OCPP_initialize("echo.websocket.events", 80, "ws://echo.websocket.events/"); + if (!osock) { + AO_DBG_ERR("osock is null"); + } + AO_DBG_ERR("no error"); + + ocppSocket = reinterpret_cast(osock); + + OCPP_initialize(*ocppSocket); +} + +extern "C" void ao_loop() { + OCPP_loop(); +} + +extern "C" void ao_set_console_out_c(void (*console_out)(const char *msg)) { + ao_set_console_out(console_out); +} + +#ifndef AO_RECEIVE_PAYLOAD_BUFSIZE +#define AO_RECEIVE_PAYLOAD_BUFSIZE 1024 +#endif + +char ao_recv_payload_buff [AO_RECEIVE_PAYLOAD_BUFSIZE] = {'\0'}; + +std::function adaptCb(OnOcppMessage cb) { + return [cb] (JsonObject payload) { + auto len = serializeJson(payload, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); + if (len <= 0) { + AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); + } + cb(len > 0 ? ao_recv_payload_buff : nullptr, len); + }; +} + +std::function adaptCb(void (*cb)()) { + return cb; +} + +ArduinoOcpp::OnReceiveErrorListener adaptCb(OnOcppError cb) { + return [cb] (const char *code, const char *description, JsonObject details) { + auto len = serializeJson(details, ao_recv_payload_buff, AO_RECEIVE_PAYLOAD_BUFSIZE); + if (len <= 0) { + AO_DBG_WARN("Received payload buffer exceeded. Continue without payload"); + } + cb(code, description, len > 0 ? ao_recv_payload_buff : "", len); + }; +} + +std::function adaptCb(SamplerBool cb) { + return cb; +} + +std::function adaptCb(SamplerString cb) { + return cb; +} + +std::function adaptCb(SamplerFloat cb) { + return cb; +} + +std::function adaptCb(SamplerInt cb) { + return cb; +} + +void ao_setPowerActiveImportSampler(SamplerFloat power) { + setPowerActiveImportSampler(adaptCb(power)); +} + +void ao_setEnergyActiveImportSampler(SamplerInt energy) { + setEnergyActiveImportSampler([energy] () -> float { + return (float) energy(); + }); +} + +void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy) { + setEvRequestsEnergySampler(adaptCb(evRequestsEnergy)); +} + +void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized) { + setConnectorEnergizedSampler(adaptCb(connectorEnergized)); +} + +void ao_setConnectorPluggedSampler(SamplerBool connectorPlugged) { + setConnectorPluggedSampler(adaptCb(connectorPlugged)); +} + +void ao_addConnectorErrorCodeSampler(SamplerString connectorErrorCode) { + addConnectorErrorCodeSampler(adaptCb(connectorErrorCode)); +} + +void ao_onChargingRateLimitChange(void (*chargingRateChanged)(float)) { + setOnChargingRateLimitChange(chargingRateChanged); +} + +void ao_onUnlockConnector(SamplerBool unlockConnector) { + setOnUnlockConnector(adaptCb(unlockConnector)); +} + +void ao_onRemoteStartTransactionSendConf(OnOcppMessage onSendConf) { + setOnRemoteStopTransactionSendConf(adaptCb(onSendConf)); +} + +void ao_onRemoteStopTransactionSendConf(OnOcppMessage onSendConf) { + setOnRemoteStopTransactionSendConf(adaptCb(onSendConf)); +} + +void ao_onRemoteStopTransactionRequest(OnOcppMessage onRequest) { + setOnRemoteStopTransactionReceiveReq(adaptCb(onRequest)); +} + +void ao_onResetSendConf(OnOcppMessage onSendConf) { + setOnResetSendConf(adaptCb(onSendConf)); +} + +extern "C" void ao_onResetRequest(OnOcppMessage onRequest) { + setOnResetReceiveReq(adaptCb(onRequest)); +} + +extern "C" void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + bootNotification("model", "vendor", adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +void ao_bootNotification_full(const char *payloadJson, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + DynamicJsonDocument *payload = new DynamicJsonDocument(JSON_OBJECT_SIZE(9) + 230 + 9); // BootNotification has at most 9 attributes with at most 230 chars + null terminators + auto err = deserializeJson(*payload, payloadJson); + if (err) { + AO_DBG_ERR("Could not process input: %s", err.c_str()); + (void)0; + } + + bootNotification(payload, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + authorize(idTag, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + startTransaction(idTag, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +void ao_stopTransaction(OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { + stopTransaction(adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); +} + +int ao_getTransactionId() { + return getTransactionId(); +} + +bool ao_ocppPermitsCharge() { + return ocppPermitsCharge(); +} + +bool ao_isAvailable() { + return isAvailable(); +} + +void ao_beginSession(const char *idTag) { + return beginSession(idTag); +} + +void ao_endSession() { + return endSession(); +} + +bool ao_isInSession() { + return isInSession(); +} + +const char *ao_getSessionIdTag() { + return getSessionIdTag(); +} diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h new file mode 100644 index 00000000..16e45b39 --- /dev/null +++ b/src/ArduinoOcpp_c.h @@ -0,0 +1,107 @@ +#ifndef ARDUINOOCPP_C_H +#define ARDUINOOCPP_C_H + +#include + +struct AOcppSocket; +typedef struct AOcppSocket AOcppSocket; + +typedef void (*OnOcppMessage) (const char *payload, size_t len); +typedef void (*OnOcppAbort) (); +typedef void (*OnOcppTimeout) (); +typedef void (*OnOcppError) (const char *code, const char *description, const char *details_json, size_t details_len); + +typedef float (*SamplerFloat)(); +typedef int (*SamplerInt)(); +typedef bool (*SamplerBool)(); +typedef const char* (*SamplerString)(); + +#ifdef __cplusplus +extern "C" { +#endif + +void ao_initialize(AOcppSocket *osock); + +void ao_deinitialize(); + +void ao_loop(); + +void ao_set_console_out_c(void (*console_out)(const char *msg)); + +/* + * Feed lib with HW related data + */ + +void ao_setPowerActiveImportSampler(SamplerFloat power); + +void ao_setEnergyActiveImportSampler(SamplerInt energy); + +void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy); + +void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized); + +void ao_setConnectorPluggedSampler(SamplerBool connectorPlugged); + +void ao_addConnectorErrorCodeSampler(SamplerString connectorErrorCode); + +/* + * Execute HW related operations on EVSE + */ + +void ao_onChargingRateLimitChange(void (*chargingRateChanged)(float)); + +void ao_onUnlockConnector(SamplerBool unlockConnector); //true: success, false: failure + +/* + * Generic listeners for OCPP operations initiated by Central System + */ + +void ao_onRemoteStartTransactionSendConf(OnOcppMessage onSendConf); //important, energize the power plug here and capture the idTag + +void ao_onRemoteStopTransactionSendConf(OnOcppMessage onSendConf); //important, de-energize the power plug here +void ao_onRemoteStopTransactionRequest(OnOcppMessage onRequest); //optional, to de-energize the power plug immediately + +void ao_onResetSendConf(OnOcppMessage onSendConf); //important, reset your device here (i.e. call ESP.reset();) +void ao_onResetRequest(OnOcppMessage onRequest); //alternative: start reset timer here + +/* + * Initiate OCPP operations + */ + +void ao_bootNotification(const char *chargePointModel, const char *chargePointVendor, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +void ao_bootNotification_full(const char *payloadJson, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +void ao_startTransaction(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +void ao_stopTransaction(OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError); + +/* + * Access OCPP state + */ + +int ao_getTransactionId(); //returns the ID of the current transaction. Returns -1 if called before or after an transaction + +bool ao_ocppPermitsCharge(); + +bool ao_isAvailable(); //if the charge point is operative or inoperative + +/* + * Charging session management + */ + +void ao_beginSession(const char *idTag); + +void ao_endSession(); + +bool ao_isInSession(); + +const char *ao_getSessionIdTag(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ao_opts.h b/src/ao_opts.h new file mode 100644 index 00000000..5a19cede --- /dev/null +++ b/src/ao_opts.h @@ -0,0 +1,31 @@ +#ifndef AOOPTS_H +#define AOOPTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned long ao_tick_ms_impl(); +//unsigned int32_t ao_avail_heap_impl(); + +#ifdef __cplusplus +} +#endif + +#ifndef ao_tick_ms +#define ao_tick_ms ao_tick_ms_impl +#endif + +#ifndef ao_avail_heap +#define ao_avail_heap() 20000 +#endif + +//#ifndef AO_CONSOLE_PRINTF +//#define AO_CONSOLE_PRINTF(...) ESP_LOGI("[ocpp]", __VA_ARGS__) +//#endif + +#ifndef AO_CUSTOM_CONSOLE_MAXMSGSIZE +#define AO_CUSTOM_CONSOLE_MAXMSGSIZE 192 +#endif + +#endif diff --git a/src/ao_opts_impl.c b/src/ao_opts_impl.c new file mode 100644 index 00000000..ce1b6559 --- /dev/null +++ b/src/ao_opts_impl.c @@ -0,0 +1,30 @@ +#include "ao_opts.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned long ao_tick_ms_impl() { + //return xTaskGetTickCount() / configTICK_RATE_HZ; + return (xTaskGetTickCount() * 1000UL) / configTICK_RATE_HZ; +} + +#ifdef __cplusplus +} +#endif From 6701e7a5fcda87ffb6c8b81b27e92c1e5974d211 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 27 Jun 2022 12:07:07 +0200 Subject: [PATCH 035/549] missing includes; fix compilation issues --- CMakeLists.txt | 3 +++ src/ArduinoOcpp/MessagesV16/UnlockConnector.h | 1 + .../Tasks/Metering/ConnectorMeterValuesRecorder.cpp | 2 +- src/ArduinoOcpp/Tasks/Metering/MeterValue.h | 1 + src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp | 1 + src/ArduinoOcpp/Tasks/Metering/SampledValue.h | 1 + src/ArduinoOcpp_c.cpp | 2 +- 7 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56345c10..cc4ae294 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,8 @@ set(AO_SRC src/ArduinoOcpp/Tasks/Heartbeat/HeartbeatService.cpp src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp + src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp + src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingService.cpp src/ArduinoOcpp.cpp @@ -60,6 +62,7 @@ target_compile_options(${COMPONENT_TARGET} PUBLIC -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE -DAO_CUSTOM_UPDATER + -DAO_CUSTOM_RESET -DAO_DEACTIVATE_FLASH -DAO_USE_FILEAPI=ESPIDF_SPIFFS -DAO_DBG_LEVEL=AO_DL_DEBUG diff --git a/src/ArduinoOcpp/MessagesV16/UnlockConnector.h b/src/ArduinoOcpp/MessagesV16/UnlockConnector.h index 2659fd2b..a8b37220 100644 --- a/src/ArduinoOcpp/MessagesV16/UnlockConnector.h +++ b/src/ArduinoOcpp/MessagesV16/UnlockConnector.h @@ -7,6 +7,7 @@ #include #include +#include namespace ArduinoOcpp { namespace Ocpp16 { diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index 56620cd5..5cc05a98 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -73,7 +73,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { AO_DBG_DEBUG("Clock aligned measurement %ds: %s", dt, abs(dt) <= 60 ? - "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); abs(-123); + "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); if (abs(dt) <= 60) { //is measurement still "clock-aligned"? auto alignedMeterValues = alignedDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::SampleClock); if (alignedMeterValues) { diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h index 450f5d5b..541360d5 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace ArduinoOcpp { diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp index 4a570829..3ab56895 100644 --- a/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp @@ -30,6 +30,7 @@ const char *cstrFromReadingContext(ReadingContext context) { return "Trigger"; default: AO_DBG_ERR("ReadingContext not specified"); + /* fall through */ case (ReadingContext::NOT_SET): return nullptr; } diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h index 7debf013..f808bbe7 100644 --- a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h @@ -7,6 +7,7 @@ #include #include +#include namespace ArduinoOcpp { diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 855689c0..bf5c6cc6 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -137,7 +137,7 @@ void ao_bootNotification_full(const char *payloadJson, OnOcppMessage onConfirmat (void)0; } - bootNotification(payload, adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); + bootNotification(std::unique_ptr(payload), adaptCb(onConfirmation), adaptCb(onAbort), adaptCb(onTimeout), adaptCb(onError)); } void ao_authorize(const char *idTag, OnOcppMessage onConfirmation, OnOcppAbort onAbort, OnOcppTimeout onTimeout, OnOcppError onError) { From 48ddb71a7857cf2dbcf50c8fafcdde3694ad1792 Mon Sep 17 00:00:00 2001 From: Matthias Akstaller <63792403+matth-x@users.noreply.github.com> Date: Tue, 28 Jun 2022 11:08:38 +0200 Subject: [PATCH 036/549] Update documentation --- README.md | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e485854a..793edd6a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # Icon   ArduinoOcpp + +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/matth-x/ArduinoOcpp/PlatformIO%20CI?logo=github)](https://github.com/matth-x/ArduinoOcpp/actions) + OCPP-J 1.6 client for the ESP8266 and the ESP32 (more coming soon) Reference usage: [OpenEVSE](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/ocpp.cpp) @@ -34,18 +37,15 @@ For simple chargers, the necessary hardware and internet integration is usually Please take `examples/ESP/main.cpp` as the starting point for your first project. It is a minimal example which shows how to establish an OCPP connection and how to start and stop charging sessions. This guide explains the concepts for a minimal integration. -- To get the library running, you have to install all dependencies (see the list below). In case you use PlatformIO, you can just add `matth-x/ArduinoOcpp` to your project using the PIO library manager. +- To install the dependencies, see the list below for a manual installation or add `matth-x/ArduinoOcpp` to your project using the PIO library manager. -- In your project's `main` file, include `ArduinoOcpp.h`. +- In your project's `main` file, include `ArduinoOcpp.h` and the Wi-Fi library. Initialize Wi-Fi and the Serial output. -- Before establishing an OCPP connection you have to ensure that your device has access to a Wi-Fi access point. All debug messages are printed on the standard serial (i.e. `Serial.print("debug msg")`). To redirect debug messages, please refer to `src/ArduinoOcpp/Platform.h`. +- To connect to the OCPP Central System, call `OCPP_initialize(const char *host, uint16_t port, const char *url)`. For a secure connection with TLS, you need to configure the WebSocket in advance. Please take `examples/ESP-TLS/main.cpp` as an example. -- To connect to your OCPP Central System, call `OCPP_initialize(String OCPP_HOST, uint16_t OCPP_PORT, String OCPP_URL)`. You need to insert the address parameters according to the configuration of your central system. Internally, the library passes these parameters to the WebSocket object without further alteration. - - To secure the connection with TLS, you have to configure the WebSocket. Please take `examples/ESP-TLS/main.cpp` as an example. +- In `setup()`, configure ArduinoOcpp with the hardware drivers. You can leave that part out for the first connection test. Please refer to `ArduinoOcpp.h` for a documentation about the supported EVSE peripherals. -- In your `setup()` function, you can add the configuration functions from `ArduinoOcpp.h` to properly integrate your hardware. For example, the library needs access to the energy meter. All configuration functions are documented in `ArduinoOcpp.h`. - -- Add `OCPP_loop()` to your `loop()` function. +- In `loop()`, add `OCPP_loop()`. **Sending OCPP operations** @@ -58,7 +58,6 @@ In practice, it looks like this: ```cpp void setup() { - ... //other code including the initialization of Wi-Fi and OCPP bootNotification("My CP model name", "My company name", [] (JsonObject confMsg) { @@ -66,8 +65,7 @@ void setup() { Serial.print(F("BootNotification was answered. Central System clock: ")); Serial.println(confMsg["currentTime"].as()); //"currentTime" is a field of the central system response - //Notify your hardare that the BootNotification.conf() has arrived. E.g.: - //evseIsBooted = true; + //evseIsBooted = true; <-- Example: Notify your hardare that the BootNotification.conf() has arrived }); ... //rest of setup() function; executed immediately as bootNotification() is non-blocking @@ -78,15 +76,15 @@ The parameters `chargePointModel` and `chargePointVendor` are equivalent to the **Receiving OCPP operations** -The library also reacts on CS-initiated operations. You can add your own behavior there too. For example, to flash a LED on receipt of a `Set Charging Profile` request, use the following function. +You can also add customized behavior to incoming OCPP messages. For example, to flash an LED on receipt of a `Set Charging Profile` request, use the following function. ```cpp setOnSetChargingProfileRequest([] (JsonObject payload) { - //... + //... will be executed every time this EVSE receives a new Charging Profile }); ``` -You can also process the original payload from the CS using the `payload` object. +Using the `payload` object you can access the original payload from the CS. *To get started quickly with or without EVSE hardware, you can flash the sketch in `examples/SECC` onto your ESP. That example mimics a full OCPP communications controller as it would look like in a real charging station. You can build a charger prototype based on that example or just view the internal state using the device monitor.* @@ -132,13 +130,9 @@ In case you use PlatformIO, you can copy all dependencies from `platformio.ini` ## Next development steps -- [x] introduce proper offline behavior and package loss / fault detection -- [x] handle fragmented input messages correctly -- [x] add support for multiple power connectors -- [x] add support for the ESP32 -- [ ] reach full compliance to OCPP 1.6 Smart Charging Profile +- [x] reach full compliance to OCPP 1.6 Smart Charging Profile - [ ] integrate Authorization Cache -- [ ] **get ready for OCPP 2.0.1** +- [ ] **get ready for OCPP 2.0.1 and ISO 15118** ## Further help From 67a32ec8fdd1dbcb7bf30fc66af834e9bade809e Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 28 Jun 2022 11:11:43 +0200 Subject: [PATCH 037/549] integrate GetCompositeSchedule --- README.md | 80 +++++----------- .../MessagesV16/GetCompositeSchedule.cpp | 95 +++++++++++++++++++ .../MessagesV16/GetCompositeSchedule.h | 36 +++++++ .../MessagesV16/RemoteStartTransaction.cpp | 1 + .../SimpleOcppOperationFactory.cpp | 3 + .../Metering/ConnectorMeterValuesRecorder.cpp | 2 +- .../SmartCharging/SmartChargingModel.cpp | 5 +- 7 files changed, 164 insertions(+), 58 deletions(-) create mode 100644 src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp create mode 100644 src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.h diff --git a/README.md b/README.md index 44b2988e..fac6eacf 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,18 @@ PlatformIO package: [ArduinoOcpp](https://platformio.org/lib/show/11975/ArduinoO Website: [www.arduino-ocpp.com](https://www.arduino-ocpp.com) -Full compatibility with the Arduino platform. Need a **FreeRTOS** version? Please [contact me](https://github.com/matth-x/ArduinoOcpp#further-help) +Fully integrated into the Arduino platform. Compatible with ESP-IDF and generic FreeRTOS ## Make your EVSE ready for OCPP :car::electric_plug::battery: You can build an OCPP Charge Point controller using the popular, Wi-Fi enabled microcontrollers ESP8266, ESP32 and comparable. This library allows your EVSE to communicate with an OCPP Central System and to participate in your Charging Network. -:heavy_check_mark: Works with [SteVe](https://github.com/RWTH-i5-IDSG/steve) and [The Mobility House OCPP package](https://github.com/mobilityhouse/ocpp) - -:heavy_check_mark: Passed compatibility tests with further commercial Central Systems +:heavy_check_mark: Works with [SteVe](https://github.com/RWTH-i5-IDSG/steve), [The Mobility House OCPP package](https://github.com/mobilityhouse/ocpp) and further commercial Central Systems :heavy_check_mark: Integrated and tested in many charging stations +:heavy_check_mark: Eligible for **public chargers**. Complies also with the legal requirements of the German Ladesäulenverordnung (LSV) + ### Features - handles the OCPP communication with the charging network @@ -37,18 +37,15 @@ For simple chargers, the necessary hardware and internet integration is usually Please take `examples/ESP/main.cpp` as the starting point for your first project. It is a minimal example which shows how to establish an OCPP connection and how to start and stop charging sessions. This guide explains the concepts for a minimal integration. -- To get the library running, you have to install all dependencies (see the list below). In case you use PlatformIO, you can just add `matth-x/ArduinoOcpp` to your project using the PIO library manager. +- To install the dependencies, see the list below for a manual installation or add `matth-x/ArduinoOcpp` to your project using the PIO library manager. -- In your project's `main` file, include `ArduinoOcpp.h`. +- In your project's `main` file, include `ArduinoOcpp.h` and the Wi-Fi library. Initialize Wi-Fi and the Serial output. -- Before establishing an OCPP connection you have to ensure that your device has access to a Wi-Fi access point. All debug messages are printed on the standard serial (i.e. `Serial.print("debug msg")`). To redirect debug messages, please refer to `src/ArduinoOcpp/Platform.h`. +- To connect to the OCPP Central System, call `OCPP_initialize(const char *host, uint16_t port, const char *url)`. For a secure connection with TLS, you need to configure the WebSocket in advance. Please take `examples/ESP-TLS/main.cpp` as an example. -- To connect to your OCPP Central System, call `OCPP_initialize(String OCPP_HOST, uint16_t OCPP_PORT, String OCPP_URL)`. You need to insert the address parameters according to the configuration of your central system. Internally, the library passes these parameters to the WebSocket object without further alteration. - - To secure the connection with TLS, you have to configure the WebSocket. Please take `examples/ESP-TLS/main.cpp` as an example. +- In `setup()`, configure ArduinoOcpp with the hardware drivers. You can leave that part out for the first connection test. Please refer to `ArduinoOcpp.h` for a documentation about the supported EVSE peripherals. -- In your `setup()` function, you can add the configuration functions from `ArduinoOcpp.h` to properly integrate your hardware. For example, the library needs access to the energy meter. All configuration functions are documented in `ArduinoOcpp.h`. - -- Add `OCPP_loop()` to your `loop()` function. +- In `loop()`, add `OCPP_loop()`. **Sending OCPP operations** @@ -61,7 +58,6 @@ In practice, it looks like this: ```cpp void setup() { - ... //other code including the initialization of Wi-Fi and OCPP bootNotification("My CP model name", "My company name", [] (JsonObject confMsg) { @@ -69,8 +65,7 @@ void setup() { Serial.print(F("BootNotification was answered. Central System clock: ")); Serial.println(confMsg["currentTime"].as()); //"currentTime" is a field of the central system response - //Notify your hardare that the BootNotification.conf() has arrived. E.g.: - //evseIsBooted = true; + //evseIsBooted = true; <-- Example: Notify your hardare that the BootNotification.conf() has arrived }); ... //rest of setup() function; executed immediately as bootNotification() is non-blocking @@ -81,67 +76,44 @@ The parameters `chargePointModel` and `chargePointVendor` are equivalent to the **Receiving OCPP operations** -The library also reacts on CS-initiated operations. You can add your own behavior there too. For example, to flash a LED on receipt of a `Set Charging Profile` request, use the following function. +You can also add customized behavior to incoming OCPP messages. For example, to flash an LED on receipt of a `Set Charging Profile` request, use the following function. ```cpp setOnSetChargingProfileRequest([] (JsonObject payload) { - //... + //... will be executed every time this EVSE receives a new Charging Profile }); ``` -You can also process the original payload from the CS using the `payload` object. +Using the `payload` object you can access the original payload from the CS. *To get started quickly with or without EVSE hardware, you can flash the sketch in `examples/SECC` onto your ESP. That example mimics a full OCPP communications controller as it would look like in a real charging station. You can build a charger prototype based on that example or just view the internal state using the device monitor.* ## Dependencies +Mandatory: + - [bblanchon/ArduinoJSON](https://github.com/bblanchon/ArduinoJson) (please upgrade to version `6.19.1`) + +If compiled with the Arduino integration: + - [Links2004/arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) (please upgrade to version `2.3.6`) In case you use PlatformIO, you can copy all dependencies from `platformio.ini` into your own configuration file. Alternatively, you can install the full library with dependencies by adding `matth-x/ArduinoOcpp` in the PIO library manager. ## Supported operations -| Operation name | supported | in progress | not supported | -| -------------- | :---------: | :-----------: | :-------------: | -| **Core profile** | -| `Authorize` | :heavy_check_mark: | -| `BootNotification` | :heavy_check_mark: | -| `ChangeAvailability` | :heavy_check_mark: | -| `ChangeConfiguration` | :heavy_check_mark: | -| `ClearCache` | :heavy_check_mark: | -| `DataTransfer` | :heavy_check_mark: | -| `GetConfiguration` | :heavy_check_mark: | -| `Heartbeat` | :heavy_check_mark: | -| `MeterValues` | :heavy_check_mark: | -| `RemoteStartTransaction` | :heavy_check_mark: | -| `RemoteStopTransaction` | :heavy_check_mark: | -| `Reset` | :heavy_check_mark: | -| `StartTransaction` | :heavy_check_mark: | -| `StatusNotification` | :heavy_check_mark: | -| `StopTransaction` | :heavy_check_mark: | -| `UnlockConnector` | :heavy_check_mark: | -| **Smart charging profile** | -| `ClearChargingProfile` | :heavy_check_mark: | -| `GetCompositeSchedule` | | | :heavy_multiplication_x: | -| `SetChargingProfile` | :heavy_check_mark: | -| **Remote trigger profile** | -| `TriggerMessage` | :heavy_check_mark: | -| **Firmware management** | -| `GetDiagnostics` | :heavy_check_mark: | -| `DiagnosticsStatusNotification` | :heavy_check_mark: | -| `FirmwareStatusNotification` | :heavy_check_mark: | -| `UpdateFirmware` | :heavy_check_mark: | +| Feature profile | supported | in progress | +| -------------- | :---------: | :-----------: | +| **Core** | :heavy_check_mark: | +| **Smart charging** | :heavy_check_mark: | +| **Remote trigger** | :heavy_check_mark: | +| **Firmware management** | :heavy_check_mark: | ## Next development steps -- [x] introduce proper offline behavior and package loss / fault detection -- [x] handle fragmented input messages correctly -- [x] add support for multiple power connectors -- [x] add support for the ESP32 -- [ ] reach full compliance to OCPP 1.6 Smart Charging Profile +- [x] reach full compliance to OCPP 1.6 Smart Charging Profile - [ ] integrate Authorization Cache -- [ ] **get ready for OCPP 2.0.1** +- [ ] **get ready for OCPP 2.0.1 and ISO 15118** ## Further help diff --git a/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp b/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp new file mode 100644 index 00000000..96399201 --- /dev/null +++ b/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp @@ -0,0 +1,95 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include +#include +#include +#include + +#include + +using ArduinoOcpp::Ocpp16::GetCompositeSchedule; + +GetCompositeSchedule::GetCompositeSchedule() { + +} + +const char* GetCompositeSchedule::getOcppOperationType(){ + return "GetCompositeSchedule"; +} + +void GetCompositeSchedule::processReq(JsonObject payload) { + + connectorId = payload["connectorId"] | -1; + duration = payload["duration"] | 0; + auto unitString = payload["chargingRateUnit"] | "W"; + + if (unitString[0] == 'A' || unitString[0] == 'a') { + chargingRateUnit = ChargingRateUnitType::Amp; + } else if (unitString[0] == 'W' || unitString[0] == 'w') { + chargingRateUnit = ChargingRateUnitType::Watt; + } else { + errorCode = "PropertyConstraintViolation"; + } + + if (ocppModel && ocppModel->getChargePointStatusService()) { + if (connectorId >= ocppModel->getChargePointStatusService()->getNumConnectors()) { + errorCode = "PropertyConstraintViolation"; + } + } + + if (connectorId < 0 || !payload.containsKey("duration")) { + errorCode = "FormatViolation"; + } + + if (!ocppModel || !ocppModel->getSmartChargingService()) { + AO_DBG_ERR("SmartChargingService not initialized! Ignore request"); + errorCode = "NotSupported"; + } +} + +std::unique_ptr GetCompositeSchedule::createConf(){ + if (!ocppModel || !ocppModel->getSmartChargingService()) { + return nullptr; + } + + auto scService = ocppModel->getSmartChargingService(); + ChargingSchedule *composite = scService->getCompositeSchedule(connectorId, duration); + DynamicJsonDocument *compositeJson {nullptr}; + + if (composite) { + compositeJson = composite->toJsonDocument(); + } + + std::unique_ptr doc; + + if (compositeJson) { + doc.reset(new DynamicJsonDocument(JSON_OBJECT_SIZE(4) + JSONDATE_LENGTH + 1 + compositeJson->capacity())); + JsonObject payload = doc->to(); + payload["status"] = "Accepted"; + if (connectorId > 0) + payload["connectorId"] = connectorId; + + char scheduleStart [JSONDATE_LENGTH + 1] {'\0'}; + auto startSchedule = (*compositeJson)["startSchedule"] | ""; + if (startSchedule[0] != '\0') { + strncpy(scheduleStart, startSchedule, JSONDATE_LENGTH + 1); + } else { + ocppModel->getOcppTime().getOcppTimestampNow().toJsonString(scheduleStart, JSONDATE_LENGTH + 1); + } + payload["scheduleStart"] = scheduleStart; + + payload["chargingSchedule"] = *compositeJson; + } else { + doc.reset(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); + JsonObject payload = doc->to(); + payload["status"] = "Rejected"; + } + + delete compositeJson; + delete composite; + + return doc; +} diff --git a/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.h b/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.h new file mode 100644 index 00000000..00482796 --- /dev/null +++ b/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.h @@ -0,0 +1,36 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef GETCOMPOSITESCHEDULE_H +#define GETCOMPOSITESCHEDULE_H + +#include +#include +#include + +namespace ArduinoOcpp { +namespace Ocpp16 { + +class GetCompositeSchedule : public OcppMessage { +private: + int connectorId {-1}; + otime_t duration {0}; + ChargingRateUnitType chargingRateUnit {ChargingRateUnitType::Watt}; + + const char *errorCode {nullptr}; +public: + GetCompositeSchedule(); + + const char* getOcppOperationType(); + + void processReq(JsonObject payload); + + std::unique_ptr createConf(); + + const char *getErrorCode() {return errorCode;} +}; + +} //end namespace Ocpp16 +} //end namespace ArduinoOcpp +#endif diff --git a/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp index 39835d7a..864035d0 100644 --- a/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/RemoteStartTransaction.cpp @@ -92,6 +92,7 @@ std::unique_ptr RemoteStartTransaction::createConf(){ bool ret = scService->clearChargingProfile([clearProfileId](int id, int, ChargingProfilePurposeType, int) { return id == clearProfileId; }); + (void)ret; *sRmtProfileId = -1; AO_DBG_DEBUG("Cleared Charging Profile from previous RemoteStartTx: %s", ret ? "success" : "already cleared"); diff --git a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp index 590818e5..274ac806 100644 --- a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp +++ b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -214,6 +215,8 @@ std::unique_ptr makeOcppOperation(const char *messageType, int co } else if (!strcmp(messageType, "BootNotification")) { msg = std::unique_ptr(new Ocpp16::BootNotification()); operation->setOnReceiveReqListener(onBootNotificationRequest); + } else if (!strcmp(messageType, "GetCompositeSchedule")) { + msg = std::unique_ptr(new Ocpp16::GetCompositeSchedule()); } else if (!strcmp(messageType, "Heartbeat")) { msg = std::unique_ptr(new Ocpp16::Heartbeat()); } else if (!strcmp(messageType, "MeterValues")) { diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index 56620cd5..5cc05a98 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -73,7 +73,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { AO_DBG_DEBUG("Clock aligned measurement %ds: %s", dt, abs(dt) <= 60 ? - "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); abs(-123); + "in time (tolerance <= 60s)" : "off, e.g. because of first run. Ignore"); if (abs(dt) <= 60) { //is measurement still "clock-aligned"? auto alignedMeterValues = alignedDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::SampleClock); if (alignedMeterValues) { diff --git a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp index 2fd2a28b..1a4b469e 100644 --- a/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp +++ b/src/ArduinoOcpp/Tasks/SmartCharging/SmartChargingModel.cpp @@ -371,9 +371,8 @@ bool ChargingProfile::checkTransactionAssignment(int txId, int profileId) { return transactionId == txId; //return if they do match } - AO_DBG_ERR("Check error"); - //neither txIds nor profileIDs apply - return false; + AO_DBG_DEBUG("Neither txIds nor profileIDs apply"); + return true; } int ChargingProfile::getStackLevel(){ From f1e289454c860129ef248f96e74be6968e2b791f Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 28 Jun 2022 11:20:45 +0200 Subject: [PATCH 038/549] add interface for custom meters --- src/ArduinoOcpp_c.cpp | 19 +++++++++++++++++++ src/ArduinoOcpp_c.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index bf5c6cc6..03fbc26a 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -81,6 +81,25 @@ void ao_setEnergyActiveImportSampler(SamplerInt energy) { }); } +void ao_addMeterValueSampler_Int(SamplerInt sampler, const char *measurand, const char *phase, const char *unit) { + + ArduinoOcpp::SampledValueProperties properties; + if (measurand) + properties.setMeasurand(measurand); + if (phase) + properties.setPhase(phase); + if (unit) + properties.setUnit(unit); + + auto adaptSampler = [sampler] (ArduinoOcpp::ReadingContext) -> int32_t { + return sampler(); + }; + + auto reader = new ArduinoOcpp::SampledValueSamplerConcrete>(properties, adaptSampler); + addMeterValueSampler( + std::unique_ptr>>(reader)); +} + void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy) { setEvRequestsEnergySampler(adaptCb(evRequestsEnergy)); } diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index 16e45b39..9f4235e8 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -36,6 +36,8 @@ void ao_setPowerActiveImportSampler(SamplerFloat power); void ao_setEnergyActiveImportSampler(SamplerInt energy); +void ao_addMeterValueSampler_Int(SamplerInt sampler, const char *measurand, const char *phase, const char *unit); + void ao_setEvRequestsEnergySampler(SamplerBool evRequestsEnergy); void ao_setConnectorEnergizedSampler(SamplerBool connectorEnergized); From 3e103b076fe8aff51f93c76adeada7ebea81323f Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 4 Jul 2022 18:05:54 +0200 Subject: [PATCH 039/549] send txData in StopTransaction --- examples/SECC/main.cpp | 1 - .../MessagesV16/StartTransaction.cpp | 2 +- .../MessagesV16/StopTransaction.cpp | 28 ++++++++++++- src/ArduinoOcpp/MessagesV16/StopTransaction.h | 6 ++- .../Metering/ConnectorMeterValuesRecorder.cpp | 39 +++++++++++++++++-- .../Metering/ConnectorMeterValuesRecorder.h | 5 ++- .../Tasks/Metering/MeteringService.cpp | 14 ++++++- .../Tasks/Metering/MeteringService.h | 4 +- 8 files changed, 85 insertions(+), 14 deletions(-) diff --git a/examples/SECC/main.cpp b/examples/SECC/main.cpp index 1ca19e35..97d4cf25 100644 --- a/examples/SECC/main.cpp +++ b/examples/SECC/main.cpp @@ -157,7 +157,6 @@ void setup() { */ std::shared_ptr filesystem = ArduinoOcpp::EspWiFi::makeDefaultFilesystemAdapter(ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail); ArduinoOcpp::configuration_init(filesystem); - filesystem = nullptr; /* * WiFiManager opens a captive portal, lets the user enter the WiFi credentials and provides a settings diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp index bee6d6cf..fa501eca 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp @@ -28,7 +28,7 @@ const char* StartTransaction::getOcppOperationType() { void StartTransaction::initiate() { if (ocppModel && ocppModel->getMeteringService()) { auto meteringService = ocppModel->getMeteringService(); - meterStart = meteringService->readEnergyActiveImportRegister(connectorId); + meterStart = meteringService->readTxEnergyMeter(connectorId, ReadingContext::TransactionBegin); } if (ocppModel) { diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp index 6466a85c..7b214cd7 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include using ArduinoOcpp::Ocpp16::StopTransaction; @@ -24,7 +25,8 @@ void StopTransaction::initiate() { if (ocppModel && ocppModel->getMeteringService()) { auto meteringService = ocppModel->getMeteringService(); - meterStop = meteringService->readEnergyActiveImportRegister(connectorId); + meterStop = meteringService->readTxEnergyMeter(connectorId, ReadingContext::TransactionEnd); + transactionData = meteringService->createStopTxMeterData(connectorId); } if (ocppModel) { @@ -52,7 +54,27 @@ std::unique_ptr StopTransaction::createReq() { return nullptr; } - auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(5) + (JSONDATE_LENGTH + 1) + (REASON_LEN_MAX + 1))); + std::vector> txDataJson; + size_t txDataJson_size = 0; + for (auto mv = transactionData.begin(); mv != transactionData.end(); mv++) { + auto mvJson = (*mv)->toJson(); + if (!mvJson) { + return nullptr; + } + txDataJson_size += mvJson->capacity(); + txDataJson.emplace_back(std::move(mvJson)); + } + + DynamicJsonDocument txDataDoc = DynamicJsonDocument(JSON_ARRAY_SIZE(txDataJson.size()) + txDataJson_size); + for (auto mvJson = txDataJson.begin(); mvJson != txDataJson.end(); mvJson++) { + txDataDoc.add(**mvJson); + } + + auto doc = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(6) + //total of 6 fields + (JSONDATE_LENGTH + 1) + //timestamp string + (REASON_LEN_MAX + 1) + //reason string + txDataDoc.capacity())); JsonObject payload = doc->to(); if (meterStop && *meterStop) { @@ -74,6 +96,8 @@ std::unique_ptr StopTransaction::createReq() { payload["reason"] = reason; } + payload["transactionData"] = txDataDoc; + return doc; } diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.h b/src/ArduinoOcpp/MessagesV16/StopTransaction.h index 08a91957..eaaacde8 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.h @@ -8,9 +8,12 @@ #include #include #include -#include namespace ArduinoOcpp { + +class SampledValue; +class MeterValue; + namespace Ocpp16 { class StopTransaction : public OcppMessage { @@ -19,6 +22,7 @@ class StopTransaction : public OcppMessage { std::unique_ptr meterStop {nullptr}; OcppTimestamp otimestamp; char reason [REASON_LEN_MAX] {'\0'}; + std::vector> transactionData; public: StopTransaction(int connectorId, const char *reason = nullptr); diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index 5cc05a98..8d68985c 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -120,6 +120,17 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { auto connector = context.getConnectorStatus(connectorId); if (connector && connector->getTransactionId() != lastTransactionId) { //transaction break + + //take a transaction-related sample which is aligned to the transaction break + if (connector->getTransactionId() >= 0 && lastTransactionId < 0) { + auto sampleStartTx = stopTxnSampledDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::TransactionBegin); + if (sampleStartTx) { + stopTxnSampledData.push_back(std::move(sampleStartTx)); + } + } else if (connector->getTransactionId() >= 0 && lastTransactionId > 0) { + AO_DBG_ERR("Cannot switch txId"); + } + MeterValues *meterValues = nullptr; if (!sampledData.empty()) { meterValues = new MeterValues(std::move(sampledData), connectorId, lastTransactionId); @@ -189,11 +200,31 @@ void ConnectorMeterValuesRecorder::addMeterValueSampler(std::unique_ptr ConnectorMeterValuesRecorder::readEnergyActiveImportRegister() { +std::unique_ptr ConnectorMeterValuesRecorder::readTxEnergyMeter(ReadingContext reason) { if (energySamplerIndex >= 0 && energySamplerIndex < samplers.size()) { - return samplers[energySamplerIndex]->takeValue(ReadingContext::NOT_SET); + return samplers[energySamplerIndex]->takeValue(reason); } else { - AO_DBG_DEBUG("Called readEnergyActiveImportRegister(), but no energySampler or handling strategy set"); - return 0; + AO_DBG_DEBUG("Called readTxEnergyMeter(), but no energySampler or handling strategy set"); + return nullptr; + } +} + +std::vector> ConnectorMeterValuesRecorder::createStopTxMeterData() { + + //create final StopTxSample + if (*MeterValueSampleInterval >= 1) { //... only if sampled Meter Values are activated + auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(context.getOcppTime().getOcppTimestampNow(), ReadingContext::TransactionEnd); + if (sampleStopTx) { + stopTxnSampledData.push_back(std::move(sampleStopTx)); + } } + + //concatenate sampled and aligned meter data; clear all StopTX data in this object + auto res{std::move(stopTxnSampledData)}; + res.insert(res.end(), std::make_move_iterator(stopTxnAlignedData.begin()), + std::make_move_iterator(stopTxnAlignedData.end())); + stopTxnSampledData.clear(); //make vectors defined after moving from them + stopTxnAlignedData.clear(); + + return std::move(res); } diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h index a145438b..7377ffed 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h @@ -74,11 +74,12 @@ class ConnectorMeterValuesRecorder { void addMeterValueSampler(std::unique_ptr meterValueSampler); - std::unique_ptr readEnergyActiveImportRegister(); + std::unique_ptr readTxEnergyMeter(ReadingContext context); OcppMessage *takeTriggeredMeterValues(); - OcppMessage *getStopTransactionData(); + std::vector> createStopTxMeterData(); + }; } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp index c5522f78..f7567cec 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.cpp @@ -53,12 +53,12 @@ void MeteringService::addMeterValueSampler(int connectorId, std::unique_ptraddMeterValueSampler(std::move(meterValueSampler)); } -std::unique_ptr MeteringService::readEnergyActiveImportRegister(int connectorId) { +std::unique_ptr MeteringService::readTxEnergyMeter(int connectorId, ReadingContext context) { if (connectorId < 0 || connectorId >= connectors.size()) { AO_DBG_ERR("connectorId is out of bounds"); return nullptr; } - return connectors[connectorId]->readEnergyActiveImportRegister(); + return connectors[connectorId]->readTxEnergyMeter(context); } std::unique_ptr MeteringService::takeTriggeredMeterValues(int connectorId) { @@ -80,3 +80,13 @@ std::unique_ptr MeteringService::takeTriggeredMeterValues(int con AO_DBG_ERR("Could not find connector"); return nullptr; } + +std::vector> MeteringService::createStopTxMeterData(int connectorId) { + if (connectorId < 0 || connectorId >= connectors.size()) { + AO_DBG_ERR("connectorId is out of bounds"); + return std::vector>(); + } + auto& connector = connectors[connectorId]; + + return connector->createStopTxMeterData(); +} diff --git a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h index 4b51defe..bad4ca29 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeteringService.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeteringService.h @@ -36,10 +36,12 @@ class MeteringService { void addMeterValueSampler(int connectorId, std::unique_ptr meterValueSampler); - std::unique_ptr readEnergyActiveImportRegister(int connectorId); + std::unique_ptr readTxEnergyMeter(int connectorId, ReadingContext reason); std::unique_ptr takeTriggeredMeterValues(int connectorId); //snapshot of all meters now + std::vector> createStopTxMeterData(int connectorId); + int getNumConnectors() {return connectors.size();} }; From 707ef4b2ee20fff29aa7f4184291f813a3da98a3 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 4 Jul 2022 18:36:49 +0200 Subject: [PATCH 040/549] fix data types --- .../Tasks/Metering/ConnectorMeterValuesRecorder.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index 8d68985c..2633828a 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -60,7 +60,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { if (*ClockAlignedDataInterval >= 1) { - if (alignedData.size() >= *MeterValuesAlignedDataMaxLength) { + if (alignedData.size() >= (size_t) *MeterValuesAlignedDataMaxLength) { auto meterValues = new MeterValues(std::move(alignedData), connectorId, -1); alignedData.clear(); return meterValues; @@ -111,7 +111,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { if (*MeterValueSampleInterval >= 1) { //record periodic tx data - if (sampledData.size() >= *MeterValuesSampledDataMaxLength) { + if (sampledData.size() >= (size_t) *MeterValuesSampledDataMaxLength) { auto meterValues = new MeterValues(std::move(sampledData), connectorId, lastTransactionId); sampledData.clear(); return meterValues; @@ -201,7 +201,7 @@ void ConnectorMeterValuesRecorder::addMeterValueSampler(std::unique_ptr ConnectorMeterValuesRecorder::readTxEnergyMeter(ReadingContext reason) { - if (energySamplerIndex >= 0 && energySamplerIndex < samplers.size()) { + if (energySamplerIndex >= 0 && (size_t) energySamplerIndex < samplers.size()) { return samplers[energySamplerIndex]->takeValue(reason); } else { AO_DBG_DEBUG("Called readTxEnergyMeter(), but no energySampler or handling strategy set"); @@ -220,7 +220,7 @@ std::vector> ConnectorMeterValuesRecorder::createSto } //concatenate sampled and aligned meter data; clear all StopTX data in this object - auto res{std::move(stopTxnSampledData)}; + decltype(stopTxnSampledData) res {std::move(stopTxnSampledData)}; res.insert(res.end(), std::make_move_iterator(stopTxnAlignedData.begin()), std::make_move_iterator(stopTxnAlignedData.end())); stopTxnSampledData.clear(); //make vectors defined after moving from them From bbbc1f74d66943aa3574ecfe950a087a3b47214d Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 4 Jul 2022 19:37:57 +0200 Subject: [PATCH 041/549] update facade --- src/ArduinoOcpp.cpp | 33 +++++++++++++++++++++++++++++++++ src/ArduinoOcpp.h | 3 +-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 781fc9c1..a9495203 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -224,6 +224,39 @@ void addMeterValueSampler(std::unique_ptr meterValueSampler model.getMeteringService()->addMeterValueSampler(OCPP_ID_OF_CONNECTOR, std::move(meterValueSampler)); //connectorId=1 } +void addMeterValueSampler(std::function value, const char *measurand, const char *unit, const char *location, const char *phase) { + if (!ocppEngine) { + AO_DBG_ERR("Please call OCPP_initialize before"); + return; + } + + if (!value) { + AO_DBG_ERR("value undefined"); + return; + } + + if (!measurand) { + measurand = "Energy.Active.Import.Register"; + AO_DBG_WARN("Measurand unspecified; assume %s", measurand); + } + + SampledValueProperties properties; + properties.setMeasurand(measurand); //mandatory for AO + + if (unit) + properties.setUnit(unit); + if (location) + properties.setLocation(location); + if (phase) + properties.setPhase(phase); + + auto valueSampler = std::unique_ptr>>( + new ArduinoOcpp::SampledValueSamplerConcrete>( + properties, + [value] (ArduinoOcpp::ReadingContext) -> int32_t {return value();})); + addMeterValueSampler(std::move(valueSampler)); +} + void setEvRequestsEnergySampler(std::function evRequestsEnergy) { if (!ocppEngine) { AO_DBG_ERR("Please call OCPP_initialize before"); diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index 40014f94..c02c19af 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -55,6 +55,7 @@ void setPowerActiveImportSampler(std::function power); void setEnergyActiveImportSampler(std::function energy); void addMeterValueSampler(std::unique_ptr meterValueSampler); +void addMeterValueSampler(std::function value, const char *measurand = nullptr, const char *unit = nullptr, const char *location = nullptr, const char *phase = nullptr); void setEvRequestsEnergySampler(std::function evRequestsEnergy); @@ -62,8 +63,6 @@ void setConnectorEnergizedSampler(std::function connectorEnergized); void setConnectorPluggedSampler(std::function connectorPlugged); -//void setConnectorFaultedSampler(std::function connectorFailed); - void addConnectorErrorCodeSampler(std::function connectorErrorCode); /* From e8202a9d68ebe724c097128dc795dbe8f86f4a14 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 4 Jul 2022 23:03:20 +0200 Subject: [PATCH 042/549] omit txData in stopTx if empty --- src/ArduinoOcpp/MessagesV16/StopTransaction.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp index 7b214cd7..69a85f27 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp @@ -96,7 +96,9 @@ std::unique_ptr StopTransaction::createReq() { payload["reason"] = reason; } - payload["transactionData"] = txDataDoc; + if (!transactionData.empty()) { + payload["transactionData"] = txDataDoc; + } return doc; } From 8741300eea920b884ebf2c71eb6fe12f3c98799b Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Tue, 5 Jul 2022 16:35:35 +0200 Subject: [PATCH 043/549] remove obsolete txId tracking --- src/ArduinoOcpp/MessagesV16/MeterValues.cpp | 4 ++-- src/ArduinoOcpp/MessagesV16/MeterValues.h | 3 +-- .../Tasks/Metering/ConnectorMeterValuesRecorder.cpp | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp index 87559f96..cb7b01cb 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp @@ -15,8 +15,8 @@ MeterValues::MeterValues() { } -MeterValues::MeterValues(std::vector>&& meterValue, int connectorId, int transactionId) - : meterValue{std::move(meterValue)}, connectorId{connectorId}, transactionId{transactionId} { +MeterValues::MeterValues(std::vector>&& meterValue, int connectorId) + : meterValue{std::move(meterValue)}, connectorId{connectorId} { } diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.h b/src/ArduinoOcpp/MessagesV16/MeterValues.h index 24f4be0e..898899ba 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.h +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.h @@ -19,10 +19,9 @@ class MeterValues : public OcppMessage { std::vector> meterValue; int connectorId = 0; - int transactionId = -1; public: - MeterValues(std::vector>&& meterValue, int connectorId, int transactionId); + MeterValues(std::vector>&& meterValue, int connectorId); MeterValues(); //for debugging only. Make this for the server pendant diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index 2633828a..a856e2ba 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -61,7 +61,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { if (*ClockAlignedDataInterval >= 1) { if (alignedData.size() >= (size_t) *MeterValuesAlignedDataMaxLength) { - auto meterValues = new MeterValues(std::move(alignedData), connectorId, -1); + auto meterValues = new MeterValues(std::move(alignedData), connectorId); alignedData.clear(); return meterValues; } @@ -112,7 +112,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { //record periodic tx data if (sampledData.size() >= (size_t) *MeterValuesSampledDataMaxLength) { - auto meterValues = new MeterValues(std::move(sampledData), connectorId, lastTransactionId); + auto meterValues = new MeterValues(std::move(sampledData), connectorId); sampledData.clear(); return meterValues; } @@ -133,7 +133,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { MeterValues *meterValues = nullptr; if (!sampledData.empty()) { - meterValues = new MeterValues(std::move(sampledData), connectorId, lastTransactionId); + meterValues = new MeterValues(std::move(sampledData), connectorId); sampledData.clear(); } lastTransactionId = connector->getTransactionId(); @@ -182,7 +182,7 @@ OcppMessage *ConnectorMeterValuesRecorder::takeTriggeredMeterValues() { decltype(sampledData) mv_now; mv_now.push_back(std::move(sample)); - return new MeterValues(std::move(mv_now), connectorId, txId_now); + return new MeterValues(std::move(mv_now), connectorId); } void ConnectorMeterValuesRecorder::setPowerSampler(PowerSampler ps){ From de19d2a00446b25d53d82e6f96584308f726eaa9 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:01:32 +0200 Subject: [PATCH 044/549] fix missing debug message null check --- src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index 8c809ff3..67f44dff 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -287,7 +287,7 @@ const char *ConnectorStatus::getErrorCode() { } void ConnectorStatus::beginSession(const char *sessionIdTag) { - AO_DBG_DEBUG("Begin session with idTag %s, overwriting idTag %s", sessionIdTag, idTag); + AO_DBG_DEBUG("Begin session with idTag %s, overwriting idTag %s", sessionIdTag != nullptr ? sessionIdTag : "", idTag); if (!sessionIdTag || *sessionIdTag == '\0') { //input string is empty snprintf(idTag, IDTAG_LEN_MAX + 1, "A0-00-00-00"); From cba8cf2172fcb618cad095b4a49f3f00024d2021 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:30:02 +0200 Subject: [PATCH 045/549] update StartTx trigger --- src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index 67f44dff..ac671131 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -61,7 +61,9 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) * - instruct the OCMF meter to begin a transaction (if OCMF meter handler is set) */ txTriggerConditions.push_back([this] () -> TxCondition { - return getSessionIdTag() == nullptr ? TxCondition::Inactive : TxCondition::Active; + if (!session) + return TxCondition::Inactive; + return getSessionIdTag() ? TxCondition::Active : TxCondition::Inactive; }); txEnableSequence.push_back([this] (TxCondition cond) -> TxEnableState { if (onOcmfMeterPollTx) { From cf3f90530e9900a5f897f0085e5aa99fffe0110d Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 14 Jul 2022 10:20:12 +0200 Subject: [PATCH 046/549] add energy meter timeout; small changes --- .../MessagesV16/GetCompositeSchedule.cpp | 5 ++++- .../MessagesV16/GetDiagnostics.cpp | 2 +- src/ArduinoOcpp/MessagesV16/MeterValues.cpp | 19 +++++++++++++++---- src/ArduinoOcpp/MessagesV16/MeterValues.h | 4 ++++ .../MessagesV16/StartTransaction.cpp | 11 ++++++++++- .../MessagesV16/StartTransaction.h | 1 + .../MessagesV16/StopTransaction.cpp | 11 ++++++++++- src/ArduinoOcpp/MessagesV16/StopTransaction.h | 1 + .../ChargePointStatus/ConnectorStatus.cpp | 5 ++--- src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp | 5 ++--- 10 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp b/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp index 96399201..686dc864 100644 --- a/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp +++ b/src/ArduinoOcpp/MessagesV16/GetCompositeSchedule.cpp @@ -52,7 +52,10 @@ void GetCompositeSchedule::processReq(JsonObject payload) { std::unique_ptr GetCompositeSchedule::createConf(){ if (!ocppModel || !ocppModel->getSmartChargingService()) { - return nullptr; + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); + JsonObject payload = doc->to(); + payload["status"] = "Rejected"; + return doc; } auto scService = ocppModel->getSmartChargingService(); diff --git a/src/ArduinoOcpp/MessagesV16/GetDiagnostics.cpp b/src/ArduinoOcpp/MessagesV16/GetDiagnostics.cpp index 18289acf..6dd5fd72 100644 --- a/src/ArduinoOcpp/MessagesV16/GetDiagnostics.cpp +++ b/src/ArduinoOcpp/MessagesV16/GetDiagnostics.cpp @@ -57,7 +57,7 @@ std::unique_ptr GetDiagnostics::createConf(){ fileName = ocppModel->getDiagnosticsService()->requestDiagnosticsUpload(location, retries, retryInterval, startTime, stopTime); } else { AO_DBG_WARN("DiagnosticsService has not been initialized before! Please have a look at ArduinoOcpp.cpp for an example. Abort"); - return nullptr; + return createEmptyDocument(); } if (fileName.empty()) { diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp index cb7b01cb..b9edaf99 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.cpp +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.cpp @@ -10,6 +10,8 @@ using ArduinoOcpp::Ocpp16::MeterValues; +#define ENERGY_METER_TIMEOUT_MS 30 * 1000 //after waiting for 30s, send MeterValues without missing readings + //can only be used for echo server debugging MeterValues::MeterValues() { @@ -28,6 +30,10 @@ const char* MeterValues::getOcppOperationType(){ return "MeterValues"; } +void MeterValues::initiate() { + emTimeout = ao_tick_ms(); +} + std::unique_ptr MeterValues::createReq() { size_t capacity = 0; @@ -35,11 +41,16 @@ std::unique_ptr MeterValues::createReq() { std::vector> entries; for (auto value = meterValue.begin(); value != meterValue.end(); value++) { auto entry = (*value)->toJson(); - if (!entry) { - return nullptr; + if (entry) { + capacity += entry->capacity(); + entries.push_back(std::move(entry)); + } else { + if (ao_tick_ms() - emTimeout < ENERGY_METER_TIMEOUT_MS) { + return nullptr; + } else { + AO_DBG_ERR("Energy meter timeout!"); + } } - capacity += entry->capacity(); - entries.push_back(std::move(entry)); } capacity += JSON_OBJECT_SIZE(3); diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.h b/src/ArduinoOcpp/MessagesV16/MeterValues.h index 898899ba..3b477d15 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.h +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.h @@ -20,6 +20,8 @@ class MeterValues : public OcppMessage { int connectorId = 0; + ulong emTimeout = 0; + public: MeterValues(std::vector>&& meterValue, int connectorId); @@ -29,6 +31,8 @@ class MeterValues : public OcppMessage { const char* getOcppOperationType(); + void initiate() override; + std::unique_ptr createReq(); void processConf(JsonObject payload); diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp index fa501eca..a52229e7 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp @@ -10,6 +10,8 @@ using ArduinoOcpp::Ocpp16::StartTransaction; +#define ENERGY_METER_TIMEOUT_MS 30 * 1000 //after waiting for 30s, send StartTx without start reading + StartTransaction::StartTransaction(int connectorId) : connectorId(connectorId) { } @@ -63,6 +65,8 @@ void StartTransaction::initiate() { transactionRev = connector->getTransactionWriteCount(); } + emTimeout = ao_tick_ms(); + AO_DBG_INFO("StartTransaction initiated"); } @@ -70,7 +74,9 @@ std::unique_ptr StartTransaction::createReq() { if (meterStart && !*meterStart) { //meterStart not ready yet - return nullptr; + if (ao_tick_ms() - emTimeout < ENERGY_METER_TIMEOUT_MS) { + return nullptr; + } } auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(5) + (JSONDATE_LENGTH + 1) + (IDTAG_LEN_MAX + 1))); @@ -79,6 +85,9 @@ std::unique_ptr StartTransaction::createReq() { payload["connectorId"] = connectorId; if (meterStart && *meterStart) { payload["meterStart"] = meterStart->toInteger(); + } else { + AO_DBG_ERR("Energy meter timeout"); + payload["meterStart"] = -1; } if (otimestamp > MIN_TIME) { diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.h b/src/ArduinoOcpp/MessagesV16/StartTransaction.h index 153da753..c06b8715 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.h @@ -20,6 +20,7 @@ class StartTransaction : public OcppMessage { OcppTimestamp otimestamp; char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; uint16_t transactionRev = 0; + ulong emTimeout = 0; public: StartTransaction(int connectorId); diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp index 69a85f27..d110e7e4 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp @@ -11,6 +11,8 @@ using ArduinoOcpp::Ocpp16::StopTransaction; +#define ENERGY_METER_TIMEOUT_MS 60 * 1000 //after waiting for 60s, send StopTx without start reading + StopTransaction::StopTransaction(int connectorId, const char *reason) : connectorId(connectorId) { if (reason) { snprintf(this->reason, REASON_LEN_MAX, "%s", reason); @@ -44,6 +46,8 @@ void StopTransaction::initiate() { } } + emTimeout = ao_tick_ms(); + AO_DBG_INFO("StopTransaction initiated!"); } @@ -51,7 +55,9 @@ std::unique_ptr StopTransaction::createReq() { if (meterStop && !*meterStop) { //meterStop not ready yet - return nullptr; + if (ao_tick_ms() - emTimeout < ENERGY_METER_TIMEOUT_MS) { + return nullptr; + } } std::vector> txDataJson; @@ -79,6 +85,9 @@ std::unique_ptr StopTransaction::createReq() { if (meterStop && *meterStop) { payload["meterStop"] = meterStop->toInteger(); + } else { + AO_DBG_ERR("Energy meter timeout"); + payload["meterStart"] = -1; } if (otimestamp > MIN_TIME) { diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.h b/src/ArduinoOcpp/MessagesV16/StopTransaction.h index eaaacde8..5e7e2918 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.h @@ -23,6 +23,7 @@ class StopTransaction : public OcppMessage { OcppTimestamp otimestamp; char reason [REASON_LEN_MAX] {'\0'}; std::vector> transactionData; + ulong emTimeout = 0; public: StopTransaction(int connectorId, const char *reason = nullptr); diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index ac671131..50df0d9a 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -183,9 +183,8 @@ OcppMessage *ConnectorStatus::loop() { } if (txTrigger == TxCondition::Active) { - for (auto trigger = txTriggerConditions.begin(); trigger != txTriggerConditions.end(); trigger++) { - auto result = trigger->operator()(); - if (result == TxCondition::Active) { + for (auto trigger : txTriggerConditions) { + if (trigger() == TxCondition::Active) { txEnable = TxEnableState::Pending; } else { txTrigger = TxCondition::Inactive; diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp index 27fe8904..658547fb 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp @@ -29,10 +29,9 @@ std::unique_ptr MeterValue::toJson() { auto jsonPayload = result->to(); char timestampStr [JSONDATE_LENGTH + 1] = {'\0'}; - if (!timestamp.toJsonString(timestampStr, JSONDATE_LENGTH + 1)) { - return nullptr; + if (timestamp.toJsonString(timestampStr, JSONDATE_LENGTH + 1)) { + jsonPayload["timestamp"] = timestampStr; } - jsonPayload["timestamp"] = timestampStr; auto jsonMeterValue = jsonPayload.createNestedArray("sampledValue"); for (auto entry = entries.begin(); entry != entries.end(); entry++) { jsonMeterValue.add(**entry); From f647e2fa5ff18a68b8e651a4ba11bfd88c985c03 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:30:02 +0200 Subject: [PATCH 047/549] update StartTx trigger --- src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index 67f44dff..ac671131 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -61,7 +61,9 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) * - instruct the OCMF meter to begin a transaction (if OCMF meter handler is set) */ txTriggerConditions.push_back([this] () -> TxCondition { - return getSessionIdTag() == nullptr ? TxCondition::Inactive : TxCondition::Active; + if (!session) + return TxCondition::Inactive; + return getSessionIdTag() ? TxCondition::Active : TxCondition::Inactive; }); txEnableSequence.push_back([this] (TxCondition cond) -> TxEnableState { if (onOcmfMeterPollTx) { From fbeed4fa0865022f22e11e29bd7533097c7ec770 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Fri, 29 Jul 2022 14:50:03 +0200 Subject: [PATCH 048/549] encapsulate JSON file read / write --- .../Core/ConfigurationContainerFlash.cpp | 1 + src/ArduinoOcpp/Core/FilesystemAdapter.cpp | 12 +- src/ArduinoOcpp/Core/FilesystemAdapter.h | 27 +--- src/ArduinoOcpp/Core/FilesystemUtils.cpp | 121 ++++++++++++++++++ src/ArduinoOcpp/Core/FilesystemUtils.h | 46 +++++++ 5 files changed, 181 insertions(+), 26 deletions(-) create mode 100644 src/ArduinoOcpp/Core/FilesystemUtils.cpp create mode 100644 src/ArduinoOcpp/Core/FilesystemUtils.h diff --git a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp index 0eea713d..46b4f2f2 100644 --- a/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp +++ b/src/ArduinoOcpp/Core/ConfigurationContainerFlash.cpp @@ -3,6 +3,7 @@ // MIT License #include +#include #include #include diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp index 1d3d720e..c43c89c1 100644 --- a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp @@ -135,6 +135,7 @@ class ArduinoFilesystemAdapter : public FilesystemAdapter { std::unique_ptr open(const char *fn, const char *mode) override { File file = USE_FS.open(fn, mode); if (file && !file.isDirectory()) { + AO_DBG_DEBUG("File open successful: %s", fn); return std::unique_ptr(new ArduinoFileAdapter(std::move(file))); } else { return nullptr; @@ -145,7 +146,13 @@ class ArduinoFilesystemAdapter : public FilesystemAdapter { }; }; -std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { +std::weak_ptr filesystemCache; + +std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt config) { + + if (auto cached = filesystemCache.lock()) { + return cached; + } if (!config.accessAllowed()) { AO_DBG_DEBUG("Access to Arduino FS not allowed by config"); @@ -153,7 +160,8 @@ std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt co } auto fs_concrete = new ArduinoFilesystemAdapter(config); - auto fs = std::unique_ptr(fs_concrete); + auto fs = std::shared_ptr(fs_concrete); + filesystemCache = fs; if (*fs_concrete) { return fs; diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.h b/src/ArduinoOcpp/Core/FilesystemAdapter.h index 93c6af27..ec7e6e34 100644 --- a/src/ArduinoOcpp/Core/FilesystemAdapter.h +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.h @@ -9,6 +9,8 @@ #define AO_FILENAME_PREFIX "" #endif +#define MAX_PATH_SIZE 30 + #define ARDUINO_LITTLEFS 1 #define ARDUINO_SPIFFS 2 #define ESPIDF_SPIFFS 3 @@ -28,29 +30,6 @@ class FileAdapter { virtual int read() = 0; }; -class ArduinoJsonFileAdapter { -private: - FileAdapter *file; -public: - ArduinoJsonFileAdapter(FileAdapter *file) : file(file) { } - - size_t readBytes(char *buf, size_t len) { - return file->read(buf, len); - } - - int read() { - return file->read(); - } - - size_t write(const uint8_t *buf, size_t len) { - return file->write((const char*) buf, len); - } - - size_t write(uint8_t c) { - return file->write((const char*) &c, 1); - } -}; - class FilesystemAdapter { public: virtual ~FilesystemAdapter() = default; @@ -92,7 +71,7 @@ class FilesystemAdapter { namespace ArduinoOcpp { namespace EspWiFi { -std::unique_ptr makeDefaultFilesystemAdapter(FilesystemOpt config); +std::shared_ptr makeDefaultFilesystemAdapter(FilesystemOpt config); } //end namespace EspWiFi } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Core/FilesystemUtils.cpp b/src/ArduinoOcpp/Core/FilesystemUtils.cpp new file mode 100644 index 00000000..390b6d6a --- /dev/null +++ b/src/ArduinoOcpp/Core/FilesystemUtils.cpp @@ -0,0 +1,121 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include +#include //FilesystemOpt +#include + +#define MAX_JSON_CAPACITY 4096 + +using namespace ArduinoOcpp; + +std::unique_ptr FilesystemUtils::loadJson(std::shared_ptr filesystem, const char *fn) { + if (!filesystem || !fn || *fn == '\0') { + AO_DBG_ERR("Format error"); + return nullptr; + } + + if (strnlen(fn, MAX_PATH_SIZE) >= MAX_PATH_SIZE) { + AO_DBG_ERR("Fn too long: %.*s", MAX_PATH_SIZE, fn); + return nullptr; + } + + size_t fsize = 0; + if (filesystem->stat(fn, &fsize) != 0) { + AO_DBG_DEBUG("File does not exist: %s", fn); + return nullptr; + } + + if (fsize < 2) { + AO_DBG_ERR("File too small for JSON, collect %s", fn); + filesystem->remove(fn); + return nullptr; + } + + auto file = filesystem->open(fn, "r"); + if (!file) { + AO_DBG_ERR("Could not open file %s", fn); + return nullptr; + } + + size_t capacity = (3 * fsize) / 2; + if (capacity < 32) { + capacity = 32; + } + if (capacity > MAX_JSON_CAPACITY) { + capacity = MAX_JSON_CAPACITY; + } + + auto doc = std::unique_ptr(nullptr); + DeserializationError err = DeserializationError::NoMemory; + ArduinoJsonFileAdapter fileReader {file.get()}; + + while (err == DeserializationError::NoMemory && capacity <= MAX_JSON_CAPACITY) { + + doc.reset(new DynamicJsonDocument(capacity)); + err = deserializeJson(*doc, fileReader); + + capacity *= 3; + capacity /= 2; + + file->seek(0); //rewind file to beginning + } + + if (err) { + AO_DBG_ERR("Error deserializing file %s: %s", fn, err.c_str()); + //skip this file + return nullptr; + } + + AO_DBG_DEBUG("Loaded JSON file: %s", fn); + serializeJson(*doc, Serial); + Serial.println(); + return doc; +} + +bool FilesystemUtils::storeJson(std::shared_ptr filesystem, const char *fn, const DynamicJsonDocument& doc) { + if (!filesystem || !fn || *fn == '\0') { + AO_DBG_ERR("Format error"); + return false; + } + + if (strnlen(fn, MAX_PATH_SIZE) >= MAX_PATH_SIZE) { + AO_DBG_ERR("Fn too long: %.*s", MAX_PATH_SIZE, fn); + return false; + } + + if (doc.isNull() || doc.overflowed()) { + AO_DBG_ERR("Invalid JSON %s", fn); + return false; + } + + size_t file_size = 0; + if (filesystem->stat(fn, &file_size) == 0) { + filesystem->remove(fn); + } + + auto file = filesystem->open(fn, "w"); + if (!file) { + AO_DBG_ERR("Could not open file %s", fn); + return false; + } + + ArduinoJsonFileAdapter fileWriter {file.get()}; + + size_t written = serializeJson(doc, fileWriter); + written = serializeJson(doc, Serial); + + if (written < 2) { + AO_DBG_ERR("Error writing file %s", fn); + if (filesystem->stat(fn, &file_size) == 0) { + AO_DBG_DEBUG("Collect invalid file %s", fn); + filesystem->remove(fn); + } + return false; + } + + AO_DBG_DEBUG("Wrote JSON file: %s", fn); + return true; +} diff --git a/src/ArduinoOcpp/Core/FilesystemUtils.h b/src/ArduinoOcpp/Core/FilesystemUtils.h new file mode 100644 index 00000000..13af63cf --- /dev/null +++ b/src/ArduinoOcpp/Core/FilesystemUtils.h @@ -0,0 +1,46 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef AO_FILESYSTEMUTILS_H +#define AO_FILESYSTEMUTILS_H + +#include +#include +#include + +namespace ArduinoOcpp { + +class ArduinoJsonFileAdapter { +private: + FileAdapter *file; +public: + ArduinoJsonFileAdapter(FileAdapter *file) : file(file) { } + + size_t readBytes(char *buf, size_t len) { + return file->read(buf, len); + } + + int read() { + return file->read(); + } + + size_t write(const uint8_t *buf, size_t len) { + return file->write((const char*) buf, len); + } + + size_t write(uint8_t c) { + return file->write((const char*) &c, 1); + } +}; + +namespace FilesystemUtils { + +std::unique_ptr loadJson(std::shared_ptr filesystem, const char *fn); +bool storeJson(std::shared_ptr filesystem, const char *fn, const DynamicJsonDocument& doc); + +} + +} + +#endif From a9b4f3d3384ed488d079db8ffbc3e7a97a37232a Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 3 Aug 2022 17:12:46 +0200 Subject: [PATCH 049/549] internet connection loss simulation --- src/ArduinoOcpp/Core/OcppSocket.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ArduinoOcpp/Core/OcppSocket.h b/src/ArduinoOcpp/Core/OcppSocket.h index f1fb6db7..c9db90e1 100644 --- a/src/ArduinoOcpp/Core/OcppSocket.h +++ b/src/ArduinoOcpp/Core/OcppSocket.h @@ -27,9 +27,14 @@ class OcppSocket { class OcppEchoSocket : public OcppSocket { private: ReceiveTXTcallback receiveTXT; + + bool connected = true; //for simulating connection losses public: void loop() override { } bool sendTXT(std::string &out) override { + if (!connected) { + return true; + } if (receiveTXT) { return receiveTXT(out.c_str(), out.length()); } else { @@ -39,6 +44,9 @@ class OcppEchoSocket : public OcppSocket { void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) override { this->receiveTXT = receiveTXT; } + + void setConnected(bool connected) {this->connected = connected;} + bool isConnected() {return connected;} }; } //end namespace ArduinoOcpp From 59c2912b9b0dcdab492eba5030acff6711de64ce Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 8 Aug 2022 10:56:11 +0200 Subject: [PATCH 050/549] MeterValue restore from flash --- src/ArduinoOcpp/MessagesV16/MeterValues.h | 4 ++- .../Metering/ConnectorMeterValuesRecorder.cpp | 8 +---- .../Metering/ConnectorMeterValuesRecorder.h | 5 --- src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp | 32 ++++++++++++++++++ src/ArduinoOcpp/Tasks/Metering/MeterValue.h | 2 ++ .../Tasks/Metering/SampledValue.cpp | 33 +++++++++++++++++-- src/ArduinoOcpp/Tasks/Metering/SampledValue.h | 15 +++++++-- 7 files changed, 82 insertions(+), 17 deletions(-) diff --git a/src/ArduinoOcpp/MessagesV16/MeterValues.h b/src/ArduinoOcpp/MessagesV16/MeterValues.h index 898899ba..99e2d077 100644 --- a/src/ArduinoOcpp/MessagesV16/MeterValues.h +++ b/src/ArduinoOcpp/MessagesV16/MeterValues.h @@ -7,11 +7,13 @@ #include #include -#include #include namespace ArduinoOcpp { + +class MeterValue; + namespace Ocpp16 { class MeterValues : public OcppMessage { diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp index a856e2ba..63ee89e1 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.cpp @@ -60,7 +60,7 @@ OcppMessage *ConnectorMeterValuesRecorder::loop() { if (*ClockAlignedDataInterval >= 1) { - if (alignedData.size() >= (size_t) *MeterValuesAlignedDataMaxLength) { + if (alignedData.size() >= (size_t) *MeterValuesAlignedDataMaxLength) { auto meterValues = new MeterValues(std::move(alignedData), connectorId); alignedData.clear(); return meterValues; @@ -173,12 +173,6 @@ OcppMessage *ConnectorMeterValuesRecorder::takeTriggeredMeterValues() { return nullptr; } - int txId_now = -1; - auto connector = context.getConnectorStatus(connectorId); - if (connector) { - txId_now = connector->getTransactionId(); - } - decltype(sampledData) mv_now; mv_now.push_back(std::move(sample)); diff --git a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h index 7377ffed..d2d2d151 100644 --- a/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h +++ b/src/ArduinoOcpp/Tasks/Metering/ConnectorMeterValuesRecorder.h @@ -5,10 +5,6 @@ #ifndef CONNECTOR_METER_VALUES_RECORDER #define CONNECTOR_METER_VALUES_RECORDER -//#define METER_VALUE_SAMPLE_INTERVAL 60 //in seconds - -//#define METER_VALUES_SAMPLED_DATA_MAX_LENGTH 4 //after 4 measurements, send the values to the CS - #include #include #include @@ -22,7 +18,6 @@ using PowerSampler = std::function; using EnergySampler = std::function; class OcppModel; -class OcppTimestamp; class OcppMessage; class ConnectorMeterValuesRecorder { diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp index 27fe8904..c8be2032 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.cpp @@ -102,3 +102,35 @@ std::unique_ptr MeterValueBuilder::takeSample(const OcppTimestamp& t return sample; } + +std::unique_ptr MeterValueBuilder::deserializeSample(const JsonObject mvJson) { + + OcppTimestamp timestamp; + bool ret = timestamp.setTime(mvJson["timestamp"] | "Invalid"); + if (!ret) { + AO_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 + const char *measurand = svJson["measurand"] | "Invalid"; + for (auto& sampler : samplers) { + if (!sampler->getMeasurand().compare(measurand)) { + //found correct sampler + auto dVal = sampler->deserializeValue(svJson); + if (dVal) { + sample->addSampledValue(std::move(dVal)); + } else { + AO_DBG_ERR("deserialization error"); + } + break; + } + } + } + + AO_DBG_VERBOSE("deserialized MV"); + return sample; +} diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h index 450f5d5b..c89565d3 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h @@ -40,6 +40,8 @@ class MeterValueBuilder { std::shared_ptr> samplers_select); std::unique_ptr takeSample(const OcppTimestamp& timestamp, const ReadingContext& context); + + std::unique_ptr deserializeSample(const JsonObject mvJson); }; } diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp index 4a570829..702de16e 100644 --- a/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.cpp @@ -10,7 +10,7 @@ using ArduinoOcpp::SampledValue; //helper function namespace ArduinoOcpp { namespace Ocpp16 { -const char *cstrFromReadingContext(ReadingContext context) { +const char *serializeReadingContext(ReadingContext context) { switch (context) { case (ReadingContext::InterruptionBegin): return "Interruption.Begin"; @@ -34,7 +34,36 @@ const char *cstrFromReadingContext(ReadingContext context) { return nullptr; } } +ReadingContext deserializeReadingContext(const char *context) { + if (!context) { + AO_DBG_ERR("Invalid argument"); + return ReadingContext::NOT_SET; + } + + if (!strcmp(context, "NOT_SET")) { + AO_DBG_DEBUG("Deserialize Null-ReadingContext"); + return ReadingContext::NOT_SET; + } else if (!strcmp(context, "Sample.Periodic")) { + return ReadingContext::SamplePeriodic; + } else if (!strcmp(context, "Sample.Clock")) { + return ReadingContext::SampleClock; + } else if (!strcmp(context, "Transaction.Begin")) { + return ReadingContext::TransactionBegin; + } else if (!strcmp(context, "Transaction.End")) { + return ReadingContext::TransactionEnd; + } else if (!strcmp(context, "Other")) { + return ReadingContext::Other; + } else if (!strcmp(context, "Interruption.Begin")) { + return ReadingContext::InterruptionBegin; + } else if (!strcmp(context, "Interruption.End")) { + return ReadingContext::InterruptionEnd; + } else if (!strcmp(context, "Trigger")) { + return ReadingContext::Trigger; + } + AO_DBG_ERR("ReadingContext not specified %.10s", context); + return ReadingContext::NOT_SET; +} }} //end namespaces std::unique_ptr SampledValue::toJson() { @@ -53,7 +82,7 @@ std::unique_ptr SampledValue::toJson() { auto result = std::unique_ptr(new DynamicJsonDocument(capacity + 100)); //TODO remove safety space auto payload = result->to(); payload["value"] = value; - auto context_cstr = Ocpp16::cstrFromReadingContext(context); + auto context_cstr = Ocpp16::serializeReadingContext(context); if (context_cstr) payload["context"] = context_cstr; if (!properties.getFormat().empty()) diff --git a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h index 7debf013..f5750f8c 100644 --- a/src/ArduinoOcpp/Tasks/Metering/SampledValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/SampledValue.h @@ -77,7 +77,8 @@ enum class ReadingContext { }; namespace Ocpp16 { -const char *cstrFromReadingContext(ReadingContext context); +const char *serializeReadingContext(ReadingContext context); +ReadingContext deserializeReadingContext(const char *serialized); } class SampledValue { @@ -119,6 +120,7 @@ class SampledValueSampler { 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 std::string& getMeasurand() {return properties.getMeasurand();}; }; @@ -129,7 +131,16 @@ class SampledValueSamplerConcrete : public SampledValueSampler { public: SampledValueSamplerConcrete(SampledValueProperties properties, std::function sampler) : SampledValueSampler(properties), sampler(sampler) { } std::unique_ptr takeValue(ReadingContext context) override { - return std::unique_ptr>(new SampledValueConcrete(properties, context, sampler(context))); + return std::unique_ptr>(new SampledValueConcrete( + properties, + context, + sampler(context))); + } + std::unique_ptr deserializeValue(JsonObject svJson) override { + return std::unique_ptr>(new SampledValueConcrete( + properties, + Ocpp16::deserializeReadingContext(svJson["context"] | "NOT_SET"), + DeSerializer::deserialize(svJson["value"] | ""))); } }; From 2225250c252300154370519afafd398167aab3d0 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 18 Aug 2022 15:37:02 +0200 Subject: [PATCH 051/549] atomic transaction operations --- src/ArduinoOcpp.cpp | 102 +++-- src/ArduinoOcpp.h | 8 +- src/ArduinoOcpp/Core/OcppMessage.h | 4 +- src/ArduinoOcpp/Core/OcppModel.cpp | 9 + src/ArduinoOcpp/Core/OcppModel.h | 5 + src/ArduinoOcpp/Core/OcppOperation.cpp | 15 + src/ArduinoOcpp/Core/OcppOperation.h | 2 + .../MessagesV16/StartTransaction.cpp | 121 +++--- .../MessagesV16/StartTransaction.h | 17 +- .../MessagesV16/StopTransaction.cpp | 93 +++-- src/ArduinoOcpp/MessagesV16/StopTransaction.h | 20 +- .../SimpleOcppOperationFactory.cpp | 4 +- .../ChargePointStatus/ConnectorStatus.cpp | 349 ++++++++---------- .../Tasks/ChargePointStatus/ConnectorStatus.h | 32 +- .../TransactionPrerequisites.h | 7 +- .../Transactions/OrderedOperationsQueue.cpp | 29 ++ .../Transactions/OrderedOperationsQueue.h | 28 ++ .../Tasks/Transactions/Transaction.cpp | 218 +++++++++++ .../Tasks/Transactions/Transaction.h | 196 ++++++++++ .../Tasks/Transactions/TransactionProcess.cpp | 113 ++++++ .../Tasks/Transactions/TransactionProcess.h | 46 +++ .../Transactions/TransactionSequence.cpp | 29 ++ .../Tasks/Transactions/TransactionSequence.h | 28 ++ .../Tasks/Transactions/TransactionService.cpp | 74 ++++ .../Tasks/Transactions/TransactionService.h | 44 +++ .../Tasks/Transactions/TransactionStore.cpp | 308 ++++++++++++++++ .../Tasks/Transactions/TransactionStore.h | 61 +++ 27 files changed, 1589 insertions(+), 373 deletions(-) create mode 100644 src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp create mode 100644 src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.h create mode 100644 src/ArduinoOcpp/Tasks/Transactions/Transaction.cpp create mode 100644 src/ArduinoOcpp/Tasks/Transactions/Transaction.h create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.cpp create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.h create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.cpp create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.h create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionService.cpp create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionService.h create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionStore.cpp create mode 100644 src/ArduinoOcpp/Tasks/Transactions/TransactionStore.h diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index a9495203..6b29a231 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ float voltage_eff {230.f}; #define OCPP_ID_OF_CONNECTOR 1 #define OCPP_ID_OF_CP 0 bool OCPP_booted = false; //if BootNotification succeeded +bool enteredLoop = false; //if loop() is called the first time } //end namespace ArduinoOcpp::Facade } //end namespace ArduinoOcpp @@ -94,6 +96,8 @@ void OCPP_initialize(OcppSocket& ocppSocket, float V_eff, ArduinoOcpp::Filesyste ocppEngine = new OcppEngine(ocppSocket, system_time); auto& model = ocppEngine->getOcppModel(); + model.setTransactionService(std::unique_ptr( + new TransactionService(*ocppEngine, OCPP_NUMCONNECTORS, filesystem))); model.setChargePointStatusService(std::unique_ptr( new ChargePointStatusService(*ocppEngine, OCPP_NUMCONNECTORS))); model.setHeartbeatService(std::unique_ptr( @@ -123,8 +127,7 @@ void OCPP_initialize(OcppSocket& ocppSocket, float V_eff, ArduinoOcpp::Filesyste } void OCPP_deinitialize() { - AO_DBG_DEBUG("Still experimental function. If you find problems, it would be great if you publish them on the GitHub page"); - + delete ocppEngine; ocppEngine = nullptr; @@ -141,15 +144,21 @@ void OCPP_deinitialize() { voltage_eff = 230.f; OCPP_booted = false; + enteredLoop = false; } void OCPP_loop() { if (!ocppEngine) { AO_DBG_WARN("Please call OCPP_initialize before"); - //delay(200); //Prevent this message from flooding the Serial monitor. return; } + if (!enteredLoop) { + enteredLoop = true; + configuration_save(); + ocppEngine->getOcppModel().getTransactionService()->initiateRestoredOperations(); + } + ocppEngine->loop(); auto& model = ocppEngine->getOcppModel(); @@ -168,7 +177,7 @@ void OCPP_loop() { void setPowerActiveImportSampler(std::function power) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } @@ -191,7 +200,7 @@ void setPowerActiveImportSampler(std::function power) { void setEnergyActiveImportSampler(std::function energy) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto& model = ocppEngine->getOcppModel(); @@ -213,7 +222,7 @@ void setEnergyActiveImportSampler(std::function energy) { void addMeterValueSampler(std::unique_ptr meterValueSampler) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto& model = ocppEngine->getOcppModel(); @@ -226,7 +235,7 @@ void addMeterValueSampler(std::unique_ptr meterValueSampler void addMeterValueSampler(std::function value, const char *measurand, const char *unit, const char *location, const char *phase) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } @@ -259,7 +268,7 @@ void addMeterValueSampler(std::function value, const char *measurand void setEvRequestsEnergySampler(std::function evRequestsEnergy) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); @@ -272,7 +281,7 @@ void setEvRequestsEnergySampler(std::function evRequestsEnergy) { void setConnectorEnergizedSampler(std::function connectorEnergized) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); @@ -285,7 +294,7 @@ void setConnectorEnergizedSampler(std::function connectorEnergized) { void setConnectorPluggedSampler(std::function connectorPlugged) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); @@ -298,7 +307,7 @@ void setConnectorPluggedSampler(std::function connectorPlugged) { void addConnectorErrorCodeSampler(std::function connectorErrorCode) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); @@ -311,7 +320,7 @@ void addConnectorErrorCodeSampler(std::function connectorErrorCo void setOnChargingRateLimitChange(std::function chargingRateChanged) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto& model = ocppEngine->getOcppModel(); @@ -324,7 +333,7 @@ void setOnChargingRateLimitChange(std::function chargingRateChanged void setOnUnlockConnector(std::function()> unlockConnector) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); @@ -335,9 +344,9 @@ void setOnUnlockConnector(std::function()> unlockConnector) { connector->setOnUnlockConnector(unlockConnector); } -void setConnectorLock(std::function lockConnector) { +void setConnectorLock(std::function lockConnector) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); @@ -348,9 +357,9 @@ void setConnectorLock(std::functionsetConnectorLock(lockConnector); } -void setTxBasedMeterUpdate(std::function updateTxState) { +void setTxBasedMeterUpdate(std::function updateTxState) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); @@ -387,7 +396,7 @@ void setOnResetReceiveReq(OnReceiveReqListener onReceiveReq) { void authorize(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { @@ -413,7 +422,7 @@ void authorize(const char *idTag, OnReceiveConfListener onConf, OnAbortListener void bootNotification(const char *chargePointModel, const char *chargePointVendor, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } @@ -427,7 +436,7 @@ void bootNotification(const char *chargePointModel, const char *chargePointVendo void bootNotification(std::unique_ptr payload, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto bootNotification = makeOcppOperation( @@ -447,17 +456,30 @@ void bootNotification(std::unique_ptr payload, OnReceiveCon ocppEngine->initiateOperation(std::move(bootNotification)); } -void startTransaction(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { +bool startTransaction(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); - return; + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before + return false; } if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { AO_DBG_ERR("idTag format violation. Expect c-style string with at most %u characters", IDTAG_LEN_MAX); - return; + return false; + } + auto transaction = ocppEngine->getOcppModel().getTransactionService()->getTransactionStore().getActiveTransaction(OCPP_ID_OF_CONNECTOR); + if (!transaction) { + AO_DBG_ERR("Transaction buffer full"); + return false; + } + + if (transaction->isRunning()) { + AO_DBG_ERR("Called StartTx while still in transaction. Please call StopTx"); + return false; } + + transaction->setIdTag(idTag); + auto startTransaction = makeOcppOperation( - new StartTransaction(OCPP_ID_OF_CONNECTOR, idTag)); + new StartTransaction(transaction)); if (onConf) startTransaction->setOnReceiveConfListener(onConf); if (onAbort) @@ -471,15 +493,31 @@ void startTransaction(const char *idTag, OnReceiveConfListener onConf, OnAbortLi else startTransaction->setTimeout(std::unique_ptr(new SuppressedTimeout())); ocppEngine->initiateOperation(std::move(startTransaction)); + + return true; } -void stopTransaction(OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { +bool stopTransaction(OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, std::unique_ptr timeout) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); - return; + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before + return false; } + + auto transaction = ocppEngine->getOcppModel().getTransactionService()->getTransactionStore().getActiveTransaction(OCPP_ID_OF_CONNECTOR); + if (!transaction || !transaction->isRunning()) { + AO_DBG_ERR("No running Tx to stop"); + return false; + } + + const char *idTag = transaction->getIdTag(); + if (idTag) { + transaction->setStopIdTag(idTag); + } + + transaction->setStopReason("Local"); + auto stopTransaction = makeOcppOperation( - new StopTransaction(OCPP_ID_OF_CONNECTOR)); + new StopTransaction(transaction)); if (onConf) stopTransaction->setOnReceiveConfListener(onConf); if (onAbort) @@ -493,6 +531,8 @@ void stopTransaction(OnReceiveConfListener onConf, OnAbortListener onAbort, OnTi else stopTransaction->setTimeout(std::unique_ptr(new SuppressedTimeout())); ocppEngine->initiateOperation(std::move(stopTransaction)); + + return true; } int getTransactionId() { @@ -539,7 +579,7 @@ bool isAvailable() { void beginSession(const char *idTag) { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) { @@ -556,7 +596,7 @@ void beginSession(const char *idTag) { void endSession() { if (!ocppEngine) { - AO_DBG_ERR("Please call OCPP_initialize before"); + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before return; } auto connector = ocppEngine->getOcppModel().getConnectorStatus(OCPP_ID_OF_CONNECTOR); diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index c02c19af..d8ffae91 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -87,7 +87,7 @@ void setOnUnlockConnector(std::function()> unlockC // - TxEnableState::Inactive if connector lock is released // - TxEnableState::Pending otherwise, e.g. if transitioning between the states //Called periodically -void setConnectorLock(std::function lockConnector); +void setConnectorLock(std::function lockConnector); //Set a Cb to update transaction-based energy measurements with the most recent transaction state. //This allows energy meters (e.g. based on OCMF) to take their measruements right before and after a transaction @@ -95,7 +95,7 @@ void setConnectorLock(std::function updateTxState); +void setTxBasedMeterUpdate(std::function updateTxState); /* * React on CS-initiated operations @@ -146,9 +146,9 @@ void bootNotification(const char *chargePointModel, const char *chargePointVendo //The OCPP operation will include the given payload without modifying it. The library will delete the payload object after successful transmission. void bootNotification(std::unique_ptr payload, OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); -void startTransaction(const char *idTag, OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); +bool startTransaction(const char *idTag, OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); -void stopTransaction(OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); +bool stopTransaction(OnReceiveConfListener onConf = nullptr, OnAbortListener onAbort = nullptr, OnTimeoutListener onTimeout = nullptr, OnReceiveErrorListener onError = nullptr, std::unique_ptr timeout = nullptr); /* * Access information about the internal state of the library diff --git a/src/ArduinoOcpp/Core/OcppMessage.h b/src/ArduinoOcpp/Core/OcppMessage.h index ab88d345..5b0cb3cd 100644 --- a/src/ArduinoOcpp/Core/OcppMessage.h +++ b/src/ArduinoOcpp/Core/OcppMessage.h @@ -26,6 +26,7 @@ namespace ArduinoOcpp { std::unique_ptr createEmptyDocument(); class OcppModel; +class TransactionRPC; class OcppMessage { private: @@ -75,7 +76,8 @@ class OcppMessage { virtual const char *getErrorCode() {return nullptr;} //nullptr means no error virtual const char *getErrorDescription() {return "";} virtual std::unique_ptr getErrorDetails() {return createEmptyDocument();} - + + virtual TransactionRPC *getTransactionSync() {return nullptr;} }; } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Core/OcppModel.cpp b/src/ArduinoOcpp/Core/OcppModel.cpp index ec6a78fc..13925ffb 100644 --- a/src/ArduinoOcpp/Core/OcppModel.cpp +++ b/src/ArduinoOcpp/Core/OcppModel.cpp @@ -3,6 +3,7 @@ // MIT License #include +#include #include #include #include @@ -41,6 +42,14 @@ void OcppModel::loop() { firmwareService->loop(); } +void OcppModel::setTransactionService(std::unique_ptr ts) { + transactionService = std::move(ts); +} + +TransactionService *OcppModel::getTransactionService() { + return transactionService.get(); +} + void OcppModel::setSmartChargingService(std::unique_ptr scs) { smartChargingService = std::move(scs); } diff --git a/src/ArduinoOcpp/Core/OcppModel.h b/src/ArduinoOcpp/Core/OcppModel.h index e0ae10b8..2b7a351a 100644 --- a/src/ArduinoOcpp/Core/OcppModel.h +++ b/src/ArduinoOcpp/Core/OcppModel.h @@ -11,6 +11,7 @@ namespace ArduinoOcpp { +class TransactionService; class SmartChargingService; class ChargePointStatusService; class ConnectorStatus; @@ -21,6 +22,7 @@ class HeartbeatService; class OcppModel { private: + std::unique_ptr transactionService; std::unique_ptr smartChargingService; std::unique_ptr chargePointStatusService; std::unique_ptr meteringService; @@ -37,6 +39,9 @@ class OcppModel { void loop(); + void setTransactionService(std::unique_ptr transactionService); + TransactionService *getTransactionService(); + void setSmartChargingService(std::unique_ptr scs); SmartChargingService* getSmartChargingService() const; diff --git a/src/ArduinoOcpp/Core/OcppOperation.cpp b/src/ArduinoOcpp/Core/OcppOperation.cpp index 723baeab..18183dcb 100644 --- a/src/ArduinoOcpp/Core/OcppOperation.cpp +++ b/src/ArduinoOcpp/Core/OcppOperation.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -118,6 +119,15 @@ bool OcppOperation::sendReq(OcppSocket& ocppSocket){ requestJson.add(ocppMessage->getOcppOperationType()); //Action requestJson.add(*requestPayload); //Payload + /* + * Transaction safety: before sending the message, store the assigned msgId so this action + * can be replayed when booted the next time + */ + auto txSync = ocppMessage->getTransactionSync(); + if (txSync) { + txSync->requestWithMsgId(unique_id_counter); + } + /* * Serialize and send. Destroy serialization and JSON object. * @@ -353,6 +363,11 @@ bool OcppOperation::isFullyConfigured(){ return ocppMessage != nullptr; } +void OcppOperation::rebaseMsgId(int msgIdCounter) { + unique_id_counter = msgIdCounter; + getMessageID(); //apply msgIdCounter to this operation +} + void OcppOperation::print_debug() { if (ocppMessage) { AO_CONSOLE_PRINTF("OcppOperation of type %s\n", ocppMessage->getOcppOperationType()); diff --git a/src/ArduinoOcpp/Core/OcppOperation.h b/src/ArduinoOcpp/Core/OcppOperation.h index 4ca7b5ad..a3c44de6 100644 --- a/src/ArduinoOcpp/Core/OcppOperation.h +++ b/src/ArduinoOcpp/Core/OcppOperation.h @@ -128,6 +128,8 @@ class OcppOperation { bool isFullyConfigured(); + void rebaseMsgId(int msgIdCounter); //workaround; remove when random UUID msg IDs are introduced + void print_debug(); }; diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp index fa501eca..9e9d91ca 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.cpp @@ -6,19 +6,16 @@ #include #include #include +#include +#include #include using ArduinoOcpp::Ocpp16::StartTransaction; +using ArduinoOcpp::TransactionRPC; -StartTransaction::StartTransaction(int connectorId) : connectorId(connectorId) { - -} -StartTransaction::StartTransaction(int connectorId, const char *idTag) : connectorId(connectorId) { - if (idTag && strnlen(idTag, IDTAG_LEN_MAX + 2) <= IDTAG_LEN_MAX) - snprintf(this->idTag, IDTAG_LEN_MAX + 1, "%s", idTag); - else - AO_DBG_ERR("Format violation"); +StartTransaction::StartTransaction(std::shared_ptr transaction) : transaction(transaction) { + } const char* StartTransaction::getOcppOperationType() { @@ -26,41 +23,28 @@ const char* StartTransaction::getOcppOperationType() { } void StartTransaction::initiate() { - if (ocppModel && ocppModel->getMeteringService()) { - auto meteringService = ocppModel->getMeteringService(); - meterStart = meteringService->readTxEnergyMeter(connectorId, ReadingContext::TransactionBegin); - } - - if (ocppModel) { - otimestamp = ocppModel->getOcppTime().getOcppTimestampNow(); - } else { - otimestamp = MIN_TIME; - } + if (ocppModel && transaction && !transaction->getStartRpcSync().isRequested()) { + //fill out tx data if not happened before - if (ocppModel && ocppModel->getConnectorStatus(connectorId)) { - auto connector = ocppModel->getConnectorStatus(connectorId); - - if (*idTag == '\0') { - const char *sessionIdTag = connector->getSessionIdTag(); - if (sessionIdTag) { - snprintf(idTag, IDTAG_LEN_MAX + 1, "%s", sessionIdTag); + auto meteringService = ocppModel->getMeteringService(); + if (transaction->getMeterStart() < 0 && meteringService) { + auto meterStart = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext::TransactionBegin); + if (meterStart && *meterStart) { + transaction->setMeterStart(meterStart->toInteger()); } else { - AO_DBG_WARN("Try to start transaction without providing idTag. Initialize session with default idTag"); - connector->beginSession(nullptr); - sessionIdTag = connector->getSessionIdTag(); //returns default idTag now - if (sessionIdTag) - snprintf(idTag, IDTAG_LEN_MAX + 1, "%s", sessionIdTag); + AO_DBG_ERR("MeterStart undefined"); } - } else { - //idTag has been overriden - connector->beginSession(idTag); } - if (connector->getTransactionId() >= 0) { - AO_DBG_WARN("Started transaction while OCPP already presumes a running transaction"); + if (transaction->getStartTimestamp() <= MIN_TIME) { + transaction->setStartTimestamp(ocppModel->getOcppTime().getOcppTimestampNow()); } - connector->setTransactionId(0); //pending - transactionRev = connector->getTransactionWriteCount(); + + auto seqNr = ocppModel->getTransactionService()->getTransactionSequence().reserveSeqNr(); + AO_DBG_DEBUG("Reserved seqNr inside StartTx: %u", seqNr); + transaction->getStartRpcSync().setRequested(seqNr); + + transaction->commit(); } AO_DBG_INFO("StartTransaction initiated"); @@ -68,59 +52,52 @@ void StartTransaction::initiate() { std::unique_ptr StartTransaction::createReq() { - if (meterStart && !*meterStart) { - //meterStart not ready yet - return nullptr; - } - - auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(5) + (JSONDATE_LENGTH + 1) + (IDTAG_LEN_MAX + 1))); + auto doc = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(5) + + (IDTAG_LEN_MAX + 1) + + (JSONDATE_LENGTH + 1))); + JsonObject payload = doc->to(); - payload["connectorId"] = connectorId; - if (meterStart && *meterStart) { - payload["meterStart"] = meterStart->toInteger(); + payload["connectorId"] = transaction->getConnectorId(); + + if (transaction->getIdTag() && *transaction->getIdTag()) { + payload["idTag"] = (char*) transaction->getIdTag(); } - if (otimestamp > MIN_TIME) { + if (transaction->isMeterStartDefined()) { + payload["meterStart"] = transaction->getMeterStart(); + } + + if (transaction->getStartTimestamp() > MIN_TIME) { char timestamp[JSONDATE_LENGTH + 1] = {'\0'}; - otimestamp.toJsonString(timestamp, JSONDATE_LENGTH + 1); + transaction->getStartTimestamp().toJsonString(timestamp, JSONDATE_LENGTH + 1); payload["timestamp"] = timestamp; } - payload["idTag"] = idTag; - return doc; } void StartTransaction::processConf(JsonObject payload) { const char* idTagInfoStatus = payload["idTagInfo"]["status"] | "not specified"; - int transactionId = payload["transactionId"] | -1; - - ConnectorStatus *connector = nullptr; - if (ocppModel) - connector = ocppModel->getConnectorStatus(connectorId); - - if (connector) { - if (transactionRev == connector->getTransactionWriteCount()) { - - if (!strcmp(idTagInfoStatus, "Accepted")) { - AO_DBG_INFO("Request has been accepted"); - } else { - AO_DBG_INFO("Request has been denied. Reason: %s", idTagInfoStatus); - AO_DBG_DEBUG("Set txId despite rejection"); - connector->setIdTagInvalidated(); - } - - connector->setTransactionId(transactionId); - } - connector->setTransactionIdSync(transactionId); - - AO_DBG_DEBUG("Local txId = %i, remote txId = %i", connector->getTransactionId(), connector->getTransactionIdSync()); + if (!strcmp(idTagInfoStatus, "Accepted")) { + AO_DBG_INFO("Request has been accepted"); + } else { + AO_DBG_INFO("Request has been denied. Reason: %s", idTagInfoStatus); + transaction->setIdTagDeauthorized(); } + int transactionId = payload["transactionId"] | -1; + transaction->setTransactionId(transactionId); + + transaction->getStartRpcSync().confirm(); + transaction->commit(); } +TransactionRPC *StartTransaction::getTransactionSync() { + return transaction ? &transaction->getStartRpcSync() : nullptr; +} void StartTransaction::processReq(JsonObject payload) { diff --git a/src/ArduinoOcpp/MessagesV16/StartTransaction.h b/src/ArduinoOcpp/MessagesV16/StartTransaction.h index 153da753..1445148f 100644 --- a/src/ArduinoOcpp/MessagesV16/StartTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StartTransaction.h @@ -11,19 +11,20 @@ #include namespace ArduinoOcpp { + +class Transaction; +class TransactionRPC; + namespace Ocpp16 { class StartTransaction : public OcppMessage { private: - int connectorId = 1; - std::unique_ptr meterStart {nullptr}; - OcppTimestamp otimestamp; - char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; - uint16_t transactionRev = 0; + std::shared_ptr transaction; public: - StartTransaction(int connectorId); - StartTransaction(int connectorId, const char *idTag); + StartTransaction(std::shared_ptr transaction); + + StartTransaction() = default; //for debugging only. Make this for the server pendant const char* getOcppOperationType(); @@ -33,6 +34,8 @@ class StartTransaction : public OcppMessage { void processConf(JsonObject payload); + TransactionRPC *getTransactionSync() override; + void processReq(JsonObject payload); std::unique_ptr createConf(); diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp index 69a85f27..ddf5b566 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.cpp @@ -7,53 +7,59 @@ #include #include #include +#include +#include #include using ArduinoOcpp::Ocpp16::StopTransaction; +using ArduinoOcpp::TransactionRPC; + +StopTransaction::StopTransaction(std::shared_ptr transaction) + : transaction(transaction) { + +} + +StopTransaction::StopTransaction(std::shared_ptr transaction, std::vector> transactionData) + : transaction(transaction), transactionData(std::move(transactionData)) { -StopTransaction::StopTransaction(int connectorId, const char *reason) : connectorId(connectorId) { - if (reason) { - snprintf(this->reason, REASON_LEN_MAX, "%s", reason); - } } +StopTransaction::StopTransaction() { } + const char* StopTransaction::getOcppOperationType(){ return "StopTransaction"; } void StopTransaction::initiate() { - if (ocppModel && ocppModel->getMeteringService()) { - auto meteringService = ocppModel->getMeteringService(); - meterStop = meteringService->readTxEnergyMeter(connectorId, ReadingContext::TransactionEnd); - transactionData = meteringService->createStopTxMeterData(connectorId); - } + if (ocppModel && transaction && !transaction->getStopRpcSync().isRequested()) { + //fill out tx data if not happened before - if (ocppModel) { - otimestamp = ocppModel->getOcppTime().getOcppTimestampNow(); - } else { - otimestamp = MIN_TIME; - } + auto meteringService = ocppModel->getMeteringService(); + if (transaction->getMeterStop() < 0 && meteringService) { + auto meterStop = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext::TransactionEnd); + if (meterStop && *meterStop) { + transaction->setMeterStop(meterStop->toInteger()); + } else { + AO_DBG_ERR("MeterStop undefined"); + } + } - if (ocppModel && ocppModel->getConnectorStatus(connectorId)){ - auto connector = ocppModel->getConnectorStatus(connectorId); - connector->setTransactionId(-1); //immediate end of transaction - if (connector->getSessionIdTag()) { - AO_DBG_DEBUG("Ending EV user session triggered by StopTransaction"); - connector->endSession(); + if (transaction->getStopTimestamp() <= MIN_TIME) { + transaction->setStopTimestamp(ocppModel->getOcppTime().getOcppTimestampNow()); } - } + auto seqNr = ocppModel->getTransactionService()->getTransactionSequence().reserveSeqNr(); + AO_DBG_DEBUG("Reserved seqNr inside StopTx: %u", seqNr); + transaction->getStopRpcSync().setRequested(seqNr); + + transaction->commit(); + } AO_DBG_INFO("StopTransaction initiated!"); } std::unique_ptr StopTransaction::createReq() { - if (meterStop && !*meterStop) { - //meterStop not ready yet - return nullptr; - } - std::vector> txDataJson; size_t txDataJson_size = 0; for (auto mv = transactionData.begin(); mv != transactionData.end(); mv++) { @@ -72,28 +78,30 @@ std::unique_ptr StopTransaction::createReq() { auto doc = std::unique_ptr(new DynamicJsonDocument( 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())); JsonObject payload = doc->to(); - if (meterStop && *meterStop) { - payload["meterStop"] = meterStop->toInteger(); + if (transaction->getStopIdTag() && *transaction->getStopIdTag()) { + payload["idTag"] = (char*) transaction->getStopIdTag(); } - if (otimestamp > MIN_TIME) { - char timestamp[JSONDATE_LENGTH + 1] = {'\0'}; - otimestamp.toJsonString(timestamp, JSONDATE_LENGTH + 1); - payload["timestamp"] = timestamp; + if (transaction->isMeterStopDefined()) { + payload["meterStop"] = transaction->getMeterStop(); } - - if (ocppModel && ocppModel->getConnectorStatus(connectorId)){ - auto connector = ocppModel->getConnectorStatus(connectorId); - payload["transactionId"] = connector->getTransactionIdSync(); + + if (transaction->getStopTimestamp() > MIN_TIME) { + char timestamp [JSONDATE_LENGTH + 1] = {'\0'}; + transaction->getStopTimestamp().toJsonString(timestamp, JSONDATE_LENGTH + 1); + payload["timestamp"] = timestamp; } - if (reason[0] != '\0') { - payload["reason"] = reason; + payload["transactionId"] = transaction->getTransactionId(); + + if (transaction->getStopReason() && *transaction->getStopReason()) { + payload["reason"] = (char*) transaction->getStopReason(); } if (!transactionData.empty()) { @@ -105,14 +113,17 @@ std::unique_ptr StopTransaction::createReq() { void StopTransaction::processConf(JsonObject payload) { - if (ocppModel && ocppModel->getConnectorStatus(connectorId)){ - auto connector = ocppModel->getConnectorStatus(connectorId); - connector->setTransactionIdSync(-1); + if (transaction) { + transaction->getStopRpcSync().confirm(); + transaction->commit(); } AO_DBG_INFO("Request has been accepted!"); } +TransactionRPC *StopTransaction::getTransactionSync() { + return transaction ? &transaction->getStopRpcSync() : nullptr; +} void StopTransaction::processReq(JsonObject payload) { /** diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.h b/src/ArduinoOcpp/MessagesV16/StopTransaction.h index eaaacde8..da40c680 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.h @@ -14,18 +14,24 @@ namespace ArduinoOcpp { class SampledValue; class MeterValue; +class Transaction; +class TransactionRPC; + namespace Ocpp16 { class StopTransaction : public OcppMessage { private: - int connectorId = 1; - std::unique_ptr meterStop {nullptr}; - OcppTimestamp otimestamp; - char reason [REASON_LEN_MAX] {'\0'}; + std::shared_ptr transaction; std::vector> transactionData; public: - StopTransaction(int connectorId, const char *reason = nullptr); + //StopTransaction(int connectorId, const char *reason = nullptr); + + StopTransaction(std::shared_ptr transaction); + + StopTransaction(std::shared_ptr transaction, std::vector> transactionData); + + StopTransaction(); //for debugging only. Make this for the server pendant const char* getOcppOperationType(); @@ -35,6 +41,10 @@ class StopTransaction : public OcppMessage { void processConf(JsonObject payload); + bool processErr(const char *code, const char *description, JsonObject details) { return false;} + + TransactionRPC *getTransactionSync() override; + void processReq(JsonObject payload); std::unique_ptr createConf(); diff --git a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp index 274ac806..9a825b85 100644 --- a/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp +++ b/src/ArduinoOcpp/SimpleOcppOperationFactory.cpp @@ -228,10 +228,10 @@ std::unique_ptr makeOcppOperation(const char *messageType, int co } else if (!strcmp(messageType, "StatusNotification")) { msg = std::unique_ptr(new Ocpp16::StatusNotification(connectorId)); } else if (!strcmp(messageType, "StartTransaction")) { - msg = std::unique_ptr(new Ocpp16::StartTransaction(1)); //connectorId 1 + msg = std::unique_ptr(new Ocpp16::StartTransaction()); operation->setOnReceiveReqListener(onStartTransactionRequest); } else if (!strcmp(messageType, "StopTransaction")) { - msg = std::unique_ptr(new Ocpp16::StopTransaction(1)); //connectorId 1 + msg = std::unique_ptr(new Ocpp16::StopTransaction()); } else if (!strcmp(messageType, "TriggerMessage")) { msg = std::unique_ptr(new Ocpp16::TriggerMessage()); operation->setOnReceiveReqListener(onTriggerMessageRequest); diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp index ac671131..5ba7dd26 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -19,19 +20,11 @@ using namespace ArduinoOcpp; using namespace ArduinoOcpp::Ocpp16; ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) - : context(context), connectorId{connectorId} { + : context(context), connectorId{connectorId}, txProcess(connectorId) { - //Set default transaction ID in memory - char key [CONF_KEYLEN_MAX + 1] = {'\0'}; - - snprintf(key, CONF_KEYLEN_MAX + 1, "AO_SID_CONN_%d", connectorId); - sIdTag = declareConfiguration(key, "", CONFIGURATION_FN, false, false, true, false); - - snprintf(key, CONF_KEYLEN_MAX + 1, "AO_TXID_CONN_%d", connectorId); - transactionId = declareConfiguration(key, -1, CONFIGURATION_FN, false, false, true, false); - - snprintf(key, CONF_KEYLEN_MAX + 1, "AO_AVAIL_CONN_%d", connectorId); - availability = declareConfiguration(key, AVAILABILITY_OPERATIVE, CONFIGURATION_FN, false, false, true, false); + char availabilityKey [CONF_KEYLEN_MAX + 1] = {'\0'}; + snprintf(availabilityKey, CONF_KEYLEN_MAX + 1, "AO_AVAIL_CONN_%d", connectorId); + availability = declareConfiguration(availabilityKey, AVAILABILITY_OPERATIVE, CONFIGURATION_FN, false, false, true, false); connectionTimeOut = declareConfiguration("ConnectionTimeOut", 30, CONFIGURATION_FN, true, true, true, false); minimumStatusDuration = declareConfiguration("MinimumStatusDuration", 0, CONFIGURATION_FN, true, true, true, false); @@ -41,17 +34,9 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) localAuthorizeOffline = declareConfiguration("LocalAuthorizeOffline", "false", CONFIGURATION_FN, true, true, false, false); localPreAuthorize = declareConfiguration("LocalPreAuthorize", "false", CONFIGURATION_FN, true, true, false, false); - if (!sIdTag || !transactionId || !availability) { - AO_DBG_ERR("Cannot declare sessionIdTag, transactionId or availability"); + if (!availability) { + AO_DBG_ERR("Cannot declare availability"); } - if (sIdTag->getBuffsize() > 0 && (*sIdTag)[0] != '\0') { - snprintf(idTag, std::min((size_t) (IDTAG_LEN_MAX + 1), sIdTag->getBuffsize()), "%s", ((const char *) *sIdTag)); - session = true; - connectionTimeOutTimestamp = ao_tick_ms(); - connectionTimeOutListen = true; - AO_DBG_DEBUG("Load session idTag at initialization"); - } - transactionIdSync = *transactionId; /* * Initialize standard EVSE behavior. @@ -60,22 +45,26 @@ ConnectorStatus::ConnectorStatus(OcppModel& context, int connectorId) * - lock the connector (if handler is set) * - instruct the OCMF meter to begin a transaction (if OCMF meter handler is set) */ - txTriggerConditions.push_back([this] () -> TxCondition { - if (!session) - return TxCondition::Inactive; - return getSessionIdTag() ? TxCondition::Active : TxCondition::Inactive; + txProcess.addTrigger([this] () -> TxTrigger { + auto transaction = this->context.getTransactionService()->getTransactionStore().getActiveTransaction(this->connectorId); + + if (transaction && transaction->isInSession() && transaction->isActive()) { + return TxTrigger::Active; + } else { + return TxTrigger::Inactive; + } }); - txEnableSequence.push_back([this] (TxCondition cond) -> TxEnableState { + txProcess.addEnableStep([this] (TxTrigger cond) -> TxEnableState { if (onOcmfMeterPollTx) { return onOcmfMeterPollTx(cond); } - return cond == TxCondition::Active ? TxEnableState::Active : TxEnableState::Inactive; + return cond == TxTrigger::Active ? TxEnableState::Active : TxEnableState::Inactive; }); - txEnableSequence.push_back([this] (TxCondition cond) -> TxEnableState { + txProcess.addEnableStep([this] (TxTrigger cond) -> TxEnableState { if (onConnectorLockPollTx) { return onConnectorLockPollTx(cond); } - return cond == TxCondition::Active ? TxEnableState::Active : TxEnableState::Inactive; + return cond == TxTrigger::Active ? TxEnableState::Active : TxEnableState::Inactive; }); } @@ -95,37 +84,30 @@ OcppEvseState ConnectorStatus::inferenceStatus() { } } + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); + if (getErrorCode() != nullptr) { return OcppEvseState::Faulted; + } else if (!transaction) { //won't start new transactions if cached tx queue is full + return OcppEvseState::Unavailable; } else if (*availability == AVAILABILITY_INOPERATIVE) { return OcppEvseState::Unavailable; - } else if (rebooting && getTransactionId() < 0) { + } else if (rebooting && !transaction->isRunning()) { return OcppEvseState::Unavailable; - } else if (getTransactionId() == 0 && // i.e. Tx pending or EVSE offline. Check if offline Tx is OFF - !(*localAuthorizeOffline && strcmp(*localAuthorizeOffline, "false")) && - !(*localPreAuthorize && strcmp(*localPreAuthorize, "false"))) { - //All modes for offline Tx are off - return OcppEvseState::Preparing; //see other Preparing case - } else if (getTransactionId() >= 0) { + } else if (transaction->isRunning()) { //Transaction is currently running if ((connectorEnergizedSampler && !connectorEnergizedSampler()) || - idTagInvalidated) { + !transaction->isActive() || //will forbid charging + transaction->isIdTagDeauthorized()) { return OcppEvseState::SuspendedEVSE; } if (evRequestsEnergySampler && !evRequestsEnergySampler()) { return OcppEvseState::SuspendedEV; } return OcppEvseState::Charging; - } else if (txEnable == TxEnableState::Inactive) { + } else if (!txProcess.existsActiveTrigger() && txProcess.getState() == TxEnableState::Inactive) { return OcppEvseState::Available; - } else if (txEnable == TxEnableState::Pending || - txEnable == TxEnableState::Active) { //reached if Tx init is delayed - - if (txEnable == TxEnableState::Active) { // TODO verify if actually possible - AO_DBG_VERBOSE("Infered Active"); // - (void)0; // - } // - + } else { /* * Either in Preparing or Finishing state. Only way to know is from previous state */ @@ -139,7 +121,7 @@ OcppEvseState ConnectorStatus::inferenceStatus() { return OcppEvseState::Preparing; } } - + AO_DBG_VERBOSE("Cannot infere status"); return OcppEvseState::Faulted; //internal error } @@ -150,112 +132,99 @@ bool ConnectorStatus::ocppPermitsCharge() { return false; } - if (idTagInvalidated) { - return false; - } + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); - OcppEvseState state = inferenceStatus(); - - return state == OcppEvseState::Charging || - state == OcppEvseState::SuspendedEV || - state == OcppEvseState::SuspendedEVSE; + return transaction && + transaction->isRunning() && + transaction->isActive() && + !getErrorCode() && + !transaction->isIdTagDeauthorized(); } OcppMessage *ConnectorStatus::loop() { - if (getTransactionId() < 0 && *availability == AVAILABILITY_INOPERATIVE_SCHEDULED) { + + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); + + if ((!transaction || !transaction->isRunning()) && *availability == AVAILABILITY_INOPERATIVE_SCHEDULED) { *availability = AVAILABILITY_INOPERATIVE; saveState(); } - - if (connectorPluggedSampler) { - if (getTransactionId() >= 0 && !connectorPluggedSampler()) { - if (!*stopTransactionOnEVSideDisconnect || strcmp(*stopTransactionOnEVSideDisconnect, "false")) { - endSession("EVDisconnected"); + + if (transaction) { //begin exclusively transaction-related operations + + if (connectorPluggedSampler) { + if (transaction->isRunning() && transaction->isInSession() && !connectorPluggedSampler()) { + if (!*stopTransactionOnEVSideDisconnect || strcmp(*stopTransactionOnEVSideDisconnect, "false")) { + AO_DBG_DEBUG("Stop Tx due to EV disconnect"); + transaction->setStopReason("EVDisconnected"); + transaction->endSession(); + transaction->commit(); + } } } - } - auto txTrigger = txTriggerConditions.empty() ? TxCondition::Inactive : TxCondition::Active; - txEnable = TxEnableState::Inactive; - - if (*availability == AVAILABILITY_INOPERATIVE) { - txTrigger = TxCondition::Inactive; - } + if (transaction->isInSession() && + !transaction->getStartRpcSync().isRequested() && + transaction->getSessionTimestamp() > MIN_TIME && + connectionTimeOut && *connectionTimeOut > 0 && + context.getOcppTime().getOcppTimestampNow() - transaction->getSessionTimestamp() >= (otime_t) *connectionTimeOut) { + + AO_DBG_INFO("Session mngt: timeout"); + transaction->endSession(); + transaction->commit(); + } - if (txTrigger == TxCondition::Active) { - for (auto trigger = txTriggerConditions.begin(); trigger != txTriggerConditions.end(); trigger++) { - auto result = trigger->operator()(); - if (result == TxCondition::Active) { - txEnable = TxEnableState::Pending; - } else { - txTrigger = TxCondition::Inactive; + if (transaction->isInSession() && transaction->isIdTagDeauthorized()) { + if (!*stopTransactionOnInvalidId || strcmp(*stopTransactionOnInvalidId, "false")) { + AO_DBG_DEBUG("DeAuthorize session"); + transaction->setStopReason("DeAuthorized"); + transaction->endSession(); + transaction->commit(); } } - } - if (txTrigger == TxCondition::Active) { - txEnable = TxEnableState::Active; + txProcess.evaluateProcessSteps(transaction->getTxNr()); + auto txEnable = txProcess.getState(); - for (auto step = txEnableSequence.rbegin(); step != txEnableSequence.rend(); step++) { - auto result = step->operator()(TxCondition::Active); - if (result != TxEnableState::Active) { - txEnable = TxEnableState::Pending; - break; + /* + * Check conditions for start or stop transaction + */ + + if (txEnable == TxEnableState::Active) { + + if (transaction->isPreparing() && !getErrorCode()) { + //start Transaction + + AO_DBG_INFO("Session mngt: trigger StartTransaction"); + //return new StartTransaction(connectorId); + auto seqNr = context.getTransactionService()->getTransactionSequence().reserveSeqNr(); + AO_DBG_DEBUG("Reserved SeqNr %u", seqNr); + transaction->getStartRpcSync().setRequested(seqNr); + transaction->commit(); + return new StartTransaction(transaction); } - } - } else { - for (auto step = txEnableSequence.begin(); step != txEnableSequence.end(); step++) { - auto result = step->operator()(TxCondition::Inactive); - if (result != TxEnableState::Inactive) { - txEnable = TxEnableState::Pending; - break; + } else if (transaction->isRunning()) { + + if (transaction->isInSession()) { + AO_DBG_DEBUG("Tx process not active"); + transaction->endSession(); + transaction->commit(); } - } - } + if (txEnable == TxEnableState::Inactive) { + //stop transaction + auto seqNr = context.getTransactionService()->getTransactionSequence().reserveSeqNr(); + transaction->getStopRpcSync().setRequested(seqNr); + transaction->commit(); - /* - * Check conditions for start or stop transaction - */ - if (txEnable == TxEnableState::Active) { - //check if not in transaction yet - if (getTransactionId() < 0 && - !getErrorCode()) { - //start Transaction - - AO_DBG_DEBUG("Session mngt: txId=%i, connectorPlugged = %s, session=%d", - getTransactionId(), - connectorPluggedSampler ? (connectorPluggedSampler() ? "plugged" : "unplugged") : "undefined", - session); - AO_DBG_INFO("Session mngt: trigger StartTransaction"); - return new StartTransaction(connectorId); - } - } else { - //check if still in transaction - if (getTransactionId() >= 0) { - //stop transaction - - AO_DBG_DEBUG("Session mngt: txId=%i, connectorPlugged = %s, session=%d", - getTransactionId(), - connectorPluggedSampler ? (connectorPluggedSampler() ? "plugged" : "unplugged") : "undefined", - session); - AO_DBG_INFO("Session mngt: trigger StopTransaction"); - return new StopTransaction(connectorId, endReason[0] != '\0' ? endReason : nullptr); - } - } + AO_DBG_INFO("Session mngt: trigger StopTransaction"); - if (connectionTimeOutListen) { - if (getTransactionId() >= 0 || !session) { - AO_DBG_DEBUG("Session mngt: release connectionTimeOut"); - connectionTimeOutListen = false; - } else { - if (ao_tick_ms() - connectionTimeOutTimestamp >= ((ulong) *connectionTimeOut) * 1000UL) { - AO_DBG_INFO("Session mngt: timeout"); - endSession(); - connectionTimeOutListen = false; + AO_DBG_DEBUG("Reserved SeqNr %u", seqNr); + + return new StopTransaction(transaction); } } - } + } //end transaction-related operations auto inferedStatus = inferenceStatus(); @@ -289,82 +258,92 @@ const char *ConnectorStatus::getErrorCode() { } void ConnectorStatus::beginSession(const char *sessionIdTag) { - AO_DBG_DEBUG("Begin session with idTag %s, overwriting idTag %s", sessionIdTag != nullptr ? sessionIdTag : "", idTag); + + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); + + if (!transaction) { + AO_DBG_ERR("Could not allocate Tx"); + return; + } + + AO_DBG_DEBUG("Begin session with idTag %s, overwriting idTag %s", sessionIdTag != nullptr ? sessionIdTag : "", transaction->getIdTag()); if (!sessionIdTag || *sessionIdTag == '\0') { //input string is empty - snprintf(idTag, IDTAG_LEN_MAX + 1, "A0-00-00-00"); + transaction->setIdTag("A0-00-00-00"); } else { - snprintf(idTag, IDTAG_LEN_MAX + 1, "%s", sessionIdTag); + transaction->setIdTag(sessionIdTag); } - sIdTag->setValue(idTag, IDTAG_LEN_MAX + 1); - saveState(); - session = true; - idTagInvalidated = false; - memset(endReason, '\0', REASON_LEN_MAX + 1); + transaction->setSessionTimestamp(context.getOcppTime().getOcppTimestampNow()); - connectionTimeOutListen = true; - connectionTimeOutTimestamp = ao_tick_ms(); + transaction->commit(); } void ConnectorStatus::endSession(const char *reason) { - AO_DBG_DEBUG("End session with idTag %s for reason %s, %s previous reason", - idTag, reason ? reason : "undefined", - endReason[0] == '\0' ? "no" : "overruled by"); - if (session) { - memset(idTag, '\0', IDTAG_LEN_MAX + 1); - *sIdTag = ""; - saveState(); - } - session = false; - if (reason && endReason[0] == '\0') { - snprintf(endReason, REASON_LEN_MAX + 1, "%s", reason); + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); + + if (!transaction) { + AO_DBG_ERR("Could not allocate Tx"); + return; } - connectionTimeOutListen = false; -} + if (transaction->isInSession()) { + AO_DBG_DEBUG("End session with idTag %s for reason %s, %s previous reason", + transaction->getIdTag(), reason ? reason : "undefined", + transaction->getStopReason() == '\0' ? "no" : "overruled by"); -void ConnectorStatus::setIdTagInvalidated() { - if (session) { - idTagInvalidated = true; - if (!*stopTransactionOnInvalidId || strcmp(*stopTransactionOnInvalidId, "false")) { - endSession("DeAuthorized"); + if (reason) { + transaction->setStopReason(reason); } - } else { - AO_DBG_WARN("Cannot invalidate IdTag outside of session"); + transaction->endSession(); + transaction->commit(); } } const char *ConnectorStatus::getSessionIdTag() { - return session ? idTag : nullptr; -} -uint16_t ConnectorStatus::getSessionWriteCount() { - return sIdTag->getValueRevision(); -} + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); -int ConnectorStatus::getTransactionId() { - return *transactionId; -} + if (!transaction) { + return nullptr; + } -int ConnectorStatus::getTransactionIdSync() { - return transactionIdSync; + return transaction->isInSession() ? transaction->getIdTag() : nullptr; } -void ConnectorStatus::setTransactionIdSync(int id) { - transactionIdSync = id; +uint16_t ConnectorStatus::getSessionWriteCount() { + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); + + return transaction ? transaction->getTxNr() : 0; } -uint16_t ConnectorStatus::getTransactionWriteCount() { - return transactionId->getValueRevision(); +int ConnectorStatus::getTransactionId() { + auto transaction = context.getTransactionService()->getTransactionStore().getActiveTransaction(connectorId); + + if (!transaction) { + return -1; + } + + if (transaction->isRunning()) { + if (transaction->getStartRpcSync().isConfirmed()) { + return transaction->getTransactionId(); + } else { + return 0; + } + } else { + return -1; + } } -void ConnectorStatus::setTransactionId(int id) { - int prevTxId = *transactionId; - *transactionId = id; - if (id != 0 || prevTxId > 0) - saveState(); +int ConnectorStatus::getTransactionIdSync() { + auto transaction = context.getTransactionService()->getTransactionStore().getTransactionSync(connectorId); + + if (transaction) { + return transaction->getTransactionId(); + } else { + return -1; + } } int ConnectorStatus::getAvailability() { @@ -390,8 +369,8 @@ void ConnectorStatus::setRebooting(bool rebooting) { void ConnectorStatus::setConnectorPluggedSampler(std::function connectorPlugged) { this->connectorPluggedSampler = connectorPlugged; - txTriggerConditions.push_back([this] () -> TxCondition { - return connectorPluggedSampler() ? TxCondition::Active : TxCondition::Inactive; + txProcess.addTrigger([this] () -> TxTrigger { + return connectorPluggedSampler() ? TxTrigger::Active : TxTrigger::Inactive; }); } @@ -419,10 +398,10 @@ std::function()> ConnectorStatus::getOnUnlockConnector() { return this->onUnlockConnector; } -void ConnectorStatus::setConnectorLock(std::function onConnectorLockPollTx) { +void ConnectorStatus::setConnectorLock(std::function onConnectorLockPollTx) { this->onConnectorLockPollTx = onConnectorLockPollTx; } -void ConnectorStatus::setTxBasedMeterUpdate(std::function onOcmfMeterPollTx) { +void ConnectorStatus::setTxBasedMeterUpdate(std::function onOcmfMeterPollTx) { this->onOcmfMeterPollTx = onOcmfMeterPollTx; } diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h index ec5492ba..43ddc867 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/ConnectorStatus.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ namespace ArduinoOcpp { class OcppModel; class OcppMessage; +class Transaction; class ConnectorStatus { private: @@ -33,18 +35,6 @@ class ConnectorStatus { std::shared_ptr> availability; bool rebooting = false; //report connector inoperative and reject new charging sessions - bool session = false; - char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; - bool idTagInvalidated {false}; //if StartTransaction.conf() has status != "Accepted" - std::shared_ptr> sIdTag; - std::shared_ptr> transactionId; - int transactionIdSync = -1; - char endReason [REASON_LEN_MAX + 1] = {'\0'}; - - std::shared_ptr> connectionTimeOut; //in seconds - bool connectionTimeOutListen {false}; - ulong connectionTimeOutTimestamp {0}; //in milliseconds - std::function connectorPluggedSampler; std::function evRequestsEnergySampler; std::function connectorEnergizedSampler; @@ -58,13 +48,12 @@ class ConnectorStatus { std::function()> onUnlockConnector; - std::function onConnectorLockPollTx; - std::function onOcmfMeterPollTx; + std::function onConnectorLockPollTx; + std::function onOcmfMeterPollTx; - std::vector> txTriggerConditions; - std::vector> txEnableSequence; - TxEnableState txEnable {TxEnableState::Inactive}; // = Result of Trigger and Enable Sequence + TransactionProcess txProcess; + std::shared_ptr> connectionTimeOut; //in seconds std::shared_ptr> stopTransactionOnInvalidId; std::shared_ptr> stopTransactionOnEVSideDisconnect; std::shared_ptr> unlockConnectorOnEVSideDisconnect; @@ -85,14 +74,10 @@ class ConnectorStatus { */ void beginSession(const char *idTag); void endSession(const char *reason = nullptr); - void setIdTagInvalidated(); //if StartTransaction.conf() has status != "Accepted" const char *getSessionIdTag(); uint16_t getSessionWriteCount(); int getTransactionId(); int getTransactionIdSync(); - uint16_t getTransactionWriteCount(); - void setTransactionId(int id); - void setTransactionIdSync(int id); int getAvailability(); void setAvailability(bool available); @@ -104,7 +89,6 @@ class ConnectorStatus { void addConnectorErrorCodeSampler(std::function connectorErrorCode); void saveState(); - //void recoverState(); OcppMessage *loop(); @@ -115,8 +99,8 @@ class ConnectorStatus { void setOnUnlockConnector(std::function()> unlockConnector); std::function()> getOnUnlockConnector(); - void setConnectorLock(std::function lockConnector); - void setTxBasedMeterUpdate(std::function updateTxBasedMeter); + void setConnectorLock(std::function lockConnector); + void setTxBasedMeterUpdate(std::function updateTxBasedMeter); }; } //end namespace ArduinoOcpp diff --git a/src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h b/src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h index d73b3310..9a503718 100644 --- a/src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h +++ b/src/ArduinoOcpp/Tasks/ChargePointStatus/TransactionPrerequisites.h @@ -11,7 +11,12 @@ namespace ArduinoOcpp { * Type definitions for extending the transaction initiation process */ -enum class TxCondition { +enum class TxPrecondition { + Active, + Inactive +}; + +enum class TxTrigger { Active, Inactive }; diff --git a/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp b/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp new file mode 100644 index 00000000..e72d0c73 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp @@ -0,0 +1,29 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include +#include + +using namespace ArduinoOcpp; + +void OrderedOperationsQueue::addOcppOperation(uint seqNr, std::unique_ptr op) { + operations.push_back(std::pair>{seqNr, std::move(op)}); +} + +void OrderedOperationsQueue::sort(uint seqEnd) { + std::sort(operations.begin(), operations.end(), [seqEnd] (std::pair>& a, std::pair>& b) { + uint da = (seqEnd - a.first + MAX_TXEVENT_CNT) % MAX_TXEVENT_CNT; //distance between a to seqEnd + uint db = (seqEnd - b.first + MAX_TXEVENT_CNT) % MAX_TXEVENT_CNT; + return da > db; //sort descending by distance to seqEnd <=> sort ascending by seqNr + }); +} + +void OrderedOperationsQueue::moveTo(std::vector>& dst) { + for (auto el = operations.begin(); el != operations.end(); el++) { + dst.emplace_back(std::move(el->second)); + } + + operations.clear(); +} diff --git a/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.h b/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.h new file mode 100644 index 00000000..52a9563f --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.h @@ -0,0 +1,28 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef OPERATIONSQUEUE_H +#define OPERATIONSQUEUE_H + +#include +#include +#include + +namespace ArduinoOcpp { + +class OrderedOperationsQueue { +private: + std::vector>> operations; +public: + + void addOcppOperation(uint seqNr, std::unique_ptr op); + + void sort(uint seqEnd); //sort and take seqEnd as largest order number + + void moveTo(std::vector>& dst); //move contents of operations to dst +}; + +} + +#endif diff --git a/src/ArduinoOcpp/Tasks/Transactions/Transaction.cpp b/src/ArduinoOcpp/Tasks/Transactions/Transaction.cpp new file mode 100644 index 00000000..337a2d66 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/Transaction.cpp @@ -0,0 +1,218 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include +#include +#include + +#include + +using namespace ArduinoOcpp; + +void TransactionRPC::requestWithMsgId(int msgId) { + context.setInitiatedMsgId(seqNr, msgId); +} + +bool TransactionRPC::serializeSessionState(JsonObject rpc) { + rpc["requested"] = requested; + if (requested) { + rpc["seqNr"] = seqNr; + } + rpc["confirmed"] = confirmed; + return true; +} + +bool Transaction::serializeSessionState(DynamicJsonDocument& out) { + out = DynamicJsonDocument(1024); + JsonObject state = out.to(); + + JsonObject sessionState = state.createNestedObject("session"); + if (session.idTag[0] != '\0') { + sessionState["idTag"] = session.idTag; + } + if (session.timestamp > MIN_TIME) { + char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; + session.timestamp.toJsonString(timeStr, JSONDATE_LENGTH + 1); + sessionState["timestamp"] = timeStr; + } + if (session.txProfileId >= 0) { + sessionState["txProfileId"] = session.txProfileId; + } + if (!session.active) { + sessionState["active"] = session.active; + } + + JsonObject txStart = state.createNestedObject("start"); + + JsonObject txStartRPC = txStart.createNestedObject("rpc"); + if (!start.rpc.serializeSessionState(txStartRPC)) { + return false; + } + + JsonObject txStartClientSide = txStart.createNestedObject("client"); + + if (start.client.timestamp > MIN_TIME) { + char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; + start.client.timestamp.toJsonString(timeStr, JSONDATE_LENGTH + 1); + txStartClientSide["timestamp"] = timeStr; + } + + if (start.client.meter >= 0) { + txStartClientSide["meter"] = start.client.meter; + } + + + if (start.rpc.confirmed) { + JsonObject txStartServerSide = txStart.createNestedObject("server"); + txStartServerSide["transactionId"] = start.server.transactionId; + txStartServerSide["authorized"] = start.server.authorized; + } + + JsonObject txStop = state.createNestedObject("stop"); + + JsonObject txStopRPC = txStop.createNestedObject("rpc"); + if (!stop.rpc.serializeSessionState(txStopRPC)) { + return false; + } + + JsonObject txStopClientSide = txStop.createNestedObject("client"); + + if (stop.client.timestamp > MIN_TIME) { + char timeStr [JSONDATE_LENGTH + 1] = {'\0'}; + stop.client.timestamp.toJsonString(timeStr, JSONDATE_LENGTH + 1); + txStopClientSide["timestamp"] = timeStr; + } + + if (stop.client.meter >= 0) { + txStopClientSide["meter"] = stop.client.meter; + } + + if (stop.client.idTag[0] != '\0') { + txStopClientSide["idTag"] = stop.client.idTag; + } + + if (stop.client.reason[0] != '\0') { + txStopClientSide["reason"] = stop.client.reason; + } + + //if (stop.rpc.confirmed) { + // JsonObject txStopServerSide = txStop.createNestedObject("server"); + //} + + if (out.overflowed()) { + AO_DBG_ERR("JSON capacity exceeded"); + return false; + } + + return true; +} + +bool TransactionRPC::deserializeSessionState(JsonObject rpc) { + if (rpc["requested"] | false) { + requested = true; + seqNr = rpc["seqNr"] | 0; + } + if (rpc["confirmed"] | false) { + confirmed = true; + } + return true; +} + +bool Transaction::deserializeSessionState(JsonObject state) { + + JsonObject sessionState = state["session"]; + + if (sessionState.containsKey("idTag")) { + if (snprintf(session.idTag, sizeof(session.idTag), "%s", sessionState["idTag"] | "") < 0) { + AO_DBG_ERR("Read err"); + return false; + } + } + if (sessionState.containsKey("timestamp")) { + session.timestamp.setTime(sessionState["timestamp"] | "Invalid"); + } + if (sessionState.containsKey("txProfileId")) { + session.txProfileId = sessionState["txProfileId"] | -1; + } + if (sessionState.containsKey("active")) { + session.active = sessionState["active"] | true; + } + + JsonObject txStart = state["start"]; + + if (txStart.containsKey("rpc")) { + JsonObject txStartRPC = txStart["rpc"]; + AO_DBG_DEBUG("Deserialize RPC. Raw:") + serializeJson(txStartRPC, Serial); + Serial.println(); + if (!start.rpc.deserializeSessionState(txStartRPC)) { + return false; + } + } + + JsonObject txStartClientSide = txStart["client"]; + + if (txStartClientSide.containsKey("timestamp")) { + start.client.timestamp.setTime(txStartClientSide["timestamp"] | "Invalid"); + } + + if (txStartClientSide.containsKey("meter")) { + start.client.meter = txStartClientSide["meter"] | 0; + } + + if (start.rpc.confirmed) { + JsonObject txStartServerSide = txStart["server"]; + start.server.transactionId = txStartServerSide["transactionId"] | -1; + start.server.authorized = txStartServerSide["authorized"] | false; + } + + JsonObject txStop = state["stop"]; + + if (txStop.containsKey("rpc")) { + JsonObject txStopRPC = txStop["rpc"]; + if (!stop.rpc.deserializeSessionState(txStopRPC)) { + return false; + } + } + + JsonObject txStopClientSide = txStop["client"]; + + if (txStopClientSide.containsKey("timestamp")) { + stop.client.timestamp.setTime(txStopClientSide["timestamp"] | "Invalid"); + } + + if (txStopClientSide.containsKey("meter")) { + stop.client.meter = txStopClientSide["meter"] | 0; + } + + if (txStopClientSide.containsKey("idTag")) { + if (snprintf(stop.client.idTag, sizeof(stop.client.idTag), "%s", txStopClientSide["idTag"] | "") < 0) { + AO_DBG_ERR("Read err"); + return false; + } + } + + if (txStopClientSide.containsKey("reason")) { + if (snprintf(stop.client.reason, sizeof(stop.client.reason), "%s", txStopClientSide["reason"] | "") < 0) { + AO_DBG_ERR("Read err"); + return false; + } + } + + AO_DBG_DEBUG("DUMP TX"); + AO_DBG_DEBUG("Session | idTag %s", session.idTag); + AO_DBG_DEBUG("Start RPC | req: %i, seq: %u, conf: %i", start.rpc.requested, start.rpc.seqNr, start.rpc.confirmed); + AO_DBG_DEBUG("Stop RPC | req: %i, seq: %u, conf: %i", stop.rpc.requested, stop.rpc.seqNr, stop.rpc.confirmed); + + //if (stop.rpc.confirmed) { + // JsonObject txStopServerSide = txStop["server"]; + //} + + return true; +} + +bool Transaction::commit() { + return context.getTransactionStore().commit(this); +} diff --git a/src/ArduinoOcpp/Tasks/Transactions/Transaction.h b/src/ArduinoOcpp/Tasks/Transactions/Transaction.h new file mode 100644 index 00000000..f5336b1b --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/Transaction.h @@ -0,0 +1,196 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef TRANSACTION_H +#define TRANSACTION_H + +#include +#include +#include +#include + +namespace ArduinoOcpp { + +/* + * 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. + * + * "ClientTransaction" is short for the client-side data, and the same goes for "ServerTranaction". The rest + * of the terminology is documented in OCPP 1.6 Specification - Edition 2, sections 3.6, 4.8, 4.10 and 5.11. + */ + +class TransactionService; + +class TransactionRPC { +private: + friend class Transaction; + + TransactionService& context; + + uint seqNr = 0; + bool requested = false; + bool confirmed = false; + + bool serializeSessionState(JsonObject out); + bool deserializeSessionState(JsonObject in); +public: + TransactionRPC(TransactionService& context) : context(context) { } + + void requestWithMsgId(int msgId); + + void setRequested(uint seqNr) { + this->seqNr = seqNr; + this->requested = true; + } + bool isRequested() {return requested;} + uint getSeqNr() {return seqNr;} + void confirm() {confirmed = true;} + bool isConfirmed() {return confirmed;} + bool isCompleted() {return isRequested() && isConfirmed();} +}; + +class ClientTransactionStart { +private: + friend class Transaction; + + OcppTimestamp timestamp = MIN_TIME; //timestamp of StartTx; can be set before actually initiating + int32_t meter = -1; //meterStart of StartTx +}; + +class ServerTransactionStart { +private: + friend class Transaction; + + bool authorized = true; //authorization status; only valid if confirmed = true + int transactionId = -1; //only valid if confirmed = true +}; + +class TransactionStart { +private: + friend class Transaction; + + TransactionRPC rpc; + ClientTransactionStart client; + ServerTransactionStart server; +public: + TransactionStart(TransactionService& context) : rpc(context) { } +}; + +class ClientTransactionStop { +private: + friend class Transaction; + + char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; + OcppTimestamp timestamp = MIN_TIME; + int32_t meter = -1; + char reason [REASON_LEN_MAX + 1] = {'\0'}; +}; + +class ServerTransactionStop { +//no data at the moment +}; + +class TransactionStop { +private: + friend class Transaction; + + TransactionRPC rpc; + ClientTransactionStop client; + ServerTransactionStop server; +public: + TransactionStop(TransactionService& context) : rpc(context) { } +}; + +class ChargingSession { +private: + friend class Transaction; + + char idTag [IDTAG_LEN_MAX + 1] = {'\0'}; + OcppTimestamp timestamp = MIN_TIME; + int txProfileId = -1; + + bool active = true; //true: ignore + //false before StartTx init: abort + //false between StartTx init and StopTx init: end + //false after StopTx init: ignore +}; + +class Transaction { +private: + TransactionService& context; + + ChargingSession session; //data that exists before the tx + TransactionStart start; + TransactionStop stop; + + int connectorId = -1; + uint txNr = 0; //only valid if session.connectorId is >= 0 +public: + Transaction(TransactionService& context, uint connectorId, uint txNr) : + context(context), + start(context), + stop(context), + connectorId(connectorId), + txNr(txNr) {} + + bool serializeSessionState(DynamicJsonDocument& out); + bool deserializeSessionState(JsonObject in); + + int getConnectorId() {return connectorId;} + void setConnectorId(uint connectorId) {this->connectorId = connectorId;} + uint getTxNr() {return txNr;} //only valid if getConnectorId() >= 0 + void setTxNr(uint txNr) {this->txNr = txNr;} + + TransactionRPC& getStartRpcSync() {return start.rpc;} + + TransactionRPC& getStopRpcSync() {return stop.rpc;} + + bool isAborted() {return !start.rpc.requested && !session.active;} + bool isCompleted() {return start.rpc.isRequested() && start.rpc.isConfirmed() && + stop.rpc.isRequested() && stop.rpc.isConfirmed();} + bool isPending() {return !isAborted() && !isCompleted();} + bool isPreparing() {return session.active && !start.rpc.isRequested() && !stop.rpc.isRequested();} + bool isRunning() {return start.rpc.isRequested() && !stop.rpc.isRequested();} + bool isActive() {return session.active && !stop.rpc.isRequested();} + bool isInSession() {return isActive() && *session.idTag;} + + const char *getIdTag() {return session.idTag;} //only for testing in StartTx.req + void setIdTag(const char *idTag) {snprintf(session.idTag, IDTAG_LEN_MAX + 1, "%s", idTag);} + OcppTimestamp& getSessionTimestamp() {return session.timestamp;} + void setSessionTimestamp(OcppTimestamp timestamp) {session.timestamp = timestamp;} + + const char *getStopReason() {return stop.client.reason;} + void setStopReason(const char *reason) {snprintf(stop.client.reason, REASON_LEN_MAX + 1, "%s", reason);} + void endSession() {session.active = false;} + + void setIdTagDeauthorized() {start.server.authorized = false;} + bool isIdTagDeauthorized() {return start.rpc.isConfirmed() && !start.server.authorized;} + + int getTransactionId() {return start.server.transactionId;} + void setTransactionId(int transactionId) {start.server.transactionId = transactionId;} + + void setMeterStart(int32_t meter) {start.client.meter = meter;} + bool isMeterStartDefined() {return start.client.meter >= 0;} //should introduce extra variable later + int32_t getMeterStart() {return start.client.meter;} + + void setStartTimestamp(OcppTimestamp timestamp) {start.client.timestamp = timestamp;} + OcppTimestamp& getStartTimestamp() {return start.client.timestamp;} + + void setMeterStop(int32_t meter) {stop.client.meter = meter;} + bool isMeterStopDefined() {return stop.client.meter >= 0;} //should introduce extra variable later + int32_t getMeterStop() {return stop.client.meter;} + + void setStopTimestamp(OcppTimestamp timestamp) {stop.client.timestamp = timestamp;} + OcppTimestamp& getStopTimestamp() {return stop.client.timestamp;} + + const char *getStopIdTag() {return stop.client.idTag;} //only for testing in StartTx.req + void setStopIdTag(const char *idTag) {snprintf(stop.client.idTag, IDTAG_LEN_MAX + 1, "%s", idTag);} + + bool commit(); +}; + +} + +#endif diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.cpp b/src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.cpp new file mode 100644 index 00000000..fe31d2dd --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.cpp @@ -0,0 +1,113 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include + +#include + +#define AO_TXPROC_FN AO_FILENAME_PREFIX "/txproc.cnf" + +using namespace ArduinoOcpp; + +TransactionProcess::TransactionProcess(uint connectorId) { + char key [30] = {'\0'}; + if (snprintf(key, 30, "AO_txNrRef_%u", connectorId) < 0) { + AO_DBG_ERR("Invalid key"); + (void)0; + } + txNrRef = declareConfiguration(key, 0, AO_TXPROC_FN, false, false, true, false); + if (!txNrRef || *txNrRef < 0) { + AO_DBG_ERR("Initialization failure"); + } +} + +/* + * Evaluate the preconditions and triggers. If all are Active, then execute the transaction enable sequence. + * That is an ordered sequence of preparation steps which must be true before the transaction can start. When + * the transaction is finished, disable the preparation steps in reversed order. + * + * txEnable is the output variable. See getState() for getting the result. + */ +void TransactionProcess::evaluateProcessSteps(uint txNr) { + +#if AO_DBG_LEVEL >= AO_DL_DEBUG + //print transitions to debug console + TxEnableState txEnableBefore = txEnable; +#endif + + //Check Tx preconditions: Tx is only possible if all of them are true; search for a false precondition + auto txPrecondition = TxPrecondition::Active; + for (auto cond : txPreconditions) { + if (cond() != TxPrecondition::Active) { + txPrecondition = TxPrecondition::Inactive; + break; + } + } + + //Check Tx triggers: all of them need to be true to trigger a tx; prepare that search here + auto txTrigger = txTriggers.empty() ? TxTrigger::Inactive : TxTrigger::Active; + if (txPrecondition != TxPrecondition::Active) { //Only trigger a tx if the preconditions are met + txTrigger = TxTrigger::Inactive; + } + + //Check if the current process is obsolete + if (txNrRef && (uint) *txNrRef != txNr) { + txTrigger = TxTrigger::Inactive; + } + + //Determine if + // - No trigger is active -> activeTriggerExists = false, txTrigger = Inactive + // - All triggers are active -> activeTriggerExists = true, txTrigger = Active + // - Some are active, some not -> activeTriggerExists = true, txTrigger = Inactive + activeTriggerExists = false; + for (auto trigger = txTriggers.begin(); trigger != txTriggers.end(); trigger++) { + auto result = trigger->operator()(); + if (result == TxTrigger::Active) { + activeTriggerExists = true; + } else { + txTrigger = TxTrigger::Inactive; + } + } + + //Also check if all devices on the charger are enabled for the Tx + if (txTrigger == TxTrigger::Active) { //Search for an unready device + txEnable = TxEnableState::Active; + + for (auto step = txEnableSequence.rbegin(); step != txEnableSequence.rend(); step++) { + auto result = step->operator()(TxTrigger::Active); + if (result != TxEnableState::Active) { + txEnable = TxEnableState::Pending; + break; + } + } + } else { //Search for a device that is still enabled + txEnable = TxEnableState::Inactive; + + for (auto step = txEnableSequence.begin(); step != txEnableSequence.end(); step++) { + auto result = step->operator()(TxTrigger::Inactive); + if (result != TxEnableState::Inactive) { + txEnable = TxEnableState::Pending; + break; + } + } + } + + //If the current process is obsolete: Check if updating the tx reference is possible + if (txNrRef && (uint) *txNrRef != txNr) { + if (txEnable == TxEnableState::Inactive) { + AO_DBG_DEBUG("Upgrade to next tx: %u", txNr); + *txNrRef = txNr; + configuration_save(); + } + } + +#if AO_DBG_LEVEL >= AO_DL_DEBUG + if (txEnableBefore != txEnable) { + AO_DBG_DEBUG("Transition from %s to %s", + txEnableBefore == TxEnableState::Active ? "Active" : txEnableBefore == TxEnableState::Inactive ? "Inactive" : "Pending", + txEnable == TxEnableState::Active ? "Active" : txEnable == TxEnableState::Inactive ? "Inactive" : "Pending"); + } +#endif + +} diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.h b/src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.h new file mode 100644 index 00000000..6240bb24 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionProcess.h @@ -0,0 +1,46 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef TRANSACTIONPROCESS_H +#define TRANSACTIONPROCESS_H + +#include +#include + +#include +#include + +namespace ArduinoOcpp { + +class TransactionProcess { +private: + + std::vector> txPreconditions; + std::vector> txTriggers; + std::vector> txEnableSequence; + + bool activeTriggerExists = false; + TxEnableState txEnable {TxEnableState::Inactive}; // = Result of Trigger and Enable Sequence + + std::shared_ptr> txNrRef; + +public: + + TransactionProcess(uint connectorId); + + void addPrecondition(std::function fn) {txPreconditions.push_back(fn);} + + void addTrigger(std::function fn) {txTriggers.push_back(fn);} + + void addEnableStep(std::function fn) {txEnableSequence.push_back(fn);} + + void evaluateProcessSteps(uint txNr); + + bool existsActiveTrigger() {return activeTriggerExists;} + TxEnableState getState() {return txEnable;} +}; + +} + +#endif diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.cpp b/src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.cpp new file mode 100644 index 00000000..b6809892 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.cpp @@ -0,0 +1,29 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include + +#include + +#define AO_TXSEQ_FN AO_FILENAME_PREFIX "/txseq.cnf" + +using namespace ArduinoOcpp; + +TransactionSequence::TransactionSequence() { + + seqEnd = declareConfiguration("AO_SEQEND", 0, AO_TXSEQ_FN, false, false, true, false); +} + +uint TransactionSequence::getSeqEnd() { + return *seqEnd; +} + +uint TransactionSequence::reserveSeqNr() { + uint seq = (uint) *seqEnd; + uint seqIncr = (seq + 1U) % MAX_TXEVENT_CNT; + *seqEnd = seqIncr; + configuration_save(); + return seq; +} diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.h b/src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.h new file mode 100644 index 00000000..76856ce0 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionSequence.h @@ -0,0 +1,28 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef TRANSACTIONSEQUENCE_H +#define TRANSACTIONSEQUENCE_H + +#include +#include + +#define MAX_TXEVENT_CNT 100000U + +namespace ArduinoOcpp { + +class TransactionSequence { +private: + std::shared_ptr> seqEnd; //one place after last event +public: + TransactionSequence(); + + uint getSeqEnd(); + + uint reserveSeqNr(); +}; + +} + +#endif diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionService.cpp b/src/ArduinoOcpp/Tasks/Transactions/TransactionService.cpp new file mode 100644 index 00000000..b5dc1320 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionService.cpp @@ -0,0 +1,74 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include +#include +#include +#include +#include +#include + +using namespace ArduinoOcpp; + +#define AO_TXSERV_FN AO_FILENAME_PREFIX "/txserv.cnf" + +TransactionService::TransactionService(OcppEngine& context, uint nConnectors, std::shared_ptr filesystem) : + context(context), + txStore(*this, nConnectors, filesystem) { + + lastInitiatedSeqNr = declareConfiguration("LastSeqNr", MAX_TXEVENT_CNT, AO_TXSERV_FN, false, false, true, false); + lastInitiatedMsgCnt = declareConfiguration("LastMsgId", 1100000, AO_TXSERV_FN, false, false, true, false); + + txStore.restorePendingTransactions(); + txStore.submitPendingOperations(); +} + +void TransactionService::addRestoredOperation(uint seqNr, std::unique_ptr o) { + if (restoredOpsInitiated) { + AO_DBG_ERR("Can only restore at initialization. Abort"); + return; + } + if (seqNr == (uint) *lastInitiatedSeqNr) { + AO_DBG_DEBUG("Rebase msgIds: %i", (int) *lastInitiatedMsgCnt); + o->rebaseMsgId(*lastInitiatedMsgCnt); + } + restoredOps.addOcppOperation(seqNr, std::move(o)); +} + +void TransactionService::initiateRestoredOperations() { + if (restoredOpsInitiated) { + AO_DBG_ERR("Called two times. Abort"); + return; + } + + restoredOps.sort(txSequence.getSeqEnd()); + + std::vector> res; + + restoredOps.moveTo(res); + + for (auto operation = res.begin(); operation != res.end(); operation++) { + context.initiateOperation(std::move(*operation)); + } + res.clear(); + + restoredOpsInitiated = true; +} + +void TransactionService::setInitiatedMsgId(uint seqNr, int msgId) { + if (seqNr >= MAX_TXEVENT_CNT) { + AO_DBG_ERR("Invalid params"); + return; + } + if (*lastInitiatedSeqNr == seqNr && *lastInitiatedMsgCnt == msgId) { + //Nothing to update + return; + } + + AO_DBG_DEBUG("Set tx-related msg Id"); + *lastInitiatedSeqNr = seqNr; + *lastInitiatedMsgCnt = msgId; + configuration_save(); +} diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionService.h b/src/ArduinoOcpp/Tasks/Transactions/TransactionService.h new file mode 100644 index 00000000..93cd63c3 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionService.h @@ -0,0 +1,44 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef TRANSACTIONSERVICE_H +#define TRANSACTIONSERVICE_H + +#include +#include +#include +#include + +namespace ArduinoOcpp { + +class OcppEngine; +class FilesystemAdapter; +class OcppOperation; + +class TransactionService { +private: + OcppEngine& context; + + TransactionSequence txSequence; + TransactionStore txStore; + OrderedOperationsQueue restoredOps;//restore pending operations from flash in case of a power loss + bool restoredOpsInitiated = false; + + std::shared_ptr> lastInitiatedSeqNr; + std::shared_ptr> lastInitiatedMsgCnt; //use this msgId for the current initiated operation +public: + TransactionService(OcppEngine& context, uint nConnectors, std::shared_ptr filesystem); + + void addRestoredOperation(uint seqNr, std::unique_ptr op); + void initiateRestoredOperations(); + + void setInitiatedMsgId(uint seqNr, int msgId); + + TransactionStore& getTransactionStore() {return txStore;} + TransactionSequence& getTransactionSequence() {return txSequence;} +}; + +} + +#endif diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionStore.cpp b/src/ArduinoOcpp/Tasks/Transactions/TransactionStore.cpp new file mode 100644 index 00000000..8deeea6d --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionStore.cpp @@ -0,0 +1,308 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace ArduinoOcpp; + +#ifndef AO_TXSTORE_DIR +#define AO_TXSTORE_DIR AO_FILENAME_PREFIX "/" +#endif + +#define AO_TXSTORE_META_FN AO_FILENAME_PREFIX "/txstore.jsn" + +#define MAX_QUEUE_SIZE 20U +#define MAX_TX_CNT 100000U + +ConnectorTransactionStore::ConnectorTransactionStore(TransactionService& context, uint connectorId, std::shared_ptr filesystem) : + context(context), + connectorId(connectorId), + filesystem(filesystem) { + + char key [30] = {'\0'}; + if (snprintf(key, 30, "AO_txBegin_%u", connectorId) < 0) { + AO_DBG_ERR("Invalid key"); + (void)0; + } + txBegin = declareConfiguration(key, 0, AO_TXSTORE_META_FN, false, false, true, false); +} + +void ConnectorTransactionStore::restorePendingTransactions() { + + if (!filesystem) { + AO_DBG_ERR("FS error"); + return; + } + + if (!txBegin || *txBegin < 0 || *txBegin > MAX_TX_CNT) { + AO_DBG_ERR("Invalid state"); + return; + } + + uint tx = *txBegin; + txEnd = tx; + + const uint MISSES_LIMIT = 3; + uint misses = 0; + + while (misses < MISSES_LIMIT) { //search until region without txs found + + char fn [MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fn, MAX_PATH_SIZE, AO_TXSTORE_DIR "tx" "-%u-%u.jsn", connectorId, tx); + if (ret < 0 || ret >= MAX_PATH_SIZE) { + AO_DBG_ERR("fn error: %i", ret); + break; //all files have same length + } + + auto doc = FilesystemUtils::loadJson(filesystem, fn); + + if (!doc) { + misses++; + tx++; + tx %= MAX_TX_CNT; + continue; + } + + std::shared_ptr transaction = std::make_shared(context, connectorId, tx); + JsonObject txJson = doc->as(); + if (!transaction->deserializeSessionState(txJson)) { + AO_DBG_ERR("Deserialization error"); + misses++; + tx++; + tx %= MAX_TX_CNT; + continue; + } + + if (!transaction->isPending()) { + AO_DBG_DEBUG("Drop aborted / finished tx"); + tx++; + tx %= MAX_TX_CNT; + continue; //normal behavior => don't increment misses + } + + transactions.push_back(std::move(transaction)); + + tx++; + tx %= MAX_TX_CNT; + txEnd = tx; + misses = 0; + + if (transactions.size() > MAX_QUEUE_SIZE) { //allow to exceed MAX_QUEUE_SIZE by 1 + AO_DBG_ERR("txBegin out of sync"); + break; + } + } + + AO_DBG_DEBUG("Restored %zu transactions", transactions.size()); +} + +void ConnectorTransactionStore::submitPendingOperations() { + for (auto& tx : transactions) { + + auto& startRpc = tx->getStartRpcSync(); + if (startRpc.isRequested() && !startRpc.isConfirmed()) { + //startTx has been initiated, but not been confirmed yet + AO_DBG_DEBUG("Restore StartTx, connectorId=%i, seqNr=%u", connectorId, startRpc.getSeqNr()); + + auto startOp = makeOcppOperation(new Ocpp16::StartTransaction(tx)); + context.addRestoredOperation(startRpc.getSeqNr(), std::move(startOp)); + } + + auto& stopRpc = tx->getStopRpcSync(); + if (stopRpc.isRequested() && !stopRpc.isConfirmed()) { + //stopTx has been initiated, but not been confirmed yet + AO_DBG_DEBUG("Restore StopTx, connectorId=%i, seqNr=%u", connectorId, stopRpc.getSeqNr()); + + //... add stop data + + auto stopOp = makeOcppOperation(new Ocpp16::StopTransaction(tx)); + context.addRestoredOperation(stopRpc.getSeqNr(), std::move(stopOp)); + } + } +} + +std::shared_ptr ConnectorTransactionStore::makeTransaction() { + + if (!filesystem) { + AO_DBG_ERR("Invalid state"); + return nullptr; + } + + if (transactions.size() >= MAX_QUEUE_SIZE) { + AO_DBG_WARN("Queue full"); + return nullptr; + } + + auto transaction = std::make_shared(context, connectorId, txEnd); + txEnd++; + txEnd %= MAX_TX_CNT; + + transactions.push_back(transaction); + + if (!commit(transaction.get())) { + std::remove_if(transactions.begin(), transactions.end(), + [&transaction] (const std::shared_ptr& el) { + return el == transaction; + } + ); + txEnd--; + txEnd += MAX_TX_CNT; + txEnd %= MAX_TX_CNT; + return nullptr; + } + + return transaction; +} + +std::shared_ptr ConnectorTransactionStore::getActiveTransaction() { + + if (!transactions.empty()) { + auto& tx = transactions.back(); + if (tx->isActive() || tx->isRunning()) { + return tx; + } + } + + AO_DBG_DEBUG("Make new Tx"); + + auto tx = makeTransaction(); + if (tx) { + tx->setConnectorId(connectorId); + } + + return tx; +} + +std::shared_ptr ConnectorTransactionStore::getTransactionSync() { + + if (!transactions.empty()) { + auto& tx = transactions.front(); + if (tx->isRunning()) { + return tx; + } + } + + return nullptr; +} + +bool ConnectorTransactionStore::commit(Transaction *transaction) { + if (transactions.empty()) { + AO_DBG_ERR("Dangling transaction"); + return false; + } + + auto found = transactions.end(); + if (transaction == (transactions.end() - 1)->get()) { //optimization: check if committing the last container element + found = transactions.end() - 1; + } else { + for (auto entry = transactions.begin(); entry != transactions.end(); entry++) { + if (transaction == entry->get()) { + found = entry; + break; + } + } + } + + if (found == transactions.end()) { + AO_DBG_ERR("Dangling transaction"); + return false; + } + + //confirmed that transaction points to an element in the transactions container + + char fn [MAX_PATH_SIZE] = {'\0'}; + auto ret = snprintf(fn, MAX_PATH_SIZE, AO_TXSTORE_DIR "tx" "-%u-%u.jsn", connectorId, transaction->getTxNr()); + if (ret < 0 || ret >= MAX_PATH_SIZE) { + AO_DBG_ERR("fn error: %i", ret); + return false; //all files have same length + } + + DynamicJsonDocument txDoc {0}; + if (!transaction->serializeSessionState(txDoc)) { + AO_DBG_ERR("Serialization error"); + return false; + } + + if (!FilesystemUtils::storeJson(filesystem, fn, txDoc)) { + AO_DBG_ERR("FS error"); + return false; + } + + //Data committed to memory; now update meta structures + + if (!transaction->isPending()) { + + AO_DBG_DEBUG("Drop completed transaction"); + transactions.erase(found); + transaction = nullptr; + auto beginNew = txEnd; + if (!transactions.empty()) { + beginNew = transactions.front()->getTxNr(); + } + if (beginNew != (uint) *txBegin) { + *txBegin = beginNew; + configuration_save(); + } + } + + //success + return true; +} + +TransactionStore::TransactionStore(TransactionService& context, uint nConnectors, std::shared_ptr filesystem) { + + for (uint i = 0; i < nConnectors; i++) { + connectors.push_back(std::unique_ptr( + new ConnectorTransactionStore(context, i, filesystem))); + } +} + +std::shared_ptr TransactionStore::getActiveTransaction(uint connectorId) { + if (connectorId >= connectors.size()) { + AO_DBG_ERR("Invalid connectorId"); + return nullptr; + } + return connectors[connectorId]->getActiveTransaction(); +} + +std::shared_ptr TransactionStore::getTransactionSync(uint connectorId) { + if (connectorId >= connectors.size()) { + AO_DBG_ERR("Invalid connectorId"); + return nullptr; + } + return connectors[connectorId]->getTransactionSync(); +} + +bool TransactionStore::commit(Transaction *transaction) { + if (!transaction) { + AO_DBG_ERR("Invalid arg"); + return false; + } + auto connectorId = transaction->getConnectorId(); + if (connectorId < 0 || connectorId >= connectors.size()) { + AO_DBG_ERR("Invalid tx"); + return false; + } + return connectors[connectorId]->commit(transaction); +} + +void TransactionStore::restorePendingTransactions() { + for (auto& connector : connectors) { + connector->restorePendingTransactions(); + } +} + +void TransactionStore::submitPendingOperations() { + for (auto& connector : connectors) { + connector->submitPendingOperations(); + } +} diff --git a/src/ArduinoOcpp/Tasks/Transactions/TransactionStore.h b/src/ArduinoOcpp/Tasks/Transactions/TransactionStore.h new file mode 100644 index 00000000..cfffdfa4 --- /dev/null +++ b/src/ArduinoOcpp/Tasks/Transactions/TransactionStore.h @@ -0,0 +1,61 @@ +// matth-x/ArduinoOcpp +// Copyright Matthias Akstaller 2019 - 2022 +// MIT License + +#ifndef TRANSACTIONSTORE_H +#define TRANSACTIONSTORE_H + +#include +#include +#include +#include +#include + +namespace ArduinoOcpp { + +class TransactionService; +class ConnectorTransactionSequence; + +class ConnectorTransactionStore { +private: + TransactionService& context; + const uint connectorId; + + std::shared_ptr filesystem; + std::shared_ptr> txBegin; //The Tx file names are consecutively numbered; first number + uint txEnd; //one place after last number + + std::deque> transactions; + + std::shared_ptr makeTransaction(); + +public: + ConnectorTransactionStore(TransactionService& context, uint nConnectors, std::shared_ptr filesystem); + + std::shared_ptr getActiveTransaction(); + std::shared_ptr getTransactionSync(); + bool commit(Transaction *transaction); + + void restorePendingTransactions(); + void submitPendingOperations(); +}; + +class TransactionStore { +private: + std::vector> connectors; + +public: + TransactionStore(TransactionService& context, uint nConnectors, std::shared_ptr filesystem); + + //std::shared_ptr makeTransaction(uint connectorId); + std::shared_ptr getActiveTransaction(uint connectorId); + std::shared_ptr getTransactionSync(uint connectorId); //fron element of the tx queue; tx which is being executed at the server now + bool commit(Transaction *transaction); + + void restorePendingTransactions(); + void submitPendingOperations(); +}; + +} + +#endif From f833b1fc2687d6b42bec2588ae34ab9a7a456779 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Thu, 25 Aug 2022 21:14:10 +0200 Subject: [PATCH 052/549] fix signature --- src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp b/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp index e72d0c73..c622af73 100644 --- a/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp +++ b/src/ArduinoOcpp/Tasks/Transactions/OrderedOperationsQueue.cpp @@ -13,7 +13,7 @@ void OrderedOperationsQueue::addOcppOperation(uint seqNr, std::unique_ptr>& a, std::pair>& b) { + std::sort(operations.begin(), operations.end(), [seqEnd] (const std::pair>& a, const std::pair>& b) { uint da = (seqEnd - a.first + MAX_TXEVENT_CNT) % MAX_TXEVENT_CNT; //distance between a to seqEnd uint db = (seqEnd - b.first + MAX_TXEVENT_CNT) % MAX_TXEVENT_CNT; return da > db; //sort descending by distance to seqEnd <=> sort ascending by seqNr From 7681ec7666dc80f076bf45d31ced1f4a4e4418cd Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 27 Aug 2022 23:51:52 +0200 Subject: [PATCH 053/549] allow access to internals via facade --- src/ArduinoOcpp.cpp | 9 +++++++++ src/ArduinoOcpp.h | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 6b29a231..1de0dc77 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -637,3 +637,12 @@ ArduinoOcpp::DiagnosticsService *getDiagnosticsService() { return model.getDiagnosticsService(); } #endif + +OcppEngine *getOcppEngine() { + if (!ocppEngine) { + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before + return nullptr; + } + + return ocppEngine; +} diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index d8ffae91..d72cdbdf 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -199,4 +199,12 @@ ArduinoOcpp::FirmwareService *getFirmwareService(); ArduinoOcpp::DiagnosticsService *getDiagnosticsService(); #endif +namespace ArduinoOcpp { +class OcppEngine; +} + +//Get access to internal functions and data structures. The returned OcppEngine object allows +//you to bypass the facace functions of this header and implement custom functionality. +ArduinoOcpp::OcppEngine *getOcppEngine(); + #endif From 5f1a8c5d2c84caf6dd2669aa09e0e89de1708df3 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 28 Aug 2022 12:22:16 +0200 Subject: [PATCH 054/549] remove superfluous debug messages --- src/ArduinoOcpp/Core/FilesystemUtils.cpp | 3 +-- src/ArduinoOcpp/Tasks/Transactions/Transaction.cpp | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ArduinoOcpp/Core/FilesystemUtils.cpp b/src/ArduinoOcpp/Core/FilesystemUtils.cpp index 390b6d6a..27056d73 100644 --- a/src/ArduinoOcpp/Core/FilesystemUtils.cpp +++ b/src/ArduinoOcpp/Core/FilesystemUtils.cpp @@ -70,8 +70,7 @@ std::unique_ptr FilesystemUtils::loadJson(std::shared_ptr Date: Sun, 28 Aug 2022 12:22:47 +0200 Subject: [PATCH 055/549] allow to initialize with multiple connectors --- src/ArduinoOcpp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 1de0dc77..3b2902ea 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -37,7 +37,10 @@ std::shared_ptr filesystem; FilesystemOpt fileSystemOpt {}; float voltage_eff {230.f}; +#ifndef OCPP_NUMCONNECTORS #define OCPP_NUMCONNECTORS 2 +#endif + #define OCPP_ID_OF_CONNECTOR 1 #define OCPP_ID_OF_CP 0 bool OCPP_booted = false; //if BootNotification succeeded From a4fef8d7f4a51fe2206d7e43ff9375b5ad3a8aa2 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 28 Aug 2022 15:01:52 +0200 Subject: [PATCH 056/549] remove superfluous console output --- src/ArduinoOcpp/Core/FilesystemUtils.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ArduinoOcpp/Core/FilesystemUtils.cpp b/src/ArduinoOcpp/Core/FilesystemUtils.cpp index 27056d73..8ed689f8 100644 --- a/src/ArduinoOcpp/Core/FilesystemUtils.cpp +++ b/src/ArduinoOcpp/Core/FilesystemUtils.cpp @@ -70,7 +70,7 @@ std::unique_ptr FilesystemUtils::loadJson(std::shared_ptr filesystem, c ArduinoJsonFileAdapter fileWriter {file.get()}; size_t written = serializeJson(doc, fileWriter); - written = serializeJson(doc, Serial); if (written < 2) { AO_DBG_ERR("Error writing file %s", fn); From 154828c2a3174e7cb6da7fed6243a436b10b02d8 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sat, 27 Aug 2022 23:51:52 +0200 Subject: [PATCH 057/549] allow access to internals via facade --- src/ArduinoOcpp.cpp | 9 +++++++++ src/ArduinoOcpp.h | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 781fc9c1..78a15f2c 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -564,3 +564,12 @@ ArduinoOcpp::DiagnosticsService *getDiagnosticsService() { return model.getDiagnosticsService(); } #endif + +OcppEngine *getOcppEngine() { + if (!ocppEngine) { + AO_DBG_ERR("OCPP uninitialized"); //please call OCPP_initialize before + return nullptr; + } + + return ocppEngine; +} diff --git a/src/ArduinoOcpp.h b/src/ArduinoOcpp.h index 40014f94..72775ed9 100644 --- a/src/ArduinoOcpp.h +++ b/src/ArduinoOcpp.h @@ -200,4 +200,12 @@ ArduinoOcpp::FirmwareService *getFirmwareService(); ArduinoOcpp::DiagnosticsService *getDiagnosticsService(); #endif +namespace ArduinoOcpp { +class OcppEngine; +} + +//Get access to internal functions and data structures. The returned OcppEngine object allows +//you to bypass the facace functions of this header and implement custom functionality. +ArduinoOcpp::OcppEngine *getOcppEngine(); + #endif From 18355d0ea32db7ca836167ca82bfb8b435ff83b6 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Sun, 28 Aug 2022 12:22:47 +0200 Subject: [PATCH 058/549] allow to initialize with multiple connectors --- src/ArduinoOcpp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 78a15f2c..8e79fc7a 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -36,7 +36,10 @@ std::shared_ptr filesystem; FilesystemOpt fileSystemOpt {}; float voltage_eff {230.f}; +#ifndef OCPP_NUMCONNECTORS #define OCPP_NUMCONNECTORS 2 +#endif + #define OCPP_ID_OF_CONNECTOR 1 #define OCPP_ID_OF_CP 0 bool OCPP_booted = false; //if BootNotification succeeded From 766963219b0187658212f87627cd493c96d6b808 Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:22:59 +0200 Subject: [PATCH 059/549] access to internals from C facade --- src/ArduinoOcpp.cpp | 6 +++++- src/ArduinoOcpp/Core/FilesystemAdapter.cpp | 3 +-- src/ArduinoOcpp/Core/FilesystemAdapter.h | 3 +-- src/ArduinoOcpp_c.cpp | 4 ++++ src/ArduinoOcpp_c.h | 9 +++++++++ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ArduinoOcpp.cpp b/src/ArduinoOcpp.cpp index 8e79fc7a..d65a754b 100644 --- a/src/ArduinoOcpp.cpp +++ b/src/ArduinoOcpp.cpp @@ -89,7 +89,11 @@ void OCPP_initialize(OcppSocket& ocppSocket, float V_eff, ArduinoOcpp::Filesyste voltage_eff = V_eff; fileSystemOpt = fsOpt; - filesystem = EspWiFi::makeDefaultFilesystemAdapter(fileSystemOpt); +#ifndef AO_DEACTIVATE_FLASH + std::shared_ptr filesystem = EspWiFi::makeDefaultFilesystemAdapter(fileSystemOpt); +#else + std::shared_ptr filesystem; +#endif AO_DBG_DEBUG("filesystem %s", filesystem ? "loaded" : "error"); configuration_init(filesystem); //call before each other library call diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp index 1d3d720e..51e1e251 100644 --- a/src/ArduinoOcpp/Core/FilesystemAdapter.cpp +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.cpp @@ -6,8 +6,7 @@ #include //FilesystemOpt #include -//#ifndef AO_DEACTIVATE_FLASH -#if 1 +#ifndef AO_DEACTIVATE_FLASH //Set default parameters; assume usage with Arduino if no build flags are present #ifndef AO_USE_FILEAPI diff --git a/src/ArduinoOcpp/Core/FilesystemAdapter.h b/src/ArduinoOcpp/Core/FilesystemAdapter.h index 93c6af27..8fd7c9c0 100644 --- a/src/ArduinoOcpp/Core/FilesystemAdapter.h +++ b/src/ArduinoOcpp/Core/FilesystemAdapter.h @@ -61,8 +61,7 @@ class FilesystemAdapter { } //end namespace ArduinoOcpp -//#ifndef AO_DEACTIVATE_FLASH -#if 1 +#ifndef AO_DEACTIVATE_FLASH //Set default parameters; assume usage with Arduino if no build flags are present #ifndef AO_USE_FILEAPI diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 03fbc26a..69ae46e0 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -198,3 +198,7 @@ bool ao_isInSession() { const char *ao_getSessionIdTag() { return getSessionIdTag(); } + +OcppHandle *getOcppHandle() { + return reinterpret_cast(getOcppEngine()); +} diff --git a/src/ArduinoOcpp_c.h b/src/ArduinoOcpp_c.h index 9f4235e8..3fe0a0b0 100644 --- a/src/ArduinoOcpp_c.h +++ b/src/ArduinoOcpp_c.h @@ -6,6 +6,9 @@ struct AOcppSocket; typedef struct AOcppSocket AOcppSocket; +struct OcppHandle; +typedef struct OcppHandle OcppHandle; + typedef void (*OnOcppMessage) (const char *payload, size_t len); typedef void (*OnOcppAbort) (); typedef void (*OnOcppTimeout) (); @@ -102,6 +105,12 @@ bool ao_isInSession(); const char *ao_getSessionIdTag(); +/* + * Get access to the internals + */ + +OcppHandle *getOcppHandle(); + #ifdef __cplusplus } #endif From 10795aad7e124917a0336b4d98fd51b47c197453 Mon Sep 17 00:00:00 2001 From: Matthias Akstaller <63792403+matth-x@users.noreply.github.com> Date: Sat, 10 Sep 2022 09:43:29 +0200 Subject: [PATCH 060/549] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 793edd6a..4e3b4c82 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ void setup() { Serial.print(F("BootNotification was answered. Central System clock: ")); Serial.println(confMsg["currentTime"].as()); //"currentTime" is a field of the central system response - //evseIsBooted = true; <-- Example: Notify your hardare that the BootNotification.conf() has arrived + //evseIsBooted = true; <-- Example: Notify your hardware that the BootNotification.conf() has arrived }); ... //rest of setup() function; executed immediately as bootNotification() is non-blocking From 5643ea004a324fa7fbf7380e83475e4bd558486d Mon Sep 17 00:00:00 2001 From: matth-x <63792403+matth-x@users.noreply.github.com> Date: Mon, 19 Sep 2022 22:47:50 +0200 Subject: [PATCH 061/549] add missing include statements --- src/ArduinoOcpp/MessagesV16/StopTransaction.h | 1 + src/ArduinoOcpp/Tasks/Metering/MeterValue.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ArduinoOcpp/MessagesV16/StopTransaction.h b/src/ArduinoOcpp/MessagesV16/StopTransaction.h index eaaacde8..c74e4601 100644 --- a/src/ArduinoOcpp/MessagesV16/StopTransaction.h +++ b/src/ArduinoOcpp/MessagesV16/StopTransaction.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace ArduinoOcpp { diff --git a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h index 450f5d5b..541360d5 100644 --- a/src/ArduinoOcpp/Tasks/Metering/MeterValue.h +++ b/src/ArduinoOcpp/Tasks/Metering/MeterValue.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace ArduinoOcpp { From c8bdc6a140736222aa7f1092427be72e6d4a8e1c Mon Sep 17 00:00:00 2001 From: Matthias Akstaller <63792403+matth-x@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:13:13 +0200 Subject: [PATCH 062/549] roll back dependency --- examples/SECC/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/SECC/platformio.ini b/examples/SECC/platformio.ini index 5453178e..72b36270 100644 --- a/examples/SECC/platformio.ini +++ b/examples/SECC/platformio.ini @@ -8,7 +8,7 @@ board = esp32dev framework = arduino lib_deps = https://github.com/matth-x/ArduinoOcpp.git - https://github.com/tzapu/WiFiManager.git + https://github.com/tzapu/WiFiManager.git#4dcf0cc78bb031351d65e0f9b9271569df8720ed monitor_speed = 115200 [env:nodemcuv2] @@ -17,5 +17,5 @@ board = nodemcuv2 framework = arduino lib_deps = https://github.com/matth-x/ArduinoOcpp.git - https://github.com/tzapu/WiFiManager.git + https://github.com/tzapu/WiFiManager.git#4dcf0cc78bb031351d65e0f9b9271569df8720ed monitor_speed = 115200 From 0a8fbfbb10e86223803a231f42f9d683d8d55fff Mon Sep 17 00:00:00 2001 From: Matthias Akstaller <63792403+matth-x@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:19:15 +0200 Subject: [PATCH 063/549] Exclude SECC example --- .github/workflows/pio.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pio.yaml b/.github/workflows/pio.yaml index ab6248b7..819bc365 100644 --- a/.github/workflows/pio.yaml +++ b/.github/workflows/pio.yaml @@ -11,10 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - example: [examples/ESP/main.cpp, examples/ESP-TLS/main.cpp, examples/SECC/main.cpp] - include: - - example: examples/SECC/main.cpp - dashboard-extra: --lib="/tmp/tzapu/WiFiManager" + example: [examples/ESP/main.cpp, examples/ESP-TLS/main.cpp] steps: - uses: actions/checkout@v2 @@ -38,10 +35,7 @@ jobs: pip install --upgrade platformio - name: Install library dependencies run: pio pkg install - - name: Extra dependencies for SECC example - if: ${{ matrix.dashboard-extra }} - run: git clone https://github.com/tzapu/WiFiManager.git /tmp/tzapu/WiFiManager - name: Run PlatformIO run: pio ci --lib="." --project-conf=platformio.ini ${{ matrix.dashboard-extra }} env: - PLATFORMIO_CI_SRC: ${{ matrix.example }} \ No newline at end of file + PLATFORMIO_CI_SRC: ${{ matrix.example }} From 579c67adffd43477a302e94dcc8846303682e255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 09:47:25 +0200 Subject: [PATCH 064/549] sample workflow for platform independent compilation --- .github/workflows/platformless.yml | 23 +++++++++++++++++++++++ .github/workflows/tests.yml | 19 +++++++++++++++++++ src/ArduinoOcpp_c.cpp | 2 ++ src/patch.cpp | 9 +++++++++ 4 files changed, 53 insertions(+) create mode 100644 .github/workflows/platformless.yml create mode 100644 .github/workflows/tests.yml create mode 100644 src/patch.cpp diff --git a/.github/workflows/platformless.yml b/.github/workflows/platformless.yml new file mode 100644 index 00000000..60764468 --- /dev/null +++ b/.github/workflows/platformless.yml @@ -0,0 +1,23 @@ +name: Platform independent compilation + +on: + push: + branches: + - feature/freertos + +jobs: + + compile-tests: + name: Compile + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: get gcc compiler + run: | + sudo apt update + sudo apt install build-essential + - name: Get ArduinoJson + run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.19.4/ArduinoJson-v6.19.4.h -O ./src/ArduinoJson.h + - name: Compile + run: g++ -I ./src -g $(find ./src -type f -iregex ".*\.cpp") -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE -DAO_CUSTOM_UPDATER -DAO_CUSTOM_RESET -DAO_DEACTIVATE_FLASH -DAO_USE_FILEAPI=ESPIDF_SPIFFS -DAO_DBG_LEVEL=AO_DL_DEBUG -DAO_TRAFFIC_OUT -DAO_FILENAME_PREFIX='"/ao_store"' -o ./output -Wall diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..d14d3fe9 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +name: Unit tests + +on: + push: + branches: + - test-integration-disabled + +jobs: + + compile-tests: + name: Compile Tests + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Compile tests + run: g++ -Wall test/unitTests/000-unitTestsMain.cpp -o test/compiledTests + - name: Run tests + run: ./test/compiledTests \ No newline at end of file diff --git a/src/ArduinoOcpp_c.cpp b/src/ArduinoOcpp_c.cpp index 69ae46e0..b4642ccf 100644 --- a/src/ArduinoOcpp_c.cpp +++ b/src/ArduinoOcpp_c.cpp @@ -1,3 +1,4 @@ +#if 0 #include "ArduinoOcpp_c.h" #include "ArduinoOcpp.h" @@ -202,3 +203,4 @@ const char *ao_getSessionIdTag() { OcppHandle *getOcppHandle() { return reinterpret_cast(getOcppEngine()); } +#endif diff --git a/src/patch.cpp b/src/patch.cpp new file mode 100644 index 00000000..2f87613c --- /dev/null +++ b/src/patch.cpp @@ -0,0 +1,9 @@ +#include + +unsigned long ao_tick_ms_impl() { + return 0; +} + +int main() { + return 0; +} From 62813473b49c38ddea0491f014381ffa918da7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 08:48:59 +0000 Subject: [PATCH 065/549] update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..015a4214 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.devcontainer +ArduinoJson.h \ No newline at end of file From 03e2b3e0323d4f98496b7bd23f4bdb8817442e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 08:52:04 +0000 Subject: [PATCH 066/549] test workflow --- .github/workflows/platformless.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/platformless.yml b/.github/workflows/platformless.yml index 60764468..e07500c0 100644 --- a/.github/workflows/platformless.yml +++ b/.github/workflows/platformless.yml @@ -20,4 +20,4 @@ jobs: - name: Get ArduinoJson run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.19.4/ArduinoJson-v6.19.4.h -O ./src/ArduinoJson.h - name: Compile - run: g++ -I ./src -g $(find ./src -type f -iregex ".*\.cpp") -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE -DAO_CUSTOM_UPDATER -DAO_CUSTOM_RESET -DAO_DEACTIVATE_FLASH -DAO_USE_FILEAPI=ESPIDF_SPIFFS -DAO_DBG_LEVEL=AO_DL_DEBUG -DAO_TRAFFIC_OUT -DAO_FILENAME_PREFIX='"/ao_store"' -o ./output -Wall + run: g++ -std=c++14 -I ./src -g $(find ./src -type f -iregex ".*\.cpp") -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE -DAO_CUSTOM_UPDATER -DAO_CUSTOM_RESET -DAO_DEACTIVATE_FLASH -DAO_USE_FILEAPI=ESPIDF_SPIFFS -DAO_DBG_LEVEL=AO_DL_DEBUG -DAO_TRAFFIC_OUT -DAO_FILENAME_PREFIX='"/ao_store"' -o ./output -Wall From 0fbbd8de8e035a99c7c26a1ca6f49a19c8d57823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 08:52:37 +0000 Subject: [PATCH 067/549] update gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 015a4214..8f15d4c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .devcontainer -ArduinoJson.h \ No newline at end of file +ArduinoJson.h +workflow.ps1 \ No newline at end of file From 17af159ef507cc573fa90d9f5b74ba9cffa5f795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 08:55:11 +0000 Subject: [PATCH 068/549] update gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8f15d4c9..0b81cddd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .devcontainer ArduinoJson.h -workflow.ps1 \ No newline at end of file +workflow.sh \ No newline at end of file From ee21d06da8c21f32ab66f3687c0b6cde3bbe9172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 09:00:26 +0000 Subject: [PATCH 069/549] test workflow --- .github/workflows/platformless.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/platformless.yml b/.github/workflows/platformless.yml index e07500c0..4b22b20a 100644 --- a/.github/workflows/platformless.yml +++ b/.github/workflows/platformless.yml @@ -17,6 +17,7 @@ jobs: run: | sudo apt update sudo apt install build-essential + g++ --version - name: Get ArduinoJson run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.19.4/ArduinoJson-v6.19.4.h -O ./src/ArduinoJson.h - name: Compile From 47168202b7f5fbad83c72808070a54b8f3ae44fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 09:34:30 +0000 Subject: [PATCH 070/549] added info for g++ compiler version --- .github/workflows/platformless.yml | 4 +++- .gitignore | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/platformless.yml b/.github/workflows/platformless.yml index 4b22b20a..83f266c9 100644 --- a/.github/workflows/platformless.yml +++ b/.github/workflows/platformless.yml @@ -17,8 +17,10 @@ jobs: run: | sudo apt update sudo apt install build-essential + sudo apt -y install gcc-9 g++-9 g++ --version + echo "g++ version must be 9.4.0" - name: Get ArduinoJson run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.19.4/ArduinoJson-v6.19.4.h -O ./src/ArduinoJson.h - name: Compile - run: g++ -std=c++14 -I ./src -g $(find ./src -type f -iregex ".*\.cpp") -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE -DAO_CUSTOM_UPDATER -DAO_CUSTOM_RESET -DAO_DEACTIVATE_FLASH -DAO_USE_FILEAPI=ESPIDF_SPIFFS -DAO_DBG_LEVEL=AO_DL_DEBUG -DAO_TRAFFIC_OUT -DAO_FILENAME_PREFIX='"/ao_store"' -o ./output -Wall + run: g++ -std=c++14 -I ./src -g $(find ./src -type f -iregex ".*\.cpp") -DAO_CUSTOM_WS -DAO_CUSTOM_CONSOLE -DAO_CUSTOM_UPDATER -DAO_CUSTOM_RESET -DAO_DEACTIVATE_FLASH -DAO_USE_FILEAPI=ESPIDF_SPIFFS -DAO_DBG_LEVEL=AO_DL_DEBUG -DAO_TRAFFIC_OUT -DAO_FILENAME_PREFIX='"/ao_store"' -o ./output.exe -Wall diff --git a/.gitignore b/.gitignore index 0b81cddd..83d29ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .devcontainer ArduinoJson.h -workflow.sh \ No newline at end of file +workflow.sh +output.exe \ No newline at end of file From c672d19fce9fa753deb203cad6cda753a8dbffad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Gr=C3=BCnberg?= Date: Tue, 27 Sep 2022 09:44:13 +0000 Subject: [PATCH 071/549] added test files --- tests/000-unitTestsMain.cpp | 12 + tests/catch2/catch.hpp | 17969 ++++++++++++++++++++++++++++++++ tests/exampleCheckNumbers.cpp | 13 + 3 files changed, 17994 insertions(+) create mode 100644 tests/000-unitTestsMain.cpp create mode 100644 tests/catch2/catch.hpp create mode 100644 tests/exampleCheckNumbers.cpp diff --git a/tests/000-unitTestsMain.cpp b/tests/000-unitTestsMain.cpp new file mode 100644 index 00000000..71a6c970 --- /dev/null +++ b/tests/000-unitTestsMain.cpp @@ -0,0 +1,12 @@ +//000- prefix is for the main-file to always be on top in folder view + +// Powershell to compile tests and run them: +// g++ -Wall test/unitTests/000-unitTestsMain.cpp -o ./test/compiledTests ; ./test/compiledTests ; Remove-Item -Path ./test/compiledTests.exe + +//use the main function provided by catch2 +#define CATCH_CONFIG_MAIN +//include the catch2 library +#include "../catch2/catch.hpp" + +//include the unit tests +#include "./exampleCheckNumbers.cpp" \ No newline at end of file diff --git a/tests/catch2/catch.hpp b/tests/catch2/catch.hpp new file mode 100644 index 00000000..07efa655 --- /dev/null +++ b/tests/catch2/catch.hpp @@ -0,0 +1,17969 @@ +/* + * Catch v2.13.9 + * Generated: 2022-04-12 22:37:23.260201 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 9 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +# if !defined(__clang__) // Handle Clang masquerading for msvc + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL + +// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template