Skip to content
Merged
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
73 changes: 32 additions & 41 deletions apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import os
import yaml

THIS_VERSION = "v7.15.0"
THIS_VERSION = "v7.15.1"
TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
TIME_FORMAT_SECONDS = "%Y-%m-%dT%H:%M:%S.%f%z"
TIME_FORMAT_OCTOPUS = "%Y-%m-%d %H:%M:%S%z"
Expand Down Expand Up @@ -4839,10 +4839,8 @@ def find_charge_window(self, rates, minute, threshold_rate, find_high):
if (
((not find_high) and (rate <= threshold_rate))
or (find_high and (rate >= threshold_rate) and (rate > 0))
or (not find_high and (minute in self.manual_charge_times or minute in self.manual_idle_times))
or (not find_high and (rate_low_start in self.manual_charge_times or rate_low_start in self.manual_idle_times))
or (find_high and (minute in self.manual_discharge_times or minute in self.manual_idle_times))
or (find_high and (rate_low_start in self.manual_discharge_times or minute in self.manual_idle_times))
or minute in self.manual_all_times
or rate_low_start in self.manual_all_times
):
if (not self.combine_mixed_rates) and (rate_low_start >= 0) and (int(self.dp2(rate) + 0.5) != int(self.dp2(rate_low_rate) + 0.5)):
# Refuse mixed rates that are different by more than 0.5p
Expand All @@ -4856,32 +4854,8 @@ def find_charge_window(self, rates, minute, threshold_rate, find_high):
# If combine is disabled, for import slots make them all N minutes so we can select some not all
rate_low_end = minute
break
if (
(not find_high)
and (
rate_low_start in self.manual_charge_times
or rate_low_start in self.manual_idle_times
or minute in self.manual_charge_times
or minute in self.manual_idle_times
)
and (rate_low_start >= 0)
and ((minute - rate_low_start) >= 30)
):
# Manual import slot
rate_low_end = minute
break
if (
find_high
and (
rate_low_start in self.manual_discharge_times
or rate_low_start in self.manual_idle_times
or minute in self.manual_discharge_times
or minute in self.manual_idle_times
)
and (rate_low_start >= 0)
and ((minute - rate_low_start) >= 30)
):
# Manual export slot
if (rate_low_start in self.manual_all_times or minute in self.manual_all_times) and (rate_low_start >= 0) and ((minute - rate_low_start) >= 30):
# Manual slot
rate_low_end = minute
break
if find_high and (rate_low_start >= 0) and ((minute - rate_low_start) >= 60 * 4):
Expand Down Expand Up @@ -5543,7 +5517,7 @@ def rate_scan_window(self, rates, rate_low_min_window, threshold_rate, find_high
# Only count those starting inside the window, those outside appear for 'free' as they won't be optimised
if window_start >= (self.minutes_now + self.forecast_minutes):
selected_rates.append(window_id)
elif take_enable or (not find_high and window_start < upcoming_period):
elif take_enable or (not find_high and window_start < upcoming_period) or window_start in self.manual_all_times:
if window_price < lowest:
lowest = window_price
if window_price > highest:
Expand Down Expand Up @@ -6506,6 +6480,7 @@ def reset(self):
self.manual_charge_times = []
self.manual_discharge_times = []
self.manual_idle_times = []
self.manual_all_times = []
self.config_index = {}
self.dashboard_index = []
self.prefix = self.args.get("prefix", "predbat")
Expand Down Expand Up @@ -7519,7 +7494,13 @@ def discard_unused_charge_slots(self, charge_limit_best, charge_window_best, res
end = window["end"]
limit = charge_limit_best[window_n]

if new_window_best and (start == new_window_best[-1]["end"]) and (limit == new_limit_best[-1]):
if (
new_window_best
and (start == new_window_best[-1]["end"])
and (limit == new_limit_best[-1])
and (start not in self.manual_all_times)
and (new_window_best[-1]["start"] not in self.manual_all_times)
):
new_window_best[-1]["end"] = end
if self.debug_enable:
self.log(
Expand Down Expand Up @@ -7726,7 +7707,13 @@ def discard_unused_discharge_slots(self, discharge_limits_best, discharge_window
for window_n in range(0, len(discharge_limits_best)):
if discharge_limits_best[window_n] < 100.0:
# Also merge contiguous enabled windows
if new_best and (discharge_window_best[window_n]["start"] == new_best[-1]["end"]) and (discharge_limits_best[window_n] == new_enable[-1]):
if (
new_best
and (discharge_window_best[window_n]["start"] == new_best[-1]["end"])
and (discharge_limits_best[window_n] == new_enable[-1])
and (discharge_window_best[window_n]["start"] not in self.manual_all_times)
and (new_best[-1]["start"] not in self.manual_all_times)
):
new_best[-1]["end"] = discharge_window_best[window_n]["end"]
if self.debug_enable:
self.log("Combine discharge slot {} with previous - percent {} slot {}".format(window_n, new_enable[-1], new_best[-1]))
Expand All @@ -7753,7 +7740,7 @@ def tweak_plan(self, end_record, load_minutes_step, load_minutes_step10, pv_fore
window_n = window_index[key]["id"]
if typ == "c":
window_start = self.charge_window_best[window_n]["start"]
if self.calculate_best_charge and (window_start not in self.manual_charge_times) and (window_start not in self.manual_idle_times):
if self.calculate_best_charge and (window_start not in self.manual_all_times):
best_soc, best_metric, best_cost, soc_min, soc_min_minute, best_keep = self.optimise_charge_limit(
window_n,
record_charge_windows,
Expand All @@ -7770,7 +7757,7 @@ def tweak_plan(self, end_record, load_minutes_step, load_minutes_step10, pv_fore
self.charge_limit_best[window_n] = best_soc
else:
window_start = self.discharge_window_best[window_n]["start"]
if self.calculate_best_discharge and (window_start not in self.manual_discharge_times) and (window_start not in self.manual_idle_times):
if self.calculate_best_discharge and (window_start not in self.manual_all_times):
if not self.calculate_discharge_oncharge:
hit_charge = self.hit_charge_window(self.charge_window_best, self.discharge_window_best[window_n]["start"], self.discharge_window_best[window_n]["end"])
if hit_charge >= 0 and self.charge_limit_best[hit_charge] > 0.0:
Expand Down Expand Up @@ -7911,7 +7898,7 @@ def optimise_all_windows(self, load_minutes_step, load_minutes_step10, pv_foreca
self.log("Skip start at high window {} best limit {}".format(window_n, (self.charge_limit_best[window_n])))
continue

if self.calculate_best_charge and (window_start not in self.manual_charge_times) and (window_start not in self.manual_idle_times):
if self.calculate_best_charge and (window_start not in self.manual_all_times):
if not printed_set:
self.log("Optimise price set {} start_at_low {} best_price {}".format(price, start_at_low, best_price))
printed_set = True
Expand Down Expand Up @@ -7960,7 +7947,7 @@ def optimise_all_windows(self, load_minutes_step, load_minutes_step10, pv_foreca
if start_at_low and ((price > best_price) or (self.discharge_limits_best[window_n] == 100.0)):
continue

if self.calculate_best_discharge and (window_start not in self.manual_discharge_times) and (window_start not in self.manual_idle_times):
if self.calculate_best_discharge and (window_start not in self.manual_all_times):
if not self.calculate_discharge_oncharge:
hit_charge = self.hit_charge_window(
self.charge_window_best, self.discharge_window_best[window_n]["start"], self.discharge_window_best[window_n]["end"]
Expand Down Expand Up @@ -8047,7 +8034,7 @@ def optimise_all_windows(self, load_minutes_step, load_minutes_step10, pv_foreca
window_n = window_index[key]["id"]
if typ == "c":
window_start = self.charge_window_best[window_n]["start"]
if self.calculate_best_charge and (window_start not in self.manual_charge_times) and (window_start not in self.manual_idle_times):
if self.calculate_best_charge and (window_start not in self.manual_all_times):
best_soc, best_metric, best_cost, soc_min, soc_min_minute, best_keep = self.optimise_charge_limit(
window_n,
record_charge_windows,
Expand All @@ -8064,7 +8051,7 @@ def optimise_all_windows(self, load_minutes_step, load_minutes_step10, pv_foreca
self.charge_limit_best[window_n] = best_soc
else:
window_start = self.discharge_window_best[window_n]["start"]
if self.calculate_best_discharge and (window_start not in self.manual_discharge_times) and (window_start not in self.manual_idle_times):
if self.calculate_best_discharge and (window_start not in self.manual_all_times):
if not self.calculate_discharge_oncharge:
hit_charge = self.hit_charge_window(self.charge_window_best, self.discharge_window_best[window_n]["start"], self.discharge_window_best[window_n]["end"])
if hit_charge >= 0 and self.charge_limit_best[hit_charge] > 0.0:
Expand Down Expand Up @@ -9791,7 +9778,9 @@ def manual_select(self, config_item, value):
values_list = []
if values:
values_list = values.split(",")
if "[" in value:
if value == "reset":
values_list = []
elif "[" in value:
value = value.replace("[", "")
value = value.replace("]", "")
if value in values_list:
Expand Down Expand Up @@ -9843,6 +9832,7 @@ def manual_times(self, config_item):
time_values.append(minute_str)
if values not in time_values:
time_values.append(values)
time_values.append("reset")
item["options"] = time_values
if not values:
values = None
Expand Down Expand Up @@ -10058,6 +10048,7 @@ def fetch_config_options(self):
self.manual_charge_times = self.manual_times("manual_charge")
self.manual_discharge_times = self.manual_times("manual_discharge")
self.manual_idle_times = self.manual_times("manual_idle")
self.manual_all_times = self.manual_charge_times + self.manual_discharge_times + self.manual_idle_times
# Update list of config options to save/restore to
self.update_save_restore_list()

Expand Down