Skip to content
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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions modules/API/API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
zero_entry.limits_to_leaves.total_power_W = {0};
zero_entry.limits_to_leaves.total_power_W = {0, "API_module"};

?


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"};
Copy link
Contributor

Choose a reason for hiding this comment

The 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;
}
Expand Down
248 changes: 246 additions & 2 deletions modules/EnergyManager/Broker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (globals.debug)
EVLOG_info << local_market.energy_flow_request.uuid << " Broker: " << *offer;
if (globals.debug) {
EVLOG_info << local_market.energy_flow_request.uuid << " Broker: " << *offer;
}


// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helps a little bit when reading through lines L46 - L57

Suggested change
// Run the actual broker implementation, depending on strategy
// Run the actual broker implementation, depending on strategy. This may change the value of traded

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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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
51 changes: 47 additions & 4 deletions modules/EnergyManager/Broker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,62 @@
// base class for different Brokers
class Broker {
Copy link
Contributor

Choose a reason for hiding this comment

The 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};

Check notice on line 50 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L50

struct member 'EnergyManagerConfig::switch_1ph_3ph_mode' is never used.
StickyNess stickyness{StickyNess::DontChange};

Check notice on line 51 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L51

struct member 'EnergyManagerConfig::stickyness' is never used.
int max_nr_of_switches_per_session{0};

Check notice on line 52 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L52

struct member 'EnergyManagerConfig::max_nr_of_switches_per_session' is never used.
int power_hysteresis_W{200};

Check notice on line 53 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L53

struct member 'EnergyManagerConfig::power_hysteresis_W' is never used.
int time_hysteresis_s{600};

Check notice on line 54 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L54

struct member 'EnergyManagerConfig::time_hysteresis_s' is never used.
};

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;

Check notice on line 84 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L84

class member 'Broker::num_phases' is never used.
Offer* offer{nullptr};

Check notice on line 85 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L85

class member 'Broker::offer' is never used.
BrokerContext& context;

ScheduleRes trading;

Check notice on line 88 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L88

class member 'Broker::trading' is never used.

bool traded{false};

Check notice on line 90 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L90

class member 'Broker::traded' is never used.

EnergyManagerConfig config;

Check notice on line 92 in modules/EnergyManager/Broker.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EnergyManager/Broker.hpp#L92

class member 'Broker::config' is never used.
};

} // namespace module
Expand Down
Loading
Loading