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
4 changes: 2 additions & 2 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 67 additions & 8 deletions homeassistant/components/alexa_devices/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from aioamazondevices.api import AmazonEchoApi
Expand All @@ -10,11 +11,36 @@

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_CODE, CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.selector import CountrySelector

from .const import CONF_LOGIN_DATA, DOMAIN

STEP_REAUTH_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_CODE): cv.string,
}
)


async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect."""

api = AmazonEchoApi(
data[CONF_COUNTRY],
data[CONF_USERNAME],
data[CONF_PASSWORD],
)

try:
data = await api.login_mode_interactive(data[CONF_CODE])
finally:
await api.close()

return data


class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Alexa Devices."""
Expand All @@ -25,13 +51,8 @@ async def async_step_user(
"""Handle the initial step."""
errors = {}
if user_input:
client = AmazonEchoApi(
user_input[CONF_COUNTRY],
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
)
try:
data = await client.login_mode_interactive(user_input[CONF_CODE])
data = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except CannotAuthenticate:
Expand All @@ -44,8 +65,6 @@ async def async_step_user(
title=user_input[CONF_USERNAME],
data=user_input | {CONF_LOGIN_DATA: data},
)
finally:
await client.close()

return self.async_show_form(
step_id="user",
Expand All @@ -61,3 +80,43 @@ async def async_step_user(
}
),
)

async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reauth flow."""
self.context["title_placeholders"] = {CONF_USERNAME: entry_data[CONF_USERNAME]}
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauth confirm."""
errors: dict[str, str] = {}

reauth_entry = self._get_reauth_entry()
entry_data = reauth_entry.data

if user_input is not None:
try:
await validate_input(self.hass, {**reauth_entry.data, **user_input})
except CannotConnect:
errors["base"] = "cannot_connect"
except CannotAuthenticate:
errors["base"] = "invalid_auth"
else:
return self.async_update_reload_and_abort(
reauth_entry,
data={
CONF_USERNAME: entry_data[CONF_USERNAME],
CONF_PASSWORD: entry_data[CONF_PASSWORD],
CONF_CODE: user_input[CONF_CODE],
},
)

return self.async_show_form(
step_id="reauth_confirm",
description_placeholders={CONF_USERNAME: entry_data[CONF_USERNAME]},
data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors,
)
10 changes: 7 additions & 3 deletions homeassistant/components/alexa_devices/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import _LOGGER, CONF_LOGIN_DATA
from .const import _LOGGER, CONF_LOGIN_DATA, DOMAIN

SCAN_INTERVAL = 30

Expand Down Expand Up @@ -55,4 +55,8 @@ async def _async_update_data(self) -> dict[str, AmazonDevice]:
except (CannotConnect, CannotRetrieveData) as err:
raise UpdateFailed(f"Error occurred while updating {self.name}") from err
except CannotAuthenticate as err:
raise ConfigEntryError("Could not authenticate") from err
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
translation_placeholders={"error": repr(err)},
) from err
2 changes: 1 addition & 1 deletion homeassistant/components/alexa_devices/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ rules:
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: todo
reauthentication-flow: done
test-coverage:
status: todo
comment: all tests missing
Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/alexa_devices/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,23 @@
"password": "[%key:component::alexa_devices::common::data_description_password%]",
"code": "[%key:component::alexa_devices::common::data_description_code%]"
}
},
"reauth_confirm": {
"data": {
"password": "[%key:common::config_flow::data::password%]",
"code": "[%key:component::alexa_devices::common::data_code%]"
},
"data_description": {
"password": "[%key:component::alexa_devices::common::data_description_password%]",
"code": "[%key:component::alexa_devices::common::data_description_code%]"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/esphome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "esphome",
"name": "ESPHome",
"after_dependencies": ["hassio", "zeroconf", "tag"],
"codeowners": ["@OttoWinter", "@jesserockz", "@kbx81", "@bdraco"],
"codeowners": ["@jesserockz", "@kbx81", "@bdraco"],
"config_flow": true,
"dependencies": ["assist_pipeline", "bluetooth", "intent", "ffmpeg", "http"],
"dhcp": [
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/iron_os/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@
"state": {
"off": "mdi:card-bulleted-off-outline"
}
},
"boost": {
"default": "mdi:thermometer-high",
"state": {
"off": "mdi:thermometer-off"
}
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions homeassistant/components/iron_os/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,16 @@ def native_max_value(self) -> float:
else super().native_max_value
)

@property
def available(self) -> bool:
"""Return True if entity is available."""
if (
self.entity_description.key is PinecilNumber.BOOST_TEMP
and self.native_value == 0
):
return False
return super().available


class IronOSSetpointNumberEntity(IronOSTemperatureNumberEntity):
"""IronOS setpoint temperature entity."""
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/iron_os/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@
},
"calibrate_cjc": {
"name": "Calibrate CJC"
},
"boost": {
"name": "Boost"
}
}
},
Expand Down
21 changes: 19 additions & 2 deletions homeassistant/components/iron_os/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
from enum import StrEnum
from typing import Any

from pynecil import CharSetting, SettingsDataResponse
from pynecil import CharSetting, SettingsDataResponse, TempUnit

from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import IronOSConfigEntry
from .const import MIN_BOOST_TEMP, MIN_BOOST_TEMP_F
from .coordinator import IronOSCoordinators
from .entity import IronOSBaseEntity

Expand All @@ -39,6 +40,7 @@ class IronOSSwitch(StrEnum):
INVERT_BUTTONS = "invert_buttons"
DISPLAY_INVERT = "display_invert"
CALIBRATE_CJC = "calibrate_cjc"
BOOST = "boost"


SWITCH_DESCRIPTIONS: tuple[IronOSSwitchEntityDescription, ...] = (
Expand Down Expand Up @@ -94,6 +96,13 @@ class IronOSSwitch(StrEnum):
entity_registry_enabled_default=False,
entity_category=EntityCategory.CONFIG,
),
IronOSSwitchEntityDescription(
key=IronOSSwitch.BOOST,
translation_key=IronOSSwitch.BOOST,
characteristic=CharSetting.BOOST_TEMP,
is_on_fn=lambda x: bool(x.get("boost_temp")),
entity_category=EntityCategory.CONFIG,
),
)


Expand Down Expand Up @@ -136,7 +145,15 @@ def is_on(self) -> bool | None:

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
await self.settings.write(self.entity_description.characteristic, True)
if self.entity_description.key is IronOSSwitch.BOOST:
await self.settings.write(
self.entity_description.characteristic,
MIN_BOOST_TEMP_F
if self.settings.data.get("temp_unit") is TempUnit.FAHRENHEIT
else MIN_BOOST_TEMP,
)
else:
await self.settings.write(self.entity_description.characteristic, True)

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity on."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/music_assistant/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, mass: MusicAssistantClient, player_id: str) -> None:
identifiers={(DOMAIN, player_id)},
manufacturer=self.player.device_info.manufacturer or provider.name,
model=self.player.device_info.model or self.player.name,
name=self.player.display_name,
name=self.player.name,
configuration_url=f"{mass.server_url}/#/settings/editplayer/{player_id}",
)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/music_assistant/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/music_assistant",
"iot_class": "local_push",
"loggers": ["music_assistant"],
"requirements": ["music-assistant-client==1.2.0"],
"requirements": ["music-assistant-client==1.2.3"],
"zeroconf": ["_mass._tcp.local."]
}
8 changes: 1 addition & 7 deletions homeassistant/components/music_assistant/media_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
from typing import TYPE_CHECKING, Any, cast

from music_assistant_models.enums import MediaType as MASSMediaType
from music_assistant_models.media_items import (
BrowseFolder,
MediaItemType,
SearchResults,
)
from music_assistant_models.media_items import MediaItemType, SearchResults

from homeassistant.components import media_source
from homeassistant.components.media_player import (
Expand Down Expand Up @@ -549,8 +545,6 @@ def _process_search_results(

# Add available items to results
for item in items:
if TYPE_CHECKING:
assert not isinstance(item, BrowseFolder)
if not item.available:
continue

Expand Down
12 changes: 6 additions & 6 deletions homeassistant/components/music_assistant/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ async def async_on_update(self) -> None:
# update generic attributes
if player.powered and active_queue is not None:
self._attr_state = MediaPlayerState(active_queue.state.value)
if player.powered and player.state is not None:
self._attr_state = MediaPlayerState(player.state.value)
if player.powered and player.playback_state is not None:
self._attr_state = MediaPlayerState(player.playback_state.value)
else:
self._attr_state = MediaPlayerState(STATE_OFF)
# active source and source list (translate to HA source names)
Expand All @@ -270,12 +270,12 @@ async def async_on_update(self) -> None:
self._attr_source = active_source_name

group_members: list[str] = []
if player.group_childs:
group_members = player.group_childs
if player.group_members:
group_members = player.group_members
elif player.synced_to and (parent := self.mass.players.get(player.synced_to)):
group_members = parent.group_childs
group_members = parent.group_members

# translate MA group_childs to HA group_members as entity id's
# translate MA group_members to HA group_members as entity id's
entity_registry = er.async_get(self.hass)
group_members_entity_ids: list[str] = [
entity_id
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/switchbot/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
"water_level": {
"name": "Water level",
"state": {
"empty": "Empty",
"empty": "[%key:common::state::empty%]",
"low": "[%key:common::state::low%]",
"medium": "[%key:common::state::medium%]",
"high": "[%key:common::state::high%]"
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/template/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ def is_open(self) -> bool:
"""Return true if lock is open."""
return self._state == LockState.OPEN

@property
def is_opening(self) -> bool:
"""Return true if lock is opening."""
return self._state == LockState.OPENING

@property
def code_format(self) -> str | None:
"""Regex for code format or None if no code is required."""
Expand Down
Loading
Loading