Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024.2.3 #111133

Merged
merged 29 commits into from
Feb 22, 2024
Merged

2024.2.3 #111133

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bba7641
Fix reauth in Overkiz for config entries created prior to 2022.12 (#1…
iMicknl Feb 20, 2024
f21313b
Handle deep standby and poweroffs of enigma2 devices gracefully (#107…
autinerd Feb 17, 2024
eff82ba
Add wake up timeout to Teslemetry (#109037)
Bre77 Feb 22, 2024
9cb425a
Fix set_temperature in Tessie climate platform (#110445)
Bre77 Feb 19, 2024
54c5c94
Fix uuid issue in Lutron (#110524)
wilburCforce Feb 19, 2024
8769921
Update rokuecp to 0.19.1 (#110670)
ctalkington Feb 17, 2024
ac74eb7
Fix scene activation with climate entities with `None` attribute valu…
mib1185 Feb 16, 2024
d85a92f
Remove matplotlib pinning due to Python 3.12 incompatibility (#110706)
sbyx Feb 16, 2024
76ec26b
Bump roombapy to 1.6.12 (#110762)
mib1185 Feb 22, 2024
2a12369
Ensure Tile timestamps are reported as UTC (#110773)
bachya Feb 17, 2024
f13052f
Detect reached API rate limit in Tankerkoenig (#110432)
mib1185 Feb 13, 2024
adf24c9
Bump aiotankerkoenig to 0.4.1 (#110840)
jpbede Feb 18, 2024
6745814
Update govee-local-api library to 1.4.4 (#110854)
Galorhallen Feb 18, 2024
b7c9788
Allow loading of more then 1 defined Apprise URL (#110868)
caronc Feb 19, 2024
e3074c6
Reolink continue setup when internet blocked (#110888)
starkillerOG Feb 19, 2024
715c6a5
Bump deluge-client to 1.10.0 (#110663)
tkdrob Feb 16, 2024
bd08807
Bump deluge-client to 1.10.2 (#110905)
dsander Feb 19, 2024
85ad0a0
Bump reolink-aio to 0.8.8 (#110959)
starkillerOG Feb 19, 2024
f590f86
Reset error state when Ecovacs bot is operational again (#110962)
mib1185 Feb 20, 2024
f6bef00
Bump motionblinds to 0.6.21 (#110970)
starkillerOG Feb 19, 2024
4eb1bac
Fix Shelly RPC RSSI sensor removal (#111035)
thecode Feb 21, 2024
4f5c808
Bump holidays to 0.43 (#111039)
gjohansson-ST Feb 20, 2024
68450bb
Fixes UniFi Protect light state check (#111058)
AngellusMortis Feb 21, 2024
2cf87b8
Bump pywebpush to 1.14.1 (#111082)
thecode Feb 21, 2024
f5d7e6f
Bump aioairzone to v0.7.4 (#111105)
Noltari Feb 21, 2024
a372878
Bump deebot-client to 5.2.2 (#111112)
edenhaus Feb 21, 2024
2f8d8ee
Ignore cloudhook already removed in mobile app (#111122)
joostlek Feb 22, 2024
c76c66c
Bump version to 2024.2.3
frenck Feb 22, 2024
728399c
Revert "Fix Shelly RPC RSSI sensor removal (#111035)"
frenck Feb 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading