diff --git a/config/config-sil.yaml b/config/config-sil.yaml index 9e093e9a5..aa1ebf36a 100644 --- a/config/config-sil.yaml +++ b/config/config-sil.yaml @@ -52,6 +52,12 @@ active_modules: energy_manager: config_module: switch_3ph1ph_while_charging_mode: Both + switch_3ph1ph_max_nr_of_switches_per_session: 5 + switch_3ph1ph_time_hysteresis_s: 20 + switch_3ph1ph_power_hysteresis_W: 1000 + switch_3ph1ph_switch_limit_stickyness: SinglePhase + schedule_interval_duration: 60 + schedule_total_duration: 10 debug: false connections: energy_trunk: diff --git a/modules/EnergyManager/Broker.cpp b/modules/EnergyManager/Broker.cpp index e7219775e..1ac54c2e2 100644 --- a/modules/EnergyManager/Broker.cpp +++ b/modules/EnergyManager/Broker.cpp @@ -7,8 +7,9 @@ namespace module { -Broker::Broker(Market& _market) : +Broker::Broker(Market& _market, BrokerContext& _context) : local_market(_market), + context(_context), first_trade(globals.schedule_length, true), slot_type(globals.schedule_length, SlotType::Undecided), num_phases(globals.schedule_length, 0) { diff --git a/modules/EnergyManager/Broker.hpp b/modules/EnergyManager/Broker.hpp index e0ac7b9f6..4ae9f7b6a 100644 --- a/modules/EnergyManager/Broker.hpp +++ b/modules/EnergyManager/Broker.hpp @@ -14,10 +14,27 @@ enum class SlotType { Undecided }; +// All context data that is stored in between optimization runs +struct BrokerContext { + BrokerContext() { + clear(); + }; + + void clear() { + number_1ph3ph_cycles = 0; + last_ac_number_of_active_phases_import = 0; + ts_1ph_optimal = date::utc_clock::now(); + }; + + int number_1ph3ph_cycles; + int last_ac_number_of_active_phases_import; + std::chrono::time_point ts_1ph_optimal; +}; + // base class for different Brokers class Broker { public: - Broker(Market& market); + Broker(Market& market, BrokerContext& context); virtual ~Broker(){}; virtual bool trade(Offer& offer) = 0; Market& get_local_market(); @@ -28,6 +45,8 @@ class Broker { std::vector first_trade; std::vector slot_type; std::vector num_phases; + + BrokerContext& context; }; } // namespace module diff --git a/modules/EnergyManager/BrokerFastCharging.cpp b/modules/EnergyManager/BrokerFastCharging.cpp index 528b6e297..2a3b4b6ce 100644 --- a/modules/EnergyManager/BrokerFastCharging.cpp +++ b/modules/EnergyManager/BrokerFastCharging.cpp @@ -7,8 +7,37 @@ namespace module { -BrokerFastCharging::BrokerFastCharging(Market& _market, Switch1ph3phMode mode) : - Broker(_market), switch_1ph3ph_mode(mode) { +BrokerFastCharging::BrokerFastCharging(Market& _market, BrokerContext& _context, Config _config) : + Broker(_market, _context), config(_config) { +} + +static auto to_timestamp(const types::energy::ScheduleReqEntry& entry) { + return Everest::Date::from_rfc3339(entry.timestamp); +} + +static bool time_slot_active(const int i, const ScheduleReq& offer) { + const auto& now = globals.start_time; + const auto t_i = to_timestamp(offer[i]); + + int active_slot = 0; + // Get active slot: + if (now < to_timestamp(offer[0])) { + // First element already in the future + active_slot = 0; + } else if (now > to_timestamp(offer[offer.size() - 1])) { + // Last element in the past + active_slot = offer.size() - 1; + } else { + // Somewhere in between + for (int n = 0; n < offer.size() - 1; n++) { + if (now > to_timestamp(offer[n]) and now < to_timestamp(offer[n + 1])) { + active_slot = n; + break; + } + } + } + + return active_slot == i; } bool BrokerFastCharging::trade(Offer& _offer) { @@ -46,6 +75,8 @@ bool BrokerFastCharging::trade(Offer& _offer) { // if we have not bought anything, we first need to buy the minimal limits for ac_amp if any. for (int i = 0; i < globals.schedule_length; i++) { + bool time_slot_is_active = time_slot_active(i, offer->import_offer); + // make this more readable auto& max_current_import = offer->import_offer[i].limits_to_root.ac_max_current_A; const auto& min_current_import = offer->import_offer[i].limits_to_root.ac_min_current_A; @@ -89,52 +120,106 @@ bool BrokerFastCharging::trade(Offer& _offer) { // A current limit is set // If an additional watt limit is set check phases, else it is max_phases (typically 3) - // First decide if we charge 1 phase or 3 phase (if switching is possible at all) + // First decide if we would like to charge 1 phase or 3 phase (if switching is possible at all) // - Check if we are below e.g. 4.2kW (min_current*voltage*3) -> we have to do single phase - // - Check if we are above e.g. 7.4kW (max_current*voltage*1) -> we have to go three phase + // - Check if we are above e.g. 4.4kW (min_current*voltage*3 + watt_hysteresis) -> we want to go three + // phase // - If we are in between, use what is currently active (hysteresis) - // One problem is that we do not know the EV's limit, so the hysteresis does not work properly - // when the EV supports 16A and the EVSE supports 32A: - // Then in single phase, it will increase until 1ph/32A before it switches to 3ph, but the EV gets - // stuck at 3.6kW/1ph/16A because that is its limit. int number_of_phases = ac_number_of_active_phases_import; - if (switch_1ph3ph_mode not_eq Switch1ph3phMode::Never and total_power_import.has_value() && - min_current_import.value() && min_current_import.value() > 0.) { - if (total_power_import.value() < - min_current_import.value() * max_phases_import * local_market.nominal_ac_voltage()) { - // We have to do single phase, it is impossible with 3ph - number_of_phases = min_phases_import; - } else if (switch_1ph3ph_mode == Switch1ph3phMode::Both and - total_power_import.value() > - max_current_import.value() * min_phases_import * local_market.nominal_ac_voltage()) { - number_of_phases = max_phases_import; + + const auto min_power_3ph = + min_current_import.value_or(0) * max_phases_import * local_market.nominal_ac_voltage(); + + bool number_of_switching_cycles_reached = false; + + if (first_trade[i]) { + if (config.switch_1ph_3ph_mode not_eq Switch1ph3phMode::Never and total_power_import.has_value() && + min_power_3ph > 0.) { + + if (total_power_import.value() < min_power_3ph) { + // We have to do single phase, it is impossible with 3ph + number_of_phases = min_phases_import; + } else if (config.switch_1ph_3ph_mode == Switch1ph3phMode::Both and + total_power_import.value() > min_power_3ph + config.power_hysteresis_W) { + number_of_phases = max_phases_import; + } else { + // Keep number of phases as they are + number_of_phases = ac_number_of_active_phases_import; + } + + // Now we made the decision what the optimal number of phases would be (in variable + // number_of_phases) We also have a time based hysteresis as well as some limits in maximum + // number of switching cycles. This means we maybe cannot use the optimal number of phases just + // now. Check those conditions and adjust number_of_phases accordingly. + + if (config.max_nr_of_switches_per_session > 0 and + context.number_1ph3ph_cycles > config.max_nr_of_switches_per_session) { + number_of_switching_cycles_reached = true; + if (config.stickyness == StickyNess::SinglePhase) { + number_of_phases = min_phases_import; + } else if (config.stickyness == StickyNess::ThreePhase) { + number_of_phases = max_phases_import; + } else { + number_of_phases = ac_number_of_active_phases_import; + } + } + + if (number_of_phases == min_phases_import) { + context.ts_1ph_optimal = date::utc_clock::now(); + } + + if (config.time_hysteresis_s > 0 and time_slot_is_active) { + // Check time based hysteresis: + // - store timestamp whenever 1ph is optimal (update continously) + // Then now-timestamp is the stable time period for a 3ph condition. + // This should only be done in the currently active time slot. Ignore time hysteresis in + // other slots in the future or past. + // Only allow an actual change to 3ph if the time exceeds the configured hysteresis limit. + const auto stable_3ph = std::chrono::duration_cast( + globals.start_time - context.ts_1ph_optimal) + .count(); + + if (stable_3ph < config.time_hysteresis_s and number_of_phases == max_phases_import) { + number_of_phases = min_phases_import; + } + } } else { - // Keep number of phases as they are - number_of_phases = ac_number_of_active_phases_import; + number_of_phases = max_phases_import; } - } else { - number_of_phases = max_phases_import; } + // store decision in context + if (ac_number_of_active_phases_import not_eq context.last_ac_number_of_active_phases_import) { + context.number_1ph3ph_cycles++; + } + context.last_ac_number_of_active_phases_import = ac_number_of_active_phases_import; + if (first_trade[i] && min_current_import.has_value() && min_current_import.value() > 0.) { num_phases[i] = number_of_phases; // EVLOG_info << "I: first trade: try to buy minimal current_A on AC: " << // min_current_import.value(); // try to buy minimal current_A if we are on AC, but don't buy less. if (not buy_ampere_import(i, min_current_import.value(), false, number_of_phases) and - switch_1ph3ph_mode not_eq Switch1ph3phMode::Never) { + config.switch_1ph_3ph_mode not_eq Switch1ph3phMode::Never and + not number_of_switching_cycles_reached) { // If we cannot buy the minimum amount we need, try again in single phase mode (it may be due to // a watt limit only) number_of_phases = 1; num_phases[i] = number_of_phases; buy_ampere_import(i, min_current_import.value(), false, number_of_phases); } + + /*EVLOG_info << "I: " << i << " -- 1ph3ph: " << min_power_3ph << " active_nr_phases " + << ac_number_of_active_phases_import << " cycles " << context.number_1ph3ph_cycles + << " number_of_phases " << number_of_phases << " time_slot_active " + << time_slot_is_active;*/ } else { // EVLOG_info << "I: Not first trade or nor min current needed."; // try to buy a slice but allow less to be bought buy_ampere_import(i, globals.slice_ampere, true, num_phases[i]); } + } else if (total_power_import.has_value()) { // only a watt limit is available // EVLOG_info << "I: Only watt limit is set." << total_power_import.value(); diff --git a/modules/EnergyManager/BrokerFastCharging.hpp b/modules/EnergyManager/BrokerFastCharging.hpp index 9b9922770..bbbe5c4a7 100644 --- a/modules/EnergyManager/BrokerFastCharging.hpp +++ b/modules/EnergyManager/BrokerFastCharging.hpp @@ -15,7 +15,22 @@ class BrokerFastCharging : public Broker { Oneway, Both, }; - explicit BrokerFastCharging(Market& market, Switch1ph3phMode mode); + + enum class StickyNess { + SinglePhase, + ThreePhase, + DontChange, + }; + + struct Config { + Switch1ph3phMode switch_1ph_3ph_mode{Switch1ph3phMode::Never}; + StickyNess stickyness{StickyNess::DontChange}; + int max_nr_of_switches_per_session{0}; + int power_hysteresis_W{200}; + int time_hysteresis_s{600}; + }; + + explicit BrokerFastCharging(Market& market, BrokerContext& context, Config config); virtual bool trade(Offer& offer) override; private: @@ -35,7 +50,7 @@ class BrokerFastCharging : public Broker { Offer* offer{nullptr}; bool traded{false}; - Switch1ph3phMode switch_1ph3ph_mode; + Config config; }; } // namespace module diff --git a/modules/EnergyManager/EnergyManager.cpp b/modules/EnergyManager/EnergyManager.cpp index 437f889cd..dfd0c9fed 100644 --- a/modules/EnergyManager/EnergyManager.cpp +++ b/modules/EnergyManager/EnergyManager.cpp @@ -84,6 +84,28 @@ static BrokerFastCharging::Switch1ph3phMode to_switch_1ph3ph_mode(const std::str } } +static BrokerFastCharging::StickyNess to_stickyness(const std::string& m) { + if (m == "DontChange") { + return BrokerFastCharging::StickyNess::DontChange; + } else if (m == "SinglePhase") { + return BrokerFastCharging::StickyNess::SinglePhase; + } else { + return BrokerFastCharging::StickyNess::ThreePhase; + } +} + +static BrokerFastCharging::Config to_broker_fast_charging_config(const Conf& module_config) { + BrokerFastCharging::Config broker_conf; + + broker_conf.max_nr_of_switches_per_session = module_config.switch_3ph1ph_max_nr_of_switches_per_session; + broker_conf.power_hysteresis_W = module_config.switch_3ph1ph_power_hysteresis_W; + broker_conf.switch_1ph_3ph_mode = to_switch_1ph3ph_mode(module_config.switch_3ph1ph_while_charging_mode); + broker_conf.time_hysteresis_s = module_config.switch_3ph1ph_time_hysteresis_s; + broker_conf.stickyness = to_stickyness(module_config.switch_3ph1ph_switch_limit_stickyness); + + return broker_conf; +} + std::vector EnergyManager::run_optimizer(types::energy::EnergyFlowRequest request) { std::scoped_lock lock(energy_mutex); @@ -106,10 +128,19 @@ std::vector EnergyManager::run_optimizer(types::e auto evse_markets = market.get_list_of_evses(); for (auto m : evse_markets) { + // Check if we need to clear the context + // Note that context is created here if it does not exist implicitly by operator[] of the map + if (m->energy_flow_request.evse_state == types::energy::EvseState::Unplugged or + m->energy_flow_request.evse_state == types::energy::EvseState::Finished) { + contexts[m->energy_flow_request.uuid].clear(); + contexts[m->energy_flow_request.uuid].ts_1ph_optimal = + globals.start_time - std::chrono::seconds(config.switch_3ph1ph_time_hysteresis_s); + } + // FIXME: check for actual optimizer_targets and create correct broker for this evse // For now always create simple FastCharging broker - brokers.push_back( - std::make_shared(*m, to_switch_1ph3ph_mode(config.switch_3ph1ph_while_charging_mode))); + brokers.push_back(std::make_shared(*m, contexts[m->energy_flow_request.uuid], + to_broker_fast_charging_config(config))); // EVLOG_info << fmt::format("Created broker for {}", m->energy_flow_request.uuid); } diff --git a/modules/EnergyManager/EnergyManager.hpp b/modules/EnergyManager/EnergyManager.hpp index 330b811fd..db6edc62c 100644 --- a/modules/EnergyManager/EnergyManager.hpp +++ b/modules/EnergyManager/EnergyManager.hpp @@ -25,6 +25,8 @@ #include +#include "Broker.hpp" + #ifdef BUILD_TESTING_MODULE_ENERGY_MANAGER #include namespace module::test { @@ -46,6 +48,10 @@ struct Conf { double slice_watt; bool debug; std::string switch_3ph1ph_while_charging_mode; + int switch_3ph1ph_max_nr_of_switches_per_session; + std::string switch_3ph1ph_switch_limit_stickyness; + int switch_3ph1ph_power_hysteresis_W; + int switch_3ph1ph_time_hysteresis_s; }; class EnergyManager : public Everest::ModuleBase { @@ -87,6 +93,8 @@ class EnergyManager : public Everest::ModuleBase { std::condition_variable mainloop_sleep_condvar; std::mutex mainloop_sleep_mutex; + std::map contexts; + #ifdef BUILD_TESTING_MODULE_ENERGY_MANAGER FRIEND_TEST(EnergyManagerTest, empty); FRIEND_TEST(EnergyManagerTest, noSchedules); diff --git a/modules/EnergyManager/manifest.yaml b/modules/EnergyManager/manifest.yaml index bb1dc3c77..5649a8b00 100644 --- a/modules/EnergyManager/manifest.yaml +++ b/modules/EnergyManager/manifest.yaml @@ -42,6 +42,40 @@ config: - Oneway - Both default: Never + switch_3ph1ph_max_nr_of_switches_per_session: + description: >- + Limit the maximum number of switches between 1ph and 3ph per charging session. + Set to 0 for no limit. + type: integer + default: 0 + switch_3ph1ph_switch_limit_stickyness: + description: >- + If the maximum number of switches between 1ph and 3ph is reached, select what should happen: + - SinglePhase: Switch to 1ph mode + - ThreePhase: Switch to 3ph mode + - DontChange: Stay in the mode it is currently in + type: string + enum: + - SinglePhase + - ThreePhase + - DontChange + default: DontChange + switch_3ph1ph_power_hysteresis_W: + description: >- + Power based hysteresis in Watt. If set to 200W for example, + the hysteresis for PWM based charging will be 4.2kW to 4.4kW. + Actual values will depend on configured nominal AC voltage, and they may be different for + PWM vs ISO based charging in the future. + type: integer + default: 200 + switch_3ph1ph_time_hysteresis_s: + description: >- + Time based hysteresis. It will only switch to 3 phases if the condition to select 3 phases + is stable for the configured number of seconds. It will always switch to 1ph mode without + waiting for this delay. + Set to 0 to disable time based hysteresis. + type: integer + default: 600 provides: main: description: Main interface of the energy manager diff --git a/modules/EnergyManager/tests/EnergyManagerTest.cpp b/modules/EnergyManager/tests/EnergyManagerTest.cpp index 4ad188c8b..265af8d4a 100644 --- a/modules/EnergyManager/tests/EnergyManagerTest.cpp +++ b/modules/EnergyManager/tests/EnergyManagerTest.cpp @@ -456,6 +456,7 @@ types::energy::EnergyFlowRequest energy_flow_request{ "evse_manager", // UUID for this node types::energy::NodeType::Evse, // node_type false, // optional - bool priority_request + std::nullopt, // optional - EvseState std::nullopt, // optional - types::energy::OptimizerTarget c_energy_usage_root, // optional - types::powermeter::Powermeter - root std::nullopt, // optional - types::powermeter::Powermeter - leaf @@ -542,6 +543,7 @@ const types::energy::EnergyFlowRequest c_efr_evse_manager{ "evse_manager", // UUID for this node types::energy::NodeType::Evse, // node_type false, // optional - bool priority_request + std::nullopt, // optional - EvseState std::nullopt, // optional - types::energy::OptimizerTarget c_energy_usage_root_evse_manager, // optional - types::powermeter::Powermeter - root std::nullopt, // optional - types::powermeter::Powermeter - leaf @@ -574,6 +576,7 @@ const types::energy::EnergyFlowRequest c_efr_cls_energy_node{ "cls_energy_node", // UUID for this node types::energy::NodeType::Generic, // node_type std::nullopt, // optional - bool priority_request + std::nullopt, // optional - EvseState std::nullopt, // optional - types::energy::OptimizerTarget std::nullopt, // optional - types::powermeter::Powermeter - root std::nullopt, // optional - types::powermeter::Powermeter - leaf @@ -606,6 +609,7 @@ const types::energy::EnergyFlowRequest c_efr_grid_connection_point{ "grid_connection_point", // UUID for this node types::energy::NodeType::Generic, // node_type std::nullopt, // optional - bool priority_request + std::nullopt, // optional - EvseState std::nullopt, // optional - types::energy::OptimizerTarget std::nullopt, // optional - types::powermeter::Powermeter - root std::nullopt, // optional - types::powermeter::Powermeter - leaf @@ -757,6 +761,7 @@ TEST(EnergyManagerTest, noSchedules) { types::energy::NodeType::Evse, // node_type false, // optional - bool priority_request std::nullopt, // optional - types::energy::OptimizerTarget + std::nullopt, // optional - EvseState c_energy_usage_root, // optional - types::powermeter::Powermeter - root std::nullopt, // optional - types::powermeter::Powermeter - leaf std::nullopt, // optional - std::vector - import @@ -816,6 +821,7 @@ TEST(EnergyManagerTest, schedules) { "evse_manager", // UUID for this node types::energy::NodeType::Evse, // node_type false, // optional - bool priority_request + std::nullopt, // optional - EvseState std::nullopt, // optional - types::energy::OptimizerTarget c_energy_usage_root, // optional - types::powermeter::Powermeter - root std::nullopt, // optional - types::powermeter::Powermeter - leaf diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 6da0ac05d..b553f8c92 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -174,6 +174,14 @@ void Charger::run_state_machine() { auto time_in_current_state = std::chrono::duration_cast(now - internal_context.current_state_started).count(); + // Clean up ongoing 1ph 3ph switching operation? + if (internal_context.last_state == EvseState::SwitchPhases and + shared_context.current_state not_eq EvseState::SwitchPhases and + shared_context.switch_3ph1ph_threephase_ongoing) { + bsp->switch_three_phases_while_charging(shared_context.switch_3ph1ph_threephase); + shared_context.switch_3ph1ph_threephase_ongoing = false; + } + switch (shared_context.current_state) { case EvseState::Disabled: if (initialize_state) { @@ -435,6 +443,7 @@ void Charger::run_state_machine() { if (time_in_current_state >= config_context.switch_3ph1ph_delay_s * 1000) { session_log.evse(false, "Exit switching phases"); bsp->switch_three_phases_while_charging(shared_context.switch_3ph1ph_threephase); + shared_context.switch_3ph1ph_threephase_ongoing = false; shared_context.current_state = internal_context.switching_phases_return_state; } break; @@ -1261,10 +1270,16 @@ bool Charger::switch_three_phases_while_charging(bool n) { if (shared_context.current_state == EvseState::Charging) { // In charging state, we need to go via a helper state for the delay shared_context.switch_3ph1ph_threephase = n; + shared_context.switch_3ph1ph_threephase_ongoing = true; internal_context.switching_phases_return_state = EvseState::PrepareCharging; shared_context.current_state = EvseState::SwitchPhases; } else if (shared_context.current_state == EvseState::SwitchPhases) { shared_context.switch_3ph1ph_threephase = n; + } else if (shared_context.current_state == EvseState::WaitingForEnergy) { + shared_context.switch_3ph1ph_threephase = n; + shared_context.switch_3ph1ph_threephase_ongoing = true; + internal_context.switching_phases_return_state = EvseState::WaitingForEnergy; + shared_context.current_state = EvseState::SwitchPhases; } else { // In all other states we can tell the bsp directly. bsp->switch_three_phases_while_charging(n); diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index 85c509d90..73247147f 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -300,6 +300,7 @@ class Charger { bool ac_with_soc_timeout; bool contactor_welded{false}; bool switch_3ph1ph_threephase{false}; + bool switch_3ph1ph_threephase_ongoing{false}; std::optional stop_signed_meter_value; std::optional start_signed_meter_value; diff --git a/modules/EvseManager/doc.rst b/modules/EvseManager/doc.rst index ca5d1bee0..08687f0da 100644 --- a/modules/EvseManager/doc.rst +++ b/modules/EvseManager/doc.rst @@ -199,8 +199,10 @@ To use this feature several things need to be enabled: - In the BSP driver, set ``supports_changing_phases_during_charging`` to true in the reported capabilities. If your bsp hardware detects e.g. the Zoe, you can set that flag to false and publish updated capabilities any time. - BSP driver capabilities: Also make sure that minimum phases are set to one and maximum phases to 3 -- BSP driver: make sure the ``ac_switch_three_phases_while_charging`` command correctly -- EnergyManager: Adjust ``switch_3ph1ph_while_charging_mode`` config option to your needs +- BSP driver: make sure the ``ac_switch_three_phases_while_charging`` command is correctly implemented +- EnergyManager: Adjust ``switch_3ph1ph_while_charging_mode``, ``switch_3ph1ph_max_nr_of_switches_per_session``, + ``switch_3ph1ph_switch_limit_stickyness``, ``switch_3ph1ph_power_hysteresis_W``, ``switch_3ph1ph_time_hysteresis_s`` + config options to your needs If all of this is properly set up, the EnergyManager will drive the 1ph/3ph switching. In order to do so, it needs an (external) limit to be set. There are two options: The external limit can be in Watt (not in Ampere), @@ -211,13 +213,9 @@ The second option is to set a limit in Ampere and set a limitation on the number This will enforce switching and can be used to decide the switching time externally. EnergyManager does not have the freedom to make the choice in this case. -In general, it works best in a configuration with 32A per phase and a limit in Watt. -In this scenario there is an actual hysteresis as the two intervals overlap: -1ph charging can be done from 1.3kW to 7.4kW and 3ph charging works from 4.2kW to 22kW(or 11kW) - -If the single phase and three phase intervals do not overlap, there is no hysteresis. -Note that many cars support 32A on 1ph even if they are limited to 16A on 3ph. Some however are limited to 16A -in 1ph mode and will hence charge slower then expected in 1ph mode. +Take care especially with the power(watt) and time based hysteresis settings. They should be adjusted to the +actual use case to avoid relays wearing due too a lot of switching cycles. Consider also to limit the maximum +number of switching cycles per charging session. Error Handling ============== diff --git a/modules/EvseManager/energy_grid/energyImpl.cpp b/modules/EvseManager/energy_grid/energyImpl.cpp index 108368d83..af290e864 100644 --- a/modules/EvseManager/energy_grid/energyImpl.cpp +++ b/modules/EvseManager/energy_grid/energyImpl.cpp @@ -129,6 +129,54 @@ void energyImpl::ready() { }); } +types::energy::EvseState to_energy_evse_state(const Charger::EvseState charger_state) { + switch (charger_state) { + case Charger::EvseState::Disabled: + return types::energy::EvseState::Disabled; + break; + case Charger::EvseState::Idle: + return types::energy::EvseState::Unplugged; + break; + case Charger::EvseState::WaitingForAuthentication: + return types::energy::EvseState::WaitForAuth; + break; + case Charger::EvseState::PrepareCharging: + return types::energy::EvseState::PrepareCharging; + break; + case Charger::EvseState::WaitingForEnergy: + return types::energy::EvseState::WaitForEnergy; + break; + case Charger::EvseState::Charging: + return types::energy::EvseState::Charging; + break; + case Charger::EvseState::ChargingPausedEV: + return types::energy::EvseState::PausedEV; + break; + case Charger::EvseState::ChargingPausedEVSE: + return types::energy::EvseState::PausedEVSE; + break; + case Charger::EvseState::StoppingCharging: + return types::energy::EvseState::Finished; + break; + case Charger::EvseState::Finished: + return types::energy::EvseState::Finished; + break; + case Charger::EvseState::T_step_EF: + return types::energy::EvseState::PrepareCharging; + break; + case Charger::EvseState::T_step_X1: + return types::energy::EvseState::PrepareCharging; + break; + case Charger::EvseState::SwitchPhases: + return types::energy::EvseState::Charging; + break; + case Charger::EvseState::Replug: + return types::energy::EvseState::PrepareCharging; + break; + } + return types::energy::EvseState::Disabled; +} + void energyImpl::request_energy_from_energy_manager(bool priority_request) { std::lock_guard lock(this->energy_mutex); clear_import_request_schedule(); @@ -251,6 +299,9 @@ void energyImpl::request_energy_from_energy_manager(bool priority_request) { energy_flow_request.priority_request = false; } + // Attach our state + energy_flow_request.evse_state = to_energy_evse_state(charger_state); + publish_energy_flow_request(energy_flow_request); // EVLOG_info << "Outgoing request " << energy_flow_request; } diff --git a/types/energy.yaml b/types/energy.yaml index 23688faa1..24c554750 100644 --- a/types/energy.yaml +++ b/types/energy.yaml @@ -7,6 +7,19 @@ types: - Undefined - Evse - Generic + EvseState: + description: Enum for simplified EVSE state + type: string + enum: + - Unplugged + - WaitForAuth + - WaitForEnergy + - PrepareCharging + - PausedEV + - PausedEVSE + - Charging + - Finished + - Disabled LimitsReq: description: Energy flow limiting object request (Evses to EnergyManager) type: object @@ -180,6 +193,10 @@ types: If set to true, this is a high priority request that needs to be handled now. Otherwise energymanager may merge multiple requests and address them later. type: boolean + evse_state: + description: State of the EVSE + type: object + $ref: /energy#/EvseState optimizer_target: description: User defined optimizer targets for this evse type: object