Skip to content

fix(inverter): stop spurious "REST failed to setExportTarget" warnings - #4413

Merged
springfall2008 merged 1 commit into
mainfrom
fix/export-target-read-back
Aug 1, 2026
Merged

fix(inverter): stop spurious "REST failed to setExportTarget" warnings#4413
springfall2008 merged 1 commit into
mainfrom
fix/export-target-read-back

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Fixes #4404

Problem

Since v8.47.1, GivEnergy REST (GivTCP) users get Warn: Inverter 0 REST failed to setExportTarget on every export slot, and the run ends Exporting with Errors reported. @davemilsom2 bisected it to 8.47.1 and pointed at #4384, which is right — that PR is the only inverter.py change in v8.47.0..v8.47.1.

There are two defects, one old and one new. They only combine from 8.47.1.

1. rest_setDischargeTarget could never report success (pre-existing)

The write was verified with:

if self.rest_data["raw"]["invertor"]["discharge_target_soc_1"] == target:

GivTCP reports the raw invertor registers as strings. From the attached debug file:

discharge_target_soc_1: '0'

So '4' == 4 is always False. Every call burned all five retries — five redundant register writes and five runAll polls — and then recorded an error, even when the register had taken the value.

The reporter's log proves the write worked:

  • 44 × Set export target slot 1 ... via REST failed, 0 successes
  • yet 14 × Current discharge target is already set to 4.0 — that path in adjust_force_export coerces with float(), so it sees the value the verify loop refused to match
  • 01:50:45 "failed", 01:55:29 "failed", 01:56:13 register reads back 4.0

rest_setReserve immediately above already does this correctly with int(float(...)).

2. 0 was doing double duty as "could not read it" (the regression)

Both branches of the export target block fall back to current = 0 when the value is None or unparseable. While the guard was current > self.reserve_percent that sentinel was harmless — it never triggered a write. #4384 changed it to current != target_soc, so an unreadable register now looks like a target parked below the reserve and gets written every cycle.

That is @russdan's second symptom, on the entity path:

Warn: Inverter 0 write_and_poll_value: Current state for discharge_target_soc is None

Predbat writing to an entity that isn't there.

Fixes

rest_setDischargeTarget — coerce the read back value with int(float(...)) before comparing, guarded for missing/None, and name the value read in the failure message so the next report is diagnosable.

adjust_force_export — an unreadable target stays None instead of collapsing to 0, and is skipped rather than written, on both the REST v3 and entity paths. #4384's bidirectional tracking is untouched for real values. The skip is a plain log line, not a Warn: — an inverter that simply has no such register would otherwise print a warning every cycle.

INVERTER_REST_TIMEOUT 5 → 10 seconds, to give a busy GivTCP more room to respond.

Tests

test_discharge_target_read_back in apps/predbat/tests/test_inverter.py:

  • a string read back counts as success, with one write rather than five
  • an unreadable REST export target is not written and records no error
  • an unreadable export target entity is not written and records no error

Written first and confirmed failing on the pre-fix code (all six assertions), passing after. The test wraps record_status so it asserts only on export target errors, rather than on unrelated fixture noise from the H M format time entities.

The existing test_discharge_target_tracks_reserve from #4384 used ints in raw, which is why it missed this — real GivTCP sends strings.

./run_all --quick and ./run_pre_commit both exit 0.

Not addressed

@russdan also reported REST failed to setDischargeSlot1. That is a different comparison (Timeslots, strings on both sides, no type mismatch) and the debug file failed to upload, so there is no evidence to work from yet. It may be collateral: fix 1 removes four spurious POSTs plus five runAll polls per export cycle, which was hammering GivTCP immediately before the slot write. Worth a retest on this branch before chasing it separately.

🤖 Generated with Claude Code

rest_setDischargeTarget verified the write with a raw string against an
int - GivTCP reports the raw invertor registers as strings, so '4' == 4
was always False and the write never counted as successful. Every export
slot burned all five retries (five redundant register writes) and then
recorded an error status, even though the register had actually taken
the value. rest_setReserve already coerces with int(float()); do the
same here and report the value read back on failure.

adjust_force_export also collapsed an unreadable target to 0 in both
branches. That sentinel was harmless while the guard was
"current > reserve_percent", but since it became "current != target_soc"
an inverter that does not expose the register looks like a target parked
below the reserve and gets written on every cycle. Keep it as None and
skip the write instead, so the bidirectional tracking still applies to
real values.

Also raise INVERTER_REST_TIMEOUT from 5 to 10 seconds to give a busy
GivTCP more room to respond.

Fixes #4404

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 16:21

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.

Pull request overview

Fixes spurious export-target warning noise for GivEnergy GivTCP REST users by making export-target read-back verification type-safe and by avoiding writes when the export-target value is unreadable/missing (e.g., inverter models without that register).

Changes:

  • Treat REST read-back values as strings and coerce before comparing so successful export-target writes can be recognised.
  • Avoid writing export-target when the current value cannot be read (REST v3 and entity paths), preventing repeated warnings/errors on inverters lacking the register/entity.
  • Increase REST request timeout to better tolerate slower/busier local GivTCP instances, and add regression tests for the above behaviours.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
apps/predbat/inverter.py Fixes REST export-target read-back verification; skips export-target writes when current target is unreadable; improves failure messaging.
apps/predbat/const.py Increases REST timeout from 5s to 10s to reduce timeouts on busy REST endpoints.
apps/predbat/tests/test_inverter.py Adds regression test covering string read-back success and “missing/unreadable target” skip behaviour for REST + entity paths.

Comment thread apps/predbat/inverter.py
Comment on lines +3395 to +3399
# GivTCP reports the raw registers as strings, so coerce before comparing or a
# successful write reads back as '4' and never matches the int target
result = self.rest_data.get("raw", {}).get("invertor", {}).get("discharge_target_soc_1", None)
try:
result = int(float(result))
@springfall2008
springfall2008 merged commit 817495f into main Aug 1, 2026
3 checks passed
@springfall2008
springfall2008 deleted the fix/export-target-read-back branch August 1, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Following update now getting: Warn: Inverter 0 REST failed to setExportTarget when exporting

2 participants