From ba0871b062bf8b32ca2fc6651b2c0c815e76ce12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piet=20G=C3=B6mpel?= <37657534+Pietfried@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:35:54 +0200 Subject: [PATCH] Error Reporting via OCPP201 (#824) * Implemented on_event in OCPP201 and add documentation for how the EventDataType is derived from the EVerest Error Type * Added implementation to trigger NotifyEvent.req in case errors are received within EVerest. Added documentation about the current limitations, which includes that currently no proper mapping to Components and Variables is implemented --------- Signed-off-by: pietfried --- modules/OCPP201/OCPP201.cpp | 23 ++++++++- modules/OCPP201/OCPP201.hpp | 1 + modules/OCPP201/doc.rst | 43 ++++++++++++++-- modules/OCPP201/error_handling.hpp | 82 ++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 modules/OCPP201/error_handling.hpp diff --git a/modules/OCPP201/OCPP201.cpp b/modules/OCPP201/OCPP201.cpp index 39aa8804f..7cb0ea3e9 100644 --- a/modules/OCPP201/OCPP201.cpp +++ b/modules/OCPP201/OCPP201.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -287,6 +288,26 @@ void OCPP201::init() { } }); } + + const auto error_handler = [this](const Everest::error::Error& error) { + if (error.type == EVSE_MANAGER_INOPERATIVE_ERROR) { + // handled by specific evse_manager error handler + return; + } + const auto event_data = get_event_data(error, false, this->event_id_counter++); + this->charge_point->on_event({event_data}); + }; + + const auto error_cleared_handler = [this](const Everest::error::Error& error) { + if (error.type == EVSE_MANAGER_INOPERATIVE_ERROR) { + // handled by specific evse_manager error handler + return; + } + const auto event_data = get_event_data(error, true, this->event_id_counter++); + this->charge_point->on_event({event_data}); + }; + + subscribe_global_all_errors(error_handler, error_cleared_handler); } void OCPP201::ready() { @@ -770,7 +791,7 @@ void OCPP201::ready() { }; // A permanent fault from the evse requirement indicates that the evse should move to faulted state - evse->subscribe_error("evse_manager/Inoperative", fault_handler, fault_cleared_handler); + evse->subscribe_error(EVSE_MANAGER_INOPERATIVE_ERROR, fault_handler, fault_cleared_handler); evse_id++; } diff --git a/modules/OCPP201/OCPP201.hpp b/modules/OCPP201/OCPP201.hpp index 253a06715..87acfbb6b 100644 --- a/modules/OCPP201/OCPP201.hpp +++ b/modules/OCPP201/OCPP201.hpp @@ -122,6 +122,7 @@ class OCPP201 : public Everest::ModuleBase { // key represents evse_id, value indicates if ready std::map evse_ready_map; std::map> evse_soc_map; + int32_t event_id_counter{0}; std::mutex evse_ready_mutex; std::mutex session_event_mutex; std::condition_variable evse_ready_cv; diff --git a/modules/OCPP201/doc.rst b/modules/OCPP201/doc.rst index 19fc1d5c6..23012ff6c 100644 --- a/modules/OCPP201/doc.rst +++ b/modules/OCPP201/doc.rst @@ -1,5 +1,40 @@ -Global Errors -============= +Error Handling +============== + +The `enable_global_errors` flag for this module is true. This module is +therefore able to retrieve and process all reported errors from other +modules that are loaded in the same EVerest configuration. + +The error reporting via OCPP2.0.1 follows the Minimum Required Error Codes (MRECS): https://inl.gov/chargex/mrec/ . This proposes a unified methodology +to define and classify a minimum required set of error codes and how to report them via OCPP2.0.1. + +StatusNotification +------------------ +In contrast to OCPP1.6, error information is not transmitted as part of the StatusNotification.req. +A `StatusNotification.req` with status `Faulted` will be set to faulted only in case the error received is of the special type `evse_manager/Inoperative`. +This indicates that the EVSE is inoperative (not ready for energy transfer). + +In OCPP2.0.1 errors can be reported using the `NotifyEventRequest.req` . This message is used to report all other errros received. + +Current Limitation +------------------ + +In OCPP2.0.1 errors can be reported using the `NotifyEventRequest` +message. The `eventData` property carries the relevant information. + +This format of reporting errors deviates from the mechanism used within +EVerest. This data structure forces to map an error to a +component-variable combination. This requires a mapping +mechanism between EVerest errors and component-variable +combination. + +Currently this module maps the Error to one of these three Components: + +* ChargingStation (if error.origin.mapping.evse is not set or 0) +* EVSE (error.origin.mapping.evse is set and error.origin.mapping.connector is not set) +* Connector (error.origin.mapping.evse is set and error.origin.mapping.connector is set) + +The Variable used as part of the NotifyEventRequest is constantly defined to `Problem` for now. + +The goal is to have a more advanced mapping of reported errors to the respective component-variable combinations in the future. -The `enable_global_errors` flag for this module is enabled. This module is therefore able to retrieve and process all reported errors -from other modules loaded in the same EVerest configuration. diff --git a/modules/OCPP201/error_handling.hpp b/modules/OCPP201/error_handling.hpp new file mode 100644 index 000000000..b5f2708a1 --- /dev/null +++ b/modules/OCPP201/error_handling.hpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef OCPP201_ERROR_HANDLING_HPP +#define OCPP201_ERROR_HANDLING_HPP + +#include +#include + +#include + +namespace module { +const std::unordered_map MREC_ERROR_MAP = { + {"connector_lock/MREC1ConnectorLockFailure", "CX001"}, + {"evse_board_support/MREC2GroundFailure", "CX002"}, + {"evse_board_support/MREC3HighTemperature", "CX003"}, + {"evse_board_support/MREC4OverCurrentFailure", "CX004"}, + {"evse_board_support/MREC5OverVoltage", "CX005"}, + {"evse_board_support/MREC6UnderVoltage", "CX006"}, + {"evse_board_support/MREC8EmergencyStop", "CX008"}, + {"evse_board_support/MREC10InvalidVehicleMode", "CX010"}, + {"evse_board_support/MREC14PilotFault", "CX014"}, + {"evse_board_support/MREC15PowerLoss", "CX015"}, + {"evse_board_support/MREC17EVSEContactorFault", "CX017"}, + {"evse_board_support/MREC18CableOverTempDerate", "CX018"}, + {"evse_board_support/MREC19CableOverTempStop", "CX019"}, + {"evse_board_support/MREC20PartialInsertion", "CX020"}, + {"evse_board_support/MREC23ProximityFault", "CX023"}, + {"evse_board_support/MREC24ConnectorVoltageHigh", "CX024"}, + {"evse_board_support/MREC25BrokenLatch", "CX025"}, + {"evse_board_support/MREC26CutCable", "CX026"}, + {"evse_manager/MREC4OverCurrentFailure", "CX004"}, + {"ac_rcd/MREC2GroundFailure", "CX002"}, +}; + +const auto EVSE_MANAGER_INOPERATIVE_ERROR = "evse_manager/Inoperative"; +const auto CHARGING_STATION_COMPONENT_NAME = "ChargingStation"; +const auto EVSE_COMPONENT_NAME = "EVSE"; +const auto CONNECTOR_COMPONENT_NAME = "Connector"; +const auto PROBLEM_VARIABLE_NAME = "Problem"; + +/// \brief Derives the EventData from the given \p error, \p cleared and \p event_id parameters +ocpp::v201::EventData get_event_data(const Everest::error::Error& error, const bool cleared, const int32_t event_id) { + ocpp::v201::EventData event_data; + event_data.eventId = event_id; // This can theoretically conflict with eventIds generated in libocpp (e.g. + // for monitoring events), but the spec does not strictly forbid that + event_data.timestamp = ocpp::DateTime(error.timestamp); + event_data.trigger = ocpp::v201::EventTriggerEnum::Alerting; + event_data.cause = std::nullopt; // TODO: use caused_by when available within error object + event_data.actualValue = cleared ? "false" : "true"; + + if (MREC_ERROR_MAP.count(error.type)) { + event_data.techCode = MREC_ERROR_MAP.at(error.type); + } else { + event_data.techCode = error.type; + } + + event_data.techInfo = error.description; + event_data.cleared = cleared; + event_data.transactionId = std::nullopt; // TODO: Do we need to set this here? + event_data.variableMonitoringId = std::nullopt; // We dont need to set this for HardwiredNotification + event_data.eventNotificationType = ocpp::v201::EventNotificationEnum::HardWiredNotification; + + // TODO: choose proper component + const auto evse_id = error.origin.mapping.has_value() ? error.origin.mapping.value().evse : 0; + if (evse_id == 0) { + // component is ChargingStation + event_data.component = {CHARGING_STATION_COMPONENT_NAME}; // TODO: use origin of error for mapping to component? + } else if (not error.origin.mapping.value().connector.has_value()) { + // component is EVSE + ocpp::v201::EVSE evse = {evse_id}; + event_data.component = {EVSE_COMPONENT_NAME, std::nullopt, evse}; + } else { + // component is Connector + ocpp::v201::EVSE evse = {evse_id, std::nullopt, error.origin.mapping.value().connector.value()}; + event_data.component = {CONNECTOR_COMPONENT_NAME, std::nullopt, evse}; + } + event_data.variable = {PROBLEM_VARIABLE_NAME}; // TODO: use type of error for mapping to variable? + return event_data; +} +}; // namespace module + +#endif // OCPP201_ERROR_HANDLING_HPP