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
24 changes: 5 additions & 19 deletions homeassistant/components/analytics/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,6 @@ def _domains_from_yaml_config(yaml_configuration: dict[str, Any]) -> set[str]:

async def async_devices_payload(hass: HomeAssistant) -> dict:
"""Return the devices payload."""
integrations_without_model_id: set[str] = set()
devices: list[dict[str, Any]] = []
dev_reg = dr.async_get(hass)
# Devices that need via device info set
Expand All @@ -400,10 +399,6 @@ async def async_devices_payload(hass: HomeAssistant) -> dict:
seen_integrations = set()

for device in dev_reg.devices.values():
# Ignore services
if device.entry_type:
continue

if not device.primary_config_entry:
continue

Expand All @@ -414,13 +409,6 @@ async def async_devices_payload(hass: HomeAssistant) -> dict:

seen_integrations.add(config_entry.domain)

if not device.model_id:
integrations_without_model_id.add(config_entry.domain)
continue

if not device.manufacturer:
continue

new_indexes[device.id] = len(devices)
devices.append(
{
Expand All @@ -432,8 +420,10 @@ async def async_devices_payload(hass: HomeAssistant) -> dict:
"hw_version": device.hw_version,
"has_configuration_url": device.configuration_url is not None,
"via_device": None,
"entry_type": device.entry_type.value if device.entry_type else None,
}
)

if device.via_device_id:
via_devices[device.id] = device.via_device_id

Expand All @@ -453,15 +443,11 @@ async def async_devices_payload(hass: HomeAssistant) -> dict:
for device_info in devices:
if integration := integrations.get(device_info["integration"]):
device_info["is_custom_integration"] = not integration.is_built_in
# Include version for custom integrations
if not integration.is_built_in and integration.version:
device_info["custom_integration_version"] = str(integration.version)

return {
"version": "home-assistant:1",
"no_model_id": sorted(
[
domain
for domain in integrations_without_model_id
if domain in integrations and integrations[domain].is_built_in
]
),
"devices": devices,
}
5 changes: 1 addition & 4 deletions homeassistant/components/apcupsd/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ rules:
status: done
comment: |
Consider deriving a base entity.
config-flow-test-coverage:
status: done
comment: |
Consider looking into making a `mock_setup_entry` fixture that just automatically do this.
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/asuswrt/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/asuswrt",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aioasuswrt", "asyncssh"],
"loggers": ["aioasuswrt", "asusrouter", "asyncssh"],
"requirements": ["aioasuswrt==1.4.0", "asusrouter==1.19.0"]
}
6 changes: 2 additions & 4 deletions homeassistant/components/braviatv/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ async def async_setup_entry(
assert unique_id is not None

async_add_entities(
BraviaTVButton(coordinator, unique_id, config_entry.title, description)
for description in BUTTONS
BraviaTVButton(coordinator, unique_id, description) for description in BUTTONS
)


Expand All @@ -67,11 +66,10 @@ def __init__(
self,
coordinator: BraviaTVCoordinator,
unique_id: str,
model: str,
description: BraviaTVButtonDescription,
) -> None:
"""Initialize the button."""
super().__init__(coordinator, unique_id, model)
super().__init__(coordinator, unique_id)
self._attr_unique_id = f"{unique_id}_{description.key}"
self.entity_description = description

Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/braviatv/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,16 @@ async def async_create_device(self) -> ConfigFlowResult:

system_info = await self.client.get_system_info()
cid = system_info[ATTR_CID].lower()
title = system_info[ATTR_MODEL]

self.device_config[CONF_MAC] = system_info[ATTR_MAC]

await self.async_set_unique_id(cid)
self._abort_if_unique_id_configured()

return self.async_create_entry(title=title, data=self.device_config)
return self.async_create_entry(
title=f"{system_info['name']} {system_info[ATTR_MODEL]}",
data=self.device_config,
)

async def async_reauth_device(self) -> ConfigFlowResult:
"""Reauthorize Bravia TV device from config."""
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/braviatv/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(
self.use_psk = config_entry.data.get(CONF_USE_PSK, False)
self.client_id = config_entry.data.get(CONF_CLIENT_ID, LEGACY_CLIENT_ID)
self.nickname = config_entry.data.get(CONF_NICKNAME, NICKNAME_PREFIX)
self.system_info: dict[str, str] = {}
self.source: str | None = None
self.source_list: list[str] = []
self.source_map: dict[str, dict] = {}
Expand Down Expand Up @@ -150,6 +151,9 @@ async def _async_update_data(self) -> None:
self.is_on = power_status == "active"
self.skipped_updates = 0

if not self.system_info:
self.system_info = await self.client.get_system_info()

if self.is_on is False:
return

Expand Down
17 changes: 5 additions & 12 deletions homeassistant/components/braviatv/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,16 @@ class BraviaTVEntity(CoordinatorEntity[BraviaTVCoordinator]):

_attr_has_entity_name = True

def __init__(
self,
coordinator: BraviaTVCoordinator,
unique_id: str,
model: str,
) -> None:
def __init__(self, coordinator: BraviaTVCoordinator, unique_id: str) -> None:
"""Initialize the entity."""
super().__init__(coordinator)

self._attr_unique_id = unique_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
connections={(CONNECTION_NETWORK_MAC, coordinator.system_info["macAddr"])},
manufacturer=ATTR_MANUFACTURER,
model=model,
name=f"{ATTR_MANUFACTURER} {model}",
model_id=coordinator.system_info["model"],
hw_version=coordinator.system_info["generation"],
serial_number=coordinator.system_info["serial"],
)
if coordinator.client.mac is not None:
self._attr_device_info["connections"] = {
(CONNECTION_NETWORK_MAC, coordinator.client.mac)
}
4 changes: 1 addition & 3 deletions homeassistant/components/braviatv/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ async def async_setup_entry(
unique_id = config_entry.unique_id
assert unique_id is not None

async_add_entities(
[BraviaTVMediaPlayer(coordinator, unique_id, config_entry.title)]
)
async_add_entities([BraviaTVMediaPlayer(coordinator, unique_id)])


class BraviaTVMediaPlayer(BraviaTVEntity, MediaPlayerEntity):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/braviatv/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def async_setup_entry(
unique_id = config_entry.unique_id
assert unique_id is not None

async_add_entities([BraviaTVRemote(coordinator, unique_id, config_entry.title)])
async_add_entities([BraviaTVRemote(coordinator, unique_id)])


class BraviaTVRemote(BraviaTVEntity, RemoteEntity):
Expand Down
17 changes: 9 additions & 8 deletions homeassistant/components/modbus/modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,19 @@ async def async_pb_connect(self) -> None:
try:
await self._client.connect() # type: ignore[union-attr]
except ModbusException as exception_error:
err = f"{self.name} connect failed, retry in pymodbus ({exception_error!s})"
self._log_error(err)
self._log_error(
f"{self.name} connect failed, please check your configuration ({exception_error!s})"
)
return
message = f"modbus {self.name} communication open"
_LOGGER.info(message)

# Start counting down to allow modbus requests.
if self._config_delay:
self._async_cancel_listener = async_call_later(
self.hass, self._config_delay, self.async_end_delay
)

async def async_setup(self) -> bool:
"""Set up pymodbus client."""
try:
Expand All @@ -340,12 +347,6 @@ async def async_setup(self) -> bool:
self._connect_task = self.hass.async_create_background_task(
self.async_pb_connect(), "modbus-connect"
)

# Start counting down to allow modbus requests.
if self._config_delay:
self._async_cancel_listener = async_call_later(
self.hass, self._config_delay, self.async_end_delay
)
return True

@callback
Expand Down
8 changes: 0 additions & 8 deletions homeassistant/components/modbus/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,6 @@
}
},
"issues": {
"removed_lazy_error_count": {
"title": "{config_key} configuration key is being removed",
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue. All errors will be reported, as lazy_error_count is accepted but ignored"
},
"deprecated_retries": {
"title": "{config_key} configuration key is being removed",
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nThe maximum number of retries is now fixed to 3."
},
"missing_modbus_name": {
"title": "Modbus entry with host {sub_2} missing name",
"description": "Please add `{sub_1}` key to the {integration} entry with host `{sub_2}` in your configuration.yaml file and restart Home Assistant to fix this issue\n\n. `{sub_1}: {sub_3}` have been added."
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/sonos/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Platform.BINARY_SENSOR,
Platform.MEDIA_PLAYER,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]
Expand Down Expand Up @@ -154,6 +155,7 @@
SONOS_CREATE_BATTERY = "sonos_create_battery"
SONOS_CREATE_FAVORITES_SENSOR = "sonos_create_favorites_sensor"
SONOS_CREATE_MIC_SENSOR = "sonos_create_mic_sensor"
SONOS_CREATE_SELECTS = "sonos_create_selects"
SONOS_CREATE_SWITCHES = "sonos_create_switches"
SONOS_CREATE_LEVELS = "sonos_create_levels"
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
Expand Down Expand Up @@ -189,6 +191,9 @@
MODEL_SONOS_ARC_ULTRA = "SONOS ARC ULTRA"

ATTR_SPEECH_ENHANCEMENT_ENABLED = "speech_enhance_enabled"
SPEECH_DIALOG_LEVEL = "speech_dialog_level"
ATTR_DIALOG_LEVEL = "dialog_level"
ATTR_DIALOG_LEVEL_ENUM = "dialog_level_enum"

AVAILABILITY_CHECK_INTERVAL = datetime.timedelta(minutes=1)
AVAILABILITY_TIMEOUT = AVAILABILITY_CHECK_INTERVAL.total_seconds() * 4.5
Expand Down
129 changes: 129 additions & 0 deletions homeassistant/components/sonos/select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""Select entities for Sonos."""

from __future__ import annotations

from dataclasses import dataclass
import logging

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import (
ATTR_DIALOG_LEVEL,
ATTR_DIALOG_LEVEL_ENUM,
MODEL_SONOS_ARC_ULTRA,
SONOS_CREATE_SELECTS,
SPEECH_DIALOG_LEVEL,
)
from .entity import SonosEntity
from .helpers import SonosConfigEntry, soco_error
from .speaker import SonosSpeaker


@dataclass(frozen=True, kw_only=True)
class SonosSelectEntityDescription(SelectEntityDescription):
"""Describes AirGradient select entity."""

soco_attribute: str
speaker_attribute: str
speaker_model: str


SELECT_TYPES: list[SonosSelectEntityDescription] = [
SonosSelectEntityDescription(
key=SPEECH_DIALOG_LEVEL,
translation_key=SPEECH_DIALOG_LEVEL,
soco_attribute=ATTR_DIALOG_LEVEL,
speaker_attribute=ATTR_DIALOG_LEVEL_ENUM,
speaker_model=MODEL_SONOS_ARC_ULTRA,
options=["off", "low", "medium", "high", "max"],
),
]

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: SonosConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Sonos select platform from a config entry."""

def available_soco_attributes(
speaker: SonosSpeaker,
) -> list[SonosSelectEntityDescription]:
features: list[SonosSelectEntityDescription] = []
for select_data in SELECT_TYPES:
if select_data.speaker_model == speaker.model_name.upper():
if (
state := getattr(speaker.soco, select_data.soco_attribute, None)
) is not None:
setattr(speaker, select_data.speaker_attribute, state)
features.append(select_data)
return features

async def _async_create_entities(speaker: SonosSpeaker) -> None:
available_features = await hass.async_add_executor_job(
available_soco_attributes, speaker
)
async_add_entities(
SonosSelectEntity(speaker, config_entry, select_data)
for select_data in available_features
)

config_entry.async_on_unload(
async_dispatcher_connect(hass, SONOS_CREATE_SELECTS, _async_create_entities)
)


class SonosSelectEntity(SonosEntity, SelectEntity):
"""Representation of a Sonos select entity."""

def __init__(
self,
speaker: SonosSpeaker,
config_entry: SonosConfigEntry,
select_data: SonosSelectEntityDescription,
) -> None:
"""Initialize the select entity."""
super().__init__(speaker, config_entry)
self._attr_unique_id = f"{self.soco.uid}-{select_data.key}"
self._attr_translation_key = select_data.translation_key
assert select_data.options is not None
self._attr_options = select_data.options
self.speaker_attribute = select_data.speaker_attribute
self.soco_attribute = select_data.soco_attribute

async def _async_fallback_poll(self) -> None:
"""Poll the value if subscriptions are not working."""
await self.hass.async_add_executor_job(self.poll_state)
self.async_write_ha_state()

@soco_error()
def poll_state(self) -> None:
"""Poll the device for the current state."""
state = getattr(self.soco, self.soco_attribute)
setattr(self.speaker, self.speaker_attribute, state)

@property
def current_option(self) -> str | None:
"""Return the current option for the entity."""
option = getattr(self.speaker, self.speaker_attribute, None)
if not isinstance(option, int) or not (0 <= option < len(self._attr_options)):
_LOGGER.error(
"Invalid option %s for %s on %s",
option,
self.soco_attribute,
self.speaker.zone_name,
)
return None
return self._attr_options[option]

@soco_error()
def select_option(self, option: str) -> None:
"""Set a new value."""
dialog_level = self._attr_options.index(option)
setattr(self.soco, self.soco_attribute, dialog_level)
Loading
Loading