diff --git a/modules/API/API.cpp b/modules/API/API.cpp index a2292e4d3..80a55f279 100644 --- a/modules/API/API.cpp +++ b/modules/API/API.cpp @@ -70,6 +70,36 @@ types::energy::ExternalLimits get_external_limits(const std::string& data, bool return external_limits; } +types::energy::ExternalLimits get_external_limits(int32_t phases, float amps) { + const auto timestamp = Everest::Date::to_rfc3339(date::utc_clock::now()); + types::energy::ExternalLimits external_limits; + types::energy::ScheduleReqEntry target_entry; + target_entry.timestamp = timestamp; + + types::energy::ScheduleReqEntry zero_entry; + zero_entry.timestamp = timestamp; + zero_entry.limits_to_leaves.total_power_W = 0; + + // check if phases are 1 or 3, otherwise throw an exception + const auto is_valid = (phases == 1 || phases == 3); + if (is_valid) { + target_entry.limits_to_leaves.ac_max_phase_count = phases; + target_entry.limits_to_leaves.ac_min_phase_count = phases; + target_entry.limits_to_leaves.ac_max_current_A = std::fabs(amps); + } else { + std::string error_msg = "Invalid phase count " + std::to_string(phases); + throw std::out_of_range(error_msg); + } + + if (amps > 0) { + external_limits.schedule_import.emplace(std::vector(1, target_entry)); + } else { + external_limits.schedule_export.emplace(std::vector(1, target_entry)); + external_limits.schedule_import.emplace(std::vector(1, zero_entry)); + } + return external_limits; +} + static void remove_error_from_list(std::vector& 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; }), @@ -534,6 +564,37 @@ void API::init() { EVLOG_warning << "Invalid limit: No conversion of given input could be performed."; } }); + + std::string cmd_set_limit_phases = cmd_base + "set_limit_amps_phases"; + + this->mqtt.subscribe(cmd_set_limit_phases, [&evse_manager_check = this->evse_manager_check, + &evse_energy_sink = evse_energy_sink](const std::string& data) { + int32_t phases; + float amps; + try { + auto arg = json::parse(data); + if (arg.contains("amps") && arg.contains("phases")) { + amps = arg.at("amps"); + phases = arg.at("phases"); + } else { + EVLOG_error << "Invalid limit: Missing amps or phases."; + return; + } + } catch (const std::exception& e) { + EVLOG_error << "set_limit_amps_phases: Cannot parse argument, command ignored: " << e.what(); + return; + } + try { + const auto external_limits = get_external_limits(phases, amps); + evse_manager_check.wait_ready(); + evse_energy_sink.call_set_external_limits(external_limits); + } catch (const std::invalid_argument& e) { + EVLOG_warning << "Invalid limit: No conversion of given input could be performed."; + } catch (const std::out_of_range& e) { + EVLOG_warning << "Invalid limit: Out of range " + << ", error: " << e.what(); + } + }); } else { EVLOG_warning << "No evse energy sink configured for evse_id: " << evse_id << ". API module does therefore not allow control of amps or power limits for this EVSE"; diff --git a/modules/API/README.md b/modules/API/README.md index afb4c211b..e574ded22 100644 --- a/modules/API/README.md +++ b/modules/API/README.md @@ -251,8 +251,26 @@ Command to set a watt limit for this EVSE that will be considered within the Ene 📌 **Note:** You have to configure one evse_energy_sink connection per EVSE within the configuration file in order to use this topic! +### everest_api/evse_manager/cmd/set_limit_amps_phases +Command to set a current (amps) and a phase limit for this EVSE, which will be considered by the energy +management. The payload should be in the following json format: +```json + { + "amps": 8.0, + "phases": 3 + } +``` +Setting these limits does not automatically imply that they will be set by the EVSE because the +energy management might consider limitations from other sources, too. The "amps" value can be a +positive or negative number. The "phases" value must be either 1 or 3. +Please consider that switching between AC single-phase (1ph) and three-phase (3ph) charging does only +work if 1ph/3ph switching is activated in the EVerest configuration. For more information please look +in the EVerest documentation. + +📌 **Note:** You have to configure one evse_energy_sink connection per EVSE within the configuration file in order to use this topic! + ### everest_api/evse_manager/cmd/force_unlock -Command to force unlock a connector on the EVSE. They payload should be a positive integer identifying the connector that should be unlocked. If the payload is empty or cannot be converted to an integer connector 1 is assumed. +Command to force unlock a connector on the EVSE. The payload should be a positive integer identifying the connector that should be unlocked. If the payload is empty or cannot be converted to an integer connector 1 is assumed. ### everest_api/evse_manager/cmd/uk_random_delay Command to control the UK Smart Charging random delay feature. The payload can be the following enum: "enable" and "disable" to enable/disable the feature entirely or "cancel" to cancel an ongoing delay.