Fox: fall back to v2/v3 scheduler API for EVO series (v1 returns errno 41200 permanently)#4181
Open
mgazza wants to merge 2 commits into
Open
Fox: fall back to v2/v3 scheduler API for EVO series (v1 returns errno 41200 permanently)#4181mgazza wants to merge 2 commits into
mgazza wants to merge 2 commits into
Conversation
Fox's v1 scheduler OpenAPI endpoints return errno 41200 permanently for EVO-series inverters (productType 812) even though the devices fully support the scheduler — verified live against an EVO 10-5-H (v1 fails, v0/v2/v3 succeed) and two KH-series controls. Each failed v1 call costs FOX_RETRIES attempts with long sleeps, so EVO devices also mark themselves via scheduler_api_v2 and skip v1 thereafter. - get_scheduler: fall back to /op/v2/device/scheduler/get and flatten each group's extraParam back to the flat v1 shape (v2 has no properties block, so the existing fdPwr/fdSoc defaults apply) - set_scheduler: fall back to /op/v3/device/scheduler/enable, nesting SOC/power fields inside extraParam and carrying only enabled groups (matching the request shape the foxesscloud reference library sends) - KH-series and other devices where v1 works see no behaviour change Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up to the v2/v3 scheduler fallback addressing an xhigh code review.
The previous approach routed a device to the v2/v3 scheduler API whenever a
v1 call returned None, and made that sticky. But errno 41200 is ALSO Fox's
shared transient rate-limit/comms code, so a single network blip on a healthy
KH-series device permanently rerouted it to the property-less v2/v3 path — a
real regression. It also never marked the device from the write path (so EVO
writes re-probed the failing v1 endpoint, stalling the loop for up to an hour),
and the flag was lost across restarts.
Replace failure-based detection with deterministic productType detection:
- FOX_V2_SCHEDULER_PRODUCT_TYPES = {"812"} (verified EVO 10-5-H code); a new
uses_v2_scheduler() helper reads device_detail productType
- get_scheduler / set_scheduler route by productType, so KH stays 100% on v1
(zero regression) and EVO uses v2/v3 directly (no per-cycle v1 stall)
- drop the runtime scheduler_api_v2 sticky flag entirely (productType is
re-derived each fetch, so it survives restarts and never mis-fires)
- harden get_scheduler_v2: null-safe groups, default per-group and top-level
enable so an active EVO schedule is not mis-read as disabled/empty
Tests: EVO uses v2/v3 directly; KH stays v1 on failure (regression guards);
null/absent-enable v2 shapes; EVO v2 also-fails returns None without crashing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Fox ESS EVO-series inverters (productType 812) cannot be controlled via the v1 scheduler OpenAPI:
/op/v1/device/scheduler/getand/op/v1/device/scheduler/get/flagreturn errno 41200 ("Failed to load data") permanently — not transiently — even though the devices fully support the scheduler.Verified live (2026-07-04) against real hardware:
/op/v1/device/scheduler/get/op/v0/device/scheduler/get/flag{enable:true, support:true}✓/op/v2/device/scheduler/get/op/v3/device/scheduler/getFox's own device-detail reports
function.scheduler: truefor the EVO. The foxesscloud reference library migratedget_schedule()/set_schedule()to the v3 interface immediately after adding EVO recognition (2.9.1 → 2.9.2).Because 41200 is (correctly) treated as retryable, each poll against an EVO burns
FOX_RETRIESattempts with up-to-30s sleeps before failing — so beyond never fetching a schedule, it also wastes minutes per cycle and inflates the account's API-call count.Changes
get_scheduler(): on v1 failure, fall back to/op/v2/device/scheduler/getand flatten each group'sextraParamback to the flat v1 shape, so downstream consumers (compute_schedule,schedules_are_equal, HA publishing) are unchanged. v2 has nopropertiesblock, so the existing fdPwr (8000, capacity-capped) and fdSoc (10) defaults apply.set_scheduler(): on v1 failure, fall back to/op/v3/device/scheduler/enable, converting groups via newgroups_to_v3()— SOC/power fields nested insideextraParam, only enabled groups carried (matching the request shape the foxesscloud library sends).scheduler_api_v2and skip v1 thereafter, avoiding the retry/sleep cost on every poll.set_scheduler_enabled()(v1set/flag) is left as-is: there is no v2/v3 flag endpoint, and the foxesscloud library also still uses the v1 flag write, which suggests v1 writes work on EVO (only reads are broken). If that turns out false we'll need a follow-up.Tests
Three new tests in
tests/test_fox_api.py(written first, watched fail, then pass):test_api_get_scheduler_v2_fallback— v1 fails → v2 used, extraParam flattened, defaults appliedtest_api_get_scheduler_v2_sticky— second poll and subsequent writes skip v1 entirelytest_api_set_scheduler_v3_fallback— v1 enable fails → v3 request carries extraParam-nested, enabled-only groups--test fox_apiand--test fox_oauthsuites pass; pre-commit (ruff, black, cspell, sorters) clean.Impact
Two PredBat.com customers with EVO 10-5-H units are currently blocked at onboarding by this (their validation probe has been fixed separately); this PR is required for actual charge/discharge control of EVO-series devices.
🤖 Generated with Claude Code