feat: add battery control and configuration services - #5
Merged
Conversation
Six services, in two groups. Configuration: set_tou_schedule writes the tariff calendar, set_operating_mode changes mode and optionally that mode's reserve. Both go through SwitchModeRequest, so neither can reset a field by omitting it. Battery control: force_charge, force_discharge, hold_battery and clear_override. The API has no forced-charge endpoint, so these move the reserve state-of-charge floor and let the device track it, written with onlySave so the operating mode is never disturbed. The revert is the hard part, and it is what override.py is really about. An override is a promise to put settings back, held by a process that can be restarted at any moment, so the baseline is written to disk before the first write. On startup an expired override reverts immediately and a live one has its timer re-armed. Verified against real hardware: a force_charge survived a full Home Assistant restart and still reverted cleanly. Testing on the live system also found that allowChargingXiaGrid is read-only on this firmware. Four payload shapes (onlySave 0 and 1, string and integer, and the correctly-spelled key alongside the API's misspelling) were all accepted with HTTP 200 and none applied, while the reserve written in the same payload took effect immediately. So: - the overrides do not touch that field, and force_charge loses the allow_grid_charging parameter it could not have honoured - the existing grid-charging switch, which until now appeared to work and silently did nothing, reads the value back and raises if the device refused A diagnostic sensor reports which override is running and when it ends; without it an override is invisible beyond a reserve slider moving on its own.
hassfest rejected two things: a service cannot carry a target device filter (it needs a device selector field, which the schemas already required), and a translation placeholder must not sit inside single quotes. Also switches uv sync and uv run from --frozen to --locked. --frozen only promises to use the lockfile; --locked additionally fails when the lockfile has drifted from pyproject.toml, so a dependency added without re-locking is caught in CI rather than resolving differently elsewhere.
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.
Six services, in two groups.
Configuration —
set_tou_schedulewrites the tariff calendar,set_operating_modechanges mode and optionally that mode's reserve. Both go throughSwitchModeRequest, so neither can reset a field by omitting it.Battery control —
force_charge,force_discharge,hold_battery,clear_override. The API has no forced-charge endpoint, so these move the reserve state-of-charge floor and let the device track it, written withonlySaveso the operating mode is never disturbed.Notably this avoids the synthetic-tariff-window approach the other integration uses. One lever, no fake user settings left behind, nothing to disentangle from a real schedule.
The revert is the hard part
An override is a promise to put settings back, held by a process that can be restarted at any moment. So the baseline goes to disk before the first write. On startup an expired override reverts immediately; a live one has its timer re-armed.
Verified on real hardware:
What live testing found
allowChargingXiaGridis read-only on this firmware. Four payload shapes —onlySave0 and 1, string and integer, and the correctly-spelled key alongside the API's own misspelling — were all accepted with HTTP 200 and none applied. The reserve written in the same payload took effect immediately, so it is that one field being refused, not the write failing.Two consequences:
force_chargeloses theallow_grid_chargingparameter it could never have honoured.Documented in
docs/api-endpoints.mdwith the evidence.Also
A diagnostic sensor reports which override is running and when it ends. Without it an override is invisible beyond a reserve slider moving on its own.
Coverage stays at 100%.