Add charging and preconditioning actions to Teslemetry (#144184)

This commit is contained in:
Brett Adams
2025-08-11 19:59:39 +10:00
committed by GitHub
parent 23e6148d3b
commit 203c908730
5 changed files with 616 additions and 49 deletions

View File

@@ -773,6 +773,18 @@
},
"time_of_use": {
"service": "mdi:clock-time-eight-outline"
},
"add_charge_schedule": {
"service": "mdi:calendar-plus"
},
"remove_charge_schedule": {
"service": "mdi:calendar-minus"
},
"add_precondition_schedule": {
"service": "mdi:hvac-outline"
},
"remove_precondition_schedule": {
"service": "mdi:hvac-off-outline"
}
}
}

View File

@@ -22,6 +22,7 @@ ATTR_ID = "id"
ATTR_GPS = "gps"
ATTR_TYPE = "type"
ATTR_VALUE = "value"
ATTR_LOCATION = "location"
ATTR_LOCALE = "locale"
ATTR_ORDER = "order"
ATTR_TIMESTAMP = "timestamp"
@@ -36,6 +37,12 @@ ATTR_DEPARTURE_TIME = "departure_time"
ATTR_OFF_PEAK_CHARGING_ENABLED = "off_peak_charging_enabled"
ATTR_OFF_PEAK_CHARGING_WEEKDAYS = "off_peak_charging_weekdays_only"
ATTR_END_OFF_PEAK_TIME = "end_off_peak_time"
ATTR_DAYS_OF_WEEK = "days_of_week"
ATTR_START_TIME = "start_time"
ATTR_END_TIME = "end_time"
ATTR_ONE_TIME = "one_time"
ATTR_NAME = "name"
ATTR_PRECONDITION_TIME = "precondition_time"
# Services
SERVICE_NAVIGATE_ATTR_GPS_REQUEST = "navigation_gps_request"
@@ -44,6 +51,10 @@ SERVICE_SET_SCHEDULED_DEPARTURE = "set_scheduled_departure"
SERVICE_VALET_MODE = "valet_mode"
SERVICE_SPEED_LIMIT = "speed_limit"
SERVICE_TIME_OF_USE = "time_of_use"
SERVICE_ADD_CHARGE_SCHEDULE = "add_charge_schedule"
SERVICE_REMOVE_CHARGE_SCHEDULE = "remove_charge_schedule"
SERVICE_ADD_PRECONDITION_SCHEDULE = "add_precondition_schedule"
SERVICE_REMOVE_PRECONDITION_SCHEDULE = "remove_precondition_schedule"
def async_get_device_for_service_call(
@@ -315,3 +326,195 @@ def async_setup_services(hass: HomeAssistant) -> None:
}
),
)
async def add_charge_schedule(call: ServiceCall) -> None:
"""Configure charging schedule for a vehicle."""
device = async_get_device_for_service_call(hass, call)
config = async_get_config_for_device(hass, device)
vehicle = async_get_vehicle_for_entry(hass, device, config)
# Extract parameters from the service call
days_of_week = call.data[ATTR_DAYS_OF_WEEK]
# If days_of_week is a list (from select with multiple), convert to comma-separated string
if isinstance(days_of_week, list):
days_of_week = ",".join(days_of_week)
enabled = call.data[ATTR_ENABLE]
# Optional parameters
location = call.data.get(
ATTR_LOCATION,
{
CONF_LATITUDE: hass.config.latitude,
CONF_LONGITUDE: hass.config.longitude,
},
)
# Handle time inputs
start_time = None
if start_time_obj := call.data.get(ATTR_START_TIME):
# Convert time object to minutes since midnight
start_time = start_time_obj.hour * 60 + start_time_obj.minute
end_time = None
if end_time_obj := call.data.get(ATTR_END_TIME):
# Convert time object to minutes since midnight
end_time = end_time_obj.hour * 60 + end_time_obj.minute
one_time = call.data.get(ATTR_ONE_TIME)
schedule_id = call.data.get(ATTR_ID)
name = call.data.get(ATTR_NAME)
await handle_vehicle_command(
vehicle.api.add_charge_schedule(
days_of_week=days_of_week,
enabled=enabled,
lat=location[CONF_LATITUDE],
lon=location[CONF_LONGITUDE],
start_time=start_time,
end_time=end_time,
one_time=one_time,
id=schedule_id,
name=name,
)
)
hass.services.async_register(
DOMAIN,
SERVICE_ADD_CHARGE_SCHEDULE,
add_charge_schedule,
schema=vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(ATTR_DAYS_OF_WEEK): cv.ensure_list,
vol.Required(ATTR_ENABLE): cv.boolean,
vol.Optional(ATTR_LOCATION): {
vol.Required(CONF_LATITUDE): cv.latitude,
vol.Required(CONF_LONGITUDE): cv.longitude,
},
vol.Optional(ATTR_START_TIME): cv.time,
vol.Optional(ATTR_END_TIME): cv.time,
vol.Optional(ATTR_ONE_TIME): cv.boolean,
vol.Optional(ATTR_ID): cv.positive_int,
vol.Optional(ATTR_NAME): cv.string,
}
),
)
async def remove_charge_schedule(call: ServiceCall) -> None:
"""Remove a charging schedule for a vehicle."""
device = async_get_device_for_service_call(hass, call)
config = async_get_config_for_device(hass, device)
vehicle = async_get_vehicle_for_entry(hass, device, config)
# Extract parameters from the service call
schedule_id = call.data[ATTR_ID]
await handle_vehicle_command(
vehicle.api.remove_charge_schedule(
id=schedule_id,
)
)
hass.services.async_register(
DOMAIN,
SERVICE_REMOVE_CHARGE_SCHEDULE,
remove_charge_schedule,
schema=vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(ATTR_ID): cv.positive_int,
}
),
)
async def add_precondition_schedule(call: ServiceCall) -> None:
"""Add or modify a precondition schedule for a vehicle."""
device = async_get_device_for_service_call(hass, call)
config = async_get_config_for_device(hass, device)
vehicle = async_get_vehicle_for_entry(hass, device, config)
# Extract parameters from the service call
days_of_week = call.data[ATTR_DAYS_OF_WEEK]
# If days_of_week is a list (from select with multiple), convert to comma-separated string
if isinstance(days_of_week, list):
days_of_week = ",".join(days_of_week)
enabled = call.data[ATTR_ENABLE]
location = call.data.get(
ATTR_LOCATION,
{
CONF_LATITUDE: hass.config.latitude,
CONF_LONGITUDE: hass.config.longitude,
},
)
# Convert time object to minutes since midnight
precondition_time = (
call.data[ATTR_PRECONDITION_TIME].hour * 60
+ call.data[ATTR_PRECONDITION_TIME].minute
)
# Optional parameters
schedule_id = call.data.get(ATTR_ID)
one_time = call.data.get(ATTR_ONE_TIME)
name = call.data.get(ATTR_NAME)
await handle_vehicle_command(
vehicle.api.add_precondition_schedule(
days_of_week=days_of_week,
enabled=enabled,
lat=location[CONF_LATITUDE],
lon=location[CONF_LONGITUDE],
precondition_time=precondition_time,
id=schedule_id,
one_time=one_time,
name=name,
)
)
hass.services.async_register(
DOMAIN,
SERVICE_ADD_PRECONDITION_SCHEDULE,
add_precondition_schedule,
schema=vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(ATTR_DAYS_OF_WEEK): cv.ensure_list,
vol.Required(ATTR_ENABLE): cv.boolean,
vol.Optional(ATTR_LOCATION): {
vol.Required(CONF_LATITUDE): cv.latitude,
vol.Required(CONF_LONGITUDE): cv.longitude,
},
vol.Required(ATTR_PRECONDITION_TIME): cv.time,
vol.Optional(ATTR_ID): cv.positive_int,
vol.Optional(ATTR_ONE_TIME): cv.boolean,
vol.Optional(ATTR_NAME): cv.string,
}
),
)
async def remove_precondition_schedule(call: ServiceCall) -> None:
"""Remove a preconditioning schedule for a vehicle."""
device = async_get_device_for_service_call(hass, call)
config = async_get_config_for_device(hass, device)
vehicle = async_get_vehicle_for_entry(hass, device, config)
# Extract parameters from the service call
schedule_id = call.data[ATTR_ID]
await handle_vehicle_command(
vehicle.api.remove_precondition_schedule(
id=schedule_id,
)
)
hass.services.async_register(
DOMAIN,
SERVICE_REMOVE_PRECONDITION_SCHEDULE,
remove_precondition_schedule,
schema=vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(ATTR_ID): cv.positive_int,
}
),
)

View File

@@ -130,3 +130,139 @@ speed_limit:
min: 1000
max: 9999
mode: box
add_charge_schedule:
fields:
device_id:
required: true
selector:
device:
filter:
integration: teslemetry
days_of_week:
required: true
selector:
select:
options:
- monday
- tuesday
- wednesday
- thursday
- friday
- saturday
- sunday
multiple: true
translation_key: days_of_week
enable:
required: true
selector:
boolean:
location:
required: false
example: '{"latitude": -27.9699373, "longitude": 153.4081865}'
selector:
location:
radius: false
start_time:
required: false
selector:
time:
end_time:
required: false
selector:
time:
one_time:
required: false
selector:
boolean:
id:
required: false
selector:
number:
min: 1
mode: box
name:
required: false
selector:
text:
remove_charge_schedule:
fields:
device_id:
required: true
selector:
device:
filter:
integration: teslemetry
id:
required: true
selector:
number:
min: 1
mode: box
add_precondition_schedule:
fields:
device_id:
required: true
selector:
device:
filter:
integration: teslemetry
days_of_week:
required: true
selector:
select:
options:
- monday
- tuesday
- wednesday
- thursday
- friday
- saturday
- sunday
multiple: true
translation_key: days_of_week
enable:
required: true
selector:
boolean:
location:
required: false
example: '{"latitude": -27.9699373, "longitude": 153.4081865}'
selector:
location:
radius: false
precondition_time:
required: true
selector:
time:
id:
required: false
selector:
number:
min: 1
mode: box
one_time:
required: false
selector:
boolean:
name:
required: false
selector:
text:
remove_precondition_schedule:
fields:
device_id:
required: true
selector:
device:
filter:
integration: teslemetry
id:
required: true
selector:
number:
min: 1
mode: box

View File

@@ -3,6 +3,26 @@
"unavailable": "Unavailable",
"abort": "Abort",
"vehicle": "Vehicle",
"wake_up_failed": "Failed to wake up vehicle: {message}",
"wake_up_timeout": "Timed out trying to wake up vehicle",
"schedule_id": "Schedule ID",
"schedule_id_description": "The ID of the schedule, use an existing ID to modify.",
"days_of_week": "Days of week",
"days_of_week_description": "Select which days this schedule should be enabled on. You can select multiple days.",
"one_time": "One-time",
"one_time_description": "If this is a one-time schedule.",
"location_description": "The approximate location the vehicle must be at to use this schedule. Defaults to Home Assistant's configured location.",
"start_time": "Start time",
"start_time_description": "The time this schedule begins, e.g. 01:05 for 1:05 AM.",
"end_time": "End time",
"end_time_description": "The time this schedule ends, e.g. 01:05 for 1:05 AM.",
"precondition_time": "Precondition time",
"precondition_time_description": "The time the vehicle should complete preconditioning, e.g. 01:05 for 1:05 AM.",
"schedule_name_description": "The name of the schedule.",
"vehicle_to_schedule": "Vehicle to schedule.",
"vehicle_to_remove_schedule": "Vehicle to remove schedule from.",
"schedule_enable_description": "If this schedule should be considered for execution.",
"schedule_id_remove_description": "The ID of the schedule to remove.",
"descr_pin": "4-digit code to enable or disable the setting"
},
"config": {
@@ -1079,15 +1099,6 @@
"invalid_cop_temp": {
"message": "Cabin overheat protection does not support that temperature"
},
"set_scheduled_charging_time": {
"message": "Time required to complete the operation"
},
"set_scheduled_departure_preconditioning": {
"message": "Departure time required to enable preconditioning"
},
"set_scheduled_departure_off_peak": {
"message": "To enable scheduled departure, 'End off-peak time' is required."
},
"invalid_device": {
"message": "Invalid device ID: {device_id}"
},
@@ -1145,7 +1156,7 @@
"description": "Sets a time at which charging should be started.",
"fields": {
"device_id": {
"description": "Vehicle to schedule.",
"description": "[%key:component::teslemetry::common::vehicle_to_schedule%]",
"name": "[%key:component::teslemetry::common::vehicle%]"
},
"enable": {
@@ -1167,7 +1178,7 @@
"name": "Departure time"
},
"device_id": {
"description": "Vehicle to schedule.",
"description": "[%key:component::teslemetry::common::vehicle_to_schedule%]",
"name": "[%key:component::teslemetry::common::vehicle%]"
},
"enable": {
@@ -1246,6 +1257,127 @@
}
},
"name": "Set valet mode"
},
"add_charge_schedule": {
"description": "Adds or modifies a charging schedule for a vehicle.",
"fields": {
"device_id": {
"description": "[%key:component::teslemetry::common::vehicle_to_schedule%]",
"name": "[%key:component::teslemetry::common::vehicle%]"
},
"days_of_week": {
"description": "[%key:component::teslemetry::common::days_of_week_description%]",
"name": "[%key:component::teslemetry::common::days_of_week%]"
},
"enable": {
"description": "[%key:component::teslemetry::common::schedule_enable_description%]",
"name": "[%key:common::action::enable%]"
},
"location": {
"description": "[%key:component::teslemetry::common::location_description%]",
"name": "Location"
},
"start_time": {
"description": "[%key:component::teslemetry::common::start_time_description%]",
"name": "[%key:component::teslemetry::common::start_time%]"
},
"end_time": {
"description": "[%key:component::teslemetry::common::end_time_description%]",
"name": "[%key:component::teslemetry::common::end_time%]"
},
"one_time": {
"description": "[%key:component::teslemetry::common::one_time_description%]",
"name": "[%key:component::teslemetry::common::one_time%]"
},
"id": {
"description": "[%key:component::teslemetry::common::schedule_id_description%]",
"name": "[%key:component::teslemetry::common::schedule_id%]"
},
"name": {
"description": "[%key:component::teslemetry::common::schedule_name_description%]",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Add charge schedule"
},
"remove_charge_schedule": {
"description": "Removes a charging schedule for a vehicle.",
"fields": {
"device_id": {
"description": "[%key:component::teslemetry::common::vehicle_to_remove_schedule%]",
"name": "[%key:component::teslemetry::common::vehicle%]"
},
"id": {
"description": "[%key:component::teslemetry::common::schedule_id_remove_description%]",
"name": "[%key:component::teslemetry::common::schedule_id%]"
}
},
"name": "Remove charge schedule"
},
"add_precondition_schedule": {
"description": "Adds or modifies a preconditioning schedule for a vehicle.",
"fields": {
"device_id": {
"description": "[%key:component::teslemetry::common::vehicle_to_schedule%]",
"name": "[%key:component::teslemetry::common::vehicle%]"
},
"days_of_week": {
"description": "[%key:component::teslemetry::common::days_of_week_description%]",
"name": "[%key:component::teslemetry::common::days_of_week%]"
},
"enable": {
"description": "[%key:component::teslemetry::common::schedule_enable_description%]",
"name": "[%key:common::action::enable%]"
},
"location": {
"description": "[%key:component::teslemetry::common::location_description%]",
"name": "Location"
},
"precondition_time": {
"description": "[%key:component::teslemetry::common::precondition_time_description%]",
"name": "[%key:component::teslemetry::common::precondition_time%]"
},
"id": {
"description": "[%key:component::teslemetry::common::schedule_id_description%]",
"name": "[%key:component::teslemetry::common::schedule_id%]"
},
"one_time": {
"description": "[%key:component::teslemetry::common::one_time_description%]",
"name": "[%key:component::teslemetry::common::one_time%]"
},
"name": {
"description": "[%key:component::teslemetry::common::schedule_name_description%]",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Add precondition schedule"
},
"remove_precondition_schedule": {
"description": "Removes a preconditioning schedule for a vehicle.",
"fields": {
"device_id": {
"description": "[%key:component::teslemetry::common::vehicle_to_remove_schedule%]",
"name": "[%key:component::teslemetry::common::vehicle%]"
},
"id": {
"description": "[%key:component::teslemetry::common::schedule_id_remove_description%]",
"name": "[%key:component::teslemetry::common::schedule_id%]"
}
},
"name": "Remove precondition schedule"
}
},
"selector": {
"days_of_week": {
"options": {
"monday": "[%key:common::time::monday%]",
"tuesday": "[%key:common::time::tuesday%]",
"wednesday": "[%key:common::time::wednesday%]",
"thursday": "[%key:common::time::thursday%]",
"friday": "[%key:common::time::friday%]",
"saturday": "[%key:common::time::saturday%]",
"sunday": "[%key:common::time::sunday%]"
}
}
}
}

View File

@@ -1,23 +1,36 @@
"""Test the Teslemetry services."""
from datetime import time
from unittest.mock import patch
import pytest
from homeassistant.components.teslemetry.const import DOMAIN
from homeassistant.components.teslemetry.services import (
ATTR_DAYS_OF_WEEK,
ATTR_DEPARTURE_TIME,
ATTR_ENABLE,
ATTR_END_OFF_PEAK_TIME,
ATTR_END_TIME,
ATTR_GPS,
ATTR_ID,
ATTR_LOCATION,
ATTR_NAME,
ATTR_OFF_PEAK_CHARGING_ENABLED,
ATTR_OFF_PEAK_CHARGING_WEEKDAYS,
ATTR_ONE_TIME,
ATTR_PIN,
ATTR_PRECONDITION_TIME,
ATTR_PRECONDITIONING_ENABLED,
ATTR_PRECONDITIONING_WEEKDAYS,
ATTR_START_TIME,
ATTR_TIME,
ATTR_TOU_SETTINGS,
SERVICE_ADD_CHARGE_SCHEDULE,
SERVICE_ADD_PRECONDITION_SCHEDULE,
SERVICE_NAVIGATE_ATTR_GPS_REQUEST,
SERVICE_REMOVE_CHARGE_SCHEDULE,
SERVICE_REMOVE_PRECONDITION_SCHEDULE,
SERVICE_SET_SCHEDULED_CHARGING,
SERVICE_SET_SCHEDULED_DEPARTURE,
SERVICE_SPEED_LIMIT,
@@ -75,23 +88,12 @@ async def test_services(
{
CONF_DEVICE_ID: vehicle_device,
ATTR_ENABLE: True,
ATTR_TIME: "6:00",
ATTR_TIME: "06:00", # 6:00 AM
},
blocking=True,
)
set_scheduled_charging.assert_called_once()
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_SCHEDULED_CHARGING,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_ENABLE: True,
},
blocking=True,
)
with patch(
"tesla_fleet_api.teslemetry.Vehicle.set_scheduled_departure",
return_value=COMMAND_OK,
@@ -104,39 +106,15 @@ async def test_services(
ATTR_ENABLE: True,
ATTR_PRECONDITIONING_ENABLED: True,
ATTR_PRECONDITIONING_WEEKDAYS: False,
ATTR_DEPARTURE_TIME: "6:00",
ATTR_DEPARTURE_TIME: "06:00", # 6:00 AM
ATTR_OFF_PEAK_CHARGING_ENABLED: True,
ATTR_OFF_PEAK_CHARGING_WEEKDAYS: False,
ATTR_END_OFF_PEAK_TIME: "5:00",
ATTR_END_OFF_PEAK_TIME: "05:00", # 5:00 AM
},
blocking=True,
)
set_scheduled_departure.assert_called_once()
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_SCHEDULED_DEPARTURE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_ENABLE: True,
ATTR_PRECONDITIONING_ENABLED: True,
},
blocking=True,
)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_SCHEDULED_DEPARTURE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_ENABLE: True,
ATTR_OFF_PEAK_CHARGING_ENABLED: True,
},
blocking=True,
)
with patch(
"tesla_fleet_api.teslemetry.Vehicle.set_valet_mode",
return_value=COMMAND_OK,
@@ -200,6 +178,112 @@ async def test_services(
)
set_time_of_use.assert_called_once()
with patch(
"tesla_fleet_api.teslemetry.Vehicle.add_charge_schedule",
return_value=COMMAND_OK,
) as add_charge_schedule:
await hass.services.async_call(
DOMAIN,
SERVICE_ADD_CHARGE_SCHEDULE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_DAYS_OF_WEEK: ["Monday", "Tuesday"],
ATTR_ENABLE: True,
ATTR_LOCATION: {CONF_LATITUDE: lat, CONF_LONGITUDE: lon},
ATTR_START_TIME: time(7, 0, 0), # 7:00 AM
ATTR_END_TIME: time(18, 0, 0), # 6:00 PM
ATTR_ONE_TIME: False,
ATTR_NAME: "Test Schedule",
},
blocking=True,
)
add_charge_schedule.assert_called_once()
# Test add_charge_schedule with minimal required parameters
with patch(
"tesla_fleet_api.teslemetry.Vehicle.add_charge_schedule",
return_value=COMMAND_OK,
) as add_charge_schedule_minimal:
await hass.services.async_call(
DOMAIN,
SERVICE_ADD_CHARGE_SCHEDULE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_DAYS_OF_WEEK: ["Monday", "Tuesday"],
ATTR_ENABLE: True,
},
blocking=True,
)
add_charge_schedule_minimal.assert_called_once()
with patch(
"tesla_fleet_api.teslemetry.Vehicle.remove_charge_schedule",
return_value=COMMAND_OK,
) as remove_charge_schedule:
await hass.services.async_call(
DOMAIN,
SERVICE_REMOVE_CHARGE_SCHEDULE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_ID: 123,
},
blocking=True,
)
remove_charge_schedule.assert_called_once()
with patch(
"tesla_fleet_api.teslemetry.Vehicle.add_precondition_schedule",
return_value=COMMAND_OK,
) as add_precondition_schedule:
await hass.services.async_call(
DOMAIN,
SERVICE_ADD_PRECONDITION_SCHEDULE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_DAYS_OF_WEEK: ["Monday", "Tuesday"],
ATTR_ENABLE: True,
ATTR_LOCATION: {CONF_LATITUDE: lat, CONF_LONGITUDE: lon},
ATTR_PRECONDITION_TIME: time(7, 0, 0), # 7:00 AM
ATTR_ONE_TIME: False,
ATTR_NAME: "Test Precondition Schedule",
},
blocking=True,
)
add_precondition_schedule.assert_called_once()
# Test add_precondition_schedule with minimal required parameters
with patch(
"tesla_fleet_api.teslemetry.Vehicle.add_precondition_schedule",
return_value=COMMAND_OK,
) as add_precondition_schedule_minimal:
await hass.services.async_call(
DOMAIN,
SERVICE_ADD_PRECONDITION_SCHEDULE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_DAYS_OF_WEEK: ["Monday", "Tuesday"],
ATTR_ENABLE: True,
ATTR_PRECONDITION_TIME: time(8, 0, 0), # 8:00 AM
},
blocking=True,
)
add_precondition_schedule_minimal.assert_called_once()
with patch(
"tesla_fleet_api.teslemetry.Vehicle.remove_precondition_schedule",
return_value=COMMAND_OK,
) as remove_precondition_schedule:
await hass.services.async_call(
DOMAIN,
SERVICE_REMOVE_PRECONDITION_SCHEDULE,
{
CONF_DEVICE_ID: vehicle_device,
ATTR_ID: 123,
},
blocking=True,
)
remove_precondition_schedule.assert_called_once()
with (
patch(
"tesla_fleet_api.teslemetry.EnergySite.time_of_use_settings",