Skip to content
Merged
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
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ No release-please or version-bump automation. To ship: bump `version` in `pyproj

`exceptions.py` maps HTTP status codes and error keys to specific exception classes. `raise_for_status()` parses responses and raises the appropriate exception. Signed command faults have separate hierarchies: `TeslaFleetInformationFault`, `TeslaFleetMessageFault`, `SignedMessageInformationFault`, `WhitelistOperationStatus`.

All exceptions inherit from `TeslaFleetError(BaseException)`.
All exceptions inherit from `TeslaFleetError(BaseException)`, deliberately **not** `Exception` — a bare `except Exception` (e.g. in retry/backoff loops around BLE reads) silently fails to catch `BluetoothTimeout` and every other library error. Catch `TeslaFleetError` (or `BaseException`) explicitly.

### Protobuf

Expand All @@ -122,6 +122,9 @@ Source protobuf definitions live in `proto/`; generated Python files live in `te
- **BLE individual-door powered-close gotcha**: `open_*_door()` unlatches a door over VCSEC; on a Model 3 there is no reliable powered close. Live-verified: `close_rear_passenger_door()` returned an OK ack (`{"result": True}`) but `closures_state().door_open_passenger_rear` stayed `True` - the ack only means the car accepted the command, not that the door physically re-latched (a human has to push it shut). Never chain an automated snapshot→act→verify→restore cycle across an individual door-open command; treat the 8 door commands as ack-verified only, or require a human to confirm the physical re-close before trusting `closures_state()` again.
- **`remote_heater_control_enabled` gate**: `climate_state().remote_heater_control_enabled` is a read-only vehicle-side setting (no command exists to flip it) that gates every "remote comfort" action - live-verified on `commands.py`: `remote_seat_heater_request`, `remote_auto_seat_climate_request`, `remote_steering_wheel_heater_request`, `remote_steering_wheel_heat_level_request`, `remote_auto_steering_wheel_heat_climate_request`. With it `false`, the vehicle ACKs `{"result": false, "reason": "cabin comfort remote settings not enabled"}` and leaves state untouched (not a library bug, not a partial mutation) - this is presumably the touchscreen/app "Remote Climate" or comfort-access toggle, outside this library's command surface. Check this field before treating a comfort-command rejection as a regression.
- **Protobuf oneof-by-string-kwargs bypasses pyright**: `remote_seat_heater_request`/`remote_seat_cooler_request` (`commands.py`) build their `HvacSeatHeaterAction`/`HvacSeatCoolerAction` via a `dict` of literal field-name strings expanded as `**kwargs` into the message constructor (with a `# pyright: ignore[reportUnknownArgumentType]` already on the call) - a typo in one of those strings (e.g. `SEAT_HEATER_MEDIUM` vs. the proto's actual `SEAT_HEATER_MED`, fixed live during PR-4) raises at call time, not at type-check time. Cross-check any new field-name string against the proto (`proto/car_server.proto`) rather than trusting pyright to catch it.
- **`scheduled_charging_mode` is tri-state and shared**: `set_scheduled_charging` and `set_scheduled_departure` (`commands.py`) both write the same `ChargeState.scheduled_charging_mode` (Off/StartAt/DepartBy) - live-verified. Disabling one when the other is active turns the whole feature Off rather than leaving the other's config intact; a caller toggling one must read `charge_state()` first and restore the exact prior mode (including the other command's fields) rather than assuming independence.
- **`set_scheduled_departure`'s `preconditioning_enabled`/`off_peak_charging_enabled` args are dead**: live-verified - `ScheduledDepartureAction` (`proto/car_server.proto`) has no fields for them, only `preconditioning_times`/`off_peak_charging_times` (weekday-recurrence only, no on/off). Passing `preconditioning_enabled=False` has no effect on the vehicle's observed state. Not a library bug to "fix" without a wider protocol capability; document, don't rely on these args to gate the feature.
- **`charge_standard()` rejects `already_standard`**: live-verified - calling it while `charge_state().charge_limit_soc` already equals `charge_limit_soc_std` gets `{"result": False, "reason": "already_standard"}` rather than a no-op success. Not a library bug; callers/tests exercising this command need the limit to actually differ from the std preset first (e.g. via `charge_max_range()` or `set_charge_limit()`).

## Maintaining this file

Expand Down
41 changes: 41 additions & 0 deletions docs/bluetooth_vehicles.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,47 @@ with `cabin comfort remote settings not enabled` when
`climate_state().remote_heater_control_enabled` is false. That field is a
read-only vehicle setting; the library has no command to enable it.

## Charging Commands

`VehicleBluetooth` inherits the signed-command charging surface. The following
commands send over BLE:

- `charge_start()`
- `charge_stop()`
- `charge_standard()`
- `charge_max_range()`
- `charge_port_door_open()`
- `charge_port_door_close()`
- `set_charge_limit(percent)`
- `set_charging_amps(charging_amps)`
- `set_scheduled_charging(enable, time)`
- `set_scheduled_departure(...)`
- `add_charge_schedule(...)`
- `remove_charge_schedule(id)`
- `batch_remove_charge_schedules(home, work, other)`
- `add_precondition_schedule(...)`
- `remove_precondition_schedule(id)`
- `batch_remove_precondition_schedules(home, work, other)`

`set_scheduled_charging()` and `set_scheduled_departure()` both update the
vehicle's shared `scheduled_charging_mode`. Disabling one mode while the other
is active can turn scheduled charging/departure off entirely, so callers that
toggle either setting should read `charge_state()` first and restore the prior
mode and fields when preserving the other schedule matters.

For signed commands, `set_scheduled_departure()` sends `enable`,
`departure_time`, weekday/all-week recurrence choices, and
`end_off_peak_time`. Its `preconditioning_enabled` and
`off_peak_charging_enabled` arguments are accepted for API compatibility but do
not map to fields in the vehicle's signed-command protobuf.

`charge_standard()` is not treated as a no-op by all vehicles: if the current
charge limit already equals `charge_limit_soc_std`, the vehicle may reject the
command with `already_standard`.

Avoid actuating `charge_port_door_open()` or `charge_port_door_close()` against
a plugged-in vehicle unless someone can reseat the cable if needed.

## Open/Close Individual Doors (Bluetooth Only)

The individual door closure commands are Bluetooth-only and are not available via Fleet API signed commands.
Expand Down
23 changes: 23 additions & 0 deletions docs/fleet_api_signed_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,29 @@ async def main():
asyncio.run(main())
```

## Other Charging Commands

Signed commands also include `charge_standard()`, `charge_max_range()`,
`set_charging_amps(charging_amps)`, schedule configuration, and charge-port
door commands. These methods share the same signatures as the Fleet API vehicle
commands, but are sent through the signed-command protocol.

`set_scheduled_charging()` and `set_scheduled_departure()` both update the
vehicle's shared `scheduled_charging_mode`. Disabling one mode while the other
is active can turn scheduled charging/departure off entirely, so callers that
toggle either setting should read the current `charge_state()` first and
restore the prior mode and fields when preserving the other schedule matters.

For signed commands, `set_scheduled_departure()` sends `enable`,
`departure_time`, weekday/all-week recurrence choices, and
`end_off_peak_time`. Its `preconditioning_enabled` and
`off_peak_charging_enabled` arguments are accepted for API compatibility but do
not map to fields in the vehicle's signed-command protobuf.

`charge_standard()` is not treated as a no-op by all vehicles: if the current
charge limit already equals `charge_limit_soc_std`, the vehicle may reject the
command with `already_standard`.

## Flash Lights

You can flash the lights of a specific vehicle using its VIN:
Expand Down
26 changes: 23 additions & 3 deletions tesla_fleet_api/tesla/vehicle/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,11 @@ async def charge_port_door_open(self) -> dict[str, Any]:
)

async def charge_standard(self) -> dict[str, Any]:
"""Charges in Standard mode."""
"""Charges in Standard mode.

Some vehicles reject this command with ``already_standard`` when the
current charge limit already equals ``charge_limit_soc_std``.
"""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
Expand Down Expand Up @@ -1366,7 +1370,13 @@ async def set_preconditioning_max(
)

async def set_scheduled_charging(self, enable: bool, time: int) -> dict[str, Any]:
"""Sets a time at which charging should be completed. The time parameter is minutes after midnight (e.g: time=120 schedules charging for 2:00am vehicle local time)."""
"""Sets the scheduled charging start time.

``time`` is minutes after midnight in vehicle-local time. This command
and ``set_scheduled_departure`` both update the vehicle's shared
``scheduled_charging_mode``; disabling one while the other is active can
turn scheduling off entirely.
"""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
Expand All @@ -1387,7 +1397,17 @@ async def set_scheduled_departure(
off_peak_charging_weekdays_only: bool = False,
end_off_peak_time: int = 0,
) -> dict[str, Any]:
"""Sets a time at which departure should be completed. The time parameter is minutes after midnight (e.g: time=120 schedules departure for 2:00am vehicle local time)."""
"""Sets the scheduled departure time.

``departure_time`` and ``end_off_peak_time`` are minutes after midnight
in vehicle-local time. This command and ``set_scheduled_charging`` both
update the vehicle's shared ``scheduled_charging_mode``; disabling one
while the other is active can turn scheduling off entirely.

``preconditioning_enabled`` and ``off_peak_charging_enabled`` are
accepted by the Python signature for compatibility but the signed
command protobuf has no fields for them, so they are not sent.
"""

if preconditioning_weekdays_only:
preconditioning_times = PreconditioningTimes(weekdays=Void())
Expand Down
Loading
Loading