Skip to content

Commit

Permalink
Update API state and error handling to new error framwork (#496)
Browse files Browse the repository at this point in the history
Update API state and error handling to new error framwork

Now report multiple errors on the API interface and raises and clear individual errors.
Breaking change if you use the state as "Error" and "PermanentFault" have been removed 
from the state enum on the API.
Now the charger is in fault if there are active errors/permanent faults set independent of the state.

Signed-off-by: Cornelius Claussen <[email protected]>
  • Loading branch information
corneliusclaussen authored Jan 16, 2024
1 parent f1a0a9f commit 903d504
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 103 deletions.
138 changes: 92 additions & 46 deletions modules/API/API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace module {
static const auto NOTIFICATION_PERIOD = std::chrono::seconds(1);

SessionInfo::SessionInfo() :
state("Unknown"),
state(State::Unknown),
start_energy_import_wh(0),
end_energy_import_wh(0),
latest_total_w(0),
Expand All @@ -19,18 +19,19 @@ SessionInfo::SessionInfo() :
this->end_time_point = this->start_time_point;
}

bool SessionInfo::is_state_charging(const std::string& current_state) {
if (current_state == "AuthRequired" || current_state == "Charging" || current_state == "ChargingPausedEV" ||
current_state == "ChargingPausedEVSE") {
bool SessionInfo::is_state_charging(const SessionInfo::State current_state) {
if (current_state == State::AuthRequired || current_state == State::Charging ||
current_state == State::ChargingPausedEV || current_state == State::ChargingPausedEVSE) {
return true;
}
return false;
}

void SessionInfo::reset() {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->state = "Unknown";
this->state_info = "";
this->state = State::Unknown;
this->active_permanent_faults.clear();
this->active_errors.clear();
this->start_energy_import_wh = 0;
this->end_energy_import_wh = 0;
this->start_energy_export_wh = 0;
Expand Down Expand Up @@ -65,43 +66,80 @@ types::energy::ExternalLimits get_external_limits(const std::string& data, bool
return external_limits;
}

void SessionInfo::update_state(const std::string& event, const std::string& state_info) {
static void remove_error_from_list(std::vector<module::SessionInfo::Error>& list, const std::string& error_type) {
list.erase(std::remove_if(list.begin(), list.end(),
[error_type](const module::SessionInfo::Error& err) { return err.type == error_type; }),
list.end());
}

void SessionInfo::update_state(const types::evse_manager::SessionEventEnum event, const SessionInfo::Error& error) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
using Event = types::evse_manager::SessionEventEnum;

if (event == Event::Enabled) {
this->state = State::Unplugged;
} else if (event == Event::Disabled) {
this->state = State::Disabled;
} else if (event == Event::SessionStarted) {
this->state = State::Preparing;
} else if (event == Event::ReservationStart) {
this->state = State::Reserved;
} else if (event == Event::ReservationEnd) {
this->state = State::Unplugged;
} else if (event == Event::AuthRequired) {
this->state = State::AuthRequired;
} else if (event == Event::WaitingForEnergy) {
this->state = State::WaitingForEnergy;
} else if (event == Event::TransactionStarted) {
this->state = State::Preparing;
} else if (event == Event::ChargingPausedEV) {
this->state = State::ChargingPausedEV;
} else if (event == Event::ChargingPausedEVSE) {
this->state = State::ChargingPausedEVSE;
} else if (event == Event::ChargingStarted) {
this->state = State::Charging;
} else if (event == Event::ChargingResumed) {
this->state = State::Charging;
} else if (event == Event::TransactionFinished) {
this->state = State::Finished;
} else if (event == Event::SessionFinished) {
this->state = State::Unplugged;
} else if (event == Event::PermanentFault) {
this->active_permanent_faults.push_back(error);
} else if (event == Event::Error) {
this->active_errors.push_back(error);
} else if (event == Event::PermanentFaultCleared or event == Event::ErrorCleared) {
remove_error_from_list(this->active_permanent_faults, error.type);
} else if (event == Event::AllErrorsCleared) {
this->active_permanent_faults.clear();
this->active_errors.clear();
}
}

this->state_info = state_info;
if (event == "Enabled") {
this->state = "Unplugged";
} else if (event == "Disabled") {
this->state = "Disabled";
} else if (event == "SessionStarted") {
this->state = "Preparing";
} else if (event == "ReservationStart") {
this->state = "Reserved";
} else if (event == "ReservationEnd") {
this->state = "Unplugged";
} else if (event == "AuthRequired") {
this->state = "AuthRequired";
} else if (event == "WaitingForEnergy") {
this->state = "WaitingForEnergy";
} else if (event == "TransactionStarted") {
this->state = "Preparing";
} else if (event == "ChargingPausedEV") {
this->state = "ChargingPausedEV";
} else if (event == "ChargingPausedEVSE") {
this->state = "ChargingPausedEVSE";
} else if (event == "ChargingStarted") {
this->state = "Charging";
} else if (event == "ChargingResumed") {
this->state = "Charging";
} else if (event == "TransactionFinished") {
this->state = "Finished";
} else if (event == "SessionFinished") {
this->state = "Unplugged";
} else if (event == "Error") {
this->state = "Error";
} else if (event == "PermanentFault") {
this->state = "PermanentFault";
std::string SessionInfo::state_to_string(SessionInfo::State s) {
switch (s) {
case SessionInfo::State::Unplugged:
return "Unplugged";
case SessionInfo::State::Disabled:
return "Disabled";
case SessionInfo::State::Preparing:
return "Preparing";
case SessionInfo::State::Reserved:
return "Reserved";
case SessionInfo::State::AuthRequired:
return "AuthRequired";
case SessionInfo::State::WaitingForEnergy:
return "WaitingForEnergy";
case SessionInfo::State::ChargingPausedEV:
return "ChargingPausedEV";
case SessionInfo::State::ChargingPausedEVSE:
return "ChargingPausedEVSE";
case SessionInfo::State::Charging:
return "Charging";
case SessionInfo::State::Finished:
return "Finished";
}
return "Unknown";
}

void SessionInfo::set_start_energy_import_wh(int32_t start_energy_import_wh) {
Expand Down Expand Up @@ -152,6 +190,10 @@ void SessionInfo::set_latest_total_w(double latest_total_w) {
this->latest_total_w = latest_total_w;
}

static void to_json(json& j, const SessionInfo::Error& e) {
j = json{{"type", e.type}, {"description", e.description}, {"severity", e.severity}};
}

SessionInfo::operator std::string() {
std::lock_guard<std::mutex> lock(this->session_info_mutex);

Expand All @@ -165,8 +207,9 @@ SessionInfo::operator std::string() {
auto charging_duration_s =
std::chrono::duration_cast<std::chrono::seconds>(this->end_time_point - this->start_time_point);

json session_info = json::object({{"state", this->state},
{"state_info", this->state_info},
json session_info = json::object({{"state", state_to_string(this->state)},
{"active_permanent_faults", this->active_permanent_faults},
{"active_errors", this->active_errors},
{"charged_energy_wh", charged_energy_wh},
{"discharged_energy_wh", discharged_energy_wh},
{"latest_total_w", this->latest_total_w},
Expand Down Expand Up @@ -252,12 +295,15 @@ void API::init() {

evse->subscribe_session_event(
[this, var_session_info, var_logging_path, &session_info](types::evse_manager::SessionEvent session_event) {
auto event = types::evse_manager::session_event_enum_to_string(session_event.event);
std::string state_info = "";
SessionInfo::Error error;
if (session_event.error.has_value()) {
state_info = types::evse_manager::error_enum_to_string(session_event.error.value().error_code);
error.type = types::evse_manager::error_enum_to_string(session_event.error.value().error_code);
error.description = session_event.error.value().error_description;
error.severity =
types::evse_manager::error_severity_to_string(session_event.error.value().error_severity);
}
session_info->update_state(event, state_info);

session_info->update_state(session_event.event, error);

if (session_event.event == types::evse_manager::SessionEventEnum::SessionStarted) {
if (session_event.session_started.has_value()) {
Expand Down
54 changes: 38 additions & 16 deletions modules/API/API.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,22 @@ namespace module {
class LimitDecimalPlaces;

class SessionInfo {
private:
std::mutex session_info_mutex;

std::string state; ///< Latest state of the EVSE
std::string state_info; ///< Additional information of this state
int32_t start_energy_import_wh; ///< Energy reading (import) at the beginning of this charging session in Wh
int32_t end_energy_import_wh; ///< Energy reading (import) at the end of this charging session in Wh
int32_t start_energy_export_wh; ///< Energy reading (export) at the beginning of this charging session in Wh
int32_t end_energy_export_wh; ///< Energy reading (export) at the end of this charging session in Wh
std::chrono::time_point<date::utc_clock> start_time_point; ///< Start of the charging session
std::chrono::time_point<date::utc_clock> end_time_point; ///< End of the charging session
double latest_total_w; ///< Latest total power reading in W

bool is_state_charging(const std::string& current_state);

public:
SessionInfo();

struct Error {
std::string type;
std::string description;
std::string severity;
};

bool start_energy_export_wh_was_set{
false}; ///< Indicate if start export energy value (optional) has been received or not
bool end_energy_export_wh_was_set{
false}; ///< Indicate if end export energy value (optional) has been received or not

void reset();
void update_state(const std::string& event, const std::string& state_info);
void update_state(const types::evse_manager::SessionEventEnum event, const SessionInfo::Error& error);
void set_start_energy_import_wh(int32_t start_energy_import_wh);
void set_end_energy_import_wh(int32_t end_energy_import_wh);
void set_latest_energy_import_wh(int32_t latest_energy_wh);
Expand All @@ -69,6 +60,37 @@ class SessionInfo {

/// \brief Converts this struct into a serialized json object
operator std::string();

private:
std::mutex session_info_mutex;

std::vector<Error> active_permanent_faults; ///< Array of currently active permanent faults that prevent charging
std::vector<Error> active_errors; ///< Array of currently active errors that do not prevent charging
int32_t start_energy_import_wh; ///< Energy reading (import) at the beginning of this charging session in Wh
int32_t end_energy_import_wh; ///< Energy reading (import) at the end of this charging session in Wh
int32_t start_energy_export_wh; ///< Energy reading (export) at the beginning of this charging session in Wh
int32_t end_energy_export_wh; ///< Energy reading (export) at the end of this charging session in Wh
std::chrono::time_point<date::utc_clock> start_time_point; ///< Start of the charging session
std::chrono::time_point<date::utc_clock> end_time_point; ///< End of the charging session
double latest_total_w; ///< Latest total power reading in W

enum class State {
Unknown,
Unplugged,
Disabled,
Preparing,
Reserved,
AuthRequired,
WaitingForEnergy,
ChargingPausedEV,
ChargingPausedEVSE,
Charging,
Finished
} state;

bool is_state_charging(const SessionInfo::State current_state);

std::string state_to_string(State s);
};
} // namespace module
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
Expand Down
77 changes: 64 additions & 13 deletions modules/API/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,37 @@ This variable is published every second and contains a json object with informat
"discharged_energy_wh": 0,
"latest_total_w": 0.0,
"state": "Unplugged",
"state_info": ""
"active_permanent_faults": [],
"active_errors": []
}
```

Example with permanent faults being active:

```json
{
"active_errors": [],
"active_permanent_faults": [
{
"description": "The control pilot voltage is out of range.",
"severity": "High",
"type": "MREC14PilotFault"
},
{
"description": "The vehicle is in an invalid mode for charging (Reported by IEC stack)",
"severity": "High",
"type": "MREC10InvalidVehicleMode"
}
],
"charged_energy_wh": 0,
"charging_duration_s": 0,
"datetime": "2024-01-15T14:58:15.172Z",
"discharged_energy_wh": 0,
"latest_total_w": 0,
"state": "Preparing"
}
```

- **charged_energy_wh** contains the charged energy in Wh
- **charging_duration_s** contains the duration of the current charging session in seconds
- **datetime** contains a string representation of the current UTC datetime in RFC3339 format
Expand All @@ -64,20 +91,44 @@ This variable is published every second and contains a json object with informat
- ChargingPausedEV
- ChargingPausedEVSE
- Finished
- Error
- PermanentFault


- **state_info** contains additional information for the current state, at the moment this is only set to a meaningful value in the Error state. Here it can have the following values:
- Car
- CarDiodeFault
- Relais
- RCD
- **active_permanent_faults** array of all active errors that are permanent faults (i.e. that block charging). If anything is set here it should be shown as an error to the user instead of showing the current state:
- RCD_Selftest
- RCD_DC
- RCD_AC
- VendorError
- VendorWarning
- ConnectorLockCapNotCharged
- ConnectorLockUnexpectedOpen
- ConnectorLockUnexpectedClose
- ConnectorLockFailedLock
- ConnectorLockFailedUnlock
- MREC1ConnectorLockFailure
- MREC2GroundFailure
- MREC3HighTemperature
- MREC4OverCurrentFailure
- MREC5OverVoltage
- MREC6UnderVoltage
- MREC8EmergencyStop
- MREC10InvalidVehicleMode
- MREC14PilotFault
- MREC15PowerLoss
- MREC17EVSEContactorFault
- MREC18CableOverTempDerate
- MREC19CableOverTempStop
- MREC20PartialInsertion
- MREC23ProximityFault
- MREC24ConnectorVoltageHigh
- MREC25BrokenLatch
- MREC26CutCable
- DiodeFault
- VentilationNotAvailable
- OverCurrent
- Internal
- SLAC
- HLC
- BrownOut
- EnergyManagement
- PermanentFault
- PowermeterTransactionStartFailed

- **active_errors** array of all active errors that do not block charging. This could be shown to the user but the current state should still be shown as it does not interfere with charging. The enum is the same as for active_permanent_faults.

### everest_api/evse_manager/var/limits
This variable is published every second and contains a json object with information relating to the current limits of this EVSE.
Expand Down
2 changes: 1 addition & 1 deletion modules/EvseManager/Charger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Charger::Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_
hlc_use_5percent_current_session = false;

// Register callbacks for errors/error clearings
error_handling->signal_error.connect([this](const types::evse_manager::ErrorEnum e, const bool prevent_charging) {
error_handling->signal_error.connect([this](const types::evse_manager::Error e, const bool prevent_charging) {
std::scoped_lock lock(stateMutex);
if (prevent_charging) {
error_prevent_charging_flag = true;
Expand Down
Loading

0 comments on commit 903d504

Please sign in to comment.