Skip to content

Commit 4141a44

Browse files
committed
Add custom commands
1 parent bdcbc4f commit 4141a44

File tree

4 files changed

+239
-3
lines changed

4 files changed

+239
-3
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ requires = ["setuptools>=77.0"]
44

55
[project]
66
name = "tesla_fleet_api"
7-
version = "1.2.1"
7+
version = "1.2.2"
88
license = "Apache-2.0"
99
description = "Tesla Fleet API library for Python"
1010
readme = "README.md"

tesla_fleet_api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tesla Fleet API"""
22

33
__author__ = "[email protected]"
4-
__version__ = "1.2.1"
4+
__version__ = "1.2.2"
55

66
from tesla_fleet_api.tesla.fleet import TeslaFleetApi
77
from tesla_fleet_api.tesla.bluetooth import TeslaBluetooth

tesla_fleet_api/const.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,25 @@ class TeslaEnergyPeriod(StrEnum):
167167
YEAR = "year"
168168
LIFETIME = "lifetime"
169169

170+
class ClosureState(StrEnum):
171+
"""Closure state options"""
172+
173+
NONE = "none"
174+
MOVE = "move"
175+
STOP = "stop"
176+
OPEN = "open"
177+
CLOSE = "close"
178+
179+
180+
class SeatHeaterLevel(StrEnum):
181+
"""Seat heater level options"""
182+
183+
OFF = "off"
184+
LOW = "low"
185+
MEDIUM = "medium"
186+
HIGH = "high"
187+
188+
170189
class BluetoothVehicleData(StrEnum):
171190
CHARGE_STATE = "GetChargeState"
172191
CLIMATE_STATE = "GetClimateState"

tesla_fleet_api/teslemetry/vehicles.py

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
2+
23
from typing import TYPE_CHECKING, Any
34

4-
from tesla_fleet_api.const import Method
5+
from tesla_fleet_api.const import Method, ClosureState, SeatHeaterLevel
56
from tesla_fleet_api.tesla.vehicle.vehicles import Vehicles
67
from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet
8+
from tesla_fleet_api.const import LOGGER
79

810
if TYPE_CHECKING:
911
pass
@@ -15,6 +17,7 @@ async def server_side_polling(
1517
self, value: bool | None = None
1618
) -> bool | None:
1719
"""Get or set Auto mode."""
20+
LOGGER.warning("This method is deprecated and will be removed in a future release.")
1821
if value is True:
1922
return (
2023
await self._request(
@@ -43,6 +46,220 @@ async def data_refresh(self) -> dict[str, Any]:
4346
f"api/refresh/{self.vin}",
4447
)
4548

49+
async def ping(self) -> dict[str, Any]:
50+
"""Performs a no-op on the vehicle."""
51+
return await self._request(
52+
Method.POST,
53+
f"api/1/vehicles/{self.vin}/custom_command/ping",
54+
)
55+
56+
async def closure(
57+
self,
58+
front_driver_door: ClosureState = ClosureState.NONE,
59+
front_passenger_door: ClosureState = ClosureState.NONE,
60+
rear_driver_door: ClosureState = ClosureState.NONE,
61+
rear_passenger_door: ClosureState = ClosureState.NONE,
62+
rear_trunk: ClosureState = ClosureState.NONE,
63+
front_trunk: ClosureState = ClosureState.NONE,
64+
charge_port: ClosureState = ClosureState.NONE,
65+
tonneau: ClosureState = ClosureState.NONE,
66+
) -> dict[str, Any]:
67+
"""Open, Close, Move and Stop the vehicle's windows, doors, and frunk/trunk.
68+
69+
Args:
70+
front_driver_door: Action for front driver door
71+
front_passenger_door: Action for front passenger door
72+
rear_driver_door: Action for rear driver door
73+
rear_passenger_door: Action for rear passenger door
74+
rear_trunk: Action for rear trunk
75+
front_trunk: Action for front trunk
76+
charge_port: Action for charge port
77+
tonneau: Action for tonneau
78+
79+
Example:
80+
# Open the front trunk
81+
await vehicle.closure(front_trunk=ClosureState.OPEN)
82+
83+
# Close all doors
84+
await vehicle.closure(
85+
front_driver_door=ClosureState.CLOSE,
86+
front_passenger_door=ClosureState.CLOSE,
87+
rear_driver_door=ClosureState.CLOSE,
88+
rear_passenger_door=ClosureState.CLOSE
89+
)
90+
"""
91+
data = {}
92+
if front_driver_door != ClosureState.NONE:
93+
data["frontDriverDoor"] = front_driver_door.value
94+
if front_passenger_door != ClosureState.NONE:
95+
data["frontPassengerDoor"] = front_passenger_door.value
96+
if rear_driver_door != ClosureState.NONE:
97+
data["rearDriverDoor"] = rear_driver_door.value
98+
if rear_passenger_door != ClosureState.NONE:
99+
data["rearPassengerDoor"] = rear_passenger_door.value
100+
if rear_trunk != ClosureState.NONE:
101+
data["rearTrunk"] = rear_trunk.value
102+
if front_trunk != ClosureState.NONE:
103+
data["frontTrunk"] = front_trunk.value
104+
if charge_port != ClosureState.NONE:
105+
data["chargePort"] = charge_port.value
106+
if tonneau != ClosureState.NONE:
107+
data["tonneau"] = tonneau.value
108+
109+
return await self._request(
110+
Method.POST,
111+
f"api/1/vehicles/{self.vin}/custom_command/closure",
112+
json=data,
113+
)
114+
115+
async def seat_heater(
116+
self,
117+
front_left: SeatHeaterLevel | None = None,
118+
front_right: SeatHeaterLevel | None = None,
119+
rear_left: SeatHeaterLevel | None = None,
120+
rear_right: SeatHeaterLevel | None = None,
121+
rear_left_back: SeatHeaterLevel | None = None,
122+
rear_center: SeatHeaterLevel | None = None,
123+
rear_right_back: SeatHeaterLevel | None = None,
124+
third_row_left: SeatHeaterLevel | None = None,
125+
third_row_right: SeatHeaterLevel | None = None,
126+
) -> dict[str, Any]:
127+
"""Sets multiple seat heaters at once.
128+
129+
Args:
130+
front_left: Front left seat heater level
131+
front_right: Front right seat heater level
132+
rear_left: Rear left seat heater level
133+
rear_right: Rear right seat heater level
134+
rear_left_back: Rear left back seat heater level
135+
rear_center: Rear center seat heater level
136+
rear_right_back: Rear right back seat heater level
137+
third_row_left: Third row left seat heater level
138+
third_row_right: Third row right seat heater level
139+
140+
Example:
141+
# Set front seats to high heat
142+
await vehicle.seat_heater(
143+
front_left=SeatHeaterLevel.HIGH,
144+
front_right=SeatHeaterLevel.HIGH
145+
)
146+
147+
# Turn off all seat heaters
148+
await vehicle.seat_heater(
149+
front_left=SeatHeaterLevel.OFF,
150+
front_right=SeatHeaterLevel.OFF,
151+
rear_left=SeatHeaterLevel.OFF,
152+
rear_right=SeatHeaterLevel.OFF
153+
)
154+
"""
155+
data = {}
156+
if front_left is not None:
157+
data["frontLeft"] = front_left.value
158+
if front_right is not None:
159+
data["frontRight"] = front_right.value
160+
if rear_left is not None:
161+
data["rearLeft"] = rear_left.value
162+
if rear_right is not None:
163+
data["rearRight"] = rear_right.value
164+
if rear_left_back is not None:
165+
data["rearLeftBack"] = rear_left_back.value
166+
if rear_center is not None:
167+
data["rearCenter"] = rear_center.value
168+
if rear_right_back is not None:
169+
data["rearRightBack"] = rear_right_back.value
170+
if third_row_left is not None:
171+
data["thirdRowLeft"] = third_row_left.value
172+
if third_row_right is not None:
173+
data["thirdRowRight"] = third_row_right.value
174+
175+
return await self._request(
176+
Method.POST,
177+
f"api/1/vehicles/{self.vin}/custom_command/seat_heater",
178+
json=data,
179+
)
180+
181+
async def charge_on_solar(
182+
self,
183+
enabled: bool | None = None,
184+
lower_charge_limit: int | None = None,
185+
upper_charge_limit: int | None = None,
186+
) -> dict[str, Any]:
187+
"""Enable or disable charging on solar, set lower and upper charge limits.
188+
189+
Args:
190+
enabled: Enable or disable charging on solar
191+
lower_charge_limit: Lower charge limit (0 - upper_charge_limit)
192+
upper_charge_limit: Upper charge limit (lower_charge_limit - 100)
193+
"""
194+
data = {}
195+
if enabled is not None:
196+
data["enabled"] = enabled
197+
if lower_charge_limit is not None:
198+
data["lowerChargeLimit"] = lower_charge_limit
199+
if upper_charge_limit is not None:
200+
data["upperChargeLimit"] = upper_charge_limit
201+
202+
return await self._request(
203+
Method.POST,
204+
f"api/1/vehicles/{self.vin}/custom_command/charge_on_solar",
205+
json=data,
206+
)
207+
208+
async def dashcam_save(self) -> dict[str, Any]:
209+
"""Save the last 10 minutes of dashcam footage."""
210+
return await self._request(
211+
Method.POST,
212+
f"api/1/vehicles/{self.vin}/custom_command/dashcam_save",
213+
)
214+
215+
async def play_video(self, url: str) -> dict[str, Any]:
216+
"""Play a supported video URL in the vehicle.
217+
218+
Args:
219+
url: Video URL to play (e.g., YouTube URL)
220+
"""
221+
return await self._request(
222+
Method.POST,
223+
f"api/1/vehicles/{self.vin}/custom_command/play_video",
224+
json={"url": url},
225+
)
226+
227+
async def stop_light_show(self) -> dict[str, Any]:
228+
"""Stop the currently playing light show."""
229+
return await self._request(
230+
Method.POST,
231+
f"api/1/vehicles/{self.vin}/custom_command/stop_light_show",
232+
)
233+
234+
async def start_light_show(
235+
self,
236+
show_index: int,
237+
start_time: int | None = None,
238+
volume: float | None = None,
239+
dance_moves: bool | None = None,
240+
) -> dict[str, Any]:
241+
"""Start a light show on the vehicle.
242+
243+
Args:
244+
show_index: Index of the light show to play
245+
start_time: Start time in milliseconds since epoch
246+
volume: Volume level for the light show
247+
dance_moves: Enable dance moves during the light show
248+
"""
249+
data: dict[str, Any] = {"show_index": show_index}
250+
if start_time is not None:
251+
data["start_time"] = start_time
252+
if volume is not None:
253+
data["volume"] = volume
254+
if dance_moves is not None:
255+
data["dance_moves"] = dance_moves
256+
257+
return await self._request(
258+
Method.POST,
259+
f"api/1/vehicles/{self.vin}/custom_command/start_light_show",
260+
json=data,
261+
)
262+
46263

47264
class TeslemetryVehicles(Vehicles):
48265
"""Class containing and creating vehicles."""

0 commit comments

Comments
 (0)