Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions homeassistant/components/home_connect/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Any, cast
from typing import Any

from aiohomeconnect.client import Client as HomeConnectClient
from aiohomeconnect.model import (
Expand Down Expand Up @@ -247,14 +247,15 @@ async def _event_listener(self) -> None: # noqa: C901
value=event.value,
)
else:
event_value = event.value
if event_key in (
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
):
) and isinstance(event_value, str):
await self.update_options(
event_message_ha_id,
event_key,
ProgramKey(cast(str, event.value)),
ProgramKey(event_value),
)
events[event_key] = event
self._call_event_listener(event_message)
Expand Down
7 changes: 2 additions & 5 deletions homeassistant/components/home_connect/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
TooManyRequestsError,
)

from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
Expand Down Expand Up @@ -62,10 +61,8 @@ def update_native_value(self) -> None:
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.update_native_value()
available = self._attr_available = self.appliance.info.connected
self.async_write_ha_state()
state = STATE_UNAVAILABLE if not available else self.state
_LOGGER.debug("Updated %s, new state: %s", self.entity_id, state)
_LOGGER.debug("Updated %s", self)

@property
def bsh_key(self) -> str:
Expand All @@ -80,7 +77,7 @@ def available(self) -> bool:
as event updates should take precedence over the coordinator
refresh.
"""
return self._attr_available
return self.appliance.info.connected and self._attr_available


class HomeConnectOptionEntity(HomeConnectEntity):
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/mvglive/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"documentation": "https://www.home-assistant.io/integrations/mvglive",
"iot_class": "cloud_polling",
"loggers": ["MVG"],
"quality_scale": "legacy",
"requirements": ["mvg==1.4.0"]
}
4 changes: 4 additions & 0 deletions homeassistant/components/sfr_box/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: SFRConfigEntry) -> bool:

async def async_unload_entry(hass: HomeAssistant, entry: SFRConfigEntry) -> bool:
"""Unload a config entry."""
if entry.data.get(CONF_USERNAME) and entry.data.get(CONF_PASSWORD):
return await hass.config_entries.async_unload_platforms(
entry, PLATFORMS_WITH_AUTH
)
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
20 changes: 1 addition & 19 deletions homeassistant/components/shelly/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
get_blu_trv_device_info,
get_device_entry_gen,
get_device_uptime,
get_entity_translation_attributes,
get_rpc_channel_name,
get_shelly_air_lamp_life,
get_virtual_component_unit,
Expand All @@ -73,25 +74,6 @@
PARALLEL_UPDATES = 0


def get_entity_translation_attributes(
channel_name: str | None,
translation_key: str | None,
device_class: str | None,
default_to_device_class_name: bool,
) -> tuple[dict[str, str] | None, str | None]:
"""Translation attributes for entity with channel name."""
if channel_name is None:
return None, None

key = translation_key
if key is None and default_to_device_class_name:
key = device_class

final_translation_key = f"{key}_with_channel_name" if key else None

return {"channel_name": channel_name}, final_translation_key


@dataclass(frozen=True, kw_only=True)
class BlockSensorDescription(BlockEntityDescription, SensorEntityDescription):
"""Class to describe a BLOCK sensor."""
Expand Down
17 changes: 17 additions & 0 deletions homeassistant/components/shelly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,23 @@ def get_rpc_entity_name(
return channel_name


def get_entity_translation_attributes(
channel_name: str | None,
translation_key: str | None,
device_class: str | None,
default_to_device_class_name: bool,
) -> tuple[dict[str, str] | None, str | None]:
"""Translation attributes for entity with channel name."""
if channel_name is None:
return None, None

key = translation_key
if key is None and default_to_device_class_name:
key = device_class

return {"channel_name": channel_name}, f"{key}_with_channel_name" if key else None


def get_device_entry_gen(entry: ConfigEntry) -> int:
"""Return the device generation from config entry."""
return entry.data.get(CONF_GEN, 1) # type: ignore[no-any-return]
Expand Down
28 changes: 20 additions & 8 deletions homeassistant/components/tuya/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType, WorkMode
from .entity import TuyaEntity
from .models import IntegerTypeData, find_dpcode
from .models import DPCodeBooleanWrapper, IntegerTypeData, find_dpcode
from .util import get_dpcode, get_dptype, remap_value


Expand Down Expand Up @@ -428,9 +428,15 @@ def async_discover_device(device_ids: list[str]):
device = manager.device_map[device_id]
if descriptions := LIGHTS.get(device.category):
entities.extend(
TuyaLightEntity(device, manager, description)
TuyaLightEntity(
device, manager, description, switch_wrapper=switch_wrapper
)
for description in descriptions
if description.key in device.status
if (
switch_wrapper := DPCodeBooleanWrapper.find_dpcode(
device, description.key, prefer_function=True
)
)
)

async_add_entities(entities)
Expand Down Expand Up @@ -464,11 +470,15 @@ def __init__(
device: CustomerDevice,
device_manager: Manager,
description: TuyaLightEntityDescription,
*,
switch_wrapper: DPCodeBooleanWrapper,
) -> None:
"""Init TuyaHaLight."""
super().__init__(device, device_manager)
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
self._switch_wrapper = switch_wrapper

color_modes: set[ColorMode] = {ColorMode.ONOFF}

# Determine DPCodes
Expand Down Expand Up @@ -546,13 +556,15 @@ def __init__(
self._fixed_color_mode = next(iter(self._attr_supported_color_modes))

@property
def is_on(self) -> bool:
def is_on(self) -> bool | None:
"""Return true if light is on."""
return self.device.status.get(self.entity_description.key, False)
return self._read_wrapper(self._switch_wrapper)

def turn_on(self, **kwargs: Any) -> None:
"""Turn on or control the light."""
commands = [{"code": self.entity_description.key, "value": True}]
commands = [
self._switch_wrapper.get_update_command(self.device, True),
]

if self._color_mode_dpcode and (
ATTR_WHITE in kwargs or ATTR_COLOR_TEMP_KELVIN in kwargs
Expand Down Expand Up @@ -673,9 +685,9 @@ def turn_on(self, **kwargs: Any) -> None:

self._send_command(commands)

def turn_off(self, **kwargs: Any) -> None:
async def async_turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off."""
self._send_command([{"code": self.entity_description.key, "value": False}])
await self._async_send_dpcode_update(self._switch_wrapper, False)

@property
def brightness(self) -> int | None:
Expand Down
27 changes: 16 additions & 11 deletions homeassistant/components/velux/entity.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Support for VELUX KLF 200 devices."""

from collections.abc import Awaitable, Callable

from pyvlx import Node

from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity

Expand All @@ -14,6 +15,7 @@ class VeluxEntity(Entity):

_attr_should_poll = False
_attr_has_entity_name = True
update_callback: Callable[["Node"], Awaitable[None]] | None = None

def __init__(self, node: Node, config_entry_id: str) -> None:
"""Initialize the Velux device."""
Expand All @@ -24,6 +26,7 @@ def __init__(self, node: Node, config_entry_id: str) -> None:
else f"{config_entry_id}_{node.node_id}"
)
self._attr_unique_id = unique_id
self.unsubscribe = None

self._attr_device_info = DeviceInfo(
identifiers={
Expand All @@ -37,16 +40,18 @@ def __init__(self, node: Node, config_entry_id: str) -> None:
via_device=(DOMAIN, f"gateway_{config_entry_id}"),
)

@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(self, node) -> None:
"""Call after device was updated."""
self.async_write_ha_state()

async def after_update_callback(device):
"""Call after device was updated."""
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Register callback and store reference for cleanup."""

self.node.register_device_updated_cb(after_update_callback)
self.update_callback = self.after_update_callback
self.node.register_device_updated_cb(self.update_callback)

async def async_added_to_hass(self) -> None:
"""Store register state change callback."""
self.async_register_callbacks()
async def async_will_remove_from_hass(self) -> None:
"""Clean up registered callbacks."""
if self.update_callback:
self.node.unregister_device_updated_cb(self.update_callback)
self.update_callback = None
8 changes: 2 additions & 6 deletions homeassistant/components/velux/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@ rules:
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: todo
comment: subscribe is ok, unsubscribe needs to be added
entity-event-setup: done
entity-unique-id: done
has-entity-name:
status: todo
comment: scenes need fixing
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup:
Expand Down
32 changes: 23 additions & 9 deletions homeassistant/components/velux/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@

from typing import Any

from pyvlx import Scene as PyVLXScene

from homeassistant.components.scene import Scene
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import VeluxConfigEntry
from .const import DOMAIN

PARALLEL_UPDATES = 1

Expand All @@ -20,22 +24,32 @@ async def async_setup_entry(
) -> None:
"""Set up the scenes for Velux platform."""
pyvlx = config_entry.runtime_data

entities = [VeluxScene(scene) for scene in pyvlx.scenes]
async_add_entities(entities)
async_add_entities(
[VeluxScene(config_entry.entry_id, scene) for scene in pyvlx.scenes]
)


class VeluxScene(Scene):
"""Representation of a Velux scene."""

def __init__(self, scene):
_attr_has_entity_name = True

# Note: there's currently no code to update the scenes dynamically if changed in
# the gateway. They're only loaded on integration setup (they're probably not
# used heavily anyway since it's a pain to set them up in the gateway and so
# much easier to use HA scenes).

def __init__(self, config_entry_id: str, scene: PyVLXScene) -> None:
"""Init velux scene."""
self.scene = scene

@property
def name(self):
"""Return the name of the scene."""
return self.scene.name
# Renaming scenes in gateway keeps scene_id stable, we can use it as unique_id
self._attr_unique_id = f"{config_entry_id}_scene_{scene.scene_id}"
self._attr_name = scene.name

# Associate scenes with the gateway device (where they are stored)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"gateway_{config_entry_id}")},
)

async def async_activate(self, **kwargs: Any) -> None:
"""Activate the scene."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/youtube/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/youtube",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["youtubeaio==2.0.0"]
"requirements": ["youtubeaio==2.1.0"]
}
Loading
Loading