From dc23355d8e27a39781059de8d597f8848a5b71e6 Mon Sep 17 00:00:00 2001 From: iainfogg Date: Sun, 22 Sep 2024 17:51:32 +0100 Subject: [PATCH 1/9] Add Jinja2 to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index d5dc8c4d..ed056a15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ appdaemon mkdocs pre-commit pytz +Jinja2 From 53e044a7e05d330fa9d38ebd699d5706b36f05fc Mon Sep 17 00:00:00 2001 From: iainfogg Date: Sun, 22 Sep 2024 16:59:44 +0000 Subject: [PATCH 2/9] Add initial cut of templating --- apps/predbat/templates/apps.html | 13 ++ apps/predbat/templates/charts.html | 19 +++ apps/predbat/templates/config.html | 13 ++ apps/predbat/templates/dash.html | 10 ++ apps/predbat/templates/docs.html | 9 ++ apps/predbat/templates/layout.html | 89 +++++++++++ apps/predbat/templates/logs.html | 16 ++ apps/predbat/templates/plan.html | 11 ++ apps/predbat/web.py | 248 +++++++++++++++++------------ 9 files changed, 330 insertions(+), 98 deletions(-) create mode 100644 apps/predbat/templates/apps.html create mode 100644 apps/predbat/templates/charts.html create mode 100644 apps/predbat/templates/config.html create mode 100644 apps/predbat/templates/dash.html create mode 100644 apps/predbat/templates/docs.html create mode 100644 apps/predbat/templates/layout.html create mode 100644 apps/predbat/templates/logs.html create mode 100644 apps/predbat/templates/plan.html diff --git a/apps/predbat/templates/apps.html b/apps/predbat/templates/apps.html new file mode 100644 index 00000000..63999d19 --- /dev/null +++ b/apps/predbat/templates/apps.html @@ -0,0 +1,13 @@ +{% extends "layout.html" %} + +{% block title %}Home{% endblock %} + +{% block content %} +

Predbat Apps.yaml

+ +
+ {% autoescape false %} + {{ apps_html }} + {% endautoescape %} +
+{% endblock %} diff --git a/apps/predbat/templates/charts.html b/apps/predbat/templates/charts.html new file mode 100644 index 00000000..ccb28148 --- /dev/null +++ b/apps/predbat/templates/charts.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} + +{% block title %}Home{% endblock %} + +{% block content %} +

{{ chart_title }} Chart

+ - Battery + Power + Cost + Rates + InDay + PV + PV7 +
+ {% autoescape false %} + {{ chart_html }} + {% endautoescape %} +
+{% endblock %} diff --git a/apps/predbat/templates/config.html b/apps/predbat/templates/config.html new file mode 100644 index 00000000..8dd1cd6d --- /dev/null +++ b/apps/predbat/templates/config.html @@ -0,0 +1,13 @@ +{% extends "layout.html" %} + +{% block title %}Home{% endblock %} + +{% block content %} +

Predbat Config

+ +
+ {% autoescape false %} + {{ config_html }} + {% endautoescape %} +
+{% endblock %} diff --git a/apps/predbat/templates/dash.html b/apps/predbat/templates/dash.html new file mode 100644 index 00000000..f1f387f9 --- /dev/null +++ b/apps/predbat/templates/dash.html @@ -0,0 +1,10 @@ +{% extends "layout.html" %} + +{% block title %}Home{% endblock %} + + +{% block content %} +{% autoescape false %} +{{ dash_html }} +{% endautoescape %} +{% endblock %} diff --git a/apps/predbat/templates/docs.html b/apps/predbat/templates/docs.html new file mode 100644 index 00000000..1a055110 --- /dev/null +++ b/apps/predbat/templates/docs.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +{% block title %}Home{% endblock %} + +{% block content %} +
+ +
+{% endblock %} diff --git a/apps/predbat/templates/layout.html b/apps/predbat/templates/layout.html new file mode 100644 index 00000000..8b034bf5 --- /dev/null +++ b/apps/predbat/templates/layout.html @@ -0,0 +1,89 @@ + + + + Predbat Web Interface + + + + {% if refresh is defined %} + + {% endif %} + + + +
+ + + + + + + + + + + +

Predbat

DashPlanChartsConfigapps.yamlLogDocs
+
+ {% block content %}{% endblock %} + + diff --git a/apps/predbat/templates/logs.html b/apps/predbat/templates/logs.html new file mode 100644 index 00000000..8a5788a7 --- /dev/null +++ b/apps/predbat/templates/logs.html @@ -0,0 +1,16 @@ +{% extends "layout.html" %} + +{% block title %}Home{% endblock %} + + +{% block content %} +

Logfile ({% if errors %}Errors{% elif warnings %}Warnings{% else %}All{% endif %})

+ - All Warnings Errors
+ + {% for line in lines %} + + {% endfor %} +
{{ line.line_no}}{{ line.start_line }} {{ line.rest_line }}
+ {% autoescape false %} + {% endautoescape %} +{% endblock %} diff --git a/apps/predbat/templates/plan.html b/apps/predbat/templates/plan.html new file mode 100644 index 00000000..d3a5e486 --- /dev/null +++ b/apps/predbat/templates/plan.html @@ -0,0 +1,11 @@ +{% extends "layout.html" %} + +{% block title %}Home{% endblock %} + + +{% block content %} +Plan +{% autoescape false %} +{{ plan_html }} +{% endautoescape %} +{% endblock %} diff --git a/apps/predbat/web.py b/apps/predbat/web.py index 980ed327..57292b69 100644 --- a/apps/predbat/web.py +++ b/apps/predbat/web.py @@ -20,6 +20,15 @@ def __init__(self, base) -> None: self.pv_power_hist = {} self.pv_forecast_hist = {} + from jinja2 import Environment, FileSystemLoader, select_autoescape + self.template_env = Environment( + loader=FileSystemLoader("templates"), + autoescape=select_autoescape() + ) + + # Disable autoescaping for the HTML plan + self.template_env.filters['raw'] = lambda value: value + def history_attribute(self, history, state_key="state", last_updated_key="last_updated", scale=1.0): results = {} if history: @@ -60,8 +69,8 @@ def history_update(self): async def start(self): # Start the web server on port 5052 - app = web.Application() - app.router.add_get("/", self.html_index) + app = web.Application(debug=True) + app.router.add_get("/", self.html_dash) app.router.add_get("/plan", self.html_plan) app.router.add_get("/log", self.html_log) app.router.add_get("/menu", self.html_menu) @@ -69,7 +78,8 @@ async def start(self): app.router.add_get("/charts", self.html_charts) app.router.add_get("/config", self.html_config) app.router.add_post("/config", self.html_config_post) - app.router.add_get("/dash", self.html_dash) + # app.router.add_get("/dash", self.html_dash) + app.router.add_get("/docs", self.html_docs) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, "0.0.0.0", 5052) @@ -266,24 +276,15 @@ def render_chart(self, series_data, yaxis_name, chart_name, now_str): -" - - if refresh: - text += ''.format(refresh) - text += "\n" - return text - def get_entity_detailedForecast(self, entity, subitem="pv_estimate"): results = {} detailedForecast = self.base.dashboard_values.get(entity, {}).get("attributes", {}).get("detailedForecast", {}) @@ -373,13 +308,7 @@ async def html_plan(self, request): """ Return the Predbat plan as an HTML page """ - # self.default_page = "./plan" - # html_plan = self.base.html_plan - # text = self.get_header("Predbat Plan", refresh=60) - # text += "{}\n".format(html_plan) - # return web.Response(content_type="text/html", text=text) - - template = self.template_env.get_template("plan.html") + template = self.template_env.get_template('plan.html') context = { "default": self.default_page, @@ -545,12 +474,9 @@ async def html_dash(self, request): Render apps.yaml as an HTML page """ self.default_page = "./dash" - # text = self.get_header("Predbat Dashboard") - # text += "\n" + soc_perc = calc_percent_limit(self.base.soc_kw, self.base.soc_max) text = self.get_status_html(soc_perc, self.base.current_status) - # text += "\n" - # return web.Response(content_type="text/html", text=text) template = self.template_env.get_template("dash.html") @@ -706,22 +632,8 @@ async def html_charts(self, request): args = request.query chart = args.get("chart", "Battery") self.default_page = "./charts?chart={}".format(chart) - text = "" - # text = self.get_header("Predbat Config") - # text += "\n" - # text += "

{} Chart

\n".format(chart) - # text += '- Battery ' - # text += 'Power ' - # text += 'Cost ' - # text += 'Rates ' - # text += 'InDay ' - # text += 'PV ' - # text += 'PV7 ' - - text += '
' + text = '
' text += self.get_chart(chart=chart) - # text += "\n" - # return web.Response(content_type="text/html", text=text) template = self.template_env.get_template("charts.html") @@ -738,10 +650,8 @@ async def html_apps(self, request): Render apps.yaml as an HTML page """ self.default_page = "./apps" - text = "" - # text = self.get_header("Predbat Config") - # text += "\n" - text += "\n" + + text = "
\n" text += "\n'.format(arg, self.render_type(arg, value)) text += "
NameValue\n" args = self.base.args @@ -756,8 +666,6 @@ async def html_apps(self, request): text += '
{}{}
" - # text += "\n" - # return web.Response(content_type="text/html", text=text) template = self.template_env.get_template("apps.html") @@ -774,10 +682,8 @@ async def html_config(self, request): """ self.default_page = "./config" - text = "" - # text = self.get_header("Predbat Config", refresh=60) - # text += "\n" - text += '
\n' + + text = '\n' text += "\n" text += "\n" @@ -833,8 +739,6 @@ async def html_config(self, request): text += "
NameEntityTypeCurrentDefaultSelect
" text += "
" - # text += "\n" - # return web.Response(content_type="text/html", text=text) template = self.template_env.get_template("config.html") @@ -878,3 +782,9 @@ async def html_docs(self, request): rendered_template = template.render(context) return web.Response(text=rendered_template, content_type="text/html") + + async def update_message(self, request): + return web.Response( + text='Please update to the latest version of the add-on or Dockerfile, as you have missing dependencies', + content_type='text/html' + ) \ No newline at end of file From f7f3e22f3f8fa1f0194226ffa6fe408f7ba423bc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:07:54 +0000 Subject: [PATCH 5/9] [pre-commit.ci lite] apply automatic fixes --- apps/predbat/environment.py | 7 +++++-- apps/predbat/web.py | 18 +++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/predbat/environment.py b/apps/predbat/environment.py index 92c7ff5f..c9341bab 100644 --- a/apps/predbat/environment.py +++ b/apps/predbat/environment.py @@ -1,16 +1,19 @@ def is_package_installed(package_name: str) -> bool: try: import importlib + module = importlib.import_module(package_name) return True except ImportError: return False + def is_jinja2_installed() -> bool: - return is_package_installed('jinja2') + return is_package_installed("jinja2") + def is_appdaemon_environment() -> bool: - if is_package_installed('appdaemon'): + if is_package_installed("appdaemon"): return True else: return False diff --git a/apps/predbat/web.py b/apps/predbat/web.py index 04eaa8d0..dc6bca5e 100644 --- a/apps/predbat/web.py +++ b/apps/predbat/web.py @@ -11,6 +11,7 @@ from config import TIME_FORMAT, TIME_FORMAT_SECONDS from environment import is_jinja2_installed + class WebInterface: def __init__(self, base) -> None: self.abort = False @@ -23,13 +24,11 @@ def __init__(self, base) -> None: # Set up Jinja2 templating (as long as installed) if is_jinja2_installed(): from jinja2 import Environment, FileSystemLoader, select_autoescape - self.template_env = Environment( - loader=FileSystemLoader("templates"), - autoescape=select_autoescape() - ) + + self.template_env = Environment(loader=FileSystemLoader("templates"), autoescape=select_autoescape()) # Disable autoescaping for the HTML plan - self.template_env.filters['raw'] = lambda value: value + self.template_env.filters["raw"] = lambda value: value def history_attribute(self, history, state_key="state", last_updated_key="last_updated", scale=1.0): results = {} @@ -87,7 +86,7 @@ async def start(self): app.router.add_get("/docs", self.html_docs) else: # Display the missing dependencies/update message - app.router.add_get('/', self.update_message) + app.router.add_get("/", self.update_message) runner = web.AppRunner(app) await runner.setup() @@ -308,7 +307,7 @@ async def html_plan(self, request): """ Return the Predbat plan as an HTML page """ - template = self.template_env.get_template('plan.html') + template = self.template_env.get_template("plan.html") context = { "default": self.default_page, @@ -784,7 +783,4 @@ async def html_docs(self, request): return web.Response(text=rendered_template, content_type="text/html") async def update_message(self, request): - return web.Response( - text='Please update to the latest version of the add-on or Dockerfile, as you have missing dependencies', - content_type='text/html' - ) \ No newline at end of file + return web.Response(text="Please update to the latest version of the add-on or Dockerfile, as you have missing dependencies", content_type="text/html") From 28ce90935858779bb0196bd53ee94a1e5ba1b35a Mon Sep 17 00:00:00 2001 From: iainfogg Date: Sun, 29 Sep 2024 14:14:13 +0000 Subject: [PATCH 6/9] Fix messed up merge --- apps/predbat/predbat.py | 220 +++++++++++++++++++++++----------------- 1 file changed, 127 insertions(+), 93 deletions(-) diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index 4d26dd8e..ba5510ed 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -672,7 +672,14 @@ def download_futurerate_data_func(self, url): if self.debug_enable: self.log("Download {}".format(url)) - r = requests.get(url) + + try: + r = requests.get(url) + except: + self.log("Warn: Error downloading futurerate data from URL {}".format(url)) + self.record_status("Warn: Error downloading futurerate data from cloud", debug=url, had_errors=True) + return {} + if r.status_code not in [200, 201]: self.log("Warn: Error downloading futurerate data from URL {}, code {}".format(url, r.status_code)) self.record_status("Warn: Error downloading futurerate data from cloud", debug=url, had_errors=True) @@ -4181,8 +4188,8 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, plan_debug = self.get_arg("plan_debug") html = "" html += "" - html += "".format( - self.now_utc.strftime("%Y-%m-%d %H:%M"), self.now_utc_real.strftime("%H:%M:%S"), THIS_VERSION + html += "".format( + self.now_utc.strftime("%Y-%m-%d %H:%M"), self.now_utc_real.strftime("%H:%M:%S"), THIS_VERSION, self.current_status ) config_str = f"best_soc_min {self.best_soc_min} best_soc_max {self.best_soc_max} best_soc_keep {self.best_soc_keep} carbon_metric {self.carbon_metric} metric_self_sufficiency {self.metric_self_sufficiency} metric_battery_value_scaling {self.metric_battery_value_scaling}" html += "" @@ -5375,104 +5382,118 @@ def optimise_charge_limit_price_threads( for loop_price in all_prices: pred_table = [] - for modulo in [2, 3, 4, 6, 8, 16, 32]: - for divide in [96, 48, 32, 16, 8, 4, 3, 2, 1]: - all_n = [] - all_d = [] - divide_count_d = 0 - highest_price_charge = price_set[-1] - lowest_price_discharge = price_set[0] - for price in price_set: - links = price_links[price] - if loop_price >= price: - for key in links: - window_n = window_index[key]["id"] - typ = window_index[key]["type"] - if typ == "c": - window_prices[window_n] = price - all_n.append(window_n) - elif discharge_enable: - # For prices above threshold try discharge - for key in links: - typ = window_index[key]["type"] - window_n = window_index[key]["id"] - if typ == "d": - window_prices_discharge[window_n] = price - if (int(divide_count_d / divide) % modulo) == 0: - all_d.append(window_n) - divide_count_d += 1 - - # Sort for print out - all_n.sort() - all_d.sort() - - # This price band setting for charge - try_charge_limit = best_limits.copy() - for window_n in range(record_charge_windows): - if window_n >= len(try_charge_limit): - continue + freeze_options = [True, False] + for freeze in freeze_options: + for modulo in [2, 3, 4, 6, 8, 16, 32]: + for divide in [96, 48, 32, 16, 8, 4, 3, 2, 1]: + all_n = [] + all_d = [] + divide_count_d = 0 + highest_price_charge = price_set[-1] + lowest_price_discharge = price_set[0] + for price in price_set: + links = price_links[price] + if loop_price >= price: + for key in links: + window_n = window_index[key]["id"] + typ = window_index[key]["type"] + if typ == "c": + if region_start and (charge_window[window_n]["start"] > region_end or charge_window[window_n]["end"] < region_start): + pass + else: + window_prices[window_n] = price + all_n.append(window_n) + elif discharge_enable: + # For prices above threshold try discharge + for key in links: + typ = window_index[key]["type"] + window_n = window_index[key]["id"] + if typ == "d": + window_prices_discharge[window_n] = price + if region_start and (discharge_window[window_n]["start"] > region_end or discharge_window[window_n]["end"] < region_start): + pass + else: + if (int(divide_count_d / divide) % modulo) == 0: + all_d.append(window_n) + divide_count_d += 1 + + # Sort for print out + all_n.sort() + all_d.sort() + + # This price band setting for charge + try_charge_limit = best_limits.copy() + for window_n in range(record_charge_windows): + if window_n >= len(try_charge_limit): + continue - if region_start and (charge_window[window_n]["start"] > region_end or charge_window[window_n]["end"] < region_start): - continue + if region_start and (charge_window[window_n]["start"] > region_end or charge_window[window_n]["end"] < region_start): + continue - if window_n in all_n: - if window_prices[window_n] > highest_price_charge: - highest_price_charge = window_prices[window_n] - try_charge_limit[window_n] = self.soc_max - else: - try_charge_limit[window_n] = 0 + if charge_window[window_n]["start"] in self.manual_all_times: + continue - # Try discharge on/off - try_discharge = best_discharge.copy() - for window_n in range(record_discharge_windows): - if window_n >= len(discharge_limits): - continue + if window_n in all_n: + if window_prices[window_n] > highest_price_charge: + highest_price_charge = window_prices[window_n] + try_charge_limit[window_n] = self.soc_max + else: + try_charge_limit[window_n] = 0 - if region_start and (discharge_window[window_n]["start"] > region_end or discharge_window[window_n]["end"] < region_start): - continue + # Try discharge on/off + try_discharge = best_discharge.copy() + for window_n in range(record_discharge_windows): + if window_n >= len(discharge_limits): + continue - try_discharge[window_n] = 100 - if window_n in all_d: - if not self.calculate_discharge_oncharge: - hit_charge = self.hit_charge_window(self.charge_window_best, discharge_window[window_n]["start"], discharge_window[window_n]["end"]) - if hit_charge >= 0 and try_charge_limit[hit_charge] > 0.0: - continue - if not self.car_charging_from_battery and self.hit_car_window(discharge_window[window_n]["start"], discharge_window[window_n]["end"]): + if region_start and (discharge_window[window_n]["start"] > region_end or discharge_window[window_n]["end"] < region_start): continue - if ( - not self.iboost_on_discharge - and self.iboost_enable - and self.iboost_plan - and (self.hit_charge_window(self.iboost_plan, discharge_window[window_n]["start"], discharge_window[window_n]["end"]) >= 0) - ): + + if discharge_window[window_n]["start"] in self.manual_all_times: continue - if window_prices_discharge[window_n] < lowest_price_discharge: - lowest_price_discharge = window_prices_discharge[window_n] - try_discharge[window_n] = 0 + try_discharge[window_n] = 100 + if window_n in all_d: + if not self.calculate_discharge_oncharge: + hit_charge = self.hit_charge_window(self.charge_window_best, discharge_window[window_n]["start"], discharge_window[window_n]["end"]) + if hit_charge >= 0 and try_charge_limit[hit_charge] > 0.0: + continue + if not self.car_charging_from_battery and self.hit_car_window(discharge_window[window_n]["start"], discharge_window[window_n]["end"]): + continue + if ( + not self.iboost_on_discharge + and self.iboost_enable + and self.iboost_plan + and (self.hit_charge_window(self.iboost_plan, discharge_window[window_n]["start"], discharge_window[window_n]["end"]) >= 0) + ): + continue - # Skip this one as it's the same as selected already - try_hash = str(try_charge_limit) + "_d_" + str(try_discharge) - if try_hash in tried_list: - if self.debug_enable and 0: - self.log( - "Skip this optimisation with divide {} windows {} discharge windows {} discharge_enable {} as it's the same as previous ones hash {}".format( - divide, all_n, all_d, discharge_enable, try_hash + if window_prices_discharge[window_n] < lowest_price_discharge: + lowest_price_discharge = window_prices_discharge[window_n] + try_discharge[window_n] = 99.0 if freeze else 0 + + # Skip this one as it's the same as selected already + try_hash = str(try_charge_limit) + "_d_" + str(try_discharge) + if try_hash in tried_list: + if self.debug_enable and 0: + self.log( + "Skip this optimisation with divide {} windows {} discharge windows {} discharge_enable {} as it's the same as previous ones hash {}".format( + divide, all_n, all_d, discharge_enable, try_hash + ) ) - ) - continue + continue - tried_list[try_hash] = True + tried_list[try_hash] = True - pred_handle = self.launch_run_prediction_single(try_charge_limit, charge_window, discharge_window, try_discharge, False, end_record=end_record, step=step) - pred_item = {} - pred_item["handle"] = pred_handle - pred_item["charge_limit"] = try_charge_limit.copy() - pred_item["discharge_limit"] = try_discharge.copy() - pred_item["highest_price_charge"] = highest_price_charge - pred_item["lowest_price_discharge"] = lowest_price_discharge - pred_item["loop_price"] = loop_price - pred_table.append(pred_item) + pred_handle = self.launch_run_prediction_single(try_charge_limit, charge_window, discharge_window, try_discharge, False, end_record=end_record, step=step) + pred_item = {} + pred_item["handle"] = pred_handle + pred_item["charge_limit"] = try_charge_limit.copy() + pred_item["discharge_limit"] = try_discharge.copy() + pred_item["highest_price_charge"] = highest_price_charge + pred_item["lowest_price_discharge"] = lowest_price_discharge + pred_item["loop_price"] = loop_price + pred_table.append(pred_item) for pred in pred_table: handle = pred["handle"] @@ -6721,14 +6742,16 @@ def optimise_all_windows(self, best_metric, metric_keep): quiet=True, ) if self.calculate_regions: - self.end_record = self.record_length(self.charge_window_best, self.charge_limit_best, best_price) region_size = int(16 * 60) - while region_size >= 2 * 60: + min_region_size = int(2 * 60) + while region_size >= min_region_size: self.log(">> Region optimisation pass width {}".format(region_size)) for region in range(0, self.end_record + self.minutes_now, min_region_size): region_end = min(region + region_size, self.end_record + self.minutes_now) + if region_end < self.minutes_now: continue + ( self.charge_limit_best, ignore_discharge_limits, @@ -6768,8 +6791,16 @@ def optimise_all_windows(self, best_metric, metric_keep): best_carbon=best_carbon, tried_list=tried_list, ) + # Reached the end of the window + if region_end >= self.end_record + self.minutes_now: + break region_size = int(region_size / 2) + # Keep the freeze but not the full discharge as that will be re-introduced later + for window_n in range(len(ignore_discharge_limits)): + if ignore_discharge_limits[window_n] == 99.0: + self.discharge_limits_best[window_n] = 99.0 + # Set the new end record and blackout period based on the levelling self.end_record = self.record_length(self.charge_window_best, self.charge_limit_best, best_price) self.optimise_charge_windows_reset(reset_all=False) @@ -6799,8 +6830,8 @@ def optimise_all_windows(self, best_metric, metric_keep): # then optimise those above the threshold lowest to highest (to turn up values) # Do the opposite for discharge. self.log( - "Starting second optimisation end_record {} best_price {} best_price_discharge {} lowest_price_charge {} with charge limits {}".format( - self.time_abs_str(self.end_record + self.minutes_now), best_price, best_price_discharge, lowest_price_charge, self.charge_limit_best + "Starting second optimisation end_record {} best_price {} best_price_discharge {} lowest_price_charge {} with charge limits {} discharge limits {}".format( + self.time_abs_str(self.end_record + self.minutes_now), best_price, best_price_discharge, lowest_price_charge, self.charge_limit_best, self.discharge_limits_best ) ) for pass_type in ["freeze", "normal", "low"]: @@ -11099,6 +11130,9 @@ def check_entity_refresh(self): self.log("Info: Refresh config entities as config_refresh state is unknown") self.update_pending = True + # Database tick + self.ha_interface.db_tick() + def run_time_loop(self, cb_args): """ Called every N minutes From c3e948023c951a7633a15d0859e3d2bac5b1b13a Mon Sep 17 00:00:00 2001 From: iainfogg Date: Sun, 29 Sep 2024 14:18:31 +0000 Subject: [PATCH 7/9] Another merge issue --- apps/predbat/web.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/predbat/web.py b/apps/predbat/web.py index dc6bca5e..37c049d8 100644 --- a/apps/predbat/web.py +++ b/apps/predbat/web.py @@ -208,15 +208,24 @@ def render_chart(self, series_data, yaxis_name, chart_name, now_str):
Plan starts: {} last updated: {} version: {} Plan starts: {} last updated: {} version: {} previous status: {}