Skip to content

Commit

Permalink
Add source to enable/disable commands/events (#505)
Browse files Browse the repository at this point in the history
* Add new enable_disable commands to EvseManager

It replaces the old enable/disable commands and adds a source and
priority. See README of the API module for more details.

Compile time breaking change on the evse_manager interface.
All enable/disable calls need to be switched over to the new
enable_disable call, for everest-core this is done in this PR.

In some uses cases it is useful if e.g. a LocalAPI source disables
a connector that only the same local source can enable again.
Think of a service technician who has an app to disable the connector
locally. Then it should be prevented that it is re-enabled by the CSMS
until the service technician enables it again via the same app.
Using priorities this is quite flexible and a lot of use-cases can
be implemented without a change in EVerest code.

The API module also has a new enable_disable command. It also has
compatibility implementations for the old enable/disable commands.
---------

Signed-off-by: James Chapman <[email protected]>
Signed-off-by: Piet Gömpel <[email protected]>
Signed-off-by: pietfried <[email protected]>
Co-authored-by: James Chapman <[email protected]>
Co-authored-by: Piet Gömpel <[email protected]>
Co-authored-by: pietfried <[email protected]>
  • Loading branch information
4 people authored Jun 3, 2024
1 parent 177a8e6 commit 571f3d4
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 100 deletions.
26 changes: 9 additions & 17 deletions interfaces/evse_manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,21 @@ cmds:
description: Object that contains information of the EVSE including its connectors
type: object
$ref: /evse_manager#/Evse
enable:
description: Enables the evse. EVSE is available for charging after this operation
enable_disable:
description: Enables or disables the evse. Turns off PWM with error F. Charging is only possible if an EVSE is enabled.
arguments:
connector_id:
description: Specifies the ID of the connector to enable. If 0, the whole EVSE should be enabled
type: integer
cmd_source:
description: Source of the enable command
type: object
$ref: /evse_manager#/EnableDisableSource
result:
description: >-
Returns true if evse was enabled (or was enabled before), returns
false if enable failed e.g. due to permanent fault.
type: boolean
disable:
description: >-
Disables the evse. EVSE is not available for charging after this
operation
arguments:
connector_id:
description: Specifies the ID of the connector. If 0, the whole EVSE should be disabled
type: integer
result:
description: >-
Returns true if evse was disabled (or was disabled before), returns
false if it could not be disabled (i.e. due to communication error with hardware)
Returns true if evse is enabled after the command, false if it is disabled.
This may not be the same value as the request, since there may be a higher priority request
from another source that is actually deciding whether it is enabled or disabled.
type: boolean
authorize_response:
description: >-
Expand Down
101 changes: 82 additions & 19 deletions modules/API/API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ void SessionInfo::set_uk_random_delay_remaining(const types::uk_random_delay::Co
this->uk_random_delay_remaining = cd;
}

void SessionInfo::set_enable_disable_source(const std::string& active_source, const std::string& active_state,
const int active_priority) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->active_enable_disable_source = active_source;
this->active_enable_disable_state = active_state;
this->active_enable_disable_priority = active_priority;
}

static void to_json(json& j, const SessionInfo::Error& e) {
j = json{{"type", e.type}, {"description", e.description}, {"severity", e.severity}};
}
Expand All @@ -240,14 +248,23 @@ 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", 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},
{"charging_duration_s", charging_duration_s.count()},
{"datetime", Everest::Date::to_rfc3339(now)}});
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},
{"charging_duration_s", charging_duration_s.count()},
{"datetime", Everest::Date::to_rfc3339(now)},

});

json active_disable_enable = json::object({{"source", this->active_enable_disable_source},
{"state", this->active_enable_disable_state},
{"priority", this->active_enable_disable_priority}});
session_info["active_enable_disable_source"] = active_disable_enable;

if (uk_random_delay_remaining.countdown_s > 0) {
json random_delay =
json::object({{"remaining_s", uk_random_delay_remaining.countdown_s},
Expand Down Expand Up @@ -346,6 +363,13 @@ void API::init() {

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

if (session_event.source.has_value()) {
const auto source = session_event.source.value();
session_info->set_enable_disable_source(
types::evse_manager::enable_source_to_string(source.enable_source),
types::evse_manager::enable_state_to_string(source.enable_state), source.enable_priority);
}

if (session_event.event == types::evse_manager::SessionEventEnum::SessionStarted) {
if (session_event.session_started.has_value()) {
auto session_started = session_event.session_started.value();
Expand Down Expand Up @@ -388,34 +412,73 @@ void API::init() {
// API commands
std::string cmd_base = evse_base + "/cmd/";

std::string cmd_enable = cmd_base + "enable";
this->mqtt.subscribe(cmd_enable, [&evse](const std::string& data) {
std::string cmd_enable_disable = cmd_base + "enable_disable";
this->mqtt.subscribe(cmd_enable_disable, [&evse](const std::string& data) {
auto connector_id = 0;
types::evse_manager::EnableDisableSource enable_source{types::evse_manager::Enable_source::LocalAPI,
types::evse_manager::Enable_state::Enable, 100};

if (!data.empty()) {
try {
connector_id = std::stoi(data);
auto arg = json::parse(data);
if (arg.contains("connector_id")) {
connector_id = arg.at("connector_id");
}
if (arg.contains("source")) {
enable_source.enable_source = types::evse_manager::string_to_enable_source(arg.at("source"));
}
if (arg.contains("state")) {
enable_source.enable_state = types::evse_manager::string_to_enable_state(arg.at("state"));
}
if (arg.contains("priority")) {
enable_source.enable_priority = arg.at("priority");
}

} catch (const std::exception& e) {
EVLOG_error << "Could not parse connector id for enable connector, ignoring command, error: "
<< e.what();
return;
EVLOG_error << "enable: Cannot parse argument, command ignored: " << e.what();
}
} else {
EVLOG_error << "enable: No argument specified, ignoring command";
}
evse->call_enable(connector_id);
evse->call_enable_disable(connector_id, enable_source);
});

std::string cmd_disable = cmd_base + "disable";
this->mqtt.subscribe(cmd_disable, [&evse](const std::string& data) {
auto connector_id = 0;
types::evse_manager::EnableDisableSource enable_source{types::evse_manager::Enable_source::LocalAPI,
types::evse_manager::Enable_state::Disable, 100};

if (!data.empty()) {
try {
connector_id = std::stoi(data);
EVLOG_warning << "disable: Argument is an integer, using deprecated compatibility mode";
} catch (const std::exception& e) {
EVLOG_error << "disable: Cannot parse argument, ignoring command";
}
} else {
EVLOG_error << "disable: No argument specified, ignoring command";
}
evse->call_enable_disable(connector_id, enable_source);
});

std::string cmd_enable = cmd_base + "enable";
this->mqtt.subscribe(cmd_enable, [&evse](const std::string& data) {
auto connector_id = 0;
types::evse_manager::EnableDisableSource enable_source{types::evse_manager::Enable_source::LocalAPI,
types::evse_manager::Enable_state::Enable, 100};

if (!data.empty()) {
try {
connector_id = std::stoi(data);
EVLOG_warning << "disable: Argument is an integer, using deprecated compatibility mode";
} catch (const std::exception& e) {
EVLOG_error << "Could not parse connector id for disable connector, ignoring command, error: "
<< e.what();
return;
EVLOG_error << "disable: Cannot parse argument, ignoring command";
}
} else {
EVLOG_error << "disable: No argument specified, ignoring command";
}
evse->call_disable(connector_id);
evse->call_enable_disable(connector_id, enable_source);
});

std::string cmd_pause_charging = cmd_base + "pause_charging";
Expand Down
6 changes: 6 additions & 0 deletions modules/API/API.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class SessionInfo {
void set_latest_energy_export_wh(int32_t latest_export_energy_wh);
void set_latest_total_w(double latest_total_w);
void set_uk_random_delay_remaining(const types::uk_random_delay::CountDown& c);
void set_enable_disable_source(const std::string& active_source, const std::string& active_state,
const int active_priority);

/// \brief Converts this struct into a serialized json object
operator std::string();
Expand Down Expand Up @@ -95,6 +97,10 @@ class SessionInfo {
bool is_state_charging(const SessionInfo::State current_state);

std::string state_to_string(State s);

std::string active_enable_disable_source{"Unspecified"};
std::string active_enable_disable_state{"Enabled"};
int active_enable_disable_priority{0};
};
} // namespace module
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
Expand Down
Loading

0 comments on commit 571f3d4

Please sign in to comment.