Skip to content

Fox: fall back to v2/v3 scheduler API for EVO series (v1 returns errno 41200 permanently)#4181

Merged
springfall2008 merged 2 commits into
mainfrom
fix/fox-evo-scheduler-v3-fallback
Jul 5, 2026
Merged

Fox: fall back to v2/v3 scheduler API for EVO series (v1 returns errno 41200 permanently)#4181
springfall2008 merged 2 commits into
mainfrom
fix/fox-evo-scheduler-v3-fallback

Conversation

@mgazza

@mgazza mgazza commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

Fox ESS EVO-series inverters (productType 812) cannot be controlled via the v1 scheduler OpenAPI: /op/v1/device/scheduler/get and /op/v1/device/scheduler/get/flag return 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:

Endpoint EVO 10-5-H KH10 / KH8
/op/v1/device/scheduler/get errno 41200, persistent across 24h+ errno 0 ✓
/op/v0/device/scheduler/get/flag errno 0 {enable:true, support:true} errno 0 ✓
/op/v2/device/scheduler/get errno 0, full schedule ✓ errno 0 ✓
/op/v3/device/scheduler/get errno 0, full schedule ✓ errno 0 ✓

Fox's own device-detail reports function.scheduler: true for the EVO. The foxesscloud reference library migrated get_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_RETRIES attempts 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/get and flatten each group's extraParam back to the flat v1 shape, so downstream consumers (compute_schedule, schedules_are_equal, HA publishing) are unchanged. v2 has no properties block, 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 new groups_to_v3() — SOC/power fields nested inside extraParam, only enabled groups carried (matching the request shape the foxesscloud library sends).
  • Devices that needed the fallback are marked in scheduler_api_v2 and skip v1 thereafter, avoiding the retry/sleep cost on every poll.
  • No behaviour change for KH-series or any device where v1 works — v1 remains the first choice and the fallback only engages when it fails.
  • set_scheduler_enabled() (v1 set/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 applied
  • test_api_get_scheduler_v2_sticky — second poll and subsequent writes skip v1 entirely
  • test_api_set_scheduler_v3_fallback — v1 enable fails → v3 request carries extraParam-nested, enabled-only groups

--test fox_api and --test fox_oauth suites 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

mgazza and others added 2 commits July 4, 2026 09:58
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds EVO-series (Fox productType "812") scheduler support by routing scheduler reads/writes away from Fox’s broken v1 scheduler endpoints, while keeping downstream schedule handling compatible with the existing v1-shaped data.

Changes:

  • Route FoxAPI.get_scheduler() to a new v2 scheduler reader for EVO devices and normalise v2 group payloads back into the v1 flat group shape.
  • Route FoxAPI.set_scheduler() to the v3 scheduler write endpoint for EVO devices, converting flat groups into v3’s extraParam nesting via groups_to_v3().
  • Add targeted Fox API tests for EVO v2/v3 scheduler handling and add foxesscloud to the cspell dictionary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
apps/predbat/fox.py Adds EVO productType detection and v2/v3 scheduler read/write paths with request-shape conversions.
apps/predbat/tests/test_fox_api.py Adds new tests covering EVO v2 scheduler reads, v3 scheduler writes, and regression guards for KH staying on v1.
.cspell/custom-dictionary-workspace.txt Adds foxesscloud to the spelling dictionary.

Comment thread apps/predbat/fox.py
@springfall2008 springfall2008 merged commit 3fa2db5 into main Jul 5, 2026
3 checks passed
@springfall2008 springfall2008 deleted the fix/fox-evo-scheduler-v3-fallback branch July 5, 2026 17:39
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.

3 participants