Skip to content

[pull] dev from home-assistant:dev #830

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

Merged
merged 5 commits into from
Jun 23, 2025
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
1 change: 1 addition & 0 deletions homeassistant/components/alexa_devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.NOTIFY,
Platform.SENSOR,
Platform.SWITCH,
]

Expand Down
88 changes: 88 additions & 0 deletions homeassistant/components/alexa_devices/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Support for sensors."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import Final

from aioamazondevices.api import AmazonDevice

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import LIGHT_LUX, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType

from .coordinator import AmazonConfigEntry
from .entity import AmazonEntity

# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class AmazonSensorEntityDescription(SensorEntityDescription):
"""Amazon Devices sensor entity description."""

native_unit_of_measurement_fn: Callable[[AmazonDevice, str], str] | None = None


SENSORS: Final = (
AmazonSensorEntityDescription(
key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement_fn=lambda device, _key: (
UnitOfTemperature.CELSIUS
if device.sensors[_key].scale == "CELSIUS"
else UnitOfTemperature.FAHRENHEIT
),
),
AmazonSensorEntityDescription(
key="illuminance",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: AmazonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Amazon Devices sensors based on a config entry."""

coordinator = entry.runtime_data

async_add_entities(
AmazonSensorEntity(coordinator, serial_num, sensor_desc)
for sensor_desc in SENSORS
for serial_num in coordinator.data
if coordinator.data[serial_num].sensors.get(sensor_desc.key) is not None
)


class AmazonSensorEntity(AmazonEntity, SensorEntity):
"""Sensor device."""

entity_description: AmazonSensorEntityDescription

@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit of measurement of the sensor."""
if self.entity_description.native_unit_of_measurement_fn:
return self.entity_description.native_unit_of_measurement_fn(
self.device, self.entity_description.key
)

return super().native_unit_of_measurement

@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.device.sensors[self.entity_description.key].value
34 changes: 19 additions & 15 deletions homeassistant/components/devolo_home_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,9 @@ async def async_setup_entry(
"""Set up the devolo account from a config entry."""
mydevolo = configure_mydevolo(entry.data)

credentials_valid = await hass.async_add_executor_job(mydevolo.credentials_valid)

if not credentials_valid:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
)

if await hass.async_add_executor_job(mydevolo.maintenance):
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="maintenance",
)

gateway_ids = await hass.async_add_executor_job(mydevolo.get_gateway_ids)
gateway_ids = await hass.async_add_executor_job(
check_mydevolo_and_get_gateway_ids, mydevolo
)

if entry.unique_id and GATEWAY_SERIAL_PATTERN.match(entry.unique_id):
uuid = await hass.async_add_executor_job(mydevolo.uuid)
Expand Down Expand Up @@ -115,3 +103,19 @@ def configure_mydevolo(conf: Mapping[str, Any]) -> Mydevolo:
mydevolo.user = conf[CONF_USERNAME]
mydevolo.password = conf[CONF_PASSWORD]
return mydevolo


def check_mydevolo_and_get_gateway_ids(mydevolo: Mydevolo) -> list[str]:
"""Check if the credentials are valid and return user's gateway IDs as long as mydevolo is not in maintenance mode."""
if not mydevolo.credentials_valid():
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
)
if mydevolo.maintenance():
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="maintenance",
)

return mydevolo.get_gateway_ids()
1 change: 1 addition & 0 deletions homeassistant/components/nina/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class NINAMessage(CoordinatorEntity[NINADataUpdateCoordinator], BinarySensorEnti
"""Representation of an NINA warning."""

_attr_device_class = BinarySensorDeviceClass.SAFETY
_attr_has_entity_name = True

def __init__(
self,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/russound_rio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from .const import DOMAIN, RUSSOUND_RIO_EXCEPTIONS

PLATFORMS = [Platform.MEDIA_PLAYER]
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.NUMBER]

_LOGGER = logging.getLogger(__name__)

Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/russound_rio/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from aiorussound import Controller, RussoundClient
from aiorussound.models import CallbackType
from aiorussound.rio import ZoneControlSurface

from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
Expand Down Expand Up @@ -58,6 +59,7 @@ def __init__(
self._controller.mac_address
or f"{self._primary_mac_address}-{self._controller.controller_id}"
)
self._zone_id = zone_id
if not zone_id:
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_identifier)},
Expand All @@ -74,6 +76,11 @@ def __init__(
via_device=(DOMAIN, self._device_identifier),
)

@property
def _zone(self) -> ZoneControlSurface:
assert self._zone_id
return self._controller.zones[self._zone_id]

async def _state_update_callback(
self, _client: RussoundClient, _callback_type: CallbackType
) -> None:
Expand Down
6 changes: 0 additions & 6 deletions homeassistant/components/russound_rio/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from aiorussound import Controller
from aiorussound.const import FeatureFlag
from aiorussound.models import PlayStatus, Source
from aiorussound.rio import ZoneControlSurface
from aiorussound.util import is_feature_supported

from homeassistant.components.media_player import (
Expand Down Expand Up @@ -67,15 +66,10 @@ def __init__(
) -> None:
"""Initialize the zone device."""
super().__init__(controller, zone_id)
self._zone_id = zone_id
_zone = self._zone
self._sources = sources
self._attr_unique_id = f"{self._primary_mac_address}-{_zone.device_str}"

@property
def _zone(self) -> ZoneControlSurface:
return self._controller.zones[self._zone_id]

@property
def _source(self) -> Source:
return self._zone.fetch_current_source()
Expand Down
112 changes: 112 additions & 0 deletions homeassistant/components/russound_rio/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""Support for Russound number entities."""

from collections.abc import Awaitable, Callable
from dataclasses import dataclass

from aiorussound.rio import Controller, ZoneControlSurface

from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import RussoundConfigEntry
from .entity import RussoundBaseEntity, command

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class RussoundZoneNumberEntityDescription(NumberEntityDescription):
"""Describes Russound number entities."""

value_fn: Callable[[ZoneControlSurface], float]
set_value_fn: Callable[[ZoneControlSurface, float], Awaitable[None]]


CONTROL_ENTITIES: tuple[RussoundZoneNumberEntityDescription, ...] = (
RussoundZoneNumberEntityDescription(
key="balance",
translation_key="balance",
native_min_value=-10,
native_max_value=10,
native_step=1,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.balance,
set_value_fn=lambda zone, value: zone.set_balance(int(value)),
),
RussoundZoneNumberEntityDescription(
key="bass",
translation_key="bass",
native_min_value=-10,
native_max_value=10,
native_step=1,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.bass,
set_value_fn=lambda zone, value: zone.set_bass(int(value)),
),
RussoundZoneNumberEntityDescription(
key="treble",
translation_key="treble",
native_min_value=-10,
native_max_value=10,
native_step=1,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.treble,
set_value_fn=lambda zone, value: zone.set_treble(int(value)),
),
RussoundZoneNumberEntityDescription(
key="turn_on_volume",
translation_key="turn_on_volume",
native_min_value=0,
native_max_value=100,
native_step=2,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.turn_on_volume * 2,
set_value_fn=lambda zone, value: zone.set_turn_on_volume(int(value / 2)),
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: RussoundConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Russound number entities based on a config entry."""
client = entry.runtime_data
async_add_entities(
RussoundNumberEntity(controller, zone_id, description)
for controller in client.controllers.values()
for zone_id in controller.zones
for description in CONTROL_ENTITIES
)


class RussoundNumberEntity(RussoundBaseEntity, NumberEntity):
"""Defines a Russound number entity."""

entity_description: RussoundZoneNumberEntityDescription

def __init__(
self,
controller: Controller,
zone_id: int,
description: RussoundZoneNumberEntityDescription,
) -> None:
"""Initialize a Russound number entity."""
super().__init__(controller, zone_id)
self.entity_description = description
self._attr_unique_id = (
f"{self._primary_mac_address}-{self._zone.device_str}-{description.key}"
)

@property
def native_value(self) -> float:
"""Return the native value of the entity."""
return float(self.entity_description.value_fn(self._zone))

@command
async def async_set_native_value(self, value: float) -> None:
"""Set the value."""
await self.entity_description.set_value_fn(self._zone, value)
16 changes: 16 additions & 0 deletions homeassistant/components/russound_rio/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@
"wrong_device": "This Russound controller does not match the existing device ID. Please make sure you entered the correct IP address."
}
},
"entity": {
"number": {
"balance": {
"name": "Balance"
},
"bass": {
"name": "Bass"
},
"treble": {
"name": "Treble"
},
"turn_on_volume": {
"name": "Turn-on volume"
}
}
},
"exceptions": {
"entry_cannot_connect": {
"message": "Error while connecting to {host}:{port}"
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/smartthings/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ class SmartThingsBinarySensorEntityDescription(BinarySensorEntityDescription):
entity_category=EntityCategory.DIAGNOSTIC,
)
},
Capability.CUSTOM_WATER_FILTER: {
Attribute.WATER_FILTER_STATUS: SmartThingsBinarySensorEntityDescription(
key=Attribute.WATER_FILTER_STATUS,
translation_key="filter_status",
device_class=BinarySensorDeviceClass.PROBLEM,
is_on_key="replace",
)
},
Capability.SAMSUNG_CE_STEAM_CLOSET_KEEP_FRESH_MODE: {
Attribute.OPERATING_STATE: SmartThingsBinarySensorEntityDescription(
key=Attribute.OPERATING_STATE,
Expand Down
10 changes: 10 additions & 0 deletions homeassistant/components/smartthings/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,16 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription):
)
]
},
Capability.CUSTOM_WATER_FILTER: {
Attribute.WATER_FILTER_USAGE: [
SmartThingsSensorEntityDescription(
key=Attribute.WATER_FILTER_USAGE,
translation_key="water_filter_usage",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.DISHWASHER_OPERATING_STATE: {
Attribute.MACHINE_STATE: [
SmartThingsSensorEntityDescription(
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/smartthings/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,9 @@
},
"water_consumption": {
"name": "Water consumption"
},
"water_filter_usage": {
"name": "Water filter usage"
}
},
"switch": {
Expand Down
Loading
Loading