Repository navigation
fix(sungrow): an outage no longer drops registers or the release (1.6.1) - #202
Merged
Merged
Conversation
Count a failed read or EMS write only when the device answers with a Modbus exception. A failed read also counts when another read in the same poll returned data. Transport errors, reconnect backoff and gateway or busy exceptions never count. The first poll that reads data after a poll that read nothing clears the read misses and the EMS write count. Once forced mode has landed, set_self_consumption always writes the release and never reports the default as held until the inverter takes it. FTW's random site rig (27 October 2026, master 83c2b24c) found both: 13000 dropped after an outage flipped the battery sign (207539, 207534), and the release was skipped while the inverter held its last forced command for up to 5 minutes (8 of 10 seeds). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This was referenced Oct 28, 2026
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.
Problem
FTW's nightly random site rig (master 83c2b24c, 27 October 2026, 10 seeds) takes the Sungrow offline for 20 to 90 s. While it is gone, every read and write fails with a transport error, such as
dial tcp …: connection refusedormodbus reconnect backoff active. The driver counted those as if the device had refused them, in two places:Reads (
optional_read, triage item D1). Three failed polls dropped a register for the rest of the session. Fromftw.log,register N failed 3 reads in a row; treating it as absent:13000 is the power-flow status that signs the battery. Without it, FTW read a discharge as a charge: 105 samples in 207539 (655–1010 s) and 13 in 207534 (968–980 s). PV was lost in 207534 and 207539, and the home-use check failed in 207539.
Writes (
note_ems_write/set_self_consumption, the driver half of triage item C1). Three failed EMS writes during the outage marked the block absent (the EMS control block rejected 3 writes in a rowappears in 9 of 10 seeds). From then on, the watchdog's release returned true without writing. The inverter kept its last forced command after it came back:In 207534 it discharged 5000 W, from 5.6 % to 5.0 %, while FTW was not dispatching it. The comment on the skip said "a write it rejects cannot have latched a forced state". That holds for a device that never took forced mode. It does not hold after an outage.
Change
drivers/lua/sungrow.lua, 1.6.0 → 1.6.1:device_refused(err)is true only for a Modbus exception reply (modbus exception … code=0x.., or a host that names the code, e.g.Illegal Data Address). Gateway codes 0x0A and 0x0B and busy 0x06 do not count: they say the device could not answer.ems_write_failures. Once this session has written forced mode (13049 = 2 accepted), the release is always written. It is never reported as held until a release lands.driver_pollnow wraps the old body. A poll that reads nothing marks the link down. The next poll that reads data clearsmiss_countsandems_write_failures.Other drivers with the same latch (not changed here).
50-125k-svk,50-125k-svk-slew,50-125k-svk-ac-slew,deye-svkandkonja-261-svkuseBUNDLE_MISSES_BEFORE_SKIP. That is a different shape:bundle_readskips a block forBUNDLE_RETRY_EVERY(12) polls and then tries it again, so an outage costs about a minute, not the session. Each also has its ownDEFAULT_MODE_REFUSALS_BEFORE_STOPwrite latch. Neither is identical to this change, so I left them for a separate PR.Tests
drivers/tests/test_sungrow_outage.py, 18 cases. 14 fail on main and all pass here:Also run:
make test-driver ID=sungrow: 114 passed, 4 skipped.make check: 5007 passed.make refused-write-report ID=sungrow:SETTLED 1 WRITES 3,3,3,0,0,0,0,0, the same as before.make absent-register-report ID=sungrow: every addressSETTLED 1.No hardware run. This changes only how failures are counted, not any register, scale or sign.
Risk
exceptionorillegalwould see refusals treated as link errors on writes. FTW's host usesmodbus exception function=0x.. code=0x... On reads, such a refusal still counts when the rest of the poll answered.driverRefusalRetryInterval(5 min) after a refused default, and does not resend the default when the device returns. That is the Core half of C1 and is not in this PR.Paired PR in srcfl/ftw moves the bundled pin: see the comment below once it is open.
🤖 Generated with Claude Code
Same change as #201 (cc16f0e), re-committed with a
Signed-off-byline so the DCO check passes. #201 closes in favour of this PR.🤖 Generated with Claude Code