[1.3.0-beta.2] - 2026-05-24
Second pre-release for early-adopter testing. Builds on 1.3.0-beta.1
with a breaking config-entry schema change (auto-migrated), a fix for a
silently-dropped option, the missing LAN read for ChargeMode, and a
UI sweep that removes every reference to the now-retired user-typed
fallback IP. HACS pre-release channel.
Changed (breaking — auto-migrated via config entry v1 → v2)
- Per-device LAN IPs are now auto-discovered and kept in sync with the V2C Cloud
/pairings/meresponse instead of requiring a single user-typed fallback. An account with N chargers is fully supported: every charger's IP is sourced from the cloud at runtime and a normalised snapshot is persisted on every successful refresh asentry.data["cached_pairings"]. During a cloud outage (rate limit, 403, transient network failure) every previously-seen charger remains addressable via its last-known IP — not just one. When the cloud returns online the cache is reconciled with the fresh response (added devices appear, removed devices disappear). - Cloud-only mode is now encoded as
entry.data["cloud_only"]: boolinstead of the empty-stringfallback_ipsentinel. The options-flow toggle still switches between Local and Cloud-only and triggers a reload, but no longer exposes a fallback IP field. - The first-setup
fallback_ipstep is gone. Initial setup requires the cloud to be reachable to capture the pairings list; this is consistent with the integration's name ("V2C Cloud") and removes a single point of failure (one user-typed IP that could not represent multi-device accounts). - Config entry
VERSIONbumped to 2.async_migrate_entrytranslates legacy entries: the cloud-only sentinel (fallback_ip == ""/"0.0.0.0") becomescloud_only: True; a non-emptyfallback_ippaired withfallback_device_idbecomes a one-recordcached_pairings; a non-emptyinitial_pairingssnapshot wins over the single-device pair. Legacy keys are dropped fromentry.data.
Fixed (LAN data accuracy)
ChargeModeSelect showed "Unknown" in LAN mode — per the official V2C Datamanager doc (rev 04/05/26) and confirmed by live probe against firmware 2.4.6,ChargeModeis write-enabled but absent from/RealTimeData; the integration's_READ_ONLY_KEYWORDSaugmentation set coveredLogoLED+LightLEDbut missedChargeMode. Added so the local coordinator now fetches/read/ChargeModein parallel with the other read-only keys and the Select displays the live value.
Fixed (options flow)
Local refresh intervaloption was not persisted — the options flow saved the value viaasync_update_entry(options=...)but then returnedasync_create_entry(title="", data={}), and HA's OptionsFlow uses thedataargument ofasync_create_entryto OVERWRITEentry.options. Result: the field always snapped back to 30 s after closing the form. Fix: pass the populated options dict toasync_create_entry(data=new_options). Regression test added.- UI strings referencing the removed
fallback_ipstep / field were cleaned up acrossstrings.jsonand the en/it/es translations: the cold-start "cloud offline" step is gone, the options-flow field label is gone, theconnection_typeandinitdescriptions now mention auto-discovered per-device IPs and LAN→cloud automatic routing instead of a manually configured fallback.
Developer Experience
- Companion Home Assistant container reachable from the dev container.
docker-compose.ymlattacheshass_core_devto a new externalv2c-devuser-defined bridge network with thehomeassistantalias, and.devcontainer.jsonjoins the dev container to the same network viarunArgs: ["--network=v2c-dev"]. Inside the dev container HA resolves ashttp://homeassistant:8123(host browser keeps usinghttp://localhost:8123). AninitializeCommandcreates the network and runsdocker compose up -d homeassistantbefore the dev container is built, so the service is ready when VS Code finishes attaching. HA usesrestart: "no": developer-initiated, not auto-restarted on host reboot. - Node.js LTS in the dev container via the
ghcr.io/devcontainers/features/node:1devcontainer feature, exposingnpxfor thecontext7andmemoryMCP servers configured in.mcp.json. mcp-proxyinstalled byscripts/setup(uv tool install --force git+https://github.com/sparfenyuk/mcp-proxy). Provides the Streamable-HTTP/SSE bridge that fronts the Home Assistant/api/mcpendpoint for Claude Code.- Unified dev-secrets file at
.env.dev(gitignored). Replaces the previous trio of single-token files (.gh-token,.v2c-token,.hass-token). The committed template.env.dev.exampledocuments every variable —GH_TOKEN,V2C_CLOUD_API_KEY,V2C_LOCAL_IP,HASS_TOKEN— including where to obtain it and which tool consumes it.scripts/setupappends an idempotent block to~/.bashrcthat sources.env.devwithset -a, so every variable is exported into every interactive shell on a single edit. Contributors fill in the file once at first checkout. The legacyHASS_TOKEN-only block in~/.bashrcis auto-removed byscripts/setupon subsequent runs.
Added
- Connection type editable post-setup — the options flow exposes a
Local (Wi-Fi)/Cloud only (4G)toggle. Switching modes triggers an automatic integration reload. Resolves the inability to change connection type after the initial setup. - Cloud-only entity coverage expanded —
_build_realtime_from_reported(the cloud → synthetic LAN payload translator) now handles seven additional numeric/reportedkeys (min_car_int,min_car_int_fb,max_car_int,max_car_int_fb,light_led,logo_led,contract_powerand itscontractedpower/contracted_poweraliases) and gains a string-field passthrough for device metadata (device_id/deviceId→ID,version→FirmwareVersion,mac→MAC). The cloud'swifi_infoJSON blob is parsed inline so the SSID and active IP populate the corresponding sensors. Verified end-to-end against a real/device/reported+/device/currentstatechargecapture (firmware 2.4.6). The set of entities that show real data in cloud-only mode grows from ~12 to ~20+. - LAN-only entities now correctly advertise as Unavailable in cloud-only mode instead of showing the misleading "Unknown" state. The six structurally LAN-only keys (
ReadyState,SignalStatus,Timer,ChargeMode,DynamicPowerMode,PauseDynamic) — none of which are present in the cloud/reportedor/currentstatechargepayloads — are gated viaavailable=Falsewhen the config entry is cloud-only. Applies to the sensor, switch, and select platforms.
Fixed
- Connection-type radio labels were not translated — the initial config-flow step rendered "Local (Wi-Fi)" / "Cloud only (4G)" in English regardless of the active UI locale because the schema used a hard-coded
vol.In({label: ...})dict. Migrated the schema toSelectSelector(translation_key="connection_type")and added a top-levelselectorblock tostrings.jsonand all translation files (en/it/es). - Italian "Intensità Light LED" entity name was mixed Italian/English. Renamed to "Intensità LED".
- Number entities
MinIntensity/MaxIntensity/LightLED/ContractedPowerwere Unknown in cloud-only mode — they read vialocal_keybut the cloud/reportedkeys (min_car_int,max_car_int,light_led,contract_power) were not in_REPORTED_TO_REALTIME. Each entity now resolves correctly in cloud-only mode. - Sensors
device_identifier/firmware_version/wifi_ssid/wifi_ipwere Unknown in cloud-only mode — the synthesis loop didfloat(str(raw))on every value and silently dropped non-numeric ones. Added a string-passthrough path plus inline parsing of the cloud'swifi_infoJSON blob to populateSSID/IP. - Cloud-only
VoltageInstallationreported a spurious ~77 V instead of the actual mains voltage — the synthesis path mapped the cloudvoltagefield (a small internal signal, e.g.0.077350) ontoVoltageInstallationand then scaled it × 1000 via the kV detection heuristic. The cloud's real mains/installation voltage is carried bycp_level(e.g.248on a 230 V EU install); remappedcp_level→VoltageInstallationand dropped the misleadingvoltagemapping. The_detect_cloud_scaleheuristic still inspectsvoltageas a magnitude signal but no longer surfaces it as a voltage reading. - Cloud-only
ContractedPowerdisplayed the wrong value (off by 100x) — the cloud encodescontract_poweras W/100 (e.g."7"= 700 W = 0.7 kW, confirmed against a live install where the V2C app showed 0.7 kW for cloud value"7"), but the Number entity layer divides the raw value by 1000 to render kW (LAN format is W). Added a_CLOUD_TO_LAN_MULTIPLIERStable that multipliesContractedPowerby 100 during synthesis, so cloud"7"becomes700in the synthetic /RealTimeData and the entity renders the expected0.7 kW. - Cloud-only
LightLEDdisplayed1for a LED set to 100 % via the V2C app — the cloud serialiseslight_ledas a 0.0-1.0 fraction ("1.000000"= 100 %), but both the LAN/write/LightLEDkeyword and the Number entity use the 0-100 % integer convention. Added a × 100 multiplier in_CLOUD_TO_LAN_MULTIPLIERSso1.000000is surfaced as100 %,0.500000as50 %, etc. - Number/Switch/Select writes silently dropped in cloud-only mode (4G) — every Number entity setter (
Intensity,MinIntensity,MaxIntensity,ContractedPower,LightLED), every Switch (Dynamic,Locked,Paused,LogoLED,Timer,PauseDynamic), and two Selects (ChargeMode,DynamicPowerMode) previously calledasync_write_keyworddirectly, so the LAN write step raisedV2CLocalApiErrorfor cloud-only devices and no cloud fallback ever fired. Promoted_async_route_local_or_cloud+_is_cloud_only_devicefrom private__init__.pyhelpers to publiclocal_api.py(async_route_local_or_cloud,is_cloud_only_device) and wired every entity setter through the router. For controls without a V2C Cloud endpoint (LightLED,ContractedPower,Timer,PauseDynamic,ChargeMode,DynamicPowerMode), the setter passescloud_call=Noneand the router raises a clear, user-facingHomeAssistantErrorin cloud-only mode instead of silently losing the write. Discovered an undocumented/device/logo_ledcloud endpoint via direct probe and exposed it asasync_cloud_set_logo_led, so the LogoLED switch is now controllable from cloud-only mode too.