Skip to content

Commit

Permalink
2024.2.3 (#111133)
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck authored Feb 22, 2024
2 parents 7aa14e2 + 728399c commit 1ee3927
Show file tree
Hide file tree
Showing 40 changed files with 469 additions and 106 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/airzone/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
8 changes: 5 additions & 3 deletions homeassistant/components/apprise/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
15 changes: 12 additions & 3 deletions homeassistant/components/climate/reproduce_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,22 @@ async def call_service(
[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:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/deluge/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/ecovacs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/ecovacs/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def on_error(self, error: str) -> None:
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
Expand Down
31 changes: 26 additions & 5 deletions homeassistant/components/enigma2/media_player.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -50,6 +53,8 @@
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,
Expand Down Expand Up @@ -143,7 +148,12 @@ def __init__(self, name: str, device: OpenWebIfDevice, about: dict) -> None:

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."""
Expand Down Expand Up @@ -191,8 +201,19 @@ async def async_select_source(self, source: str) -> None:

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 = {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/govee_light_local/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/holiday/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/html5/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
110 changes: 108 additions & 2 deletions homeassistant/components/lutron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
)
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
Expand Down Expand Up @@ -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=[],
Expand All @@ -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
Expand All @@ -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)},
Expand All @@ -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)
8 changes: 4 additions & 4 deletions homeassistant/components/lutron/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ def _update_callback(
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:
Expand All @@ -63,7 +63,7 @@ def __init__(
"""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,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mobile_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
2 changes: 1 addition & 1 deletion homeassistant/components/motion_blinds/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
4 changes: 2 additions & 2 deletions homeassistant/components/overkiz/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,9 @@ async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:

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))
Expand Down
22 changes: 10 additions & 12 deletions homeassistant/components/reolink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@ async def async_check_firmware_update() -> (
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 "
Expand Down Expand Up @@ -133,15 +130,16 @@ async def async_check_firmware_update() -> (
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,
Expand Down
Loading

0 comments on commit 1ee3927

Please sign in to comment.