From bfb8e304124e45ad87c90a15cd27cf8b1496f17b Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Tue, 23 Jan 2024 09:29:34 +0100 Subject: [PATCH] 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: Cornelius Claussen --- interfaces/evse_manager.yaml | 26 ++-- modules/API/API.cpp | 101 ++++++++++--- modules/API/API.hpp | 6 + modules/API/README.md | 111 ++++++++++++-- modules/EvseManager/Charger.cpp | 138 +++++++++++++++--- modules/EvseManager/Charger.hpp | 10 +- modules/EvseManager/EvseManager.cpp | 3 +- modules/EvseManager/evse/evse_managerImpl.cpp | 22 +-- modules/EvseManager/evse/evse_managerImpl.hpp | 4 +- modules/OCPP/OCPP.cpp | 9 +- modules/OCPP201/OCPP201.cpp | 8 +- types/evse_manager.yaml | 40 ++++- 12 files changed, 397 insertions(+), 81 deletions(-) diff --git a/interfaces/evse_manager.yaml b/interfaces/evse_manager.yaml index 82225b19ff..262d10d1d1 100644 --- a/interfaces/evse_manager.yaml +++ b/interfaces/evse_manager.yaml @@ -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 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: >- diff --git a/modules/API/API.cpp b/modules/API/API.cpp index 19d94c32ef..d7300bc71f 100644 --- a/modules/API/API.cpp +++ b/modules/API/API.cpp @@ -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 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}}; } @@ -240,14 +248,23 @@ SessionInfo::operator std::string() { auto charging_duration_s = std::chrono::duration_cast(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}, @@ -346,6 +363,13 @@ void API::init() { session_info->update_state(session_event.event, error); + if (session_event.source.has_value()) { + session_info->set_enable_disable_source( + types::evse_manager::enable_source_to_string(session_event.source.value().enable_source), + types::evse_manager::enable_state_to_string(session_event.source.value().enable_state), + session_event.source.value().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(); @@ -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"; diff --git a/modules/API/API.hpp b/modules/API/API.hpp index 1e88546607..75f5db7014 100644 --- a/modules/API/API.hpp +++ b/modules/API/API.hpp @@ -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(); @@ -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 diff --git a/modules/API/README.md b/modules/API/README.md index 1c90e82b9e..996030b057 100644 --- a/modules/API/README.md +++ b/modules/API/README.md @@ -46,12 +46,19 @@ This variable is published every second and contains a json object with informat "state": "Unplugged", "active_permanent_faults": [], "active_errors": [], + "active_enable_disable_source": { + "source": "Unspecified", + "state": "Enable", + "priority": 5000 + }, "uk_random_delay": { "remaining_s": 34, "current_limit_after_delay_A": 16.0, "current_limit_during_delay_A": 0.0, "start_time": "2024-02-28T14:11:11.129Z" - } + }, + "last_enable_disable_source": "Unspecified", + "enable_disable_allow_others_to_modify": true } ``` @@ -77,7 +84,9 @@ Example with permanent faults being active: "datetime": "2024-01-15T14:58:15.172Z", "discharged_energy_wh": 0, "latest_total_w": 0, - "state": "Preparing" + "state": "Preparing", + "last_enable_disable_source": "Unspecified", + "enable_disable_other_sources_may_modify": true } ``` @@ -135,10 +144,13 @@ Example with permanent faults being active: - 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. +- **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. +This variable is published every second and contains a json object with information +relating to the current limits of this EVSE. ```json { "max_current": 16.0, @@ -161,7 +173,8 @@ This variable is published every second and contains telemetry of the EVSE. ``` ### everest_api/evse_manager/var/powermeter -This variable is published every second and contains powermeter information of the EVSE. +This variable is published every second and contains powermeter information +of the EVSE. ```json { "current_A": { @@ -201,16 +214,96 @@ This variable is published every second and contains powermeter information of t ## Periodically published variables for OCPP ### everest_api/ocpp/var/connection_status -This variable is published every second and contains the connection status of the OCPP module. -If the OCPP module has not yet published its "is_connected" status or no OCPP module is configured "unknown" is published. Otherwise "connected" or "disconnected" are published. +This variable is published every second and contains the connection +status of the OCPP module. +If the OCPP module has not yet published its "is_connected" status or +no OCPP module is configured "unknown" is published. Otherwise "connected" +or "disconnected" are published. ## Commands and variables published in response +### everest_api/evse_manager/cmd/enable_disable +Command to enable or disable a connector on the EVSE. The payload should be +the following json: + +```json + { + "connector_id": 0, + "source": "LocalAPI", + "state": "Enable", + "priority": 42 + } +``` +connector_id is a positive integer identifying the connector that should be +enabled. If the connector_id is 0 the whole EVSE is enabled. + +The source is an enum of the following source types : + + - Unspecified + - LocalAPI + - LocalKeyLock + - ServiceTechnician + - RemoteKeyLock + - MobileApp + - FirmwareUpdate + - CSMS + +The state can be either "enable", "disable", or "unassigned". + +"enable" and "disable" enforce the state to be enable/disable, while unassigned means +that the source does not care about the state and other sources may decide. + +Each call to this command will update an internal table that looks like this: + +| Source | State | Priority | +| ------------ | ---------- | -------- | +| Unspecified | unassigned | 10000 | +| LocalAPI | disable | 42 | +| LocalKeyLock | enable | 0 | + +Evaluation will be done based on priorities. 0 is the highest priority, +10000 the lowest, so in this example the connector will be enabled regardless +of what other sources say. +Imagine LocalKeyLock sends a "unassigned, prio 0", the table will then look like this: + +| Source | State | Priority | +| ------------ | ---------- | -------- | +| Unspecified | unassigned | 10000 | +| LocalAPI | disable | 42 | +| LocalKeyLock | unassigned | 0 | + +So now the connector will be disabled, because the second highest priority (42) sets it to disabled. + +If all sources are unassigned, the connector is enabled. +If two sources have the same priority, "disabled" has priority over "enabled". + ### everest_api/evse_manager/cmd/enable -Command to enable a connector on the EVSE. They payload should be a positive integer identifying the connector that should be enabled. If the payload is 0 the whole EVSE is enabled. +Legacy command to enable a connector on the EVSE kept for compatibility reasons. +They payload should be a positive integer identifying the connector that should be enabled. +If the payload is 0 the whole EVSE is enabled. +It will actually call the following command on everest_api/evse_manager/cmd/enable_enable: +```json + { + "connector_id": 1, + "source": "LocalAPI", + "state": "enable", + "priority": 0 + } +``` ### everest_api/evse_manager/cmd/disable -Command to disable a connector on the EVSE. They payload should be a positive integer identifying the connector that should be disabled. If the payload is 0 the whole EVSE is disabled. +Legacy command to enable a connector on the EVSE kept for compatibility reasons. +Command to disable a connector on the EVSE. They payload should be a positive integer +identifying the connector that should be disabled. If the payload is 0 the whole EVSE is disabled. +It will actually call the following command on everest_api/evse_manager/cmd/enable_disable: +```json + { + "connector_id": 1, + "source": "LocalAPI", + "state": "disable", + "priority": 0 + } +``` ### everest_api/evse_manager/cmd/pause_charging If any arbitrary payload is published to this topic charging will be paused by the EVSE. diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 5e83a11f76..b0313f185d 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -1145,31 +1145,131 @@ bool Charger::deauthorize_internal() { return false; } -bool Charger::disable(int connector_id) { +bool Charger::enable_disable(int connector_id, const types::evse_manager::EnableDisableSource& source) { Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_disable); - if (connector_id not_eq 0) { - shared_context.connector_enabled = false; + + // insert the new request into the table + bool replaced = false; + for (auto& entry : enable_disable_source_table) { + if (entry.enable_source == source.enable_source) { + // replace this entry as it is an update from the same source + entry = source; + replaced = true; + } } - shared_context.current_state = EvseState::Disabled; - signal_simple_event(types::evse_manager::SessionEventEnum::Disabled); - return true; -} -bool Charger::enable(int connector_id) { - Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_enable); + if (not replaced) { + // Add to the table + enable_disable_source_table.push_back(source); + } + + // Find out if we are actually enabled or disabled at the moment + bool is_enabled = parse_enable_disable_source_table(); + + if (is_enabled) { + if (connector_id not_eq 0) { + shared_context.connector_enabled = true; + } - if (connector_id not_eq 0) { - shared_context.connector_enabled = true; + signal_simple_event(types::evse_manager::SessionEventEnum::Enabled); + if (shared_context.current_state == EvseState::Disabled) { + if (shared_context.connector_enabled) { + shared_context.current_state = EvseState::Idle; + } + return true; + } + } else { + if (connector_id not_eq 0) { + shared_context.connector_enabled = false; + } + shared_context.current_state = EvseState::Disabled; + signal_simple_event(types::evse_manager::SessionEventEnum::Disabled); } + return is_enabled; +} - signal_simple_event(types::evse_manager::SessionEventEnum::Enabled); - if (shared_context.current_state == EvseState::Disabled) { - if (shared_context.connector_enabled) { - shared_context.current_state = EvseState::Idle; +bool Charger::parse_enable_disable_source_table() { + /* Decide if we are enabled or disabled from the current table. + The logic is as follows: + + The source is an enum of the following source types : + + - Unspecified + - LocalAPI + - LocalKeyLock + - ServiceTechnician + - RemoteKeyLock + - MobileApp + - FirmwareUpdate + - CSMS + + The state can be either "enable", "disable", or "unassigned". + + "enable" and "disable" enforce the state to be enable/disable, while unassigned means that the source does not care + about the state and other sources may decide. + + Each call to this command will update an internal table that looks like this: + + | Source | State | Priority | + | ------------ | ------------- | -------- | + | Unspecified | unassigned | 10000 | + | LocalAPI | disable | 42 | + | LocalKeyLock | enable | 0 | + + Evaluation will be done based on priorities. 0 is the highest priority, 10000 the lowest, so in this example the + connector will be enabled regardless of what other sources say. Imagine LocalKeyLock sends a "unassigned, prio 0", + the table will then look like this: + + | Source | State | Priority | + | ------------ | ------------- | -------- | + | Unspecified | unassigned | 10000 | + | LocalAPI | disable | 42 | + | LocalKeyLock | unassigned | 0 | + + So now the connector will be disabled, because the second highest priority (42) sets it to disabled. + + If all sources are unassigned, the connector is enabled. + If two sources have the same priority, "disabled" has priority over "enabled". + */ + + bool is_enabled = true; // By default, it is enabled. + types::evse_manager::EnableDisableSource winning_source{types::evse_manager::Enable_source::Unspecified, + types::evse_manager::Enable_state::Unassigned, 10000}; + + // Walk through table + for (const auto& entry : enable_disable_source_table) { + // Ignore unassigned entries + if (entry.enable_state == types::evse_manager::Enable_state::Unassigned) { + continue; + } + + if (winning_source.enable_state == types::evse_manager::Enable_state::Unassigned) { + // Use the first entry that is not Unassigned + winning_source = entry; + if (entry.enable_state == types::evse_manager::Enable_state::Disable) { + is_enabled = false; + } else { + is_enabled = true; + } + } else if (entry.enable_priority == winning_source.enable_priority) { + // At the same priority, disable has higher priority then enable + if (entry.enable_state == types::evse_manager::Enable_state::Disable) { + is_enabled = false; + winning_source = entry; + } + + } else if (entry.enable_priority < winning_source.enable_priority) { + winning_source = entry; + if (entry.enable_state == types::evse_manager::Enable_state::Disable) { + is_enabled = false; + } else { + is_enabled = true; + } } - return true; } - return false; + + active_enable_disable_source = winning_source; + return is_enabled; } void Charger::set_faulted() { @@ -1504,4 +1604,8 @@ void Charger::clear_errors_on_unplug() { error_handling->clear_powermeter_transaction_start_failed_error(); } +types::evse_manager::EnableDisableSource Charger::get_last_enable_disable_source() { + return active_enable_disable_source; +} + } // namespace module diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index 3aaaecb8f3..65d6110b56 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -93,8 +93,7 @@ class Charger { bool ac_hlc_enabled, bool ac_hlc_use_5percent, bool ac_enforce_hlc, bool ac_with_soc_timeout, float soft_over_current_tolerance_percent, float soft_over_current_measurement_noise_A); - bool enable(int connector_id); - bool disable(int connector_id); + bool enable_disable(int connector_id, const types::evse_manager::EnableDisableSource& source); void set_faulted(); void set_hlc_error(); @@ -175,6 +174,8 @@ class Charger { bool errors_prevent_charging(); + types::evse_manager::EnableDisableSource get_last_enable_disable_source(); + private: bool errors_prevent_charging_internal(); float get_max_current_internal(); @@ -321,6 +322,11 @@ class Charger { // 4 seconds according to table 3 of ISO15118-3 static constexpr int T_STEP_EF = 4000; static constexpr int SOFT_OVER_CURRENT_TIMEOUT = 7000; + + types::evse_manager::EnableDisableSource active_enable_disable_source{ + types::evse_manager::Enable_source::Unspecified, types::evse_manager::Enable_state::Unassigned, 10000}; + std::vector enable_disable_source_table; + bool parse_enable_disable_source_table(); }; } // namespace module diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 66f9f569d9..3bf1934f21 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -867,7 +867,8 @@ void EvseManager::ready() { void EvseManager::ready_to_start_charging() { timepoint_ready_for_charging = std::chrono::steady_clock::now(); charger->run(); - charger->enable(0); + charger->enable_disable( + 0, {types::evse_manager::Enable_source::Unspecified, types::evse_manager::Enable_state::Enable, 10000}); this->p_evse->publish_ready(true); EVLOG_info << fmt::format(fmt::emphasis::bold | fg(fmt::terminal_color::green), diff --git a/modules/EvseManager/evse/evse_managerImpl.cpp b/modules/EvseManager/evse/evse_managerImpl.cpp index cb04b47b61..ca2320c939 100644 --- a/modules/EvseManager/evse/evse_managerImpl.cpp +++ b/modules/EvseManager/evse/evse_managerImpl.cpp @@ -40,10 +40,16 @@ void evse_managerImpl::init() { [&charger = mod->charger, this](std::string data) { mod->updateLocalMaxWattLimit(std::stof(data)); }); mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/enable", mod->config.connector_id), - [&charger = mod->charger](const std::string& data) { charger->enable(0); }); + [&charger = mod->charger](const std::string& data) { + charger->enable_disable(0, {types::evse_manager::Enable_source::LocalAPI, + types::evse_manager::Enable_state::Enable, 100}); + }); mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/disable", mod->config.connector_id), - [&charger = mod->charger](const std::string& data) { charger->disable(0); }); + [&charger = mod->charger](const std::string& data) { + charger->enable_disable(0, {types::evse_manager::Enable_source::LocalAPI, + types::evse_manager::Enable_state::Disable, 100}); + }); mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/faulted", mod->config.connector_id), [&charger = mod->charger](const std::string& data) { charger->set_faulted(); }); @@ -303,6 +309,9 @@ void evse_managerImpl::ready() { if (connector_status_changed) { se.connector_id = 1; } + + // Add source information (Who initiated this state change) + se.source = mod->charger->get_last_enable_disable_source(); } se.uuid = session_uuid; @@ -349,9 +358,9 @@ types::evse_manager::Evse evse_managerImpl::handle_get_evse() { return evse; } -bool evse_managerImpl::handle_enable(int& connector_id) { +bool evse_managerImpl::handle_enable_disable(int& connector_id, types::evse_manager::EnableDisableSource& cmd_source) { connector_status_changed = connector_id != 0; - return mod->charger->enable(connector_id); + return mod->charger->enable_disable(connector_id, cmd_source); }; void evse_managerImpl::handle_authorize_response(types::authorization::ProvidedIdToken& provided_token, @@ -389,11 +398,6 @@ void evse_managerImpl::handle_cancel_reservation() { mod->cancel_reservation(true); }; -bool evse_managerImpl::handle_disable(int& connector_id) { - connector_status_changed = connector_id != 0; - return mod->charger->disable(connector_id); -}; - void evse_managerImpl::handle_set_faulted() { mod->charger->set_faulted(); }; diff --git a/modules/EvseManager/evse/evse_managerImpl.hpp b/modules/EvseManager/evse/evse_managerImpl.hpp index a9af2dfa7c..d979eed347 100644 --- a/modules/EvseManager/evse/evse_managerImpl.hpp +++ b/modules/EvseManager/evse/evse_managerImpl.hpp @@ -35,8 +35,8 @@ class evse_managerImpl : public evse_managerImplBase { protected: // command handler functions (virtual) virtual types::evse_manager::Evse handle_get_evse() override; - virtual bool handle_enable(int& connector_id) override; - virtual bool handle_disable(int& connector_id) override; + virtual bool handle_enable_disable(int& connector_id, + types::evse_manager::EnableDisableSource& cmd_source) override; virtual void handle_authorize_response(types::authorization::ProvidedIdToken& provided_token, types::authorization::ValidationResult& validation_result) override; virtual void handle_withdraw_authorization() override; diff --git a/modules/OCPP/OCPP.cpp b/modules/OCPP/OCPP.cpp index 316e9af3de..c4b4571c39 100644 --- a/modules/OCPP/OCPP.cpp +++ b/modules/OCPP/OCPP.cpp @@ -79,6 +79,7 @@ static ErrorInfo get_error_info(const std::optional case types::evse_manager::ErrorEnum::EnergyManagement: case types::evse_manager::ErrorEnum::PermanentFault: case types::evse_manager::ErrorEnum::PowermeterTransactionStartFailed: + default: return {ocpp::v16::ChargePointErrorCode::InternalError, types::evse_manager::error_enum_to_string(error_code)}; } @@ -624,7 +625,9 @@ void OCPP::ready() { this->charge_point->register_disable_evse_callback([this](int32_t connector) { if (this->connector_evse_index_map.count(connector)) { - return this->r_evse_manager.at(this->connector_evse_index_map.at(connector))->call_disable(0); + return this->r_evse_manager.at(this->connector_evse_index_map.at(connector)) + ->call_enable_disable( + 0, {types::evse_manager::Enable_source::CSMS, types::evse_manager::Enable_state::Disable, 5000}); } else { return false; } @@ -632,7 +635,9 @@ void OCPP::ready() { this->charge_point->register_enable_evse_callback([this](int32_t connector) { if (this->connector_evse_index_map.count(connector)) { - return this->r_evse_manager.at(this->connector_evse_index_map.at(connector))->call_enable(0); + return this->r_evse_manager.at(this->connector_evse_index_map.at(connector)) + ->call_enable_disable( + 0, {types::evse_manager::Enable_source::CSMS, types::evse_manager::Enable_state::Enable, 5000}); } else { return false; } diff --git a/modules/OCPP201/OCPP201.cpp b/modules/OCPP201/OCPP201.cpp index 391d9a7c4f..070854993b 100644 --- a/modules/OCPP201/OCPP201.cpp +++ b/modules/OCPP201/OCPP201.cpp @@ -164,11 +164,15 @@ void OCPP201::ready() { callbacks.connector_effective_operative_status_changed_callback = [this](const int32_t evse_id, const int32_t connector_id, const ocpp::v201::OperationalStatusEnum new_status) { if (new_status == ocpp::v201::OperationalStatusEnum::Operative) { - if (this->r_evse_manager.at(evse_id - 1)->call_enable(connector_id)) { + if (this->r_evse_manager.at(evse_id - 1) + ->call_enable_disable(connector_id, {types::evse_manager::Enable_source::CSMS, + types::evse_manager::Enable_state::Enable, 5000})) { this->charge_point->on_enabled(evse_id, connector_id); } } else { - if (this->r_evse_manager.at(evse_id - 1)->call_disable(connector_id)) { + if (this->r_evse_manager.at(evse_id - 1) + ->call_enable_disable(connector_id, {types::evse_manager::Enable_source::CSMS, + types::evse_manager::Enable_state::Disable, 5000})) { this->charge_point->on_unavailable(evse_id, connector_id); } } diff --git a/types/evse_manager.yaml b/types/evse_manager.yaml index 5d1e35c51b..ac35df79d2 100644 --- a/types/evse_manager.yaml +++ b/types/evse_manager.yaml @@ -293,6 +293,39 @@ types: vendor_error: description: The error code of the vendor type: string + EnableDisableSource: + description: >- + Source of a Enable or Disable command/event + type: object + required: + - enable_source + - enable_state + - enable_priority + properties: + enable_source: + description: Specifies the source + type: string + enum: + - Unspecified + - LocalAPI + - LocalKeyLock + - ServiceTechnician + - RemoteKeyLock + - MobileApp + - FirmwareUpdate + - CSMS + enable_state: + description: Specifies the state for this entry + type: string + enum: + - Unassigned + - Disable + - Enable + enable_priority: + description: Priority of this entry. The highest priority is 0. + type: integer + minimum: 0 + maximum: 10000 SessionEvent: description: Emits all events related to sessions type: object @@ -339,6 +372,11 @@ types: description: Details on error type type: object $ref: /evse_manager#/Error + source: + description: >- + Additional data for Enabled/Disabled events. Specifies the source of the command that changed the state. + type: object + $ref: /evse_manager#/EnableDisableSource Limits: description: Limits of this EVSE type: object @@ -505,4 +543,4 @@ types: type: object $ref: /evse_manager#/Connector minItems: 1 - maxItems: 128 + maxItems: 128 \ No newline at end of file