Skip to main content

Async Python client for the Ratio EV Charging cloud API

Project description

aioratio

Async Python client for the Ratio EV Charging cloud API.

PyPI Python License: MIT

What this is

A standalone, async, dependency-light Python client for the cloud API behind the official Ratio EV Charging mobile app. Authenticates via AWS Cognito (USER_SRP_AUTH + DEVICE_SRP_AUTH), persists device and refresh tokens, exposes typed dataclass models for chargers, sessions, settings, and vehicles. Designed to be embedded in Home Assistant integrations or used directly from scripts.

This is an unofficial library. Not affiliated with Ratio.

Why a separate library

The previous community Home Assistant integration (RowanRamasray/Ratio_Ev_Charger) bundled boto3 and warrant inside the custom component, fused protocol logic with homeassistant.core, and only covered part of the API surface. aioratio extracts the protocol layer into a pure-async, HA-free library so any Python consumer can use it and the HA integration becomes a thin wrapper.

Install

pip install aioratio

Requires Python 3.11+. Only runtime dep is aiohttp>=3.9.

Quick start

import asyncio
import aiohttp
from aioratio import RatioClient, JsonFileTokenStore

async def main() -> None:
    async with aiohttp.ClientSession() as session:
        client = RatioClient(
            email="you@example.com",
            password="...",
            token_store=JsonFileTokenStore("ratio-tokens.json"),
            session=session,
        )
        async with client:
            chargers = await client.chargers_overview()
            for c in chargers:
                print(c.serial_number, c.cloud_connection_state)

            await client.start_charge(chargers[0].serial_number,
                                      vehicle_id="<your-vehicle-id>")

asyncio.run(main())

The token store persists access/refresh tokens and Cognito device metadata (DeviceKey, DeviceGroupKey, DevicePassword) so subsequent runs go through the DEVICE_SRP_AUTH fast path without re-prompting.

Public API

RatioClient is the entry point. Construct with email + password (and optionally a TokenStore), use as an async context manager.

Method Returns Notes
login() None Force a fresh login. Idempotent.
user_id() str Cognito sub claim from the IdToken.
chargers() list[Charger] Bare charger registry.
chargers_overview() list[ChargerOverview] Aggregate status of all chargers (single call).
charger_overview(serial) ChargerOverview Single-charger full state.
start_charge(serial, vehicle_id=None) None vehicle_id is optional but recommended; if omitted, the client sends an empty startCommandParameters object.
stop_charge(serial) None
user_settings(serial) UserSettings
set_user_settings(serial, settings) None Accepts dataclass or dict; converts snake_case → camelCase automatically.
charge_schedule(serial) ChargeSchedule
set_charge_schedule(serial, schedule) None
solar_settings(serial) SolarSettings
session_history(...) SessionHistoryPage Paginated; pass next_token to continue.
vehicles() list[Vehicle]
add_vehicle(vehicle) Vehicle
remove_vehicle(vehicle_id) None

Errors:

  • RatioAuthError — credentials invalid, refresh expired, unsupported Cognito challenge.
  • RatioApiError — non-2xx response from the REST API. HTTP details are included in the exception message; they are not exposed as status/body attributes.
  • RatioRateLimitError — HTTP 429.
  • RatioConnectionError — network/timeout failure.
  • RatioError — common base.

Token storage:

  • JsonFileTokenStore(path) — atomic writes, mode 0o600.
  • MemoryTokenStore() — for tests / ephemeral CLI use.
  • TokenStore (ABC) — implement load() / save() / clear() to plug into other backends (e.g. HA's Store helper).

Architecture

+-------------------+       +-----------------+       +------------+
|   RatioClient     |  -->  | _CloudTransport |  -->  | aiohttp    |
|  (public façade)  |       |  (private)      |       +------------+
+-------------------+       +-----------------+
        |                           |
        |                           v 401 retry once via auth
        v
+-------------------+       +-----------------+
| CognitoSrpAuth    |  -->  | TokenStore      |
| USER_SRP +        |       | (Memory/JSON)   |
| DEVICE_SRP +      |       +-----------------+
| REFRESH_TOKEN     |
+-------------------+
        |
        v
+-------------------+
| srp.py            |
| Cognito SRP-6a    |
+-------------------+

Files under src/aioratio/:

  • client.py — public RatioClient async context manager.
  • _transport.py — private aiohttp transport. 401 → refresh-then-retry once.
  • auth.pyCognitoSrpAuth driver: USER_SRP first-login, ConfirmDevice + UpdateDeviceStatus, REFRESH_TOKEN_AUTH (with rotation handling), DEVICE_SRP_AUTH second-login. Refresh serialised via asyncio.Lock.
  • srp.py — pure-Python Cognito SRP-6a (3072-bit MODP, Caldera Derived Key HKDF, Java-style timestamp). Uses Java BigInteger.toByteArray() (padHex) semantics throughout — variable length with 0x00 sign byte when high bit set. Both UserSrp and DeviceSrp variants.
  • token_store.pyTokenBundle dataclass + TokenStore ABC + MemoryTokenStore + JsonFileTokenStore (atomic write).
  • models/ — dataclasses derived from APK DTOs: Charger, ChargerOverview, ChargerStatus, UserSettings, ChargeSchedule, SolarSettings, Session, SessionHistoryPage, Vehicle, plus nested types. All have a from_dict() classmethod tolerant of unknown fields.
  • exceptions.py, const.py.

Cognito specifics (notes for maintainers and LLMs)

The Ratio user pool uses USER_SRP_AUTH with no client secret. Pool eu-west-1_mH4sFjLoF, client 78cs05mc0hc5ibqv1tui22n962, region eu-west-1, API base https://8q4y72fwo3.execute-api.eu-west-1.amazonaws.com/prod — all centralised in const.py.

Confirmed-live behaviours:

  • First login flow: InitiateAuth(USER_SRP_AUTH)RespondToAuthChallenge(PASSWORD_VERIFIER) → tokens + NewDeviceMetadataConfirmDevice (with our generated device verifier) → UpdateDeviceStatus(remembered).
  • Subsequent login on a remembered device: PASSWORD_VERIFIER → DEVICE_SRP_AUTHDEVICE_PASSWORD_VERIFIER → tokens.
  • REFRESH_TOKEN_AUTH does not rotate the refresh token (server keeps the existing one; library handles either case).
  • compute_x uses the format poolName + username + ":" + password (no colon between pool and user). All salt, verifier, A, B, u, S use Java BigInteger.toByteArray byte semantics, not fixed-length padding.
  • start-charge requires startCommandParameters to always be present in the body (empty object is accepted; missing object is not).

Development

git clone https://github.com/aaearon/aioratio
cd aioratio
uv venv --python 3.11        # or python3.11 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest -q

77 mocked tests + 11 SRP vector tests. Live smoke against a real account lives in scripts/smoke.py (reads creds from ../.env).

The SRP vector tests are intentionally self-consistent and do not catch encoding bugs against Cognito — see commit 0283fb5 for two real bugs that slipped past them. Live smoke is the source of truth.

Status

Early. Used in production by home-assistant-ratio. Field nullability across some models is best-effort against the decompiled APK; flag mismatches as issues.

License

MIT.

Disclaimer

Unofficial. Reverse-engineered from the public Android APK and observed Cognito traffic. Use at your own risk; the API is not contractually stable. No affiliation with Ratio.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aioratio-0.2.0.tar.gz (38.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aioratio-0.2.0-py3-none-any.whl (28.5 kB view details)

Uploaded Python 3

File details

Details for the file aioratio-0.2.0.tar.gz.

File metadata

  • Download URL: aioratio-0.2.0.tar.gz
  • Upload date:
  • Size: 38.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aioratio-0.2.0.tar.gz
Algorithm Hash digest
SHA256 86fe1f2b8448b7a336636aacdbd0aaa533c0483d5b06aba9f2968fd5dd5eba56
MD5 ea5a55995e5bf46c9d49e818ffd6f432
BLAKE2b-256 8185f52a92fab737ff7bdaa4fe99bb6e88074636d07348c215148b6ae6f62e6e

See more details on using hashes here.

Provenance

The following attestation bundles were made for aioratio-0.2.0.tar.gz:

Publisher: publish.yml on aaearon/aioratio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aioratio-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: aioratio-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 28.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aioratio-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a3a4c863ad4fddfdd5d49e6b6d3dc23c503d8cff690d9dc81d782bdd7dd82bc5
MD5 d902db81284ab52eef682fa6dbc49c5c
BLAKE2b-256 1ceac51210075792383a06b8b5cec69eb4a32b88cf312ab0230c3d3f5d3bfd25

See more details on using hashes here.

Provenance

The following attestation bundles were made for aioratio-0.2.0-py3-none-any.whl:

Publisher: publish.yml on aaearon/aioratio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page