Home Assistant Unofficial Reference 2024.12.1
services.py
Go to the documentation of this file.
1 """Service calls for the Teslemetry integration."""
2 
3 import logging
4 
5 import voluptuous as vol
6 from voluptuous import All, Range
7 
8 from homeassistant.config_entries import ConfigEntry
9 from homeassistant.const import CONF_DEVICE_ID, CONF_LATITUDE, CONF_LONGITUDE
10 from homeassistant.core import HomeAssistant, ServiceCall
11 from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
12 from homeassistant.helpers import config_validation as cv, device_registry as dr
13 
14 from .const import DOMAIN
15 from .helpers import handle_command, handle_vehicle_command, wake_up_vehicle
16 from .models import TeslemetryEnergyData, TeslemetryVehicleData
17 
18 _LOGGER = logging.getLogger(__name__)
19 
20 # Attributes
21 ATTR_ID = "id"
22 ATTR_GPS = "gps"
23 ATTR_TYPE = "type"
24 ATTR_VALUE = "value"
25 ATTR_LOCALE = "locale"
26 ATTR_ORDER = "order"
27 ATTR_TIMESTAMP = "timestamp"
28 ATTR_FIELDS = "fields"
29 ATTR_ENABLE = "enable"
30 ATTR_TIME = "time"
31 ATTR_PIN = "pin"
32 ATTR_TOU_SETTINGS = "tou_settings"
33 ATTR_PRECONDITIONING_ENABLED = "preconditioning_enabled"
34 ATTR_PRECONDITIONING_WEEKDAYS = "preconditioning_weekdays_only"
35 ATTR_DEPARTURE_TIME = "departure_time"
36 ATTR_OFF_PEAK_CHARGING_ENABLED = "off_peak_charging_enabled"
37 ATTR_OFF_PEAK_CHARGING_WEEKDAYS = "off_peak_charging_weekdays_only"
38 ATTR_END_OFF_PEAK_TIME = "end_off_peak_time"
39 
40 # Services
41 SERVICE_NAVIGATE_ATTR_GPS_REQUEST = "navigation_gps_request"
42 SERVICE_SET_SCHEDULED_CHARGING = "set_scheduled_charging"
43 SERVICE_SET_SCHEDULED_DEPARTURE = "set_scheduled_departure"
44 SERVICE_VALET_MODE = "valet_mode"
45 SERVICE_SPEED_LIMIT = "speed_limit"
46 SERVICE_TIME_OF_USE = "time_of_use"
47 
48 
50  hass: HomeAssistant, call: ServiceCall
51 ) -> dr.DeviceEntry:
52  """Get the device entry related to a service call."""
53  device_id = call.data[CONF_DEVICE_ID]
54  device_registry = dr.async_get(hass)
55  if (device_entry := device_registry.async_get(device_id)) is None:
57  translation_domain=DOMAIN,
58  translation_key="invalid_device",
59  translation_placeholders={"device_id": device_id},
60  )
61 
62  return device_entry
63 
64 
66  hass: HomeAssistant, device_entry: dr.DeviceEntry
67 ) -> ConfigEntry:
68  """Get the config entry related to a device entry."""
69  config_entry: ConfigEntry
70  for entry_id in device_entry.config_entries:
71  if entry := hass.config_entries.async_get_entry(entry_id):
72  if entry.domain == DOMAIN:
73  config_entry = entry
74  return config_entry
75 
76 
78  hass: HomeAssistant, device: dr.DeviceEntry, config: ConfigEntry
79 ) -> TeslemetryVehicleData:
80  """Get the vehicle data for a config entry."""
81  vehicle_data: TeslemetryVehicleData
82  assert device.serial_number is not None
83  for vehicle in config.runtime_data.vehicles:
84  if vehicle.vin == device.serial_number:
85  vehicle_data = vehicle
86  return vehicle_data
87 
88 
90  hass: HomeAssistant, device: dr.DeviceEntry, config: ConfigEntry
91 ) -> TeslemetryEnergyData:
92  """Get the energy site data for a config entry."""
93  energy_data: TeslemetryEnergyData
94  assert device.serial_number is not None
95  for energysite in config.runtime_data.energysites:
96  if str(energysite.id) == device.serial_number:
97  energy_data = energysite
98  return energy_data
99 
100 
101 def async_register_services(hass: HomeAssistant) -> None: # noqa: C901
102  """Set up the Teslemetry services."""
103 
104  async def navigate_gps_request(call: ServiceCall) -> None:
105  """Send lat,lon,order with a vehicle."""
106  device = async_get_device_for_service_call(hass, call)
107  config = async_get_config_for_device(hass, device)
108  vehicle = async_get_vehicle_for_entry(hass, device, config)
109 
110  await wake_up_vehicle(vehicle)
112  vehicle.api.navigation_gps_request(
113  lat=call.data[ATTR_GPS][CONF_LATITUDE],
114  lon=call.data[ATTR_GPS][CONF_LONGITUDE],
115  order=call.data.get(ATTR_ORDER),
116  )
117  )
118 
119  hass.services.async_register(
120  DOMAIN,
121  SERVICE_NAVIGATE_ATTR_GPS_REQUEST,
122  navigate_gps_request,
123  schema=vol.Schema(
124  {
125  vol.Required(CONF_DEVICE_ID): cv.string,
126  vol.Required(ATTR_GPS): {
127  vol.Required(CONF_LATITUDE): cv.latitude,
128  vol.Required(CONF_LONGITUDE): cv.longitude,
129  },
130  vol.Optional(ATTR_ORDER): cv.positive_int,
131  }
132  ),
133  )
134 
135  async def set_scheduled_charging(call: ServiceCall) -> None:
136  """Configure fleet telemetry."""
137  device = async_get_device_for_service_call(hass, call)
138  config = async_get_config_for_device(hass, device)
139  vehicle = async_get_vehicle_for_entry(hass, device, config)
140 
141  time: int | None = None
142  # Convert time to minutes since minute
143  if "time" in call.data:
144  (hours, minutes, *seconds) = call.data["time"].split(":")
145  time = int(hours) * 60 + int(minutes)
146  elif call.data["enable"]:
148  translation_domain=DOMAIN, translation_key="set_scheduled_charging_time"
149  )
150 
151  await wake_up_vehicle(vehicle)
153  vehicle.api.set_scheduled_charging(enable=call.data["enable"], time=time)
154  )
155 
156  hass.services.async_register(
157  DOMAIN,
158  SERVICE_SET_SCHEDULED_CHARGING,
159  set_scheduled_charging,
160  schema=vol.Schema(
161  {
162  vol.Required(CONF_DEVICE_ID): cv.string,
163  vol.Required(ATTR_ENABLE): bool,
164  vol.Optional(ATTR_TIME): str,
165  }
166  ),
167  )
168 
169  async def set_scheduled_departure(call: ServiceCall) -> None:
170  """Configure fleet telemetry."""
171  device = async_get_device_for_service_call(hass, call)
172  config = async_get_config_for_device(hass, device)
173  vehicle = async_get_vehicle_for_entry(hass, device, config)
174 
175  enable = call.data.get("enable", True)
176 
177  # Preconditioning
178  preconditioning_enabled = call.data.get(ATTR_PRECONDITIONING_ENABLED, False)
179  preconditioning_weekdays_only = call.data.get(
180  ATTR_PRECONDITIONING_WEEKDAYS, False
181  )
182  departure_time: int | None = None
183  if ATTR_DEPARTURE_TIME in call.data:
184  (hours, minutes, *seconds) = call.data[ATTR_DEPARTURE_TIME].split(":")
185  departure_time = int(hours) * 60 + int(minutes)
186  elif preconditioning_enabled:
188  translation_domain=DOMAIN,
189  translation_key="set_scheduled_departure_preconditioning",
190  )
191 
192  # Off peak charging
193  off_peak_charging_enabled = call.data.get(ATTR_OFF_PEAK_CHARGING_ENABLED, False)
194  off_peak_charging_weekdays_only = call.data.get(
195  ATTR_OFF_PEAK_CHARGING_WEEKDAYS, False
196  )
197  end_off_peak_time: int | None = None
198 
199  if ATTR_END_OFF_PEAK_TIME in call.data:
200  (hours, minutes, *seconds) = call.data[ATTR_END_OFF_PEAK_TIME].split(":")
201  end_off_peak_time = int(hours) * 60 + int(minutes)
202  elif off_peak_charging_enabled:
204  translation_domain=DOMAIN,
205  translation_key="set_scheduled_departure_off_peak",
206  )
207 
208  await wake_up_vehicle(vehicle)
210  vehicle.api.set_scheduled_departure(
211  enable,
212  preconditioning_enabled,
213  preconditioning_weekdays_only,
214  departure_time,
215  off_peak_charging_enabled,
216  off_peak_charging_weekdays_only,
217  end_off_peak_time,
218  )
219  )
220 
221  hass.services.async_register(
222  DOMAIN,
223  SERVICE_SET_SCHEDULED_DEPARTURE,
224  set_scheduled_departure,
225  schema=vol.Schema(
226  {
227  vol.Required(CONF_DEVICE_ID): cv.string,
228  vol.Optional(ATTR_ENABLE): bool,
229  vol.Optional(ATTR_PRECONDITIONING_ENABLED): bool,
230  vol.Optional(ATTR_PRECONDITIONING_WEEKDAYS): bool,
231  vol.Optional(ATTR_DEPARTURE_TIME): str,
232  vol.Optional(ATTR_OFF_PEAK_CHARGING_ENABLED): bool,
233  vol.Optional(ATTR_OFF_PEAK_CHARGING_WEEKDAYS): bool,
234  vol.Optional(ATTR_END_OFF_PEAK_TIME): str,
235  }
236  ),
237  )
238 
239  async def valet_mode(call: ServiceCall) -> None:
240  """Configure fleet telemetry."""
241  device = async_get_device_for_service_call(hass, call)
242  config = async_get_config_for_device(hass, device)
243  vehicle = async_get_vehicle_for_entry(hass, device, config)
244 
245  await wake_up_vehicle(vehicle)
247  vehicle.api.set_valet_mode(
248  call.data.get("enable"), call.data.get("pin", "")
249  )
250  )
251 
252  hass.services.async_register(
253  DOMAIN,
254  SERVICE_VALET_MODE,
255  valet_mode,
256  schema=vol.Schema(
257  {
258  vol.Required(CONF_DEVICE_ID): cv.string,
259  vol.Required(ATTR_ENABLE): cv.boolean,
260  vol.Required(ATTR_PIN): All(cv.positive_int, Range(min=1000, max=9999)),
261  }
262  ),
263  )
264 
265  async def speed_limit(call: ServiceCall) -> None:
266  """Configure fleet telemetry."""
267  device = async_get_device_for_service_call(hass, call)
268  config = async_get_config_for_device(hass, device)
269  vehicle = async_get_vehicle_for_entry(hass, device, config)
270 
271  await wake_up_vehicle(vehicle)
272  enable = call.data.get("enable")
273  if enable is True:
275  vehicle.api.speed_limit_activate(call.data.get("pin"))
276  )
277  elif enable is False:
279  vehicle.api.speed_limit_deactivate(call.data.get("pin"))
280  )
281 
282  hass.services.async_register(
283  DOMAIN,
284  SERVICE_SPEED_LIMIT,
285  speed_limit,
286  schema=vol.Schema(
287  {
288  vol.Required(CONF_DEVICE_ID): cv.string,
289  vol.Required(ATTR_ENABLE): cv.boolean,
290  vol.Required(ATTR_PIN): All(cv.positive_int, Range(min=1000, max=9999)),
291  }
292  ),
293  )
294 
295  async def time_of_use(call: ServiceCall) -> None:
296  """Configure time of use settings."""
297  device = async_get_device_for_service_call(hass, call)
298  config = async_get_config_for_device(hass, device)
299  site = async_get_energy_site_for_entry(hass, device, config)
300 
301  resp = await handle_command(
302  site.api.time_of_use_settings(call.data.get(ATTR_TOU_SETTINGS))
303  )
304  if "error" in resp:
305  raise HomeAssistantError(
306  translation_domain=DOMAIN,
307  translation_key="command_error",
308  translation_placeholders={"error": resp["error"]},
309  )
310 
311  hass.services.async_register(
312  DOMAIN,
313  SERVICE_TIME_OF_USE,
314  time_of_use,
315  schema=vol.Schema(
316  {
317  vol.Required(CONF_DEVICE_ID): cv.string,
318  vol.Required(ATTR_TOU_SETTINGS): dict,
319  }
320  ),
321  )
bool handle_vehicle_command(Awaitable command)
Definition: helpers.py:50
None wake_up_vehicle(TeslaFleetVehicleData vehicle)
Definition: helpers.py:15
dict[str, Any] handle_command(Awaitable command)
Definition: helpers.py:36
None async_register_services(HomeAssistant hass)
Definition: services.py:101
TeslemetryVehicleData async_get_vehicle_for_entry(HomeAssistant hass, dr.DeviceEntry device, ConfigEntry config)
Definition: services.py:79
dr.DeviceEntry async_get_device_for_service_call(HomeAssistant hass, ServiceCall call)
Definition: services.py:51
ConfigEntry async_get_config_for_device(HomeAssistant hass, dr.DeviceEntry device_entry)
Definition: services.py:67
TeslemetryEnergyData async_get_energy_site_for_entry(HomeAssistant hass, dr.DeviceEntry device, ConfigEntry config)
Definition: services.py:91