Skip to content

Releases: samuelebistoletti/HomeAssistant-V2C-Cloud

v1.3.3

17 Jun 20:44

Choose a tag to compare

[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.2 commit: its release pipeline failed on the
security gate (a fresh batch of aiohttp test-only advisories landed) before
any tag or artifact was published. This release folds in that audit fix.

Changed

  • ruff 0.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 <10 retained).
  • pytest-asyncio >=1.3.0 → >=1.4.0 (requirements_test.txt, Dependabot #32, upper bound <2 retained).
  • codecov/codecov-action v6.0.1 → v7.0.0 (Dependabot #37) — removes an internal license-compliance workflow; no input/output changes for callers.
  • gitleaks/gitleaks-action v2 → 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/hassfest pinned SHA refreshed to upstream master (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 — aioresponses 0.7.8 (its latest release) does not pass the stream_writer kwarg 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 — the security.yaml test-deps audit now ignores all 11 (runtime audit stays --strict with zero ignores), and the matching Dependabot alerts are dismissed as tolerable_risk — until aioresponses ships a 3.14-compatible release. The durable fix (migrating off aioresponses) is tracked in the backlog.

v1.3.1

09 Jun 21:17

Choose a tag to compare

[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

  • ruff 0.15.13 → 0.15.16 (requirements.txt, Dependabot #26) — lint + format gates re-verified clean against the bumped pin.
  • softprops/action-gh-release v2.5.0 → v3.0.0 in tag-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 the actions/stale v10 bump shipped in 1.3.0.

v1.3.0

09 Jun 20:13

Choose a tag to compare

[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.x1.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 to 1.2.x is
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, and GET /device/connected. Each is exercised by dedicated tests in tests/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/me response 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-facing HomeAssistantError in 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.
  • ChargeMode select (monophasic / threephasic / mixed) and LightLED number (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_reported synthesises a LAN-shaped payload from the cloud /reported document, including seven additional numeric keys plus device metadata (ID, firmware, MAC, SSID, IP via the wifi_info blob). 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_led cloud endpoint (undocumented, live on firmware 2.4.6) – the LogoLED switch is now controllable from cloud-only mode via async_cloud_set_logo_led.
  • Spanish UI translation – the previously incomplete Spanish support is now a full translations/es.json (235 keys), at parity with en.json and it.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-restore flag 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 via anchore/sbom-action) attached to every release, and a reusable security.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_entry is version-aware via a _MIGRATIONS registry; SCHEMA_VERSION = 2 in const.py is the single source of truth for both config_flow.VERSION and the migration target. Legacy entries are translated: the cloud-only sentinel (fallback_ip == "" / "0.0.0.0") becomes cloud_only: True; a non-empty fallback_ip paired with fallback_device_id becomes a one-record cached_pairings; an initial_pairings snapshot wins over the single-device pair. Legacy keys are dropped from entry.data.
  • Cloud-only mode is encoded as entry.data["cloud_only"]: bool instead of the empty-string fallback_ip sentinel. 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_keyword validates the keyword against a documented WRITEABLE_KEYWORDS whitelist 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 new DEFAULT/MIN/MAX_LOCAL_INTERVAL bounds.
  • HA minimum version stays in hacs.json ("homeassistant": "2025.4.0") — hassfest rejects min_ha_version in manifest.json as an unknown field.

Fixed

  • ChargeMode Select showed "Unknown" in LAN mode – it is write-enabled but absent from /RealTimeData; the integration's read-only-keyword augmentation covered LogoLED + LightLED but missed ChargeMode. The local coordinator now also fetches /read/ChargeMode in parallel and the Select displays the live value.
  • Local refresh interval option was not persisted – the options flow returned async_create_entry(title="", data={}), and HA uses that data argument to overwrite entry.options, so the field always snapped back to 30 s. Fix: pass the populated options dict to async_create_entry(data=new_options).
  • Connection-type radio labels were not translated – the schema used a hard-coded vol.In({label: ...}) dict. Migrated to SelectSelector(translation_key="connection_type") with a top-level selector block in strings.json and 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/currentstatecharge capture):
    • VoltageInstallation reported a spurious ~77 V – the cloud voltage field is a small internal signal; the real mains/installation voltage is carried by cp_level (e.g. 248 on a 230 V EU install). Remapped cp_level → VoltageInstallation and dropped the misleading voltage mapping.
    • ContractedPower was off by 100× – the cloud encodes contract_power as W/100 ("7" = 700 W = 0.7 kW), but the Number entity divides by 1000 to render kW. Added a _CLOUD_TO_LAN_MULTIPLIERS table that multiplies ContractedPower by 100 during synthesis.
    • LightLED showed 1 for a LED set to 100 % – the cloud serialises light_led as 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_keyword directly, so the LAN write raised V2CLocalApiError and no cloud fallback fired. Every setter now routes through async_route_local_or_cloud.
    • device_identifier / firmware_version / wifi_ssid / wifi_ip sensors were "Unknown" – the synthesis loop coerced every value via float(str(raw)) and silently dropped non-numeric ones. Added a string-passthrough path plus inline parsing of the cloud's wifi_info JSON blob.
  • UI strings referencing the removed fallback_ip step / field were cleaned up across strings.json and the en/it/es translations; the connection_type and init descriptions now mention auto-discovered per-device IPs and automatic LAN→cloud routing.
  • requirements.txt: pyyaml is now version-pinned (>=6.0,<7). requirements_test.txt: all packages have upper bounds for reproducible CI builds.
  • .ruff.toml: target-version aligned with CI (py312); test/script directories get a targeted per-file-ignores so the strict select = ALL rule set no longer drowns the lint output. The whole tree is ruff format-clean and both ruff check and ruff format --check gate every PR and release.
  • CI hardening: persist-credentials: false on every read-only checkout; pip-audit --strict; bandit artifact retention pinned to 30 days; hacs.yaml + hassfest.yaml push triggers scoped to branches: [main] (daily cron retained).

Hardening (post-beta review pass)

  • _normalise_pairings deduplicated into a single _pairings.py module imported by both config_flow.py and __init__.py, eliminating drift between the two persistence paths....
Read more

v1.3.0-beta.2

24 May 19:39

Choose a tag to compare

v1.3.0-beta.2 Pre-release
Pre-release

[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/me response 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 as entry.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"]: bool instead of the empty-string fallback_ip sentinel. 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_ip step 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 VERSION bumped to 2. async_migrate_entry translates legacy entries: the cloud-only sentinel (fallback_ip == "" / "0.0.0.0") becomes cloud_only: True; a non-empty fallback_ip paired with fallback_device_id becomes a one-record cached_pairings; a non-empty initial_pairings snapshot wins over the single-device pair. Legacy keys are dropped from entry.data.

Fixed (LAN data accuracy)

  • ChargeMode Select 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, ChargeMode is write-enabled but absent from /RealTimeData; the integration's _READ_ONLY_KEYWORDS augmentation set covered LogoLED + LightLED but missed ChargeMode. Added so the local coordinator now fetches /read/ChargeMode in parallel with the other read-only keys and the Select displays the live value.

Fixed (options flow)

  • Local refresh interval option was not persisted — the options flow saved the value via async_update_entry(options=...) but then returned async_create_entry(title="", data={}), and HA's OptionsFlow uses the data argument of async_create_entry to OVERWRITE entry.options. Result: the field always snapped back to 30 s after closing the form. Fix: pass the populated options dict to async_create_entry(data=new_options). Regression test added.
  • UI strings referencing the removed fallback_ip step / field were cleaned up across strings.json and the en/it/es translations: the cold-start "cloud offline" step is gone, the options-flow field label is gone, the connection_type and init descriptions 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.yml attaches hass_core_dev to a new external v2c-dev user-defined bridge network with the homeassistant alias, and .devcontainer.json joins the dev container to the same network via runArgs: ["--network=v2c-dev"]. Inside the dev container HA resolves as http://homeassistant:8123 (host browser keeps using http://localhost:8123). An initializeCommand creates the network and runs docker compose up -d homeassistant before the dev container is built, so the service is ready when VS Code finishes attaching. HA uses restart: "no": developer-initiated, not auto-restarted on host reboot.
  • Node.js LTS in the dev container via the ghcr.io/devcontainers/features/node:1 devcontainer feature, exposing npx for the context7 and memory MCP servers configured in .mcp.json.
  • mcp-proxy installed by scripts/setup (uv tool install --force git+https://github.com/sparfenyuk/mcp-proxy). Provides the Streamable-HTTP/SSE bridge that fronts the Home Assistant /api/mcp endpoint 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.example documents every variable — GH_TOKEN, V2C_CLOUD_API_KEY, V2C_LOCAL_IP, HASS_TOKEN — including where to obtain it and which tool consumes it. scripts/setup appends an idempotent block to ~/.bashrc that sources .env.dev with set -a, so every variable is exported into every interactive shell on a single edit. Contributors fill in the file once at first checkout. The legacy HASS_TOKEN-only block in ~/.bashrc is auto-removed by scripts/setup on 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 /reported keys (min_car_int, min_car_int_fb, max_car_int, max_car_int_fb, light_led, logo_led, contract_power and its contractedpower/contracted_power aliases) and gains a string-field passthrough for device metadata (device_id/deviceIdID, versionFirmwareVersion, macMAC). The cloud's wifi_info JSON blob is parsed inline so the SSID and active IP populate the corresponding sensors. Verified end-to-end against a real /device/reported + /device/currentstatecharge capture (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 /reported or /currentstatecharge payloads — are gated via available=False when 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 to SelectSelector(translation_key="connection_type") and added a top-level selector block to strings.json and 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 / ContractedPower were Unknown in cloud-only mode — they read via local_key but the cloud /reported keys (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_ip were Unknown in cloud-only mode — the synthesis loop did float(str(raw)) on every value and silently dropped non-numeric ones. Added a string-passthrough path plus inline parsing of the cloud's wifi_info JSON blob to populate SSID / IP.
  • Cloud-only VoltageInstallation reported a spurious ~77 V instead of the actual mains voltage — the synthesis path mapped the cloud voltage field (a small internal signal, e.g. 0.077350) onto VoltageInstallation and then scaled it × 1000 via the kV detection heuristic. The cloud's real mains/installation voltage is carried by cp_level (e.g. 248 on a 230 V EU install); remapped cp_levelVoltageInstallation and dropped the misleading voltage mapping. The _detect_cloud_scale heuristic still inspects voltage as a magnitude signal but no longer surfaces it as a voltage reading.
  • Cloud-only ContractedPower displayed the wrong value (off by 100x) — the cloud encodes contract_power as 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_MULTIPLIERS table that multiplies ContractedPower by 100 during synthesis, so cloud "7" becomes 700 in the synthetic /RealTimeData and the entity renders the expected 0.7 kW.
  • Cloud-only LightLED displayed 1 for a LED set to 100 % via the V2C app — the cloud serialises light_led as a 0.0-1.0 fraction ("1.000000" = 100 %), but both the LAN /write/LightLED keyword and the Number entity use the 0-100 % integer convention. Added a × 100 multiplier in _CLOUD_TO_LAN_MULTIPLIERS so 1.000000 is surfaced as 100 %, 0.500000 as 50 %, 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 called async_write_keyword directly, so the LAN write step raised V2CLocalApiError for cloud-only devices and no cloud fallback ever fired. Promoted _async_route_local_or_cloud + _is_cloud_only_device from pri...
Read more

v1.3.0-beta.1

19 May 12:07

Choose a tag to compare

v1.3.0-beta.1 Pre-release
Pre-release

[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

23 Mar 20:54

Choose a tag to compare

[1.1.6] - 2026-03-24

Fixed

  • Reauth completion shows wrong message – the reauth config flow called async_update_reload_and_abort without an explicit reason= 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". The reason="reauth_successful" argument is now passed explicitly.
  • Raw reconfigure_successful key shown in UI – the config.abort.reconfigure_successful key was missing from strings.json and 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-abc123 used in tests/conftest.py triggered the generic-api-key Gitleaks rule on the full git history scan, causing the security CI job to fail. Added .gitleaks.toml with a stopwords entry for test-api-key and a path allowlist for the tests/ directory; the fixture is intentionally non-functional and has never been a real credential.

Changed

  • Removed unused cannot_connect error key – the config.error.cannot_connect key was declared in strings.json and both translation files but was never emitted by config_flow.py. When the V2C Cloud is unreachable during initial setup the flow redirects to the fallback_ip step rather than showing a connection error. The dead key has been removed from all three files.

v1.1.5

23 Mar 20:35

Choose a tag to compare

[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-Remaining header – successful responses from the V2C Cloud include a RateLimit-Remaining header 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

19 Mar 08:50

Choose a tag to compare

[1.1.4] - 2026-03-19

Security

  • Clear-text logging of sensitive data eliminated – three CodeQL alerts (py/clear-text-logging-sensitive-data) resolved: headers and body removed from the HTTP debug log in _request (the apikey header was already masked but the dict comprehension still constituted a taint path); params masking made case-insensitive; fallback_device_id (derived from entry.data, which contains the API key) removed from startup warning messages in __init__.py; exception objects replaced with type(err).__name__ to prevent accidental credential leakage via exception messages.

v1.1.3

19 Mar 08:21

Choose a tag to compare

[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) have is_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 reject is_link_local addresses.
  • API key / authorization headers masked in debug logs – the apikey and authorization headers 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 ConfigEntryNotReady and 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_bool synced with coerce_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_state now fires the reported, rfid and version API calls concurrently via asyncio.gather instead of sequentially, reducing per-device cloud poll latency by up to 2 × on fast connections.
  • Rate-limit retry jitter – backoff after a 429 response now includes a small random component to avoid simultaneous retries across multiple devices.
  • Type annotations cleaned up – entity modules use V2CClient instead of Any for the client parameter; device_info now declares a DeviceInfo return type; DataUpdateCoordinator imports follow the TYPE_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

12 Mar 15:21

Choose a tag to compare

[1.1.2] - 2026-03-12

CI

  • actions/checkout upgraded to v6 – bumped from v2 (in hacs.yaml, hassfest.yaml) and v4 (in tests.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/action pinned to v22.5.0 – replaced the mutable @main floating tag with a commit-pinned reference (d556e736...) for supply-chain security.