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
12 changes: 3 additions & 9 deletions homeassistant/components/apcupsd/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .coordinator import APCUPSdConfigEntry, APCUPSdCoordinator
from .entity import APCUPSdEntity

PARALLEL_UPDATES = 0

Expand Down Expand Up @@ -40,22 +40,16 @@ async def async_setup_entry(
async_add_entities([OnlineStatus(coordinator, _DESCRIPTION)])


class OnlineStatus(CoordinatorEntity[APCUPSdCoordinator], BinarySensorEntity):
class OnlineStatus(APCUPSdEntity, BinarySensorEntity):
"""Representation of a UPS online status."""

_attr_has_entity_name = True

def __init__(
self,
coordinator: APCUPSdCoordinator,
description: BinarySensorEntityDescription,
) -> None:
"""Initialize the APCUPSd binary device."""
super().__init__(coordinator, context=description.key.upper())

self.entity_description = description
self._attr_unique_id = f"{coordinator.unique_device_id}_{description.key}"
self._attr_device_info = coordinator.device_info
super().__init__(coordinator, description)

@property
def is_on(self) -> bool | None:
Expand Down
26 changes: 26 additions & 0 deletions homeassistant/components/apcupsd/entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Base entity for APCUPSd integration."""

from __future__ import annotations

from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .coordinator import APCUPSdCoordinator


class APCUPSdEntity(CoordinatorEntity[APCUPSdCoordinator]):
"""Base entity for APCUPSd integration."""

_attr_has_entity_name = True

def __init__(
self,
coordinator: APCUPSdCoordinator,
description: EntityDescription,
) -> None:
"""Initialize the APCUPSd entity."""
super().__init__(coordinator, context=description.key.upper())

self.entity_description = description
self._attr_unique_id = f"{coordinator.unique_device_id}_{description.key}"
self._attr_device_info = coordinator.device_info
5 changes: 1 addition & 4 deletions homeassistant/components/apcupsd/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ rules:
action-setup: done
appropriate-polling: done
brands: done
common-modules:
status: done
comment: |
Consider deriving a base entity.
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
Expand Down
12 changes: 3 additions & 9 deletions homeassistant/components/apcupsd/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import LAST_S_TEST
from .coordinator import APCUPSdConfigEntry, APCUPSdCoordinator
from .entity import APCUPSdEntity

PARALLEL_UPDATES = 0

Expand Down Expand Up @@ -490,22 +490,16 @@ def infer_unit(value: str) -> tuple[str, str | None]:
return value, None


class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity):
class APCUPSdSensor(APCUPSdEntity, SensorEntity):
"""Representation of a sensor entity for APCUPSd status values."""

_attr_has_entity_name = True

def __init__(
self,
coordinator: APCUPSdCoordinator,
description: SensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator=coordinator, context=description.key.upper())

self.entity_description = description
self._attr_unique_id = f"{coordinator.unique_device_id}_{description.key}"
self._attr_device_info = coordinator.device_info
super().__init__(coordinator, description)

# Initial update of attributes.
self._update_attrs()
Expand Down
8 changes: 3 additions & 5 deletions homeassistant/components/freebox/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def async_setup_entry(

async_add_entities(
(
FreeboxAlarm(hass, router, node)
FreeboxAlarm(router, node)
for node in router.home_devices.values()
if node["category"] == FreeboxHomeCategory.ALARM
),
Expand All @@ -49,11 +49,9 @@ class FreeboxAlarm(FreeboxHomeEntity, AlarmControlPanelEntity):

_attr_code_arm_required = False

def __init__(
self, hass: HomeAssistant, router: FreeboxRouter, node: dict[str, Any]
) -> None:
def __init__(self, router: FreeboxRouter, node: dict[str, Any]) -> None:
"""Initialize an alarm."""
super().__init__(hass, router, node)
super().__init__(router, node)

# Commands
self._command_trigger = self.get_command_id(
Expand Down
15 changes: 6 additions & 9 deletions homeassistant/components/freebox/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ async def async_setup_entry(

for node in router.home_devices.values():
if node["category"] == FreeboxHomeCategory.PIR:
binary_entities.append(FreeboxPirSensor(hass, router, node))
binary_entities.append(FreeboxPirSensor(router, node))
elif node["category"] == FreeboxHomeCategory.DWS:
binary_entities.append(FreeboxDwsSensor(hass, router, node))
binary_entities.append(FreeboxDwsSensor(router, node))

binary_entities.extend(
FreeboxCoverSensor(hass, router, node)
FreeboxCoverSensor(router, node)
for endpoint in node["show_endpoints"]
if (
endpoint["name"] == "cover"
Expand All @@ -74,13 +74,12 @@ class FreeboxHomeBinarySensor(FreeboxHomeEntity, BinarySensorEntity):

def __init__(
self,
hass: HomeAssistant,
router: FreeboxRouter,
node: dict[str, Any],
sub_node: dict[str, Any] | None = None,
) -> None:
"""Initialize a Freebox binary sensor."""
super().__init__(hass, router, node, sub_node)
super().__init__(router, node, sub_node)
self._command_id = self.get_command_id(
node["type"]["endpoints"], "signal", self._sensor_name
)
Expand Down Expand Up @@ -123,9 +122,7 @@ class FreeboxCoverSensor(FreeboxHomeBinarySensor):

_sensor_name = "cover"

def __init__(
self, hass: HomeAssistant, router: FreeboxRouter, node: dict[str, Any]
) -> None:
def __init__(self, router: FreeboxRouter, node: dict[str, Any]) -> None:
"""Initialize a cover for another device."""
cover_node = next(
filter(
Expand All @@ -134,7 +131,7 @@ def __init__(
),
None,
)
super().__init__(hass, router, node, cover_node)
super().__init__(router, node, cover_node)


class FreeboxRaidDegradedSensor(BinarySensorEntity):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/freebox/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def __init__(
) -> None:
"""Initialize a camera."""

super().__init__(hass, router, node)
super().__init__(router, node)
device_info = {
CONF_NAME: node["label"].strip(),
CONF_INPUT: node["props"]["Stream"],
Expand Down
23 changes: 3 additions & 20 deletions homeassistant/components/freebox/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

from __future__ import annotations

from collections.abc import Callable
import logging
from typing import Any

from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
Expand All @@ -22,13 +20,11 @@ class FreeboxHomeEntity(Entity):

def __init__(
self,
hass: HomeAssistant,
router: FreeboxRouter,
node: dict[str, Any],
sub_node: dict[str, Any] | None = None,
) -> None:
"""Initialize a Freebox Home entity."""
self._hass = hass
self._router = router
self._node = node
self._sub_node = sub_node
Expand All @@ -44,7 +40,6 @@ def __init__(
self._available = True
self._firmware = node["props"].get("FwVersion")
self._manufacturer = "Freebox SAS"
self._remove_signal_update: Callable[[], None] | None = None

self._model = CATEGORY_TO_MODEL.get(node["category"])
if self._model is None:
Expand All @@ -61,10 +56,7 @@ def __init__(
model=self._model,
name=self._device_name,
sw_version=self._firmware,
via_device=(
DOMAIN,
router.mac,
),
via_device=(DOMAIN, router.mac),
)

async def async_update_signal(self) -> None:
Expand Down Expand Up @@ -116,23 +108,14 @@ def get_command_id(self, nodes, ep_type: str, name: str) -> int | None:

async def async_added_to_hass(self) -> None:
"""Register state update callback."""
self.remove_signal_update(
self.async_on_remove(
async_dispatcher_connect(
self._hass,
self.hass,
self._router.signal_home_device_update,
self.async_update_signal,
)
)

async def async_will_remove_from_hass(self) -> None:
"""When entity will be removed from hass."""
if self._remove_signal_update is not None:
self._remove_signal_update()

def remove_signal_update(self, dispatcher: Callable[[], None]) -> None:
"""Register state update callback."""
self._remove_signal_update = dispatcher

def get_value(self, ep_type: str, name: str):
"""Get the value."""
node = next(
Expand Down
21 changes: 11 additions & 10 deletions homeassistant/components/freebox/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,14 @@ async def async_setup_entry(
) -> None:
"""Set up the sensors."""
router = entry.runtime_data
entities: list[SensorEntity] = []

_LOGGER.debug(
"%s - %s - %s temperature sensors",
router.name,
router.mac,
len(router.sensors_temperature),
)
entities = [
entities: list[SensorEntity] = [
FreeboxSensor(
router,
SensorEntityDescription(
Expand Down Expand Up @@ -105,14 +104,16 @@ async def async_setup_entry(
for description in DISK_PARTITION_SENSORS
)

for node in router.home_devices.values():
for endpoint in node["show_endpoints"]:
if (
endpoint["name"] == "battery"
and endpoint["ep_type"] == "signal"
and endpoint.get("value") is not None
):
entities.append(FreeboxBatterySensor(hass, router, node, endpoint))
entities.extend(
FreeboxBatterySensor(router, node, endpoint)
for node in router.home_devices.values()
for endpoint in node["show_endpoints"]
if (
endpoint["name"] == "battery"
and endpoint["ep_type"] == "signal"
and endpoint.get("value") is not None
)
)

if entities:
async_add_entities(entities, True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower_ble",
"iot_class": "local_polling",
"requirements": ["automower-ble==0.2.7"]
"requirements": ["automower-ble==0.2.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/media_extractor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"iot_class": "calculated",
"loggers": ["yt_dlp"],
"quality_scale": "internal",
"requirements": ["yt-dlp[default]==2025.07.21"],
"requirements": ["yt-dlp[default]==2025.08.11"],
"single_config_entry": true
}
5 changes: 5 additions & 0 deletions homeassistant/components/modbus/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,11 @@ async def _async_update(self) -> None:
if hvac_mode == value:
self._attr_hvac_mode = mode
break
else:
# since there are no hvac_mode_register, this
# integration should not touch the attr.
# However it lacks in the climate component.
self._attr_hvac_mode = HVACMode.AUTO

# Read the HVAC action register if defined
if self._hvac_action_register is not None:
Expand Down
11 changes: 9 additions & 2 deletions homeassistant/components/nanoleaf/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable
import voluptuous as vol

from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import (
SOURCE_REAUTH,
SOURCE_USER,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.const import CONF_HOST, CONF_TOKEN
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.json import save_json
Expand Down Expand Up @@ -200,7 +205,9 @@ async def async_setup_finish(
return self.async_abort(reason="unknown")
name = self.nanoleaf.name

await self.async_set_unique_id(name)
await self.async_set_unique_id(
name, raise_on_progress=self.source != SOURCE_USER
)
self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})

if discovery_integration_import:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/spotify/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["spotifyaio"],
"requirements": ["spotifyaio==0.8.11"]
"requirements": ["spotifyaio==1.0.0"]
}
Loading
Loading