diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index b05397280c2..aeb05b1d112 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -10,7 +10,7 @@ on: env: BUILD_TYPE: core - DEFAULT_PYTHON: "3.12" + DEFAULT_PYTHON: "3.12.3" PIP_TIMEOUT: 60 UV_HTTP_TIMEOUT: 60 UV_SYSTEM_PYTHON: "true" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6cb8f8deec4..5a582586c89 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,8 +37,8 @@ env: UV_CACHE_VERSION: 1 MYPY_CACHE_VERSION: 8 HA_SHORT_VERSION: "2024.6" - DEFAULT_PYTHON: "3.12" - ALL_PYTHON_VERSIONS: "['3.12']" + DEFAULT_PYTHON: "3.12.3" + ALL_PYTHON_VERSIONS: "['3.12.3']" # 10.3 is the oldest supported version # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) # 10.6 is the current long-term-support diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml index f487292e79a..92c4c845e7d 100644 --- a/.github/workflows/translations.yml +++ b/.github/workflows/translations.yml @@ -10,7 +10,7 @@ on: - "**strings.json" env: - DEFAULT_PYTHON: "3.11" + DEFAULT_PYTHON: "3.12.3" jobs: upload: diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index fc169619325..13f5177bd7e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -17,7 +17,7 @@ on: - "script/gen_requirements_all.py" env: - DEFAULT_PYTHON: "3.12" + DEFAULT_PYTHON: "3.12.3" concurrency: group: ${{ github.workflow }}-${{ github.ref_name}} diff --git a/homeassistant/components/buienradar/manifest.json b/homeassistant/components/buienradar/manifest.json index 4885f45032c..5b08f5c631a 100644 --- a/homeassistant/components/buienradar/manifest.json +++ b/homeassistant/components/buienradar/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/buienradar", "iot_class": "cloud_polling", "loggers": ["buienradar", "vincenty"], - "requirements": ["buienradar==1.0.5"] + "requirements": ["buienradar==1.0.6"] } diff --git a/homeassistant/components/concord232/alarm_control_panel.py b/homeassistant/components/concord232/alarm_control_panel.py index 2799481ccaa..0256f5aab37 100644 --- a/homeassistant/components/concord232/alarm_control_panel.py +++ b/homeassistant/components/concord232/alarm_control_panel.py @@ -86,6 +86,7 @@ class Concord232Alarm(AlarmControlPanelEntity): self._attr_name = name self._code = code + self._alarm_control_panel_option_default_code = code self._mode = mode self._url = url self._alarm = concord232_client.Client(self._url) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index d3d19375105..1b17601a2f6 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240610.0"] + "requirements": ["home-assistant-frontend==20240610.1"] } diff --git a/homeassistant/components/gardena_bluetooth/manifest.json b/homeassistant/components/gardena_bluetooth/manifest.json index 1e3ef156d72..4812def7dde 100644 --- a/homeassistant/components/gardena_bluetooth/manifest.json +++ b/homeassistant/components/gardena_bluetooth/manifest.json @@ -13,5 +13,6 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth", "iot_class": "local_polling", + "loggers": ["bleak", "bleak_esphome", "gardena_bluetooth"], "requirements": ["gardena-bluetooth==1.4.2"] } diff --git a/homeassistant/components/goodwe/manifest.json b/homeassistant/components/goodwe/manifest.json index 8506d1fd6af..41e0ed91f6a 100644 --- a/homeassistant/components/goodwe/manifest.json +++ b/homeassistant/components/goodwe/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/goodwe", "iot_class": "local_polling", "loggers": ["goodwe"], - "requirements": ["goodwe==0.3.5"] + "requirements": ["goodwe==0.3.6"] } diff --git a/homeassistant/components/group/manifest.json b/homeassistant/components/group/manifest.json index d86fc4ba622..a2045f370b1 100644 --- a/homeassistant/components/group/manifest.json +++ b/homeassistant/components/group/manifest.json @@ -4,7 +4,10 @@ "after_dependencies": [ "alarm_control_panel", "climate", + "cover", "device_tracker", + "lock", + "media_player", "person", "plant", "vacuum", diff --git a/homeassistant/components/ping/helpers.py b/homeassistant/components/ping/helpers.py index 7f1696d2ed9..82ebf4532da 100644 --- a/homeassistant/components/ping/helpers.py +++ b/homeassistant/components/ping/helpers.py @@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any from icmplib import NameLookupError, async_ping from homeassistant.core import HomeAssistant -from homeassistant.helpers.update_coordinator import UpdateFailed from .const import ICMP_TIMEOUT, PING_TIMEOUT @@ -59,9 +58,10 @@ class PingDataICMPLib(PingData): timeout=ICMP_TIMEOUT, privileged=self._privileged, ) - except NameLookupError as err: + except NameLookupError: + _LOGGER.debug("Error resolving host: %s", self.ip_address) self.is_alive = False - raise UpdateFailed(f"Error resolving host: {self.ip_address}") from err + return _LOGGER.debug( "async_ping returned: reachable=%s sent=%i received=%s", @@ -152,17 +152,22 @@ class PingDataSubProcess(PingData): if TYPE_CHECKING: assert match is not None rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups() - except TimeoutError as err: + except TimeoutError: + _LOGGER.debug( + "Timed out running command: `%s`, after: %s", + " ".join(self._ping_cmd), + self._count + PING_TIMEOUT, + ) + if pinger: with suppress(TypeError): await pinger.kill() # type: ignore[func-returns-value] del pinger - raise UpdateFailed( - f"Timed out running command: `{self._ping_cmd}`, after: {self._count + PING_TIMEOUT}s" - ) from err + return None except AttributeError as err: - raise UpdateFailed from err + _LOGGER.debug("Error matching ping output: %s", err) + return None return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": rtt_mdev} async def async_update(self) -> None: diff --git a/homeassistant/components/reolink/camera.py b/homeassistant/components/reolink/camera.py index a2c396e7ef5..4adac1a96d8 100644 --- a/homeassistant/components/reolink/camera.py +++ b/homeassistant/components/reolink/camera.py @@ -116,7 +116,6 @@ async def async_setup_entry( class ReolinkCamera(ReolinkChannelCoordinatorEntity, Camera): """An implementation of a Reolink IP camera.""" - _attr_supported_features: CameraEntityFeature = CameraEntityFeature.STREAM entity_description: ReolinkCameraEntityDescription def __init__( @@ -130,6 +129,9 @@ class ReolinkCamera(ReolinkChannelCoordinatorEntity, Camera): ReolinkChannelCoordinatorEntity.__init__(self, reolink_data, channel) Camera.__init__(self) + if "snapshots" not in entity_description.stream: + self._attr_supported_features = CameraEntityFeature.STREAM + if self._host.api.model in DUAL_LENS_MODELS: self._attr_translation_key = ( f"{entity_description.translation_key}_lens_{self._channel}" diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 487bc519a26..3cb5d7fbce9 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -37,7 +37,6 @@ from .const import ( CONST_MODE_SMART_SCHEDULE, CONST_OVERLAY_MANUAL, CONST_OVERLAY_TADO_OPTIONS, - CONST_OVERLAY_TIMER, DATA, DOMAIN, HA_TERMINATION_DURATION, @@ -65,7 +64,7 @@ from .const import ( TYPE_HEATING, ) from .entity import TadoZoneEntity -from .helper import decide_overlay_mode +from .helper import decide_duration, decide_overlay_mode _LOGGER = logging.getLogger(__name__) @@ -603,14 +602,12 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): overlay_mode=overlay_mode, zone_id=self.zone_id, ) - # If we ended up with a timer but no duration, set a default duration - if overlay_mode == CONST_OVERLAY_TIMER and duration is None: - duration = ( - int(self._tado_zone_data.default_overlay_termination_duration) - if self._tado_zone_data.default_overlay_termination_duration is not None - else 3600 - ) - + duration = decide_duration( + tado=self._tado, + duration=duration, + zone_id=self.zone_id, + overlay_mode=overlay_mode, + ) _LOGGER.debug( ( "Switching to %s for zone %s (%d) with temperature %s °C and duration" diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index c62352a6d95..be35bbb8e25 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -212,3 +212,5 @@ SERVICE_ADD_METER_READING = "add_meter_reading" CONF_CONFIG_ENTRY = "config_entry" CONF_READING = "reading" ATTR_MESSAGE = "message" + +WATER_HEATER_FALLBACK_REPAIR = "water_heater_fallback" diff --git a/homeassistant/components/tado/helper.py b/homeassistant/components/tado/helper.py index fee23aef64a..efcd3e7c4ea 100644 --- a/homeassistant/components/tado/helper.py +++ b/homeassistant/components/tado/helper.py @@ -29,3 +29,23 @@ def decide_overlay_mode( ) return overlay_mode + + +def decide_duration( + tado: TadoConnector, + duration: int | None, + zone_id: int, + overlay_mode: str | None = None, +) -> None | int: + """Return correct duration based on the selected overlay mode/duration and tado config.""" + # If we ended up with a timer but no duration, set a default duration + # If we ended up with a timer but no duration, set a default duration + if overlay_mode == CONST_OVERLAY_TIMER and duration is None: + duration = ( + int(tado.data["zone"][zone_id].default_overlay_termination_duration) + if tado.data["zone"][zone_id].default_overlay_termination_duration + is not None + else 3600 + ) + + return duration diff --git a/homeassistant/components/tado/repairs.py b/homeassistant/components/tado/repairs.py new file mode 100644 index 00000000000..5ffc3c76bf7 --- /dev/null +++ b/homeassistant/components/tado/repairs.py @@ -0,0 +1,34 @@ +"""Repair implementations.""" + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir + +from .const import ( + CONST_OVERLAY_MANUAL, + CONST_OVERLAY_TADO_DEFAULT, + DOMAIN, + WATER_HEATER_FALLBACK_REPAIR, +) + + +def manage_water_heater_fallback_issue( + hass: HomeAssistant, + water_heater_entities: list, + integration_overlay_fallback: str | None, +) -> None: + """Notify users about water heater respecting fallback setting.""" + if ( + integration_overlay_fallback + in [CONST_OVERLAY_TADO_DEFAULT, CONST_OVERLAY_MANUAL] + and len(water_heater_entities) > 0 + ): + for water_heater_entity in water_heater_entities: + ir.async_create_issue( + hass=hass, + domain=DOMAIN, + issue_id=f"{WATER_HEATER_FALLBACK_REPAIR}_{water_heater_entity.zone_name}", + is_fixable=False, + is_persistent=False, + severity=ir.IssueSeverity.WARNING, + translation_key=WATER_HEATER_FALLBACK_REPAIR, + ) diff --git a/homeassistant/components/tado/strings.json b/homeassistant/components/tado/strings.json index 51e36fe5355..d992befe112 100644 --- a/homeassistant/components/tado/strings.json +++ b/homeassistant/components/tado/strings.json @@ -165,6 +165,10 @@ "import_failed_invalid_auth": { "title": "Failed to import, invalid credentials", "description": "Failed to import the configuration for the Tado Device Tracker, due to invalid credentials. Please fix the YAML configuration and restart Home Assistant. Alternatively you can use the UI to configure Tado. Don't forget to delete the YAML configuration, once the import is successful." + }, + "water_heater_fallback": { + "title": "Tado Water Heater entities now support fallback options", + "description": "Due to added support for water heaters entities, these entities may use different overlay. Please configure integration entity and tado app water heater zone overlay options." } } } diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index 9b449dd43cc..a31b70a8f9a 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -32,7 +32,8 @@ from .const import ( TYPE_HOT_WATER, ) from .entity import TadoZoneEntity -from .helper import decide_overlay_mode +from .helper import decide_duration, decide_overlay_mode +from .repairs import manage_water_heater_fallback_issue _LOGGER = logging.getLogger(__name__) @@ -80,6 +81,12 @@ async def async_setup_entry( async_add_entities(entities, True) + manage_water_heater_fallback_issue( + hass=hass, + water_heater_entities=entities, + integration_overlay_fallback=tado.fallback, + ) + def _generate_entities(tado: TadoConnector) -> list[WaterHeaterEntity]: """Create all water heater entities.""" @@ -283,7 +290,12 @@ class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity): duration=duration, zone_id=self.zone_id, ) - + duration = decide_duration( + tado=self._tado, + duration=duration, + zone_id=self.zone_id, + overlay_mode=overlay_mode, + ) _LOGGER.debug( "Switching to %s for zone %s (%d) with temperature %s", self._current_tado_hvac_mode, diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 0f41011361d..05ae7936fb3 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -54,6 +54,10 @@ SCAN_INTERVAL = timedelta(seconds=DEFAULT_SCAN_INTERVAL) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) +EARLY_ACCESS_URL = ( + "https://www.home-assistant.io/integrations/unifiprotect#software-support" +) + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the UniFi Protect.""" @@ -122,8 +126,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: DOMAIN, "ea_channel_warning", is_fixable=True, - is_persistent=True, - learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access", + is_persistent=False, + learn_more_url=EARLY_ACCESS_URL, severity=IssueSeverity.WARNING, translation_key="ea_channel_warning", translation_placeholders={"version": str(nvr_info.version)}, diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 00a96483f70..ce512ca3f3c 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -40,7 +40,7 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["uiprotect", "unifi_discovery"], - "requirements": ["uiprotect==0.4.1", "unifi-discovery==1.1.8"], + "requirements": ["uiprotect==1.7.2", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index b83d514f836..54023a1768f 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -55,7 +55,7 @@ "all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)", "override_connection_host": "Override Connection Host", "max_media": "Max number of event to load for Media Browser (increases RAM usage)", - "allow_ea": "Allow Early Access versions of Protect (WARNING: Will mark your integration as unsupported)" + "allow_ea_channel": "Allow Early Access versions of Protect (WARNING: Will mark your integration as unsupported)" } } } @@ -67,7 +67,7 @@ "step": { "start": { "title": "UniFi Protect Early Access enabled", - "description": "You are either running an Early Access version of UniFi Protect (v{version}) or opt-ed into a release channel that is not the Official Release Channel. [Home Assistant does not support Early Access versions](https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access), so you should immediately switch to the Official Release Channel. Accidentally upgrading to an Early Access version can break your UniFi Protect integration.\n\nBy submitting this form, you have switched back to the Official Release Channel or agree to run an unsupported version of UniFi Protect, which may break your Home Assistant integration at any time." + "description": "You are either running an Early Access version of UniFi Protect (v{version}) or opt-ed into a release channel that is not the Official Release Channel.\n\nAs these Early Access releases may not be tested yet, using it may cause the UniFi Protect integration to behave unexpectedly. [Read more about Early Access and Home Assistant]({learn_more}).\n\nSubmit to dismiss this message." }, "confirm": { "title": "[%key:component::unifiprotect::issues::ea_channel_warning::fix_flow::step::start::title%]", diff --git a/homeassistant/components/unifiprotect/utils.py b/homeassistant/components/unifiprotect/utils.py index 8a3028bcea7..cf917d894ac 100644 --- a/homeassistant/components/unifiprotect/utils.py +++ b/homeassistant/components/unifiprotect/utils.py @@ -89,10 +89,8 @@ def async_get_devices_by_type( bootstrap: Bootstrap, device_type: ModelType ) -> dict[str, ProtectAdoptableDeviceModel]: """Get devices by type.""" - - devices: dict[str, ProtectAdoptableDeviceModel] = getattr( - bootstrap, f"{device_type.value}s" - ) + devices: dict[str, ProtectAdoptableDeviceModel] + devices = getattr(bootstrap, device_type.devices_key) return devices diff --git a/homeassistant/components/workday/__init__.py b/homeassistant/components/workday/__init__.py index f25cf41b992..60a0489ec5c 100644 --- a/homeassistant/components/workday/__init__.py +++ b/homeassistant/components/workday/__init__.py @@ -35,7 +35,7 @@ async def _async_validate_country_and_province( DOMAIN, "bad_country", is_fixable=True, - is_persistent=True, + is_persistent=False, severity=IssueSeverity.ERROR, translation_key="bad_country", translation_placeholders={"title": entry.title}, @@ -59,7 +59,7 @@ async def _async_validate_country_and_province( DOMAIN, "bad_province", is_fixable=True, - is_persistent=True, + is_persistent=False, severity=IssueSeverity.ERROR, translation_key="bad_province", translation_placeholders={ diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 8caf296674c..12e427334e2 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -21,11 +21,11 @@ "universal_silabs_flasher" ], "requirements": [ - "bellows==0.39.0", + "bellows==0.39.1", "pyserial==3.5", "zha-quirks==0.0.116", "zigpy-deconz==0.23.1", - "zigpy==0.64.0", + "zigpy==0.64.1", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.1", diff --git a/homeassistant/const.py b/homeassistant/const.py index 500a74140f2..cd340cd5079 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -24,7 +24,7 @@ if TYPE_CHECKING: APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 6 -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, 12, 0) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index d7c0f90e2f9..faf16ad572c 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -1362,6 +1362,8 @@ class IntentResponse: if self.reprompt: response_dict["reprompt"] = self.reprompt + if self.speech_slots: + response_dict["speech_slots"] = self.speech_slots response_data: dict[str, Any] = {} diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c8c9419339d..94f030c6104 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,7 +32,7 @@ habluetooth==3.1.1 hass-nabucasa==0.81.1 hassil==1.7.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240610.0 +home-assistant-frontend==20240610.1 home-assistant-intents==2024.6.5 httpx==0.27.0 ifaddr==0.2.0 diff --git a/pyproject.toml b/pyproject.toml index b71f80bbaf8..1ca2b5cb40e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.6.2" +version = "2024.6.3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" diff --git a/requirements_all.txt b/requirements_all.txt index 34c2e2bfa46..289a4eead5d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -547,7 +547,7 @@ beautifulsoup4==4.12.3 # beewi-smartclim==0.0.10 # homeassistant.components.zha -bellows==0.39.0 +bellows==0.39.1 # homeassistant.components.bmw_connected_drive bimmer-connected[china]==0.15.3 @@ -634,7 +634,7 @@ bthomehub5-devicelist==0.1.1 btsmarthub-devicelist==0.2.3 # homeassistant.components.buienradar -buienradar==1.0.5 +buienradar==1.0.6 # homeassistant.components.dhcp cached_ipaddress==0.3.0 @@ -961,7 +961,7 @@ glances-api==0.8.0 goalzero==0.2.2 # homeassistant.components.goodwe -goodwe==0.3.5 +goodwe==0.3.6 # homeassistant.components.google_mail # homeassistant.components.google_tasks @@ -1087,7 +1087,7 @@ hole==0.8.0 holidays==0.50 # homeassistant.components.frontend -home-assistant-frontend==20240610.0 +home-assistant-frontend==20240610.1 # homeassistant.components.conversation home-assistant-intents==2024.6.5 @@ -2779,7 +2779,7 @@ twitchAPI==4.0.0 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==0.4.1 +uiprotect==1.7.2 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 @@ -2981,7 +2981,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.64.0 +zigpy==0.64.1 # homeassistant.components.zoneminder zm-py==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a0dd8f939e..6bf487f7ef9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -472,7 +472,7 @@ base36==0.1.1 beautifulsoup4==4.12.3 # homeassistant.components.zha -bellows==0.39.0 +bellows==0.39.1 # homeassistant.components.bmw_connected_drive bimmer-connected[china]==0.15.3 @@ -536,7 +536,7 @@ brunt==1.2.0 bthome-ble==3.8.1 # homeassistant.components.buienradar -buienradar==1.0.5 +buienradar==1.0.6 # homeassistant.components.dhcp cached_ipaddress==0.3.0 @@ -790,7 +790,7 @@ glances-api==0.8.0 goalzero==0.2.2 # homeassistant.components.goodwe -goodwe==0.3.5 +goodwe==0.3.6 # homeassistant.components.google_mail # homeassistant.components.google_tasks @@ -889,7 +889,7 @@ hole==0.8.0 holidays==0.50 # homeassistant.components.frontend -home-assistant-frontend==20240610.0 +home-assistant-frontend==20240610.1 # homeassistant.components.conversation home-assistant-intents==2024.6.5 @@ -2153,7 +2153,7 @@ twitchAPI==4.0.0 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==0.4.1 +uiprotect==1.7.2 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 @@ -2322,7 +2322,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.64.0 +zigpy==0.64.1 # homeassistant.components.zwave_js zwave-js-server-python==0.56.0 diff --git a/tests/components/tado/test_helper.py b/tests/components/tado/test_helper.py index ff85dfce944..bdd7977f858 100644 --- a/tests/components/tado/test_helper.py +++ b/tests/components/tado/test_helper.py @@ -9,7 +9,7 @@ from homeassistant.components.tado.const import ( CONST_OVERLAY_TADO_MODE, CONST_OVERLAY_TIMER, ) -from homeassistant.components.tado.helper import decide_overlay_mode +from homeassistant.components.tado.helper import decide_duration, decide_overlay_mode from homeassistant.core import HomeAssistant @@ -21,7 +21,7 @@ def dummy_tado_connector(hass: HomeAssistant, fallback) -> TadoConnector: async def test_overlay_mode_duration_set(hass: HomeAssistant) -> None: """Test overlay method selection when duration is set.""" tado = dummy_tado_connector(hass=hass, fallback=CONST_OVERLAY_TADO_MODE) - overlay_mode = decide_overlay_mode(tado=tado, duration="01:00:00", zone_id=1) + overlay_mode = decide_overlay_mode(tado=tado, duration=3600, zone_id=1) # Must select TIMER overlay assert overlay_mode == CONST_OVERLAY_TIMER @@ -52,3 +52,36 @@ async def test_overlay_mode_tado_default_fallback(hass: HomeAssistant) -> None: overlay_mode = decide_overlay_mode(tado=tado, duration=None, zone_id=zone_id) # Must fallback to zone setting assert overlay_mode == zone_fallback + + +async def test_duration_enabled_without_tado_default(hass: HomeAssistant) -> None: + """Test duration decide method when overlay is timer and duration is set.""" + overlay = CONST_OVERLAY_TIMER + expected_duration = 600 + tado = dummy_tado_connector(hass=hass, fallback=CONST_OVERLAY_MANUAL) + duration = decide_duration( + tado=tado, duration=expected_duration, overlay_mode=overlay, zone_id=0 + ) + # Should return the same duration value + assert duration == expected_duration + + +async def test_duration_enabled_with_tado_default(hass: HomeAssistant) -> None: + """Test overlay method selection when ended up with timer overlay and None duration.""" + zone_fallback = CONST_OVERLAY_TIMER + expected_duration = 45000 + tado = dummy_tado_connector(hass=hass, fallback=zone_fallback) + + class MockZoneData: + def __init__(self) -> None: + self.default_overlay_termination_duration = expected_duration + + zone_id = 1 + + zone_data = {"zone": {zone_id: MockZoneData()}} + with patch.dict(tado.data, zone_data): + duration = decide_duration( + tado=tado, duration=None, zone_id=zone_id, overlay_mode=zone_fallback + ) + # Must fallback to zone timer setting + assert duration == expected_duration diff --git a/tests/components/tado/test_repairs.py b/tests/components/tado/test_repairs.py new file mode 100644 index 00000000000..2e055884272 --- /dev/null +++ b/tests/components/tado/test_repairs.py @@ -0,0 +1,64 @@ +"""Repair tests.""" + +import pytest + +from homeassistant.components.tado.const import ( + CONST_OVERLAY_MANUAL, + CONST_OVERLAY_TADO_DEFAULT, + CONST_OVERLAY_TADO_MODE, + DOMAIN, + WATER_HEATER_FALLBACK_REPAIR, +) +from homeassistant.components.tado.repairs import manage_water_heater_fallback_issue +from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir + + +class MockWaterHeater: + """Mock Water heater entity.""" + + def __init__(self, zone_name) -> None: + """Init mock entity class.""" + self.zone_name = zone_name + + +async def test_manage_water_heater_fallback_issue_not_created( + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, +) -> None: + """Test water heater fallback issue is not needed.""" + zone_name = "Hot Water" + expected_issue_id = f"{WATER_HEATER_FALLBACK_REPAIR}_{zone_name}" + water_heater_entities = [MockWaterHeater(zone_name)] + manage_water_heater_fallback_issue( + water_heater_entities=water_heater_entities, + integration_overlay_fallback=CONST_OVERLAY_TADO_MODE, + hass=hass, + ) + assert ( + issue_registry.async_get_issue(issue_id=expected_issue_id, domain=DOMAIN) + is None + ) + + +@pytest.mark.parametrize( + "integration_overlay_fallback", [CONST_OVERLAY_TADO_DEFAULT, CONST_OVERLAY_MANUAL] +) +async def test_manage_water_heater_fallback_issue_created( + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + integration_overlay_fallback: str, +) -> None: + """Test water heater fallback issue created cases.""" + zone_name = "Hot Water" + expected_issue_id = f"{WATER_HEATER_FALLBACK_REPAIR}_{zone_name}" + water_heater_entities = [MockWaterHeater(zone_name)] + manage_water_heater_fallback_issue( + water_heater_entities=water_heater_entities, + integration_overlay_fallback=integration_overlay_fallback, + hass=hass, + ) + assert ( + issue_registry.async_get_issue(issue_id=expected_issue_id, domain=DOMAIN) + is not None + ) diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index 7d76550f7c7..6b54f464b26 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -61,7 +61,7 @@ async def test_ea_warning_ignore( flow_id = data["flow_id"] assert data["description_placeholders"] == { - "learn_more": "https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access", + "learn_more": "https://www.home-assistant.io/integrations/unifiprotect#software-support", "version": str(version), } assert data["step_id"] == "start" @@ -73,7 +73,7 @@ async def test_ea_warning_ignore( flow_id = data["flow_id"] assert data["description_placeholders"] == { - "learn_more": "https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access", + "learn_more": "https://www.home-assistant.io/integrations/unifiprotect#software-support", "version": str(version), } assert data["step_id"] == "confirm" @@ -123,7 +123,7 @@ async def test_ea_warning_fix( flow_id = data["flow_id"] assert data["description_placeholders"] == { - "learn_more": "https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access", + "learn_more": "https://www.home-assistant.io/integrations/unifiprotect#software-support", "version": str(version), } assert data["step_id"] == "start" diff --git a/tests/helpers/test_llm.py b/tests/helpers/test_llm.py index 6ac17a2fe0e..b4a768c4429 100644 --- a/tests/helpers/test_llm.py +++ b/tests/helpers/test_llm.py @@ -149,8 +149,13 @@ async def test_assist_api( assert test_context.json_fragment # To reproduce an error case in tracing intent_response = intent.IntentResponse("*") - intent_response.matched_states = [State("light.matched", "on")] - intent_response.unmatched_states = [State("light.unmatched", "on")] + intent_response.async_set_states( + [State("light.matched", "on")], [State("light.unmatched", "on")] + ) + intent_response.async_set_speech("Some speech") + intent_response.async_set_card("Card title", "card content") + intent_response.async_set_speech_slots({"hello": 1}) + intent_response.async_set_reprompt("Do it again") tool_input = llm.ToolInput( tool_name="test_intent", tool_args={"area": "kitchen", "floor": "ground_floor"}, @@ -181,8 +186,22 @@ async def test_assist_api( "success": [], "targets": [], }, + "reprompt": { + "plain": { + "extra_data": None, + "reprompt": "Do it again", + }, + }, "response_type": "action_done", - "speech": {}, + "speech": { + "plain": { + "extra_data": None, + "speech": "Some speech", + }, + }, + "speech_slots": { + "hello": 1, + }, } # Call with a device/area/floor @@ -227,7 +246,21 @@ async def test_assist_api( "targets": [], }, "response_type": "action_done", - "speech": {}, + "reprompt": { + "plain": { + "extra_data": None, + "reprompt": "Do it again", + }, + }, + "speech": { + "plain": { + "extra_data": None, + "speech": "Some speech", + }, + }, + "speech_slots": { + "hello": 1, + }, }