Repository navigation
fix(givtcp): accept GivEnergy's quantised charge/discharge rate read-back (#5324) - #5348
Merged
Merged
Conversation
…back (#5324) GivEnergy stores the battery charge and discharge rate as a whole percent of nominal battery capacity, rounded down: on a 13.41kWh battery, 1300W reads back 1206W. Since #4864 moved GivTCP REST into its own component, rate writes go through write_and_poll_value, whose tolerance is 5% of the rate ceiling. With an inverter_limit_charge override of 1300W that is 65W, so the 94W miss was rejected and the write was repeated ten times every cycle. - INVERTER_DEF gains rate_step_percent_of_capacity (GE: 1). A custom inverter type built from the GE row does not inherit it. - Inverter.rate_write_tolerance() widens the tolerance for a read-back BELOW the rate written to one step. A read-back above it keeps the plain 5%, because the step rounds down, so a 134W rate is still written down to a 0W hold. rate_needs_write() applies the same rule to the "does this need writing" check. - write_and_poll_value takes an optional fuzzy_below. It defaults to the existing symmetric behaviour, so other callers are unchanged. - The GivTCP discovery record carries step_percent_of_capacity on its rate descriptors, and inverter_definition() maps it across, so the discovery contract still rebuilds the GE row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…review) Review fixes for the #5324 rate tolerance: - Inverter.rate_tolerances() returns (fuzzy, fuzzy_below) for a rate ceiling. A module-level within_fuzzy() now holds the "short of target / over it" rule, which both the "does this need writing" check and write_and_poll_value's read-back match use, so the 5% rule is no longer written out four times and the two checks cannot drift apart. The docstring records the trade-offs deliberately accepted: a relaxed check rather than predicting GivEnergy's exact read-back, a ~1W boundary where a request exactly one step up is taken as already set, and the step being only as right as nominal_capacity. - The custom-inverter-type test restores the shared fixture's args and HA state whole, since building an Inverter writes both (#5079). - inverter-setup.md says the step's slack applies only below the rate written. The vocabulary spec matches inverter_definition(): an absent step is not a gap, and base's value stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ters behind the hub (#5324) The hub reports the charge and discharge rate the inverter holds, so a GivEnergy inverter behind it reads back short of the rate written just as it does through GivTCP. The hub is inverter type GWMQTT whatever brand it drives, so the step cannot go on the INVERTER_DEF row. - gateway.py marks step_percent_of_capacity on the charge_rate and discharge_rate entities of GivEnergy inverters only. - Inverter.refresh_config() takes the step from the charge_rate entity's attribute when it has one, else the INVERTER_DEF row's value as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Address failed-read handling in inverter.py and restore shared test state in test_inverter.py.
Review effort: Lite
Findings: None
What changed in this PR
Fixes repeated GivTCP charge/discharge rate writes by tolerating GivEnergy’s quantised read-backs.
Changes:
- Adds configurable, directional rate-step tolerance handling.
- Propagates rate-step metadata through discovery and inverter configuration.
- Adds documentation and regression tests.
| File | Summary |
|---|---|
docs/superpowers/specs/2026-09-24-discovery-inverter-record-vocabulary-design.md |
Updates discovery vocabulary. |
docs/inverter-setup.md |
Documents custom rate-step configuration. |
apps/predbat/tests/test_inverter.py |
Adds quantised-rate tests; test state should be restored between cases. |
apps/predbat/tests/test_gateway.py |
Tests Gateway metadata. |
apps/predbat/tests/test_coordinator.py |
Tests descriptor validation and mapping. |
apps/predbat/tests/discovery_contract.py |
Extends discovery contract defaults. |
apps/predbat/inverter.py |
Implements fuzzy rate matching; failed reads must not confirm a value. |
apps/predbat/givtcp.py |
Publishes rate-step discovery metadata. |
apps/predbat/gateway.py |
Publishes GivEnergy rate metadata. |
apps/predbat/coordinator.py |
Validates and maps discovery metadata. |
apps/predbat/config.py |
Adds the GE default rate step. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
gcoan
pushed a commit
to gcoan/batpred
that referenced
this pull request
Oct 3, 2026
…tes); mark GH#5324 fixed by PR springfall2008#5348 and GH#5345 fixed by PR springfall2008#5347; add the startup API-error-fatal and SIG model-only-reserve rows Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Posted by Claude on Rik's behalf.
Closes #5324
Problem
GivEnergy stores the battery charge and discharge rate as a whole percent of nominal battery capacity, rounded down. On a 13.41kWh battery, 1300W reads back as 1206W.
Before #4864, GivTCP REST mode wrote the rate directly and checked it against 1/12 of the rate, so a 1206W read-back passed. Since #4864 moved GivTCP REST into its own component, the write goes through
write_and_poll_value, which allows a difference of only 5% of the rate ceiling. With aninverter_limit_chargeoverride of 1300W, that is 65W. The 94W miss was rejected and the rate was written again ten times every 5-minute cycle (the GivTCP log in #5324 shows the burst).Fix
INVERTER_DEFgainsrate_step_percent_of_capacity, set to 1 forGE. A custom inverter type built from the GE row does not inherit it.within_fuzzy()holds that rule in one place. Both the "does this need writing" check inadjust_charge_rate/adjust_discharge_rateandwrite_and_poll_value's read-back match use it.write_and_poll_valuegains an optionalfuzzy_below. It defaults to the existing behaviour, so its other callers are unchanged.step_percent_of_capacityon its rate descriptors, andinverter_definition()maps it across. The discovery contract still rebuilds the GE row, and the vocabulary spec is updated to match.docs/inverter-setup.mddocumentsrate_step_percent_of_capacity, so a custom inverter type defined throughinverter:can set it.Deliberately not done
nominal_capacity. For GivTCP that is GivTCP's own reported capacity. If that sensor is unavailable and Predbat falls back to 8kWh, the step comes out too small.GEC) is unchanged. It writes the same GivEnergy power setting, so it may round the same way, but nothing in Predbat GivTCP REST errors when setting charge rate #5324 shows that. It can follow once a GE Cloud user confirms what the read-back looks like.Tests
test_inverter: a stand-in inverter that rounds the way GivEnergy does. Without the fix it reproduces the 10 writes per cycle. With it, a 1300W write is accepted at 1206W first time, nothing is re-written once settled, a request one step higher still writes, and a 0W hold and a step down are written (charge and discharge).rate_tolerances()andwithin_fuzzy(), and a test that a custom inverter type doesn't inherit the GE step.test_coordinator: the descriptor field must be a percentage above 0 and up to 100, andinverter_definition()maps it, keeps base's value when it is absent, and gives 0 without a base.run_pre_commitpass.🤖 Generated with Claude Code