Async Python client for the Ratio EV Charging cloud API
Project description
aioratio
Async Python client for the Ratio EV Charging cloud API.
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.
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 UserSettings dataclass (recommended) or a pre-formed camelCase dict. |
charge_schedule(serial) |
ChargeSchedule |
|
set_charge_schedule(serial, schedule) |
None |
|
solar_settings(serial) |
SolarSettings |
|
set_solar_settings(serial, settings) |
None |
Accepts SolarSettings dataclass (recommended) or a pre-formed camelCase dict. |
grant_upgrade_permission(serial, firmware_update_job_ids) |
None |
Approve queued firmware update jobs by id. Raises ValueError if the list is empty. |
diagnostics(serial) |
ChargerDiagnostics |
Read-only system info: hardware/firmware versions, network status (WiFi/ethernet), backend connectivity, OCPP status. |
ocpp_settings(serial) |
InstallerOcppSettings |
Read installer OCPP settings including is_change_allowed metadata per field. |
set_ocpp_settings(serial, settings) |
None |
Write OCPP settings (enabled, cpms, charge_point_identifier). Accepts InstallerOcppSettings or a flat dict. |
cpms_options(serial) |
list[CpmsConfig] |
List operator-provided CPMS choices. Returns [] on 403/error (operator may not expose this). |
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. Also raised when calling methods on a closed client.RatioRateLimitError-- HTTP 429.RatioConnectionError-- network/timeout failure.RatioError-- common base.
All public methods raise RatioApiError("client is closed") after close() has been called. User-supplied path segments (serial numbers, user IDs, vehicle IDs) are percent-encoded to prevent path traversal.
Token storage:
JsonFileTokenStore(path)— atomic writes, mode 0o600.MemoryTokenStore()— for tests / ephemeral CLI use.TokenStore(ABC) — implementload()/save()/clear()to plug into other backends (e.g. HA'sStorehelper).
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— publicRatioClientasync context manager._transport.py-- private aiohttp transport. 401 -> invalidate_access_token -> retry once.auth.py--CognitoSrpAuthdriver: USER_SRP first-login, ConfirmDevice + UpdateDeviceStatus, REFRESH_TOKEN_AUTH (with rotation handling), DEVICE_SRP_AUTH second-login. Refresh serialised viaasyncio.Lock. Accepts atimeoutparameter (default 30s) for Cognito HTTP calls. Exposesinvalidate_access_token()for callers to force a refresh on the next access.srp.py— pure-Python Cognito SRP-6a (3072-bit MODP,Caldera Derived KeyHKDF, Java-style timestamp). Uses JavaBigInteger.toByteArray()(padHex) semantics throughout — variable length with0x00sign byte when high bit set. BothUserSrpandDeviceSrpvariants.token_store.py—TokenBundledataclass +TokenStoreABC +MemoryTokenStore+JsonFileTokenStore(atomic write).models/— dataclasses derived from APK DTOs:Charger,ChargerOverview,ChargerStatus,UserSettings,ChargeSchedule,SolarSettings,InstallerOcppSettings,CpmsConfig,ChargerDiagnostics,Session,SessionHistoryPage,Vehicle, plus nested types. All have afrom_dict()classmethod tolerant of unknown fields.InstallerOcppSettings.to_dict()emits the flat PUT shape;ChargerDiagnosticsis read-only (noto_dict).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 +NewDeviceMetadata→ConfirmDevice(with our generated device verifier) →UpdateDeviceStatus(remembered). - Subsequent login on a remembered device: PASSWORD_VERIFIER →
DEVICE_SRP_AUTH→DEVICE_PASSWORD_VERIFIER→ tokens. REFRESH_TOKEN_AUTHdoes not rotate the refresh token (server keeps the existing one; library handles either case).compute_xuses the formatpoolName + username + ":" + password(no colon between pool and user). Allsalt,verifier,A,B,u,Suse JavaBigInteger.toByteArraybyte semantics, not fixed-length padding.start-chargerequiresstartCommandParametersto 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
Tests are run with pytest. 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 against the Ratio Solar charger. Field nullability across some models is best-effort against the decompiled APK; flag mismatches as issues.
set_solar_settingsHTTP 502 (#9): Fixed. The cloud PUT endpoint expects flat nullable integers ("sunOffDelayMinutes": 5), not the nested value objects returned by GET ({"value": 5, "isChangeAllowed": true, ...}).SolarSettings.to_dict()now emits the correct PUT shape. Smoke-tested against the live API.ScheduleSlotandChargeSchedulenow have explicitto_dict()methods for controlled serialisation.UpperLowerLimitSetting.to_dict()now echoes back the full raw GET shape (used byUserSettings.to_dict()).
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
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 aioratio-0.7.0.tar.gz.
File metadata
- Download URL: aioratio-0.7.0.tar.gz
- Upload date:
- Size: 51.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83b89a25046c36f3690a7ee983054d611abba8375a829d07e2d57b989f713798
|
|
| MD5 |
49815613d3e6c7da6e901126443c24db
|
|
| BLAKE2b-256 |
3dd79c9b9c36bb90501563a32ccad92912a2ab4525ceb9d34f4686ce36dd69f9
|
Provenance
The following attestation bundles were made for aioratio-0.7.0.tar.gz:
Publisher:
publish.yml on aaearon/aioratio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aioratio-0.7.0.tar.gz -
Subject digest:
83b89a25046c36f3690a7ee983054d611abba8375a829d07e2d57b989f713798 - Sigstore transparency entry: 1427608304
- Sigstore integration time:
-
Permalink:
aaearon/aioratio@db0450ff552d89647c4a1d952e3c12fe808c4f37 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/aaearon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db0450ff552d89647c4a1d952e3c12fe808c4f37 -
Trigger Event:
push
-
Statement type:
File details
Details for the file aioratio-0.7.0-py3-none-any.whl.
File metadata
- Download URL: aioratio-0.7.0-py3-none-any.whl
- Upload date:
- Size: 34.9 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 |
c01c6aab062055c299c3c1fa83cbebb794a2a6a7fdd19368cb8881f0769f9bc6
|
|
| MD5 |
901cc16f6b286b78e9c79e8f52821d66
|
|
| BLAKE2b-256 |
df8088da7e3363145bf4bcfc4a9718db06aa70f327129bd9e04cdef3cd54c867
|
Provenance
The following attestation bundles were made for aioratio-0.7.0-py3-none-any.whl:
Publisher:
publish.yml on aaearon/aioratio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aioratio-0.7.0-py3-none-any.whl -
Subject digest:
c01c6aab062055c299c3c1fa83cbebb794a2a6a7fdd19368cb8881f0769f9bc6 - Sigstore transparency entry: 1427608512
- Sigstore integration time:
-
Permalink:
aaearon/aioratio@db0450ff552d89647c4a1d952e3c12fe808c4f37 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/aaearon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db0450ff552d89647c4a1d952e3c12fe808c4f37 -
Trigger Event:
push
-
Statement type: