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
42 changes: 42 additions & 0 deletions tesla_fleet_api/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,48 @@ class SeatHeaterLevel(StrEnum):
HIGH = "high"


class TemperatureUnit(IntEnum):
"""Displayed temperature unit options"""

FAHRENHEIT = 0
CELSIUS = 1


class DistanceUnit(IntEnum):
"""Displayed distance unit options"""

MILES = 0
KILOMETERS = 1


class TimeDisplayFormat(IntEnum):
"""Displayed clock format options"""

HOUR_12 = 0
HOUR_24 = 1


class TirePressureUnit(IntEnum):
"""Displayed tire pressure unit options"""

PSI = 0
BAR = 1


class EnergyDisplayFormat(IntEnum):
"""Displayed energy/range unit options"""

PERCENTAGE = 0
DISTANCE = 1


class PhoneFontSize(IntEnum):
"""Paired-phone display font size options"""

STANDARD = 0
LARGE = 1


class BluetoothVehicleData(StrEnum):
CHARGE_STATE = "GetChargeState"
CLIMATE_STATE = "GetClimateState"
Expand Down
293 changes: 293 additions & 0 deletions tesla_fleet_api/tesla/vehicle/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
CabinOverheatProtectionTemp,
SunRoofCommand,
WindowCommand,
TemperatureUnit,
DistanceUnit,
TimeDisplayFormat,
TirePressureUnit,
EnergyDisplayFormat,
PhoneFontSize,
)

# Protocol
Expand Down Expand Up @@ -182,6 +188,30 @@
GetLocalProfilesForVaultUuidAction,
DeleteDashcamClipsAction,
FormatUSBAction,
SetTemperatureUnitAction,
SetDistanceUnitAction,
SetTimeDisplayFormatAction,
SetTirePressureUnitAction,
SetEnergyDisplayFormatAction,
SetPhoneSettingPreferencesAction,
PhoneUnitPreferences,
UiSetUpcomingCalendarEntries,
TakeDrivenoteAction,
VideoRequestAction,
NavigationRouteAction,
GetMessagesAction,
SetRateTariffRequest,
GetRateTariffRequest,
AddManagedChargingSiteRequest,
RemoveManagedChargingSiteRequest,
GetManagedChargingSitesRequest,
SetDischargeLimitAction,
ManagedChargingSite,
ManagerType,
SiteController,
BluetoothClassicPairingRequest,
BandwidthTest,
FetchKeysInfoAction,
)
from google.protobuf.timestamp_pb2 import Timestamp
from tesla_protocol.command.vehicle_pb2 import (
Expand Down Expand Up @@ -2541,3 +2571,266 @@ async def delete_dashcam_clips(self) -> dict[str, Any]:
)
)
)

async def set_temperature_unit(self, unit: TemperatureUnit | int) -> dict[str, Any]:
"""Sets the vehicle's displayed temperature unit."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
setTemperatureUnitAction=SetTemperatureUnitAction(
unit=unit # pyright: ignore[reportArgumentType]
)
)
)
)

async def set_distance_unit(self, unit: DistanceUnit | int) -> dict[str, Any]:
"""Sets the vehicle's displayed distance unit."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
setDistanceUnitAction=SetDistanceUnitAction(
unit=unit # pyright: ignore[reportArgumentType]
)
)
)
)

async def set_time_display_format(
self, format: TimeDisplayFormat | int
) -> dict[str, Any]:
"""Sets the vehicle's displayed clock format (12h/24h)."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
setTimeDisplayFormatAction=SetTimeDisplayFormatAction(
format=format # pyright: ignore[reportArgumentType]
)
)
)
)

async def set_tire_pressure_unit(
self, unit: TirePressureUnit | int
) -> dict[str, Any]:
"""Sets the vehicle's displayed tire pressure unit."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
setTirePressureUnitAction=SetTirePressureUnitAction(
unit=unit # pyright: ignore[reportArgumentType]
)
)
)
)

async def set_energy_display_format(
self, format: EnergyDisplayFormat | int
) -> dict[str, Any]:
"""Sets the vehicle's displayed energy/range unit (percentage/distance)."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
setEnergyDisplayFormatAction=SetEnergyDisplayFormatAction(
format=format # pyright: ignore[reportArgumentType]
)
)
)
)

async def set_phone_setting_preferences(
self,
font_size: PhoneFontSize | int = PhoneFontSize.STANDARD,
*,
distance_unit: DistanceUnit | int | None = None,
temperature_unit: TemperatureUnit | int | None = None,
Comment on lines +2643 to +2646

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require complete phone preferences for updates

This signature allows callers to update only one phone preference, but the underlying proto fields are implicit proto3 enum fields whose omitted values read back as their zero defaults (STANDARD, MILES, FAHRENHEIT). For example, set_phone_setting_preferences(distance_unit=DistanceUnit.KILOMETERS) also leaves font_size at the default STANDARD and temperature_unit at the default FAHRENHEIT, so a vehicle currently using large phone text or Celsius can be silently reset while changing distance. Please require/pass the full current preference set instead of treating these fields as independently optional.

Useful? React with 👍 / 👎.

) -> dict[str, Any]:
"""Sets paired-phone display preferences: font size and, optionally, distance/temperature units."""
action = SetPhoneSettingPreferencesAction(
font_size=font_size # pyright: ignore[reportArgumentType]
)
if distance_unit is not None or temperature_unit is not None:
unit_kwargs: dict[str, Any] = {}
if distance_unit is not None:
unit_kwargs["distance_unit"] = distance_unit
if temperature_unit is not None:
unit_kwargs["temperature_unit"] = temperature_unit
action.unit_preferences.CopyFrom(PhoneUnitPreferences(**unit_kwargs))
return await self._sendInfotainment(
Action(vehicleAction=VehicleAction(setPhoneSettingPreferencesAction=action))
)

async def upcoming_calendar_entries(self, calendar_data: str) -> dict[str, Any]:
"""Sends upcoming calendar entries to the vehicle.

Signed-command sibling of the REST-only ``VehicleFleet.upcoming_calendar_entries``.
"""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
uiSetUpcomingCalendarEntries=UiSetUpcomingCalendarEntries(
calendar_data=calendar_data
)
)
)
)

async def take_drivenote(self, note: str) -> dict[str, Any]:
"""Records a drive note.

Signed-command sibling of the REST-only ``VehicleFleet.take_drivenote``.
"""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
takeDrivenoteAction=TakeDrivenoteAction(note=note)
)
)
)

async def video_request(self, url: str) -> dict[str, Any]:
"""Requests the vehicle open a video stream from the given URL (e.g. sentry/dashcam viewer)."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
videoRequestAction=VideoRequestAction(url=url)
)
)
)

async def navigation_route(self) -> dict[str, Any]:
"""Triggers vehicle route navigation."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
navigationRouteAction=NavigationRouteAction()
)
)
)

async def get_messages(self) -> dict[str, Any]:
"""Gets vehicle in-car messages."""
return await self._sendInfotainment(
Action(vehicleAction=VehicleAction(getMessagesAction=GetMessagesAction())),
mutating=False,
)

async def set_rate_tariff(
self,
seasons: SetRateTariffRequest.Seasons,
tariff: SetRateTariffRequest.Tariff | None = None,
) -> dict[str, Any]:
"""Sets a time-of-use rate tariff schedule for charge-on-solar-style optimization.

``seasons``/``tariff`` are ``tesla_protocol`` message types
(``SetRateTariffRequest.Seasons``/``.Tariff``) - the tariff schedule is
deeply nested (up to 5 named seasons, each with 4 time-of-use period
types), so construct them directly rather than through a parallel
flattened API.
"""
action = SetRateTariffRequest(seasons=seasons)
if tariff is not None:
action.tariff.CopyFrom(tariff)
return await self._sendInfotainment(
Action(vehicleAction=VehicleAction(setRateTariffRequest=action))
)

async def get_rate_tariff(self) -> dict[str, Any]:
"""Gets the current time-of-use rate tariff schedule."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(getRateTariffRequest=GetRateTariffRequest())
Comment on lines +2740 to +2742

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return the new response oneof payloads

When the vehicle answers this new getter, the protobuf response is carried in Response.getRateTariffResponse, but _command() only unwraps ping, vehicleData, and actionStatus before falling through to {"result": True}. In practice get_rate_tariff() will report success while discarding the tariff data; the same applies to the other newly-added response-producing reads such as managed charging sites, messages, and keys info unless their Response oneof fields are explicitly returned.

Useful? React with 👍 / 👎.

),
mutating=False,
)

async def add_managed_charging_site(
self, public_key: str, lat: float, lon: float
) -> dict[str, Any]:
"""Registers a managed charging site (utility managed-charging program) for this vehicle."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
addManagedChargingSiteRequest=AddManagedChargingSiteRequest(
site=ManagedChargingSite(
public_key=public_key,
manager_type=ManagerType(site_controller=SiteController()),
lat_lon=LatLong(latitude=lat, longitude=lon),
)
)
)
)
)

async def remove_managed_charging_site(self, public_key: str) -> dict[str, Any]:
"""Removes a previously-registered managed charging site by its public key."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
removeManagedChargingSiteRequest=RemoveManagedChargingSiteRequest(
public_key=public_key
)
)
)
)

async def get_managed_charging_sites(self) -> dict[str, Any]:
"""Gets the list of registered managed charging sites."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
getManagedChargingSitesRequest=GetManagedChargingSitesRequest()
)
),
mutating=False,
)

async def set_discharge_limit(self, discharge_limit: int) -> dict[str, Any]:
"""Sets the vehicle's general discharge limit.

Not the same feature as ``set_powershare_discharge_limit``
(``SetPowershareDischargeLimitAction``, a distinct proto message).
"""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
setDischargeLimitAction=SetDischargeLimitAction(
discharge_limit=discharge_limit
)
)
)
)

async def bluetooth_classic_pairing_request(
self, name: str, mac_address: bytes
) -> dict[str, Any]:
"""Requests Bluetooth Classic (not BLE) pairing with a phone for calls/audio."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
bluetoothClassicPairingRequest=BluetoothClassicPairingRequest(
utf8_name=name,
mac_address=mac_address,
)
)
)
)

async def bandwidth_test(self, requested_size: int) -> dict[str, Any]:
"""Runs a diagnostic bandwidth test of the given size in bytes."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(
bandwidthTest=BandwidthTest(requested_size=requested_size)
)
)
)

async def fetch_keys_info(self) -> dict[str, Any]:
"""Gets information about keys paired with the vehicle."""
return await self._sendInfotainment(
Action(
vehicleAction=VehicleAction(fetchKeysInfoAction=FetchKeysInfoAction())
),
mutating=False,
)
Loading
Loading