diff --git a/.gitignore b/.gitignore index 45cd75e..38a5750 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ test*.py register.py *.pyc __pycache__ +*.egg-info/ auth.json opensource.py *.pem diff --git a/tesla_fleet_api.egg-info/PKG-INFO b/tesla_fleet_api.egg-info/PKG-INFO deleted file mode 100644 index 474c4e5..0000000 --- a/tesla_fleet_api.egg-info/PKG-INFO +++ /dev/null @@ -1,360 +0,0 @@ -Metadata-Version: 2.4 -Name: tesla_fleet_api -Version: 1.10.0 -Summary: Tesla Fleet API library for Python -Author-email: Brett Adams -License-Expression: Apache-2.0 -Project-URL: Homepage, https://github.com/Teslemetry/python-tesla-fleet-api -Classifier: Development Status :: 5 - Production/Stable -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Operating System :: OS Independent -Requires-Python: >=3.13 -Description-Content-Type: text/markdown -License-File: LICENSE -Requires-Dist: aiohttp>=3 -Requires-Dist: aiofiles>=24 -Requires-Dist: aiolimiter>=1 -Requires-Dist: cryptography>=43 -Requires-Dist: protobuf>=6.32.0 -Requires-Dist: tesla-protocol>=0.5.0 -Requires-Dist: bleak>=0.22 -Requires-Dist: bleak-retry-connector>=3.9 -Dynamic: license-file - -# Tesla Fleet API - -Tesla Fleet API is a Python library that provides an interface to interact with Tesla's Fleet API, including signed commands and encrypted local Bluetooth (BLE) communication. It also supports interactions with Teslemetry and Tessie services. - -## Features - -- Fleet API for vehicles -- Fleet API for energy sites -- Fleet API with signed vehicle commands -- Bluetooth for vehicles -- Routing and failover across backends for vehicles and energy sites (e.g. Bluetooth/local primary, cloud fallback) -- Teslemetry integration -- Tessie integration - -## Installation - -You can install the library using pip: - -```bash -pip install tesla-fleet-api -``` - -## Usage - -### Authentication - -The `TeslaFleetOAuth` class provides methods that help with authenticating to the Tesla Fleet API. Here's a basic example: - -```python -import asyncio -import aiohttp -from tesla_fleet_api import TeslaFleetOAuth - -async def main(): - async with aiohttp.ClientSession() as session: - oauth = TeslaFleetOAuth( - session=session, - client_id="", - client_secret="", - redirect_uri="", - ) - - # Get the login URL and navigate the user to it - login_url = oauth.get_login_url(scopes=["openid", "email", "offline_access"]) - print(f"Please go to {login_url} and authorize access.") - - # After the user authorizes access, they will be redirected to the redirect_uri with a code - code = input("Enter the code you received: ") - - # Exchange the code for a refresh token - await oauth.get_refresh_token(code) - print(f"Access token: {oauth.access_token}") - print(f"Refresh token: {oauth.refresh_token}") - # Dont forget to store the refresh token so you can use it again later - -asyncio.run(main()) -``` - -### Fleet API for Vehicles - -The `TeslaFleetApi` class provides methods to interact with the Fleet API for vehicles. Here's a basic example: - -```python -import asyncio -import aiohttp -from tesla_fleet_api import TeslaFleetApi -from tesla_fleet_api.exceptions import TeslaFleetError - -async def main(): - async with aiohttp.ClientSession() as session: - api = TeslaFleetApi( - access_token="", - session=session, - region="na", - ) - - try: - data = await api.vehicles.list() - print(data) - except TeslaFleetError as e: - print(e) - -asyncio.run(main()) -``` - -For more detailed examples, see [Fleet API for Vehicles](docs/fleet_api_vehicles.md). - -### Fleet API for Energy Sites - -The `EnergySites` class provides methods to interact with the Fleet API for energy sites. Here's a basic example: - -```python -import asyncio -import aiohttp -from tesla_fleet_api import TeslaFleetApi -from tesla_fleet_api.exceptions import TeslaFleetError - -async def main(): - async with aiohttp.ClientSession() as session: - api = TeslaFleetApi( - access_token="", - session=session, - region="na", - ) - - try: - energy_sites = await api.energySites.list() - print(energy_sites) - except TeslaFleetError as e: - print(e) - -asyncio.run(main()) -``` - -For more detailed examples, see [Fleet API for Energy Sites](docs/fleet_api_energy_sites.md). - -To pair an energy gateway's RSA key over the cloud and compose the resulting -signed local LAN control (via the sibling `aiopowerwall` library) with a -cloud fallback through `EnergySiteRouter`, see [Energy: Local -Control](docs/energy_local_control.md). - -### Fleet API with Signed Vehicle Commands - -The `VehicleSigned` class provides methods to interact with the Fleet API using signed vehicle commands. Here's a basic example: - -```python -import asyncio -import aiohttp -from tesla_fleet_api import TeslaFleetApi -from tesla_fleet_api.tesla.vehicle.signed import VehicleSigned -from tesla_fleet_api.exceptions import TeslaFleetError - -async def main(): - async with aiohttp.ClientSession() as session: - api = TeslaFleetApi( - access_token="", - session=session, - region="na", - ) - - try: - vehicle = VehicleSigned(api, "") - data = await vehicle.wake_up() - print(data) - except TeslaFleetError as e: - print(e) - -asyncio.run(main()) -``` - -For more detailed examples, see [Fleet API with Signed Vehicle Commands](docs/fleet_api_signed_commands.md). - -### Bluetooth for Vehicles - -The `TeslaBluetooth` class provides methods to interact with Tesla vehicles using Bluetooth. Here's a basic example: - -```python -import asyncio -from bleak import BleakScanner -from tesla_fleet_api import TeslaBluetooth - -async def main(): - scanner = BleakScanner() - devices = await scanner.discover() - for device in devices: - if TeslaBluetooth().valid_name(device.name): - print(f"Found Tesla vehicle: {device.name}") - -asyncio.run(main()) -``` - -For more detailed examples, see [Bluetooth for Vehicles](docs/bluetooth_vehicles.md). - -`get_private_key(path)` loads an existing EC private key or creates a new -unencrypted PEM key file. Newly created key files are created owner-readable -and owner-writable only (`0600`) from the start, with no write-then-chmod -window, and concurrent creators fall back to reading the file that won the -create race. - -`VehicleBluetooth` keeps a held BLE connection alive during idle periods by -default with a passive GATT read about every 20 seconds. Pass -`keepalive_interval=None` (or `0`) when creating the vehicle to disable it; -leaving it enabled can keep an already-awake car awake longer, so disconnect or -disable keepalive when vehicle sleep is preferred. - -BLE connect/notify failures, and GATT writes rejected before backend I/O, raise -`BluetoothTransportError`, a `TeslaFleetError` subclass, with the original -transport exception chained as `__cause__` when available. A GATT write that -entered backend I/O and then failed or timed out is delivery-ambiguous and -raises `BluetoothTimeout`/`BluetoothUnconfirmedCommand` instead. Mutating BLE -commands use a confirmation ladder controlled by `confirmation` (`"ack"` by -default) and `raise_unconfirmed` (`False` by default): an inconclusive lost -acknowledgement resolves as best-effort success unless you opt in to -`BluetoothUnconfirmedCommand`, while a command proven not to have applied raises -`BluetoothCommandFailed`. See [Bluetooth for Vehicles](docs/bluetooth_vehicles.md) -for the full ladder. Catch `TeslaFleetError` to handle Bluetooth transport -failures (including `bleak.exc.BleakError` and builtin `TimeoutError` from -ESPHome proxies) and response-wait timeouts through the same library error -hierarchy. - -`VehicleBluetooth` can also register persistent BLE broadcast listeners for -unsolicited VCSEC `VehicleStatus` updates. Use typed `listen_*` helpers for the -decoded vehicle-status fields, or `listen_broadcast(domain, callback)` for raw -per-domain broadcast messages. Use `listen_connection_status(callback)` for -`True`/`False` BLE session transition notifications. See -[Bluetooth for Vehicles](docs/bluetooth_vehicles.md#connection-status-events) -for the connection-event contract. - -### Routing and Failover - -The `Router` class composes an ordered list of two-or-more backends that share a common method surface and dispatches each method call down the chain, automatically failing over on most errors. `VehicleRouter` and `EnergySiteRouter` are thin entity-specific subclasses. A common setup is a local `VehicleBluetooth` primary with a cloud fallback (e.g. a `TeslemetryVehicle`), so commands go over Bluetooth when the vehicle is reachable and route to the cloud otherwise: - -```python -import asyncio -import aiohttp -from tesla_fleet_api import TeslaBluetooth, Teslemetry -from tesla_fleet_api.router import VehicleRouter -from tesla_fleet_api.exceptions import TeslaFleetError - -async def main(): - async with aiohttp.ClientSession() as session: - # Primary: local Bluetooth - tesla_bluetooth = TeslaBluetooth() - await tesla_bluetooth.get_private_key("path/to/private_key.pem") - primary = tesla_bluetooth.vehicles.create("", confirmation="verify") - - # Secondary (fallback): Teslemetry cloud - teslemetry = Teslemetry(access_token="", session=session) - secondary = teslemetry.vehicles.create("") - - vehicle = VehicleRouter(primary, secondary) - - try: - await vehicle.wake_up() - except TeslaFleetError as e: - print(e) - -asyncio.run(main()) -``` - -The constructor is `Router(primary, secondary, *more_backends, health=None)`; the two-argument form shown above is fully backward compatible, and any number of extra backends may follow to extend the chain. Each call is tried on the first backend that has the method and, on any exception except `BluetoothUnconfirmedCommand`, retried on the next backend that has it, returning the first success (raising the last error only if every applicable backend fails). Non-callable attributes (e.g. `vin`) resolve to the first backend that has them. - -By default the router attempts the primary and fails over on any error, with no up-front probe. You can also pass an explicit `health` check — a `bool`, a sync callable, or an async callable returning `bool` — to decide up front whether to route to the primary or skip straight to the rest of the chain. The health check gates **only the primary** (the first backend); later backends are reached purely through per-command failover. - -`EnergySiteRouter` follows the same pattern for energy sites, pairing a duck-typed local `EnergySite`-shaped object (e.g. aiopowerwall's `PowerwallEnergySite`, no dependency added) with a cloud `TeslemetryEnergySite` fallback: - -```python -from tesla_fleet_api.router import EnergySiteRouter - -router = EnergySiteRouter(local_energysite, teslemetry_energysite) -await router.operation(...) # local first, cloud on failure -``` - -`Router`, `VehicleRouter`, and `EnergySiteRouter` are all importable from `tesla_fleet_api.router` (and, for backward compatibility, from `tesla_fleet_api.tesla`). - -Enable `DEBUG` logging for `tesla_fleet_api` to see which backend served a routed call and why failover happened. - -> **Warning:** Because a failed call is replayed on the next backend, a non-idempotent command (e.g. `honk_horn`, `actuate_trunk`, `door_unlock`, `charge_start`) that fails _mid-flight_ — after a backend may have already partially applied it — can be **double-executed** (or executed more than once across a longer chain) when it is retried on the next backend. This is a deliberate tradeoff of per-command failover. `BluetoothUnconfirmedCommand` is the exception: it propagates without failover because the BLE command may already have executed. When the primary is `VehicleBluetooth`, pass `confirmation="verify"` to resolve supported mutating command timeouts by state before they reach the router, and set `raise_unconfirmed=True` when callers must see still-ambiguous outcomes instead of the default best-effort success; callers needing exactly-once semantics for other commands should gate dispatch with an explicit `health` check or call the underlying backends directly. -> -> Dispatch is implemented via `__getattr__`, which does not proxy dunder methods, so `async with Router(...)` does **not** manage a backend's BLE connection lifecycle (`__aenter__`/`__aexit__`). Commands still auto-connect on send; for explicit connect/disconnect reach through `router.primary` (or `router.backends`). - -### Debug Logging - -Enable the `tesla_fleet_api` logger at `DEBUG` to see each command's command -name, transport/backend, and result. In standalone scripts, configure a handler -first: - -```python -import logging - -logging.basicConfig(level=logging.DEBUG) -logging.getLogger("tesla_fleet_api").setLevel(logging.DEBUG) -``` - -Command log lines use `transport=bluetooth`, `fleet`, `teslemetry`, or `tessie`. -Routers also emit `backend=` lines for each backend tried. See -[Bluetooth for Vehicles](docs/bluetooth_vehicles.md#troubleshooting-enable-debug-logging) -for examples and the signed-command naming details. -REST responses that are valid JSON but not objects, such as `null`, lists, or -scalars, are returned unchanged and log as `result=success`. - -### Teslemetry - -The `Teslemetry` class provides methods to interact with the Teslemetry service. Here's a basic example: - -```python -import asyncio -import aiohttp -from tesla_fleet_api import Teslemetry -from tesla_fleet_api.exceptions import TeslaFleetError - -async def main(): - async with aiohttp.ClientSession() as session: - api = Teslemetry( - access_token="", - session=session, - ) - - try: - data = await api.vehicles.list() - print(data) - except TeslaFleetError as e: - print(e) - -asyncio.run(main()) -``` - -For more detailed examples, see [Teslemetry](docs/teslemetry.md). - -### Tessie - -The `Tessie` class provides methods to interact with the Tessie service. Here's a basic example: - -```python -import asyncio -import aiohttp -from tesla_fleet_api import Tessie -from tesla_fleet_api.exceptions import TeslaFleetError - -async def main(): - async with aiohttp.ClientSession() as session: - api = Tessie( - access_token="", - session=session, - ) - - try: - data = await api.vehicles.list() - print(data) - except TeslaFleetError as e: - print(e) - -asyncio.run(main()) -``` - -For more detailed examples, see [Tessie](docs/tessie.md). diff --git a/tesla_fleet_api.egg-info/SOURCES.txt b/tesla_fleet_api.egg-info/SOURCES.txt deleted file mode 100644 index ecdea15..0000000 --- a/tesla_fleet_api.egg-info/SOURCES.txt +++ /dev/null @@ -1,108 +0,0 @@ -LICENSE -README.md -pyproject.toml -tesla_fleet_api/__init__.py -tesla_fleet_api/const.py -tesla_fleet_api/exceptions.py -tesla_fleet_api/py.typed -tesla_fleet_api/tariff.py -tesla_fleet_api/util.py -tesla_fleet_api.egg-info/PKG-INFO -tesla_fleet_api.egg-info/SOURCES.txt -tesla_fleet_api.egg-info/dependency_links.txt -tesla_fleet_api.egg-info/requires.txt -tesla_fleet_api.egg-info/top_level.txt -tesla_fleet_api/router/__init__.py -tesla_fleet_api/router/base.py -tesla_fleet_api/router/energysite.py -tesla_fleet_api/router/vehicle.py -tesla_fleet_api/tesla/__init__.py -tesla_fleet_api/tesla/bluetooth.py -tesla_fleet_api/tesla/charging.py -tesla_fleet_api/tesla/energysite.py -tesla_fleet_api/tesla/fleet.py -tesla_fleet_api/tesla/oauth.py -tesla_fleet_api/tesla/partner.py -tesla_fleet_api/tesla/tesla.py -tesla_fleet_api/tesla/user.py -tesla_fleet_api/tesla/vehicle/__init__.py -tesla_fleet_api/tesla/vehicle/bluetooth.py -tesla_fleet_api/tesla/vehicle/broadcast.py -tesla_fleet_api/tesla/vehicle/commands.py -tesla_fleet_api/tesla/vehicle/fleet.py -tesla_fleet_api/tesla/vehicle/signed.py -tesla_fleet_api/tesla/vehicle/vehicle.py -tesla_fleet_api/tesla/vehicle/vehicles.py -tesla_fleet_api/tesla/vehicle/proto/__init__.py -tesla_fleet_api/tesla/vehicle/proto/car_server_pb2.py -tesla_fleet_api/tesla/vehicle/proto/common_pb2.py -tesla_fleet_api/tesla/vehicle/proto/errors_pb2.py -tesla_fleet_api/tesla/vehicle/proto/keys_pb2.py -tesla_fleet_api/tesla/vehicle/proto/managed_charging_pb2.py -tesla_fleet_api/tesla/vehicle/proto/session_pb2.py -tesla_fleet_api/tesla/vehicle/proto/signatures_pb2.py -tesla_fleet_api/tesla/vehicle/proto/universal_message_pb2.py -tesla_fleet_api/tesla/vehicle/proto/vcsec_pb2.py -tesla_fleet_api/tesla/vehicle/proto/vehicle_pb2.py -tesla_fleet_api/teslemetry/__init__.py -tesla_fleet_api/teslemetry/energysite.py -tesla_fleet_api/teslemetry/teslemetry.py -tesla_fleet_api/teslemetry/vehicle.py -tesla_fleet_api/tessie/__init__.py -tesla_fleet_api/tessie/tessie.py -tesla_fleet_api/tessie/vehicle.py -tests/test_auto_seat_climate.py -tests/test_ble_broadcast_confirmation.py -tests/test_ble_broadcast_listeners.py -tests/test_ble_charging_commands.py -tests/test_ble_charging_utility_commands.py -tests/test_ble_client_binding.py -tests/test_ble_climate_commands.py -tests/test_ble_command_verification.py -tests/test_ble_confirmation_mode.py -tests/test_ble_connect_retry_budget.py -tests/test_ble_connection_status.py -tests/test_ble_connectivity_diagnostics_commands.py -tests/test_ble_destructive_commands.py -tests/test_ble_expects_data.py -tests/test_ble_keepalive.py -tests/test_ble_message_routing.py -tests/test_ble_mocked_closures_locks.py -tests/test_ble_mocked_commands.py -tests/test_ble_mocked_media_commands.py -tests/test_ble_mocked_state_readers.py -tests/test_ble_mocked_state_readers_new.py -tests/test_ble_nav_messaging_media_commands.py -tests/test_ble_nav_misc_commands.py -tests/test_ble_niche_commands.py -tests/test_ble_optimistic_and_best_effort.py -tests/test_ble_pair.py -tests/test_ble_reassembling_buffer.py -tests/test_ble_send_transport.py -tests/test_ble_stream_sink.py -tests/test_ble_ui_unit_preference_commands.py -tests/test_ble_unconfirmed_command.py -tests/test_ble_write_timeout_router.py -tests/test_command_counter_lock.py -tests/test_command_logging.py -tests/test_cross_transport_parity.py -tests/test_energysite_authorized_clients.py -tests/test_energysite_island_mode.py -tests/test_find_vehicle_scan_filter.py -tests/test_firmware_at_least.py -tests/test_fleet_auth_refresh.py -tests/test_power_mode_commands.py -tests/test_proto_compatibility.py -tests/test_proto_coverage_lock.py -tests/test_router.py -tests/test_session_info_authentication.py -tests/test_tariff.py -tests/test_tesla_private_key.py -tests/test_teslemetry_authorized_clients.py -tests/test_teslemetry_gateway_address.py -tests/test_teslemetry_register_client.py -tests/test_teslemetry_vehicle_custom_commands.py -tests/test_teslemetry_wait_until_paired.py -tests/test_tessie_vehicle_params.py -tests/test_vehicle_image_state.py -tests/test_vehicle_model.py \ No newline at end of file diff --git a/tesla_fleet_api.egg-info/dependency_links.txt b/tesla_fleet_api.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/tesla_fleet_api.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tesla_fleet_api.egg-info/requires.txt b/tesla_fleet_api.egg-info/requires.txt deleted file mode 100644 index 8317d96..0000000 --- a/tesla_fleet_api.egg-info/requires.txt +++ /dev/null @@ -1,8 +0,0 @@ -aiohttp>=3 -aiofiles>=24 -aiolimiter>=1 -cryptography>=43 -protobuf>=6.32.0 -tesla-protocol>=0.5.0 -bleak>=0.22 -bleak-retry-connector>=3.9 diff --git a/tesla_fleet_api.egg-info/top_level.txt b/tesla_fleet_api.egg-info/top_level.txt deleted file mode 100644 index 7396a77..0000000 --- a/tesla_fleet_api.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -tesla_fleet_api