Migrate EV/Wallbox to site-scoped v2 endpoints (fixes Enphase 30401) - #7
Merged
Merged
Conversation
Preparation for the endpoint migration to /api/v1/sites/{id}/assets/ev-chargers.
The site-scoped response returns the wallbox identifier under `id`; the legacy
system-scoped path returned it as `gridxHardwareId`. Both fields co-exist on
the dataclass so the from_dict() code path stays symmetric during the transition.
After the endpoint migration lands, `gridx_hardware_id` will be `None` for
every user (site-scoped payload does not surface it). HA integration only
reads it as a presence-boolean via getattr fallback, so no consumer breaks.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgkYaQACLxezeceA1hrXQs
Enphase-based systems (no GridX device gateway) hit HTTP 422 error_code
30401 "DeviceGateway not found" on the legacy system-scoped routes:
- GET /api/v1/systems/{id}/devices/evs
- GET /api/v1/systems/{id}/devices/ev-chargers
- PATCH /api/v1/systems/{id}/devices/evs/{eid}
The 1KOMMA5° app itself uses the site-scoped equivalents on all
systems (verified live for the GridX author account + Enphase-user HAR
+ 2026-09-05 own-app HAR). Migrating the SDK to the same routes fixes
the Enphase HTTP 422 and stays on the trajectory the vendor is
maintaining.
Payload-shape changes handled inside EVCharger:
- Reader getters (name/manufacturer/model/capacity/chargingMode/
targetSoc/defaultSoc/manualSoc/manualSocTimestamp/chargerId/
departureTime/minChargingCurrent) now read from the flat top-level
fields instead of the nested profile/chargeSettings blocks.
- capacity_wh() normalises kWh<->Wh (the site-scoped payload uses
either unit user-dependently; still returns Wh consistently).
- assigned_charger_id() reads chargerId (site-scoped name).
- Fields the site-scoped payload no longer surfaces return None:
safety_range_km, updated_at, charging_mode_updated_at,
secondary_schedule_departure_time/soc. primary_schedule_days
returns [] (no schedule-day field either).
- primary_schedule_departure_soc() now returns target_soc(); v2 API
consolidated the two fields (user-confirmed via app UI showing a
single departure-SoC value). HA consumers see the same value they
saw before, semantically consistent with the vendor UI.
Setter bodies converted from nested chargeSettings.* to flat top-level
keys (chargingMode/manualSoc/targetSoc/departureTime). Verified live
that the site-scoped endpoint accepts both partial-flat and legacy
nested bodies; picking flat matches the app's own convention.
Wallbox model: `id` (new canonical identifier) is populated from the
site-scoped payload; `gridx_hardware_id` stays declared for
backwards-compatible presence-check callers but is now always None.
Live-verified round trip on GridX system:
- get_ev_chargers() → 1 EVCharger, correct manufacturer/model/capacity
- get_wallboxes() → 1 Wallbox with id set, gridx_hardware_id None
- set_target_soc round-trip (0.8→0.9→0.8) with PATCH bodies matching
the app's minimal flat form
Tests updated in a follow-up commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgkYaQACLxezeceA1hrXQs
- Fixtures (make_ev_data, make_wallboxes_data) now return the flat
site-scoped v2 shape with the same values existing tests already
expect. New capacity_unit parameter on make_ev_data lets tests
exercise the kWh→Wh normalisation path.
- Test URLs bumped: v2/sites/assets/evs (EV chargers), v1/sites/assets/
ev-chargers (wallboxes); _SITE_BASE_V1/V2 constants added.
- Setter body assertions now check the flat top-level keys the SDK
sends (chargingMode, targetSoc, manualSoc, departureTime) instead
of the deprecated nested chargeSettings.* form.
- New regression tests:
- test_capacity_wh_normalizes_kwh_unit
- test_primary_schedule_departure_soc_aliases_target_soc
- test_gridx_hardware_id_is_none_on_site_scoped_response
- test_safety_range_km/updated_at/charging_mode_updated_at/
primary_schedule_days/secondary_schedule_* return None on v2
- CLI cmd_wallboxes prints Wallbox.id (canonical) instead of
gridx_hardware_id (now always None post-migration).
Full suite: 345 passing, 0 failing, ruff clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgkYaQACLxezeceA1hrXQs
- API.md URLs updated in place: /devices/evs → /assets/evs (v2), /devices/ev-chargers → /assets/ev-chargers (v1). Notes about capacity unit variance (Wh vs kWh) added to the unit table. - Postman collection: all 6 EV/Wallbox request URLs and path arrays rewritten; wallbox description mentions the id/gridxHardwareId change and the Enphase-compatibility rationale. - Memory endpoints_reference: matching row updates with the migration note (kept in the user's Claude memory dir, not the repo). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RgkYaQACLxezeceA1hrXQs
The v2 site-scoped API has a single departure-time slot per vehicle
(v1 had a primary/secondary distinction that never populated for
regular users). Cosmetic cleanup so nothing in the user's view still
says 'primary':
- CLI subparser help: 'Set EV departure time' (was 'Set EV primary
departure time'); print output drops 'primary'; docstring updated.
- EVCharger.set_primary_departure_time / primary_schedule_departure_time
docstrings now describe them as 'the scheduled departure time' and
explain the historical name.
- Error label 'Failed to set primary departure time' → 'Failed to set
departure time' (single-slot semantics).
- CLI 'ev' output now prints the departure line based on whether
departure_time is set, not on the (now always-empty)
primary_schedule_days.
- API.md List-EV response example rewritten to match the actual v2
flat shape (top-level chargingMode/targetSoc/departureTime/
manualSoc/defaultSoc/chargerId), with a note explaining that
targetSoc and primaryScheduleDepartureSoc were consolidated.
- API.md Update-EV curl examples switched from nested chargeSettings.*
bodies to the flat form the app and SDK send; departure-time example
renamed and body is {"departureTime": "07:30"}.
- API.md wallbox response example uses `id` (was `gridxHardwareId`),
with a note about the legacy field and the Enphase-fix motivation.
SDK method names (set_primary_departure_time,
primary_schedule_departure_time, primary_schedule_departure_soc) are
retained unchanged — HA integration and any other callers keep
working. Method-name cleanup can come in a later release if desired.
345 tests passing, ruff clean, Postman JSON valid.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgkYaQACLxezeceA1hrXQs
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
get_wallboxes()(and by extensionget_ev_chargers()) hits HTTP 422 error_code 30401 "DeviceGateway not found" on systems without a GridX device gateway. Confirmed live for an Enphase-based user (2026-09-04 diagnostics): the legacy system-scoped routes are coupled to the GridX DeviceGateway record, and non-GridX setups can't be served.Solution
Migrate the three affected endpoints to the modern site-scoped equivalents that the 1KOMMA5° app itself uses (confirmed via HAR analysis of my own GridX system + the Enphase-user HAR):
GET /api/v1/systems/{id}/devices/evs→GET /api/v2/sites/{id}/assets/evsGET /api/v1/systems/{id}/devices/ev-chargers→GET /api/v1/sites/{id}/assets/ev-chargersPATCH /api/v1/systems/{id}/devices/evs/{eid}→PATCH /api/v2/sites/{id}/assets/evs/{eid}The site-scoped path is universal — works on both GridX-native and non-GridX setups, and matches what the vendor app has moved to.
Public-API impact
EVCharger.primary_schedule_departure_soc()now aliasestarget_soc(). The v2 API consolidated the two former separate SoC fields into a single value (user-confirmed via app UI showing one departure-SoC field). HA sensors keep working with correct semantics — nounavailablestate.Wallbox.gridx_hardware_idreturnsNonefor all users; the new canonical wallbox identifier isWallbox.id. The HA integration only readsgridx_hardware_idas a presence boolean viagetattr(..., None), so nothing breaks.safety_range_km,updated_at,charging_mode_updated_at,secondary_schedule_*,primary_schedule_days) returnNone/[]— v2 payload does not carry them.capacity_wh()normalises kWh ↔ Wh (v2 payload uses either unit user-dependently).Verification
primary_schedule_departure_soc() == target_soc()fallback holds,Wallbox.idpopulated,gridx_hardware_idNone, setter round-trip OK.get_wallboxes()returns the Enphase-Wallbox instead of raising RequestError.Test plan
Release plan
Bump to v0.2.0 (major) after merge. Signals the internal endpoint migration and new
Wallbox.idfield. HA integration (mrebbert/1komma5-ha) needs a follow-up release to bump its constraint from<0.2to<0.3.🤖 Generated with Claude Code
https://claude.ai/code/session_01RgkYaQACLxezeceA1hrXQs