mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-08-31 12:46:38 +00:00
feat: complete 15-minute optimization support
This commit is contained in:
@@ -58,6 +58,8 @@ class Battery:
|
||||
self.max_charge_power_w = self.capacity_wh # TODO this should not be equal capacity_wh
|
||||
self.discharge_array = np.full(self.prediction_hours, 0)
|
||||
self.charge_array = np.full(self.prediction_hours, 0)
|
||||
self._discharged_raw_wh_per_slot = np.zeros(self.prediction_hours, dtype=float)
|
||||
self._charged_raw_wh_per_slot = np.zeros(self.prediction_hours, dtype=float)
|
||||
self.soc_wh = (self.initial_soc_percentage / 100) * self.capacity_wh
|
||||
self.min_soc_wh = (self.min_soc_percentage / 100) * self.capacity_wh
|
||||
self.max_soc_wh = (self.max_soc_percentage / 100) * self.capacity_wh
|
||||
@@ -101,6 +103,17 @@ class Battery:
|
||||
self.soc_wh = min(self.soc_wh, self.max_soc_wh) # Only clamp to max
|
||||
self.discharge_array = np.full(self.prediction_hours, 0)
|
||||
self.charge_array = np.full(self.prediction_hours, 0)
|
||||
self._discharged_raw_wh_per_slot = np.zeros(self.prediction_hours, dtype=float)
|
||||
self._charged_raw_wh_per_slot = np.zeros(self.prediction_hours, dtype=float)
|
||||
|
||||
def remaining_discharge_energy_wh(self, hour: int) -> float:
|
||||
"""Return DC energy still deliverable within one optimization slot."""
|
||||
raw_power_budget_wh = self.max_charge_power_w * self.slot_duration_h
|
||||
raw_power_remaining_wh = max(
|
||||
raw_power_budget_wh - self._discharged_raw_wh_per_slot[hour], 0.0
|
||||
)
|
||||
raw_soc_available_wh = max(self.soc_wh - self.min_soc_wh, 0.0)
|
||||
return min(raw_power_remaining_wh, raw_soc_available_wh) * self.discharging_efficiency
|
||||
|
||||
def set_discharge_per_hour(self, discharge_array: np.ndarray) -> None:
|
||||
"""Sets the discharge values for each hour."""
|
||||
@@ -151,8 +164,9 @@ class Battery:
|
||||
# Maximum raw discharge due to power limit, scaled to the slot duration.
|
||||
# max_charge_power_w is a power [W]; energy movable in one slot is
|
||||
# power x slot_duration_h.
|
||||
max_raw_wh = (
|
||||
self.max_charge_power_w * self.slot_duration_h
|
||||
max_raw_wh = max(
|
||||
self.max_charge_power_w * self.slot_duration_h - self._discharged_raw_wh_per_slot[hour],
|
||||
0.0,
|
||||
) # TODO rename to max_discharge_power_w
|
||||
|
||||
# Actual raw withdrawal (internal)
|
||||
@@ -170,6 +184,7 @@ class Battery:
|
||||
# Update SoC
|
||||
self.soc_wh -= raw_used_wh
|
||||
self.soc_wh = max(self.soc_wh, self.min_soc_wh)
|
||||
self._discharged_raw_wh_per_slot[hour] += raw_used_wh
|
||||
|
||||
# Losses
|
||||
losses_wh = raw_used_wh - delivered_wh
|
||||
@@ -246,7 +261,10 @@ class Battery:
|
||||
soc_wh_fast = self.soc_wh
|
||||
# Scale the power cap [W] to a per-slot energy cap [Wh] (W x slot hours).
|
||||
# At slot_duration_h=1.0 (hourly) this equals the legacy power value.
|
||||
max_charge_per_slot_wh_fast = self.max_charge_power_w * self.slot_duration_h
|
||||
max_charge_per_slot_wh_fast = max(
|
||||
self.max_charge_power_w * self.slot_duration_h - self._charged_raw_wh_per_slot[hour],
|
||||
0.0,
|
||||
)
|
||||
charging_efficiency_fast = self.charging_efficiency
|
||||
|
||||
# Decide mode & determine raw_request_wh and raw_charge_wh
|
||||
@@ -290,6 +308,7 @@ class Battery:
|
||||
)
|
||||
|
||||
self.soc_wh = new_soc
|
||||
self._charged_raw_wh_per_slot[hour] += raw_input_wh
|
||||
losses_wh = raw_input_wh - stored_wh
|
||||
|
||||
return stored_wh, losses_wh
|
||||
|
||||
@@ -74,9 +74,12 @@ class Inverter:
|
||||
grid_import = -remaining_power # Negative indicates feeding into the grid
|
||||
self_consumption = self.max_power_wh
|
||||
else:
|
||||
# Calculate scr using cached results per energy management/optimization run
|
||||
# Calculate scr using cached results per energy management/optimization run.
|
||||
# The interpolator expects power levels [W]; consumption/generation are
|
||||
# energy per slot [Wh], so convert via the slot duration (identical at
|
||||
# the hourly default, ×4 on the 15-minute grid).
|
||||
scr = self.self_consumption_predictor.calculate_self_consumption(
|
||||
consumption, generation
|
||||
consumption / self.slot_duration_h, generation / self.slot_duration_h
|
||||
)
|
||||
|
||||
# Remaining power after consumption
|
||||
@@ -133,12 +136,10 @@ class Inverter:
|
||||
|
||||
if allow_battery_grid_export and self.battery:
|
||||
export_capacity = max(self.max_power_wh - consumption - grid_export, 0.0)
|
||||
max_discharge_dc = getattr(self.battery, "max_charge_power_w", None)
|
||||
if max_discharge_dc is not None:
|
||||
remaining_battery_ac = max(
|
||||
(max_discharge_dc - from_battery_dc) * dc_to_ac_eff, 0.0
|
||||
)
|
||||
export_capacity = min(export_capacity, remaining_battery_ac)
|
||||
remaining_battery_ac = (
|
||||
self.battery.remaining_discharge_energy_wh(hour) * dc_to_ac_eff
|
||||
)
|
||||
export_capacity = min(export_capacity, remaining_battery_ac)
|
||||
battery_export_ac, battery_export_losses = self._discharge_battery_to_ac(
|
||||
export_capacity, hour
|
||||
)
|
||||
@@ -171,12 +172,10 @@ class Inverter:
|
||||
|
||||
if allow_battery_grid_export and self.battery and grid_import <= 0.0:
|
||||
export_capacity = max(self.max_power_wh - consumption, 0.0)
|
||||
max_discharge_dc = getattr(self.battery, "max_charge_power_w", None)
|
||||
if max_discharge_dc is not None:
|
||||
remaining_battery_ac = max(
|
||||
(max_discharge_dc - battery_discharge_dc) * dc_to_ac_eff, 0.0
|
||||
)
|
||||
export_capacity = min(export_capacity, remaining_battery_ac)
|
||||
remaining_battery_ac = (
|
||||
self.battery.remaining_discharge_energy_wh(hour) * dc_to_ac_eff
|
||||
)
|
||||
export_capacity = min(export_capacity, remaining_battery_ac)
|
||||
battery_export_ac, battery_export_losses = self._discharge_battery_to_ac(
|
||||
export_capacity, hour
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user