A local Home Assistant integration for KEBA KeContact P40 wallboxes, using the charger's HTTPS REST API. No cloud connection or Modbus polling is required.
- 33 monitoring entities: charging and connection status, phase currents and voltages, power, lifetime/session energy, session timing and diagnostics.
- Local discovery, manual setup, automatic JWT renewal and certificate pinning.
- Charging status kept separate from an open session; exact session energy.
- Four optional controls, disabled by default; candidate validation is described below.
Tested hardware: one P40 with REST API 2.5.0. P40 Pro and other firmware versions are not yet physically verified. This is an independent community project, not an official KEBA product.
Domain: keba_p40.
Version 0.2.5. Requires Home Assistant 2026.9 or newer (Python 3.14).
33 existing monitoring entities retain their unique IDs. This release adds a control-result diagnostic sensor. Four additional controls are registered but disabled by default. Setup, discovery and polling never send controls. No Modbus polling or writes are used.
This release includes the production control receipts and passive meter
continuity instrumentation, alongside fixes for past validFrom dates, null
numberOfPhases, and the Home Assistant NumberEntity.value naming collision.
Per-action START/STOP receipts survive subsequent current-limit commands. An
ambiguous control is recorded and followed by physical reads; the API never replays
an uncertain command. Equal meter readings still publish fresh observation bounds.
Wallbox START/STOP/availability commands use separate fresh TLS connections;
profile writes and read-only traffic keep the existing shared connection pool.
The reference installation completed overnight charging to its 80% target on 2026-10-18, with 31.1068 kWh delivered, automatic closure and no controller fault. Earlier current-limit and automatic-stop trials exercised the production code. A bounded extra STOP/START sequence in the separate charging controller remains documented; this is not a claim that every vehicle, outage or firmware is validated. The original disconnected-control root cause is not proven.
Regression tests cover Home Assistant entities/registry, supported profile shapes, control receipts, passive meter continuity, current-feedback races and real loopback TLS. Tests use synthetic credentials and never contact a physical charger.
- Make a Home Assistant backup. Update while charging and the charging session are both off, with no imminent scheduled charging window.
- Download 0.2.5 in HACS, retaining the existing KEBA integration/config entry.
- Fully restart Home Assistant; an integration reload alone is insufficient for reliably replacing Python modules already imported by other integrations.
- Check that the current limit is numeric, existing entity IDs and enabled controls are retained, and any separate charging controller has no fault. Do not issue a START merely to verify installation.
HACS replaces custom_components/keba_p40. This release includes the stability
patches used by the reference installation, so updating that patched 0.2.4 install
retains those fixes. Local EV Smart Charging code, helpers, scripts, templates and
dashboards are separate; this package neither installs nor updates them. No climate
feature is included. Restart discards in-memory receipts/observation history; a
separate controller must reconcile physical state and require fresh evidence.
For rollback, restore the complete pre-update backup and restart while idle.
Public 0.2.4 lacks these fixes and is not an equivalent rollback for a patched install.
Monitoring reference: one physical KEBA P40 (device identifiers withheld), package
1.5.1+cert-meter, REST API 2.5.0, BaseIO 5.0.1, meter 3.0.1, safety 3.1.3.
Authenticated read-only audit: 2026-10-05. Public fixtures use the audited response structure with synthetic identifiers,
energy values, session IDs, dates and durations. The private raw audit is not included. The original 0.2.4 release had no physical control validation. The newer
stability observations above describe the controls tested since then.
Read-only monitoring remained available on the reference P40, with changing phase
voltage measurements received in Home Assistant during consecutive observations.
The reported package version was still 1.5.1+cert-meter and REST API version
2.5.0. This observation verifies current monitoring connectivity; it does not
validate charging controls or the charger's behavior when a vehicle is plugged in.
The owner reported receiving replacement user and installer passwords, a recovery key and a PIN from KEBA. Whether these had been activated on the charger was not verified, and neither replacement login nor recovery was attempted. Continued monitoring does not establish whether existing credentials will remain valid after activation or token renewal. No passwords, recovery keys or PINs are published.
Guest charging without a Home Assistant plan has not been physically verified. This integration does not implement a plan requirement or automatically block charging on plug-in. Such a policy depends on the charger's own authorization/start settings and any separately configured Home Assistant automation. A working monitoring connection alone does not verify that policy.
Core PR #173179 proposes a
built-in P40 integration. This repository is a separate implementation with its
own physical audit and tests; it is not a testing distribution of that PR.
Both use the domain keba_p40. Do not install both implementations together.
A future transition will require checking config-entry and entity compatibility.
- Back up Home Assistant and any existing
custom_components/keba_p40directory. - Extract the ZIP. Copy its
custom_components/keba_p40directory into/config/custom_components/keba_p40. Keep the folder namekeba_p40. - Restart Home Assistant. Open Settings → Devices & services → Add integration → KEBA KeContact P40, or accept its discovery card.
- Enter
p40.localor192.0.2.14, HTTPS port8443, normal KEBA username and password. These are stored as standard HA config-entry credentials. JWTs stay in memory. HA config backups contain credentials; protect those backups. - Use the device certificate SHA-256 fingerprint with CA validation disabled for a pinned self-signed certificate, or trust its certificate in HA. Disabling CA validation without a fingerprint provides encryption without server identity verification. No certificate is guessed or bundled into this package.
- Confirm the model, serial, energy, state, phase measurements and versions before enabling any controls. All 33 monitoring entities are enabled by default.
- To test a control, open its entity settings and enable that specific entity.
Follow
PHYSICAL_TEST_PLAN.md. No controls are automatically called on enable.
Both HA and the charger must be able to reach each other on the local network.
Port 502 is irrelevant to this integration. p40.local requires working mDNS.
Add https://github.com/Rschmidt79/home-assistant-keba-p40 in HACS →
Custom repositories → Integration. Download the latest release, then restart
Home Assistant. The existing config entry and current entities are reused.
Do not delete the integration or create a duplicate entry.
Remove the old Ich-h4lt repository from HACS management first so it cannot overwrite this integration. Keep the HA config entry/device. Back up your HA configuration before switching repositories. Future releases appear as HACS updates. This is a custom repository, not an entry in the default HACS catalog.
See ENTITIES.md for all 37 entities and CONTROLS.md for request contracts.
sessionActive means an open session, not charging. Charging is state=CHARGING.
The observed reference had an open BLOCKED session, READY_FOR_CHARGING and 0 W.
Actual currents and offered PWM current are separate. Total energy is a lifetime
total_increasing kWh sensor suitable for the Energy Dashboard. Session energy
uses exact energyConsumed, has state_class=total and last_reset=session_start;
it is never total_increasing across sessions. energyConsumedInKwh is not used.
Times and duration use milliseconds; session duration includes waiting.
Missing data and API failures produce unavailable entities, never synthetic zero.
Serial is stable identity; alias is metadata. No vehicle identity inference exists.
The Session token ID sensor exposes tokenId from the selected session. The
KEBA REST schema defines this as the Token.id that authorized that session. It
is an opaque identifier: case and leading zeros are preserved. No separate token
list request, authorization change or charging command is made.
Attributes include session_id, session_start, session_end, session_status
and active_session. After closure the latest session's ID can remain visible
with active_session: false; it is historical, not a fresh chip swipe. Missing or
invalid IDs become unavailable and never inherit the previous session's chip.
The sensor follows existing session polling (20 seconds or plug/session changes),
so it is not an instantaneous reader event and does not report every swipe.
Automations may map an assigned chip to an explicit vehicle selection, but the chip identifies the assigned tag, not the connected vehicle or its battery SOC. Use a fresh active session and its session identity when binding a selection; never use a historical tag as proof for a new connection. This integration does not add vehicle mappings, bypass charging plans, or synthesize battery data.
Chip IDs are available locally in HA states/history but redacted from integration diagnostics. Do not publish real IDs in issues or test fixtures. Session chip values are schema-supported; representation on each firmware requires live verification.
Wallbox: 5 s; sessions: 20 s or session/plug transition; Modbus/load-management config
and editable baseline profile: 120 s; device/version/alias: 6 h. The Modbus configs
are read via REST, not Modbus. Authentication and reads are serialized by an
async lock. Expired access tokens refresh with POST /v2/jwt/refresh; rejected
refreshes fall back to login. Controls obtain a fresh login token under the same
lock, so non-fresh-token restrictions are respected. No uncertain write is replayed.
No username, password, JWT, Authorization header or raw API response is logged or
returned by diagnostics. Diagnostic output also redacts identifying information.
Current control edits only a simple all-day ChargePointMaxProfile at stack level zero, with exactly one wallbox. It preserves the existing item except its current, refuses validity windows, complex schedules and explicit phase settings, and does not create OCPP-sourced profiles or change OCPP configuration. Other higher priority profiles may further restrict effective PWM; a cap is not a promise of actual current. A missing/complex profile leaves the number unavailable; monitoring continues.
Pause/resume is intentionally not exposed. REST 2.5.0 has no documented dedicated pause/resume endpoint. Zero-current profile suspension and same-session resumption are not established by our REST audit/specification. Stop/start and out-of-order availability must not silently substitute for session-preserving pause/resume. This requested capability remains pending authoritative semantics and physical validation. No phase-switching control is implemented.
Replace the integration directory, restart HA and retain the existing config entry.
No migration or unique-ID change is needed: existing IDs remain {serial}_{key}.
The display name drops “Read Only”. The four new controls start disabled. Do not
install this alongside Ich-h4lt's integration: both use domain keba_p40 and cannot
coexist under that domain. Entity IDs may retain their prior names after upgrades.
- Connection failure: check IP/hostname, LAN/VLAN routing, port 8443 and certificate.
- Authentication: reauthenticate with KEBA credentials; never paste JWTs into logs.
- Number unavailable: inspect the baseline profile with authorized GET; complex or phase-specific profiles are intentionally protected. HTTP 404 means unsupported.
- Control error: read physical state before trying again. A timeout can occur after the charger accepted a command. The integration does not retry the write.
- Availability enabled but no charging: vehicle, authorization and charging profiles still determine whether charging actually starts.
- Use HA Reload for retry. Unload clears credentials/tokens from the API instance and removes coordinator listeners; the shared HA HTTP session remains owned by HA.
Install requirements_test.txt into a Python 3.14 environment. Run pytest --cov=custom_components.keba_p40,
ruff check . and ruff format --check .. pytest disables network sockets by default.
See VALIDATION.md for actual results, and PHYSICAL_TEST_PLAN.md for the pending
real-device control tests. No automatic pricing, PV/load balancing, phase switching,
firmware, reboot, unlock, OCPP changes or Modbus writes are implemented.
Old Ich-h4lt config entries omitted username because they always used admin. Version 0.2.1 migrates those entries locally, fills the documented old admin username, preserves credentials/serial, and consolidates old options into data. It does not delete the entry or change its identity. Replace the folder and restart HA; the existing entry is reused. Missing passwords trigger reauthentication rather than a KeyError. No physical control request is part of migration.
After successful setup and registration of all 33 monitoring entities, this release removes only the 25 known legacy entities from the old implementation, matched by exact old unique ID, platform, entity domain and config entry. Current unique IDs/HA entity IDs remain unchanged. Other devices, config entries and unknown prefixed entities are untouched. Cleanup is idempotent.
Before upgrading, update any dashboards/automations using the old entities and make a Home Assistant backup. No automatic reference remapping is attempted. The cleanup uses HA's entity registry API, not direct .storage editing. The missing DOMAIN import reported in the locally installed cleanup is fixed and protected by registry/setup tests and ruff F821 checks.
Change the manifest version, run tests/ruff/hassfest, commit and push to main,
then create a matching GitHub release such as v0.2.5. HACS downloads the release
source tree. A tag alone is not a release. Active GitHub Actions in .github/workflows/checks.yaml run pytest, ruff,
hassfest and HACS validation. Tests block network sockets and no CI job contacts
a real charger.