Async Python client for the A Better Routeplanner (ABRP) / Iternio telemetry API
Project description
aioabrp
Async Python client for the A Better Routeplanner
(ABRP) / Iternio telemetry API. The library mirrors ABRP's API points 1:1 and
has no Home Assistant dependency: a stateless request/response AbrpClient
(garage, vehicle catalog, one-shot telemetry snapshot) and a resilient
TelemetryStream (server-sent events with reconnect/backoff/watchdog) that
delivers extracted, typed metric values — never raw wire dicts — to consumer
callbacks. Authentication is injected: the consumer owns the token lifecycle
and hands the library a fresh access token via AbstractAuth (or the
fixed-token StaticAuth convenience).
Installation
pip install aioabrp
Requires Python ≥ 3.14. The only runtime dependency is aiohttp.
Usage
A runnable standalone example (fill in a real partner API key and access token before running — the calls below hit the live API):
import asyncio
import aiohttp
from aioabrp import (
AbrpClient,
ConnectionEvent,
StaticAuth,
Telemetry,
TelemetryStream,
)
API_KEY = "your-iternio-partner-api-key"
ACCESS_TOKEN = "your-abrp-access-token"
def on_update(vehicle_id: int, telemetry: Telemetry) -> None:
for metric, mv in telemetry.items():
print(f"vehicle {vehicle_id}: {metric} = {mv.value!r} (time={mv.time})")
def on_connection_change(event: ConnectionEvent) -> None:
print(f"connection: {event.state.name} (reason={event.reason})")
async def main() -> None:
async with aiohttp.ClientSession() as session:
auth = StaticAuth(ACCESS_TOKEN)
client = AbrpClient(session, API_KEY, auth)
vehicles = await client.async_get_vehicles()
for vehicle in vehicles:
print(f"{vehicle.vehicle_id}: {vehicle.name or vehicle.vehicle_model}")
stream = TelemetryStream(
session,
API_KEY,
auth,
vehicle_ids=[v.vehicle_id for v in vehicles],
on_update=on_update,
on_connection_change=on_connection_change,
)
await stream.start()
try:
await asyncio.sleep(600) # stream telemetry for ten minutes
finally:
await stream.stop()
if __name__ == "__main__":
asyncio.run(main())
Get your own API key
The api_key constructor argument is an Iternio partner API key, not a
per-user credential. If you are building your own consumer, obtain your own
key from Iternio — see the
Iternio Telemetry API documentation
or contact Iternio for a partner API key. The per-user access token comes
from your own auth flow and is handed to the library through AbstractAuth.
Consumer contracts
These behaviors are pinned by the test suite; consumers may rely on them.
Callbacks
on_updateandon_connection_changeare synchronous callbacks, delivered on the event loop that ranstart(). They must be non-blocking — a slow callback stalls the stream (and every other stream on the same loop).- A raising callback is logged with its traceback and swallowed; the stream continues (frame loss beats stream death).
Connection events
CONNECTEDfires on the first frame of a connection, not on the HTTP connect — a connection that opens but never produces a frame keeps reading as down until proven healthy.DISCONNECTEDis steady-state, not exceptional: the ABRP server unilaterally closes idle streams at roughly 200 s and the stream reconnects with backoff. Do not treat aDISCONNECTEDevent as an outage. Events are status reports, not strict state transitions — aDISCONNECTEDMAY arrive before the firstCONNECTED(for example when the very first connection attempt fails).AUTH_FAILEDis terminal: the stream stops itself and will not retry. The consumer decides whether/when to restart with fresh credentials.- A transient (non-
AbrpAuthError) failure from the token getter emits noConnectionEventat all (debug log only) — no connection attempt was made, so consumers keep seeing the last known state during a token-endpoint outage.
Lifecycle
stop()is idempotent and cancel-based (never a graceful join). No callbacks fire afterstop()returns.stop()propagates the caller's own cancellation: if you wrap it inasyncio.wait_for(stream.stop(), timeout)and that times out, the resultingTimeoutErrormeans the stream may still be running — the stop was interrupted, not completed.start()afterstop()restarts the stream.
Monotonicity gate
Each stream keeps one piece of state: a per-(vehicle_id, Metric) map of
the last adopted block timestamp.
- A block whose
timeis strictly older than the last adopted time for that(vehicle, metric)is dropped. - An equal-time block re-emits by design: every reconnect re-delivers a full-state snapshot with unchanged block times, and consumers rely on that backfill.
- A time-less block (missing/malformed/naive
time) is adopted and clears the gate for that metric — it carries no ordering claim, so it also stops gating subsequent values. - A block whose
timeis in the future is rewritten to "now" before delivery (and before gating), so a clock-skewed upstream stamp can neither be delivered to the consumer nor become an unreachable high-water mark that silently stalls the metric.AbrpClient.async_get_current_telemetryapplies the same clamp (it does not gate). - Known limitation: a legitimately backdated server correction (an older timestamp that really is a newer truth) is suppressed for the stream's lifetime.
The gate can be pre-warmed across process restarts: pass
TelemetryStream(..., seed=Mapping[int, Mapping[Metric, datetime]]) — a
per-vehicle map of metric to its last wire-block time (e.g. derived from the
consumer's last persisted snapshot) — and the stream seeds its high-water marks
from those times, each clamped not-future. Only times are needed; no typed
values. The clock is the aioabrp._clock._now seam (the monkeypatch target in
tests).
Logging
Enable the aioabrp logger at DEBUG for triage. Connect/disconnect and
reasons log at INFO, watchdog stalls at WARNING, per-frame activity at
DEBUG. Frames are logged as keys and sizes only — frame bodies, header
values, and tokens (including GPS coordinates and other PII) are never
logged. Pass name= to TelemetryStream to prefix its log lines when
running multiple streams.
Development
This project uses uv:
uv sync
uv run ruff format . && uv run ruff check . && uv run mypy && uv run pytest
Note: the locked dev environment constrains aiohttp<3.14 because the
latest aioresponses release is incompatible with aiohttp ≥ 3.14
(aioresponses#289);
the published runtime dependency stays unpinned.
Releases
Commits follow Conventional Commits;
there is no CHANGELOG file — release notes are generated from the commit
history by git-cliff (cliff.toml) and published
as GitHub Releases. Pushing a v* tag runs the quality gate, builds the
sdist + wheel, verifies the tag matches aioabrp.__version__, publishes to
PyPI via Trusted Publishing
(OIDC), and creates the GitHub Release.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aioabrp-0.1.0.tar.gz.
File metadata
- Download URL: aioabrp-0.1.0.tar.gz
- Upload date:
- Size: 93.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06364d7c93767c81bf6184a19ad3e78dbc07e3cbbc70b724c967c08dcd2e93bd
|
|
| MD5 |
3d81877f644f27ed060051614c71ce2b
|
|
| BLAKE2b-256 |
92bf7ecddd54a2d02a2bfa4a842739c77fcc7e1ba0d24a2108104c2841396ac1
|
Provenance
The following attestation bundles were made for aioabrp-0.1.0.tar.gz:
Publisher:
release.yml on mtandersson/aioabrp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aioabrp-0.1.0.tar.gz -
Subject digest:
06364d7c93767c81bf6184a19ad3e78dbc07e3cbbc70b724c967c08dcd2e93bd - Sigstore transparency entry: 1800868161
- Sigstore integration time:
-
Permalink:
mtandersson/aioabrp@6ca1486ca53602edaccc9aec3689d756d1bbb184 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mtandersson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6ca1486ca53602edaccc9aec3689d756d1bbb184 -
Trigger Event:
push
-
Statement type:
File details
Details for the file aioabrp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: aioabrp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11716bfcd9b3cd4c71af622ecaa0fa6f661a9e9eca90323dfdf4ddd659c2b70a
|
|
| MD5 |
bf85e225766326236acd4b0d6fc2eed0
|
|
| BLAKE2b-256 |
e4288cf6b243dc742c180c4eb5e8f7276199b41513fcc04fa56fa3cb03dee3ae
|
Provenance
The following attestation bundles were made for aioabrp-0.1.0-py3-none-any.whl:
Publisher:
release.yml on mtandersson/aioabrp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aioabrp-0.1.0-py3-none-any.whl -
Subject digest:
11716bfcd9b3cd4c71af622ecaa0fa6f661a9e9eca90323dfdf4ddd659c2b70a - Sigstore transparency entry: 1800868584
- Sigstore integration time:
-
Permalink:
mtandersson/aioabrp@6ca1486ca53602edaccc9aec3689d756d1bbb184 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mtandersson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6ca1486ca53602edaccc9aec3689d756d1bbb184 -
Trigger Event:
push
-
Statement type: