Skip to content

Commit

Permalink
Error Reporting via OCPP201 (#824)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
Pietfried authored and hikinggrass committed Oct 25, 2024
1 parent f6ccd75 commit ba0871b
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 5 deletions.
23 changes: 22 additions & 1 deletion modules/OCPP201/OCPP201.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <websocketpp_utils/uri.hpp>

#include <conversions.hpp>
#include <error_handling.hpp>
#include <evse_security_ocpp.hpp>
#include <ocpp_conversions.hpp>

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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++;
}
Expand Down
1 change: 1 addition & 0 deletions modules/OCPP201/OCPP201.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class OCPP201 : public Everest::ModuleBase {
// key represents evse_id, value indicates if ready
std::map<int32_t, bool> evse_ready_map;
std::map<int32_t, std::optional<float>> 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;
Expand Down
43 changes: 39 additions & 4 deletions modules/OCPP201/doc.rst
Original file line number Diff line number Diff line change
@@ -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.
82 changes: 82 additions & 0 deletions modules/OCPP201/error_handling.hpp
Original file line number Diff line number Diff line change
@@ -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 <ocpp/v201/ocpp_types.hpp>
#include <utils/error.hpp>

#include <unordered_map>

namespace module {
const std::unordered_map<std::string, std::string> 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

0 comments on commit ba0871b

Please sign in to comment.