Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ Scope flags on `TeslaFleetApi.__init__` control which submodules are instantiate

`util.py` holds small, dependency-free helpers shared across the library, re-exported from the top-level package. `firmware_compare(a, b) -> int` compares dotted, numeric, week-based Tesla firmware version strings (e.g. `2025.14.3`) correctly — plain string comparison misorders them (`"2025.10" < "2025.9"`). It returns 1/-1/0, right-pads shorter versions with zeros before comparing, and treats unparseable strings (e.g. `"Unknown"`) as sorting behind any parseable version. `firmware_at_least(firmware, minimum) -> bool` is a thin wrapper (`firmware_compare(firmware, minimum) >= 0`) for the common "does this vehicle's firmware support feature X" gate — ported from Home Assistant core PR #175745, which fixed the same lexicographic bug in the `teslemetry` integration. Deliberately implemented as native tuple comparison rather than taking on an `AwesomeVersion` dependency, matching this library's narrow, purpose-built dependency list (no general-purpose version-parsing lib elsewhere).

### Tariff V2 Resolver

`tariff.py` provides the pure, offline Tariff V2 resolver. Its user-facing contract and example are documented in `docs/fleet_api_energy_sites.md`; its edge-case invariants are owned by the module docstrings and `tests/test_tariff.py`.

### Release Process

No release-please or version-bump automation. To ship: bump `version` in `pyproject.toml` and `__version__` in `tesla_fleet_api/__init__.py` in a `Bump version to X.Y.Z` commit on `main`, then push a matching `vX.Y.Z` tag. `.github/workflows/python-publish.yml` runs on every push but only builds+publishes to PyPI (and creates a GitHub Release) when `github.ref` starts with `refs/tags/` — pushing the tag is what actually ships the release; merging to `main` alone does not.
Expand Down
29 changes: 28 additions & 1 deletion docs/fleet_api_energy_sites.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,41 @@ async def main():

try:
energy_site = api.energySites.create(12345)
time_of_use_settings_response = await energy_site.time_of_use_settings(settings={"tou_settings": {"tariff_content_v2": {}}})
time_of_use_settings_response = await energy_site.time_of_use_settings(settings={})
print(time_of_use_settings_response)
except TeslaFleetError as e:
print(e)

asyncio.run(main())
```

The top-level `get_tariff_periods` helper resolves the current buy and sell
rates from a raw `tariff_content_v2` object without making any API calls.
`unwrap_tariff_v2` accepts the `site_info()` response envelope, the
`tou_settings` write envelope, or a bare tariff object:

```python
from datetime import datetime
from zoneinfo import ZoneInfo

from tesla_fleet_api import get_tariff_periods, unwrap_tariff_v2

site_info = await energy_site.site_info()
tariff = unwrap_tariff_v2(site_info)
site_timezone = ZoneInfo("<installation_time_zone from site_info>")
now = datetime.now(site_timezone)
resolution = get_tariff_periods(tariff, now, horizon_hours=24)
```

`now` must be timezone-aware and expressed in the site's local timezone because
the tariff object does not carry its own timezone. A naive `now` raises
`ValueError`. The result contains the current buy and sell rates, the current
period's start, the next change, the currency, and (when `horizon_hours` is
provided) upcoming periods. It returns `None` when no tariff season covers
`now`. Missing rates remain `None`, while a real zero price remains `0.0`.
Malformed or null response envelopes passed to `unwrap_tariff_v2` raise
`InvalidResponse`.

## Device Commands

Energy gateways (Powerwalls, etc.) support gRPC commands sent via `POST /api/1/energy_sites/{id}/command`. These are undocumented Tesla API endpoints that communicate directly with the gateway hardware. All device command methods require the `energy_cmds` scope.
Expand Down
23 changes: 23 additions & 0 deletions tesla_fleet_api.egg-info/SOURCES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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
Expand Down Expand Up @@ -32,6 +33,17 @@ 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
Expand All @@ -43,21 +55,28 @@ 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_climate_commands.py
tests/test_ble_command_verification.py
tests/test_ble_confirmation_mode.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_ui_unit_preference_commands.py
tests/test_ble_unconfirmed_command.py
tests/test_ble_write_timeout_router.py
tests/test_command_counter_lock.py
Expand All @@ -68,7 +87,11 @@ 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
Expand Down
12 changes: 12 additions & 0 deletions tesla_fleet_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
__version__ = "1.7.7"

from tesla_fleet_api.const import Region, is_valid_region
from tesla_fleet_api.tariff import (
TariffPeriod,
TariffRate,
TariffResolution,
get_tariff_periods,
unwrap_tariff_v2,
)
from tesla_fleet_api.tesla.bluetooth import TeslaBluetooth
from tesla_fleet_api.tesla.fleet import TeslaFleetApi
from tesla_fleet_api.tesla.oauth import TeslaFleetOAuth
Expand All @@ -13,12 +20,17 @@

__all__ = [
"Region",
"TariffPeriod",
"TariffRate",
"TariffResolution",
"TeslaFleetApi",
"TeslaBluetooth",
"TeslaFleetOAuth",
"Teslemetry",
"Tessie",
"firmware_at_least",
"firmware_compare",
"get_tariff_periods",
"is_valid_region",
"unwrap_tariff_v2",
]
Loading
Loading