Instruction file imported from uniclaw-labs/uniclaw (
.github/instructions/providers.instructions.md). Copyright stays with the author.
Vehicle Provider Guidelines
Interface Contract
Every provider must implement VehicleProvider(ABC) from src/uniclaw/provider.py.
Required members:
# Properties
provider_type: str # e.g. "kia", "bmw", "demo"
authenticated: bool
otp_pending: bool
otp_destination: str | None
login_error: str | None
# Methods
async def authenticate() -> bool
async def do_login(credentials: dict) -> None
async def list_vehicles() -> list[VehicleInfo]
async def get_status(vehicle_id: str) -> dict
async def lock(vehicle_id: str) -> None
async def unlock(vehicle_id: str) -> None
async def start_climate(vehicle_id: str) -> None
async def stop_climate(vehicle_id: str) -> None
async def start_charge(vehicle_id: str) -> None
async def stop_charge(vehicle_id: str) -> None
Adapter Pattern
adapter.py wraps the OEM library/client into VehicleProvider.
client.py holds low-level HTTP or library calls (use aiohttp for custom clients).
Protocol-Specific Rules
MD5 for protocol signatures (BYD, CUPRA): use hashlib.md5(..., usedforsecurity=False).
Never add # noqa: S324 — S324 is not enabled so the directive triggers RUF100.
OTP flow: set otp_pending = True after do_login() triggers an OTP send.
Clear it and set authenticated = True once the OTP is verified.
Tesla dual-provider pattern: Tesla registers two providers simultaneously when both sets of credentials are present:
tesla_fleet—FleetAPIAuthenticator(PKCE OAuth2,TESLA_CLIENT_ID+TESLA_CLIENT_SECRET)tesla_personal— TeslaPy (TESLA_USERNAME+TESLA_PASSWORD)
Fleet API auth routes live at /providers/tesla_fleet/auth/* in routes/providers.py.
The one-time partner registration must be run before user tokens can call Fleet API endpoints.
See src/providers/tesla/README.md for the full Tesla Developer Portal walkthrough.
Auth Manager Test Fixtures
When a test uses AuthManager.__new__ to bypass __init__, all instance attributes
set in __init__ will be missing. Always add them explicitly in the fixture:
@pytest.fixture
def auth_mgr():
mgr = AuthManager.__new__(AuthManager)
mgr._encryption_key = None # required — not set by __new__
mgr.some_other_attr = ...
return mgr
Normalisation Helpers
Use src/providers/_utils.py for unit/type coercion:
from providers._utils import safe_float, safe_int, km_to_miles
Never do raw float(x) on API responses — they can be None or strings.
Registering a New Provider
src/providers/<slug>/__init__.py— export the provider classsrc/providers/<slug>/adapter.py—VehicleProviderimplementationsrc/providers/<slug>/client.py— low-level API calls (if custom client)src/uniclaw/registry.py— register slug → provider class mappingsrc/uniclaw/config.py— addSettingsfields for credentials.env.example— add commented credential keystests/providers/test_<slug>.py— unit testsdesktop/src/pages/SetupWizard.tsx— add toPROVIDER_GROUPS
Cross-Provider Contract Tests
tests/providers/test_contract.py runs the same assertions against every provider.
When you add a provider, add it to the parametrize list there.