Compare commits

...

56 Commits

Author SHA1 Message Date
Franck Nijhof
b315b566e5 2024.6.4 (#120114) 2024-06-21 20:31:04 +02:00
Thomas Kistler
febcb33545 Update pydrawise to 2024.6.4 (#119868) 2024-06-21 20:00:47 +02:00
Franck Nijhof
e62268d5ea Revert "Make UniFi services handle unloaded config entry (#120028)"
This reverts commit 39f67afa64.
2024-06-21 19:59:25 +02:00
Franck Nijhof
c1dc6fd511 Bump version to 2024.6.4 2024-06-21 18:42:30 +02:00
Michael Hansen
92c12fdf0a Bump intents to 2024.6.21 (#120106) 2024-06-21 18:42:19 +02:00
Álvaro Fernández Rojas
53a21dcb6b Update AEMET-OpenData to v0.5.2 (#120065) 2024-06-21 18:42:17 +02:00
Jan Bouwhuis
59b2f4e56f Bump aioimaplib to 1.1.0 (#120045) 2024-06-21 18:42:13 +02:00
Glenn Waters
75a469f4d6 Bump env-canada to 0.6.3 (#120035)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-06-21 18:42:10 +02:00
Robert Svensson
39f67afa64 Make UniFi services handle unloaded config entry (#120028) 2024-06-21 18:41:33 +02:00
BestPig
5b322f1af5 Fix songpal crash for soundbars without sound modes (#119999)
Getting soundField on soundbar that doesn't support it crash raise an exception, so it make the whole components unavailable. As there is no simple way to know if soundField is supported, I just get all sound settings, and then pick soundField one if present. If not present, then return None to make it continue, it will just have to effect to display no sound mode and not able to select one (Exactly what we want).
2024-06-21 18:41:30 +02:00
Thomas Kistler
5edf480a15 Fix Hydrawise volume unit bug (#119988) 2024-06-21 18:41:27 +02:00
Erik Montnemery
96adf98625 Always create a new HomeAssistant object when falling back to recovery mode (#119969) 2024-06-21 18:41:24 +02:00
Bouwe Westerdijk
500ef94ad4 Bump plugwise to v0.37.4.1 (#119963)
* Bump plugwise to v0.37.4

* bump plugwise to v0.37.4.1
2024-06-21 18:41:21 +02:00
Artur Pragacz
a55e82366a Fix Onkyo zone volume (#119949) 2024-06-21 18:41:18 +02:00
G Johansson
c3607bd6d5 Bump python-holidays to 0.51 (#119918) 2024-06-21 18:41:15 +02:00
G Johansson
16314c5c7c Bump babel to 2.15.0 (#119006) 2024-06-21 18:41:10 +02:00
Jeef
91064697b5 Bump weatherflow4py to 0.2.21 (#119889) 2024-06-21 18:39:45 +02:00
Tsvi Mostovicz
5b1b137fd2 Bump hdate to 0.10.9 (#119887) 2024-06-21 18:39:42 +02:00
Brent Petit
e1225d3f56 Fix up ecobee windspeed unit (#119870) 2024-06-21 18:39:39 +02:00
J. Nick Koston
99d1de901e Bump aiozoneinfo to 0.2.0 (#119845) 2024-06-21 18:39:35 +02:00
Marc Mueller
08578147f5 Pin tenacity to 8.3.0 (#119815) 2024-06-21 18:36:10 +02:00
0bmay
0b4bbbffc8 Bump py-canary to v0.5.4 (#119793)
fix gathering data from Canary sensors
2024-06-21 18:36:06 +02:00
dubstomp
98aeb0b034 Add Kasa Dimmer to Matter TRANSITION_BLOCKLIST (#119751) 2024-06-21 18:36:03 +02:00
jjlawren
7a9537dcc9 Fix model import in Spotify (#119747)
* Always import HomeAssistantSpotifyData in spotify.media_browser

Relocate HomeAssistantSpotifyData to avoid circular import

* Fix moved import

* Rename module to 'models'

* Adjust docstring
2024-06-21 18:36:00 +02:00
Tsvi Mostovicz
8e63bd3ac0 Fix Jewish Calendar unique id migration (#119683)
* Implement correct passing fix

* Keep the test as is, as it simulates the current behavior

* Last minor change
2024-06-21 18:35:57 +02:00
Lode Smets
ddec6d04e1 Fix for Synology DSM shared images (#117695)
* Fix for shared images

* - FIX: Synology shared photos

* - changes after review

* Added test

* added test

* fix test
2024-06-21 18:35:48 +02:00
Robert Svensson
94800cb11e UniFi temp fix to handle runtime data (#120031)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-06-21 10:55:21 +02:00
Franck Nijhof
394dafd980 2024.6.3 (#119742) 2024-06-15 21:05:26 +02:00
Franck Nijhof
eba429dc54 Temporary pin CI to Python 3.12.3 (#119261) 2024-06-15 20:36:35 +02:00
Franck Nijhof
89ce8478de Bump version to 2024.6.3 2024-06-15 18:23:39 +02:00
Franck Nijhof
a4a8315376 Ensure workday issues are not persistent (#119732) 2024-06-15 18:23:29 +02:00
Franck Nijhof
3a705fd668 Ensure UniFi Protect EA warning is not persistent (#119730) 2024-06-15 18:23:25 +02:00
TheJulianJES
dc0fc318b8 Bump ZHA dependencies (#119713)
* Bump bellows to 0.39.1

* Bump zigpy to 0.64.1
2024-06-15 18:23:22 +02:00
J. Nick Koston
5ceb8537eb Bump uiprotect to 1.7.2 (#119705)
changelog: https://github.com/uilibs/uiprotect/compare/v1.7.1...v1.7.2
2024-06-15 18:23:19 +02:00
J. Nick Koston
d7d7782a69 Bump uiprotect to 1.7.1 (#119694)
changelog: https://github.com/uilibs/uiprotect/compare/v1.6.0...v1.7.0
2024-06-15 18:23:16 +02:00
G Johansson
2d4176d581 Fix alarm default code in concord232 (#119691) 2024-06-15 18:23:12 +02:00
J. Nick Koston
204e9a79c5 Bump uiprotect to 1.6.0 (#119661) 2024-06-15 18:23:09 +02:00
J. Nick Koston
ace7da2328 Bump uiprotect to 1.4.1 (#119653) 2024-06-15 18:21:52 +02:00
mletenay
dfe25ff804 Bump goodwe to 0.3.6 (#119646) 2024-06-15 18:21:49 +02:00
J. Nick Koston
2b44cf898e Soften unifiprotect EA channel message (#119641) 2024-06-15 18:21:45 +02:00
Paul Bottein
c77ed921de Update frontend to 20240610.1 (#119634) 2024-06-15 18:21:03 +02:00
Jan Bouwhuis
78e13d138f Fix group enabled platforms are preloaded if they have alternative states (#119621) 2024-06-15 18:20:05 +02:00
J. Nick Koston
4e394597bd Bump uiprotect to 1.2.1 (#119620)
* Bump uiprotect to 1.2.0

changelog: https://github.com/uilibs/uiprotect/compare/v1.1.0...v1.2.0

* bump
2024-06-15 18:20:02 +02:00
starkillerOG
78c2dc708c Fix error for Reolink snapshot streams (#119572) 2024-06-15 18:19:58 +02:00
Ethem Cem Özkan
4c1d2e7ac8 Revert "Revert Use integration fallback configuration for tado water fallback" (#119526)
* Revert "Revert Use integration fallback configuration for tado water heater fallback (#119466)"

This reverts commit ade936e6d5.

* add decide method for duration

* add repair issue to let users know

* test module for repairs

* Update strings.json

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

* repair issue should not be persistent

* use issue_registery fixture instead of mocking

* fix comment

* parameterize repair issue created test case

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-06-15 18:19:55 +02:00
Jan-Philipp Benecke
7b809a8e55 Partially revert "Add more debug logging to Ping integration" (#119487) 2024-06-15 18:19:52 +02:00
Erwin Douna
4eea448f9d Revert Use integration fallback configuration for tado water heater fallback (#119466) 2024-06-15 18:19:48 +02:00
Joakim Plate
f58882c878 Add loggers to gardena bluetooth (#119460) 2024-06-15 18:19:45 +02:00
J. Nick Koston
4e6e9f35b5 Bump uiprotect to 1.1.0 (#119449) 2024-06-15 18:19:42 +02:00
Sebastian Goscik
d5e9976b2c Bump uiprotect to v1.0.1 (#119436) 2024-06-15 18:19:39 +02:00
MJJ
8d547d4599 Bump buieradar to 1.0.6 (#119433) 2024-06-15 18:19:32 +02:00
J. Nick Koston
94d79440a0 Fix incorrect key name in unifiprotect options strings (#119417) 2024-06-15 18:19:29 +02:00
J. Nick Koston
d602b7d19b Bump uiprotect to 1.0.0 (#119415) 2024-06-15 18:19:26 +02:00
J. Nick Koston
fb5de55c3e Bump uiprotect to 0.13.0 (#119344) 2024-06-15 18:19:23 +02:00
J. Nick Koston
5cf0ee936d Bump uiprotect to 0.10.1 (#119327)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2024-06-15 18:19:19 +02:00
tronikos
7443878333 Make remaining time of timers available to LLMs (#118696)
* Include speech_slots in IntentResponse.as_dict

* Populate speech_slots only if available

* fix typo

* Add test

* test all fields

* Fix another test

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-06-15 18:19:16 +02:00
68 changed files with 556 additions and 175 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -10,7 +10,7 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: "3.11"
DEFAULT_PYTHON: "3.12.3"
jobs:
upload:

View File

@@ -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}}

View File

@@ -256,22 +256,39 @@ async def async_setup_hass(
runtime_config: RuntimeConfig,
) -> core.HomeAssistant | None:
"""Set up Home Assistant."""
hass = core.HomeAssistant(runtime_config.config_dir)
async_enable_logging(
hass,
runtime_config.verbose,
runtime_config.log_rotate_days,
runtime_config.log_file,
runtime_config.log_no_color,
)
def create_hass() -> core.HomeAssistant:
"""Create the hass object and do basic setup."""
hass = core.HomeAssistant(runtime_config.config_dir)
loader.async_setup(hass)
if runtime_config.debug or hass.loop.get_debug():
hass.config.debug = True
async_enable_logging(
hass,
runtime_config.verbose,
runtime_config.log_rotate_days,
runtime_config.log_file,
runtime_config.log_no_color,
)
if runtime_config.debug or hass.loop.get_debug():
hass.config.debug = True
hass.config.safe_mode = runtime_config.safe_mode
hass.config.skip_pip = runtime_config.skip_pip
hass.config.skip_pip_packages = runtime_config.skip_pip_packages
return hass
async def stop_hass(hass: core.HomeAssistant) -> None:
"""Stop hass."""
# Ask integrations to shut down. It's messy but we can't
# do a clean stop without knowing what is broken
with contextlib.suppress(TimeoutError):
async with hass.timeout.async_timeout(10):
await hass.async_stop()
hass = create_hass()
hass.config.safe_mode = runtime_config.safe_mode
hass.config.skip_pip = runtime_config.skip_pip
hass.config.skip_pip_packages = runtime_config.skip_pip_packages
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
_LOGGER.warning(
"Skipping pip installation of required modules. This may cause issues"
@@ -283,7 +300,6 @@ async def async_setup_hass(
_LOGGER.info("Config directory: %s", runtime_config.config_dir)
loader.async_setup(hass)
block_async_io.enable()
config_dict = None
@@ -309,27 +325,28 @@ async def async_setup_hass(
if config_dict is None:
recovery_mode = True
await stop_hass(hass)
hass = create_hass()
elif not basic_setup_success:
_LOGGER.warning("Unable to set up core integrations. Activating recovery mode")
recovery_mode = True
await stop_hass(hass)
hass = create_hass()
elif any(domain not in hass.config.components for domain in CRITICAL_INTEGRATIONS):
_LOGGER.warning(
"Detected that %s did not load. Activating recovery mode",
",".join(CRITICAL_INTEGRATIONS),
)
# Ask integrations to shut down. It's messy but we can't
# do a clean stop without knowing what is broken
with contextlib.suppress(TimeoutError):
async with hass.timeout.async_timeout(10):
await hass.async_stop()
recovery_mode = True
old_config = hass.config
old_logging = hass.data.get(DATA_LOGGING)
hass = core.HomeAssistant(old_config.config_dir)
recovery_mode = True
await stop_hass(hass)
hass = create_hass()
if old_logging:
hass.data[DATA_LOGGING] = old_logging
hass.config.debug = old_config.debug

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/aemet",
"iot_class": "cloud_polling",
"loggers": ["aemet_opendata"],
"requirements": ["AEMET-OpenData==0.5.1"]
"requirements": ["AEMET-OpenData==0.5.2"]
}

View File

@@ -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"]
}

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/canary",
"iot_class": "cloud_polling",
"loggers": ["canary"],
"requirements": ["py-canary==0.5.3"]
"requirements": ["py-canary==0.5.4"]
}

View File

@@ -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)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==1.7.1", "home-assistant-intents==2024.6.5"]
"requirements": ["hassil==1.7.1", "home-assistant-intents==2024.6.21"]
}

View File

@@ -59,7 +59,7 @@ class EcobeeWeather(WeatherEntity):
_attr_native_pressure_unit = UnitOfPressure.HPA
_attr_native_temperature_unit = UnitOfTemperature.FAHRENHEIT
_attr_native_visibility_unit = UnitOfLength.METERS
_attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
_attr_native_wind_speed_unit = UnitOfSpeed.MILES_PER_HOUR
_attr_has_entity_name = True
_attr_name = None
_attr_supported_features = WeatherEntityFeature.FORECAST_DAILY

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
"iot_class": "cloud_polling",
"loggers": ["env_canada"],
"requirements": ["env-canada==0.6.2"]
"requirements": ["env-canada==0.6.3"]
}

View File

@@ -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"]
}

View File

@@ -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"]
}

View File

@@ -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"]
}

View File

@@ -4,7 +4,10 @@
"after_dependencies": [
"alarm_control_panel",
"climate",
"cover",
"device_tracker",
"lock",
"media_player",
"person",
"plant",
"vacuum",

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.50", "babel==2.13.1"]
"requirements": ["holidays==0.51", "babel==2.15.0"]
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/hydrawise",
"iot_class": "cloud_polling",
"loggers": ["pydrawise"],
"requirements": ["pydrawise==2024.6.3"]
"requirements": ["pydrawise==2024.6.4"]
}

View File

@@ -48,7 +48,7 @@ def _get_zone_daily_active_water_use(sensor: HydrawiseSensor) -> float:
return float(daily_water_summary.active_use_by_zone_id.get(sensor.zone.id, 0.0))
def _get_controller_daily_active_water_use(sensor: HydrawiseSensor) -> float:
def _get_controller_daily_active_water_use(sensor: HydrawiseSensor) -> float | None:
"""Get active water use for the controller."""
daily_water_summary = sensor.coordinator.data.daily_water_use[sensor.controller.id]
return daily_water_summary.total_active_use
@@ -71,7 +71,6 @@ FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = (
key="daily_total_water_use",
translation_key="daily_total_water_use",
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=UnitOfVolume.GALLONS,
suggested_display_precision=1,
value_fn=_get_controller_daily_total_water_use,
),
@@ -79,7 +78,6 @@ FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = (
key="daily_active_water_use",
translation_key="daily_active_water_use",
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=UnitOfVolume.GALLONS,
suggested_display_precision=1,
value_fn=_get_controller_daily_active_water_use,
),
@@ -87,7 +85,6 @@ FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = (
key="daily_inactive_water_use",
translation_key="daily_inactive_water_use",
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=UnitOfVolume.GALLONS,
suggested_display_precision=1,
value_fn=_get_controller_daily_inactive_water_use,
),
@@ -98,7 +95,6 @@ FLOW_ZONE_SENSORS: tuple[SensorEntityDescription, ...] = (
key="daily_active_water_use",
translation_key="daily_active_water_use",
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=UnitOfVolume.GALLONS,
suggested_display_precision=1,
value_fn=_get_zone_daily_active_water_use,
),
@@ -165,6 +161,17 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity):
entity_description: HydrawiseSensorEntityDescription
@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit_of_measurement of the sensor."""
if self.entity_description.device_class != SensorDeviceClass.VOLUME:
return self.entity_description.native_unit_of_measurement
return (
UnitOfVolume.GALLONS
if self.coordinator.data.user.units.units_name == "imperial"
else UnitOfVolume.LITERS
)
@property
def icon(self) -> str | None:
"""Icon of the entity based on the value."""

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/imap",
"iot_class": "cloud_push",
"loggers": ["aioimaplib"],
"requirements": ["aioimaplib==1.0.1"]
"requirements": ["aioimaplib==1.1.0"]
}

View File

@@ -72,11 +72,14 @@ def get_unique_prefix(
havdalah_offset: int | None,
) -> str:
"""Create a prefix for unique ids."""
# location.altitude was unset before 2024.6 when this method
# was used to create the unique id. As such it would always
# use the default altitude of 754.
config_properties = [
location.latitude,
location.longitude,
location.timezone,
location.altitude,
754,
location.diaspora,
language,
candle_lighting_offset,

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/jewish_calendar",
"iot_class": "calculated",
"loggers": ["hdate"],
"requirements": ["hdate==0.10.8"],
"requirements": ["hdate==0.10.9"],
"single_config_entry": true
}

View File

@@ -56,6 +56,7 @@ TRANSITION_BLOCKLIST = (
(5010, 769, "3.0", "1.0.0"),
(4999, 25057, "1.0", "27.0"),
(4448, 36866, "V1", "V1.0.0.5"),
(5009, 514, "1.0", "1.0.0"),
)

View File

@@ -341,7 +341,7 @@ class OnkyoDevice(MediaPlayerEntity):
del self._attr_extra_state_attributes[ATTR_PRESET]
self._attr_is_volume_muted = bool(mute_raw[1] == "on")
# AMP_VOL/MAX_RECEIVER_VOL*(MAX_VOL/100)
# AMP_VOL / (MAX_RECEIVER_VOL * (MAX_VOL / 100))
self._attr_volume_level = volume_raw[1] / (
self._receiver_max_volume * self._max_volume / 100
)
@@ -511,9 +511,9 @@ class OnkyoDeviceZone(OnkyoDevice):
elif ATTR_PRESET in self._attr_extra_state_attributes:
del self._attr_extra_state_attributes[ATTR_PRESET]
if self._supports_volume:
# AMP_VOL/MAX_RECEIVER_VOL*(MAX_VOL/100)
self._attr_volume_level = (
volume_raw[1] / self._receiver_max_volume * (self._max_volume / 100)
# AMP_VOL / (MAX_RECEIVER_VOL * (MAX_VOL / 100))
self._attr_volume_level = volume_raw[1] / (
self._receiver_max_volume * self._max_volume / 100
)
@property

View File

@@ -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:

View File

@@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["plugwise"],
"requirements": ["plugwise==0.37.3"],
"requirements": ["plugwise==0.37.4.1"],
"zeroconf": ["_plugwise._tcp.local."]
}

View File

@@ -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}"

View File

@@ -140,7 +140,12 @@ class SongpalEntity(MediaPlayerEntity):
async def _get_sound_modes_info(self):
"""Get available sound modes and the active one."""
settings = await self._dev.get_sound_settings("soundField")
for settings in await self._dev.get_sound_settings():
if settings.target == "soundField":
break
else:
return None, {}
if isinstance(settings, Setting):
settings = [settings]

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
from typing import Any
@@ -22,6 +21,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .browse_media import async_browse_media
from .const import DOMAIN, LOGGER, SPOTIFY_SCOPES
from .models import HomeAssistantSpotifyData
from .util import (
is_spotify_media_type,
resolve_spotify_media_type,
@@ -39,16 +39,6 @@ __all__ = [
]
@dataclass
class HomeAssistantSpotifyData:
"""Spotify data stored in the Home Assistant data object."""
client: Spotify
current_user: dict[str, Any]
devices: DataUpdateCoordinator[list[dict[str, Any]]]
session: OAuth2Session
type SpotifyConfigEntry = ConfigEntry[HomeAssistantSpotifyData]

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from enum import StrEnum
from functools import partial
import logging
from typing import TYPE_CHECKING, Any
from typing import Any
from spotipy import Spotify
import yarl
@@ -20,11 +20,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from .const import DOMAIN, MEDIA_PLAYER_PREFIX, MEDIA_TYPE_SHOW, PLAYABLE_MEDIA_TYPES
from .models import HomeAssistantSpotifyData
from .util import fetch_image_url
if TYPE_CHECKING:
from . import HomeAssistantSpotifyData
BROWSE_LIMIT = 48

View File

@@ -29,9 +29,10 @@ from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import utcnow
from . import HomeAssistantSpotifyData, SpotifyConfigEntry
from . import SpotifyConfigEntry
from .browse_media import async_browse_media_internal
from .const import DOMAIN, MEDIA_PLAYER_PREFIX, PLAYABLE_MEDIA_TYPES, SPOTIFY_SCOPES
from .models import HomeAssistantSpotifyData
from .util import fetch_image_url
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,19 @@
"""Models for use in Spotify integration."""
from dataclasses import dataclass
from typing import Any
from spotipy import Spotify
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@dataclass
class HomeAssistantSpotifyData:
"""Spotify data stored in the Home Assistant data object."""
client: Spotify
current_user: dict[str, Any]
devices: DataUpdateCoordinator[list[dict[str, Any]]]
session: OAuth2Session

View File

@@ -46,6 +46,8 @@ DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED
ENTITY_UNIT_LOAD = "load"
SHARED_SUFFIX = "_shared"
# Signals
SIGNAL_CAMERA_SOURCE_CHANGED = "synology_dsm.camera_stream_source_changed"

View File

@@ -21,7 +21,7 @@ from homeassistant.components.media_source import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .const import DOMAIN, SHARED_SUFFIX
from .models import SynologyDSMData
@@ -45,6 +45,7 @@ class SynologyPhotosMediaSourceIdentifier:
self.album_id = None
self.cache_key = None
self.file_name = None
self.is_shared = False
if parts:
self.unique_id = parts[0]
@@ -54,6 +55,9 @@ class SynologyPhotosMediaSourceIdentifier:
self.cache_key = parts[2]
if len(parts) > 3:
self.file_name = parts[3]
if self.file_name.endswith(SHARED_SUFFIX):
self.is_shared = True
self.file_name = self.file_name.removesuffix(SHARED_SUFFIX)
class SynologyPhotosMediaSource(MediaSource):
@@ -160,10 +164,13 @@ class SynologyPhotosMediaSource(MediaSource):
if isinstance(mime_type, str) and mime_type.startswith("image/"):
# Force small small thumbnails
album_item.thumbnail_size = "sm"
suffix = ""
if album_item.is_shared:
suffix = SHARED_SUFFIX
ret.append(
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}/{identifier.album_id}/{album_item.thumbnail_cache_key}/{album_item.file_name}",
identifier=f"{identifier.unique_id}/{identifier.album_id}/{album_item.thumbnail_cache_key}/{album_item.file_name}{suffix}",
media_class=MediaClass.IMAGE,
media_content_type=mime_type,
title=album_item.file_name,
@@ -186,8 +193,11 @@ class SynologyPhotosMediaSource(MediaSource):
mime_type, _ = mimetypes.guess_type(identifier.file_name)
if not isinstance(mime_type, str):
raise Unresolvable("No file extension")
suffix = ""
if identifier.is_shared:
suffix = SHARED_SUFFIX
return PlayMedia(
f"/synology_dsm/{identifier.unique_id}/{identifier.cache_key}/{identifier.file_name}",
f"/synology_dsm/{identifier.unique_id}/{identifier.cache_key}/{identifier.file_name}{suffix}",
mime_type,
)
@@ -223,13 +233,14 @@ class SynologyDsmMediaView(http.HomeAssistantView):
# location: {cache_key}/{filename}
cache_key, file_name = location.split("/")
image_id = int(cache_key.split("_")[0])
if shared := file_name.endswith(SHARED_SUFFIX):
file_name = file_name.removesuffix(SHARED_SUFFIX)
mime_type, _ = mimetypes.guess_type(file_name)
if not isinstance(mime_type, str):
raise web.HTTPNotFound
diskstation: SynologyDSMData = self.hass.data[DOMAIN][source_dir_id]
assert diskstation.api.photos is not None
item = SynoPhotosItem(image_id, "", "", "", cache_key, "", False)
item = SynoPhotosItem(image_id, "", "", "", cache_key, "", shared)
try:
image = await diskstation.api.photos.download_item(item)
except SynologyDSMException as exc:

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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."
}
}
}

View File

@@ -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,

View File

@@ -164,10 +164,14 @@ class UnifiFlowHandler(ConfigFlow, domain=UNIFI_DOMAIN):
abort_reason = "reauth_successful"
if config_entry:
hub = config_entry.runtime_data
try:
hub = config_entry.runtime_data
if hub and hub.available:
return self.async_abort(reason="already_configured")
if hub and hub.available:
return self.async_abort(reason="already_configured")
except AttributeError:
pass
return self.async_update_reload_and_abort(
config_entry, data=self.config, reason=abort_reason

View File

@@ -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)},

View File

@@ -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",

View File

@@ -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%]",

View File

@@ -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

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud",
"iot_class": "cloud_polling",
"requirements": ["weatherflow4py==0.2.20"]
"requirements": ["weatherflow4py==0.2.21"]
}

View File

@@ -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={

View File

@@ -7,5 +7,5 @@
"iot_class": "local_polling",
"loggers": ["holidays"],
"quality_scale": "internal",
"requirements": ["holidays==0.50"]
"requirements": ["holidays==0.51"]
}

View File

@@ -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",

View File

@@ -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 = "4"
__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)

View File

@@ -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] = {}

View File

@@ -7,7 +7,7 @@ aiohttp-fast-url-dispatcher==0.3.0
aiohttp-fast-zlib==0.1.0
aiohttp==3.9.5
aiohttp_cors==0.7.0
aiozoneinfo==0.1.0
aiozoneinfo==0.2.0
astral==2.2
async-interrupt==1.1.1
async-upnp-client==0.38.3
@@ -32,8 +32,8 @@ 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-intents==2024.6.5
home-assistant-frontend==20240610.1
home-assistant-intents==2024.6.21
httpx==0.27.0
ifaddr==0.2.0
Jinja2==3.1.4
@@ -197,3 +197,6 @@ scapy>=2.5.0
# Only tuf>=4 includes a constraint to <1.0.
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
tuf>=4.0.0
# https://github.com/jd/tenacity/issues/471
tenacity<8.4.0

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2024.6.2"
version = "2024.6.4"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"
@@ -28,7 +28,7 @@ dependencies = [
"aiohttp_cors==0.7.0",
"aiohttp-fast-url-dispatcher==0.3.0",
"aiohttp-fast-zlib==0.1.0",
"aiozoneinfo==0.1.0",
"aiozoneinfo==0.2.0",
"astral==2.2",
"async-interrupt==1.1.1",
"attrs==23.2.0",

View File

@@ -8,7 +8,7 @@ aiohttp==3.9.5
aiohttp_cors==0.7.0
aiohttp-fast-url-dispatcher==0.3.0
aiohttp-fast-zlib==0.1.0
aiozoneinfo==0.1.0
aiozoneinfo==0.2.0
astral==2.2
async-interrupt==1.1.1
attrs==23.2.0

View File

@@ -4,7 +4,7 @@
-r requirements.txt
# homeassistant.components.aemet
AEMET-OpenData==0.5.1
AEMET-OpenData==0.5.2
# homeassistant.components.honeywell
AIOSomecomfort==0.0.25
@@ -261,7 +261,7 @@ aiohomekit==3.1.5
aiohue==4.7.1
# homeassistant.components.imap
aioimaplib==1.0.1
aioimaplib==1.1.0
# homeassistant.components.apache_kafka
aiokafka==0.10.0
@@ -526,7 +526,7 @@ azure-kusto-ingest==3.1.0
azure-servicebus==7.10.0
# homeassistant.components.holiday
babel==2.13.1
babel==2.15.0
# homeassistant.components.baidu
baidu-aip==1.6.6
@@ -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
@@ -810,7 +810,7 @@ enocean==0.50
enturclient==0.2.4
# homeassistant.components.environment_canada
env-canada==0.6.2
env-canada==0.6.3
# homeassistant.components.season
ephem==4.1.5
@@ -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
@@ -1056,7 +1056,7 @@ hass-splunk==0.1.1
hassil==1.7.1
# homeassistant.components.jewish_calendar
hdate==0.10.8
hdate==0.10.9
# homeassistant.components.heatmiser
heatmiserV3==1.1.18
@@ -1084,13 +1084,13 @@ hole==0.8.0
# homeassistant.components.holiday
# homeassistant.components.workday
holidays==0.50
holidays==0.51
# homeassistant.components.frontend
home-assistant-frontend==20240610.0
home-assistant-frontend==20240610.1
# homeassistant.components.conversation
home-assistant-intents==2024.6.5
home-assistant-intents==2024.6.21
# homeassistant.components.home_connect
homeconnect==0.7.2
@@ -1566,7 +1566,7 @@ plexauth==0.0.6
plexwebsocket==0.0.14
# homeassistant.components.plugwise
plugwise==0.37.3
plugwise==0.37.4.1
# homeassistant.components.plum_lightpad
plumlightpad==0.0.11
@@ -1619,7 +1619,7 @@ pvo==2.1.1
py-aosmith==1.0.8
# homeassistant.components.canary
py-canary==0.5.3
py-canary==0.5.4
# homeassistant.components.ccm15
py-ccm15==0.0.9
@@ -1794,7 +1794,7 @@ pydiscovergy==3.0.1
pydoods==1.0.2
# homeassistant.components.hydrawise
pydrawise==2024.6.3
pydrawise==2024.6.4
# homeassistant.components.android_ip_webcam
pydroid-ipcam==2.0.0
@@ -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
@@ -2867,7 +2867,7 @@ watchdog==2.3.1
waterfurnace==1.1.0
# homeassistant.components.weatherflow_cloud
weatherflow4py==0.2.20
weatherflow4py==0.2.21
# homeassistant.components.webmin
webmin-xmlrpc==0.0.2
@@ -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

View File

@@ -4,7 +4,7 @@
-r requirements_test.txt
# homeassistant.components.aemet
AEMET-OpenData==0.5.1
AEMET-OpenData==0.5.2
# homeassistant.components.honeywell
AIOSomecomfort==0.0.25
@@ -237,7 +237,7 @@ aiohomekit==3.1.5
aiohue==4.7.1
# homeassistant.components.imap
aioimaplib==1.0.1
aioimaplib==1.1.0
# homeassistant.components.apache_kafka
aiokafka==0.10.0
@@ -463,7 +463,7 @@ azure-kusto-data[aio]==3.1.0
azure-kusto-ingest==3.1.0
# homeassistant.components.holiday
babel==2.13.1
babel==2.15.0
# homeassistant.components.homekit
base36==0.1.1
@@ -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
@@ -664,7 +664,7 @@ energyzero==2.1.0
enocean==0.50
# homeassistant.components.environment_canada
env-canada==0.6.2
env-canada==0.6.3
# homeassistant.components.season
ephem==4.1.5
@@ -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
@@ -867,7 +867,7 @@ hass-nabucasa==0.81.1
hassil==1.7.1
# homeassistant.components.jewish_calendar
hdate==0.10.8
hdate==0.10.9
# homeassistant.components.here_travel_time
here-routing==0.2.0
@@ -886,13 +886,13 @@ hole==0.8.0
# homeassistant.components.holiday
# homeassistant.components.workday
holidays==0.50
holidays==0.51
# homeassistant.components.frontend
home-assistant-frontend==20240610.0
home-assistant-frontend==20240610.1
# homeassistant.components.conversation
home-assistant-intents==2024.6.5
home-assistant-intents==2024.6.21
# homeassistant.components.home_connect
homeconnect==0.7.2
@@ -1243,7 +1243,7 @@ plexauth==0.0.6
plexwebsocket==0.0.14
# homeassistant.components.plugwise
plugwise==0.37.3
plugwise==0.37.4.1
# homeassistant.components.plum_lightpad
plumlightpad==0.0.11
@@ -1284,7 +1284,7 @@ pvo==2.1.1
py-aosmith==1.0.8
# homeassistant.components.canary
py-canary==0.5.3
py-canary==0.5.4
# homeassistant.components.ccm15
py-ccm15==0.0.9
@@ -1405,7 +1405,7 @@ pydexcom==0.2.3
pydiscovergy==3.0.1
# homeassistant.components.hydrawise
pydrawise==2024.6.3
pydrawise==2024.6.4
# homeassistant.components.android_ip_webcam
pydroid-ipcam==2.0.0
@@ -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
@@ -2226,7 +2226,7 @@ wallbox==0.6.0
watchdog==2.3.1
# homeassistant.components.weatherflow_cloud
weatherflow4py==0.2.20
weatherflow4py==0.2.21
# homeassistant.components.webmin
webmin-xmlrpc==0.0.2
@@ -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

View File

@@ -219,6 +219,9 @@ scapy>=2.5.0
# Only tuf>=4 includes a constraint to <1.0.
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
tuf>=4.0.0
# https://github.com/jd/tenacity/issues/471
tenacity<8.4.0
"""
GENERATED_MESSAGE = (

View File

@@ -563,7 +563,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called kitchen light',
'speech': 'Sorry, I am not aware of any device called kitchen',
}),
}),
}),
@@ -703,7 +703,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called late added light',
'speech': 'Sorry, I am not aware of any device called late added',
}),
}),
}),
@@ -783,7 +783,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called kitchen light',
'speech': 'Sorry, I am not aware of any device called kitchen',
}),
}),
}),
@@ -803,7 +803,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called my cool light',
'speech': 'Sorry, I am not aware of any device called my cool',
}),
}),
}),
@@ -943,7 +943,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called kitchen light',
'speech': 'Sorry, I am not aware of any device called kitchen',
}),
}),
}),
@@ -993,7 +993,7 @@
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'Sorry, I am not aware of any device called renamed light',
'speech': 'Sorry, I am not aware of any device called renamed',
}),
}),
}),

View File

@@ -15,6 +15,7 @@ from pydrawise.schema import (
Sensor,
SensorModel,
SensorStatus,
UnitsSummary,
User,
Zone,
)
@@ -84,7 +85,11 @@ def mock_auth() -> Generator[AsyncMock, None, None]:
@pytest.fixture
def user() -> User:
"""Hydrawise User fixture."""
return User(customer_id=12345, email="asdf@asdf.com")
return User(
customer_id=12345,
email="asdf@asdf.com",
units=UnitsSummary(units_name="imperial"),
)
@pytest.fixture

View File

@@ -3,13 +3,18 @@
from collections.abc import Awaitable, Callable
from unittest.mock import patch
from pydrawise.schema import Controller, Zone
from pydrawise.schema import Controller, User, Zone
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util.unit_system import (
METRIC_SYSTEM,
US_CUSTOMARY_SYSTEM,
UnitSystem,
)
from tests.common import MockConfigEntry, snapshot_platform
@@ -45,7 +50,7 @@ async def test_suspended_state(
assert next_cycle.state == "unknown"
async def test_no_sensor_and_water_state2(
async def test_no_sensor_and_water_state(
hass: HomeAssistant,
controller: Controller,
mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]],
@@ -63,3 +68,30 @@ async def test_no_sensor_and_water_state2(
sensor = hass.states.get("binary_sensor.home_controller_connectivity")
assert sensor is not None
assert sensor.state == "on"
@pytest.mark.parametrize(
("hydrawise_unit_system", "unit_system", "expected_state"),
[
("imperial", METRIC_SYSTEM, "454.6279552584"),
("imperial", US_CUSTOMARY_SYSTEM, "120.1"),
("metric", METRIC_SYSTEM, "120.1"),
("metric", US_CUSTOMARY_SYSTEM, "31.7270634882136"),
],
)
async def test_volume_unit_conversion(
hass: HomeAssistant,
unit_system: UnitSystem,
hydrawise_unit_system: str,
expected_state: str,
user: User,
mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]],
) -> None:
"""Test volume unit conversion."""
hass.config.units = unit_system
user.units.units_name = hydrawise_unit_system
await mock_add_config_entry()
daily_active_water_use = hass.states.get("sensor.zone_one_daily_active_water_use")
assert daily_active_water_use is not None
assert daily_active_water_use.state == expected_state

View File

@@ -38,7 +38,6 @@ async def test_import_unique_id_migration(hass: HomeAssistant) -> None:
latitude=yaml_conf[DOMAIN][CONF_LATITUDE],
longitude=yaml_conf[DOMAIN][CONF_LONGITUDE],
timezone=hass.config.time_zone,
altitude=hass.config.elevation,
diaspora=DEFAULT_DIASPORA,
)
old_prefix = get_unique_prefix(location, DEFAULT_LANGUAGE, 20, 50)

View File

@@ -23,7 +23,9 @@ CONF_DATA = {
}
def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=None):
def _create_mocked_device(
throw_exception=False, wired_mac=MAC, wireless_mac=None, no_soundfield=False
):
mocked_device = MagicMock()
type(mocked_device).get_supported_methods = AsyncMock(
@@ -101,7 +103,14 @@ def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=Non
soundField = MagicMock()
soundField.currentValue = "sound_mode2"
soundField.candidate = [sound_mode1, sound_mode2, sound_mode3]
type(mocked_device).get_sound_settings = AsyncMock(return_value=[soundField])
settings = MagicMock()
settings.target = "soundField"
settings.__iter__.return_value = [soundField]
type(mocked_device).get_sound_settings = AsyncMock(
return_value=[] if no_soundfield else [settings]
)
type(mocked_device).set_power = AsyncMock()
type(mocked_device).set_sound_settings = AsyncMock()

View File

@@ -159,6 +159,43 @@ async def test_state(
assert entity.unique_id == MAC
async def test_state_nosoundmode(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test state of the entity with no soundField in sound settings."""
mocked_device = _create_mocked_device(no_soundfield=True)
entry = MockConfigEntry(domain=songpal.DOMAIN, data=CONF_DATA)
entry.add_to_hass(hass)
with _patch_media_player_device(mocked_device):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state.name == FRIENDLY_NAME
assert state.state == STATE_ON
attributes = state.as_dict()["attributes"]
assert attributes["volume_level"] == 0.5
assert attributes["is_volume_muted"] is False
assert attributes["source_list"] == ["title1", "title2"]
assert attributes["source"] == "title2"
assert "sound_mode_list" not in attributes
assert "sound_mode" not in attributes
assert attributes["supported_features"] == SUPPORT_SONGPAL
device = device_registry.async_get_device(identifiers={(songpal.DOMAIN, MAC)})
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, MAC)}
assert device.manufacturer == "Sony Corporation"
assert device.name == FRIENDLY_NAME
assert device.sw_version == SW_VERSION
assert device.model == MODEL
entity = entity_registry.async_get(ENTITY_ID)
assert entity.unique_id == MAC
async def test_state_wireless(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,

View File

@@ -50,7 +50,8 @@ def dsm_with_photos() -> MagicMock:
dsm.photos.get_albums = AsyncMock(return_value=[SynoPhotosAlbum(1, "Album 1", 10)])
dsm.photos.get_items_from_album = AsyncMock(
return_value=[
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", False)
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", False),
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", True),
]
)
dsm.photos.get_item_thumbnail_url = AsyncMock(
@@ -102,6 +103,11 @@ async def test_resolve_media_bad_identifier(
"/synology_dsm/ABC012345/12631_47189/filename.png",
"image/png",
),
(
"ABC012345/12/12631_47189/filename.png_shared",
"/synology_dsm/ABC012345/12631_47189/filename.png_shared",
"image/png",
),
],
)
async def test_resolve_media_success(
@@ -333,7 +339,7 @@ async def test_browse_media_get_items_thumbnail_error(
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
assert len(result.children) == 2
item = result.children[0]
assert isinstance(item, BrowseMedia)
assert item.thumbnail is None
@@ -372,7 +378,7 @@ async def test_browse_media_get_items(
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
assert len(result.children) == 2
item = result.children[0]
assert isinstance(item, BrowseMedia)
assert item.identifier == "mocked_syno_dsm_entry/1/10_1298753/filename.jpg"
@@ -382,6 +388,15 @@ async def test_browse_media_get_items(
assert item.can_play
assert not item.can_expand
assert item.thumbnail == "http://my.thumbnail.url"
item = result.children[1]
assert isinstance(item, BrowseMedia)
assert item.identifier == "mocked_syno_dsm_entry/1/10_1298753/filename.jpg_shared"
assert item.title == "filename.jpg"
assert item.media_class == MediaClass.IMAGE
assert item.media_content_type == "image/jpeg"
assert item.can_play
assert not item.can_expand
assert item.thumbnail == "http://my.thumbnail.url"
@pytest.mark.usefixtures("setup_media_source")
@@ -435,3 +450,8 @@ async def test_media_view(
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg"
)
assert isinstance(result, web.Response)
with patch.object(tempfile, "tempdir", tmp_path):
result = await view.get(
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg_shared"
)
assert isinstance(result, web.Response)

View File

@@ -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

View File

@@ -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
)

View File

@@ -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"

View File

@@ -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,
},
}