-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/energymanagement extensions #1033
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,19 +53,19 @@ types::energy::ExternalLimits get_external_limits(const std::string& data, bool | |
|
||
types::energy::ScheduleReqEntry zero_entry; | ||
zero_entry.timestamp = timestamp; | ||
zero_entry.limits_to_leaves.total_power_W = 0; | ||
zero_entry.limits_to_leaves.total_power_W = {0}; | ||
|
||
if (is_watts) { | ||
target_entry.limits_to_leaves.total_power_W = std::fabs(limit); | ||
target_entry.limits_to_leaves.total_power_W = {std::fabs(limit), "API_module"}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can be defined as constexpr |
||
} else { | ||
target_entry.limits_to_leaves.ac_max_current_A = std::fabs(limit); | ||
target_entry.limits_to_leaves.ac_max_current_A = {std::fabs(limit), "API_module"}; | ||
} | ||
|
||
if (limit > 0) { | ||
external_limits.schedule_import.emplace(std::vector<types::energy::ScheduleReqEntry>(1, target_entry)); | ||
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, target_entry); | ||
} else { | ||
external_limits.schedule_export.emplace(std::vector<types::energy::ScheduleReqEntry>(1, target_entry)); | ||
external_limits.schedule_import.emplace(std::vector<types::energy::ScheduleReqEntry>(1, zero_entry)); | ||
external_limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, target_entry); | ||
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, zero_entry); | ||
} | ||
return external_limits; | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,16 +7,260 @@ | |||||||||||
|
||||||||||||
namespace module { | ||||||||||||
|
||||||||||||
Broker::Broker(Market& _market, BrokerContext& _context) : | ||||||||||||
Broker::Broker(Market& _market, BrokerContext& _context, EnergyManagerConfig _config) : | ||||||||||||
local_market(_market), | ||||||||||||
context(_context), | ||||||||||||
config(_config), | ||||||||||||
first_trade(globals.schedule_length, true), | ||||||||||||
slot_type(globals.schedule_length, SlotType::Undecided), | ||||||||||||
num_phases(globals.schedule_length, 0) { | ||||||||||||
num_phases(globals.schedule_length, {0, "empty"}) { | ||||||||||||
} | ||||||||||||
|
||||||||||||
Market& Broker::get_local_market() { | ||||||||||||
return local_market; | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool Broker::trade(Offer& _offer) { | ||||||||||||
offer = &_offer; | ||||||||||||
|
||||||||||||
if (globals.debug) | ||||||||||||
EVLOG_info << local_market.energy_flow_request.uuid << " Broker: " << *offer; | ||||||||||||
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
// create a new schedules that contains everything we want to buy | ||||||||||||
trading = globals.empty_schedule_res; | ||||||||||||
|
||||||||||||
// buy/sell nothing in the beginning | ||||||||||||
|
||||||||||||
for (int i = 0; i < globals.schedule_length; i++) { | ||||||||||||
// make this more readable | ||||||||||||
auto& max_current = offer->import_offer[i].limits_to_root.ac_max_current_A; | ||||||||||||
auto& total_power = offer->import_offer[i].limits_to_root.total_power_W; | ||||||||||||
if (max_current.has_value()) { | ||||||||||||
trading[i].limits_to_root.ac_max_current_A = {0.}; | ||||||||||||
} | ||||||||||||
if (total_power.has_value()) { | ||||||||||||
trading[i].limits_to_root.total_power_W = {0.}; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
traded = false; | ||||||||||||
|
||||||||||||
if (offer->import_offer.size() != offer->export_offer.size()) { | ||||||||||||
EVLOG_error << "import and export offer do not have the same size!"; | ||||||||||||
return false; | ||||||||||||
} | ||||||||||||
|
||||||||||||
// Run the actual broker implementation, depending on strategy | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helps a little bit when reading through lines L46 - L57
Suggested change
|
||||||||||||
tradeImpl(); | ||||||||||||
|
||||||||||||
// if we want to buy anything: | ||||||||||||
if (traded) { | ||||||||||||
if (globals.debug) { | ||||||||||||
EVLOG_info << fmt::format("\033[1;33m {}A {}W \033[1;0m", | ||||||||||||
(trading[0].limits_to_root.ac_max_current_A.has_value() | ||||||||||||
? std::to_string(trading[0].limits_to_root.ac_max_current_A.value().value) | ||||||||||||
: " [NOT_SET] "), | ||||||||||||
(trading[0].limits_to_root.total_power_W.has_value() | ||||||||||||
? std::to_string(trading[0].limits_to_root.total_power_W.value().value) | ||||||||||||
: " [NOT_SET] ")); | ||||||||||||
} | ||||||||||||
// execute the trade on the market | ||||||||||||
local_market.trade(trading); | ||||||||||||
|
||||||||||||
// clear the source as we are not done yet (or started trading again after a no trade) | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is that a TODO ? |
||||||||||||
|
||||||||||||
return true; | ||||||||||||
} else { | ||||||||||||
if (globals.debug) { | ||||||||||||
EVLOG_info << fmt::format("\033[1;33m NO TRADE \033[1;0m"); | ||||||||||||
} | ||||||||||||
|
||||||||||||
// execute the zero trade on the market | ||||||||||||
local_market.trade(trading); | ||||||||||||
|
||||||||||||
// If no trade happens for the first time after successful tradings, set the source. If trading happens again, | ||||||||||||
// clear the source again. If this is a second call to no trade, do not update source. | ||||||||||||
return false; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
date::utc_clock::time_point Broker::to_timestamp(const types::energy::ScheduleReqEntry& entry) { | ||||||||||||
return Everest::Date::from_rfc3339(entry.timestamp); | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool Broker::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 Broker::buy_ampere_import(int index, float ampere, bool allow_less, | ||||||||||||
types::energy::IntegerWithSource number_of_phases) { | ||||||||||||
return buy_ampere(offer->import_offer[index], index, ampere, allow_less, true, number_of_phases); | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool Broker::buy_ampere_export(int index, float ampere, bool allow_less, | ||||||||||||
types::energy::IntegerWithSource number_of_phases) { | ||||||||||||
return buy_ampere(offer->export_offer[index], index, ampere, allow_less, false, number_of_phases); | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool Broker::buy_watt_import(int index, float watt, bool allow_less) { | ||||||||||||
return buy_watt(offer->import_offer[index], index, watt, allow_less, true); | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool Broker::buy_watt_export(int index, float watt, bool allow_less) { | ||||||||||||
return buy_watt(offer->export_offer[index], index, watt, allow_less, false); | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool Broker::buy_ampere(const types::energy::ScheduleReqEntry& _offer, int index, float ampere, bool allow_less, | ||||||||||||
bool import, types::energy::IntegerWithSource number_of_phases) { | ||||||||||||
// make this more readable | ||||||||||||
auto& max_current = _offer.limits_to_root.ac_max_current_A; | ||||||||||||
auto& total_power = _offer.limits_to_root.total_power_W; | ||||||||||||
|
||||||||||||
if (!max_current.has_value()) { | ||||||||||||
// no ampere limit set, cannot do anything here. | ||||||||||||
EVLOG_error << "[FAIL] called buy_ampere with only watt limit available."; | ||||||||||||
return false; | ||||||||||||
} | ||||||||||||
|
||||||||||||
// enough ampere available? | ||||||||||||
if (max_current.value().value >= ampere) { | ||||||||||||
|
||||||||||||
// do we have an additional watt limit? | ||||||||||||
if (total_power.has_value()) { | ||||||||||||
// is the watt limit high enough? | ||||||||||||
if (total_power.value().value >= ampere * number_of_phases.value * local_market.nominal_ac_voltage()) { | ||||||||||||
// yes, buy both ampere and watt | ||||||||||||
// EVLOG_info << "[OK] buy amps and total power is big enough for trade of " << a << "A /" | ||||||||||||
// << a * number_of_phases * local_market.nominal_ac_voltage(); | ||||||||||||
buy_ampere_unchecked(index, {(import ? +1 : -1) * ampere, max_current.value().source}, | ||||||||||||
number_of_phases); | ||||||||||||
// It was not actually limited by the watt limit, set the source from ampere limit | ||||||||||||
buy_watt_unchecked( | ||||||||||||
index, {(import ? +1 : -1) * ampere * number_of_phases.value * local_market.nominal_ac_voltage(), | ||||||||||||
max_current.value().source}); | ||||||||||||
return true; | ||||||||||||
} | ||||||||||||
} else { | ||||||||||||
// no additional watt limit, let's just buy the ampere value | ||||||||||||
// EVLOG_info << "[OK] total power is not set, buying amps only " << a; | ||||||||||||
buy_ampere_unchecked(index, {(import ? +1 : -1) * ampere, max_current.value().source}, number_of_phases); | ||||||||||||
return true; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
// we are still here, so we were not successfull in buying what we wanted. | ||||||||||||
// should we try to buy the leftovers? | ||||||||||||
|
||||||||||||
if (allow_less && max_current.value().value > 0.) { | ||||||||||||
|
||||||||||||
// we have an additional watt limit | ||||||||||||
if (total_power.has_value()) { | ||||||||||||
if (total_power.value().value > 0) { | ||||||||||||
// is the watt limit high enough? | ||||||||||||
if (total_power.value().value >= | ||||||||||||
max_current.value().value * number_of_phases.value * local_market.nominal_ac_voltage()) { | ||||||||||||
// yes, buy both ampere and watt | ||||||||||||
// EVLOG_info << "[OK leftovers] total power is big enough for trade of " | ||||||||||||
// << a * number_of_phases * local_market.nominal_ac_voltage(); | ||||||||||||
buy_ampere_unchecked(index, | ||||||||||||
{(import ? +1 : -1) * max_current.value().value, max_current.value().source}, | ||||||||||||
number_of_phases); | ||||||||||||
// It was not actually limited by the watt limit, set the source from ampere limit | ||||||||||||
buy_watt_unchecked(index, {(import ? +1 : -1) * max_current.value().value * number_of_phases.value * | ||||||||||||
local_market.nominal_ac_voltage(), | ||||||||||||
max_current.value().source}); | ||||||||||||
return true; | ||||||||||||
} else { | ||||||||||||
// watt limit is lower, try to reduce ampere | ||||||||||||
float reduced_ampere = | ||||||||||||
total_power.value().value / number_of_phases.value / local_market.nominal_ac_voltage(); | ||||||||||||
// EVLOG_info << "[OK leftovers] total power is not big enough, buy reduced current " << | ||||||||||||
// reduced_ampere | ||||||||||||
// << reduced_ampere * number_of_phases * local_market.nominal_ac_voltage(); | ||||||||||||
// Actually limited by watt limit, so use the watt source | ||||||||||||
buy_ampere_unchecked(index, {(import ? +1 : -1) * reduced_ampere, total_power.value().source}, | ||||||||||||
number_of_phases); | ||||||||||||
buy_watt_unchecked(index, {(import ? +1 : -1) * reduced_ampere * number_of_phases.value * | ||||||||||||
local_market.nominal_ac_voltage(), | ||||||||||||
total_power.value().source}); | ||||||||||||
return true; | ||||||||||||
} | ||||||||||||
} else { | ||||||||||||
// Don't buy anything if the total power limit is 0 | ||||||||||||
return false; | ||||||||||||
} | ||||||||||||
} else { | ||||||||||||
buy_ampere_unchecked(index, {(import ? +1 : -1) * max_current.value().value, max_current.value().source}, | ||||||||||||
number_of_phases); | ||||||||||||
return true; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
return false; | ||||||||||||
} | ||||||||||||
|
||||||||||||
bool Broker::buy_watt(const types::energy::ScheduleReqEntry& _offer, int index, float watt, bool allow_less, | ||||||||||||
bool import) { | ||||||||||||
// make this more readable | ||||||||||||
auto& total_power = _offer.limits_to_root.total_power_W; | ||||||||||||
|
||||||||||||
if (!total_power.has_value()) { | ||||||||||||
// no watt limit set, cannot do anything here. | ||||||||||||
EVLOG_error << "[FAIL] called buy watt with no watt limit available."; | ||||||||||||
return false; | ||||||||||||
} | ||||||||||||
|
||||||||||||
// enough watt available? | ||||||||||||
if (total_power.value().value >= watt) { | ||||||||||||
// EVLOG_info << "[OK] enough power available, buying " << a; | ||||||||||||
buy_watt_unchecked(index, {(import ? +1 : -1) * watt, total_power.value().source}); | ||||||||||||
return true; | ||||||||||||
} | ||||||||||||
|
||||||||||||
// we are still here, so we were not successfull in buying what we wanted. | ||||||||||||
// should we try to buy the leftovers? | ||||||||||||
|
||||||||||||
if (allow_less && total_power.value().value > 0.) { | ||||||||||||
// EVLOG_info << "[OK] buying leftovers " << total_power.value(); | ||||||||||||
buy_watt_unchecked(index, {(import ? +1 : -1) * total_power.value().value, total_power.value().source}); | ||||||||||||
return true; | ||||||||||||
} | ||||||||||||
|
||||||||||||
return false; | ||||||||||||
} | ||||||||||||
|
||||||||||||
void Broker::buy_ampere_unchecked(int index, types::energy::NumberWithSource ampere, | ||||||||||||
types::energy::IntegerWithSource number_of_phases) { | ||||||||||||
trading[index].limits_to_root.ac_max_current_A = ampere; | ||||||||||||
trading[index].limits_to_root.ac_max_phase_count = number_of_phases; | ||||||||||||
traded = true; | ||||||||||||
first_trade[index] = false; | ||||||||||||
} | ||||||||||||
|
||||||||||||
void Broker::buy_watt_unchecked(int index, types::energy::NumberWithSource watt) { | ||||||||||||
trading[index].limits_to_root.total_power_W = watt; | ||||||||||||
traded = true; | ||||||||||||
} | ||||||||||||
|
||||||||||||
} // namespace module |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,19 +34,62 @@ | |
// base class for different Brokers | ||
class Broker { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some documentation for the public functions and Enum definitions of this class would help. |
||
public: | ||
Broker(Market& market, BrokerContext& context); | ||
enum class Switch1ph3phMode { | ||
Never, | ||
Oneway, | ||
Both, | ||
}; | ||
|
||
enum class StickyNess { | ||
SinglePhase, | ||
ThreePhase, | ||
DontChange, | ||
}; | ||
|
||
struct EnergyManagerConfig { | ||
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}; | ||
}; | ||
|
||
Broker(Market& market, BrokerContext& context, EnergyManagerConfig config); | ||
virtual ~Broker(){}; | ||
virtual bool trade(Offer& offer) = 0; | ||
bool trade(Offer& offer); | ||
virtual void tradeImpl() = 0; | ||
Market& get_local_market(); | ||
|
||
protected: | ||
void buy_ampere_unchecked(int index, types::energy::NumberWithSource ampere, | ||
types::energy::IntegerWithSource number_of_phases); | ||
void buy_watt_unchecked(int index, types::energy::NumberWithSource watt); | ||
|
||
bool buy_ampere_import(int index, float ampere, bool allow_less, types::energy::IntegerWithSource number_of_phases); | ||
bool buy_ampere_export(int index, float ampere, bool allow_less, types::energy::IntegerWithSource number_of_phases); | ||
bool buy_ampere(const types::energy::ScheduleReqEntry& _offer, int index, float ampere, bool allow_less, | ||
bool import, types::energy::IntegerWithSource number_of_phases); | ||
|
||
bool buy_watt_import(int index, float watt, bool allow_less); | ||
bool buy_watt_export(int index, float watt, bool allow_less); | ||
bool buy_watt(const types::energy::ScheduleReqEntry& _offer, int index, float watt, bool allow_less, bool import); | ||
|
||
date::utc_clock::time_point to_timestamp(const types::energy::ScheduleReqEntry& entry); | ||
bool time_slot_active(const int i, const ScheduleReq& offer); | ||
|
||
// reference to local market at the broker's node | ||
Market& local_market; | ||
std::vector<bool> first_trade; | ||
std::vector<SlotType> slot_type; | ||
std::vector<int> num_phases; | ||
|
||
std::vector<types::energy::IntegerWithSource> num_phases; | ||
Offer* offer{nullptr}; | ||
BrokerContext& context; | ||
|
||
ScheduleRes trading; | ||
|
||
bool traded{false}; | ||
|
||
EnergyManagerConfig config; | ||
}; | ||
|
||
} // namespace module | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?