Skip to content
Merged
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
3 changes: 2 additions & 1 deletion apps/predbat/config/apps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ pred_bat:
# - date: '2023-09-10'
# start: '14:00:00'
# end: '14:30:00'
# rate: 5
# rate: 112
# load_scaling: 0.8

# For pv estimate, leave blank for central estimate, or add 10 for 10% curve (worst case) or 90 or 90% curve (best case)
# If you use 10 then disable pv_metric10_weight below
Expand Down
49 changes: 43 additions & 6 deletions apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@
"icon": "mdi:multiplication",
"default": 1.1,
},
{
"name": "load_scaling_saving",
"friendly_name": "Load Scaling for saving sessions",
"type": "input_number",
"min": 0,
"max": 2.0,
"step": 0.01,
"unit": "multiple",
"icon": "mdi:multiplication",
"default": 1.0,
},
{
"name": "battery_rate_max_scaling",
"friendly_name": "Battery rate max scaling",
Expand Down Expand Up @@ -3639,7 +3650,7 @@ def get_cloud_factor(self, minutes_now, pv_data, pv_data10):
else:
return None

def step_data_history(self, item, minutes_now, forward, step=PREDICT_STEP, scale_today=1.0, type_load=False, load_forecast={}, cloud_factor=None):
def step_data_history(self, item, minutes_now, forward, step=PREDICT_STEP, scale_today=1.0, type_load=False, load_forecast={}, cloud_factor=None, load_scaling_dynamic=None):
"""
Create cached step data for historical array
"""
Expand All @@ -3649,6 +3660,10 @@ def step_data_history(self, item, minutes_now, forward, step=PREDICT_STEP, scale
value = 0
minute_absolute = minute + minutes_now

scaling_dynamic = 1.0
if load_scaling_dynamic:
scaling_dynamic = load_scaling_dynamic.get(minute_absolute, scaling_dynamic)

# Reset in-day adjustment for tomorrow
if (minute + minutes_now) > 24 * 60:
scale_today = 1.0
Expand All @@ -3668,7 +3683,7 @@ def step_data_history(self, item, minutes_now, forward, step=PREDICT_STEP, scale
if load_forecast:
for offset in range(0, step):
load_extra += self.get_from_incrementing(load_forecast, minute_absolute, backwards=False)
values[minute] = value * scale_today + load_extra
values[minute] = (value * scale_today + load_extra) * scaling_dynamic

# Simple cloud model keeps the same generation but brings PV generation up and down every 5 minutes
if cloud_factor and cloud_factor > 0:
Expand Down Expand Up @@ -4918,6 +4933,7 @@ def basic_rates(self, info, rtype, prev=None):
start_str = self.resolve_arg("start", start_str, "00:00:00")
end_str = this_rate.get("end", "00:00:00")
end_str = self.resolve_arg("end", end_str, "00:00:00")
load_scaling = this_rate.get("load_scaling", None)

if start_str.count(":") < 2:
start_str += ":00"
Expand Down Expand Up @@ -4978,8 +4994,12 @@ def basic_rates(self, info, rtype, prev=None):
for minute in range(start_minutes, end_minutes):
if (not date) or (minute >= 0 and minute < max_minute):
rates[minute % max_minute] = rate
if load_scaling is not None:
self.load_scaling_dynamic[minute % max_minute] = load_scaling
if not date and not prev:
rates[(minute % max_minute) + max_minute] = rate
if load_scaling is not None:
self.load_scaling_dynamic[(minute % max_minute) + max_minute] = load_scaling

return rates

Expand Down Expand Up @@ -5132,6 +5152,7 @@ def load_saving_slot(self, octopus_saving_slots, export=False):
self.rate_export[minute] += rate
else:
self.rate_import[minute] += rate
self.load_scaling_dynamic[minute] = self.load_scaling_saving

def load_octopus_slots(self, octopus_slots):
"""
Expand Down Expand Up @@ -6627,6 +6648,7 @@ def reset(self):
self.metric_cloud_coverage = 0.0
self.future_energy_rates_import = {}
self.future_energy_rates_export = {}
self.load_scaling_dynamic = {}

def optimise_charge_limit_price(
self,
Expand Down Expand Up @@ -8627,10 +8649,22 @@ def calculate_plan(self, recompute=True):
# Created optimised step data
self.metric_cloud_coverage = self.get_cloud_factor(self.minutes_now, self.pv_forecast_minute, self.pv_forecast_minute10)
load_minutes_step = self.step_data_history(
self.load_minutes, self.minutes_now, forward=False, scale_today=self.load_inday_adjustment, type_load=True, load_forecast=self.load_forecast
self.load_minutes,
self.minutes_now,
forward=False,
scale_today=self.load_inday_adjustment,
type_load=True,
load_forecast=self.load_forecast,
load_scaling_dynamic=self.load_scaling_dynamic,
)
load_minutes_step10 = self.step_data_history(
self.load_minutes, self.minutes_now, forward=False, scale_today=self.load_inday_adjustment * self.load_scaling10, type_load=True, load_forecast=self.load_forecast
self.load_minutes,
self.minutes_now,
forward=False,
scale_today=self.load_inday_adjustment * self.load_scaling10,
type_load=True,
load_forecast=self.load_forecast,
load_scaling_dynamic=self.load_scaling_dynamic,
)
pv_forecast_minute_step = self.step_data_history(self.pv_forecast_minute, self.minutes_now, forward=True, cloud_factor=self.metric_cloud_coverage)
pv_forecast_minute10_step = self.step_data_history(self.pv_forecast_minute10, self.minutes_now, forward=True, cloud_factor=self.metric_cloud_coverage)
Expand Down Expand Up @@ -9255,6 +9289,7 @@ def fetch_sensor_data(self):
self.load_forecast = {}
self.pv_forecast_minute = {}
self.pv_forecast_minute10 = {}
self.load_scaling_dynamic = {}

# Iboost load data
if self.iboost_enable:
Expand Down Expand Up @@ -9772,6 +9807,7 @@ def manual_times(self, config_item):
"""
time_overrides = []
minutes_now = int(self.minutes_now / 30) * 30
manual_time_max = 18 * 60

# Deconstruct the value into a list of minutes
item = self.config_index.get(config_item)
Expand All @@ -9788,7 +9824,7 @@ def manual_times(self, config_item):
minutes = start_time.hour * 60 + start_time.minute
if minutes < minutes_now:
minutes += 24 * 60
if (minutes - minutes_now) < 12 * 60:
if (minutes - minutes_now) < manual_time_max:
time_overrides.append(minutes)

# Reconstruct the list in order based on minutes
Expand All @@ -9800,7 +9836,7 @@ def manual_times(self, config_item):

# Create the new dropdown
time_values = []
for minute in range(minutes_now, minutes_now + 12 * 60, 30):
for minute in range(minutes_now, minutes_now + manual_time_max, 30):
minute_str = (self.midnight + timedelta(minutes=minute)).strftime("%H:%M:%S")
if minute in time_overrides:
minute_str = "[" + minute_str + "]"
Expand Down Expand Up @@ -9888,6 +9924,7 @@ def fetch_config_options(self):
self.pv_metric10_weight = self.get_arg("pv_metric10_weight")
self.load_scaling = self.get_arg("load_scaling")
self.load_scaling10 = self.get_arg("load_scaling10")
self.load_scaling_saving = self.get_arg("load_scaling_saving")
self.battery_rate_max_scaling = self.get_arg("battery_rate_max_scaling")

self.best_soc_step = 0.25
Expand Down
5 changes: 4 additions & 1 deletion docs/customisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ Use 1.0 to use exactly previous load data (1.1 would add 10% to load)
This can be used to make the 10% scenario take into account extra load usage and hence be more pessimistic while leaving the central
scenario unchanged. The default is 1.1 meaning an extra 10% load is added. This will only have an impact if the PV 10% weighting is non-zero.

**input_number.load_scaling_saving** is a Scaling factor applied to historical load only during Octopus Saving sessions. This can be used to model
your household cutting down on energy use only inside a session (e.g. turning off a heat pump).

**input_number.pv_scaling** is a scaling factor applied to PV data, tune down if you want to be more pessimistic on PV production vs Solcast
Use 1.0 to use exactly the Solcast data (0.9 would remove 10% from forecast)

Expand Down Expand Up @@ -405,7 +408,7 @@ manual force charge feature. The force discharge takes priority over force charg
The **select.predbat_manual_idle** selector is used to force Predbat to be idle during a 30 minute slot, this implies no charging or discharging and thus the
battery will cover the house load (if there is enough charge).

When you use the manual override features you can only select times in the next 12 hours, the overrides will be removed once their time
When you use the manual override features you can only select times in the next 18 hours, the overrides will be removed once their time
slot expires (they do not repeat).

_CAUTION: If you leave Predbat turned off for a long period of time then the override timeslots could end up repeating when you restart_
Expand Down
17 changes: 17 additions & 0 deletions docs/energy-rates.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ In the Predbat plan, for joined saving sessions the energy rates for import and
The assumed rate will be taken from the Octopus Energy integration and converted into pence
using the **octopus_saving_session_octopoints_per_penny** configuration item in apps.yaml (default is 8).

If you normally cut back your house usage during a saving session then you can change **input_number.predbat_load_scaling_saving** to allow Predbat to assume an energy
reduction in this period. E.g. setting to a value of 0.8 would indicate you will use 80% of the normal consumption in that period (a 20% reduction).

As the saving session import and export rates are very high compared to normal Predbat will plan additional export during the saving session period.
If necessary, a pre-charge may happen at some point during the day to maintain the battery right level for the session.

Expand Down Expand Up @@ -184,7 +187,21 @@ rates_export_override:
rate : pence
```

Optionally you can add a predicted load scaling in these periods using **load_scaling** for example:

```yaml
rates_import_override:
- date: '2024-01-21'
start: '17:30:00'
end: '18:30:00'
rate: 150
load_scaling: 0.8
```

Would say that during a 1 hour period at 5:30-6:30pm on 21st of Jan set the import rate to 150p and assume our load will be 80% of normal (20% lower).

**date** is in the date format of "YYYY-MM-DD" e.g. "2023-09-09", **start** and **end** in "HH:MM:SS" time format e.g. "12:30:00", and **rate** in pence.
**load_scaling** is a factor, where 1.0 would be no change, 0.8 is 80% of nominal.

## Rate offsets

Expand Down