warp-charger
Async Python client for the local API of Tinkerforge WARP wallboxes and the WARP Energy Manager.
It keeps one WebSocket open for push updates (no polling) and sends commands over HTTP. It powers the Tinkerforge WARP Home Assistant integration, but has no Home Assistant dependency.
- Full state mirror: every API topic (
evse/state,meters/0/values, …) in one dict, kept current by the device's push stream - Topic and connection callbacks
- Automatic reconnect with backoff; survives device reboots
- HTTP Digest authentication (via aiohttp's
DigestAuthMiddleware), HTTP and HTTPS - Helpers: enum tables, meter value IDs, UID encoding, NFC scan detection
Requires Python 3.13+ and aiohttp 3.12+.
Install
pip install warp-charger
Quick start
import asyncio
import aiohttp
from warp_charger import WarpClient
from warp_charger.const import CHARGER_STATE
async def main() -> None:
async with aiohttp.ClientSession() as session:
client = WarpClient(session, "http://warp3-AbCd.local")
task = asyncio.create_task(client.run()) # reconnect loop
await client.wait_ready(timeout=30) # first full state dump received
state = client.states["evse/state"]
print("Charger:", CHARGER_STATE[state["charger_state"]])
client.subscribe("evse/state", lambda payload: print("now", payload["charger_state"]))
await client.put("evse/external_current", {"current": 16000}) # 16 A
await asyncio.sleep(60)
await client.stop()
await task
asyncio.run(main())
Authentication
If the web interface has a login, put the credentials into the session. The client does not handle them itself:
session = aiohttp.ClientSession(middlewares=(aiohttp.DigestAuthMiddleware("user", "password"),))
client = WarpClient(session, "https://warp3-AbCd.local")
The device does not validate digest nonces, so a captured login can be replayed on plain HTTP. Prefer HTTPS when a password is set. For the self-signed certificate, pass a connector with ssl=False.
API
WarpClient(session, base_url, **tunables)
| Member | Description |
|---|---|
await run() |
Connect and keep reconnecting until stop(). Raises WarpAuthError on HTTP 401. |
await stop() |
Close the WebSocket and end run(). Idempotent. |
await wait_ready(timeout) |
Wait for the first complete state dump. Raises WarpConnectionError on timeout, WarpAuthError on 401. |
states |
Read-only mapping topic -> payload of the last complete state. |
connected |
True while a connection with a complete dump is up. |
subscribe(topic, cb) |
cb(payload) on every update of topic. Returns an unsubscribe function. |
subscribe_all(cb) |
cb(topic, payload) on every update. |
subscribe_connection(cb) |
cb(True) after each complete dump, cb(False) on disconnect. |
await get(path) |
GET /path, returns parsed JSON. |
await put(path, payload=None) |
PUT /path with a JSON body (None sends null). Calls are serialised. |
kicked_repeatedly |
True if the device dropped the connection ≥ 3 times in 10 min (see limits). |
Optional keyword tunables are reconnect_delay (5 s), max_reconnect_delay (60 s), jitter, stable_after, going_away_delay, receive_timeout (15 s), request_timeout (10 s), kick_threshold, kick_window and clock.
Callbacks run in the event loop. Exceptions in callbacks are logged, never propagated.
After a reconnect, the new dump replaces states in one step. Then every subscribed topic is called once, then the connection callbacks.
Exceptions
WarpError is the base class of:
WarpAuthError: HTTP 401.WarpConnectionError: network errors, timeouts, unexpected status.WarpCommandError: HTTP 400;str(err)is the device's error message.
Helpers
warp_charger.constCHARGER_STATE,IEC61851_STATE,ERROR_STATE,DC_FAULT_STATE: int → key.CHARGE_MODE, plusCHARGE_MODE_DEFAULT(4 is a state only, never selectable).DURATION_LIMITSfor charge-limit durations.METER_VALUES: meter value id →MeterValue(key, unit, digits), for decodingmeters/N/valueswithmeters/N/value_ids.INT32_MAX.
warp_charger.uid:encode(int) -> stranddecode(str) -> int. These convert between the numeric UID from mDNS TXT records and the device UID string shown ininfo/name(base58, or zbase32 for large UIDs).warp_charger.NfcScanDetector:feed(nfc/seen_tags payload)returns the newly scanned(tag_type, tag_id)pairs. It uses the firmware web UI's 2 s threshold.
Device API in a nutshell
The topics and payloads are the device's MQTT/HTTP API, documented in the WARP API reference.
- Read: everything arrives over
/ws. On connect the device sends a full dump of all topics, then only changes, at most once per second per topic. - Write:
put("<topic>_update", {...})for configuration and state topics, e.g.evse/auto_start_charging_update.put("<command>")for commands, e.g.evse/start_chargingorreboot.- The device answers 200 even for some silent no-ops; for example, start/stop without a vehicle is ignored.
- Limits:
- At most 5 WebSocket clients (web UI tabs count). When full, the device drops the client that answered pings least recently.
- The ESP32 serves one request at a time, so avoid parallel or high-rate requests.
Never GET a command path. The firmware executes payload-less commands on GET, so GET /reboot reboots the device. get() is meant for state topics only.
Recording a fixture
scripts/record_dump.py writes one state dump as JSONL (one {"topic", "payload"} per line). It anonymises MACs, IPs, SSIDs, tag ids, user names and coordinates. It is read-only: it only opens the WebSocket.
uv run python scripts/record_dump.py warp3-AbCd.local --replace-uid -o warp3.jsonl
uv run python scripts/record_dump.py 192.168.1.20 -u admin -p secret --https --insecure
Development
uv sync
uv run pytest
uv run ruff check . && uv run ruff format --check .
The tests run against a fake device on an aiohttp test server and never touch real hardware.
Release: bump version in pyproject.toml, then tag vX.Y.Z. CI publishes to PyPI via trusted publishing.
License
MIT
Metadata
Release files for warp-charger 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| warp_charger-0.1.0.tar.gz | 21.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| warp_charger-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.8 kB
Release files / warp_charger-0.1.0.tar.gz
| Download URL | warp_charger-0.1.0.tar.gz |
|---|---|
| Size | 21.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cdbe4b52835089b0c8545578bc63f470777dabd025cd524e125d21d946d7e890
|
|
BLAKE2b-256 checksum How to use checksums |
bc1fffe839ee661521905adce11f2cbb7836136d73668033d062e9083a93be27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / warp_charger-0.1.0-py3-none-any.whl
| Download URL | warp_charger-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
00f1976daa71e97cafafaca1d6ef3e1c8816ca993e030cac9177ef9e1f971f22
|
|
BLAKE2b-256 checksum How to use checksums |
1f8855ba2c1031dfbebfe26f88cbfdefa7157a82ae2583de0b4d67fb8af27873
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log