From a4f089fc6f2ff2681d3e0f3dc3e9561a4615cb4e Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 3 Jan 2025 10:18:54 +0000 Subject: [PATCH 01/42] Remove dependencies from `config.yaml` --- apps/pv_opt/config/config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/pv_opt/config/config.yaml b/apps/pv_opt/config/config.yaml index 9bc591e..652c820 100644 --- a/apps/pv_opt/config/config.yaml +++ b/apps/pv_opt/config/config.yaml @@ -14,9 +14,9 @@ inverters: pv_opt: module: pv_opt class: PVOpt - dependencies: - - pvpy - - solis + # dependencies: + # - pvpy + # - solis log: pv_opt_log prefix: pvopt From 4e9c7c505d88d48d83d48166f3c48223de758eb8 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 20:45:11 +0000 Subject: [PATCH 02/42] Fix for #355 --- apps/pv_opt/config/config.yaml | 6 +++--- apps/pv_opt/pv_opt.py | 1 + apps/pv_opt/pvpy.py | 2 ++ apps/pv_opt/solis.py | 10 +++++++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/pv_opt/config/config.yaml b/apps/pv_opt/config/config.yaml index 652c820..9bc591e 100644 --- a/apps/pv_opt/config/config.yaml +++ b/apps/pv_opt/config/config.yaml @@ -14,9 +14,9 @@ inverters: pv_opt: module: pv_opt class: PVOpt - # dependencies: - # - pvpy - # - solis + dependencies: + - pvpy + - solis log: pv_opt_log prefix: pvopt diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 35d7948..281c231 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -2414,6 +2414,7 @@ def optimise(self): self.log("") self.log(f"Initial SOC: {self.pv_system.initial_soc}") + self.log(f"Current SOC: {self.pv_system.soc_now}") self.pv_system.calculate_flows() self.flows = {"Base": self.pv_system.flows} diff --git a/apps/pv_opt/pvpy.py b/apps/pv_opt/pvpy.py index dd45e54..87109a3 100644 --- a/apps/pv_opt/pvpy.py +++ b/apps/pv_opt/pvpy.py @@ -822,6 +822,8 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio self.flows["soc"] = (self.flows["chg"] / self.battery.capacity) * 100 self.flows["soc_end"] = (self.flows["chg_end"] / self.battery.capacity) * 100 + # self.log(f">>>\n{self.flows.iloc[:3].to_string()}") + if self.prices is not None: self.flows = pd.concat( [self.prices, consumption, self.flows], diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index b1c4a08..610543e 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -698,6 +698,10 @@ def __init__(self, inverter_type, host): class SolisSolaxModbusInverter(SolisInverter): def __init__(self, inverter_type: str, host): super().__init__(inverter_type, host) + entity_id = self.brand_config["id_inverter_mode"] + entity_modes = self._host.get_state_retry(entity_id, attribute="options") + self._codes = INVERTER_DEFS[inverter_type]["codes"][self._hmi_fb00] + self._modes = {self._codes[code]: code for code in entity_modes} def _get_times_current(self, direction): # Required if the times are set as separate_hours and units @@ -795,9 +799,9 @@ def _write_modbus_register(self, register, value, cfg=None, tolerance=0, multipl self._host.call_service("modbus/write_register", **data) sleep(0.1) new_value = int(float(self.get_config(cfg))) / multiplier - self.log(f">>> current_value: {current_value/multiplier}") - self.log(f">>> value: {value}") - self.log(f">>> new_value: {new_value}") + # self.log(f">>> current_value: {current_value/multiplier}") + # self.log(f">>> value: {value}") + # self.log(f">>> new_value: {new_value}") written = new_value == value return changed, written From 2ffe491d628b9b402ca53a88ac8e2e09f1a06aab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Jan 2025 20:48:31 +0000 Subject: [PATCH 03/42] Update version to 4.0.6 --- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d39752..5403ee6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.5 +# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 281c231..c5309f1 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "4.0.5" +VERSION = "4.0.6" UNITS = { "current": "A", "power": "W", From 3525388ae7109481e806c0014ca6c877d1b568f2 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 20:55:12 +0000 Subject: [PATCH 04/42] Update version validation to use tags --- .github/workflows/black.yaml | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index f3c02db..f51f28e 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -65,25 +65,25 @@ jobs: exit 0 fi - - name: Fetch main branch into a temporary branch + - name: Get Latest Tag + id: get_latest_tag run: | - git fetch origin main - git checkout -b temp-main origin/main + LATEST_TAG=$(git describe --tags --abbrev=0) + if [ -z "$LATEST_TAG" ]; then + echo "Error: No tags found in the repository." >&2 + exit 1 + fi + echo "latest_tag=$LATEST_TAG" >> $GITHUB_ENV - - name: Get VERSION from Main Branch - id: get_main_version + - name: Get VERSION from Latest Tag + id: get_tag_version run: | - VERSION=$(grep -m 1 -oP '(?<=^VERSION = ")[^"]+' apps/pv_opt/pv_opt.py) + VERSION=$(echo "$LATEST_TAG" | grep -oP '(?<=v)[0-9]+\.[0-9]+\.[0-9]+') if [ -z "$VERSION" ]; then - echo "Error: VERSION not found in apps/pv_opt/pv_opt.py on main branch." >&2 + echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 fi - echo "main_version=$VERSION" >> $GITHUB_ENV - - - name: Switch Back to Source Branch - run: | - git fetch origin $GITHUB_HEAD_REF - git checkout $GITHUB_HEAD_REF + echo "tag_version=$VERSION" >> $GITHUB_ENV - name: Get VERSION from Current Branch id: get_patch_version @@ -99,16 +99,16 @@ jobs: id: validate_or_fix_version run: | patch_version=$patch_version - main_version=$main_version + tag_version=$tag_version - main_major=$(echo "$main_version" | awk -F '.' '{print $1}') - main_minor=$(echo "$main_version" | awk -F '.' '{print $2}') - main_patch=$(echo "$main_version" | awk -F '.' '{print $3}') + tag_major=$(echo "$tag_version" | awk -F '.' '{print $1}') + tag_minor=$(echo "$tag_version" | awk -F '.' '{print $2}') + tag_patch=$(echo "$tag_version" | awk -F '.' '{print $3}') if [[ "$GITHUB_HEAD_REF" == patch* ]]; then - new_patch_version="$main_major.$main_minor.$((main_patch + 1))" + new_patch_version="$tag_major.$tag_minor.$((tag_patch + 1))" elif [[ "$GITHUB_HEAD_REF" == dev* ]]; then - new_patch_version="$main_major.$((main_minor + 1)).0" + new_patch_version="$tag_major.$((tag_minor + 1)).0" else echo "Error: Unsupported source branch type." >&2 exit 1 From beddaa3065359424fa3e099d6566225d837640a5 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 20:56:06 +0000 Subject: [PATCH 05/42] Update auto-release workflow --- .github/workflows/auto_release.yaml | 59 +++++------------------------ 1 file changed, 9 insertions(+), 50 deletions(-) diff --git a/.github/workflows/auto_release.yaml b/.github/workflows/auto_release.yaml index 7bdd93e..87ef9f7 100644 --- a/.github/workflows/auto_release.yaml +++ b/.github/workflows/auto_release.yaml @@ -14,73 +14,32 @@ jobs: # Step 1: Checkout the repository - name: Checkout Repository uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history for all branches and tags - - # Step 2: Debug Tag Visibility - - name: List Tags - run: | - echo "Listing all available tags:" - git tag - # Step 3: Extract Version from `pv_opt.py` + # Step 2: Extract Version from `pv_opt.py` - name: Extract Version id: extract_version run: | - echo "Extracting version from apps/pv_opt/pv_opt.py" # Extract the VERSION variable from pv_opt.py VERSION=$(grep -oP '(?<=^VERSION = ")[^"]+' apps/pv_opt/pv_opt.py) if [ -z "$VERSION" ]; then echo "Error: VERSION not found in apps/pv_opt/pv_opt.py" exit 1 fi - echo "Extracted VERSION=$VERSION" + echo "VERSION=$VERSION" echo "version=$VERSION" >> $GITHUB_ENV # Save to environment file - # Step 4: Generate Release Notes - - name: Generate Release Notes - id: generate_notes - run: | - echo "Generating release notes..." - # Get the latest tag before this release - LAST_TAG=$(git describe --tags --abbrev=0 --match "v*" 2>/dev/null) - - # If no tags exist, use the initial commit hash - if [ -z "$LAST_TAG" ]; then - echo "No previous tags found. Using initial commit as starting point." - LAST_TAG=$(git rev-list --max-parents=0 HEAD) - else - echo "Found latest tag: $LAST_TAG" - fi - - # Gather commit messages since the last tag - echo "Collecting commits since $LAST_TAG..." - COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s (%h)") - - # Format the release notes - RELEASE_NOTES="## Changes\n" - if [ -z "$COMMITS" ]; then - echo "No significant changes found." - RELEASE_NOTES+="No significant changes." - else - echo "Found commits:" - echo -e "${COMMITS}" - RELEASE_NOTES+=$(echo -e "${COMMITS}") - fi - - # Output the release notes - echo -e "Release notes generated:\n${RELEASE_NOTES}" - echo "${RELEASE_NOTES}" > release_notes.txt - echo "RELEASE_NOTES=$(> $GITHUB_ENV - - - # Step 5: Create GitHub Release + # Step 3: Create GitHub Release - name: Create GitHub Release + if: | + github.event_name == 'push' || + (github.event_name == 'pull_request_review' && github.event.review.state == 'approved') uses: actions/create-release@v1 with: tag_name: "v${{ env.version }}" release_name: "Release v${{ env.version }}" - body: ${{ env.release_notes }} + body: | + ## Changes + This release was automatically generated. draft: false prerelease: false env: From 61f4ccf4bad2e3e7401d097a48fb38ed0e7fed34 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:01:22 +0000 Subject: [PATCH 06/42] Fix workflow --- .github/workflows/black.yaml | 2 +- apps/pv_opt/solis.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index f51f28e..0ccaaa8 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -78,7 +78,7 @@ jobs: - name: Get VERSION from Latest Tag id: get_tag_version run: | - VERSION=$(echo "$LATEST_TAG" | grep -oP '(?<=v)[0-9]+\.[0-9]+\.[0-9]+') + VERSION=$(echo "$LATEST_TAG" | sed -n 's/^v\([0-9]\+\.[0-9]\+\.[0-9]\+\)$/\1/p') if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index 610543e..e754d9a 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -799,9 +799,7 @@ def _write_modbus_register(self, register, value, cfg=None, tolerance=0, multipl self._host.call_service("modbus/write_register", **data) sleep(0.1) new_value = int(float(self.get_config(cfg))) / multiplier - # self.log(f">>> current_value: {current_value/multiplier}") - # self.log(f">>> value: {value}") - # self.log(f">>> new_value: {new_value}") + written = new_value == value return changed, written From 45972fe079c047bc8bf573a152f3cab47303c4e7 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:11:36 +0000 Subject: [PATCH 07/42] Add echo --- .github/workflows/black.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 0ccaaa8..9617535 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -78,13 +78,14 @@ jobs: - name: Get VERSION from Latest Tag id: get_tag_version run: | - VERSION=$(echo "$LATEST_TAG" | sed -n 's/^v\([0-9]\+\.[0-9]\+\.[0-9]\+\)$/\1/p') + VERSION=$(echo "$LATEST_TAG" | sed -n 's/^v\([0-9]\+\.[0-9]\+\.[0-9]\+\)$/\1/p' | tr -d '[:space:]') + echo "Extracted VERSION: '$VERSION'" if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 fi echo "tag_version=$VERSION" >> $GITHUB_ENV - + - name: Get VERSION from Current Branch id: get_patch_version run: | From 75fc08bb60b1914b519d8bff524b999682055003 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:13:33 +0000 Subject: [PATCH 08/42] Add logging --- .github/workflows/black.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 9617535..35c2a73 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -78,14 +78,16 @@ jobs: - name: Get VERSION from Latest Tag id: get_tag_version run: | - VERSION=$(echo "$LATEST_TAG" | sed -n 's/^v\([0-9]\+\.[0-9]\+\.[0-9]\+\)$/\1/p' | tr -d '[:space:]') - echo "Extracted VERSION: '$VERSION'" + echo "Latest Tag: '$LATEST_TAG'" # Debug: Print the raw tag value + VERSION=$(echo "$LATEST_TAG" | sed -n 's/^v\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\)$/\\1/p' | tr -d '[:space:]') + echo "Extracted VERSION: '$VERSION'" # Debug: Print the extracted version if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 fi echo "tag_version=$VERSION" >> $GITHUB_ENV + - name: Get VERSION from Current Branch id: get_patch_version run: | From ac8aac7ab946aeb4b099c6f9a67d341fb49a3089 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:16:31 +0000 Subject: [PATCH 09/42] Tag --- .github/workflows/black.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 35c2a73..75c4c36 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -68,12 +68,14 @@ jobs: - name: Get Latest Tag id: get_latest_tag run: | - LATEST_TAG=$(git describe --tags --abbrev=0) + LATEST_TAG=$(git tag --list | sort -V | tail -n 1) if [ -z "$LATEST_TAG" ]; then echo "Error: No tags found in the repository." >&2 exit 1 fi + echo "Latest Tag: '$LATEST_TAG'" # Debug: Log the tag echo "latest_tag=$LATEST_TAG" >> $GITHUB_ENV + - name: Get VERSION from Latest Tag id: get_tag_version From a4bc6ad1c300a0da6bc2cdaa1ebab61fd01b8e41 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:18:24 +0000 Subject: [PATCH 10/42] Updated workflow --- .github/workflows/black.yaml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 75c4c36..8f38bfd 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -73,21 +73,20 @@ jobs: echo "Error: No tags found in the repository." >&2 exit 1 fi - echo "Latest Tag: '$LATEST_TAG'" # Debug: Log the tag - echo "latest_tag=$LATEST_TAG" >> $GITHUB_ENV - + echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV + echo "Latest Tag: '$LATEST_TAG'" - name: Get VERSION from Latest Tag id: get_tag_version run: | - echo "Latest Tag: '$LATEST_TAG'" # Debug: Print the raw tag value - VERSION=$(echo "$LATEST_TAG" | sed -n 's/^v\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\)$/\\1/p' | tr -d '[:space:]') - echo "Extracted VERSION: '$VERSION'" # Debug: Print the extracted version + VERSION=$(echo "${{ env.LATEST_TAG }}" | sed -n 's/^v\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\)$/\\1/p') if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 fi - echo "tag_version=$VERSION" >> $GITHUB_ENV + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "Extracted VERSION: '$VERSION'" + - name: Get VERSION from Current Branch From 6bfb63fde24becf94a3eb37dfcf55ad55342f6dc Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:20:33 +0000 Subject: [PATCH 11/42] Updated workflow --- .github/workflows/black.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 8f38bfd..3623fc7 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -79,16 +79,15 @@ jobs: - name: Get VERSION from Latest Tag id: get_tag_version run: | - VERSION=$(echo "${{ env.LATEST_TAG }}" | sed -n 's/^v\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\)$/\\1/p') + VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v([0-9]+\\.[0-9]+\\.[0-9]+)$/\\1/') + echo "Extracted VERSION: '$VERSION'" if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 fi - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "Extracted VERSION: '$VERSION'" + echo "tag_version=$VERSION" >> $GITHUB_ENV - - name: Get VERSION from Current Branch id: get_patch_version run: | From 196f5feef24e63c94d6a490af8a21de5cf013bde Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:25:13 +0000 Subject: [PATCH 12/42] Updatd commit --- .github/workflows/black.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 3623fc7..fc19a58 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -129,6 +129,10 @@ jobs: - name: Commit Version Changes if: env.VERSION_CHANGED == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -140,4 +144,4 @@ jobs: exit 0 fi git commit -m "Update version to ${{ env.new_patch_version }}" - git push origin $GITHUB_HEAD_REF + git push origin HEAD From 0427fac5513f907a72384f2625e7b3aede25731b Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:26:42 +0000 Subject: [PATCH 13/42] Typo in workflow --- .github/workflows/black.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index fc19a58..ebd3110 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -129,10 +129,6 @@ jobs: - name: Commit Version Changes if: env.VERSION_CHANGED == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | From 8ac228672b38c2f50f8f1078e686a0cd67450518 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:29:06 +0000 Subject: [PATCH 14/42] sdg --- .github/workflows/black.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index ebd3110..447feca 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -115,7 +115,7 @@ jobs: else echo "Error: Unsupported source branch type." >&2 exit 1 - fi + fi sed -i "s/^VERSION = \".*\"/VERSION = \"$new_patch_version\"/" apps/pv_opt/pv_opt.py echo "Corrected version to $new_patch_version." From d351b8137e5f41e0ea191a4c8be3dfe34facd973 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:30:43 +0000 Subject: [PATCH 15/42] Test --- .github/workflows/black.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 447feca..2e87351 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -114,7 +114,7 @@ jobs: new_patch_version="$tag_major.$((tag_minor + 1)).0" else echo "Error: Unsupported source branch type." >&2 - exit 1 + exit 1 fi sed -i "s/^VERSION = \".*\"/VERSION = \"$new_patch_version\"/" apps/pv_opt/pv_opt.py From 96ed391ff9ebc1647a06b84993943ecbad748fac Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:32:16 +0000 Subject: [PATCH 16/42] Updated commit step --- .github/workflows/black.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 2e87351..b2289ba 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -134,10 +134,12 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -B $GITHUB_HEAD_REF # Create or switch to the correct branch git add apps/pv_opt/pv_opt.py README.md if git diff --cached --quiet; then echo "No version changes to commit." exit 0 fi git commit -m "Update version to ${{ env.new_patch_version }}" - git push origin HEAD + git push origin $GITHUB_HEAD_REF + \ No newline at end of file From 053898206da820115fb3ef6cf9cb8f66efe10f71 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Jan 2025 21:33:02 +0000 Subject: [PATCH 17/42] Update version to v4.0.6 --- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5403ee6..67eee2f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 +# PV Opt: Home Assistant Solar/Battery Optimiser vv4.0.6 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index c5309f1..ad06afb 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "4.0.6" +VERSION = "v4.0.6" UNITS = { "current": "A", "power": "W", From 1077ca368f0bbe541abdf7dc2cab01a3445b63f8 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:47:29 +0000 Subject: [PATCH 18/42] Workflow fixes --- .github/workflows/auto_release.yaml | 59 ++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto_release.yaml b/.github/workflows/auto_release.yaml index 87ef9f7..ffec378 100644 --- a/.github/workflows/auto_release.yaml +++ b/.github/workflows/auto_release.yaml @@ -14,32 +14,73 @@ jobs: # Step 1: Checkout the repository - name: Checkout Repository uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history for all branches and tags + + # Step 2: Debug Tag Visibility + - name: List Tags + run: | + echo "Listing all available tags:" + git tag - # Step 2: Extract Version from `pv_opt.py` + # Step 3: Extract Version from `pv_opt.py` - name: Extract Version id: extract_version run: | + echo "Extracting version from apps/pv_opt/pv_opt.py" # Extract the VERSION variable from pv_opt.py VERSION=$(grep -oP '(?<=^VERSION = ")[^"]+' apps/pv_opt/pv_opt.py) if [ -z "$VERSION" ]; then echo "Error: VERSION not found in apps/pv_opt/pv_opt.py" exit 1 fi - echo "VERSION=$VERSION" + echo "Extracted VERSION=$VERSION" echo "version=$VERSION" >> $GITHUB_ENV # Save to environment file - # Step 3: Create GitHub Release + # Step 4: Generate Release Notes + - name: Generate Release Notes + id: generate_notes + run: | + echo "Generating release notes..." + # Get the latest tag before this release + LAST_TAG=$(git describe --tags --abbrev=0 --match "v*" 2>/dev/null) + + # If no tags exist, use the initial commit hash + if [ -z "$LAST_TAG" ]; then + echo "No previous tags found. Using initial commit as starting point." + LAST_TAG=$(git rev-list --max-parents=0 HEAD) + else + echo "Found latest tag: $LAST_TAG" + fi + + # Gather commit messages since the last tag + echo "Collecting commits since $LAST_TAG..." + COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s (%h)") + + # Format the release notes + RELEASE_NOTES="## Changes\n" + if [ -z "$COMMITS" ]; then + echo "No significant changes found." + RELEASE_NOTES+="No significant changes." + else + echo "Found commits:" + echo "${COMMITS}" + RELEASE_NOTES+="${COMMITS}" + fi + + # Output the release notes + echo "Release notes generated:"\n"${RELEASE_NOTES}" + echo "release_notes<> $GITHUB_ENV + echo "${RELEASE_NOTES}" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + # Step 5: Create GitHub Release - name: Create GitHub Release - if: | - github.event_name == 'push' || - (github.event_name == 'pull_request_review' && github.event.review.state == 'approved') uses: actions/create-release@v1 with: tag_name: "v${{ env.version }}" release_name: "Release v${{ env.version }}" - body: | - ## Changes - This release was automatically generated. + body: ${{ env.release_notes }} draft: false prerelease: false env: From 78abff151fab1a2edc122676780393ae6b120fd9 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:47:43 +0000 Subject: [PATCH 19/42] Workflow fixes --- .github/workflows/black.yaml | 28 ++++++++++++---------------- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index b2289ba..02a046b 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -29,7 +29,7 @@ jobs: - name: Format Code with Black and isort run: | black --line-length=119 . - isort . + isort --profile="black" . # Step 5: Commit Formatting Changes - name: Commit Formatting Changes @@ -68,26 +68,23 @@ jobs: - name: Get Latest Tag id: get_latest_tag run: | - LATEST_TAG=$(git tag --list | sort -V | tail -n 1) + LATEST_TAG=$(git describe --tags --abbrev=0) if [ -z "$LATEST_TAG" ]; then echo "Error: No tags found in the repository." >&2 exit 1 fi - echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV - echo "Latest Tag: '$LATEST_TAG'" + echo "latest_tag=$LATEST_TAG" >> $GITHUB_ENV - name: Get VERSION from Latest Tag id: get_tag_version run: | - VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v([0-9]+\\.[0-9]+\\.[0-9]+)$/\\1/') - echo "Extracted VERSION: '$VERSION'" + VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)$/\1/') if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 fi echo "tag_version=$VERSION" >> $GITHUB_ENV - - + - name: Get VERSION from Current Branch id: get_patch_version run: | @@ -114,18 +111,18 @@ jobs: new_patch_version="$tag_major.$((tag_minor + 1)).0" else echo "Error: Unsupported source branch type." >&2 - exit 1 - fi + exit 1 + fi - sed -i "s/^VERSION = \".*\"/VERSION = \"$new_patch_version\"/" apps/pv_opt/pv_opt.py - echo "Corrected version to $new_patch_version." - echo "new_patch_version=$new_patch_version" >> $GITHUB_ENV + sed -i "s/^VERSION = \".*\"/VERSION = \"v$new_patch_version\"/" apps/pv_opt/pv_opt.py + echo "Corrected version to v$new_patch_version." + echo "new_patch_version=v$new_patch_version" >> $GITHUB_ENV echo "VERSION_CHANGED=true" >> $GITHUB_ENV - name: Update README.md version run: | new_patch_version=${{ env.new_patch_version }} - sed -i "1s/v[0-9]*\.[0-9]*\.[0-9]*/v$new_patch_version/" README.md + sed -i "1s/v[0-9]*\.[0-9]*\.[0-9]*/$new_patch_version/" README.md - name: Commit Version Changes if: env.VERSION_CHANGED == 'true' @@ -134,7 +131,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git checkout -B $GITHUB_HEAD_REF # Create or switch to the correct branch + git checkout -B $GITHUB_HEAD_REF git add apps/pv_opt/pv_opt.py README.md if git diff --cached --quiet; then echo "No version changes to commit." @@ -142,4 +139,3 @@ jobs: fi git commit -m "Update version to ${{ env.new_patch_version }}" git push origin $GITHUB_HEAD_REF - \ No newline at end of file diff --git a/README.md b/README.md index 67eee2f..5403ee6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser vv4.0.6 +# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index ad06afb..c5309f1 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "v4.0.6" +VERSION = "4.0.6" UNITS = { "current": "A", "power": "W", From 7a37275998426830fef5b709a15a3833edb4d1d5 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:50:01 +0000 Subject: [PATCH 20/42] Fixed sed --- .github/workflows/black.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 02a046b..ca348b5 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -78,7 +78,7 @@ jobs: - name: Get VERSION from Latest Tag id: get_tag_version run: | - VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)$/\1/') + VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v([0-9]+\\.[0-9]+\\.[0-9]+)$/\\1/') if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 From 82d5fb915bb7d2cc9ba7e6a461b6729a876e82cc Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 21:53:25 +0000 Subject: [PATCH 21/42] Grrrr --- .github/workflows/black.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index ca348b5..2cc508b 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -68,22 +68,26 @@ jobs: - name: Get Latest Tag id: get_latest_tag run: | - LATEST_TAG=$(git describe --tags --abbrev=0) + LATEST_TAG=$(git tag --list | sort -V | tail -n 1) if [ -z "$LATEST_TAG" ]; then echo "Error: No tags found in the repository." >&2 exit 1 fi - echo "latest_tag=$LATEST_TAG" >> $GITHUB_ENV + echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV + echo "Latest Tag: '$LATEST_TAG'" - name: Get VERSION from Latest Tag id: get_tag_version run: | VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v([0-9]+\\.[0-9]+\\.[0-9]+)$/\\1/') + echo "Extracted VERSION: '$VERSION'" if [ -z "$VERSION" ]; then echo "Error: VERSION could not be extracted from the latest tag." >&2 exit 1 fi echo "tag_version=$VERSION" >> $GITHUB_ENV + + - name: Get VERSION from Current Branch id: get_patch_version From b4aec53ae90894f7a3aded8198414de5a03765dc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Jan 2025 21:54:01 +0000 Subject: [PATCH 22/42] Update version to vv4.0.6 --- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5403ee6..67eee2f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 +# PV Opt: Home Assistant Solar/Battery Optimiser vv4.0.6 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index c5309f1..6c7ccff 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "4.0.6" +VERSION = "vv4.0.6" UNITS = { "current": "A", "power": "W", From 94665c2b576cac4973ef57b7e7947ef323be3c6a Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 22:04:27 +0000 Subject: [PATCH 23/42] Remove v's --- .github/workflows/black.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 2cc508b..51608f8 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -118,15 +118,16 @@ jobs: exit 1 fi - sed -i "s/^VERSION = \".*\"/VERSION = \"v$new_patch_version\"/" apps/pv_opt/pv_opt.py - echo "Corrected version to v$new_patch_version." + sed -i "s/^VERSION = \".*\"/VERSION = \"$new_patch_version\"/" apps/pv_opt/pv_opt.py + echo "Corrected version to $new_patch_version." echo "new_patch_version=v$new_patch_version" >> $GITHUB_ENV echo "VERSION_CHANGED=true" >> $GITHUB_ENV - + - name: Update README.md version run: | new_patch_version=${{ env.new_patch_version }} - sed -i "1s/v[0-9]*\.[0-9]*\.[0-9]*/$new_patch_version/" README.md + version_without_v=$(echo "$new_patch_version" | sed 's/^v//') + sed -i "1s/v[0-9]*\.[0-9]*\.[0-9]*/v$version_without_v/" README.md - name: Commit Version Changes if: env.VERSION_CHANGED == 'true' From 43a0da9270981d9c4f2837f0cccaf8408b0e6b63 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 22:05:11 +0000 Subject: [PATCH 24/42] Remove v's --- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67eee2f..5403ee6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser vv4.0.6 +# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 6c7ccff..c5309f1 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "vv4.0.6" +VERSION = "4.0.6" UNITS = { "current": "A", "power": "W", From 0d839dbc4dc5b07ed122aa0a81ba4a62f4f1dded Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Jan 2025 22:06:18 +0000 Subject: [PATCH 25/42] Update version to vv4.0.6 --- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5403ee6..67eee2f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 +# PV Opt: Home Assistant Solar/Battery Optimiser vv4.0.6 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index c5309f1..ad06afb 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "4.0.6" +VERSION = "v4.0.6" UNITS = { "current": "A", "power": "W", From 1370b56b10b6228e42c375394752e67ae76ca475 Mon Sep 17 00:00:00 2001 From: fboundy Date: Tue, 7 Jan 2025 22:12:14 +0000 Subject: [PATCH 26/42] Ditch the v's --- .github/workflows/black.yaml | 24 +++++------------------- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 51608f8..dbc6af6 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -68,26 +68,13 @@ jobs: - name: Get Latest Tag id: get_latest_tag run: | - LATEST_TAG=$(git tag --list | sort -V | tail -n 1) + LATEST_TAG=$(git describe --tags --abbrev=0) if [ -z "$LATEST_TAG" ]; then echo "Error: No tags found in the repository." >&2 exit 1 fi - echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV - echo "Latest Tag: '$LATEST_TAG'" - - - name: Get VERSION from Latest Tag - id: get_tag_version - run: | - VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v([0-9]+\\.[0-9]+\\.[0-9]+)$/\\1/') - echo "Extracted VERSION: '$VERSION'" - if [ -z "$VERSION" ]; then - echo "Error: VERSION could not be extracted from the latest tag." >&2 - exit 1 - fi + VERSION=$(echo "$LATEST_TAG" | sed -E 's/^v?([0-9]+\.[0-9]+\.[0-9]+)$/\1/') echo "tag_version=$VERSION" >> $GITHUB_ENV - - - name: Get VERSION from Current Branch id: get_patch_version @@ -120,14 +107,13 @@ jobs: sed -i "s/^VERSION = \".*\"/VERSION = \"$new_patch_version\"/" apps/pv_opt/pv_opt.py echo "Corrected version to $new_patch_version." - echo "new_patch_version=v$new_patch_version" >> $GITHUB_ENV + echo "new_patch_version=$new_patch_version" >> $GITHUB_ENV echo "VERSION_CHANGED=true" >> $GITHUB_ENV - + - name: Update README.md version run: | new_patch_version=${{ env.new_patch_version }} - version_without_v=$(echo "$new_patch_version" | sed 's/^v//') - sed -i "1s/v[0-9]*\.[0-9]*\.[0-9]*/v$version_without_v/" README.md + sed -i "1s/v[0-9]*\.[0-9]*\.[0-9]*/v$new_patch_version/" README.md - name: Commit Version Changes if: env.VERSION_CHANGED == 'true' diff --git a/README.md b/README.md index 67eee2f..5403ee6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser vv4.0.6 +# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index ad06afb..c5309f1 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "v4.0.6" +VERSION = "4.0.6" UNITS = { "current": "A", "power": "W", From aa08458c17043d4130e1e4709d1be8dfcaea600b Mon Sep 17 00:00:00 2001 From: stevebuk1 <78320816+stevebuk1@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:55:50 +0000 Subject: [PATCH 27/42] Bugfix - correct case in text for Energy Storage Control switch options --- apps/pv_opt/solis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index e754d9a..1b6a3b7 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -29,11 +29,11 @@ "Feed-in priority": 64, }, False: { - "SelfUse - No Grid Charging": 1, + "Selfuse - No Grid Charging": 1, "Self-Use - No Grid Charging": 1, "Timed Charge/Discharge - No Grid Charging": 3, "Backup/Reserve - No Grid Charging": 17, - "SelfUse": 33, + "Selfuse": 33, "Self-Use - No Timed Charge/Discharge": 33, "Self-Use": 35, "Timed Charge/Discharge": 35, From 4eedb834da1139aca45c9da48f3d7efe68d1da90 Mon Sep 17 00:00:00 2001 From: stevebuk1 <78320816+stevebuk1@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:58:26 +0000 Subject: [PATCH 28/42] Update Version to v4.0.7-Beta-1 for solis.py bugfix for #355 --- apps/pv_opt/pv_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index c5309f1..d4c3a12 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "4.0.6" +VERSION = "4.0.7-Beta-1" UNITS = { "current": "A", "power": "W", From 2b1817a279298d260031235aa56b9aa4f5a1310c Mon Sep 17 00:00:00 2001 From: stevebuk1 <78320816+stevebuk1@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:36:42 +0000 Subject: [PATCH 29/42] Bugfix - restore operation of compare_tariffs --- apps/pv_opt/pv_opt.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index d4c3a12..f917c7b 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -3987,10 +3987,17 @@ def _compare_tariffs(self): return consumption = self.load_consumption(start, end) - static = pd.concat([solar, consumption], axis=1).set_axis(["solar", "consumption"], axis=1) + self.pv_system.static_flows = pd.concat([solar, consumption], axis=1).set_axis(["solar", "consumption"], axis=1) initial_soc_df = self.hass2df(self.config["id_battery_soc"], days=2, freq="30min") - initial_soc = initial_soc_df.loc[start] + self.pv_system.initial_soc = initial_soc_df.loc[start] + + # Not sure about the next lines, but calculate_flows requires soc_now + soc_now = self.get_config("id_battery_soc") + self.time_now = pd.Timestamp.utcnow() + self.pv_system.soc_now = (self.time_now, soc_now) + + # self.pv_system.soc_now = initial_soc_df.loc[start] self.pv_system.calculate_flows() base = self.pv_system.flows @@ -4000,12 +4007,12 @@ def _compare_tariffs(self): self.log("") self.log(f"Start: {start.strftime(DATE_TIME_FORMAT_SHORT):>15s}") self.log(f"End: {end.strftime(DATE_TIME_FORMAT_SHORT):>15s}") - self.log(f"Initial SOC: {initial_soc:>15.1f}%") - self.log(f"Consumption: {static['consumption'].sum()/2000:15.1f} kWh") - self.log(f"Solar: {static['solar'].sum()/2000:15.1f} kWh") + self.log(f"Initial SOC: {self.pv_system.initial_soc:>15.1f}%") + self.log(f"Consumption: {self.pv_system.static_flows['consumption'].sum()/2000:15.1f} kWh") + self.log(f"Solar: {self.pv_system.static_flows['solar'].sum()/2000:15.1f} kWh") if self.debug and "T" in self.debug_cat: - self.log(f">>> Yesterday's data:\n{static.to_string()}") + self.log(f">>> Yesterday's data:\n{self.pv_system.static_flows.to_string()}") for tariff_set in self.config["alt_tariffs"]: code = {} @@ -4025,7 +4032,7 @@ def _compare_tariffs(self): ) actual = self._cost_actual(start=start, end=end - pd.Timedelta(30, "minutes")) - static["period_start"] = static.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00" + self.pv_system.static_flows["period_start"] = self.pv_system.static_flows.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00" entity_id = f"sensor.{self.prefix}_opt_cost_actual" self.set_state( state=round(actual.sum() / 100, 2), @@ -4036,7 +4043,7 @@ def _compare_tariffs(self): "unit_of_measurement": "GBP", "friendly_name": f"PV Opt Comparison Actual", } - | {col: static[["period_start", col]].to_dict("records") for col in ["solar", "consumption"]}, + | {col: self.pv_system.static_flows[["period_start", col]].to_dict("records") for col in ["solar", "consumption"]}, ) self.ulog("Net Cost comparison:", underline=None) From 78c4c06940b6e6d80b239f5059271cc9827b46a4 Mon Sep 17 00:00:00 2001 From: stevebuk1 <78320816+stevebuk1@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:41:31 +0000 Subject: [PATCH 30/42] Bugfix - limit inverter current to "battery_current_limit_amps" --- apps/pv_opt/solis.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index 1b6a3b7..79c588e 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -571,6 +571,9 @@ def _control_charge_discharge(self, direction, enable, **kwargs): self.log(f">>> direction: {direction}") self.log(f">>> times: {times}") self.log(f">>> current: {current}") + + current = min(current, self.get_config("battery_current_limit_amps")) + changed = self._set_times(direction, **times) self.log(f">>> changed: {changed}") changed = changed or self._set_current(direction, current) From 0151632d3b23a7a40a6a8b2e4348345f7a3a8076 Mon Sep 17 00:00:00 2001 From: stevebuk1 <78320816+stevebuk1@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:00:50 +0000 Subject: [PATCH 31/42] Roll version to 4.0.7-Beta-2 for solis.py change for battery current bugfix --- apps/pv_opt/pv_opt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index f917c7b..bd5a157 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,12 +13,13 @@ import pvpy as pv from numpy import nan -VERSION = "4.0.7-Beta-1" +VERSION = "4.0.7-Beta-2" UNITS = { "current": "A", "power": "W", } + OCTOPUS_PRODUCT_URL = r"https://api.octopus.energy/v1/products/" DEBUG = False From 75a352e10745012895c7f5361afb5c1e1160037a Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 10 Jan 2025 08:30:52 +0000 Subject: [PATCH 32/42] Go --- apps/pv_opt/config/config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/pv_opt/config/config.yaml b/apps/pv_opt/config/config.yaml index 9bc591e..12a3c01 100644 --- a/apps/pv_opt/config/config.yaml +++ b/apps/pv_opt/config/config.yaml @@ -121,7 +121,7 @@ pv_opt: # ======================================== # Octopus account parameters # ======================================== - # octopus_auto: False # Read tariffs from the Octopus Energy integration. If successful this over-rides the following parameters + octopus_auto: False # Read tariffs from the Octopus Energy integration. If successful this over-rides the following parameters # octopus_account: !secret octopus_account # octopus_api_key: !secret octopus_api_key @@ -159,12 +159,12 @@ pv_opt: # octopus_import_tariff_code: E-1R-AGILE-23-12-06-G # # octopus_export_tariff_code: E-1R-OUTGOING-LITE-FIX-12M-23-09-12-G - # octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G + octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G # octopus_import_tariff_code: E-1R-FLUX-IMPORT-23-02-14-G # octopus_export_tariff_code: E-1R-FLUX-EXPORT-23-02-14-G - # octopus_import_tariff_code: E-1R-GO-VAR-22-10-14-N + octopus_import_tariff_code: E-1R-GO-VAR-22-10-14-N # octopus_export_tariff_code: E-1R-OUTGOING-LITE-FIX-12M-23-09-12-N # ======================================== From 7f4f5a91a1af038445cdc1dbb961261073092260 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 10 Jan 2025 08:34:17 +0000 Subject: [PATCH 33/42] Tidy up logging --- apps/pv_opt/solis.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index 79c588e..dca5a17 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -400,7 +400,6 @@ def brand_config(self): return self._brand_config def write_to_hass(self, entity_id, value, **kwargs): - self.log(f">>> {value}") try: value = float(value) except: @@ -474,7 +473,6 @@ def enable_timed_mode(self): self._enable_slot(direction="discharge") else: code = 35 - self.log(f">>> Setting energy control switch to {code} to enable timed mode") self._set_energy_control_switch(code) def _enable_slot(self, direction="charge"): @@ -483,7 +481,6 @@ def _enable_slot(self, direction="charge"): if cfg in self._brand_config and entity_id is not None: try: self._host.call_service("switch/turn_on", entity_id=entity_id) - self.log(f">>> Switch {entity_id} turned ON") except: self.log(f"Failed to turn on switch {entity_id}", level="WARNING") else: @@ -568,16 +565,9 @@ def _control_charge_discharge(self, direction, enable, **kwargs): else: target_soc = self.get_config("maximum_dod_percent") - self.log(f">>> direction: {direction}") - self.log(f">>> times: {times}") - self.log(f">>> current: {current}") - current = min(current, self.get_config("battery_current_limit_amps")) - changed = self._set_times(direction, **times) - self.log(f">>> changed: {changed}") changed = changed or self._set_current(direction, current) - self.log(f">>> changed: {changed}") if changed and self._requires_button_press: self.log("Something changed - need to press the appropriate Button") @@ -586,7 +576,6 @@ def _control_charge_discharge(self, direction, enable, **kwargs): else: entity_id = self.brand_config.get(f"id_timed_charge_discharge_button", None) - self.log(f">>> {entity_id}") if entity_id is not None: self._press_button(entity_id=entity_id) @@ -602,7 +591,6 @@ def hold_soc(self, enable, target_soc=0, **kwargs): start = kwargs.get("start", pd.Timestamp.now(tz=self._tz).floor("1min")) end = kwargs.get("end", pd.Timestamp.now(tz=self._tz).ceil("30min")) self._hold_soc = {"active": enable, "soc": target_soc} - self.log(f">>>>SOC: {target_soc}") if self._hmi_fb00: self._control_charge_discharge( @@ -856,5 +844,4 @@ def _write_modbus_register(self, register, value, cfg=None, tolerance=0, multipl if changed: data = {"register": register, "value": value} self._host.call_service("solarman/write_holding_register", **data) - self.log(">>> Writing {value} to inverter register {address} using Solarman") written = True From e040a4f5b8f8c5273893c77ac38c61d0bd42d821 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 10 Jan 2025 11:37:58 +0000 Subject: [PATCH 34/42] Fix for #356 --- apps/pv_opt/pv_opt.py | 134 +++++++++++++++++++++++------------------- apps/pv_opt/pvpy.py | 121 ++++++++++---------------------------- 2 files changed, 107 insertions(+), 148 deletions(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index bd5a157..3d89ead 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -2355,67 +2355,69 @@ def optimise(self): return self.pv_system.static_flows = pd.concat([solcast, consumption], axis=1) - self.time_now = pd.Timestamp.utcnow() + self.time_now = pd.Timestamp.utcnow().floor("1min") self.pv_system.static_flows = self.pv_system.static_flows[self.time_now.floor("30min") :].fillna(0) + self.pv_system.static_flows.index = [self.time_now] + list(self.pv_system.static_flows.index[1:]) soc_now = self.get_config("id_battery_soc") - soc_last_day = self.hass2df(self.config["id_battery_soc"], days=1, log=self.debug) - if self.debug and "S" in self.debug_cat: - self.log(f">>> soc_now: {soc_now}") - self.log(f">>> soc_last_day: {soc_last_day}") - self.log( - f">>> Original: {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}" - ) - - try: - soc_now = float(soc_now) - - except: - self.log("") - self.log( - "Unable to get current SOC from HASS. Using last value from History.", - level="WARNING", - ) - soc_now = soc_last_day.iloc[-1] - - # x = x.astype(float) - - try: - soc_last_day = pd.to_numeric(soc_last_day, errors="coerce").interpolate() - - soc_last_day = soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :] - if self.debug and "S" in self.debug_cat: - self.log( - f">>> Fixed : {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}" - ) - - soc_last_day = pd.concat( - [ - soc_last_day, - pd.Series( - data=[soc_now, nan], - index=[self.time_now, self.pv_system.static_flows.index[0]], - ), - ] - ).sort_index() - self.pv_system.initial_soc = soc_last_day.interpolate().loc[self.pv_system.static_flows.index[0]] - except: - self.pv_system.initial_soc = None - - if not isinstance(self.pv_system.initial_soc, float): - self.log("") - self.log( - "Unable to retrieve initial SOC - assuming it is the same as current SOC", - level="WARNING", - ) - self.pv_system.initial_soc = soc_now - - self.pv_system.soc_now = (self.time_now, soc_now) + self.pv_system.initial_soc = soc_now + # soc_last_day = self.hass2df(self.config["id_battery_soc"], days=1, log=self.debug) + # if self.debug and "S" in self.debug_cat: + # self.log(f">>> soc_now: {soc_now}") + # self.log(f">>> soc_last_day: {soc_last_day}") + # self.log( + # f">>> Original: {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}" + # ) + + # try: + # soc_now = float(soc_now) + + # except: + # self.log("") + # self.log( + # "Unable to get current SOC from HASS. Using last value from History.", + # level="WARNING", + # ) + # soc_now = soc_last_day.iloc[-1] + + # # x = x.astype(float) + + # try: + # soc_last_day = pd.to_numeric(soc_last_day, errors="coerce").interpolate() + + # soc_last_day = soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :] + # if self.debug and "S" in self.debug_cat: + # self.log( + # f">>> Fixed : {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}" + # ) + + # soc_last_day = pd.concat( + # [ + # soc_last_day, + # pd.Series( + # data=[soc_now, nan], + # index=[self.time_now, self.pv_system.static_flows.index[0]], + # ), + # ] + # ).sort_index() + # self.pv_system.initial_soc = soc_last_day.interpolate().loc[self.pv_system.static_flows.index[0]] + # except: + # self.pv_system.initial_soc = None + + # if not isinstance(self.pv_system.initial_soc, float): + # self.log("") + # self.log( + # "Unable to retrieve initial SOC - assuming it is the same as current SOC", + # level="WARNING", + # ) + # self.pv_system.initial_soc = soc_now + + # self.pv_system.soc_now = (self.time_now, soc_now) - self.log("") - self.log(f"Initial SOC: {self.pv_system.initial_soc}") - self.log(f"Current SOC: {self.pv_system.soc_now}") + # self.log("") + # self.log(f"Initial SOC: {self.pv_system.initial_soc}") + # self.log(f"Current SOC: {self.pv_system.soc_now}") self.pv_system.calculate_flows() self.flows = {"Base": self.pv_system.flows} @@ -2508,6 +2510,13 @@ def optimise(self): self.opt = self.flows[self.selected_case] + self.base = self.flows["Base"] + + self.log("") + self.ulog("1/2 Hour Base") + self.log(f"\n{self.base.to_string()}") + self.log("") + # SVB debug logging # self.log("") # self.log("Returned from .flows. self.opt is........") @@ -3988,7 +3997,9 @@ def _compare_tariffs(self): return consumption = self.load_consumption(start, end) - self.pv_system.static_flows = pd.concat([solar, consumption], axis=1).set_axis(["solar", "consumption"], axis=1) + self.pv_system.static_flows = pd.concat([solar, consumption], axis=1).set_axis( + ["solar", "consumption"], axis=1 + ) initial_soc_df = self.hass2df(self.config["id_battery_soc"], days=2, freq="30min") self.pv_system.initial_soc = initial_soc_df.loc[start] @@ -4033,7 +4044,9 @@ def _compare_tariffs(self): ) actual = self._cost_actual(start=start, end=end - pd.Timedelta(30, "minutes")) - self.pv_system.static_flows["period_start"] = self.pv_system.static_flows.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00" + self.pv_system.static_flows["period_start"] = ( + self.pv_system.static_flows.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00" + ) entity_id = f"sensor.{self.prefix}_opt_cost_actual" self.set_state( state=round(actual.sum() / 100, 2), @@ -4044,7 +4057,10 @@ def _compare_tariffs(self): "unit_of_measurement": "GBP", "friendly_name": f"PV Opt Comparison Actual", } - | {col: self.pv_system.static_flows[["period_start", col]].to_dict("records") for col in ["solar", "consumption"]}, + | { + col: self.pv_system.static_flows[["period_start", col]].to_dict("records") + for col in ["solar", "consumption"] + }, ) self.ulog("Net Cost comparison:", underline=None) diff --git a/apps/pv_opt/pvpy.py b/apps/pv_opt/pvpy.py index 87109a3..f52bb28 100644 --- a/apps/pv_opt/pvpy.py +++ b/apps/pv_opt/pvpy.py @@ -60,6 +60,12 @@ # outputs self.unit. +def get_dt_hours(df: pd.DataFrame | pd.Series) -> pd.Series: + df = pd.DataFrame(df) + df["dt_hours"] = -df.index.diff(-1) / pd.Timedelta("60min") + return df["dt_hours"].ffill() + + class Tariff: def __init__( self, @@ -288,64 +294,9 @@ def to_df(self, start=None, end=None, **kwargs): df = pd.DataFrame(self.unit).set_index("valid_from")["value_inc_vat"] df.index = pd.to_datetime(df.index) df = df.sort_index() - - # df at this point is a series of start and end times and not a series of 1/2 hour slots - - # SVB logging - # self.log("Df loaded from self.unit") - # self.log(df) - # self.log("") - # self.log("Printing Df") - # self.log(df.to_string()) - if "AGILE" in self.name and use_day_ahead: - # if self.day_ahead is not None and df.index[-1].day == end.day: - # # reset the day ahead forecasts if we've got a forecast going into tomorrow - # self.agile_predict = None - # self.day_ahead = None - # self.log("") - # self.log(f"Cleared day ahead forecast for tariff {self.name}") - - # if pd.Timestamp.now(tz=self.tz).hour > 11 and df.index[-1].day != end.day: - # # if it is after 11 but we don't have new Agile prices yet, check for a day-ahead forecast - # if self.day_ahead is None: - # self.day_ahead = self.get_day_ahead(df.index[0]) - # if self.day_ahead is not None: - # self.day_ahead = self.day_ahead.sort_index() - # self.log("") - # self.log( - # f"Retrieved day ahead forecast for period {self.day_ahead.index[0].strftime(TIME_FORMAT)} - {self.day_ahead.index[-1].strftime(TIME_FORMAT)} for tariff {self.name}" - # ) - - # if self.day_ahead is not None: - # if self.export: - # factors = AGILE_FACTORS["export"][self.area] - # else: - # factors = AGILE_FACTORS["import"][self.area] - - # mask = (self.day_ahead.index.tz_convert("GB").hour >= 16) & ( - # self.day_ahead.index.tz_convert("GB").hour < 19 - # ) - - # agile = ( - # pd.concat( - # [ - # (self.day_ahead[mask] * factors[0] + factors[1] + factors[2]), - # (self.day_ahead[~mask] * factors[0] + factors[1]), - # ] - # ) - # .sort_index() - # .loc[df.index[-1] :] - # .iloc[1:] - # ) - - # df = pd.concat([df, agile]) - # else: - # Otherwise download the latest forecast from AgilePredict if self.agile_predict is None: self.agile_predict = self._get_agile_predict() - # self.log("Agile_predict is") - # self.log(f"\n{self.agile_predict}") if self.agile_predict is not None: df = pd.concat( @@ -388,9 +339,6 @@ def to_df(self, start=None, end=None, **kwargs): # Add IO slot prices as a column to dataframe. df = pd.concat([df, self.host.io_prices], axis=1).set_axis(["unit", "io_unit"], axis=1) - # self.log("To_df, Printing concat") - # self.log(df.to_string()) - df = df.dropna(subset=["unit"]) # Drop Nans mask = df["io_unit"] < df["unit"] # Mask is true if an IOslot df.loc[mask, "unit"] = df[ @@ -698,15 +646,17 @@ def net_cost(self, grid_flow, sum=True, decimals=1, **kwargs): grid_imp = grid_flow.clip(0) grid_exp = grid_flow.clip(upper=0) + dt = get_dt_hours(grid_flow) + imp_df = self.tariffs["import"].to_df(start, end, **kwargs) nc = imp_df["fixed"] if kwargs.get("log") and (self.host.debug and "F" in self.host.debug_cat): self.rlog(f">>> Import{self.tariffs['import'].to_df(start,end).to_string()}") - nc += imp_df["unit"] * grid_imp / 2000 + nc += imp_df["unit"] * grid_imp / 1000 * dt if kwargs.get("log") and (self.host.debug and "F" in self.host.debug_cat): self.rlog(f">>> Export{self.tariffs['export'].to_df(start,end).to_string()}") if self.tariffs["export"] is not None: - nc += self.tariffs["export"].to_df(start, end, **kwargs)["unit"] * grid_exp / 2000 + nc += self.tariffs["export"].to_df(start, end, **kwargs)["unit"] * grid_exp / 1000 * dt if self.host.debug and "V" in self.host.debug_cat: self.log("") @@ -721,12 +671,14 @@ def net_cost(self, grid_flow, sum=True, decimals=1, **kwargs): def prices(self, start=None, end=None): prices = pd.concat( [ - self.tariffs[direction].to_df(start=start, end=end)["unit"] + self.tariffs[direction].to_df(start=start.floor("30min"), end=end)["unit"] for direction in self.tariffs if self.tariffs[direction] is not None ], axis=1, ) + + prices.index = [start] + list(prices.index[1:]) return prices @@ -753,13 +705,14 @@ def __str__(self): pass def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumption", **kwargs): - solar = self.static_flows[solar_id] - consumption = self.static_flows[consumption_id] + # solar = self.static_flows[solar_id] + # consumption = self.static_flows[consumption_id] - self.flows = self.static_flows.copy() - - battery_flows = solar - consumption - forced_charge = pd.Series(index=self.flows.index, data=0) + self.flows = self.static_flows.copy()[[solar_id, consumption_id]].set_axis(["solar", "consumption"], axis=1) + self.flows["dt_hours"] = get_dt_hours(self.flows) + self.flows["battery_no_force"] = self.flows["consumption"] - self.flows["solar"] + self.flows["forced"] = 0 + # forced_charge = pd.Series(index=self.flows.index, data=0) if len(slots) > 0: timed_slot_flows = pd.Series(index=self.flows.index, data=0) @@ -769,19 +722,16 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio timed_slot_flows.loc[t] += int(c) chg_mask = timed_slot_flows != 0 - battery_flows[chg_mask] = timed_slot_flows[chg_mask] - forced_charge[chg_mask] = timed_slot_flows[chg_mask] + self.flows["battery_no_force"][chg_mask] = timed_slot_flows[chg_mask] + self.flows["forced"][chg_mask] = timed_slot_flows[chg_mask] - if self.soc_now is None: - chg = [self.initial_soc / 100 * self.battery.capacity] - freq = pd.infer_freq(self.static_flows.index) / pd.Timedelta(60, "minutes") + chg = [self.initial_soc / 100 * self.battery.capacity] - else: - chg = [self.soc_now[1] / 100 * self.battery.capacity] - freq = (self.soc_now[0] - self.flows.index[0]) / pd.Timedelta(60, "minutes") + for idx in self.flows.index: + flow = self.flows["battery_no_force"].loc[idx] + dt_hours = self.flows["dt_hours"].loc[idx] - for i, flow in enumerate(battery_flows): - if flow < 0: + if flow > 0: flow = flow / self.inverter.inverter_efficiency else: flow = flow * self.inverter.charger_efficiency @@ -792,7 +742,7 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio [ min( [ - chg[-1] + flow * freq, + chg[-1] - flow * dt_hours, self.battery.capacity, ] ), @@ -802,31 +752,24 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio 1, ) ) - if (self.soc_now is not None) and (i == 0): - freq = pd.infer_freq(self.static_flows.index) / pd.Timedelta(60, "minutes") - - if self.soc_now is not None: - chg[0] = self.initial_soc / 100 * self.battery.capacity self.flows["chg"] = chg[:-1] self.flows["chg"] = self.flows["chg"].ffill() self.flows["chg_end"] = chg[1:] self.flows["chg_end"] = self.flows["chg_end"].bfill() - self.flows["battery"] = (pd.Series(chg).diff(-1) / freq)[:-1].to_list() + self.flows["battery"] = pd.Series(chg).diff(-1)[:-1].to_list() + self.flows["battery"] /= self.flows["dt_hours"] self.flows.loc[self.flows["battery"] > 0, "battery"] = ( self.flows["battery"] * self.inverter.inverter_efficiency ) self.flows.loc[self.flows["battery"] < 0, "battery"] = self.flows["battery"] / self.inverter.charger_efficiency - self.flows["grid"] = -(solar - consumption + self.flows["battery"]).round(0) - self.flows["forced"] = forced_charge + self.flows["grid"] = (self.flows["battery_no_force"] - self.flows["battery"]).round(0) self.flows["soc"] = (self.flows["chg"] / self.battery.capacity) * 100 self.flows["soc_end"] = (self.flows["chg_end"] / self.battery.capacity) * 100 - # self.log(f">>>\n{self.flows.iloc[:3].to_string()}") - if self.prices is not None: self.flows = pd.concat( - [self.prices, consumption, self.flows], + [self.flows, self.prices], axis=1, ) From a9b72da6d0933833347bd1460fc29edce75bd76c Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 10 Jan 2025 11:40:56 +0000 Subject: [PATCH 35/42] Add logging --- apps/pv_opt/pv_opt.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index bd5a157..ec7dc80 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -2508,6 +2508,13 @@ def optimise(self): self.opt = self.flows[self.selected_case] + self.base = self.flows["Base"] + + self.log("") + self.ulog("1/2 Hour Base") + self.log(f"\n{self.base.to_string()}") + self.log("") + # SVB debug logging # self.log("") # self.log("Returned from .flows. self.opt is........") @@ -3988,7 +3995,9 @@ def _compare_tariffs(self): return consumption = self.load_consumption(start, end) - self.pv_system.static_flows = pd.concat([solar, consumption], axis=1).set_axis(["solar", "consumption"], axis=1) + self.pv_system.static_flows = pd.concat([solar, consumption], axis=1).set_axis( + ["solar", "consumption"], axis=1 + ) initial_soc_df = self.hass2df(self.config["id_battery_soc"], days=2, freq="30min") self.pv_system.initial_soc = initial_soc_df.loc[start] @@ -4033,7 +4042,9 @@ def _compare_tariffs(self): ) actual = self._cost_actual(start=start, end=end - pd.Timedelta(30, "minutes")) - self.pv_system.static_flows["period_start"] = self.pv_system.static_flows.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00" + self.pv_system.static_flows["period_start"] = ( + self.pv_system.static_flows.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00" + ) entity_id = f"sensor.{self.prefix}_opt_cost_actual" self.set_state( state=round(actual.sum() / 100, 2), @@ -4044,7 +4055,10 @@ def _compare_tariffs(self): "unit_of_measurement": "GBP", "friendly_name": f"PV Opt Comparison Actual", } - | {col: self.pv_system.static_flows[["period_start", col]].to_dict("records") for col in ["solar", "consumption"]}, + | { + col: self.pv_system.static_flows[["period_start", col]].to_dict("records") + for col in ["solar", "consumption"] + }, ) self.ulog("Net Cost comparison:", underline=None) From d1d609e9c9a26cd6ffd26294fa68cc7f426117b3 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 10 Jan 2025 14:11:29 +0000 Subject: [PATCH 36/42] Fixes for #356 --- apps/pv_opt/pv_opt.py | 5 ----- apps/pv_opt/pvpy.py | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 3d89ead..2cb4a83 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -2512,11 +2512,6 @@ def optimise(self): self.base = self.flows["Base"] - self.log("") - self.ulog("1/2 Hour Base") - self.log(f"\n{self.base.to_string()}") - self.log("") - # SVB debug logging # self.log("") # self.log("Returned from .flows. self.opt is........") diff --git a/apps/pv_opt/pvpy.py b/apps/pv_opt/pvpy.py index f52bb28..e562019 100644 --- a/apps/pv_opt/pvpy.py +++ b/apps/pv_opt/pvpy.py @@ -710,8 +710,9 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio self.flows = self.static_flows.copy()[[solar_id, consumption_id]].set_axis(["solar", "consumption"], axis=1) self.flows["dt_hours"] = get_dt_hours(self.flows) - self.flows["battery_no_force"] = self.flows["consumption"] - self.flows["solar"] + self.flows["battery_grid_requirement"] = self.flows["consumption"] - self.flows["solar"] self.flows["forced"] = 0 + self.flows["battery_temp"] = self.flows["consumption"] - self.flows["solar"] # forced_charge = pd.Series(index=self.flows.index, data=0) if len(slots) > 0: @@ -722,13 +723,13 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio timed_slot_flows.loc[t] += int(c) chg_mask = timed_slot_flows != 0 - self.flows["battery_no_force"][chg_mask] = timed_slot_flows[chg_mask] + self.flows["battery_temp"][chg_mask] = -timed_slot_flows[chg_mask] self.flows["forced"][chg_mask] = timed_slot_flows[chg_mask] chg = [self.initial_soc / 100 * self.battery.capacity] for idx in self.flows.index: - flow = self.flows["battery_no_force"].loc[idx] + flow = self.flows["battery_temp"].loc[idx] dt_hours = self.flows["dt_hours"].loc[idx] if flow > 0: @@ -763,7 +764,7 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio self.flows["battery"] * self.inverter.inverter_efficiency ) self.flows.loc[self.flows["battery"] < 0, "battery"] = self.flows["battery"] / self.inverter.charger_efficiency - self.flows["grid"] = (self.flows["battery_no_force"] - self.flows["battery"]).round(0) + self.flows["grid"] = (self.flows["battery_grid_requirement"] - self.flows["battery"]).round(0) self.flows["soc"] = (self.flows["chg"] / self.battery.capacity) * 100 self.flows["soc_end"] = (self.flows["chg_end"] / self.battery.capacity) * 100 @@ -929,13 +930,17 @@ def _high_cost_swaps(self, log=True): if (i > 96) or (available.sum() == 0): done = True - import_cost = ((self.flows["import"] * self.flows["grid"]).clip(0) / 2000)[~tested] + import_cost = ((self.flows["import"] * self.flows["grid"]).clip(0) * self.flows["dt_hours"] / 1000)[ + ~tested + ] if len(import_cost[self.flows["forced"] == 0]) > 0: max_import_cost = import_cost[self.flows["forced"] == 0].max() if len(import_cost[import_cost == max_import_cost]) > 0: max_slot = import_cost[import_cost == max_import_cost].index[0] - max_slot_energy = round(self.flows["grid"].loc[max_slot] / 2000, 2) # kWh + max_slot_energy = round( + self.flows["grid"].loc[max_slot] / 1000 * self.flows["dt_hours"].loc[max_slot], 2 + ) # kWh str_log = f"{i:3d} {available.sum():3d} {max_slot.tz_convert(self.tz).strftime(TIME_FORMAT)}:" if max_slot_energy > 0: @@ -1006,6 +1011,8 @@ def _high_cost_swaps(self, log=True): slots_added += 1 self.calculate_flows(slots=slots) + if i == 1: + self.log(f">>>\n{self.flows.to_string()}") self.net_costs.append(self.net_cost) slot_count.append(len(factors)) From a93dc57ecfe240f74e702b2171485ed087f0d1fb Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 10 Jan 2025 14:40:21 +0000 Subject: [PATCH 37/42] Remove extra logging --- apps/pv_opt/pvpy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/pv_opt/pvpy.py b/apps/pv_opt/pvpy.py index e562019..fdf066d 100644 --- a/apps/pv_opt/pvpy.py +++ b/apps/pv_opt/pvpy.py @@ -1011,8 +1011,6 @@ def _high_cost_swaps(self, log=True): slots_added += 1 self.calculate_flows(slots=slots) - if i == 1: - self.log(f">>>\n{self.flows.to_string()}") self.net_costs.append(self.net_cost) slot_count.append(len(factors)) From 44445fa21adfb084ab96dd391a3e680c9535328f Mon Sep 17 00:00:00 2001 From: stevebuk1 <78320816+stevebuk1@users.noreply.github.com> Date: Fri, 10 Jan 2025 20:28:05 +0000 Subject: [PATCH 38/42] Add message logging for when battery_current_limit_amps curtails inverter current --- apps/pv_opt/solis.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index dca5a17..bf74425 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -565,7 +565,15 @@ def _control_charge_discharge(self, direction, enable, **kwargs): else: target_soc = self.get_config("maximum_dod_percent") - current = min(current, self.get_config("battery_current_limit_amps")) + battery_current_limit = self.get_config("battery_current_limit_amps") + if battery_current_limit < current: + self.log(f"battery_current_limit_amps of {battery_current_limit} is less than current of {current}A required by charging plan.") + self.log(f"Reducing inverter charge current to {battery_current_limit}A. ") + self.log("Check value of charger_power_watts in config.yaml if this is unexpected.") + self.log("") + + current = min(current, battery_current_limit) + changed = self._set_times(direction, **times) changed = changed or self._set_current(direction, current) From 1decf9dc53e6ff768c875d0646ec27754e430011 Mon Sep 17 00:00:00 2001 From: stevebuk1 <78320816+stevebuk1@users.noreply.github.com> Date: Fri, 10 Jan 2025 21:10:21 +0000 Subject: [PATCH 39/42] pypy.py - code change to rectify user warnings in appdaemon.log --- apps/pv_opt/pvpy.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/pv_opt/pvpy.py b/apps/pv_opt/pvpy.py index fdf066d..dfe06d9 100644 --- a/apps/pv_opt/pvpy.py +++ b/apps/pv_opt/pvpy.py @@ -723,8 +723,11 @@ def calculate_flows(self, slots=[], solar_id="solar", consumption_id="consumptio timed_slot_flows.loc[t] += int(c) chg_mask = timed_slot_flows != 0 - self.flows["battery_temp"][chg_mask] = -timed_slot_flows[chg_mask] - self.flows["forced"][chg_mask] = timed_slot_flows[chg_mask] + # self.flows["battery_temp"][chg_mask] = -timed_slot_flows[chg_mask] + # self.flows["forced"][chg_mask] = timed_slot_flows[chg_mask] + + self.flows.loc[chg_mask, "battery_temp"] = -timed_slot_flows[chg_mask] + self.flows.loc[chg_mask, "forced"] = timed_slot_flows[chg_mask] chg = [self.initial_soc / 100 * self.battery.capacity] From 5b86e196d8188b5b3d94fc79e42aabfc7d8f1254 Mon Sep 17 00:00:00 2001 From: fboundy Date: Sat, 11 Jan 2025 10:40:48 +0000 Subject: [PATCH 40/42] Add additional logging to wrirting time --- apps/pv_opt/solis.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index bf74425..8c3575c 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -409,7 +409,7 @@ def write_to_hass(self, entity_id, value, **kwargs): return self._host.write_and_poll_value(entity_id=entity_id, value=value, **kwargs) else: try: - return self._host.write_and_poll_time(entity_id=entity_id, time=value, **kwargs) + return self._host.write_and_poll_time(entity_id=entity_id, time=value, verbose=True, **kwargs) except: self.log( f"Unable to write value {value} to entity {entity_id}", @@ -567,13 +567,15 @@ def _control_charge_discharge(self, direction, enable, **kwargs): battery_current_limit = self.get_config("battery_current_limit_amps") if battery_current_limit < current: - self.log(f"battery_current_limit_amps of {battery_current_limit} is less than current of {current}A required by charging plan.") + self.log( + f"battery_current_limit_amps of {battery_current_limit} is less than current of {current}A required by charging plan." + ) self.log(f"Reducing inverter charge current to {battery_current_limit}A. ") self.log("Check value of charger_power_watts in config.yaml if this is unexpected.") self.log("") current = min(current, battery_current_limit) - + changed = self._set_times(direction, **times) changed = changed or self._set_current(direction, current) From 8e58e028b3cf55d1a43c4669be5b356463355220 Mon Sep 17 00:00:00 2001 From: fboundy Date: Sat, 11 Jan 2025 10:46:07 +0000 Subject: [PATCH 41/42] 4.0.7 --- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5403ee6..51f2cdc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6 +# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.7 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 2cb4a83..015a98b 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -13,7 +13,7 @@ import pvpy as pv from numpy import nan -VERSION = "4.0.7-Beta-2" +VERSION = "4.0.7" UNITS = { "current": "A", "power": "W", From 3567e96e539b302578cddea3be02cf213002cf53 Mon Sep 17 00:00:00 2001 From: fboundy Date: Sat, 11 Jan 2025 11:03:15 +0000 Subject: [PATCH 42/42] Add id_solar_power for solax --- apps/pv_opt/config/config.yaml | 30 +++++++++++++++--------------- apps/pv_opt/solis.py | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/pv_opt/config/config.yaml b/apps/pv_opt/config/config.yaml index 12a3c01..b78af3c 100644 --- a/apps/pv_opt/config/config.yaml +++ b/apps/pv_opt/config/config.yaml @@ -444,19 +444,19 @@ pv_opt: # Tariff comparison # id_daily_solar: sensor.{device_name}_power_generation_today - # id_solar_power: - # # - sensor.{device_name}_pv_power_1 - # # - sensor.{device_name}_pv_power_2 - # alt_tariffs: - # - name: Agile_Fix - # octopus_import_tariff_code: E-1R-AGILE-23-12-06-G - # octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G - - # # - name: Eco7_Fix - # # octopus_import_tariff_code: E-2R-VAR-22-11-01-G - # # octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G - - # - name: Flux - # octopus_import_tariff_code: E-1R-FLUX-IMPORT-23-02-14-G - # octopus_export_tariff_code: E-1R-FLUX-EXPORT-23-02-14-G + id_solar_power: + - sensor.{device_name}_pv_power_1 + - sensor.{device_name}_pv_power_2 + alt_tariffs: + - name: Agile_Fix + octopus_import_tariff_code: E-1R-AGILE-23-12-06-G + octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G + + # - name: Eco7_Fix + # octopus_import_tariff_code: E-2R-VAR-22-11-01-G + # octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G + + - name: Flux + octopus_import_tariff_code: E-1R-FLUX-IMPORT-23-02-14-G + octopus_export_tariff_code: E-1R-FLUX-EXPORT-23-02-14-G diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index 8c3575c..35adfa4 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -151,6 +151,7 @@ "id_timed_charge_discharge_button": "button.{device_name}_update_charge_discharge_times", "id_inverter_mode": "select.{device_name}_energy_storage_control_switch", "id_backup_mode_soc": "number.{device_name}_backup_mode_soc", + "id_solar_power": ["sensor.{device_name}_pv_power_1", "sensor.{device_name}_pv_power_2"], }, }, "SOLIS_CORE_MODBUS": {