Skip to content

fix(inverter): enable Enable_Charge_Target before writing a GivTCP discharge target - #4542

Closed
chalfontchubby wants to merge 1 commit into
mainfrom
fix/givtcp-discharge-target-enable-backoff
Closed

chalfontchubby wants to merge 1 commit into
mainfrom
fix/givtcp-discharge-target-enable-backoff

Conversation

@chalfontchubby

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #4517/#4518, for the case #4518 explicitly doesn't fix (a REST-v3 inverter where Control.Discharge_Target_SOC_1 is entirely absent, so the caller falls through to the slower raw.invertor field with no change in behaviour).

Working from a reporter's real REST_Response.txt and GIVTCP_Logs.log: Control.Enable_Charge_Target reads "disable", and GivTCP's own log shows "Setting Discharge Target 1 was a success" repeatedly - the write itself is genuinely reaching the inverter and being acknowledged, but doesn't persist, so adjust_force_export() sees a permanent mismatch and rewrites every single cycle. adjust_battery_target() already calls rest_enableChargeTarget(True) before writing a charge target ("without it the inverter ignores the target") - that call was never mirrored on the discharge-target side. This PR does that.

Not confirmed as the root cause - correlational, from comparing against a second, unrelated working sample (#4421) where the pairing is the opposite (Enable_Charge_Target: enable + Discharge_Target_SOC_1 present). Every hardware-identifying field GivTCP exposes (model/Invertor_Type, device_type_code, num_mppt, and firmware version, which differs only trivially) is identical between the two samples - so this isn't a generation/model distinction we could gate on statically even if we wanted to; the only thing that differs is Enable_Charge_Target itself.

Deliberately hardened for the case this theory is wrong:

  • If rest_enableChargeTarget(True) itself fails, the discharge-target write is skipped rather than also attempted - a genuinely unsupported inverter retries one REST endpoint per cycle instead of two.
  • After DISCHARGE_TARGET_ENABLE_MAX_CYCLE_FAILURES consecutive cycles where enabling has never once succeeded, stop attempting either endpoint entirely and surface it via record_status (the inverter loses its hardware-side reserve backstop during force export, not just a noisy log). Resets on restart and on any genuine success, and a regression (worked before, now failing) is flagged distinctly rather than being silently written off as unsupported.

Posted as draft specifically to ask for a live test before this goes anywhere near merge - it's unconfirmed and the person who can confirm it is the original reporter.

Test plan

  • Regression tests in test_inverter.py, each confirmed to fail without its corresponding fix and pass with it
  • ./run_all --quick passes
  • ./run_pre_commit passes
  • Real hardware test - not yet done, this is the actual thing in question

🤖 Generated with Claude Code

…scharge target (#4517)

adjust_force_export() decided a discharge-target write was needed based on
rest_readDischargeTarget() alone, but on some inverters GivTCP reports each
write as successful while it never actually sticks - Control.Enable_Charge_Target
stays off, so the value is written but the inverter doesn't treat it as
authoritative, and Predbat sees a permanent mismatch and rewrites every
cycle indefinitely.

adjust_battery_target() already calls rest_enableChargeTarget(True) before
writing a charge target for exactly this reason (documented: without it the
inverter ignores the target). Mirror that call for the discharge target too.
No-ops wherever it's already enabled, which is the common case.

Whether every rest_v3 inverter actually has this register at all is
unconfirmed, so this is deliberately hardened for the case it's wrong:

- If enabling itself fails, skip the discharge-target write rather than
  also attempting one already known to be doomed - a genuinely unsupported
  inverter retries one REST endpoint per cycle instead of two.
- After DISCHARGE_TARGET_ENABLE_MAX_CYCLE_FAILURES consecutive cycles where
  enabling has never once succeeded, stop attempting either endpoint
  entirely rather than retrying forever, and surface it via record_status
  so the reduced safety margin (no inverter-side reserve backstop during
  force export) is visible, not just logged. Resets naturally on restart
  and on any genuine success, so a working register never gets written off,
  and a regression (worked before, now failing) is flagged distinctly
  rather than silently treated the same as never-supported.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chalfontchubby

Copy link
Copy Markdown
Collaborator Author

Approach taken had diverged (into try the writes, back off ofter failing a number of times) - so the pr description was invalid. But replaced with an inverter type check to gate the writes.

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.

1 participant