diff --git a/AGENTS.md b/AGENTS.md index 66fb8eb7e..a9325ec51 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t ## Project Overview -Predbat is a Home Assistant addon (app) that predicts and optimizes home battery charging/discharging based on electricity rates, solar forecasts, and historical load data. It supports inverters from GivEnergy, Solis, Huawei, SolarEdge, and Sofar, and integrates with energy providers like Octopus Energy, Kraken (EDF/E.ON), and Axle Energy VPP. +Predbat is a Home Assistant App (addon) that predicts and optimizes home battery charging/discharging based on electricity rates, solar forecasts, and historical load data. It supports inverters from GivEnergy, Solis, Huawei, SolarEdge, and Sofar, and integrates with energy providers like Octopus Energy, Kraken (EDF/E.ON), and Axle Energy VPP. It also supports Predbat.com which is a cloud based product that does not use Home Assistant and can run in a Docker environment. @@ -76,7 +76,7 @@ The main loop (`update_pred()`) runs every 5 minutes: fetch data → run optimiz ### Core Modules | Module | Role | -|--------|------| +| -------- | ------ | | `plan.py` | Optimization engine — multi-threaded search across thousands of charge/discharge window scenarios | | `predict.py` / `prediction.py` | Battery SOC prediction models, PV generation, load forecasting | | `fetch.py` | Pulls PV forecasts, historical load, rate data, and inverter state | diff --git a/CLAUDE.md b/CLAUDE.md index 3b44a67e7..0cce02c1e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Predbat is a Home Assistant addon (app) that predicts and optimizes home battery charging/discharging based on electricity rates, solar forecasts, and historical load data. It supports inverters from GivEnergy, Solis, Huawei, SolarEdge, and Sofar, and integrates with energy providers like Octopus Energy, Kraken (EDF/E.ON), and Axle Energy VPP. +Predbat is a Home Assistant App (addon) that predicts and optimizes home battery charging/discharging based on electricity rates, solar forecasts, and historical load data. It supports inverters from GivEnergy, Solis, Huawei, SolarEdge, and Sofar, and integrates with energy providers like Octopus Energy, Kraken (EDF/E.ON), and Axle Energy VPP. It also supports Predbat.com which is a cloud based product that does not use Home Assistant and can run in a Docker environment. diff --git a/apps/predbat/axle.py b/apps/predbat/axle.py index b6ebc377c..2ca4dd40f 100644 --- a/apps/predbat/axle.py +++ b/apps/predbat/axle.py @@ -411,7 +411,8 @@ async def _fetch_byok_event(self): if self.get_arg("set_event_notify"): local_start = start_time.astimezone(self.local_tz) local_end = end_time.astimezone(self.local_tz) - self.call_notify("Predbat: Scheduled Axle VPP event {}-{}, {} p/kWh".format(local_start.strftime("%a %d/%m %H:%M"), local_end.strftime("%H:%M"), self.pence_per_kwh)) + msg = f"Scheduled Axle VPP event {local_start.strftime('%a %d/%m %H:%M')}-{local_end.strftime('%H:%M')}, {self.pence_per_kwh} p/kWh" + self.call_notify(f"{self.prefix.capitalize()}: {msg}") self.cleanup_event_history() await self.save_event_history() diff --git a/apps/predbat/build_kernel.sh b/apps/predbat/build_kernel.sh index be48a2772..85b52d871 100644 --- a/apps/predbat/build_kernel.sh +++ b/apps/predbat/build_kernel.sh @@ -4,7 +4,7 @@ # Copyright Trefor Southwell 2026 - All Rights Reserved # ----------------------------------------------------------------------------- # Build the C++ prediction kernel as a shared library for local testing or -# inside the addon Docker image. Falls back to the Python engine when absent. +# inside the app Docker image. Falls back to the Python engine when absent. # # Usage: bash apps/predbat/build_kernel.sh [output.so] set -e diff --git a/apps/predbat/build_kernel_cross.sh b/apps/predbat/build_kernel_cross.sh index d3a688986..f383b8383 100644 --- a/apps/predbat/build_kernel_cross.sh +++ b/apps/predbat/build_kernel_cross.sh @@ -11,7 +11,7 @@ # matching the platform and machine at runtime, or skip if missing: # prediction_kernel_lib_.so (Linux) # prediction_kernel_lib_darwin_.so (macOS) -# where Linux is x86_64, aarch64, armv7l or i686, covering the addon's +# where Linux is x86_64, aarch64, armv7l or i686, covering the app's # Ubuntu (glibc) base images (amd64, aarch64, armv7/armhf, i386; glibc floor 2.17), # and macOS is arm64 (Apple Silicon) or x86_64 (Intel). # diff --git a/apps/predbat/config/apps.yaml b/apps/predbat/config/apps.yaml index b7fab9ade..4832ae13c 100644 --- a/apps/predbat/config/apps.yaml +++ b/apps/predbat/config/apps.yaml @@ -92,8 +92,8 @@ pred_bat: # Example below is auto-restart for GivTCP app itself #auto_restart: # - shell: 'rm -rf /homeassistant/GivTCP/*.pkl' - # - service: hassio/addon_restart - # addon: 533ea71a_givtcp + # - service: hassio/app_restart + # app: 533ea71a_givtcp # # Example on how to restart the inverter via GivTCP # - service: button.press diff --git a/apps/predbat/ha.py b/apps/predbat/ha.py index 53bb7e919..ca550b12d 100644 --- a/apps/predbat/ha.py +++ b/apps/predbat/ha.py @@ -422,10 +422,15 @@ def initialize(self, ha_url, ha_key, db_enable, db_mirror_ha, db_primary): self.log("Info: Using SQL Lite database as primary data source, no HA interface available") if self.ha_key: - # Get the current addon info, but suppress warning message if the API call fails as non-HAOS installs won't have supervisor running + # Get the current app info, but suppress warning message if the API call fails as non-HAOS installs won't have supervisor running + # + # HA changed terminology from 'addons' to 'apps' in HA 2026.2 but retained the old service calls for transition + # + # At present have not changed Predbat API call in order to not break installations that are still using an older HA supervisor + # Propose in Feb 2027 that Predbat be changed to use the new service call res = self.api_call("/addons/self/info", core=False, silent=True) if res: - # get app slug name which is the actual directory name under /addon_configs that /config is mounted to + # get app slug name which is the actual directory name under /apps_configs that /config is mounted to self.slug = res["data"]["slug"] self.log("Info: App slug is {}".format(self.slug)) diff --git a/apps/predbat/hass.py b/apps/predbat/hass.py index 58d48479f..e274e9610 100644 --- a/apps/predbat/hass.py +++ b/apps/predbat/hass.py @@ -108,6 +108,11 @@ async def main(): # # List of root directories to search + # HA changed terminology from 'addons' to 'apps' in HA 2026.2 with 'addon_configs' becoming 'app_configs' but retained + # the old directory names for transition + # + # At present have not changed Predbat directory call in order to not break installations that are still using an older HA supervisor + # Propose in Feb 2027 that Predbat be changed to use the new directory call roots = [".", "/addon"] # Find all .py files in the directory hierarchy, plus the one real apps.yaml this diff --git a/apps/predbat/inverter.py b/apps/predbat/inverter.py index 5c299389c..828ba58f6 100644 --- a/apps/predbat/inverter.py +++ b/apps/predbat/inverter.py @@ -103,8 +103,12 @@ def auto_restart(self, reason): for command in restart_command: shell = command.get("shell", None) service = command.get("service", None) + app = command.get("app", None) + # retain addon service parameter for backwards compatibility with configs using the pre HA 2026.2 nomenclature addon = command.get("addon", None) - if addon: + if app: + app = self.base.resolve_arg(service, app, indirect=False) + elif addon: addon = self.base.resolve_arg(service, addon, indirect=False) entity_id = command.get("entity_id", None) if entity_id: @@ -113,7 +117,10 @@ def auto_restart(self, reason): self.log("Warn: Calling restart shell command: {}".format(shell)) os.system(shell) if service: - if addon: + if app: + self.log("Warn: Calling restart service {} with app {}".format(service, app)) + self.base.call_service_wrapper(service, app=app) + elif addon: self.log("Warn: Calling restart service {} with addon {}".format(service, addon)) self.base.call_service_wrapper(service, addon=addon) elif entity_id: @@ -123,7 +130,7 @@ def auto_restart(self, reason): self.log("Warn: Calling restart service {}".format(service)) self.base.call_service_wrapper(service) if self.base.get_arg("set_system_notify"): - self.base.call_notify("Auto-restart service {} called due to: {}".format(service, reason)) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Auto-restart service {service} called due to: {reason}") self.sleep(15) raise Exception("Auto-restart triggered") else: @@ -1791,7 +1798,7 @@ def adjust_reserve(self, reserve): else: self.write_and_poll_value("reserve", self.base.get_arg("reserve", indirect=False, index=self.id, required_unit="%"), reserve) if self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} Target Reserve has been changed to {}% at {}".format(self.id, dp0(reserve), self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} Target Reserve has been changed to {dp0(reserve)}% at {self.base.time_now_str()}") self.mqtt_message(topic="set/reserve", payload=reserve) else: self.base.log("Inverter {} Current reserve is {}%, already at target".format(self.id, dp0(current_reserve))) @@ -1871,7 +1878,7 @@ def adjust_charge_rate(self, new_rate, notify=True): self.write_and_poll_value("charge_rate_percent", self.base.get_arg("charge_rate_percent", indirect=False, index=self.id, required_unit="%"), min(int(new_rate / self.battery_rate_max_raw * 100), 100), fuzzy=5, required_unit="%") if notify and self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} charge rate changes to {}W at {}".format(self.id, new_rate, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} charge rate changes to {new_rate}W at {self.base.time_now_str()}") self.mqtt_message(topic="set/charge_rate", payload=new_rate) # Re-assert the timed current register on every call, not just when charge_rate itself @@ -1915,7 +1922,7 @@ def adjust_discharge_rate(self, new_rate, notify=True): self.write_and_poll_value("discharge_rate_percent", self.base.get_arg("discharge_rate_percent", indirect=False, index=self.id, required_unit="%"), min(int(new_rate / self.battery_rate_max_raw * 100), 100), fuzzy=5, required_unit="%") if notify and self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} discharge rate changes to {}W at {}".format(self.id, new_rate, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} discharge rate changes to {new_rate}W at {self.base.time_now_str()}") self.mqtt_message(topic="set/discharge_rate", payload=new_rate) # Re-assert the timed current register on every call, not just when discharge_rate itself @@ -1976,7 +1983,7 @@ def adjust_battery_target(self, soc, isCharging=False, isExporting=False): self.press_and_poll_button() if self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} Target SoC has been changed to {}% at {}".format(self.id, soc, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} Target SoC has been changed to {soc}% at {self.base.time_now_str()}") self.mqtt_message(topic="set/target_soc", payload=soc) else: self.base.log("Inverter {} Current Target SoC is {}%, already at target".format(self.id, current_soc)) @@ -2230,7 +2237,7 @@ def adjust_pause_mode(self, pause_charge=False, pause_discharge=False): self.write_and_poll_option("pause_mode", entity_mode, new_pause_mode) if self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} pause mode to set {} at time {}".format(self.id, new_pause_mode, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} pause mode to set {new_pause_mode} at time {self.base.time_now_str()}") self.base.log("Inverter {} set pause mode to {}".format(self.id, new_pause_mode)) @@ -2303,7 +2310,7 @@ def adjust_inverter_mode(self, force_export, changed_start_end=False): # Notify if self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} Force export set to {} at time {}".format(self.id, force_export, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} Force export set to {force_export} at time {self.base.time_now_str()}") self.base.log("Inverter {} set force export to {}".format(self.id, force_export)) @@ -2602,7 +2609,7 @@ def adjust_force_export(self, force_export, new_start_time=None, new_end_time=No # Notify if changed_start_end: if self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} Export time slot set to {} - {} at time {}".format(self.id, new_start, new_end, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} Export time slot set to {new_start} - {new_end} at time {self.base.time_now_str()}") def disable_charge_window(self, notify=True): """ @@ -2650,7 +2657,7 @@ def disable_charge_window(self, notify=True): self.enable_charge_discharge_with_time_current("charge", False) if self.base.set_inverter_notify and notify: - self.base.call_notify("Predbat: Inverter {} Disabled scheduled charging at {}".format(self.id, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} Disabled scheduled charging at {self.base.time_now_str()}") self.base.log("Inverter {} Turning off scheduled charge".format(self.id)) @@ -3025,7 +3032,7 @@ def adjust_charge_window(self, charge_start_time, charge_end_time, minutes_now): self.rest_setChargeSlot1(new_start, new_end) if self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} Charge window change to: {} - {} at {}".format(self.id, new_start, new_end, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} Charge window change to: {new_start} - {new_end} at {self.base.time_now_str()}") self.base.log("Inverter {} Updated start and end charge window to {} - {} (old {} - {})".format(self.id, new_start, new_end, old_start, old_end)) if (old_charge_schedule_enable == "off" or have_disabled) and (new_start != new_end): @@ -3042,7 +3049,7 @@ def adjust_charge_window(self, charge_start_time, charge_end_time, minutes_now): # Only notify if it's a real change and not a temporary one if old_charge_schedule_enable == "off" and self.base.set_inverter_notify: - self.base.call_notify("Predbat: Inverter {} Enabling scheduled charging at {}".format(self.id, self.base.time_now_str())) + self.base.call_notify(f"{self.base.prefix.capitalize()}: Inverter {self.id} Enabling scheduled charging at {self.base.time_now_str()}") self.charge_enable_time = True diff --git a/apps/predbat/octopus.py b/apps/predbat/octopus.py index ba8acfb98..09437059b 100644 --- a/apps/predbat/octopus.py +++ b/apps/predbat/octopus.py @@ -2934,7 +2934,7 @@ def fetch_octopus_sessions(self, axle_sessions=None): octopus_saving_slots = [] if "octopus_saving_session" in self.args: saving_rate = 200 # Default rate if not reported - octopoints_per_penny = self.get_arg("octopus_saving_session_octopoints_per_penny", 8) # Default 8 octopoints per found + octopoints_per_penny = self.get_arg("octopus_saving_session_octopoints_per_penny", 8) # Default 8 octopoints per penny joined_events = [] available_events = [] @@ -2983,17 +2983,29 @@ def fetch_octopus_sessions(self, axle_sessions=None): if self._saving_event_conflicts_axle(start_time, end_time, axle_sessions): self.log("Octopus: Skipping saving event code {} {}-{} - conflicts with an Axle VPP session".format(code, start_time.strftime("%a %d/%m %H:%M"), end_time.strftime("%H:%M"))) continue - if code: # Join the new Octopus saving event and send an alert + if code: # Join the new Octopus saving event and send an alert if successfully joined self.log("Octopus: Joining Octopus saving event code {} {}-{} at rate {} p/kWh".format(code, start_time.strftime("%a %d/%m %H:%M"), end_time.strftime("%H:%M"), saving_rate)) entity_id_join = self.get_arg("octopus_saving_session_join", indirect=False) if entity_id_join: # Join via selector - self.call_service_wrapper("select/select_option", entity_id=entity_id_join, option=code) + cmd = "select/select_option, entity_id={}, option={}".format(entity_id_join, code) + result = self.call_service_wrapper("select/select_option", entity_id=entity_id_join, option=code, return_response=True) else: # Join via octopus event (Bottle Cap Dave) + cmd = "octopus_energy/join_octoplus_saving_session_event, event_code={}, entity_id={}".format(code, entity_id) + # result = self.call_service_wrapper("octopus_energy/join_octoplus_saving_session_event", event_code=code, entity_id=entity_id, return_response=True) + # + # at present bottle cap dave integration doesn't return a response from the saving session so can't check it succeeds + # see https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/issues/1823 + # so for now dummy a positive response + result = True self.call_service_wrapper("octopus_energy/join_octoplus_saving_session_event", event_code=code, entity_id=entity_id) - if self.get_arg("set_event_notify"): - self.call_notify("Predbat: Joined Octopus saving event {}-{}, {} p/kWh".format(start_time.strftime("%a %d/%m %H:%M"), end_time.strftime("%H:%M"), saving_rate)) + if result: + if self.get_arg("set_event_notify"): + msg = "Joined Octopus saving event " + start_time.strftime("%a %d/%m %H:%M") + "-" + end_time.strftime("%H:%M") + ", " + str(saving_rate) + " p/kWh" + self.call_notify(f"{self.prefix.capitalize()}: {msg}") + else: + self.log("Warn: Unable to join Octoplus saving event with command {}, result was {}".format(cmd, result)) self.octopus_last_joined_try = self.now_utc # Default saving session rate for when octopoints_per_kwh is not available diff --git a/apps/predbat/output.py b/apps/predbat/output.py index 10b7be77a..606675727 100644 --- a/apps/predbat/output.py +++ b/apps/predbat/output.py @@ -2505,7 +2505,7 @@ def record_status(self, message, debug="", had_errors=False, notify=False, extra # Already in error state, do not notify second error in a single run (spam) pass else: - self.call_notify("Predbat status change to: " + message + extra) + self.call_notify(f"{self.prefix.capitalize()} status change to: {message} {extra}") self.previous_status = message error_count = self.get_state_wrapper(self.prefix + ".status", attribute="error_count", default=0) diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index 456eb7d83..f90c96ad3 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -1152,7 +1152,7 @@ def download_predbat_version(self, version): if files: # Notify before killing threads so the WebSocket is still healthy if self.get_arg("set_system_notify"): - self.call_notify("Predbat: update to: {}".format(version)) + self.call_notify(f"{self.prefix.capitalize()}: update to: {version}") # Kill the current threads self.log("Kill current threads before update") @@ -1621,7 +1621,7 @@ def initialize(self): slug = self.ha_interface.get_slug() if slug: # and use slug name to determine printable config_root pathname when writing debug info to the log file - self.config_root_p = "/addon_configs/" + slug + self.config_root_p = "/apps_configs/" + slug self.log("Config root is {} and printable config_root_p is now {}".format(self.config_root, self.config_root_p)) diff --git a/apps/predbat/prediction_kernel.py b/apps/predbat/prediction_kernel.py index f3eba15eb..bab5644ca 100644 --- a/apps/predbat/prediction_kernel.py +++ b/apps/predbat/prediction_kernel.py @@ -257,7 +257,7 @@ def kernel_library_candidates(): # Local development/Docker build (named _lib so Python's importer never picks it up in place of this module) candidates.append(os.path.join(base_dir, "prediction_kernel_lib.so")) # Cross-built per-architecture binaries (see build_kernel_cross.sh, delivered by - # self-update); Linux ones are glibc-based as the addon images are Ubuntu + # self-update); Linux ones are glibc-based as the app images are Ubuntu machine = platform.machine().lower() if machine and sys.platform == "linux": candidates.append(os.path.join(base_dir, "prediction_kernel_lib_{}.so".format(machine))) diff --git a/apps/predbat/tests/test_hainterface_api.py b/apps/predbat/tests/test_hainterface_api.py index 65cf73496..f59693b0b 100644 --- a/apps/predbat/tests/test_hainterface_api.py +++ b/apps/predbat/tests/test_hainterface_api.py @@ -4,7 +4,7 @@ Tests for HAInterface API-related methods: - api_call() - GET/POST requests with error handling -- initialize() - Addon/services checks +- initialize() - App/services checks - get_history() - Historical data fetching """ @@ -124,7 +124,7 @@ def test_hainterface_api_call_supervisor(my_predbat=None): mock_env.return_value = "supervisor_token" mock_get.return_value = create_mock_requests_response(200, {"supervisor": "data"}) - result = ha_interface.api_call("/addons/self/info", core=False) + result = ha_interface.api_call("/apps/self/info", core=False) # Verify supervisor URL used if not mock_get.called: @@ -332,18 +332,18 @@ def test_hainterface_api_call_error_reset(my_predbat=None): return failed -def test_hainterface_initialize_addon_check(my_predbat=None): - """Test initialize() checks for addon/services""" - print("\n=== Testing HAInterface initialize() addon check ===") +def test_hainterface_initialize_app_check(my_predbat=None): + """Test initialize() checks for app/services""" + print("\n=== Testing HAInterface initialize() app check ===") failed = 0 mock_base = MockBase() - # Mock both addon info and services calls in initialize() + # Mock both app info and services calls in initialize() with patch("ha.os.environ.get") as mock_env, patch("ha.requests.get") as mock_get: mock_env.return_value = "test_supervisor_token" # Mock SUPERVISOR_TOKEN mock_get.side_effect = [ - create_mock_requests_response(200, {"data": {"slug": "predbat_addon"}}), # addon info + create_mock_requests_response(200, {"data": {"slug": "predbat_app"}}), # app info create_mock_requests_response(200, [{"domain": "homeassistant"}]), # services ] @@ -363,18 +363,18 @@ def test_hainterface_initialize_addon_check(my_predbat=None): ha_interface.initialize("http://test:8123", "test_key", False, False, False) # Verify slug set - if ha_interface.slug != "predbat_addon": - print(f"ERROR: Slug should be 'predbat_addon', got {ha_interface.slug}") + if ha_interface.slug != "predbat_app": + print(f"ERROR: Slug should be 'predbat_app', got {ha_interface.slug}") failed += 1 else: - print("✓ Addon slug detected correctly") + print("✓ App slug detected correctly") return failed -def test_hainterface_initialize_no_addon(my_predbat=None): - """Test initialize() handles missing addon gracefully""" - print("\n=== Testing HAInterface initialize() no addon ===") +def test_hainterface_initialize_no_app(my_predbat=None): + """Test initialize() handles missing app gracefully""" + print("\n=== Testing HAInterface initialize() no app ===") failed = 0 mock_base = MockBase() @@ -382,9 +382,9 @@ def test_hainterface_initialize_no_addon(my_predbat=None): with patch("ha.os.environ.get") as mock_env, patch("ha.requests.get") as mock_get: # Mock supervisor token mock_env.return_value = "test_supervisor_token" - # Mock addon call returns None (supervisor timeout), services call success + # Mock app call returns None (supervisor timeout), services call success mock_get.side_effect = [ - requests.Timeout("Supervisor timeout"), # addon info fails with timeout + requests.Timeout("Supervisor timeout"), # app info fails with timeout create_mock_requests_response(200, [{"domain": "homeassistant"}]), # services succeeds ] @@ -408,7 +408,7 @@ def test_hainterface_initialize_no_addon(my_predbat=None): print(f"ERROR: Slug should be None, got {ha_interface.slug}") failed += 1 else: - print("✓ Missing addon handled gracefully") + print("✓ Missing app handled gracefully") return failed @@ -561,8 +561,8 @@ def run_hainterface_api_tests(my_predbat): failed += test_hainterface_api_call_silent_mode(my_predbat) failed += test_hainterface_api_call_error_limit(my_predbat) failed += test_hainterface_api_call_error_reset(my_predbat) - failed += test_hainterface_initialize_addon_check(my_predbat) - failed += test_hainterface_initialize_no_addon(my_predbat) + failed += test_hainterface_initialize_app_check(my_predbat) + failed += test_hainterface_initialize_no_app(my_predbat) failed += test_hainterface_get_history_basic(my_predbat) failed += test_hainterface_get_history_no_key(my_predbat) failed += test_hainterface_get_history_api_error(my_predbat) diff --git a/apps/predbat/tests/test_hainterface_common.py b/apps/predbat/tests/test_hainterface_common.py index 8e1564ea1..08145ab8c 100644 --- a/apps/predbat/tests/test_hainterface_common.py +++ b/apps/predbat/tests/test_hainterface_common.py @@ -320,7 +320,7 @@ async def session_aexit(*args): return mock_session -def create_ha_interface(mock_base, ha_url="http://test", ha_key=None, db_enable=False, db_mirror_ha=False, db_primary=False, skip_addon_check=True, websocket_active=False): +def create_ha_interface(mock_base, ha_url="http://test", ha_key=None, db_enable=False, db_mirror_ha=False, db_primary=False, skip_app_check=True, websocket_active=False): """ Helper to create HAInterface with initialization. @@ -331,7 +331,7 @@ def create_ha_interface(mock_base, ha_url="http://test", ha_key=None, db_enable= db_enable: Enable database db_mirror_ha: Enable DB mirroring db_primary: DB primary mode - skip_addon_check: If True, mock API calls to bypass addon/services check + skip_app_check: If True, mock API calls to bypass app/services check websocket_active: If True, set websocket_active flag Returns: @@ -365,7 +365,7 @@ def create_ha_interface(mock_base, ha_url="http://test", ha_key=None, db_enable= ha_interface.db_manager = None # Now call initialize with proper parameters - if skip_addon_check and ha_key: + if skip_app_check and ha_key: # Mock the API calls in initialize() with patch("ha.requests.get") as mock_get: # Mock services check to return valid data diff --git a/apps/predbat/tests/test_hainterface_lifecycle.py b/apps/predbat/tests/test_hainterface_lifecycle.py index cc406d574..d5b75aef7 100644 --- a/apps/predbat/tests/test_hainterface_lifecycle.py +++ b/apps/predbat/tests/test_hainterface_lifecycle.py @@ -124,7 +124,7 @@ def test_hainterface_initialize_api_check_failed(my_predbat=None): ha_interface.api_stop = False with patch("ha.requests.get") as mock_get: - # First call (addon check) returns None, second call (services check) returns None + # First call (app check) returns None, second call (services check) returns None mock_get.return_value = create_mock_requests_response(500, None) try: @@ -306,7 +306,7 @@ def mock_sleep(seconds): def test_hainterface_get_slug(my_predbat=None): - """Test get_slug() returns addon slug""" + """Test get_slug() returns app slug""" print("\n=== Testing HAInterface get_slug() ===") failed = 0 @@ -318,8 +318,12 @@ def test_hainterface_get_slug(my_predbat=None): ha_interface.api_stop = False with patch("ha.requests.get") as mock_get: - # Mock addon info response + # Mock app info response def mock_get_side_effect(url, *args, **kwargs): + # HA changed terminology from 'addons' to 'apps' in HA 2026.2 but retained the old service calls for transition + # + # At present have not changed Predbat API call in order to not break installations that are still using an older HA supervisor + # Propose in Feb 2027 that Predbat be changed to use the new service call if "/addons/self/info" in url: return create_mock_requests_response(200, {"data": {"slug": "predbat_addon"}}) else: diff --git a/apps/predbat/tests/test_infra.py b/apps/predbat/tests/test_infra.py index 76f592b44..247b0cb2b 100644 --- a/apps/predbat/tests/test_infra.py +++ b/apps/predbat/tests/test_infra.py @@ -189,7 +189,7 @@ def call_service(self, service, **kwargs): print("Calling service: {} {}".format(service, kwargs)) if self.service_store_enable: self.service_store.append([service, kwargs]) - return None + return True # changed to return true so that dummy service call will pass test framework if service == "number/set_value": entity_id = kwargs.get("entity_id", None) diff --git a/apps/predbat/tests/test_inverter.py b/apps/predbat/tests/test_inverter.py index 3a683c1b6..0264c9530 100644 --- a/apps/predbat/tests/test_inverter.py +++ b/apps/predbat/tests/test_inverter.py @@ -3191,8 +3191,8 @@ def run_inverter_tests(my_predbat_dummy): ha, inv, dummy_items, - service={"command": "service", "service": "restart_service", "addon": "adds"}, - expected=[["restart_service", {"addon": "adds"}]], + service={"command": "service", "service": "restart_service", "app": "adds"}, + expected=[["restart_service", {"app": "adds"}]], expect_notify=True, ) if failed: diff --git a/apps/predbat/userinterface.py b/apps/predbat/userinterface.py index b9590f24d..7a008c82e 100644 --- a/apps/predbat/userinterface.py +++ b/apps/predbat/userinterface.py @@ -629,7 +629,7 @@ async def async_restore_settings_yaml(self, filename): self.log("Restore setting: {} = {} (was {})".format(item["name"], item["default"], item["value"])) await self.async_expose_config(item["name"], item["default"], event=True) if self.get_arg("set_system_notify"): - await self.async_call_notify("Predbat settings restored from default") + await self.async_call_notify(f"{self.prefix.capitalize()} settings restored from default") else: filepath = os.path.join(self.save_restore_dir, filename) if os.path.exists(filepath): @@ -644,7 +644,7 @@ async def async_restore_settings_yaml(self, filename): self.log("Restore setting: {} = {} (was {})".format(item["name"], item["value"], current["value"])) await self.async_expose_config(item["name"], item["value"], event=True) if self.get_arg("set_system_notify"): - await self.async_call_notify("Predbat settings restored from {}".format(filename)) + await self.async_call_notify(f"{self.prefix.capitalize()} settings restored from {filename}") await self.async_expose_config("saverestore", None) def load_current_config(self): @@ -716,7 +716,7 @@ async def async_save_settings_yaml(self, filename=None): yaml.dump(self.CONFIG_ITEMS, file) self.log("Saved Predbat settings to {}".format(filepath_p)) if self.get_arg("set_system_notify"): - await self.async_call_notify("Predbat settings saved to {}".format(filename)) + await self.async_call_notify(f"{self.prefix.capitalize()} settings saved to {filename}") def read_debug_yaml(self, filename): """ diff --git a/coverage/cases/predbat_debug_agile1.yaml b/coverage/cases/predbat_debug_agile1.yaml index 702d29e3a..bb4381b6c 100755 --- a/coverage/cases/predbat_debug_agile1.yaml +++ b/coverage/cases/predbat_debug_agile1.yaml @@ -1556,7 +1556,7 @@ SERVICE_REGISTER_LIST: args: auto_restart: - shell: rm -rf /homeassistant/GivTCP/*.pkl - - entity_id: switch.givtcp_{geserial}_reboot_addon + - entity_id: switch.givtcp_{geserial}_reboot_addon. # switch name remains 'reboot_addon' in GivTCP so not changed to new HA App naming convention service: switch/turn_on balance_inverters_seconds: 60 battery_charge_power_curve: @@ -68233,7 +68233,7 @@ combine_rate_threshold: 0.0 computed_charge_curve: false computed_discharge_curve: false config_root: ./ -config_root_p: /addon_configs/6adb4f0d_predbat +config_root_p: /apps_configs/6adb4f0d_predbat cost_today_sofar: 705.8050399499946 cost_total_car: 0.0 cost_yesterday_car: 0 diff --git a/coverage/cases/predbat_debug_pre_saving1.yaml b/coverage/cases/predbat_debug_pre_saving1.yaml index 86853c1b7..77d23feae 100755 --- a/coverage/cases/predbat_debug_pre_saving1.yaml +++ b/coverage/cases/predbat_debug_pre_saving1.yaml @@ -1645,7 +1645,7 @@ SERVICE_REGISTER_LIST: args: auto_restart: - shell: rm -rf /homeassistant/GivTCP/*.pkl - - entity_id: switch.givtcp_{geserial}_reboot_addon + - entity_id: switch.givtcp_{geserial}_reboot_addon. # switch name remains 'reboot_addon' in GivTCP so not changed to new HA App naming convention service: switch/turn_on balance_inverters_seconds: 60 battery_charge_power_curve: &id001 @@ -68386,7 +68386,7 @@ config_index: update: *id110 version: *id111 config_root: /config -config_root_p: /addon_configs/6adb4f0d_predbat +config_root_p: /apps_configs/6adb4f0d_predbat cost_today_sofar: 172.64380255000106 cost_total_car: 0.0 cost_yesterday_car: 0.0 diff --git a/coverage/deploy b/coverage/deploy index 0f042ee55..1e3fdaf89 100644 --- a/coverage/deploy +++ b/coverage/deploy @@ -1,3 +1,8 @@ +# HA changed terminology from 'addons' to 'apps' in HA 2026.2 with 'addon_configs' becoming 'app_configs' but retained +# the old directory names for transition +# +# At present have not changed Predbat deployment in order to not break installations that are still using an older HA supervisor +# Propose in Feb 2027 that Predbat be changed to use the new directory name cp ../apps/predbat/*.py /Volumes/addon_configs/6adb4f0d_predbat cp ../apps/predbat/*.proto /Volumes/addon_configs/6adb4f0d_predbat cp ../apps/predbat/*.so /Volumes/addon_configs/6adb4f0d_predbat diff --git a/coverage/standalone b/coverage/standalone index 726c47035..dd42e8dfe 100755 --- a/coverage/standalone +++ b/coverage/standalone @@ -4,6 +4,11 @@ mkdir -p predbat_standalone cd predbat_standalone ln -s ../../apps/predbat/*.py . cd .. +# HA changed terminology from 'addons' to 'apps' in HA 2026.2 with 'addon_configs' becoming 'app_configs' but retained +# the old directory names for transition +# +# At present have not changed Predbat deployment in order to not break installations that are still using an older HA supervisor +# Propose in Feb 2027 that Predbat be changed to use the new directory name #cp /Volumes/addon_configs/6adb4f0d_predbat/*.py predbat_standalone cp /Volumes/addon_configs/6adb4f0d_predbat/apps_direct.yaml predbat_standalone/apps.yaml cd predbat_standalone diff --git a/docs/apps-yaml.md b/docs/apps-yaml.md index f208a08df..3aa3edf8e 100644 --- a/docs/apps-yaml.md +++ b/docs/apps-yaml.md @@ -4,11 +4,11 @@ The basic Predbat configuration is defined in the `apps.yaml` file. Depending on how you installed Predbat the `apps.yaml` file will be held in one of three different directories in Home Assistant: -- if you have used the [Predbat app installation method](install.md#predbat-app-install), `apps.yaml` will be in the directory `/addon_configs/6adb4f0d_predbat`, +- if you have used the [Predbat app installation method](install.md#predbat-app-install), `apps.yaml` will be in the directory `/apps_configs/6adb4f0d_predbat`, -- with the [HACS, Appdaemon app then Predbat installation method](install.md#predbat-installation-into-appdaemon), it's in `/config/appdaemon/apps/batpred/config/`, or +- with the deprecated [HACS, Appdaemon app then Predbat installation method](install.md#predbat-installation-into-appdaemon), it's in `/config/appdaemon/apps/batpred/config/`, or -- if the combined AppDaemon/Predbat app installation method was used, it's in `/addon_configs/46f69597_appdaemon-predbat/apps`. +- if the deprecated combined AppDaemon/Predbat app installation method was used, it's in `/apps_configs/46f69597_appdaemon-predbat/apps`. You will need to use a file editor within Home Assistant (e.g. either the File editor or Studio Code Server apps) to edit the `apps.yaml` file - see [editing configuration files within Home Assistant](install.md#editing-configuration-files-in-home-assistant) if you need to install an editor. @@ -16,7 +16,7 @@ to edit the `apps.yaml` file - see [editing configuration files within Home Assi This section of the documentation describes what the different configuration items in `apps.yaml` do. When you edit `apps.yaml`, the change will automatically be detected and Predbat will be reloaded with the updated file. -You don't need to restart the Predbat or AppDaemon app for your edits to take effect. +You don't need to restart Predbat app for your edits to take effect. ## Templates @@ -34,7 +34,7 @@ and it's very easy to end up with an incorrectly formatted file that will cause The [YAML Basics from This Smart Home](https://www.youtube.com/watch?v=nETF43QJebA) is a good introduction video to how YAML should be correctly structured but as a brief introduction: -At the start of the `apps.yaml` file is the predbat module definition: +At the start of the `apps.yaml` file is the Predbat module definition: ```yaml pred_bat: @@ -106,7 +106,7 @@ sets the configuration item **battery_temperature_history** to the Home Assistan note the list items appear on separate lines beneath the configuration item name, with each entry being indented by two spaces, a dash, a space and then the configuration value. -- Entries where the predbat configuration item includes a variable name set earlier in `apps.yaml` that is then expanded, e.g.: +- Entries where the Predbat configuration item includes a variable name set earlier in `apps.yaml` that is then expanded, e.g.: ```yaml dno_region: "A" @@ -1058,6 +1058,8 @@ To disable, set it to 1440. - **iboost_energy_today** - Set to a sensor which tracks the amount of energy sent to your solar diverter, which can also be used to subtract from your historical load for more accurate predictions. +The iboost energy sensor should reset to zero each day so if your source sensor doesn't, then its recommended to wrap it in a utility meter and configure Predbat to use the utility meter. + ## Inverter control configurations NB: literal numeric values for the power-limit settings below (`inverter_limit`, `pv_ac_limit`, `export_limit`, `inverter_limit_charge`, `inverter_limit_discharge`, `inverter_limit_export`, `inverter_limit_charge_dc`, `battery_rate_max`, `inverter_battery_rate_min`) must always be in **watts** — e.g. `7300` for a 7.3 kW inverter, never `7.3`. Predbat's unit auto-conversion only fires when the value is a sensor reference (it reads `unit_of_measurement` from the HA entity); for literal values there is no entity to read, so the raw number is taken as watts. A literal `inverter_limit: 7.3` will be interpreted as 7.3 W and clamp `battery_draw` to ~0.0006 kWh per 5-min step, producing a plan that looks like Predbat refuses to discharge the battery. @@ -1508,9 +1510,16 @@ Called when a charge/discharge is cancelled and the inverter goes back to home d topic: **topic**/set/auto payload: true -## Solcast Solar Forecast +## Solar Forecast + +As described in the [Predbat installation instructions](install.md#solar-forecast-install), Predbat needs a solar forecast +in order to predict solar generation and battery charging. + +The Solar forecast configuration in `apps.yaml` should be configured for either for the [Solcast integration](#solcast-solar-forecast), [Forecast.solar](#forecastsolar-solar-forecast) or [Open-Meteo](#open-meteo-solar-forecast). + +### Solcast Solar Forecast -As described in the [Predbat installation instructions](install.md#solcast-install), Predbat needs a solar forecast +As described in the [Predbat installation instructions](install.md#solar-forecast-install, Predbat needs a solar forecast in order to predict solar generation and battery charging which can be provided by the Solcast integration. By default, the template `apps.yaml` is pre-configured to use the [Solcast forecast integration](install.md#solcast-home-assistant-integration-method) for Home Assistant. @@ -1590,7 +1599,7 @@ This can have an impact on planning, especially for things like freeze charging See also [PV configuration options in Home Assistant](customisation.md#solar-pv-adjustment-options). -## Forecast.solar Solar Forecast +### Forecast.solar Solar Forecast The Forecast.solar service can also be used in Predbat, the free version offer access without an API Key but is limited to hourly data and does not provide any 10% or 90% data. Predbat Solar calibration can use past data to improve this information and provide both the 10% and the 90% data, each derived from the central forecast @@ -1686,7 +1695,7 @@ source changes. Do not judge the accuracy of the new source until the settling p [Open-Meteo](https://open-meteo.com/) is a free, open-source weather API that provides solar irradiance forecasts with no API key required. Predbat fetches the Global Tilted Irradiance (GTI) for each array and converts it to a power estimate using a PVWatts cell-temperature model. -Ensemble members are used to derive a P10 pessimistic estimate alongside the central P50. +Ensemble members are used to derive a PV10 pessimistic estimate alongside the central PV50. You can define one or more rooftop arrays by providing a list; they will be summed automatically. @@ -1757,7 +1766,7 @@ These are described in detail in [Energy Rates](energy-rates.md) and are listed - **rates_export_override** - Over-ride export rate for specific date and time range - **futurerate_url** - URL of future energy market prices for Agile users - **futurerate_adjust_import** and **futurerate_adjust_export** - Whether tomorrow's predicted import or export prices should be adjusted based on market prices or not -- **futurerate_adjust_auto** - Auto-detect which of import/export are Agile and calibrate only those rates; overrides `futurerate_adjust_import` / `futurerate_adjust_export`; requires the Octopus Energy integration or Predbat's Octopus Component +- **futurerate_adjust_auto** - Auto-detect which of the import/export rates are Agile and calibrate only those rates; overrides `futurerate_adjust_import` / `futurerate_adjust_export`; requires the Octopus Energy integration or Predbat's Octopus Component - **futurerate_peak_start** and **futurerate_peak_end** - start/end times for peak-rate adjustment - **carbon_postcode** - Postcode to retrieve Carbon intensity grid information for - **carbon_automatic** - Retrieve Carbon intensity information automatically based upon postcode @@ -1989,7 +1998,7 @@ rather than the usual *givtcp_SERIAL_NUMBER_soc* GivTCP entity so everything lin Default false. When set to true Predbat will automatically calculate `battery_scaling` based on historical charge data rather than using the static value above. The calculation uses `find_battery_size()` to estimate the actual usable battery capacity from historical charging periods and -compares it to the nominal capacity (`soc_max`). A 7-day rolling history of daily estimates is stored in a new sensor +compares it to the nominal capacity (`soc_max`). A 7-day rolling history of daily estimates is stored in the sensor `sensor.predbat_soc_max_calculated` (or `sensor.predbat_soc_max_calculated_N` for inverter N > 0). The sensor state is the trimmed mean of the history (the highest and lowest samples are discarded when 3 or more data points exist, giving a stable average that is robust to occasional outliers). @@ -2056,17 +2065,17 @@ This may be useful with GivTCP if you have time sync errors or lose the REST ser The auto_restart itself is a list of commands to run to trigger a restart. - The **shell** command will call a 'sh' shell and can be used to delete files and suchlike. -- The **service** command is used to call a service and can contain arguments of **addon** and/or **entity_id**. The configuration below is for GivTCP v3. +- The **service** command is used to call a service and can contain arguments of **app** and/or **entity_id**. The configuration below is for GivTCP v3. ```yaml auto_restart: - shell: 'rm -rf /homeassistant/GivTCP/*.pkl' - - service: hassio/addon_restart - addon: 533ea71a_givtcp + - service: hassio/app_restart + app: 533ea71a_givtcp ``` NB: If you are running GivTCP v2 then the line '533ea71a_givtcp' must be replaced with 'a6a2857d_givtcp' -as the slug-id (Home Assistant app identifier) is different between GivTCP v2 and v3. +as the slug-id (Home Assistant App identifier) is different between GivTCP v2 and v3. ## Battery charge/discharge curves @@ -2102,7 +2111,7 @@ or an edit being made to `apps.yaml`), then Predbat will automatically calculate You should look at the [Predbat logfile](output-data.md#predbat-logfile) to find the predicted battery charging curve and copy/paste it into your `apps.yaml` file. -The logfile *may* also include an Info recommendation for how to set your **input_number.battery_rate_max_scaling**/**_scaling_discharge** setting in HA if predbat detects that your inverter is charging/discharging at a different maximum rate than is configured in `apps.yaml`.
+The logfile *may* also include an Info recommendation for how to set your **input_number.battery_rate_max_scaling**/**_scaling_discharge** setting in HA if Predbat detects that your inverter is charging/discharging at a different maximum rate than is configured in `apps.yaml`.
If you don't get such a message then Predbat didn't detect any charge/discharge rate discrepancy. The YouTube video [charging curve and low power charging](https://youtu.be/L2vY_Vj6pQg) explains how the curve works and shows how Predbat automatically creates it. diff --git a/docs/car-charging.md b/docs/car-charging.md index 6adcafedb..1ef8ed881 100644 --- a/docs/car-charging.md +++ b/docs/car-charging.md @@ -60,7 +60,7 @@ or edit as necessary in `apps.yaml` for your charger sensor.
Note that this must be configured to point to an 'energy today' sensor in kWh not an instantaneous power sensor (in kW) from the car charger.

*IMPORTANT:* Predbat will subtract all car_charging_energy from your historic house load so if car_charging_energy is not configured with the correct sensor, your car charging energy sensor does not accurately report your car charging data (e.g. it falsely reports charging data when not actually charging), or your house load sensor already excludes car charging, -then this will really mess up your predbat plan as Predbat will exclude all car_charging_energy from your load predictions and you could end up with erroneous or zero house load predictions. Do check the entity!

+then this will really mess up your Predbat plan as Predbat will exclude all car_charging_energy from your load predictions and you could end up with erroneous or zero house load predictions. Do check the entity!

*NOTE:* The car charging energy sensor must be a daily incrementing kWh sensor. Check the history of your sensor in Home Assistant, that it increments through the day when your car is charging, resets to zero at midnight, and does not dip down in value or reset to zero other than at midnight. Some car charger energy sensors do not behave as Predbat requires them to do; for example, they may show cumulative energy per charge, not cumulative charge energy today, or may show 'unavailable' or 'unknown' when the car isn't plugged in.
You may need to wrap the car charger energy sensor into a daily resetting utility meter to create a sensor that increments through the day and only changes to zero at midnight, or if your car energy sensor reports unknown/unavailable then create a helper template sensor, e.g.: diff --git a/docs/compare.md b/docs/compare.md index e1e37b367..dfebc686d 100644 --- a/docs/compare.md +++ b/docs/compare.md @@ -19,7 +19,7 @@ When changing tariffs, you should use your judgment, the Predbat Compare data is ## Configuring the tariff's to compare -First, you need to tell Predbat in apps.yaml which tariffs you want to compare, you should list all the tariffs you realistically might want to switch between, including your current tariff to act as a baseline. +First, you need to tell Predbat in `apps.yaml` which tariffs you want to compare, you should list all the tariffs you realistically might want to switch between, including your current tariff to act as a baseline. Below is a suggestion of various Octopus tariff combinations (valid October 2025) against region A. You will need to change **dno_region** to match your region code if you decide to use this template - see list of [Electricity region codes](https://energy-stats.uk/dno-region-codes-explained/). @@ -43,7 +43,7 @@ As well as Octopus rate URLs (rates_import_octopus_url/rates_export_octopus_url) Octopus integration rates (metric_octopus_import/metric_octopus_export) and Energi Data service rates (metric_energidataservice_import/metric_energidataservice_export). Each tariff must be given an ID which will be used to create a sensor to track predicted cost over time, the full name is used in the description of that sensor and on the web page. -The ID can contain alphanumeric characters or underscores; do not use slashes, commas, spaces or other special characters in the ID or predbat will crash when running the compare! +The ID can contain alphanumeric characters or underscores; do not use slashes, commas, spaces or other special characters in the ID or Predbat will crash when running the compare! If you do not set an import or export rate for a particular tariff then your existing energy rates will be used. @@ -138,7 +138,7 @@ The predicted cost is also shown, but keep in mind ending the day with an empty ## Comparison sensors -For each tariff a new sensor is created in Home Assistant called **predbat.compare_tariff_id** where **id** is the ID name you entered above in apps.yaml. This sensor will track the cost as its main value and many details about the prediction in its attributes. +For each tariff a new sensor is created in Home Assistant called **predbat.compare_tariff_id** where **id** is the ID name you entered above in `apps.yaml`. This sensor will track the cost as its main value and many details about the prediction in its attributes. You can create charts from these sensors to show how the different tariffs compare on a daily basis. @@ -146,8 +146,8 @@ You can create charts from these sensors to show how the different tariffs compa ## Overriding Predbat configuration per tariff -You can override any standard Predbat configuration setting for a specific tariff comparison using the `config:` block. -This is applied before the scenario is run and is used to model that comparison scenario. +You can override any standard Predbat configuration setting for a specific tariff comparison using the `config:` block in `apps.yaml`. +This configuration override is applied before the comparison scenario is run and is used to model that scenario. For example, to model a tariff combined with a higher minimum SoC target: @@ -167,7 +167,7 @@ These are applied after live inverter data is fetched, so they fully replace the All four keys are optional and can be combined freely: | Key | Description | Unit | -|-----|-------------|------| +| ----- | ------------- | ------ | | `override_soc_max_kwh` | Battery usable capacity | kWh | | `override_battery_rate_max_charge_kw` | Maximum battery charge rate | kW | | `override_battery_rate_max_discharge_kw` | Maximum battery discharge rate | kW | diff --git a/docs/components.md b/docs/components.md index 8ea0380ac..108bd2fab 100644 --- a/docs/components.md +++ b/docs/components.md @@ -185,7 +185,7 @@ Example usage in VSCode - Browse all entities - Retrieve battery plan data - Override plan for specific time periods -- Access apps.yaml configuration +- Access `apps.yaml` configuration --- @@ -281,7 +281,7 @@ Connects to your Octopus Energy account to automatically download your tariff ra | ------ | ---- | -------- | ------- | ---------- | ----------- | | `key` | String | Yes | - | `octopus_api_key` | Your Octopus Energy API key | | `account_id` | String | Yes | - | `octopus_api_account` | Your Octopus Energy account number (starts with A-) | -| `automatic` | Boolean | No | true | `octopus_automatic` | Set to `true` to automatically configure Predbat to use this Component (no need to update apps.yaml) | +| `automatic` | Boolean | No | true | `octopus_automatic` | Set to `true` to automatically configure Predbat to use this Component (no need to update `apps.yaml`) | #### How to get your API credentials (octopus) @@ -340,7 +340,7 @@ Select control my battery for 'Events Only'. 1. Log in to your Axle Energy VPP portal at 2. Navigate to the Home Assistant integration section 3. Copy your API key -4. Paste it into `axle_api_key` in apps.yaml +4. Paste it into **axle_api_key** in `apps.yaml` #### Sensor Attributes (axle) @@ -524,7 +524,7 @@ Integrates with Fox ESS inverters for monitoring and controlling Fox ESS battery | Option | Type | Required | Default | Config Key | Description | | ------ | ---- | -------- | ------- | ---------- | ----------- | | `key` | String | Yes | - | `fox_key` | Your Fox ESS API key | -| `automatic` | Boolean | No | false | `fox_automatic` | Set to `true` to automatically configured Predbat to use the Fox inverter (no manual apps.yaml updates required) | +| `automatic` | Boolean | No | false | `fox_automatic` | Set to `true` to automatically configured Predbat to use the Fox inverter (no manual `apps.yaml` updates required) | | `automatic_ignore_pv` | Boolean | No | false | `fox_automatic_ignore_pv` | When `automatic` is enabled, set to `true` to prevent Fox Cloud from overwriting `pv_power` and `pv_today` config. Useful for AC-coupled setups where PV is measured independently and Fox Cloud reports zero/absent PV data | --- @@ -676,7 +676,7 @@ Integrates with Solis inverters for monitoring and controlling Solis battery sys | `api_key` | String | Yes | - | `solis_api_key` | Your Solis Cloud API Key (KeyId) | | `api_secret` | String | Yes | - | `solis_api_secret` | Your Solis Cloud API Secret (KeySecret) | | `inverter_sn` | String/List | No | - | `solis_inverter_sn` | Inverter serial number(s) - Leave unset to see all. Single string or list of strings for multiple inverters | -| `automatic` | Boolean | No | false | `solis_automatic` | Set to `true` to automatically configure Predbat to use the Solis inverter (no manual apps.yaml sensor updates required) | +| `automatic` | Boolean | No | false | `solis_automatic` | Set to `true` to automatically configure Predbat to use the Solis inverter (no manual `apps.yaml` sensor updates required) | | `base_url` | String | No | Auto-detected | `solis_base_url` | Solis Cloud API base URL (automatically selects correct region) | | `control_enable` | Boolean | No | true | `solis_control_enable` | Enable/disable control commands (set to false for monitoring only) | | `nominal_voltage` | Float | No | - | `solis_nominal_voltage` | Your battery's nominal pack voltage (e.g. cell count x nominal cell voltage), used only for the battery capacity sensor. Not the same as the live measured battery voltage. Without it, the capacity sensor is still published but flagged unreliable - see [apps.yaml](apps-yaml.md#solis-cloud-api) | @@ -1110,7 +1110,7 @@ For a detailed explanation of how the neural network works and comprehensive con | `load_ml_max_days_history` | Integer | No | 28 | `load_ml_max_days_history` | Maximum days of load history to fetch from HA on each poll (bounded by HA recorder retention) | | `load_ml_database_days` | Integer | No | 90 | `load_ml_database_days` | Days of history to accumulate in the on-disk database (`predbat_ml_history.npz`); set to 0 to disable the database | -Note: `load_today`, `pv_today` and `car_charging_energy` apps.yaml configuration items are also used, but these should already be set in Predbat. +Note: **load_today**, **pv_today** and **car_charging_energy** `apps.yaml` configuration items are also used, but these should already be set in Predbat. #### Configuration example (load_ml) diff --git a/docs/configuration-guide.md b/docs/configuration-guide.md index 030c3a76e..cc00bc9d1 100644 --- a/docs/configuration-guide.md +++ b/docs/configuration-guide.md @@ -1,7 +1,7 @@ # Configuration guide First, get the basics set up, ensure you have the [inverter controls configured](install.md#inverter-control-install), -you have [configured apps.yaml](apps-yaml.md) to your setup, and the [solar forecast](install.md#solcast-install) is in place. +you have [configured apps.yaml](apps-yaml.md) to your setup, and the [solar forecast](install.md#solar-forecast-install) is in place. Make sure your [energy rates](energy-rates.md) are configured correctly for import and export. If you have an EV try to set up the [car charging sensor](car-charging.md#filtering-car-charging-energy-from-house-load) correctly so Predbat can tell what part of your historical load is EV charging. diff --git a/docs/customisation.md b/docs/customisation.md index 66ad08586..a94bf05db 100644 --- a/docs/customisation.md +++ b/docs/customisation.md @@ -4,7 +4,7 @@ This document describes the Predbat configuration items in Home Assistant that y All of these settings are entities that can be configured directly in Home Assistant (unlike the '[apps.yaml](apps-yaml.md)' configuration items that have to be edited with a file editor). -Note the default values of the settings inside Home Assistant are set inside Predbat, but the default can be overridden by setting its value in apps.yaml prior to starting Predbat for the first time. +Note the default values of the settings inside Home Assistant are set inside Predbat, but the default can be overridden by setting its value in `apps.yaml` prior to starting Predbat for the first time. See [Displaying output data](output-data.md) for information on how to view and edit these entities within diff --git a/docs/developing.md b/docs/developing.md index 7fb65f76e..3b5deea31 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -24,7 +24,7 @@ Predbat now has some unit-level tests, to run them on your local machine: 2. Copy `apps.yaml` to your test area 3. Copy the files from github to this area 4. Extract cases.tgz -5. Have /Volumes/add_configs/6adb4f0d_predbat point to your Predbat app directory or edit run_all to change the path +5. Have /Volumes/app_configs/6adb4f0d_predbat point to your Predbat app directory or edit run_all to change the path 6. Run run_all You can add --quick to run just the faster tests. If the tests fail then debug them. diff --git a/docs/devices.md b/docs/devices.md index a35e11de7..88b34c79d 100644 --- a/docs/devices.md +++ b/docs/devices.md @@ -22,7 +22,7 @@ For plugged-in detection on V2 models, see guidance There have been occasions with some Octopus Integration installations where the event name is as above but the sensor name has a different prefix, e.g. sensor.electricity_METER_NUMBER_current_rate and not sensor.octopus_energy_electricity_METER_NUMBER_current_rate.
@@ -133,11 +134,11 @@ If this is the case then the sensor must be renamed to the correct format so tha ### Standing charge Predbat can also (optionally) include the daily standing charge in cost predictions. -The following configuration item in apps.yaml defaults to obtaining the standing charge from the Octopus Energy integration: +The following configuration item in `apps.yaml` defaults to obtaining the standing charge from the Octopus Energy integration: - **metric_standing_charge** - Standing charge in pounds. By default points to the Octopus Energy integration sensor sensor.octopus_energy_electricity_METER_NUMBER_current_standing_charge -You can manually change this to a standing charge in pounds, e.g. 0.50 is 50p, or delete this line from apps.yaml, or set it to zero +You can manually change this to a standing charge in pounds, e.g. 0.50 is 50p, or delete this line from `apps.yaml`, or set it to zero if you don't want the standing charge (and only have consumption usage) to be included in Predbat charts and output data. Note that this configuration option to suppress the standing charge only applies if you are using the Octopus Integration from Predbat. @@ -184,11 +185,11 @@ simply delete or comment out the **octopus_saving_session** entry in `apps.yaml` Predbat can automatically detect Octopus free events and adjust your battery plan according. Note that this is derived from external sources, which do not verify your eligibility for free sessions. -For Predbat to automatically manage Octopus free sessions the following additional configuration item in apps.yaml is used. +For Predbat to automatically manage Octopus free sessions the following additional configuration item in `apps.yaml` is used. Note: **You must have signed up to the Octopus Octoplus scheme and eligible to benefit from these events** -Like the electricity rates, this is set in the apps.yaml template to a regular expression that should auto-discover the Octopus Energy integration. +Like the electricity rates, this is set in the `apps.yaml` template to a regular expression that should auto-discover the Octopus Energy integration. **octopus_free_session** - Will point to the free event sensor that is exposed by the Octopus Energy Integration. This event sensor contains the dates/times of all the free events. @@ -215,7 +216,7 @@ not work in future if Octopus ever change the website format. If you enable this As an alternative to the Octopus Direct or Octopus Energy integration methods, for Octopus Energy customers, you can configure Predbat to get the electricity rates directly online from the Octopus website. -In apps.yaml configure the following lines: +In `apps.yaml` configure the following lines: - **rates_import_octopus_url** to point to the appropriate import tariff URL on the Octopus website - **rates_export_octopus_url** to point to the export tariff URL @@ -351,7 +352,7 @@ To make sure Predbat calculates 15-minute pricing correctly, add `plan_interval_ Full code for CZ energy spot rate template: -(flat clamped sell price sensor is used to trick predbat when invertor is set to block export during negative pricing) +(flat clamped sell price sensor is used to trick Predbat when invertor is set to block export during negative pricing) ### Frank Energie Export rates with export fee @@ -395,9 +396,9 @@ A template sensor can be used to manipulate the rates provided by the integratio ## Rate Bands to manually configure Energy Rates -If you are not an Octopus Energy customer, or you are but your energy rates repeat simply, you can configure your rate bands in apps.yaml using rates_import/rates_export/rates_gas. +If you are not an Octopus Energy customer, or you are but your energy rates repeat simply, you can configure your rate bands in `apps.yaml` using rates_import/rates_export/rates_gas. -Add the following entries to apps.yaml to define the pattern of rates over 24 hours: +Add the following entries to `apps.yaml` to define the pattern of rates over 24 hours: ```yaml rates_import: @@ -441,7 +442,7 @@ The gas rates are only required if you have a gas boiler, or an iBoost, and are ## Manually over-riding energy rates -You can also override the import or export energy rates (regardless of whether they are set manually or via the Octopus Energy integration) by using the override feature in apps.yaml. +You can also override the import or export energy rates (regardless of whether they are set manually or via the Octopus Energy integration) by using the override feature in `apps.yaml`. Rate override is used to set the specific date and time period where your rates are different, e.g. an Octopus Power Up session (zero rate for an hour or two), or the British Gas half-price electricity on Sundays. @@ -520,7 +521,7 @@ and **input_number.predbat_metric_future_rate_offset_export** (*expert mode*) in ## Future Agile energy rates In the energy market, it's possible to calculate the Octopus Agile rates from around 10am UK time using public data, you can -enable this in apps.yaml for Import, Export or both. This will approximate the next day's rates based on the spot prices. +enable this in `apps.yaml` for Import, Export or both. This will approximate the next day's rates based on the spot prices. The approximation is only used until the real Octopus Agile rates are released around 4pm. - **futurerate_url** - URL of future energy market prices; this should not normally need to be changed @@ -602,7 +603,7 @@ NB: The postcode must be formatted as just the outward code (e.g. 'SW1') or a fu This direct connection will also set **sensor.predbat_carbon** with the current carbon data. -By enabling **switch.predbat_carbon_enable** you can view Carbon Intensity [in the predbat plan](predbat-plan-card.md). +By enabling **switch.predbat_carbon_enable** you can view Carbon Intensity [in the Predbat plan](predbat-plan-card.md). Predbat can also [optimise your grid charging based on the Carbon footprint](customisation.md#battery-margins-and-metrics-options) by setting **input_number.predbat_carbon_metric**. diff --git a/docs/faq.md b/docs/faq.md index 1297a40e3..cea6488d6 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -5,8 +5,8 @@ - First look at **predbat.status** in Home Assistant and the Predbat App log (which can be found in the list of log files in the System/Log area of the GUI). See if any errors are warnings are found. If you see an error something is likely configured incorrectly, check your entity settings are correct. -- Make sure Solcast is installed and it's auto-updated at least a couple of times a day (see the [Solcast instructions](install.md#solcast-install)). -The default Solcast sensor names may be wrong, you might need to update the `apps.yaml` config to match your own names +- Make sure a Solar Forecast is installed and it's auto-updated at least a couple of times a day (see the [Solar Forecast instructions](install.md#solar-forecast-install)). +If you are using the Solcast Integration then the default Solcast sensor names may be wrong, you might need to update the `apps.yaml` config to match your own names (some people don't have the solcast_ bit in their entity names). ## Predbat is failing with Warn: Service call select/select_option data failed @@ -179,7 +179,7 @@ Predbat can only work on the information it's given, although it does run every The plan Predbat produces assumes that your average load and PV forecasts are accurate and Predbat will aim to give you the maximum return. -Make sure you have set up your [Solcast solar forecast correctly](install.md#solcast-install) with the number of panels, orientation, output, etc. +Make sure you have set up your [Solar forecast correctly](install.md#solar-forecast-install) with the number of panels, orientation, output, etc. Projected daily load is determined from historical load information so make sure you have set [days_previous and days_previous_weight in apps.yaml](apps-yaml.md#basics) to give appropriately representative load history to Predbat, and read the [longer explanation of how days_previous works](apps-yaml.md#understanding-how-days_previous-works). @@ -326,7 +326,7 @@ delete all the old Octopus sensors, and [re-install the Octopus Integration](ins If you get this warning message in the Predbat log file or you see that the 'PV kWh' column in the [Predbat plan card](predbat-plan-card.md) is completely blank: -- Ensure that you have [installed and configured Solcast correctly](install.md#solcast-install) +- Ensure that you have [installed and configured a Solar Forecast correctly](install.md#solar-forecast-install) - Check the Solcast integration in Home Assistant is configured and enabled (go to Settings / Integrations / Solcast) - Check that there are no errors relating to Solcast in the Home Assistant log (go to Settings / System / Logs and view the 'Home Assistant Core' log). If you see an error 429 message in the log then this is as a result of Solcast's rate limiting for Hobbyist accounts. @@ -397,7 +397,7 @@ In the GivEnergy portal, go to My Inverters / Remote Control (cog symbol) / clic - Finally, check how often your inverter integration is polling your inverter for new data. For [GivTCP the Self Run Loop Timer](apps-yaml.md#rest-interface-inverter-control) is recommended to be set to a value of between 20 and 60 seconds. -If you have checked the above and keep getting “time is skewed” warnings then it means Home Assistant/predbat isn’t getting the same time from the inverter as it is expecting. +If you have checked the above and keep getting “time is skewed” warnings then it means Home Assistant/Predbat isn’t getting the same time from the inverter as it is expecting. Either GivTCP has lost communications with the inverter or the inverter has stopped talking to the world. If you look at the Logbook in Home Assistant you should see a steady stream of entities changing in HA. diff --git a/docs/install.md b/docs/install.md index 633b3ec05..7ae3650a6 100644 --- a/docs/install.md +++ b/docs/install.md @@ -19,6 +19,10 @@ There are plenty of "Home Assistant basics" tutorials on YouTube, but here are a If you get stuck, please read the [FAQs](faq.md) and if necessary raise a [GitHub ticket](https://github.com/springfall2008/batpred/issues) for support. +## Home Assistant Pre-requisites + +Ensure that the computer (or VM) that is running your Home Assistant has sufficient memory. Predbat (and in particular LoadML if you are using it) can be quite resource intensive and its recommended that your HA computer has at least 3Gb of memory allocated. If Predbat doesn't have sufficient memory then it can crash with few diagnostics. + ## Inverter Control install You will need to install an integration to communicate with and control your inverter. Predbat was originally written for GivEnergy inverters controlled by the GivTCP app but has been extended for other inverter types. @@ -74,12 +78,12 @@ Ensure 'start on boot' is enabled and click 'start'. **Note:** Throughout the rest of the Predbat documentation you will find reference to the Predbat configuration file `apps.yaml` and the Predbat logfile. -These are located under the Home Assistant directory `/addon_configs/6adb4f0d_predbat` which contains: +These are located under the Home Assistant directory `/apps_configs/6adb4f0d_predbat` which contains: - **predbat.log** - Predbat's active logfile that reports details of what Predbat is doing, and details of any errors - **apps.yaml** - Predbat's configuration file which will need to be customised to your system and requirements. This configuration process is described below. -You can use your file editor (i.e. 'File editor' or 'Studio Code Server' app) to open the directory `/addon_configs/6adb4f0d_predbat` and view these files. +You can use your file editor (i.e. 'File editor' or 'Studio Code Server' app) to open the directory `/apps_configs/6adb4f0d_predbat` and view these files. The Predbat web interface will work through the Predbat app, you can click on the 'Web UI' button to open it once Predbat is running. @@ -95,14 +99,14 @@ Installation instructions can be found at [https://github.com/nipar4/predbat_add Installing HACS, Appdaemon and then Predbat within Appdaemon has been deprecated, please swap to either the Predbat app or Docker installation methods. -The process to [upgrade from Predbat within Appdaemon to Predbat addon](#upgrading-from-appdaemon-to-predbat-app) is described below. +The process to [upgrade from Predbat within Appdaemon to Predbat App](#upgrading-from-appdaemon-to-predbat-app) is described below. -## Solcast Install +## Solar Forecast Install Predbat needs a solar forecast to predict solar generation and battery charging. If you have solar panels it's recommended to use the Solcast integration to retrieve your forecast solar generation. -If you do not want to use Solcast you can also use [Forecast.solar](#predbat-direct-to-forecastsolar) (less accurate) - see below. +If you do not want to use Solcast you can also use [Forecast.solar](#predbat-direct-to-forecastsolar) (less accurate) or [Open-Meteo](#predbat-direct-to-open-meteo) - see below. If you don't have one already, register for a free [Solcast hobbyist account](https://solcast.com/) and enter the details of your system. You can create 2 sites maximum under one (free hobbyist) account, if you have more aspects then it suggests you average the angle based on the number of panels @@ -243,7 +247,7 @@ For a house with two differently oriented roof aspects, add a second entry to th Install the Solcast integration (), create a free [Solcast account](https://solcast.com/), configure details of your solar arrays, and request an API key that you enter into the Solcast integration in Home Assistant. -Make sure that the configuration option 'Enable forecast half-hourly detail attributes' is turned on as predbat requires the half-hourly detailed solar forecast to populate the predbat plan. +Make sure that the configuration option 'Enable forecast half-hourly detail attributes' is turned on as Predbat requires the half-hourly detailed solar forecast to populate the Predbat plan. By default the Solcast integration only provides hourly forecasts and Predbat will take each hourly PV forecast and treat it as a half-hour value - doubling your solar generation forecast! Predbat is configured in `apps.yaml` to automatically discover the Solcast forecast entities created by the Solcast integration in Home Assistant. @@ -330,7 +334,7 @@ multiple import rates during the day, and variable tariffs such as Agile, etc. The detailed [Predbat Customisation Guide](customisation.md) details all the Predbat configuration items (switches, input numbers, etc) in Home Assistant, and what each of them does. -The Predbat [Web Interface](web-interface.md) provides an easy way to view all of the Predbat configuration items, check apps.yaml for errors, and view the logfile. +The Predbat [Web Interface](web-interface.md) provides an easy way to view all of the Predbat configuration items, check `apps.yaml` for errors, and view the logfile. ## Ready to light the touch-paper @@ -444,12 +448,12 @@ From the Assets list, download the source code (zip) file: - Unzip the source code file you have downloaded - Navigate to the release sub folder, then `apps`, and finally the `predbat` sub-folder - Shutdown Predbat -- Copy all the .py files from the `predbat` sub-folder into your Predbat application folder in Home Assistant and overwrite the existing .py files. If you are running the Predbat app then the destination folder will be `/addon_configs/6adb4f0d_predbat` +- Copy all the .py files from the `predbat` sub-folder into your Predbat application folder in Home Assistant and overwrite the existing .py files. If you are running the Predbat app then the destination folder will be `/apps_configs/6adb4f0d_predbat` - Restart Predbat ## Upgrading from AppDaemon to Predbat app -These steps assume you already have a working Predbat system and want to upgrade to using the Predbat app instead of using either the AppDaemon or the AppDaemon-predbat app. +These steps assume you already have a working Predbat system and want to upgrade to using the Predbat app instead of using either the (now deprecated) AppDaemon or the AppDaemon-predbat apps. Using the Predbat app is the strategic direction for Predbat and resolves some performance and data load issues that can occur with AppDaemon. The Predbat code that runs is the same and the configuration is exactly the same, it is just changing the 'container' that Predbat runs within. @@ -470,21 +474,21 @@ The Predbat code that runs is the same and the configuration is exactly the same - Click on the existing AppDaemon/AppDaemon-predbat app - Click STOP, and untick 'Start on boot' -6. Briefly start the new Predbat app so that it creates the addon_config folder and the template `apps.yaml` file: +6. Briefly start the new Predbat app so that it creates the `app_configs` folder and the template `apps.yaml` file: - Go to Settings/Apps - Click on the Predbat app - - Click START, wait a minute for the app to initialise itself, then click STOP. A predbat status warning that you have a template apps.yaml file is normal and can be ignored + - Click START, wait a minute for the app to initialise itself, then click STOP. A Predbat status warning that you have a template `apps.yaml` file is normal and can be ignored 7. Open your file editor and open your existing `apps.yaml` file: - - If you are using the old 'combined AppDaemon/Predbat app installation method' it's in the directory `/addon_configs/46f69597_appdaemon-predbat/apps`, + - If you are using the 'combined AppDaemon/Predbat app installation method' it's in the directory `/app_configs/46f69597_appdaemon-predbat/apps`, or - - with the old HACS Appdaemon app then Predbat installation method it's in `/config/appdaemon/apps/batpred/config/` + - with the original HACS Appdaemon app then Predbat installation method it's in `/config/appdaemon/apps/batpred/config/` -8. Select all the contents of the apps.yaml file and 'copy' (control-C, command-C, etc as appropriate) +8. Select all the contents of the `apps.yaml` file and 'copy' (control-C, command-C, etc as appropriate) -9. Now open the template `apps.yaml` file that's supplied with the Predbat app and has been created in the directory `/addon_configs/6adb4f0d_predbat`, -select all the contents of the template apps.yaml file, and paste in the contents of your existing apps.yaml, overwriting the template with your specific configuration +9. Now open the template `apps.yaml` file that's supplied with the Predbat app and has been created in the directory `/app_configs/6adb4f0d_predbat`, +select all the contents of the template `apps.yaml` file, and paste in the contents of your existing `apps.yaml`, overwriting the template with your specific configuration 10. Now you are ready to swap from running the AppDaemon or AppDaemon-predbat app to the Predbat app: - Go to Settings/Apps diff --git a/docs/installation-summary.md b/docs/installation-summary.md index 4afb4a052..4c4bcc9d9 100644 --- a/docs/installation-summary.md +++ b/docs/installation-summary.md @@ -7,14 +7,14 @@ see the [video guides](video-guides.md) section for those and other videos 2. Make sure the right [inverter control module](install.md#inverter-control-install) is installed and running 3. Install a file editor (either the File editor or Studio Code Server app) to enable you to edit configuration files if you haven't already - [Editing configuration files](install.md#editing-configuration-files-in-home-assistant) 4. Install - [Predbat app install](install.md#predbat-app-install) for HAOS, or as a [Docker install](install.md#docker-install) -5. Decide on and setup a Solar Forecast. It's recommended that you use [Solcast](install.md#solcast-install), or you can use [Open-Meteo](install.md#predbat-direct-to-open-meteo) (free, no API key required) or [Forecast.solar](install.md#predbat-direct-to-forecastsolar).
For Solcast: - - Register for a Solcast hobbyist account if you haven't already [Solcast install](install.md#solcast-install) and either: +5. Decide on and setup a Solar Forecast. It's recommended that you use [Solcast](install.md#solcast-home-assistant-integration-method), or you can use [Open-Meteo](install.md#predbat-direct-to-open-meteo) (free, no API key required) or [Forecast.solar](install.md#predbat-direct-to-forecastsolar).
For Solcast: + - Register for a Solcast hobbyist account if you haven't already [Solcast install](install.md#solar-forecast-install) and either: - [Configure Predbat to call Solcast](install.md#predbat-direct-solcast-method) for the Solar forecast, or - [Install and configure the Solcast integration](install.md#solcast-home-assistant-integration-method), and check that you see the Solcast data in Home Assistant 6. Follow the [Energy Rates](energy-rates.md) instructions to tell Predbat what your import and export energy rates are. If you use Octopus Energy then this includes installing the Octopus Energy integration (if you haven't already) - [Octopus Energy](energy-rates.md#octopus-energy-integration) 7. Edit Predbat's `apps.yaml` configuration file to match your system - [apps.yaml settings](apps-yaml.md)
-The apps.yaml file will be in either the directory `/addon_configs/6adb4f0d_predbat` or `/config/appdaemon/apps/predbat/config/` depending on which Predbat install method you used. +The apps.yaml file will be in either the directory `/app_configs/6adb4f0d_predbat` or `/config/appdaemon/apps/predbat/config/` depending on which Predbat install method you used. - Inverter settings match the names in GivTCP - should be automatic (but if you have 2 names you will have to edit apps.yaml) - You have set the right number of inverters (**num_inverters**) - Adjust your **inverter_limit** and **export_limit** as required diff --git a/docs/inverter-setup.md b/docs/inverter-setup.md index e9ba1b1e3..a15ba7776 100644 --- a/docs/inverter-setup.md +++ b/docs/inverter-setup.md @@ -87,7 +87,7 @@ It's recommended that you first watch the [Installing GivTCP and Mosquitto Apps '[https://github.com/britkat1980/ha-addons](https://github.com/britkat1980/ha-addons')' into the text box and click 'Add' then 'Close'
NB: this URL is for GivTCP v3, not v2 as covered in the video. - Click the back button and then re-navigate to Settings / Apps / Install app so Home Assistant picks up the GivTCP app from the custom repository -- Scroll down the app list, to find 'GivTCP-V3', you should see the three addons; the production version, the latest beta and the latest dev versions. +- Scroll down the app list, to find 'GivTCP-V3', you should see the three apps; the production version, the latest beta and the latest dev versions. Click on the 'GivTCP' app, then click 'INSTALL' - Once GivTCP has been installed, ensure that the 'Start on boot' and 'Watchdog' options are turned on @@ -105,7 +105,7 @@ Click on the 'GivTCP' app, then click 'INSTALL' For example, if you have a gateway and two AIOs you could use the prefixes 'GW', 'AIO-1' and 'AIO-2'. The prefixes should be set before you start using GivTCP in anger as changing the prefixes later on will result in both the old and new sensor names appearing in Home Assistant with the 'old' sensors being "unavailable".
- Note that if you do change the givtcp prefixes then you will also have to edit the apps.yaml configuration file to match, + Note that if you do change the givtcp prefixes then you will also have to edit the `apps.yaml` configuration file to match, and change the sensor names that Predbat is looking for (by default prefixed 'givtcp_xxx') to your new sensor naming structure - Click Next and Next to get to the Selfrun page, and turn on Self Run so that GivTCP automatically retrieves data from your inverter. The Self Run Loop Timer is how often GivTCP will retrieve data - it's @@ -660,7 +660,7 @@ Max value: (Inverter Battery max charge in watt) input_number.predbat_discharge_rate # this is used to set battery discharge to zero Min value: 0 Max value: (Inverter Battery max discharge in watt) -```text +``` - To control the Kostal inverter you need to use a modbus/tcp connection, this is not a part of the Kostal integration. Add the following modbus configuration to your `configuration.yaml`: @@ -1419,8 +1419,7 @@ triggers: entity_id: automation.luxpower_freeze_charge_watchdog mode: single - - ``` +``` **Enable Freeze Charging** @@ -1438,7 +1437,7 @@ If you have a LuxPower inverter with the **Charge Last** feature, enable the Pre **Note** Freeze Exporting requires fewer supporting automations than Freeze Charging, as it relies primarily on inverter-side behaviour. No additional watchdog or guard logic is required. - In your `apps.yaml` file: +In your `apps.yaml` file: - Look for `support_discharge_freeze` in the inverter section and change `False` to `True` - Uncomment the last two lines of the `discharge_stop_service` section so Predbat turns `switch.lux_charge_last` off when Freeze exporting stops. @@ -1461,7 +1460,7 @@ After Predbat recomputes, you may see some dark grey **FrzExp** slots in the sta ```yaml name: Predbat Ready entity_id: input_boolean.predbat_ready -```text +``` The `predbat_ready` helper prevents automation actions until LuxPower entities are fully available after startup. Ensure it is On after it has been created. @@ -2008,7 +2007,7 @@ sensor: If you have multiple batteries connected to your SolarEdge inverter and are using the SolarEdge Modbus Multi integration, this enumerates the multiple batteries as b1, b2, b3, etc with separate entities per battery. -You will need to make a number of changes to the solaredge apps.yaml, replacing the following entries: +You will need to make a number of changes to the solaredge `apps.yaml`, replacing the following entries: ```yaml battery_rate_max: diff --git a/docs/load-ml.md b/docs/load-ml.md index 78322a036..c12b1f411 100644 --- a/docs/load-ml.md +++ b/docs/load-ml.md @@ -112,7 +112,7 @@ To prevent drift in long-range predictions, the model blends autoregressive pred - Validates on the last 24 hours of data - Saves model to disk: `predbat_ml_model.npz` -**Regularization:** +**Regularisation:** - **Weight Decay**: L2 penalty (0.01) applied to network weights to prevent overfitting - **Dropout**: 10% of hidden neurons are randomly dropped during each training forward pass (inverted dropout — no scaling needed at inference). Reduces over-reliance on any single neuron. @@ -293,6 +293,7 @@ Before enabling ML load prediction: 2. Optionally configure `pv_today` if you have solar panels 3. **Recommended**: Enable the Temperature component (Temperature Component in components documentation) 4. Ensure you have at least 1 day of historical data (7+ days recommended); the database will accumulate history over time beyond what HA retains +5. Ensure that the computer (or VM) that is running your Home Assistant has sufficient memory. LoadML is quite resource intensive and its recommended that your HA computer has at least 3Gb of memory allocated. If Predbat doesn't have sufficient memory then it can crash with few diagnostics. ### Step 2: Enable the Component diff --git a/docs/output-data.md b/docs/output-data.md index 35f79dab0..eb6a31cc8 100644 --- a/docs/output-data.md +++ b/docs/output-data.md @@ -20,7 +20,7 @@ Each Predbat configuration item is named *input_number.predbat_xxx*, *switch.pre Each time Predbat runs it auto-generates a dashboard with the filename **predbat_dashboard.yaml** that can be used as a starter for your own Predbat dashboard. Depending on how you installed Predbat this predbat_dashboard.yaml file will be held in one of three different directories in Home Assistant: -- if you have used the [Predbat app installation method](install.md#predbat-app-install), it will be in the directory `/addon_configs/6adb4f0d_predbat/`, +- if you have used the [Predbat app installation method](install.md#predbat-app-install), it will be in the directory `/app_configs/6adb4f0d_predbat/`, or, @@ -293,7 +293,7 @@ Predbat outputs the values it read from your inverters as totals, this gives the Predbat outputs the following sensors to predict what your battery is expected to do *over the forecast_hours duration of the plan* with no changes made by Predbat. This is considered to be the 'baseline' plan: -NB: All of Predbat's forecasts are from midnight today to the forecast_hours duration (set in apps.yaml) into the future and shouldn't be confused with 'today' figures. +NB: All of Predbat's forecasts are from midnight today to the forecast_hours duration (set in `apps.yaml`) into the future and shouldn't be confused with 'today' figures. e.g. predbat.pv_energy is the actual PV energy from midnight today, and for the predicted forecast_hours (typically 48) ahead so will be much larger than sensor.solcast_pv_forecast_today which is today's Solcast PV forecast. @@ -389,11 +389,12 @@ Predbat outputs the following best results under the PV 10% scenario for the for The following sensors are used in the in-day adjustment chart - see [creating the Predbat charts](creating-charts.md) and [in-day load adjustment](customisation.md#battery-margins-and-metrics-options): - predbat.load_energy_actual - Total kWh of house load to end of plan, energy up to 'now' taken from today's actual energy, energy after 'now' from Predbat's prediction. -Attributes of this actual/predicted energy in 5-minute slots from midnight today to the end of the plan for charting -- predbat.load_energy_adjusted - Total kWh of predicted house load to end of the plan, adjusted based on variance of today's actual load +`results` attributes of this actual/predicted energy in 5-minute slots from midnight today to the end of the plan for charting +- predbat.load_energy_adjusted - Total kWh of predicted house load to end of today, adjusted based on variance of today's actual load to the predicted load (based on historical data), dampened according to input_number.predbat_metric_inday_adjust_damping. -Attributes contain the 5-minute slot forecasts to the end of the plan for charting -- predbat.load_energy_predicted - Total predicted kWh of house load to end of plan, attributes of predicted load in 5-minute slots from midnight today to the end of the plan for charting +`results` attribute contain the 5-minute slot forecasts to the end of the plan for charting, `today` attribute contains the total predicted today (same as the entity state), `today_so_far` the cumulative actual load energy consumed so far today, and `today_remaining` the remaining load energy predicted for today. +- predbat.load_energy_predicted - Total predicted kWh of house load to end of today, `results` attribute of predicted load in 5-minute slots from midnight today to the end of the plan for charting and `today`, `today_so_far` and `today_remaining` that are similar to predbat.load_energy_adjusted.
+**NB:** If you are using the [LoadML](load-ml.md) engine for your load prediction instead of days_previous, then be aware that the entity value and attributes of predbat.load_energy_predicted are not populated correctly. Use predbat.load_energy_adjusted instead until this is fixed. - predbat.load_inday_adjustment - the % in-day adjustment factor used to adjust Predbat's predicted load by the actual load today. After midnight when insufficient data is available, this blends yesterday's final adjustment factor with today's developing factor: - **0-3 hours**: Uses 100% of yesterday's adjustment factor @@ -406,14 +407,14 @@ After midnight when insufficient data is available, this blends yesterday's fina ## 'Today' energy data The following sensor's output by Predbat give the 'today' energy readings. -They mirror input sensors fed into Predbat in apps.yaml and are used in the data prediction chart - see [creating the Predbat charts](creating-charts.md): +They mirror input sensors fed into Predbat in `apps.yaml` and are used in the data prediction chart - see [creating the Predbat charts](creating-charts.md): -- predbat.export_energy_h0 - Mirrors the export_today sensor configured in apps.yaml and gives today's total kWh of export energy -- predbat.import_energy_h0 - Mirrors the import_today sensor configured in apps.yaml and gives today's total kWh of import energy -- predbat.load_energy_h0 - Mirrors the load_today sensor configured in apps.yaml and gives today's total kWh of house load energy. +- predbat.export_energy_h0 - Mirrors the export_today sensor configured in `apps.yaml` and gives today's total kWh of export energy +- predbat.import_energy_h0 - Mirrors the import_today sensor configured in `apps.yaml` and gives today's total kWh of import energy +- predbat.load_energy_h0 - Mirrors the load_today sensor configured in `apps.yaml` and gives today's total kWh of house load energy. Note that if you have configured [load scaling](customisation.md#scaling-and-weight-options) then load_energy_h0 will have been scaled by the scaling factor. -- predbat.pv_energy_h0 - Mirrors the pv_today sensor configured in apps.yaml and gives today's total kWh of generated PV energy -- predbat.soc_kw_h0 - Mirrors the soc_kwh sensor configured in apps.yaml and gives today's total kWh of battery state of charge (SoC). +- predbat.pv_energy_h0 - Mirrors the pv_today sensor configured in `apps.yaml` and gives today's total kWh of generated PV energy +- predbat.soc_kw_h0 - Mirrors the soc_kwh sensor configured in `apps.yaml` and gives today's total kWh of battery state of charge (SoC). Note that if you have configured [battery scaling](apps-yaml.md#battery-size-scaling) then soc_kw_h0 will have been scaled by the configured scaling factor ## Battery status @@ -503,6 +504,10 @@ You'll need to change the hard-coded timestamp "2024-12-12..." to the date/time *TIP:* If your inverter is ever replaced and you want to reset the inverter register writes back to zero, simply update the entity state of predbat.inverter_register_writes using Settings / Developer Tools / States and search for the entity. +## Battery data + +- sensor.predbat_soc_max_calculated[_N] - Created if [automatic battery size scaling is enabled in apps.yaml](apps-yaml.md#battery-size-scaling), contains a 7 day rolling history of daily estimates of usable battery capacity, calculated from historical charging data compared to nominal capacity. + ## Car data - binary_sensor.predbat_car_charging_slot - A binary sensor indicating when to charge your car (if car planning is enabled) - which can be used in an automation @@ -596,7 +601,7 @@ and only charging at the lowest import rate in the 24 hour period vs not having a PV and battery system at all and all house load being met from grid import Note: The savings using Predbat are calculated by default compared to having one fixed nightly charge slot set to charge at the lowest import rate with a target of 100% -You can change the number of simulated charge slots in apps.yaml by setting **calculate_savings_max_charge_slots** to the number of slots to allow. +You can change the number of simulated charge slots in `apps.yaml` by setting **calculate_savings_max_charge_slots** to the number of slots to allow. If set to 0 then Demand (ECO) mode will be used as the baseline or if non-zero then the maximum number of slots can be set (e.g. 2). Note: The 'without Predbat' simulation is a self-consistent parallel universe — each day's starting SoC is the ending SoC of the previous day's simulation, not the actual midnight SoC. @@ -620,9 +625,9 @@ levels and at different times in the upcoming forecast window. The results are p - `grid_export_now` - Current grid export rate (p/kWh) - `baseline_metric` - Internal baseline cost used to compute deltas (standing charge excluded) - `rate_now_low_consumption` - Marginal cost now for the 'low' (1 kWh) load level - - `rate_now_med_consumption` - Marginal cost now for the 'med' (2 kWh) load level + - `rate_now_med_consumption` - Marginal cost now for the 'medium' (2 kWh) load level - `rate_now_high_consumption` - Marginal cost now for the 'high' (4 kWh) load level - - `rate_now_ev_consumption` - Marginal cost now for the 'ev' (8 kWh) load level + - `rate_now_ev_consumption` - Marginal cost now for the 'EV' (8 kWh) load level ### Cheap/moderate binary sensors @@ -648,7 +653,7 @@ These binary sensors are useful in Home Assistant automations, e.g. to start an ## Solar forecast data The following sensors give the forecast Solar data from Solcast. -Predbat populates these sensors irrespective of whether you are using the [Predbat direct Solcast or Solcast integration method](install.md#solcast-install) to get your Solar forecast, +Predbat populates these sensors irrespective of whether you are using the [Predbat direct Solcast or Solcast integration method](install.md#solar-forecast-install) to get your Solar forecast, but if you are using the Solcast integration then the Predbat sensors mirror the similarly named Solcast integration sensors so could be disabled if you so wish. - sensor.predbat_pv_today - Tracks the PV forecast in kWh for today, attributes give the total today, remaining amount today and the half-hourly data @@ -693,11 +698,11 @@ If you are using the Predbat app then the logfile can easily be viewed via the ' To directly view the physical logfile, it can be found in one of three different directories in Home Assistant with slightly different filenames depending on how you installed Predbat: -- if you have used the [Predbat app installation method](install.md#predbat-app-install), the logfile will be `/addon_configs/6adb4f0d_predbat/predbat.log`, +- if you have used the [Predbat app installation method](install.md#predbat-app-install), the logfile will be `/app_configs/6adb4f0d_predbat/predbat.log`, -- if the [HACS, Appdaemon app then Predbat installation method](install.md#predbat-installation-into-appdaemon), it's `/homeassistant/appdaemon/appdaemon.log`, or +- if the original (but now deprecated) [HACS, Appdaemon app then Predbat installation method](install.md#predbat-installation-into-appdaemon), it's `/homeassistant/appdaemon/appdaemon.log`, or -- if the combined AppDaemon/Predbat app installation method was used, it's `/addon_configs/46f69597_appdaemon-predbat/predbat.log`. +- if the (also deprecated) combined AppDaemon/Predbat app installation method was used, it's `/app_configs/46f69597_appdaemon-predbat/predbat.log`. You will need to use a file editor within Home Assistant (e.g. either the File editor or Studio Code Server apps) to view Predbat's logfile if you are not using the Predbat app. @@ -816,17 +821,17 @@ actions: value_template: "{{ restart_app == 'GivTCP' }}" sequence: - alias: Restart GivTCP app - action: hassio.addon_restart + action: hassio.app_restart data: - addon: 533ea71a_givtcp + app: 533ea71a_givtcp - conditions: - condition: template value_template: "{{ restart_app == 'Mosquitto' }}" sequence: - alias: Restart Mosquitto app - action: hassio.addon_restart + action: hassio.app_restart data: - addon: core_mosquitto + app: core_mosquitto trace: stored_traces: 20 mode: single @@ -873,6 +878,15 @@ triggers: alert_text: >- Predbat status is {{ states('predbat.status') }}, error={{ state_attr('predbat.status', 'error') }} + - trigger: template + alias: Predbat status contains 'unable to read REST data' for 10 minutes + value_template: "{{ 'unable to read REST data' in states('predbat.status') }}" + for: + minutes: 10 + variables: + alert_text: >- + Predbat status is {{ states('predbat.status') }}, error={{ + state_attr('predbat.status', 'error') }} - trigger: state alias: Predbat is in error status for 10 minutes entity_id: predbat.status @@ -981,9 +995,9 @@ actions: - condition: template value_template: "{{ restart_predbat == 'Y' }}" then: - - action: hassio.addon_restart + - action: hassio.app_restart data: - addon: 6adb4f0d_predbat + app 6adb4f0d_predbat alias: Restart Predbat app mode: single ``` diff --git a/docs/predbat-plan-card.md b/docs/predbat-plan-card.md index ebeb7fa8f..b51cb2a3a 100644 --- a/docs/predbat-plan-card.md +++ b/docs/predbat-plan-card.md @@ -53,9 +53,9 @@ Rate symbols (import and export): - ? ⅆ - Rate that has been modified based on **input_number.predbat_metric_future_rate_offset_import** or **input_number.predbat_metric_future_rate_offset_export** - ? ⚖ - Rate that has been estimated using future rate estimation data (e.g. Nordpool) -- = - Rate that has been overridden by the user's apps.yaml -- ± - Rate that has been adjusted with a rate offset in the user's apps.yaml -- $ - Rate that has been adjusted for an Octopus Saving session +- = - Rate that has been overridden by the user's `apps.yaml` +- ± - Rate that has been adjusted with a rate offset in the user's `apps.yaml` +- $ - Rate that has been adjusted for an Octopus Saving session or Axle event - ? - Rate that has not yet been defined and the previous day's data was used instead Battery SoC symbols: diff --git a/docs/web-interface.md b/docs/web-interface.md index 01458f0de..61fe770b8 100644 --- a/docs/web-interface.md +++ b/docs/web-interface.md @@ -80,12 +80,12 @@ You can also [create a Predbat Plan card](predbat-plan-card.md) to show Predbat' ### Entities View -This view enables you to look at the history of any predbat output entity or (input) configuration setting. +This view enables you to look at the history of any Predbat output entity or (input) configuration setting. Select the entity required to be viewed, the attributes to be charted (defaults to entity state) and the time period of history to view the entity over (but note you may need to [increase HA's purge_keep_days](apps-yaml.md#days_previous) first). You can select multiple entities and their attributes and Predbat will then display the current entity values, a chart of the selected attribute history, and then a list of prior historical values. -The example below shows predbat import and export rates charted for the last 7 days: +The example below shows Predbat import and export rates charted for the last 7 days: ![image](images/web-interface-entities-view.png) ![image](images/web-interface-entities-chart.png) @@ -100,8 +100,9 @@ The chart also shows where charging is planned under the Base and Best scenarios - **Cost** - Shows the historic import, export and net total cost incurred for today and the predicted cost for the plan duration under the Base/Base10/Best and Best10 scenarios - **Rates** - Shows historic and future import and export rates along with historic hourly and today pence per kWh so you can see where you have earned or spent the most on electricity during today - **InDay** - Shows Predbat's predicted house load for today, the actual house load that has occurred so far today, and then Predbat's adjusted house load prediction based on the variance of today's actual load to predicted load -- **PV** - Shows today's predicted solar generation under the PV, PV10 and PV90 scenarios alongside today's actual solar generation -- **PV7** - Similar to the PV chart, but shows actual solar generation and forecast for the last 7 days including today +- **PV** - Shows today's predicted solar power generation under the PV, PV10 and PV90 scenarios alongside today's actual solar power generation +- **PV7** - Similar to the PV chart, but shows actual solar power generation and forecast for the last 7 days including today +- **PVAccuracy** - Shows how accurate the PV energy forecast is, comparing cumulative PV forecast energy for today to today's actual cumulative solar energy generation - **Load ML** - Shows the correlation between your actual house load and the [Load ML predictions](load-ml.md), charting current prediction, the 1 hour in the future prediction, and the 8 hours future prediction - **LoadMLPower** - Similar to the Load ML chart, but also plots actual PV production, predicted PV production and temperature predictions. - **MarginalCosts** - Shows the marginal cost of consuming extra electricity at different load levels (1, 2, 4, 8 kWh) across upcoming time windows. @@ -170,8 +171,7 @@ You can restart individual Predbat components if required. ### Editor View -The editor view allows you to edit `apps.yaml` as text directly within the web interface. If you make a syntax error then the error will be highlighted and save -will be disabled pending a fix. +The editor view allows you to edit `apps.yaml` as text directly within the web interface. If you make a syntax error then the error will be highlighted and save will be disabled pending a fix. image of Predbat Editor view @@ -191,9 +191,18 @@ These are intended for debugging and developer activities, in normal use you can ### Metrics View -The Metrics view gives a dashboard of Predbat's internal metrics, including application health, plan status, battery state, energy totals, costs, savings and API status. +The Metrics view provides a dashboard giving an overview of Predbat internal metrics, including application health, plan status, battery state, energy totals, costs, savings and API status. +The dashboard auto-refreshes every 30 seconds and contains the following 5 sections: +- **System Health** - shows a set of system health cards including confirming that Predbat is running, the configuration and plan are valid, how long since the last Predbat run, that there are no component errors and the amount of data history that Predbat is using. The System Health row includes a **Data Age** card, showing how many days of historical load data (`load_today`) Predbat was able to retrieve from Home Assistant, going back from now. This is a measure of how much history is *available*, not how stale the latest reading is - a higher number generally means better load forecasting, since day-of-week weighted forecasts (the `days_previous` setting) need enough history to match against. The card is only flagged as a warning when the retrieved depth falls short of what your `days_previous` configuration actually needs (shown in the "need Xd" sub-label) - for example, if `days_previous` includes `7`, Predbat needs at least 7 days of history, and a shortfall usually means Home Assistant's recorder purged old data before Predbat could read it, or a load sensor is newly added. +- **Battery Status** - shows battery SoC doughnut and power meters for current battery charge, discharge, house load, PV, grid import and grid export +- **Energy Today** - shows total energy today for house load, grid import, grid export and PV generation +- **Cost & Savings** - shows electricity cost today and yesterday, and savings achieved yesterday from having solar and battery and from using predbat +- **API & Solar Status** - shows API health (requests, failures and last call) made by Predbat to Axle, Solcast/Forecast.Solar, GivEnergy Cloud, Fox ESS Cloud, Solis Cloud, and Open Meteo (Temperature for LoadML). Data is only shown for components that are active, i.e. if Predbat is not using the GivEnergy Cloud integration, no API call metrics are shown.
+Solar status shows Solcast API calls (Predbat Solcast direct only) and PV Calibration Scaling (worst day, best day and total) + +![image](images/web-interface-metrics-view.png) ### Docs View diff --git a/templates/givenergy_givtcp.yaml b/templates/givenergy_givtcp.yaml index 54a1e6291..0809e3a61 100644 --- a/templates/givenergy_givtcp.yaml +++ b/templates/givenergy_givtcp.yaml @@ -90,8 +90,8 @@ pred_bat: # #auto_restart: # - shell: 'rm -rf /homeassistant/GivTCP/*.pkl' - # - service: hassio/addon_restart - # addon: 533ea71a_givtcp + # - service: hassio/app_restart + # app: 533ea71a_givtcp # Example on how to restart the inverter via GivTCP # - service: button.press