Skip to content

Commit

Permalink
iBoost smart min length feature (#1771)
Browse files Browse the repository at this point in the history
* iBoost smart min length feature

* [pre-commit.ci lite] apply automatic fixes

* Update customisation.md

* Extra test

* [pre-commit.ci lite] apply automatic fixes

* Updated

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
  • Loading branch information
springfall2008 and pre-commit-ci-lite[bot] authored Dec 21, 2024
1 parent 1697f56 commit a5516e5
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 38 deletions.
12 changes: 12 additions & 0 deletions apps/predbat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,18 @@
"enable": "iboost_enable",
"default": False,
},
{
"name": "iboost_smart_min_length",
"friendly_name": "iBoost smart min length",
"type": "input_number",
"min": 30,
"max": 120,
"step": 30,
"unit": "minutes",
"icon": "mdi:currency-usd",
"enable": "iboost_enable",
"default": 30,
},
{
"name": "iboost_on_export",
"oldname": "iboost_on_export",
Expand Down
4 changes: 3 additions & 1 deletion apps/predbat/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,13 +1076,14 @@ def fetch_sensor_data(self):
if self.iboost_enable and (((not self.iboost_solar) and (not self.iboost_charging)) or self.iboost_smart):
self.iboost_plan = self.plan_iboost_smart()
self.log(
"IBoost iboost_solar {} rate threshold import {} rate threshold export {} iboost_gas {} iboost_gas_export {} iboost_smart {} plan is: {}".format(
"IBoost iboost_solar {} rate threshold import {} rate threshold export {} iboost_gas {} iboost_gas_export {} iboost_smart {} min_length {} plan is: {}".format(
self.iboost_solar,
self.iboost_rate_threshold,
self.iboost_rate_threshold_export,
self.iboost_gas,
self.iboost_gas_export,
self.iboost_smart,
self.iboost_smart_min_length,
self.iboost_plan,
)
)
Expand Down Expand Up @@ -1856,6 +1857,7 @@ def fetch_config_options(self):
self.iboost_gas = self.get_arg("iboost_gas")
self.iboost_gas_export = self.get_arg("iboost_gas_export")
self.iboost_smart = self.get_arg("iboost_smart")
self.iboost_smart_min_length = self.get_arg("iboost_smart_min_length")
self.iboost_on_export = self.get_arg("iboost_on_export")
self.iboost_prevent_discharge = self.get_arg("iboost_prevent_discharge")
self.iboost_solar = self.get_arg("iboost_solar")
Expand Down
90 changes: 55 additions & 35 deletions apps/predbat/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -3109,13 +3109,24 @@ def plan_iboost_smart(self):
iboost_today = self.iboost_today
iboost_max = self.iboost_max_energy
iboost_power = self.iboost_max_power * 60
iboost_min_length = max(int((self.iboost_smart_min_length + 29) / 30) * 30, 30)

self.log("Create iBoost smart plan, max {} kWh, power {} kW, min length {} minutes".format(iboost_max, iboost_power, iboost_min_length))

low_rates = []
start_minute = int(self.minutes_now / 30) * 30
for minute in range(start_minute, start_minute + self.forecast_minutes, 30):
import_rate = self.rate_import.get(minute, self.rate_min)
export_rate = self.rate_export.get(minute, 0)
low_rates.append({"start": minute, "end": minute + 30, "average": import_rate, "export": export_rate})
import_rate = 0
export_rate = 0
slot_length = 0
slot_count = 0
for slot_start in range(minute, minute + iboost_min_length, 30):
import_rate += self.rate_import.get(minute, self.rate_min)
export_rate += self.rate_export.get(minute, 0)
slot_length += 30
slot_count += 1
if slot_count:
low_rates.append({"start": minute, "end": minute + slot_length, "average": import_rate / slot_count, "export": export_rate / slot_count})

# Get prices
if self.iboost_smart:
Expand All @@ -3128,6 +3139,7 @@ def plan_iboost_smart(self):
iboost_soc = [0 for n in range(total_days)]
iboost_soc[0] = iboost_today

used_slots = {}
for window_n in price_sorted:
window = low_rates[window_n]
price = window["average"]
Expand All @@ -3140,47 +3152,55 @@ def plan_iboost_smart(self):
day_start_minutes = day * 24 * 60
day_end_minutes = day_start_minutes + 24 * 60

start = max(window["start"], self.minutes_now, day_start_minutes)
end = min(window["end"], day_end_minutes)
slot_start = max(window["start"], self.minutes_now, day_start_minutes)
slot_end = min(window["end"], day_end_minutes)

if start < end:
if slot_start < slot_end:
rate_okay = True

# Boost on import/export rate
if price > self.iboost_rate_threshold:
rate_okay = False
if export_price > self.iboost_rate_threshold_export:
rate_okay = False
for start in range(slot_start, slot_end, 30):
end = min(start + 30, slot_end)

# Boost on gas rate vs import price
if self.iboost_gas and self.rate_gas:
gas_rate = self.rate_gas.get(start, 99) * self.iboost_gas_scale
if price > gas_rate:
# Avoid duplicate slots
if minute in used_slots:
rate_okay = False

# Boost on gas rate vs export price
if self.iboost_gas_export and self.rate_gas:
gas_rate = self.rate_gas.get(start, 99) * self.iboost_gas_scale
if export_price > gas_rate:
# Boost on import/export rate
if price > self.iboost_rate_threshold:
rate_okay = False
if export_price > self.iboost_rate_threshold_export:
rate_okay = False

if not rate_okay:
continue
# Boost on gas rate vs import price
if self.iboost_gas and self.rate_gas:
gas_rate = self.rate_gas.get(start, 99) * self.iboost_gas_scale
if price > gas_rate:
rate_okay = False

# Boost on gas rate vs export price
if self.iboost_gas_export and self.rate_gas:
gas_rate = self.rate_gas.get(start, 99) * self.iboost_gas_scale
if export_price > gas_rate:
rate_okay = False

if not rate_okay:
continue

# Work out charging amounts
length = end - start
hours = length / 60
kwh = iboost_power * hours
kwh = min(kwh, iboost_max - iboost_soc[day])
if kwh > 0:
new_slot = {}
new_slot["start"] = start
new_slot["end"] = end
new_slot["kwh"] = dp3(kwh)
new_slot["average"] = window["average"]
new_slot["cost"] = dp2(new_slot["average"] * kwh)
plan.append(new_slot)
iboost_soc[day] = dp3(iboost_soc[day] + kwh)
# Work out charging amounts
length = end - start
hours = length / 60
kwh = iboost_power * hours
kwh = min(kwh, iboost_max - iboost_soc[day])
if kwh > 0:
iboost_soc[day] = dp3(iboost_soc[day] + kwh)
new_slot = {}
new_slot["start"] = start
new_slot["end"] = end
new_slot["kwh"] = dp3(kwh)
new_slot["average"] = window["average"]
new_slot["cost"] = dp2(new_slot["average"] * kwh)
plan.append(new_slot)
used_slots[start] = True

# Return sorted back in time order
plan = self.sort_window_by_time(plan)
Expand Down
1 change: 1 addition & 0 deletions apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ def reset(self):
self.iboost_gas = False
self.iboost_gas_export = False
self.iboost_smart = False
self.iboost_smart_min_length = 30
self.iboost_on_export = False
self.iboost_prevent_discharge = False
self.iboost_smart_threshold = 0
Expand Down
57 changes: 55 additions & 2 deletions apps/predbat/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ def simple_scenario(
gas_scale=1.0,
iboost_charging=False,
iboost_max_energy=100.0,
iboost_smart_min_length=30,
assert_final_iboost=0.0,
end_record=None,
pv10=False,
Expand Down Expand Up @@ -470,6 +471,7 @@ def simple_scenario(
my_predbat.iboost_min_power = 0.0
my_predbat.iboost_max_power = export_limit / 60.0
my_predbat.iboost_max_energy = iboost_max_energy
my_predbat.iboost_smart_min_length = iboost_smart_min_length
my_predbat.iboost_on_export = iboost_on_export
my_predbat.iboost_prevent_discharge = iboost_prevent_discharge
my_predbat.rate_gas = {n: rate_gas for n in range(my_predbat.forecast_minutes + my_predbat.minutes_now)}
Expand Down Expand Up @@ -908,9 +910,9 @@ def run_single_debug(my_predbat, debug_file):

# Force off combine export XXX:
print("Combined export slots {} min_improvement_export {}".format(my_predbat.combine_export_slots, my_predbat.metric_min_improvement_export))
# my_predbat.combine_export_slots = False
my_predbat.combine_export_slots = False
# my_predbat.best_soc_keep = 1.0
# my_predbat.metric_min_improvement_export = 5
my_predbat.metric_min_improvement_export = 5

if re_do_rates:
# Set rate thresholds
Expand Down Expand Up @@ -4084,6 +4086,57 @@ def run_model_tests(my_predbat):
assert_iboost_running=True,
assert_iboost_running_full=True,
)
failed |= simple_scenario(
"iboost_smart1",
my_predbat,
0,
0,
assert_final_metric=import_rate * 120,
assert_final_soc=0,
with_battery=False,
iboost_enable=True,
iboost_charging=False,
iboost_smart=True,
assert_final_iboost=120,
iboost_max_energy=60,
assert_iboost_running=True,
assert_iboost_running_full=True,
)
failed |= simple_scenario(
"iboost_smart2",
my_predbat,
0,
0,
assert_final_metric=import_rate * 120 * 1.5,
assert_final_soc=0,
with_battery=False,
iboost_enable=True,
iboost_charging=False,
iboost_smart=True,
assert_final_iboost=120,
iboost_max_energy=60,
iboost_smart_min_length=60,
assert_iboost_running=True,
assert_iboost_running_full=True,
)
failed |= simple_scenario(
"iboost_smart3",
my_predbat,
0,
0,
assert_final_metric=import_rate * 120 * 1.5 - 2 * import_rate * 5 * 2,
assert_final_soc=0,
with_battery=False,
iboost_enable=True,
iboost_charging=False,
iboost_smart=True,
assert_final_iboost=110,
iboost_max_energy=55,
iboost_smart_min_length=60,
assert_iboost_running=True,
assert_iboost_running_full=True,
)

failed |= simple_scenario(
"iboost_rate_pv1",
my_predbat,
Expand Down
3 changes: 3 additions & 0 deletions docs/customisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ Only slots of at or below the rate threshold will be selected.

Note this option only applies when iboost_solar and iboost battery are both off.

- **input_number.predbat_iboost_smart_min_length** Sets the minimum slot length in minutes to iBoost for (only applies for energy rate only modes).
The default is 30 minutes but can be set in multiples of 30. Increasing this slot size could increase costs depending on your tariff.

- **switch.predbat_iboost_on_export** If set to on allows iBoost to run even if the battery is force exporting to the grid, otherwise it won't run in these circumstances.

- **switch.iboost_prevent_discharge** When set will stop your battery from discharging when iBoost is active and thus prevent your battery from draining to the diverter.
Expand Down

0 comments on commit a5516e5

Please sign in to comment.