Skip to content

fix(givtcp): accept GivEnergy's quantised charge/discharge rate read-back (#5324) - #5348

Merged
springfall2008 merged 3 commits into
mainfrom
fix/givtcp-rate-verify-quantisation
Oct 2, 2026
Merged

springfall2008 merged 3 commits into
mainfrom
fix/givtcp-rate-verify-quantisation

Conversation

@chalfontchubby

@chalfontchubby chalfontchubby commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

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 an inverter_limit_charge override 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_DEF gains rate_step_percent_of_capacity, set to 1 for GE. A custom inverter type built from the GE row does not inherit it.
  • A read-back below the rate written may miss by up to one step plus 1W. A read-back above it keeps the plain 5%: the step only rounds down, so a rate held one step up (134W) is still written down to a 0W hold.
  • within_fuzzy() holds that rule in one place. Both the "does this need writing" check in adjust_charge_rate/adjust_discharge_rate and write_and_poll_value's read-back match use it.
  • write_and_poll_value gains an optional fuzzy_below. It defaults to the existing behaviour, so its other callers are unchanged.
  • The GivTCP discovery record carries step_percent_of_capacity on its rate descriptors, and inverter_definition() maps it across. The discovery contract still rebuilds the GE row, and the vocabulary spec is updated to match.
  • docs/inverter-setup.md documents rate_step_percent_of_capacity, so a custom inverter type defined through inverter: can set it.

Deliberately not done

  • Predicting GivEnergy's exact read-back (rounding the target to a step before comparing). That needs Predbat's capacity to match the inverter's to the watt, and writing a rounded-down value can drop a further step (1206W comes out as 8.99%, which truncates to 8%). The approach here is a relaxed check that tolerates a small capacity mismatch.
  • A ~1W boundary. A request landing exactly one step above the rate the inverter holds is taken as already set, so the rate stays one step low. That is the same size of error the inverter's rounding makes anyway. Tightening it to exactly one step would instead bring the re-write loop back for read-backs in that 1W window.
  • The step comes from 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.
  • GE Cloud (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).
  • Tests for rate_tolerances() and within_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, and inverter_definition() maps it, keeps base's value when it is absent, and gives 0 without a base.
  • The quick suite and run_pre_commit pass.

🤖 Generated with Claude Code

chalfontchubby and others added 2 commits October 2, 2026 09:25
…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>
@chalfontchubby chalfontchubby added bug Something isn't working givtcp labels Oct 2, 2026
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@springfall2008
springfall2008 merged commit 07c7afa into main Oct 2, 2026
3 checks passed
@springfall2008
springfall2008 deleted the fix/givtcp-rate-verify-quantisation branch October 2, 2026 19:54
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working givtcp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Predbat GivTCP REST errors when setting charge rate

3 participants