Skip to content

Write charge/discharge schedules to the periodic API as well - #270

Merged
CharlesGillanders merged 9 commits into
CharlesGillanders:mainfrom
Poshy163:fix/periodic-charge-discharge
Aug 14, 2026
Merged

Write charge/discharge schedules to the periodic API as well#270
CharlesGillanders merged 9 commits into
CharlesGillanders:mainfrom
Poshy163:fix/periodic-charge-discharge

Conversation

@Poshy163

@Poshy163 Poshy163 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Some regions have moved to a backend that only acts on setTimeChargeBySn. There the legacy updateChargeConfigInfo call still returns 200 but the inverter never applies it until you press Save in the app, or SetConfig times out. Other regions still only understand the legacy endpoints, so write both: periodic first, legacy after.

Support is probed once per inverter on setup; systems that aren't entitled return 6017 and keep using the legacy endpoints only.

The write payload was being rebuilt in time.py, switch.py and number.py, so move it onto the coordinator and have the buttons and services use it too.

Fixes #267
Fixes #269

Some regions have moved to a backend that only acts on setTimeChargeBySn.
There the legacy updateChargeConfigInfo call still returns 200 but the
inverter never applies it until you press Save in the app, or SetConfig
times out. Other regions still only understand the legacy endpoints, so
write both: periodic first, legacy after.

Support is probed once per inverter on setup; systems that aren't
entitled return 6017 and keep using the legacy endpoints only.

The write payload was being rebuilt in time.py, switch.py and number.py,
so move it onto the coordinator and have the buttons and services use it
too.

Fixes CharlesGillanders#267
Fixes CharlesGillanders#269
The switch writes are logged by the coordinator now, so _LOGGER here was
assigned but never read.
Shows whether an inverter is on the newer periodic backend or only accepts
the legacy endpoints, which is the first thing worth knowing on any report
of charge/discharge times not applying.

Also retry the probe on full polls while the answer is still unknown, so a
cloud hiccup during setup doesn't leave the sensor stuck, and include the
value in the downloadable diagnostics.
getTimeChargeBySn is separately permissioned from setTimeChargeBySn and
returns 6017 on accounts whose systems are on the new backend, so using it
to decide whether to write the periodic schedule skipped the write for
exactly the users CharlesGillanders#267 and CharlesGillanders#269 are about.

Write both APIs unconditionally instead. A failed periodic write now falls
through to the legacy one rather than aborting, and a change is only
reported as failed when neither write got through.

The diagnostic sensor can only speak for the read, so rename it to Periodic
Schedule Read with readable/unreadable/unknown and say plainly that
unreadable does not mean the system is on the old backend.
Verified against the live API: an empty chargeTimeList/dischargeTimeList is
rejected with 6001 "time list is null", and omitting one gives 10001. Both
lists must contain at least one period, and there is no confirmed way to say
"no periods on this side" — a 00:00-00:00 element passes validation but its
meaning is unverified, and would be a 24-hour window if the server reads
end <= start as wrapping midnight.

So skip the periodic write when either list would be empty and log why. The
legacy endpoints still run. This also means Reset Charge/Discharge cannot
clear a periodic schedule, which the README now says.
@Swamp-Ig

Copy link
Copy Markdown

Yes, I need this for my system. Happy to help with testing / coding.

@Swamp-Ig

Copy link
Copy Markdown

and code reviews for that matter.

@Poshy163

Copy link
Copy Markdown
Collaborator Author

Heya, yeah I'm almost done, I'm just testing with my inverter atm as there seems to be a few quirks and return codes that are unique when you haven't set "time based charging" up at all

@Swamp-Ig

Copy link
Copy Markdown

Oh shame, I just started poking at it tonight to try and get it to work!

@Poshy163

Copy link
Copy Markdown
Collaborator Author

Got an upstream bump to do before this can merge: CharlesGillanders/alphaess-openAPI#29

alphaessopenapi 0.0.21 can surface the code behind a failure instead of
returning None for everything, so turn it on and act on it.

The periodic write is the reason for this. It used to be impossible to tell
an accepted schedule from a rejected one, because setTimeChargeBySn answers
with data: null either way. Now a rejection arrives as AlphaESSApiError, so a
6017 means we know this system isn't entitled to the periodic API and can stop
asking on every write. The legacy endpoints still get written as before.

The flag applies to reads too, which would have been a regression: an endpoint
the account can't use would have raised mid-fetch, aborted the whole inverter,
and after three polls tripped the backoff and dropped it entirely. Polled reads
now go through _read, which absorbs an API-level rejection and carries on with
that one value missing, exactly as they behaved before. Transport errors still
propagate — those really do mean the inverter is unreachable.

Also handles the case where getESSList is refused during setup, which used to
be an unhandled traceback; credential-shaped codes raise ConfigEntryAuthFailed
and the rest ConfigEntryNotReady.
Turning on raise_on_error meant several paths that quietly received None now
receive an exception instead, and most of them were not ready for it.

Switch and number writes left their optimistic value on screen when the write
failed, so the UI claimed a setting had been applied when nothing had. Both now
revert, the way the time entities already did. Number puts the stored value back
too, since that is what the charge/discharge buttons send next.

Buttons took their 30 second rate limit slot before the call and kept it even
when the command was refused, so a rejected press cost the user half a minute
before they could retry. The timestamp is now restored on failure and the
notification says what happened rather than claiming success.

The two services raised the library exception straight at the user; they now
raise HomeAssistantError with a readable message.

Config flow gets raise_on_error too, which fixes a pre-existing bug: a wrong
AppSecret returns 6007 with an empty body, which used to read as "no systems"
and still created the entry. It now fails validation as invalid auth.

Narrowed AUTH_FAILURE_CODES to codes re-entering credentials can actually fix.
6005 (appId not bound to that SN) and 6009 (IP whitelist) are portal-side
configuration problems, and prompting for reauth would have looped the user.
Four of the periodic log lines printed a bare code — "(6017)", "6042" — which
are the ones most likely to end up pasted into an issue, and the least useful
without the meaning next to them.

describe_api_error renders them consistently from the description the library
already attaches to the exception, so the wording keeps coming from
RETURN_CODES / UNDOCUMENTED_RETURN_CODES upstream and isn't copied into this
repo. Codes the library doesn't know still show their number.
async_set_native_value stores the new value before pushing it, so when the
push failed the rejected value stayed behind — and that stored value is what
the charge/discharge buttons send next time.

Reverting it is not quite enough either. get_number_setting only falls back to
its default when the key is missing, so writing None back would shadow the
90/10 defaults and hand None to the API. When there was no previous value the
key is now dropped instead, via clear_number_setting.
@Poshy163
Poshy163 marked this pull request as ready for review August 14, 2026 10:55
@Poshy163

Poshy163 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Bump @CharlesGillanders

@Swamp-Ig im going to release this as a beta so i can see how the implementation is for regions outside of Australia, and regions still using the "old" server (i think china still does) then make any changes as needed. as im still not 100% confident in my implementation

@Swamp-Ig

Copy link
Copy Markdown

@Poshy163 I'm in Australia too, so I expect it will be pretty similar, but will do a code review over the weekend and give it a try.

@CharlesGillanders CharlesGillanders left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you!

@CharlesGillanders
CharlesGillanders merged commit d60e33b into CharlesGillanders:main Aug 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants