Releases: samuelebistoletti/HomeAssistant-V2C-Cloud
v1.3.3
[1.3.3] - 2026-06-17
Maintenance release. No functional change to the integration — runtime code,
entities and the config-entry schema are identical to 1.3.1. Dependency,
test-tooling and CI-action updates only. Full suite (474 tests), the ruff
lint + format gates and the pip-audit dependency audit re-verified green
against every bumped pin.
Supersedes the unreleased
1.3.2commit: its release pipeline failed on the
securitygate (a fresh batch of aiohttp test-only advisories landed) before
any tag or artifact was published. This release folds in that audit fix.
Changed
ruff0.15.16 → 0.15.17 (requirements.txt, Dependabot #30) — lint + format gates re-verified clean.pip>=26.1.1 → >=26.1.2 (requirements.txt, Dependabot #35).pytest>=9.0.3 → >=9.1.0 (requirements_test.txt, Dependabot #33, upper bound<10retained).pytest-asyncio>=1.3.0 → >=1.4.0 (requirements_test.txt, Dependabot #32, upper bound<2retained).codecov/codecov-actionv6.0.1 → v7.0.0 (Dependabot #37) — removes an internal license-compliance workflow; no input/output changes for callers.gitleaks/gitleaks-actionv2 → v3.0.0 (Dependabot #34) — runtime Node 20 → Node 24, no input/output/behaviour changes; clears the Node 20 deprecation ahead of GitHub's 2026-09-16 runner removal.home-assistant/actions/hassfestpinned SHA refreshed to upstreammaster(Dependabot #36).
Security
- 11 aiohttp advisories now affect the pinned test dependency
aiohttp<3.14(requirements_test.txt): the original CVE-2026-34993 / CVE-2026-47265 plus a fresh batch (CVE-2026-50269 and CVE-2026-54273…54280), every one fixed only in aiohttp 3.14.0/3.14.1. The upgrade to aiohttp 3.14 (Dependabot #31) was verified to break the entire test suite —aioresponses0.7.8 (its latest release) does not pass thestream_writerkwarg that aiohttp 3.14 made mandatory. End users are unaffected: the integration ships"requirements": []; the patched aiohttp is provided by Home Assistant core at runtime. The advisories are scoped to the test harness — thesecurity.yamltest-deps audit now ignores all 11 (runtime audit stays--strictwith zero ignores), and the matching Dependabot alerts are dismissed astolerable_risk— untilaioresponsesships a 3.14-compatible release. The durable fix (migrating offaioresponses) is tracked in the backlog.
v1.3.1
[1.3.1] - 2026-06-09
Maintenance release. No functional change to the integration — runtime code,
entities and the config-entry schema are identical to 1.3.0. Dependency and
CI-tooling updates only.
Changed
ruff0.15.13 → 0.15.16 (requirements.txt, Dependabot #26) — lint + format gates re-verified clean against the bumped pin.softprops/action-gh-releasev2.5.0 → v3.0.0 intag-and-release.yaml(Dependabot #29). v3.0.0 moves the action runtime from Node 20 to Node 24 (no input/API changes); this clears the Node 20 deprecation ahead of the 2026-06-16 GitHub Actions enforcement, matching theactions/stalev10 bump shipped in1.3.0.
v1.3.0
[1.3.0] - 2026-06-09
Stable release. Promotes the 1.3.0 line to general availability after the
public beta window (beta.1 2026-05-19 → beta.3 2026-06-01) closed with no
regressions reported. No code changes relative to 1.3.0-beta.3.
This is the cumulative 1.2.x → 1.3.0 change set, developed and validated
across the three pre-releases listed below. The integration gains full V2C
Cloud endpoint coverage, automatic multi-charger discovery, a LAN-vs-cloud
control router, and substantially better cloud-only (4G) behaviour.
Breaking (auto-migrated): the config entry schema is upgraded from v1 to
v2 on first load — no user action required. Rolling back to1.2.xis
not supported; see Upgrade / downgrade notes below.
Added
- Full V2C Cloud endpoint coverage – 10 new client methods cover every previously missing public endpoint:
start_charge,pause_charge,intensity,locked,dynamic,chargefvmode,max_car_int,min_car_int,denka/max_power, andGET /device/connected. Each is exercised by dedicated tests intests/test_cloud_endpoints_1_3.py. - 10 new Home Assistant services:
start_charge,pause_charge,set_charge_intensity,set_locked,set_dynamic,set_fv_mode,set_max_car_intensity,set_min_car_intensity,set_denka_max_power,get_connected_status. The first five use the LAN-vs-cloud router; the photovoltaic and Denka calls are cloud-only. - Automatic multi-charger discovery – an account with N chargers is fully supported. Every charger's LAN IP is sourced from the cloud
/pairings/meresponse at runtime and a normalised snapshot is persisted on every successful refresh (entry.data["cached_pairings"]). During a cloud outage every previously-seen charger stays addressable via its last-known IP; when the cloud returns, the cache is reconciled (added devices appear, removed devices disappear). Replaces the single user-typed fallback IP. - Smart LAN-vs-cloud router (
local_api.async_route_local_or_cloud) – control commands shared between LAN (/write/) and cloud (/device/*) prefer the LAN path and transparently fall back to the cloud endpoint when LAN is unreachable or the device is cloud-only. Covers start/pause charge, intensity, locked, dynamic. Controls with no cloud endpoint (LightLED,ContractedPower,Timer,PauseDynamic,ChargeMode,DynamicPowerMode) raise a clear, user-facingHomeAssistantErrorin cloud-only mode instead of silently dropping the write. - Editable connection type – an options-flow
Local (Wi-Fi)/Cloud only (4G)toggle switches modes post-setup and triggers an automatic integration reload. ChargeModeselect (monophasic / threephasic / mixed) andLightLEDnumber (0-100 %) entities.- User-configurable local refresh interval (5-300 s, default 30 s) via the Reconfigure dialog (
CONF_LOCAL_UPDATE_INTERVAL). Cloud-only (4G) devices keep their fixed cadence and ignore the option. Applied live via an entry update listener — no reload required. - Expanded cloud-only entity coverage –
_build_realtime_from_reportedsynthesises a LAN-shaped payload from the cloud/reporteddocument, including seven additional numeric keys plus device metadata (ID, firmware, MAC, SSID, IP via thewifi_infoblob). The set of entities showing real data in cloud-only mode grows from ~12 to ~20+. Structurally LAN-only entities (ReadyState,SignalStatus,Timer,ChargeMode,DynamicPowerMode,PauseDynamic) now correctly advertise as Unavailable in cloud-only mode instead of the misleading "Unknown". - Discovered
/device/logo_ledcloud endpoint (undocumented, live on firmware 2.4.6) – the LogoLED switch is now controllable from cloud-only mode viaasync_cloud_set_logo_led. - Spanish UI translation – the previously incomplete Spanish support is now a full
translations/es.json(235 keys), at parity withen.jsonandit.json. - Live smoke-test script (
scripts/live_smoke_test.py) – exercises every read endpoint and issues safe no-op writes against a real Trydan plus the V2C Cloud, then verifies snapshot/restore. Requires the explicit--confirm-restoreflag and is never run in CI. - CI / supply-chain hardening – Python 3.12/3.13/3.14 matrix, ruff lint + format gates, Codecov coverage reporting (
.coveragerc),concurrency:blocks on every workflow, pip caching,.github/dependabot.yml(weekly grouped Actions + pip updates), SBOM (SPDX-JSON + CycloneDX-JSON viaanchore/sbom-action) attached to every release, and a reusablesecurity.yaml(workflow_call) so the release pipeline gates on the exact same SAST / dependency-audit / secret-scan jobs as PRs.
Changed
- Config entry schema v1 → v2 (auto-migrated).
async_migrate_entryis version-aware via a_MIGRATIONSregistry;SCHEMA_VERSION = 2inconst.pyis the single source of truth for bothconfig_flow.VERSIONand the migration target. Legacy entries are translated: the cloud-only sentinel (fallback_ip == ""/"0.0.0.0") becomescloud_only: True; a non-emptyfallback_ippaired withfallback_device_idbecomes a one-recordcached_pairings; aninitial_pairingssnapshot wins over the single-device pair. Legacy keys are dropped fromentry.data. - Cloud-only mode is encoded as
entry.data["cloud_only"]: boolinstead of the empty-stringfallback_ipsentinel. The first-setup fallback-IP step is gone — initial setup now requires the cloud to be reachable to capture the pairings list, consistent with the integration's name and removing a single point of failure. - SSRF guard deduplicated into
custom_components/v2c_cloud/_net.py::validate_private_ip(private + not loopback + not link-local + not unspecified), replacing four scattered copies with a single tested helper. async_write_keywordvalidates the keyword against a documentedWRITEABLE_KEYWORDSwhitelist to reduce the LAN write/SSRF surface and prevent accidental misuse from automations.- Local API constants consolidated in
const.py:LOCAL_HTTP_TIMEOUT,LOCAL_MAX_RETRIES,LOCAL_RETRY_BACKOFF,LOCAL_WRITE_RETRY_DELAY,CLOUD_ONLY_UPDATE_INTERVAL, and the newDEFAULT/MIN/MAX_LOCAL_INTERVALbounds. - HA minimum version stays in
hacs.json("homeassistant": "2025.4.0") — hassfest rejectsmin_ha_versioninmanifest.jsonas an unknown field.
Fixed
ChargeModeSelect showed "Unknown" in LAN mode – it is write-enabled but absent from/RealTimeData; the integration's read-only-keyword augmentation coveredLogoLED+LightLEDbut missedChargeMode. The local coordinator now also fetches/read/ChargeModein parallel and the Select displays the live value.Local refresh intervaloption was not persisted – the options flow returnedasync_create_entry(title="", data={}), and HA uses thatdataargument to overwriteentry.options, so the field always snapped back to 30 s. Fix: pass the populated options dict toasync_create_entry(data=new_options).- Connection-type radio labels were not translated – the schema used a hard-coded
vol.In({label: ...})dict. Migrated toSelectSelector(translation_key="connection_type")with a top-levelselectorblock instrings.jsonand all translation files. Italian "Intensità Light LED" renamed to "Intensità LED". - Cloud-only data accuracy (validated end-to-end against a live firmware-2.4.6
/device/reported+/device/currentstatechargecapture):VoltageInstallationreported a spurious ~77 V – the cloudvoltagefield is a small internal signal; the real mains/installation voltage is carried bycp_level(e.g.248on a 230 V EU install). Remappedcp_level → VoltageInstallationand dropped the misleadingvoltagemapping.ContractedPowerwas off by 100× – the cloud encodescontract_poweras W/100 ("7"= 700 W = 0.7 kW), but the Number entity divides by 1000 to render kW. Added a_CLOUD_TO_LAN_MULTIPLIERStable that multipliesContractedPowerby 100 during synthesis.LightLEDshowed1for a LED set to 100 % – the cloud serialiseslight_ledas a 0.0-1.0 fraction; the LAN keyword and entity use 0-100 % integers. Added a × 100 multiplier.- Number / Switch / Select writes were silently dropped in cloud-only (4G) – every setter called
async_write_keyworddirectly, so the LAN write raisedV2CLocalApiErrorand no cloud fallback fired. Every setter now routes throughasync_route_local_or_cloud. device_identifier/firmware_version/wifi_ssid/wifi_ipsensors were "Unknown" – the synthesis loop coerced every value viafloat(str(raw))and silently dropped non-numeric ones. Added a string-passthrough path plus inline parsing of the cloud'swifi_infoJSON blob.
- UI strings referencing the removed
fallback_ipstep / field were cleaned up acrossstrings.jsonand the en/it/es translations; theconnection_typeandinitdescriptions now mention auto-discovered per-device IPs and automatic LAN→cloud routing. requirements.txt:pyyamlis now version-pinned (>=6.0,<7).requirements_test.txt: all packages have upper bounds for reproducible CI builds..ruff.toml:target-versionaligned with CI (py312); test/script directories get a targetedper-file-ignoresso the strictselect = ALLrule set no longer drowns the lint output. The whole tree isruff format-clean and bothruff checkandruff format --checkgate every PR and release.- CI hardening:
persist-credentials: falseon every read-only checkout;pip-audit --strict; bandit artifact retention pinned to 30 days;hacs.yaml+hassfest.yamlpush triggers scoped tobranches: [main](daily cron retained).
Hardening (post-beta review pass)
_normalise_pairingsdeduplicated into a single_pairings.pymodule imported by bothconfig_flow.pyand__init__.py, eliminating drift between the two persistence paths....
v1.3.0-beta.2
[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 pri...
v1.3.0-beta.1
[1.3.0-beta.1] - 2026-05-19
Pre-release for early-adopter testing — same code as the unreleased 1.3.0
candidate (see entry below), but published with prerelease: true so HACS
only proposes the update to users who opted into "Show beta versions".
This is a HACS-side gate; install via Settings → Devices & Services →
HACS → V2C Cloud → ⋮ → Redownload, then pick 1.3.0-beta.1 from the
version dropdown. Promotion to 1.3.0 stable will follow after a quiet
period if no regressions are reported.
See the full set of changes in the [1.3.0] section below.
v1.1.6
[1.1.6] - 2026-03-24
Fixed
- Reauth completion shows wrong message – the reauth config flow called
async_update_reload_and_abortwithout an explicitreason=argument, which defaults to"reconfigure_successful". As a result, after a successful re-authentication the UI displayed "Reconfiguration was successful" instead of "Re-authentication was successful". Thereason="reauth_successful"argument is now passed explicitly. - Raw
reconfigure_successfulkey shown in UI – theconfig.abort.reconfigure_successfulkey was missing fromstrings.jsonand both translation files (en.json,it.json). Home Assistant rendered the raw key string instead of the localised message after a successful reconfigure flow. The key has been added to all three files. - Gitleaks CI false positive on test fixture – the placeholder API key
test-api-key-abc123used intests/conftest.pytriggered thegeneric-api-keyGitleaks rule on the full git history scan, causing the security CI job to fail. Added.gitleaks.tomlwith astopwordsentry fortest-api-keyand a path allowlist for thetests/directory; the fixture is intentionally non-functional and has never been a real credential.
Changed
- Removed unused
cannot_connecterror key – theconfig.error.cannot_connectkey was declared instrings.jsonand both translation files but was never emitted byconfig_flow.py. When the V2C Cloud is unreachable during initial setup the flow redirects to thefallback_ipstep rather than showing a connection error. The dead key has been removed from all three files.
v1.1.5
[1.1.5] - 2026-03-23
Fixed
- Self-reinforcing rate-limit loop eliminated – when the V2C Cloud API returned HTTP 429, the integration retried the same request up to three times before raising the error. Each retry consumed an additional call from an already-exhausted daily quota (1 000 calls/day), causing the budget to be burned at up to 3× the normal rate. Once the limit was hit, the quota was spent in its entirety on retries alone, making recovery impossible until the next daily reset. HTTP 429 responses are now raised immediately without any retry; the coordinator's exponential back-off (see below) handles pacing instead.
- Coordinator keeps hammering the API when rate-limited – after a 429, the cloud polling interval was not adjusted, so the integration kept attempting requests every 120 s regardless of how many times it had been rejected. The poll interval now doubles on each rate-limit cycle (
120 s → 240 s → 480 s → 600 s), capped at 10 minutes. The interval automatically resets to the normal cadence on the first successful response, so no manual intervention is required once the daily quota window resets.
Changed
- Proactive pacing via
RateLimit-Remainingheader – successful responses from the V2C Cloud include aRateLimit-Remainingheader indicating how many calls are left in the current daily window. When this value drops below 150, the integration stretches the polling interval proportionally (reserving 50 calls for user-initiated commands), so the remaining budget lasts a full 24 hours in the worst case. This prevents the quota from being exhausted mid-day on days with heavy polling or frequent HA restarts.
v1.1.4
[1.1.4] - 2026-03-19
Security
- Clear-text logging of sensitive data eliminated – three CodeQL alerts (
py/clear-text-logging-sensitive-data) resolved:headersandbodyremoved from the HTTP debug log in_request(theapikeyheader was already masked but the dict comprehension still constituted a taint path);paramsmasking made case-insensitive;fallback_device_id(derived fromentry.data, which contains the API key) removed from startup warning messages in__init__.py; exception objects replaced withtype(err).__name__to prevent accidental credential leakage via exception messages.
v1.1.3
[1.1.3] - 2026-03-19
Security
- SSRF guard now blocks link-local addresses – on Python 3.11+ link-local IPs (
169.254.x.x) haveis_private=True, so the previous guard (is_private AND NOT is_loopback) incorrectly allowed them through. All three guard sites (config_flow._probe_local_api,local_api.async_write_keyword,local_api._async_fetch_local_data) now also rejectis_link_localaddresses. - API key / authorization headers masked in debug logs – the
apikeyandauthorizationheaders are now logged as***in all request debug output, preventing credential leakage in log files.
Fixed
- Startup failure when cloud is rate-limited in Cloud+LAN mode – if the V2C Cloud returned HTTP 429 during initial coordinator startup, the integration raised
ConfigEntryNotReadyand retried indefinitely at a very short interval. It now treats the rate-limit error as a transient failure and backs off to the normal poll cadence (#6). - OCPP server URL, date fields and RFID tag data tightened – malformed values are now rejected early with clear validation errors before reaching the API.
_normalize_boolsynced withcoerce_bool– the API client's bool parser now recognises"enabled"/"disabled"tokens, matching the entity-layer helper and preventing silent mismatches on firmware variants that report boolean fields as strings.
Changed
- Parallel cloud fetch per device –
_fetch_single_device_statenow fires thereported,rfidandversionAPI calls concurrently viaasyncio.gatherinstead of sequentially, reducing per-device cloud poll latency by up to 2 × on fast connections. - Rate-limit retry jitter – backoff after a
429response now includes a small random component to avoid simultaneous retries across multiple devices. - Type annotations cleaned up – entity modules use
V2CClientinstead ofAnyfor the client parameter;device_infonow declares aDeviceInforeturn type;DataUpdateCoordinatorimports follow theTYPE_CHECKING-only pattern where the type is annotation-only.
Testing
- Test suite expanded to 350 tests – ten new test modules cover all entity types (binary sensor, sensor, switch, number, select, button), config flow SSRF guard, local API and device-state gathering. The full suite runs without a live Home Assistant instance or charger.
v1.1.2
[1.1.2] - 2026-03-12
CI
actions/checkoutupgraded to v6 – bumped from v2 (inhacs.yaml,hassfest.yaml) and v4 (intests.yaml,security.yaml,codeql.yaml,tag-and-release.yaml) to v6, resolving the Node.js 20 deprecation warning ahead of the June 2026 enforcement deadline.hacs/actionpinned to v22.5.0 – replaced the mutable@mainfloating tag with a commit-pinned reference (d556e736...) for supply-chain security.