mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
2024.2.3 (#111133)
This commit is contained in:
commit
1ee39275fc
@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.7.2"]
|
||||
"requirements": ["aioairzone==0.7.4"]
|
||||
}
|
||||
|
@ -52,9 +52,11 @@ def get_service(
|
||||
return None
|
||||
|
||||
# Ordered list of URLs
|
||||
if config.get(CONF_URL) and not a_obj.add(config[CONF_URL]):
|
||||
_LOGGER.error("Invalid Apprise URL(s) supplied")
|
||||
return None
|
||||
if urls := config.get(CONF_URL):
|
||||
for entry in urls:
|
||||
if not a_obj.add(entry):
|
||||
_LOGGER.error("One or more specified Apprise URL(s) are invalid")
|
||||
return None
|
||||
|
||||
return AppriseNotificationService(a_obj)
|
||||
|
||||
|
@ -68,13 +68,22 @@ async def _async_reproduce_states(
|
||||
[ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW],
|
||||
)
|
||||
|
||||
if ATTR_PRESET_MODE in state.attributes:
|
||||
if (
|
||||
ATTR_PRESET_MODE in state.attributes
|
||||
and state.attributes[ATTR_PRESET_MODE] is not None
|
||||
):
|
||||
await call_service(SERVICE_SET_PRESET_MODE, [ATTR_PRESET_MODE])
|
||||
|
||||
if ATTR_SWING_MODE in state.attributes:
|
||||
if (
|
||||
ATTR_SWING_MODE in state.attributes
|
||||
and state.attributes[ATTR_SWING_MODE] is not None
|
||||
):
|
||||
await call_service(SERVICE_SET_SWING_MODE, [ATTR_SWING_MODE])
|
||||
|
||||
if ATTR_FAN_MODE in state.attributes:
|
||||
if (
|
||||
ATTR_FAN_MODE in state.attributes
|
||||
and state.attributes[ATTR_FAN_MODE] is not None
|
||||
):
|
||||
await call_service(SERVICE_SET_FAN_MODE, [ATTR_FAN_MODE])
|
||||
|
||||
if ATTR_HUMIDITY in state.attributes:
|
||||
|
@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["deluge_client"],
|
||||
"requirements": ["deluge-client==1.7.1"]
|
||||
"requirements": ["deluge-client==1.10.2"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||
"requirements": ["py-sucks==0.9.9", "deebot-client==5.2.1"]
|
||||
"requirements": ["py-sucks==0.9.9", "deebot-client==5.2.2"]
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ class EcovacsLegacyVacuum(StateVacuumEntity):
|
||||
This will not change the entity's state. If the error caused the state
|
||||
to change, that will come through as a separate on_status event
|
||||
"""
|
||||
if error == "no_error":
|
||||
if error in ["no_error", sucks.ERROR_CODES["100"]]:
|
||||
self.error = None
|
||||
else:
|
||||
self.error = error
|
||||
|
@ -1,9 +1,12 @@
|
||||
"""Support for Enigma2 media players."""
|
||||
from __future__ import annotations
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
import contextlib
|
||||
from logging import getLogger
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError, ServerDisconnectedError
|
||||
from openwebif.api import OpenWebIfDevice
|
||||
from openwebif.enums import RemoteControlCodes, SetVolumeOption
|
||||
from openwebif.enums import PowerState, RemoteControlCodes, SetVolumeOption
|
||||
import voluptuous as vol
|
||||
from yarl import URL
|
||||
|
||||
@ -50,6 +53,8 @@ ATTR_MEDIA_DESCRIPTION = "media_description"
|
||||
ATTR_MEDIA_END_TIME = "media_end_time"
|
||||
ATTR_MEDIA_START_TIME = "media_start_time"
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
@ -143,7 +148,12 @@ class Enigma2Device(MediaPlayerEntity):
|
||||
|
||||
async def async_turn_off(self) -> None:
|
||||
"""Turn off media player."""
|
||||
await self._device.turn_off()
|
||||
if self._device.turn_off_to_deep:
|
||||
with contextlib.suppress(ServerDisconnectedError):
|
||||
await self._device.set_powerstate(PowerState.DEEP_STANDBY)
|
||||
self._attr_available = False
|
||||
else:
|
||||
await self._device.set_powerstate(PowerState.STANDBY)
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn the media player on."""
|
||||
@ -191,8 +201,19 @@ class Enigma2Device(MediaPlayerEntity):
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update state of the media_player."""
|
||||
await self._device.update()
|
||||
self._attr_available = not self._device.is_offline
|
||||
try:
|
||||
await self._device.update()
|
||||
except ClientConnectorError as err:
|
||||
if self._attr_available:
|
||||
_LOGGER.warning(
|
||||
"%s is unavailable. Error: %s", self._device.base.host, err
|
||||
)
|
||||
self._attr_available = False
|
||||
return
|
||||
|
||||
if not self._attr_available:
|
||||
_LOGGER.debug("%s is available", self._device.base.host)
|
||||
self._attr_available = True
|
||||
|
||||
if not self._device.status.in_standby:
|
||||
self._attr_extra_state_attributes = {
|
||||
|
@ -6,5 +6,5 @@
|
||||
"dependencies": ["network"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/govee_light_local",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["govee-local-api==1.4.1"]
|
||||
"requirements": ["govee-local-api==1.4.4"]
|
||||
}
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.42", "babel==2.13.1"]
|
||||
"requirements": ["holidays==0.43", "babel==2.13.1"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/html5",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["http_ece", "py_vapid", "pywebpush"],
|
||||
"requirements": ["pywebpush==1.9.2"]
|
||||
"requirements": ["pywebpush==1.14.1"]
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import slugify
|
||||
@ -186,6 +186,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
lutron_client.connect()
|
||||
_LOGGER.info("Connected to main repeater at %s", host)
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
entry_data = LutronData(
|
||||
client=lutron_client,
|
||||
binary_sensors=[],
|
||||
@ -201,17 +204,39 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
for area in lutron_client.areas:
|
||||
_LOGGER.debug("Working on area %s", area.name)
|
||||
for output in area.outputs:
|
||||
platform = None
|
||||
_LOGGER.debug("Working on output %s", output.type)
|
||||
if output.type == "SYSTEM_SHADE":
|
||||
entry_data.covers.append((area.name, output))
|
||||
platform = Platform.COVER
|
||||
elif output.type == "CEILING_FAN_TYPE":
|
||||
entry_data.fans.append((area.name, output))
|
||||
platform = Platform.FAN
|
||||
# Deprecated, should be removed in 2024.8
|
||||
entry_data.lights.append((area.name, output))
|
||||
elif output.is_dimmable:
|
||||
entry_data.lights.append((area.name, output))
|
||||
platform = Platform.LIGHT
|
||||
else:
|
||||
entry_data.switches.append((area.name, output))
|
||||
platform = Platform.SWITCH
|
||||
|
||||
_async_check_entity_unique_id(
|
||||
hass,
|
||||
entity_registry,
|
||||
platform,
|
||||
output.uuid,
|
||||
output.legacy_uuid,
|
||||
entry_data.client.guid,
|
||||
)
|
||||
_async_check_device_identifiers(
|
||||
hass,
|
||||
device_registry,
|
||||
output.uuid,
|
||||
output.legacy_uuid,
|
||||
entry_data.client.guid,
|
||||
)
|
||||
|
||||
for keypad in area.keypads:
|
||||
for button in keypad.buttons:
|
||||
# If the button has a function assigned to it, add it as a scene
|
||||
@ -228,11 +253,46 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
)
|
||||
entry_data.scenes.append((area.name, keypad, button, led))
|
||||
|
||||
platform = Platform.SCENE
|
||||
_async_check_entity_unique_id(
|
||||
hass,
|
||||
entity_registry,
|
||||
platform,
|
||||
button.uuid,
|
||||
button.legacy_uuid,
|
||||
entry_data.client.guid,
|
||||
)
|
||||
if led is not None:
|
||||
platform = Platform.SWITCH
|
||||
_async_check_entity_unique_id(
|
||||
hass,
|
||||
entity_registry,
|
||||
platform,
|
||||
led.uuid,
|
||||
led.legacy_uuid,
|
||||
entry_data.client.guid,
|
||||
)
|
||||
|
||||
entry_data.buttons.append(LutronButton(hass, area.name, keypad, button))
|
||||
if area.occupancy_group is not None:
|
||||
entry_data.binary_sensors.append((area.name, area.occupancy_group))
|
||||
platform = Platform.BINARY_SENSOR
|
||||
_async_check_entity_unique_id(
|
||||
hass,
|
||||
entity_registry,
|
||||
platform,
|
||||
area.occupancy_group.uuid,
|
||||
area.occupancy_group.legacy_uuid,
|
||||
entry_data.client.guid,
|
||||
)
|
||||
_async_check_device_identifiers(
|
||||
hass,
|
||||
device_registry,
|
||||
area.occupancy_group.uuid,
|
||||
area.occupancy_group.legacy_uuid,
|
||||
entry_data.client.guid,
|
||||
)
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, lutron_client.guid)},
|
||||
@ -247,6 +307,52 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
return True
|
||||
|
||||
|
||||
def _async_check_entity_unique_id(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
platform: str,
|
||||
uuid: str,
|
||||
legacy_uuid: str,
|
||||
controller_guid: str,
|
||||
) -> None:
|
||||
"""If uuid becomes available update to use it."""
|
||||
|
||||
if not uuid:
|
||||
return
|
||||
|
||||
unique_id = f"{controller_guid}_{legacy_uuid}"
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
domain=platform, platform=DOMAIN, unique_id=unique_id
|
||||
)
|
||||
|
||||
if entity_id:
|
||||
new_unique_id = f"{controller_guid}_{uuid}"
|
||||
_LOGGER.debug("Updating entity id from %s to %s", unique_id, new_unique_id)
|
||||
entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id)
|
||||
|
||||
|
||||
def _async_check_device_identifiers(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
uuid: str,
|
||||
legacy_uuid: str,
|
||||
controller_guid: str,
|
||||
) -> None:
|
||||
"""If uuid becomes available update to use it."""
|
||||
|
||||
if not uuid:
|
||||
return
|
||||
|
||||
unique_id = f"{controller_guid}_{legacy_uuid}"
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, unique_id)})
|
||||
if device:
|
||||
new_unique_id = f"{controller_guid}_{uuid}"
|
||||
_LOGGER.debug("Updating device id from %s to %s", unique_id, new_unique_id)
|
||||
device_registry.async_update_device(
|
||||
device.id, new_identifiers={(DOMAIN, new_unique_id)}
|
||||
)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Clean up resources and entities associated with the integration."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@ -41,11 +41,11 @@ class LutronBaseEntity(Entity):
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str | None:
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
# Temporary fix for https://github.com/thecynic/pylutron/issues/70
|
||||
|
||||
if self._lutron_device.uuid is None:
|
||||
return None
|
||||
return f"{self._controller.guid}_{self._lutron_device.legacy_uuid}"
|
||||
return f"{self._controller.guid}_{self._lutron_device.uuid}"
|
||||
|
||||
def update(self) -> None:
|
||||
@ -63,7 +63,7 @@ class LutronDevice(LutronBaseEntity):
|
||||
"""Initialize the device."""
|
||||
super().__init__(area_name, lutron_device, controller)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, lutron_device.uuid)},
|
||||
identifiers={(DOMAIN, self.unique_id)},
|
||||
manufacturer="Lutron",
|
||||
name=lutron_device.name,
|
||||
suggested_area=area_name,
|
||||
|
@ -150,5 +150,5 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
await store.async_save(savable_state(hass))
|
||||
|
||||
if CONF_CLOUDHOOK_URL in entry.data:
|
||||
with suppress(cloud.CloudNotAvailable):
|
||||
with suppress(cloud.CloudNotAvailable, ValueError):
|
||||
await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
|
@ -21,5 +21,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["motionblinds"],
|
||||
"requirements": ["motionblinds==0.6.20"]
|
||||
"requirements": ["motionblinds==0.6.21"]
|
||||
}
|
||||
|
@ -354,9 +354,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self._user = self._reauth_entry.data[CONF_USERNAME]
|
||||
self._server = self._reauth_entry.data[CONF_HUB]
|
||||
self._api_type = self._reauth_entry.data[CONF_API_TYPE]
|
||||
self._api_type = self._reauth_entry.data.get(CONF_API_TYPE, APIType.CLOUD)
|
||||
|
||||
if self._reauth_entry.data[CONF_API_TYPE] == APIType.LOCAL:
|
||||
if self._api_type == APIType.LOCAL:
|
||||
self._host = self._reauth_entry.data[CONF_HOST]
|
||||
|
||||
return await self.async_step_user(dict(entry_data))
|
||||
|
@ -100,10 +100,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
async with asyncio.timeout(host.api.timeout * (RETRY_ATTEMPTS + 2)):
|
||||
try:
|
||||
return await host.api.check_new_firmware()
|
||||
except (ReolinkError, asyncio.exceptions.CancelledError) as err:
|
||||
task = asyncio.current_task()
|
||||
if task is not None:
|
||||
task.uncancel()
|
||||
except ReolinkError as err:
|
||||
if starting:
|
||||
_LOGGER.debug(
|
||||
"Error checking Reolink firmware update at startup "
|
||||
@ -133,15 +130,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
update_interval=FIRMWARE_UPDATE_INTERVAL,
|
||||
)
|
||||
# Fetch initial data so we have data when entities subscribe
|
||||
try:
|
||||
# If camera WAN blocked, firmware check fails, do not prevent setup
|
||||
await asyncio.gather(
|
||||
device_coordinator.async_config_entry_first_refresh(),
|
||||
firmware_coordinator.async_config_entry_first_refresh(),
|
||||
)
|
||||
except ConfigEntryNotReady:
|
||||
results = await asyncio.gather(
|
||||
device_coordinator.async_config_entry_first_refresh(),
|
||||
firmware_coordinator.async_config_entry_first_refresh(),
|
||||
return_exceptions=True,
|
||||
)
|
||||
# If camera WAN blocked, firmware check fails, do not prevent setup
|
||||
# so don't check firmware_coordinator exceptions
|
||||
if isinstance(results[0], BaseException):
|
||||
await host.stop()
|
||||
raise
|
||||
raise results[0]
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = ReolinkData(
|
||||
host=host,
|
||||
|
@ -18,5 +18,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["reolink_aio"],
|
||||
"requirements": ["reolink-aio==0.8.7"]
|
||||
"requirements": ["reolink-aio==0.8.8"]
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["rokuecp"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["rokuecp==0.19.0"],
|
||||
"requirements": ["rokuecp==0.19.1"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "roku:ecp",
|
||||
|
@ -24,7 +24,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/roomba",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["paho_mqtt", "roombapy"],
|
||||
"requirements": ["roombapy==1.6.10"],
|
||||
"requirements": ["roombapy==1.6.12"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_amzn-alexa._tcp.local.",
|
||||
|
@ -12,6 +12,7 @@ from aiotankerkoenig import (
|
||||
TankerkoenigConnectionError,
|
||||
TankerkoenigError,
|
||||
TankerkoenigInvalidKeyError,
|
||||
TankerkoenigRateLimitError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -19,7 +20,7 @@ from homeassistant.const import CONF_API_KEY, CONF_SHOW_ON_MAP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_FUEL_TYPES, CONF_STATIONS
|
||||
|
||||
@ -78,13 +79,22 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
station_ids = list(self.stations)
|
||||
|
||||
prices = {}
|
||||
|
||||
# The API seems to only return at most 10 results, so split the list in chunks of 10
|
||||
# and merge it together.
|
||||
for index in range(ceil(len(station_ids) / 10)):
|
||||
data = await self._tankerkoenig.prices(
|
||||
station_ids[index * 10 : (index + 1) * 10]
|
||||
)
|
||||
try:
|
||||
data = await self._tankerkoenig.prices(
|
||||
station_ids[index * 10 : (index + 1) * 10]
|
||||
)
|
||||
except TankerkoenigInvalidKeyError as err:
|
||||
raise ConfigEntryAuthFailed(err) from err
|
||||
except (TankerkoenigError, TankerkoenigConnectionError) as err:
|
||||
if isinstance(err, TankerkoenigRateLimitError):
|
||||
_LOGGER.warning(
|
||||
"API rate limit reached, consider to increase polling interval"
|
||||
)
|
||||
raise UpdateFailed(err) from err
|
||||
|
||||
prices.update(data)
|
||||
|
||||
return prices
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/tankerkoenig",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aiotankerkoenig"],
|
||||
"requirements": ["aiotankerkoenig==0.3.0"]
|
||||
"requirements": ["aiotankerkoenig==0.4.1"]
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from tesla_fleet_api.exceptions import TeslaFleetError
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
@ -45,11 +48,22 @@ class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator
|
||||
async def wake_up_if_asleep(self) -> None:
|
||||
"""Wake up the vehicle if its asleep."""
|
||||
async with self._wakelock:
|
||||
times = 0
|
||||
while self.coordinator.data["state"] != TeslemetryState.ONLINE:
|
||||
state = (await self.api.wake_up())["response"]["state"]
|
||||
try:
|
||||
if times == 0:
|
||||
cmd = await self.api.wake_up()
|
||||
else:
|
||||
cmd = await self.api.vehicle()
|
||||
state = cmd["response"]["state"]
|
||||
except TeslaFleetError as e:
|
||||
raise HomeAssistantError(str(e)) from e
|
||||
self.coordinator.data["state"] = state
|
||||
if state != TeslemetryState.ONLINE:
|
||||
await asyncio.sleep(5)
|
||||
times += 1
|
||||
if times >= 4: # Give up after 30 seconds total
|
||||
raise HomeAssistantError("Could not wake up vehicle")
|
||||
await asyncio.sleep(times * 5)
|
||||
|
||||
def get(self, key: str | None = None, default: Any | None = None) -> Any:
|
||||
"""Return a specific value from coordinator data."""
|
||||
|
@ -11,6 +11,7 @@ from tessie_api import (
|
||||
)
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_HVAC_MODE,
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
@ -112,9 +113,12 @@ class TessieClimateEntity(TessieEntity, ClimateEntity):
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the climate temperature."""
|
||||
temp = kwargs[ATTR_TEMPERATURE]
|
||||
await self.run(set_temperature, temperature=temp)
|
||||
self.set(("climate_state_driver_temp_setting", temp))
|
||||
if mode := kwargs.get(ATTR_HVAC_MODE):
|
||||
await self.async_set_hvac_mode(mode)
|
||||
|
||||
if temp := kwargs.get(ATTR_TEMPERATURE):
|
||||
await self.run(set_temperature, temperature=temp)
|
||||
self.set(("climate_state_driver_temp_setting", temp))
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the climate mode and state."""
|
||||
|
@ -20,6 +20,7 @@ from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
from homeassistant.util.dt import as_utc
|
||||
|
||||
from . import TileData
|
||||
from .const import DOMAIN
|
||||
@ -145,16 +146,23 @@ class TileDeviceTracker(CoordinatorEntity[DataUpdateCoordinator[None]], TrackerE
|
||||
@callback
|
||||
def _update_from_latest_data(self) -> None:
|
||||
"""Update the entity from the latest data."""
|
||||
self._attr_extra_state_attributes.update(
|
||||
{
|
||||
ATTR_ALTITUDE: self._tile.altitude,
|
||||
ATTR_IS_LOST: self._tile.lost,
|
||||
ATTR_LAST_LOST_TIMESTAMP: self._tile.lost_timestamp,
|
||||
ATTR_LAST_TIMESTAMP: self._tile.last_timestamp,
|
||||
ATTR_RING_STATE: self._tile.ring_state,
|
||||
ATTR_VOIP_STATE: self._tile.voip_state,
|
||||
}
|
||||
)
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_ALTITUDE: self._tile.altitude,
|
||||
ATTR_IS_LOST: self._tile.lost,
|
||||
ATTR_RING_STATE: self._tile.ring_state,
|
||||
ATTR_VOIP_STATE: self._tile.voip_state,
|
||||
}
|
||||
for timestamp_attr in (
|
||||
(ATTR_LAST_LOST_TIMESTAMP, self._tile.lost_timestamp),
|
||||
(ATTR_LAST_TIMESTAMP, self._tile.last_timestamp),
|
||||
):
|
||||
if not timestamp_attr[1]:
|
||||
# If the API doesn't return a value for a particular timestamp
|
||||
# attribute, skip it:
|
||||
continue
|
||||
self._attr_extra_state_attributes[timestamp_attr[0]] = as_utc(
|
||||
timestamp_attr[1]
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle entity which will be added."""
|
||||
|
@ -78,7 +78,7 @@ class ProtectLight(ProtectDeviceEntity, LightEntity):
|
||||
is a change.
|
||||
"""
|
||||
|
||||
return (self._attr_available, self._attr_brightness)
|
||||
return (self._attr_available, self._attr_is_on, self._attr_brightness)
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.42"]
|
||||
"requirements": ["holidays==0.43"]
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ from .helpers.deprecation import (
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2024
|
||||
MINOR_VERSION: Final = 2
|
||||
PATCH_VERSION: Final = "2"
|
||||
PATCH_VERSION: Final = "3"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||
|
@ -141,10 +141,6 @@ pubnub!=6.4.0
|
||||
# https://github.com/dahlia/iso4217/issues/16
|
||||
iso4217!=1.10.20220401
|
||||
|
||||
# Matplotlib 3.6.2 has issues building wheels on armhf/armv7
|
||||
# We need at least >=2.1.0 (tensorflow integration -> pycocotools)
|
||||
matplotlib==3.6.1
|
||||
|
||||
# pyOpenSSL 24.0.0 or later required to avoid import errors when
|
||||
# cryptography 42.0.0 is installed with botocore
|
||||
pyOpenSSL>=24.0.0
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2024.2.2"
|
||||
version = "2024.2.3"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -191,7 +191,7 @@ aioairq==0.3.2
|
||||
aioairzone-cloud==0.3.8
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.7.2
|
||||
aioairzone==0.7.4
|
||||
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2024.01.0
|
||||
@ -377,7 +377,7 @@ aioswitcher==3.4.1
|
||||
aiosyncthing==0.5.1
|
||||
|
||||
# homeassistant.components.tankerkoenig
|
||||
aiotankerkoenig==0.3.0
|
||||
aiotankerkoenig==0.4.1
|
||||
|
||||
# homeassistant.components.tractive
|
||||
aiotractive==0.5.6
|
||||
@ -687,7 +687,7 @@ debugpy==1.8.0
|
||||
# decora==0.6
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==5.2.1
|
||||
deebot-client==5.2.2
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@ -695,7 +695,7 @@ deebot-client==5.2.1
|
||||
defusedxml==0.7.1
|
||||
|
||||
# homeassistant.components.deluge
|
||||
deluge-client==1.7.1
|
||||
deluge-client==1.10.2
|
||||
|
||||
# homeassistant.components.lametric
|
||||
demetriek==0.4.0
|
||||
@ -967,7 +967,7 @@ gotailwind==0.2.2
|
||||
govee-ble==0.31.0
|
||||
|
||||
# homeassistant.components.govee_light_local
|
||||
govee-local-api==1.4.1
|
||||
govee-local-api==1.4.4
|
||||
|
||||
# homeassistant.components.remote_rpi_gpio
|
||||
gpiozero==1.6.2
|
||||
@ -1059,7 +1059,7 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.42
|
||||
holidays==0.43
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240207.1
|
||||
@ -1313,7 +1313,7 @@ moehlenhoff-alpha2==1.3.0
|
||||
mopeka-iot-ble==0.5.0
|
||||
|
||||
# homeassistant.components.motion_blinds
|
||||
motionblinds==0.6.20
|
||||
motionblinds==0.6.21
|
||||
|
||||
# homeassistant.components.motioneye
|
||||
motioneye-client==0.3.14
|
||||
@ -2360,7 +2360,7 @@ pywaze==0.5.1
|
||||
pyweatherflowudp==1.4.5
|
||||
|
||||
# homeassistant.components.html5
|
||||
pywebpush==1.9.2
|
||||
pywebpush==1.14.1
|
||||
|
||||
# homeassistant.components.wemo
|
||||
pywemo==1.4.0
|
||||
@ -2423,7 +2423,7 @@ renault-api==0.2.1
|
||||
renson-endura-delta==1.7.1
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.8.7
|
||||
reolink-aio==0.8.8
|
||||
|
||||
# homeassistant.components.idteck_prox
|
||||
rfk101py==0.0.1
|
||||
@ -2444,13 +2444,13 @@ rjpl==0.3.6
|
||||
rocketchat-API==0.6.1
|
||||
|
||||
# homeassistant.components.roku
|
||||
rokuecp==0.19.0
|
||||
rokuecp==0.19.1
|
||||
|
||||
# homeassistant.components.romy
|
||||
romy==0.0.7
|
||||
|
||||
# homeassistant.components.roomba
|
||||
roombapy==1.6.10
|
||||
roombapy==1.6.12
|
||||
|
||||
# homeassistant.components.roon
|
||||
roonapi==0.1.6
|
||||
|
@ -170,7 +170,7 @@ aioairq==0.3.2
|
||||
aioairzone-cloud==0.3.8
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.7.2
|
||||
aioairzone==0.7.4
|
||||
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2024.01.0
|
||||
@ -350,7 +350,7 @@ aioswitcher==3.4.1
|
||||
aiosyncthing==0.5.1
|
||||
|
||||
# homeassistant.components.tankerkoenig
|
||||
aiotankerkoenig==0.3.0
|
||||
aiotankerkoenig==0.4.1
|
||||
|
||||
# homeassistant.components.tractive
|
||||
aiotractive==0.5.6
|
||||
@ -562,7 +562,7 @@ dbus-fast==2.21.1
|
||||
debugpy==1.8.0
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==5.2.1
|
||||
deebot-client==5.2.2
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@ -570,7 +570,7 @@ deebot-client==5.2.1
|
||||
defusedxml==0.7.1
|
||||
|
||||
# homeassistant.components.deluge
|
||||
deluge-client==1.7.1
|
||||
deluge-client==1.10.2
|
||||
|
||||
# homeassistant.components.lametric
|
||||
demetriek==0.4.0
|
||||
@ -784,7 +784,7 @@ gotailwind==0.2.2
|
||||
govee-ble==0.31.0
|
||||
|
||||
# homeassistant.components.govee_light_local
|
||||
govee-local-api==1.4.1
|
||||
govee-local-api==1.4.4
|
||||
|
||||
# homeassistant.components.gpsd
|
||||
gps3==0.33.3
|
||||
@ -855,7 +855,7 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.42
|
||||
holidays==0.43
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240207.1
|
||||
@ -1049,7 +1049,7 @@ moehlenhoff-alpha2==1.3.0
|
||||
mopeka-iot-ble==0.5.0
|
||||
|
||||
# homeassistant.components.motion_blinds
|
||||
motionblinds==0.6.20
|
||||
motionblinds==0.6.21
|
||||
|
||||
# homeassistant.components.motioneye
|
||||
motioneye-client==0.3.14
|
||||
@ -1809,7 +1809,7 @@ pywaze==0.5.1
|
||||
pyweatherflowudp==1.4.5
|
||||
|
||||
# homeassistant.components.html5
|
||||
pywebpush==1.9.2
|
||||
pywebpush==1.14.1
|
||||
|
||||
# homeassistant.components.wemo
|
||||
pywemo==1.4.0
|
||||
@ -1857,7 +1857,7 @@ renault-api==0.2.1
|
||||
renson-endura-delta==1.7.1
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.8.7
|
||||
reolink-aio==0.8.8
|
||||
|
||||
# homeassistant.components.rflink
|
||||
rflink==0.0.65
|
||||
@ -1866,13 +1866,13 @@ rflink==0.0.65
|
||||
ring-doorbell[listen]==0.8.7
|
||||
|
||||
# homeassistant.components.roku
|
||||
rokuecp==0.19.0
|
||||
rokuecp==0.19.1
|
||||
|
||||
# homeassistant.components.romy
|
||||
romy==0.0.7
|
||||
|
||||
# homeassistant.components.roomba
|
||||
roombapy==1.6.10
|
||||
roombapy==1.6.12
|
||||
|
||||
# homeassistant.components.roon
|
||||
roonapi==0.1.6
|
||||
|
@ -134,10 +134,6 @@ pubnub!=6.4.0
|
||||
# https://github.com/dahlia/iso4217/issues/16
|
||||
iso4217!=1.10.20220401
|
||||
|
||||
# Matplotlib 3.6.2 has issues building wheels on armhf/armv7
|
||||
# We need at least >=2.1.0 (tensorflow integration -> pycocotools)
|
||||
matplotlib==3.6.1
|
||||
|
||||
# pyOpenSSL 24.0.0 or later required to avoid import errors when
|
||||
# cryptography 42.0.0 is installed with botocore
|
||||
pyOpenSSL>=24.0.0
|
||||
|
@ -118,7 +118,48 @@ async def test_apprise_notification(hass: HomeAssistant) -> None:
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Validate calls were made under the hood correctly
|
||||
obj.add.assert_called_once_with([config[BASE_COMPONENT]["url"]])
|
||||
obj.add.assert_called_once_with(config[BASE_COMPONENT]["url"])
|
||||
obj.notify.assert_called_once_with(
|
||||
**{"body": data["message"], "title": data["title"], "tag": None}
|
||||
)
|
||||
|
||||
|
||||
async def test_apprise_multiple_notification(hass: HomeAssistant) -> None:
|
||||
"""Test apprise notification."""
|
||||
|
||||
config = {
|
||||
BASE_COMPONENT: {
|
||||
"name": "test",
|
||||
"platform": "apprise",
|
||||
"url": [
|
||||
"mailto://user:pass@example.com, mailto://user:pass@gmail.com",
|
||||
"json://user:pass@gmail.com",
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
# Our Message
|
||||
data = {"title": "Test Title", "message": "Test Message"}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.apprise.notify.apprise.Apprise"
|
||||
) as mock_apprise:
|
||||
obj = MagicMock()
|
||||
obj.add.return_value = True
|
||||
obj.notify.return_value = True
|
||||
mock_apprise.return_value = obj
|
||||
assert await async_setup_component(hass, BASE_COMPONENT, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test the existence of our service
|
||||
assert hass.services.has_service(BASE_COMPONENT, "test")
|
||||
|
||||
# Test the call to our underlining notify() call
|
||||
await hass.services.async_call(BASE_COMPONENT, "test", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Validate 2 calls were made under the hood
|
||||
assert obj.add.call_count == 2
|
||||
obj.notify.assert_called_once_with(
|
||||
**{"body": data["message"], "title": data["title"], "tag": None}
|
||||
)
|
||||
|
@ -119,6 +119,25 @@ async def test_attribute(hass: HomeAssistant, service, attribute) -> None:
|
||||
assert calls_1[0].data == {"entity_id": ENTITY_1, attribute: value}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service", "attribute"),
|
||||
[
|
||||
(SERVICE_SET_PRESET_MODE, ATTR_PRESET_MODE),
|
||||
(SERVICE_SET_SWING_MODE, ATTR_SWING_MODE),
|
||||
(SERVICE_SET_FAN_MODE, ATTR_FAN_MODE),
|
||||
],
|
||||
)
|
||||
async def test_attribute_with_none(hass: HomeAssistant, service, attribute) -> None:
|
||||
"""Test that service call is not made for attributes with None value."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, service)
|
||||
|
||||
await async_reproduce_states(hass, [State(ENTITY_1, None, {attribute: None})])
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls_1) == 0
|
||||
|
||||
|
||||
async def test_attribute_partial_temperature(hass: HomeAssistant) -> None:
|
||||
"""Test that service call ignores null attributes."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_SET_TEMPERATURE)
|
||||
|
@ -5,6 +5,7 @@ from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.cloud import CloudNotAvailable
|
||||
from homeassistant.components.mobile_app.const import (
|
||||
ATTR_DEVICE_NAME,
|
||||
CONF_CLOUDHOOK_URL,
|
||||
@ -118,6 +119,32 @@ async def test_create_cloud_hook_on_setup(
|
||||
await _test_create_cloud_hook(hass, hass_admin_user, {}, True, additional_steps)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exception", (CloudNotAvailable, ValueError))
|
||||
async def test_remove_cloudhook(
|
||||
hass: HomeAssistant,
|
||||
hass_admin_user: MockUser,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test removing a cloud hook when config entry is removed."""
|
||||
|
||||
async def additional_steps(
|
||||
config_entry: ConfigEntry, mock_create_cloudhook: Mock, cloud_hook: str
|
||||
) -> None:
|
||||
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
|
||||
assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook
|
||||
with patch(
|
||||
"homeassistant.components.cloud.async_delete_cloudhook",
|
||||
side_effect=exception,
|
||||
) as delete_cloudhook:
|
||||
await hass.config_entries.async_remove(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
delete_cloudhook.assert_called_once_with(hass, webhook_id)
|
||||
assert str(exception) not in caplog.text
|
||||
|
||||
await _test_create_cloud_hook(hass, hass_admin_user, {}, True, additional_steps)
|
||||
|
||||
|
||||
async def test_create_cloud_hook_aleady_exists(
|
||||
hass: HomeAssistant,
|
||||
hass_admin_user: MockUser,
|
||||
|
@ -98,12 +98,12 @@ def _mocked_discovery(*_):
|
||||
|
||||
roomba = RoombaInfo(
|
||||
hostname="irobot-BLID",
|
||||
robot_name="robot_name",
|
||||
robotname="robot_name",
|
||||
ip=MOCK_IP,
|
||||
mac="mac",
|
||||
firmware="firmware",
|
||||
sw="firmware",
|
||||
sku="sku",
|
||||
capabilities="capabilities",
|
||||
cap={"cap": 1},
|
||||
)
|
||||
|
||||
roomba_discovery.get_all = MagicMock(return_value=[roomba])
|
||||
|
51
tests/components/tankerkoenig/test_coordinator.py
Normal file
51
tests/components/tankerkoenig/test_coordinator.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""Tests for the Tankerkoening integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aiotankerkoenig.exceptions import TankerkoenigRateLimitError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.tankerkoenig.const import DEFAULT_SCAN_INTERVAL
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("setup_integration")
|
||||
async def test_rate_limit(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
tankerkoenig: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test detection of API rate limit."""
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
state = hass.states.get("binary_sensor.station_somewhere_street_1_status")
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
tankerkoenig.prices.side_effect = TankerkoenigRateLimitError
|
||||
async_fire_time_changed(
|
||||
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_SCAN_INTERVAL)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert (
|
||||
"API rate limit reached, consider to increase polling interval" in caplog.text
|
||||
)
|
||||
state = hass.states.get("binary_sensor.station_somewhere_street_1_status")
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
tankerkoenig.prices.side_effect = None
|
||||
async_fire_time_changed(
|
||||
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_SCAN_INTERVAL * 2)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("binary_sensor.station_somewhere_street_1_status")
|
||||
assert state
|
||||
assert state.state == "on"
|
@ -37,6 +37,16 @@ def mock_wake_up():
|
||||
yield mock_wake_up
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_vehicle():
|
||||
"""Mock Tesla Fleet API Vehicle Specific vehicle method."""
|
||||
with patch(
|
||||
"homeassistant.components.teslemetry.VehicleSpecific.vehicle",
|
||||
return_value=WAKE_UP_ONLINE,
|
||||
) as mock_vehicle:
|
||||
yield mock_vehicle
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_request():
|
||||
"""Mock Tesla Fleet API Vehicle Specific class."""
|
||||
|
@ -26,6 +26,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import assert_entities, setup_platform
|
||||
from .const import WAKE_UP_ASLEEP, WAKE_UP_ONLINE
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
@ -108,7 +109,11 @@ async def test_errors(
|
||||
|
||||
|
||||
async def test_asleep_or_offline(
|
||||
hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory
|
||||
hass: HomeAssistant,
|
||||
mock_vehicle_data,
|
||||
mock_wake_up,
|
||||
mock_vehicle,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Tests asleep is handled."""
|
||||
|
||||
@ -123,9 +128,47 @@ async def test_asleep_or_offline(
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
mock_vehicle_data.assert_called_once()
|
||||
mock_wake_up.reset_mock()
|
||||
|
||||
# Run a command that will wake up the vehicle, but not immediately
|
||||
# Run a command but fail trying to wake up the vehicle
|
||||
mock_wake_up.side_effect = InvalidCommand
|
||||
with pytest.raises(HomeAssistantError) as error:
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
assert error
|
||||
mock_wake_up.assert_called_once()
|
||||
|
||||
mock_wake_up.side_effect = None
|
||||
mock_wake_up.reset_mock()
|
||||
|
||||
# Run a command but timeout trying to wake up the vehicle
|
||||
mock_wake_up.return_value = WAKE_UP_ASLEEP
|
||||
mock_vehicle.return_value = WAKE_UP_ASLEEP
|
||||
with patch(
|
||||
"homeassistant.components.teslemetry.entity.asyncio.sleep"
|
||||
), pytest.raises(HomeAssistantError) as error:
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
assert error
|
||||
mock_wake_up.assert_called_once()
|
||||
mock_vehicle.assert_called()
|
||||
|
||||
mock_wake_up.reset_mock()
|
||||
mock_vehicle.reset_mock()
|
||||
mock_wake_up.return_value = WAKE_UP_ONLINE
|
||||
mock_vehicle.return_value = WAKE_UP_ONLINE
|
||||
|
||||
# Run a command and wake up the vehicle immediately
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: [entity_id]}, blocking=True
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_wake_up.assert_called_once()
|
||||
|
@ -54,14 +54,22 @@ async def test_climate(
|
||||
with patch(
|
||||
"homeassistant.components.tessie.climate.set_temperature",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_set:
|
||||
) as mock_set, patch(
|
||||
"homeassistant.components.tessie.climate.start_climate_preconditioning",
|
||||
return_value=TEST_RESPONSE,
|
||||
) as mock_set2:
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_TEMPERATURE: 20},
|
||||
{
|
||||
ATTR_ENTITY_ID: [entity_id],
|
||||
ATTR_HVAC_MODE: HVACMode.HEAT_COOL,
|
||||
ATTR_TEMPERATURE: 20,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_set.assert_called_once()
|
||||
mock_set2.assert_called_once()
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user