Merge pull request #66624 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2022-02-15 19:59:26 -08:00 committed by GitHub
commit e33671db60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 241 additions and 179 deletions

View File

@ -7,8 +7,8 @@
"@Bre77"
],
"requirements": [
"advantage_air==0.2.5"
"advantage_air==0.3.0"
],
"quality_scale": "platinum",
"iot_class": "local_polling"
}
}

View File

@ -4,7 +4,7 @@
"config_flow": true,
"dependencies": ["network"],
"documentation": "https://www.home-assistant.io/integrations/flux_led",
"requirements": ["flux_led==0.28.22"],
"requirements": ["flux_led==0.28.26"],
"quality_scale": "platinum",
"codeowners": ["@icemanch", "@bdraco"],
"iot_class": "local_push",

View File

@ -12,10 +12,9 @@ from homeassistant.components.button import (
ButtonEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .common import AvmWrapper
@ -41,28 +40,28 @@ BUTTONS: Final = [
key="firmware_update",
name="Firmware Update",
device_class=ButtonDeviceClass.UPDATE,
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
press_action=lambda avm_wrapper: avm_wrapper.async_trigger_firmware_update(),
),
FritzButtonDescription(
key="reboot",
name="Reboot",
device_class=ButtonDeviceClass.RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
press_action=lambda avm_wrapper: avm_wrapper.async_trigger_reboot(),
),
FritzButtonDescription(
key="reconnect",
name="Reconnect",
device_class=ButtonDeviceClass.RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
press_action=lambda avm_wrapper: avm_wrapper.async_trigger_reconnect(),
),
FritzButtonDescription(
key="cleanup",
name="Cleanup",
icon="mdi:broom",
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
press_action=lambda avm_wrapper: avm_wrapper.async_trigger_cleanup(),
),
]

View File

@ -327,11 +327,19 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator):
_LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host)
self._update_available, self._latest_firmware = self._update_device_info()
try:
topology = self.fritz_hosts.get_mesh_topology()
except FritzActionError:
self.mesh_role = MeshRoles.SLAVE
return
if (
"Hosts1" not in self.connection.services
or "X_AVM-DE_GetMeshListPath"
not in self.connection.services["Hosts1"].actions
):
self.mesh_role = MeshRoles.NONE
else:
try:
topology = self.fritz_hosts.get_mesh_topology()
except FritzActionError:
self.mesh_role = MeshRoles.SLAVE
# Avoid duplicating device trackers
return
_LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host)
_default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds()

View File

@ -13,8 +13,8 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import FritzBoxEntity
@ -49,7 +49,7 @@ BINARY_SENSOR_TYPES: Final[tuple[FritzBinarySensorEntityDescription, ...]] = (
key="lock",
name="Button Lock on Device",
device_class=BinarySensorDeviceClass.LOCK,
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
suitable=lambda device: device.lock is not None,
is_on=lambda device: not device.lock,
),
@ -57,7 +57,7 @@ BINARY_SENSOR_TYPES: Final[tuple[FritzBinarySensorEntityDescription, ...]] = (
key="device_lock",
name="Button Lock via UI",
device_class=BinarySensorDeviceClass.LOCK,
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
suitable=lambda device: device.device_lock is not None,
is_on=lambda device: not device.device_lock,
),

View File

@ -9,9 +9,9 @@ from goodwe import Inverter, InverterError
from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG, PERCENTAGE, POWER_WATT
from homeassistant.const import PERCENTAGE, POWER_WATT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, KEY_DEVICE_INFO, KEY_INVERTER
@ -39,7 +39,7 @@ NUMBERS = (
key="grid_export_limit",
name="Grid export limit",
icon="mdi:transmission-tower",
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
unit_of_measurement=POWER_WATT,
getter=lambda inv: inv.get_grid_export_limit(),
setter=lambda inv, val: inv.set_grid_export_limit(val),
@ -51,7 +51,7 @@ NUMBERS = (
key="battery_discharge_depth",
name="Depth of discharge (on-grid)",
icon="mdi:battery-arrow-down",
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
unit_of_measurement=PERCENTAGE,
getter=lambda inv: inv.get_ongrid_battery_dod(),
setter=lambda inv, val: inv.set_ongrid_battery_dod(val),

View File

@ -5,9 +5,8 @@ from goodwe import Inverter, InverterError
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, KEY_DEVICE_INFO, KEY_INVERTER
@ -26,7 +25,7 @@ OPERATION_MODE = SelectEntityDescription(
key="operation_mode",
name="Inverter operation mode",
icon="mdi:solar-power",
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
)

View File

@ -26,7 +26,7 @@ from pycec.const import (
from homeassistant.components.media_player import MediaPlayerEntity
from homeassistant.components.media_player.const import (
DOMAIN,
DOMAIN as MP_DOMAIN,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
SUPPORT_PLAY_MEDIA,
@ -48,11 +48,11 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import ATTR_NEW, CecEntity
from . import ATTR_NEW, DOMAIN, CecEntity
_LOGGER = logging.getLogger(__name__)
ENTITY_ID_FORMAT = DOMAIN + ".{}"
ENTITY_ID_FORMAT = MP_DOMAIN + ".{}"
def setup_platform(
@ -77,7 +77,7 @@ class CecPlayerEntity(CecEntity, MediaPlayerEntity):
def __init__(self, device, logical) -> None:
"""Initialize the HDMI device."""
CecEntity.__init__(self, device, logical)
self.entity_id = f"{DOMAIN}.hdmi_{hex(self._logical_address)[2:]}"
self.entity_id = f"{MP_DOMAIN}.hdmi_{hex(self._logical_address)[2:]}"
def send_keypress(self, key):
"""Send keypress to CEC adapter."""

View File

@ -3,17 +3,17 @@ from __future__ import annotations
import logging
from homeassistant.components.switch import DOMAIN, SwitchEntity
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import ATTR_NEW, CecEntity
from . import ATTR_NEW, DOMAIN, CecEntity
_LOGGER = logging.getLogger(__name__)
ENTITY_ID_FORMAT = DOMAIN + ".{}"
ENTITY_ID_FORMAT = SWITCH_DOMAIN + ".{}"
def setup_platform(
@ -38,7 +38,7 @@ class CecSwitchEntity(CecEntity, SwitchEntity):
def __init__(self, device, logical) -> None:
"""Initialize the HDMI device."""
CecEntity.__init__(self, device, logical)
self.entity_id = f"{DOMAIN}.hdmi_{hex(self._logical_address)[2:]}"
self.entity_id = f"{SWITCH_DOMAIN}.hdmi_{hex(self._logical_address)[2:]}"
def turn_on(self, **kwargs) -> None:
"""Turn device on."""

View File

@ -16,13 +16,12 @@ from homeassistant.const import (
DEVICE_CLASS_GAS,
DEVICE_CLASS_POWER,
ENERGY_KILO_WATT_HOUR,
ENTITY_CATEGORY_DIAGNOSTIC,
PERCENTAGE,
POWER_WATT,
VOLUME_CUBIC_METERS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -37,19 +36,19 @@ SENSORS: Final[tuple[SensorEntityDescription, ...]] = (
key="smr_version",
name="DSMR Version",
icon="mdi:counter",
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key="meter_model",
name="Smart Meter Model",
icon="mdi:gauge",
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key="wifi_ssid",
name="Wifi SSID",
icon="mdi:wifi",
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key="wifi_strength",
@ -57,7 +56,7 @@ SENSORS: Final[tuple[SensorEntityDescription, ...]] = (
icon="mdi:wifi",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(

View File

@ -3,7 +3,7 @@
"name": "Philips Hue",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hue",
"requirements": ["aiohue==4.0.1"],
"requirements": ["aiohue==4.1.2"],
"ssdp": [
{
"manufacturer": "Royal Philips Electronics",

View File

@ -5,7 +5,11 @@ from typing import Any
from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.scenes import Scene as HueScene, ScenesController
from aiohue.v2.controllers.scenes import (
Scene as HueScene,
ScenePut as HueScenePut,
ScenesController,
)
import voluptuous as vol
from homeassistant.components.scene import ATTR_TRANSITION, Scene as SceneEntity
@ -131,7 +135,7 @@ class HueSceneEntity(HueBaseEntity, SceneEntity):
await self.bridge.async_request_call(
self.controller.update,
self.resource.id,
HueScene(self.resource.id, speed=speed / 100),
HueScenePut(speed=speed / 100),
)
await self.bridge.async_request_call(

View File

@ -5,8 +5,12 @@ from typing import Any, Union
from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.sensors import LightLevelController, MotionController
from aiohue.v2.models.resource import SensingService
from aiohue.v2.controllers.sensors import (
LightLevel,
LightLevelController,
Motion,
MotionController,
)
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.config_entries import ConfigEntry
@ -20,6 +24,8 @@ from .v2.entity import HueBaseEntity
ControllerType = Union[LightLevelController, MotionController]
SensingService = Union[LightLevel, Motion]
async def async_setup_entry(
hass: HomeAssistant,

View File

@ -4,13 +4,13 @@ from __future__ import annotations
from typing import Any, Union
from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.config import EntertainmentConfigurationController
from aiohue.v2.controllers.config import (
EntertainmentConfiguration,
EntertainmentConfigurationController,
)
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.sensors import MotionController
from aiohue.v2.models.entertainment import (
EntertainmentConfiguration,
EntertainmentStatus,
)
from aiohue.v2.models.entertainment_configuration import EntertainmentStatus
from aiohue.v2.models.motion import Motion
from homeassistant.components.binary_sensor import (
@ -109,4 +109,4 @@ class HueEntertainmentActiveSensor(HueBinarySensorBase):
def name(self) -> str:
"""Return sensor name."""
type_title = self.resource.type.value.replace("_", " ").title()
return f"{self.resource.name}: {type_title}"
return f"{self.resource.metadata.name}: {type_title}"

View File

@ -1,11 +1,12 @@
"""Generic Hue Entity Model."""
from __future__ import annotations
from typing import TYPE_CHECKING, Union
from aiohue.v2.controllers.base import BaseResourcesController
from aiohue.v2.controllers.events import EventType
from aiohue.v2.models.clip import CLIPResource
from aiohue.v2.models.connectivity import ConnectivityServiceStatus
from aiohue.v2.models.resource import ResourceTypes
from aiohue.v2.models.zigbee_connectivity import ConnectivityServiceStatus
from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo, Entity
@ -14,6 +15,16 @@ from homeassistant.helpers.entity_registry import async_get as async_get_entity_
from ..bridge import HueBridge
from ..const import CONF_IGNORE_AVAILABILITY, DOMAIN
if TYPE_CHECKING:
from aiohue.v2.models.device_power import DevicePower
from aiohue.v2.models.grouped_light import GroupedLight
from aiohue.v2.models.light import Light
from aiohue.v2.models.light_level import LightLevel
from aiohue.v2.models.motion import Motion
HueResource = Union[Light, DevicePower, GroupedLight, LightLevel, Motion]
RESOURCE_TYPE_NAMES = {
# a simple mapping of hue resource type to Hass name
ResourceTypes.LIGHT_LEVEL: "Illuminance",
@ -30,7 +41,7 @@ class HueBaseEntity(Entity):
self,
bridge: HueBridge,
controller: BaseResourcesController,
resource: CLIPResource,
resource: HueResource,
) -> None:
"""Initialize a generic Hue resource entity."""
self.bridge = bridge
@ -122,7 +133,7 @@ class HueBaseEntity(Entity):
# used in subclasses
@callback
def _handle_event(self, event_type: EventType, resource: CLIPResource) -> None:
def _handle_event(self, event_type: EventType, resource: HueResource) -> None:
"""Handle status event for this resource (or it's parent)."""
if event_type == EventType.RESOURCE_DELETED and resource.id == self.resource.id:
self.logger.debug("Received delete for %s", self.entity_id)

View File

@ -7,7 +7,7 @@ from typing import Any
from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.groups import GroupedLight, Room, Zone
from aiohue.v2.models.feature import DynamicsFeatureStatus
from aiohue.v2.models.feature import DynamicStatus
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -283,7 +283,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
total_brightness += dimming.brightness
if (
light.dynamics
and light.dynamics.status == DynamicsFeatureStatus.DYNAMIC_PALETTE
and light.dynamics.status == DynamicStatus.DYNAMIC_PALETTE
):
lights_in_dynamic_mode += 1

View File

@ -12,10 +12,10 @@ from aiohue.v2.controllers.sensors import (
TemperatureController,
ZigbeeConnectivityController,
)
from aiohue.v2.models.connectivity import ZigbeeConnectivity
from aiohue.v2.models.device_power import DevicePower
from aiohue.v2.models.light_level import LightLevel
from aiohue.v2.models.temperature import Temperature
from aiohue.v2.models.zigbee_connectivity import ZigbeeConnectivity
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.sensor import (

View File

@ -8,8 +8,8 @@ write_coil:
required: true
selector:
number:
min: 1
max: 255
min: 0
max: 65535
state:
name: State
description: State to write.
@ -42,8 +42,8 @@ write_register:
required: true
selector:
number:
min: 1
max: 255
min: 0
max: 65535
unit:
name: Unit
description: Address of the modbus unit.

View File

@ -127,6 +127,15 @@ class NestCamera(Camera):
return STREAM_TYPE_WEB_RTC
return super().frontend_stream_type
@property
def available(self) -> bool:
"""Return True if entity is available."""
# Cameras are marked unavailable on stream errors in #54659 however nest streams have
# a high error rate (#60353). Given nest streams are so flaky, marking the stream
# unavailable has other side effects like not showing the camera image which sometimes
# are still able to work. Until the streams are fixed, just leave the streams as available.
return True
async def stream_source(self) -> str | None:
"""Return the source of the stream."""
if not self.supported_features & SUPPORT_STREAM:

View File

@ -2,8 +2,8 @@
from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .base import ONVIFBaseEntity
@ -25,7 +25,7 @@ class RebootButton(ONVIFBaseEntity, ButtonEntity):
"""Defines a ONVIF reboot button."""
_attr_device_class = ButtonDeviceClass.RESTART
_attr_entity_category = ENTITY_CATEGORY_CONFIG
_attr_entity_category = EntityCategory.CONFIG
def __init__(self, device: ONVIFDevice) -> None:
"""Initialize the button entity."""

View File

@ -148,9 +148,12 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]):
@property
def unique_id(self) -> str:
"""Return the system descriptor."""
assert self.config_entry
assert self.config_entry.unique_id
return self.config_entry.unique_id
entry: ConfigEntry = self.config_entry
assert entry
if entry.unique_id:
return entry.unique_id
assert entry.entry_id
return entry.entry_id
@property
def _notify_wanted(self):

View File

@ -122,9 +122,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(hub.system["serialnumber"])
self._abort_if_unique_id_configured()
if serialnumber := hub.system.get("serialnumber"):
await self.async_set_unique_id(serialnumber)
self._abort_if_unique_id_configured()
self._current[CONF_SYSTEM] = hub.system
self._current[CONF_API_VERSION] = hub.api_version

View File

@ -2,7 +2,6 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
import voluptuous as vol
@ -109,20 +108,12 @@ class I2CHatBinarySensor(BinarySensorEntity):
self._device_class = device_class
self._state = self.I2C_HATS_MANAGER.read_di(self._address, self._channel)
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
if TYPE_CHECKING:
assert self.I2C_HATS_MANAGER
def online_callback():
"""Call fired when board is online."""
self.schedule_update_ha_state()
await self.hass.async_add_executor_job(
self.I2C_HATS_MANAGER.register_online_callback,
self._address,
self._channel,
online_callback,
self.I2C_HATS_MANAGER.register_online_callback(
self._address, self._channel, online_callback
)
def edge_callback(state):
@ -130,11 +121,8 @@ class I2CHatBinarySensor(BinarySensorEntity):
self._state = state
self.schedule_update_ha_state()
await self.hass.async_add_executor_job(
self.I2C_HATS_MANAGER.register_di_callback,
self._address,
self._channel,
edge_callback,
self.I2C_HATS_MANAGER.register_di_callback(
self._address, self._channel, edge_callback
)
@property

View File

@ -2,7 +2,6 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
import voluptuous as vol
@ -101,7 +100,6 @@ class I2CHatSwitch(SwitchEntity):
self._channel = channel
self._name = name or DEVICE_DEFAULT_NAME
self._invert_logic = invert_logic
self._state = initial_state
if initial_state is not None:
if self._invert_logic:
state = not initial_state
@ -109,27 +107,14 @@ class I2CHatSwitch(SwitchEntity):
state = initial_state
self.I2C_HATS_MANAGER.write_dq(self._address, self._channel, state)
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
if TYPE_CHECKING:
assert self.I2C_HATS_MANAGER
def online_callback():
"""Call fired when board is online."""
self.schedule_update_ha_state()
await self.hass.async_add_executor_job(
self.I2C_HATS_MANAGER.register_online_callback,
self._address,
self._channel,
self.online_callback,
self.I2C_HATS_MANAGER.register_online_callback(
self._address, self._channel, online_callback
)
def online_callback(self):
"""Call fired when board is online."""
try:
self._state = self.I2C_HATS_MANAGER.read_dq(self._address, self._channel)
except I2CHatsException as ex:
_LOGGER.error(self._log_message(f"Is ON check failed, {ex!s}"))
self._state = False
self.schedule_update_ha_state()
def _log_message(self, message):
"""Create log message."""
string = f"{self._name} "
@ -150,7 +135,12 @@ class I2CHatSwitch(SwitchEntity):
@property
def is_on(self):
"""Return true if device is on."""
return self._state != self._invert_logic
try:
state = self.I2C_HATS_MANAGER.read_dq(self._address, self._channel)
return state != self._invert_logic
except I2CHatsException as ex:
_LOGGER.error(self._log_message(f"Is ON check failed, {ex!s}"))
return False
def turn_on(self, **kwargs):
"""Turn the device on."""

View File

@ -23,7 +23,7 @@ UID_POSTFIX = "01400"
_LOGGER = logging.getLogger(__name__)
_T = TypeVar("_T", "SonosSpeaker", "SonosEntity")
_T = TypeVar("_T", bound="SonosSpeaker | SonosEntity")
_R = TypeVar("_R")
_P = ParamSpec("_P")

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import datetime
import logging
from typing import Any
from soco.exceptions import SoCoException, SoCoSlaveException, SoCoUPnPException
@ -342,20 +343,20 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity):
ATTR_INCLUDE_LINKED_ZONES: self.alarm.include_linked_zones,
}
async def async_turn_on(self, **kwargs) -> None:
def turn_on(self, **kwargs: Any) -> None:
"""Turn alarm switch on."""
await self.async_handle_switch_on_off(turn_on=True)
self._handle_switch_on_off(turn_on=True)
async def async_turn_off(self, **kwargs) -> None:
def turn_off(self, **kwargs: Any) -> None:
"""Turn alarm switch off."""
await self.async_handle_switch_on_off(turn_on=False)
self._handle_switch_on_off(turn_on=False)
async def async_handle_switch_on_off(self, turn_on: bool) -> None:
def _handle_switch_on_off(self, turn_on: bool) -> None:
"""Handle turn on/off of alarm switch."""
try:
_LOGGER.debug("Toggling the state of %s", self.entity_id)
self.alarm.enabled = turn_on
await self.hass.async_add_executor_job(self.alarm.save)
self.alarm.save()
except (OSError, SoCoException, SoCoUPnPException) as exc:
_LOGGER.error("Could not update %s: %s", self.entity_id, exc)

View File

@ -121,6 +121,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady from err
async def _update_devices() -> list[dict[str, Any]]:
if not session.valid_token:
await session.async_ensure_token_valid()
await hass.async_add_executor_job(
spotify.set_auth, session.token["access_token"]
)
try:
devices: dict[str, Any] | None = await hass.async_add_executor_job(
spotify.devices

View File

@ -515,9 +515,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
run_coroutine_threadsafe(
self._session.async_ensure_token_valid(), self.hass.loop
).result()
self._spotify_data[DATA_SPOTIFY_CLIENT] = Spotify(
auth=self._session.token["access_token"]
)
self._spotify.set_auth(auth=self._session.token["access_token"])
current = self._spotify.current_playback()
self._currently_playing = current or {}
@ -581,7 +579,11 @@ async def async_browse_media_internal(
partial(library_payload, can_play_artist=can_play_artist)
)
await session.async_ensure_token_valid()
if not session.valid_token:
await session.async_ensure_token_valid()
await hass.async_add_executor_job(
spotify.set_auth, session.token["access_token"]
)
# Strip prefix
media_content_type = media_content_type[len(MEDIA_PLAYER_PREFIX) :]

View File

@ -342,7 +342,9 @@ class Stream:
stream_state.discontinuity()
if not self.keepalive or self._thread_quit.is_set():
if self._fast_restart_once:
# The stream source is updated, restart without any delay.
# The stream source is updated, restart without any delay and reset the retry
# backoff for the new url.
wait_timeout = 0
self._fast_restart_once = False
self._thread_quit.clear()
continue

View File

@ -8,7 +8,7 @@ DATA_BRIDGE = "bridge"
DATA_DEVICE = "device"
DATA_DISCOVERY = "discovery"
DISCOVERY_TIME_SEC = 6
DISCOVERY_TIME_SEC = 12
SIGNAL_DEVICE_ADD = "switcher_device_add"
@ -19,4 +19,4 @@ SERVICE_SET_AUTO_OFF_NAME = "set_auto_off"
SERVICE_TURN_ON_WITH_TIMER_NAME = "turn_on_with_timer"
# Defines the maximum interval device must send an update before it marked unavailable
MAX_UPDATE_INTERVAL_SEC = 20
MAX_UPDATE_INTERVAL_SEC = 30

View File

@ -158,7 +158,10 @@ async def async_setup_entry(
device = hass_data.device_manager.device_map[device_id]
if descriptions := COVERS.get(device.category):
for description in descriptions:
if description.key in device.status:
if (
description.key in device.function
or description.key in device.status_range
):
entities.append(
TuyaCoverEntity(
device, hass_data.device_manager, description

View File

@ -182,8 +182,6 @@ class TariffSelect(RestoreEntity):
async def async_added_to_hass(self):
"""Run when entity about to be added."""
await super().async_added_to_hass()
if self._current_tariff is not None:
return
state = await self.async_get_last_state()
if not state or state.state not in self._tariffs:

View File

@ -32,6 +32,7 @@ from homeassistant.helpers.event import (
async_track_state_change_event,
)
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.template import is_number
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util
@ -166,13 +167,10 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
self._parent_meter = parent_meter
self._sensor_source_id = source_entity
self._state = None
self._last_period = 0
self._last_period = Decimal(0)
self._last_reset = dt_util.utcnow()
self._collecting = None
if name:
self._name = name
else:
self._name = f"{source_entity} meter"
self._name = name
self._unit_of_measurement = None
self._period = meter_type
if meter_type is not None:
@ -231,8 +229,6 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
return
self._state += adjustment
except ValueError as err:
_LOGGER.warning("While processing state changes: %s", err)
except DecimalException as err:
_LOGGER.warning(
"Invalid state (%s > %s): %s", old_state.state, new_state.state, err
@ -282,7 +278,7 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
return
_LOGGER.debug("Reset utility meter <%s>", self.entity_id)
self._last_reset = dt_util.utcnow()
self._last_period = str(self._state)
self._last_period = Decimal(self._state) if self._state else Decimal(0)
self._state = 0
self.async_write_ha_state()
@ -319,9 +315,10 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
ATTR_UNIT_OF_MEASUREMENT
)
self._last_period = (
float(state.attributes.get(ATTR_LAST_PERIOD))
Decimal(state.attributes[ATTR_LAST_PERIOD])
if state.attributes.get(ATTR_LAST_PERIOD)
else 0
and is_number(state.attributes[ATTR_LAST_PERIOD])
else Decimal(0)
)
self._last_reset = dt_util.as_utc(
dt_util.parse_datetime(state.attributes.get(ATTR_LAST_RESET))
@ -399,7 +396,7 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity):
state_attr = {
ATTR_SOURCE_ID: self._sensor_source_id,
ATTR_STATUS: PAUSED if self._collecting is None else COLLECTING,
ATTR_LAST_PERIOD: self._last_period,
ATTR_LAST_PERIOD: str(self._last_period),
}
if self._period is not None:
state_attr[ATTR_PERIOD] = self._period

View File

@ -14,8 +14,8 @@ import requests
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ViCareRequiredKeysMixin
@ -36,7 +36,7 @@ BUTTON_DESCRIPTIONS: tuple[ViCareButtonEntityDescription, ...] = (
key=BUTTON_DHW_ACTIVATE_ONETIME_CHARGE,
name="Activate one-time charge",
icon="mdi:shower-head",
entity_category=ENTITY_CATEGORY_CONFIG,
entity_category=EntityCategory.CONFIG,
value_getter=lambda api: api.activateOneTimeCharge(),
),
)

View File

@ -145,7 +145,7 @@ class VizioDevice(MediaPlayerEntity):
self._volume_step = config_entry.options[CONF_VOLUME_STEP]
self._current_input = None
self._current_app_config = None
self._app_name = None
self._attr_app_name = None
self._available_inputs = []
self._available_apps = []
self._all_apps = apps_coordinator.data if apps_coordinator else None
@ -209,7 +209,7 @@ class VizioDevice(MediaPlayerEntity):
self._attr_volume_level = None
self._attr_is_volume_muted = None
self._current_input = None
self._app_name = None
self._attr_app_name = None
self._current_app_config = None
self._attr_sound_mode = None
return
@ -265,13 +265,13 @@ class VizioDevice(MediaPlayerEntity):
log_api_exception=False
)
self._app_name = find_app_name(
self._attr_app_name = find_app_name(
self._current_app_config,
[APP_HOME, *self._all_apps, *self._additional_app_configs],
)
if self._app_name == NO_APP_RUNNING:
self._app_name = None
if self._attr_app_name == NO_APP_RUNNING:
self._attr_app_name = None
def _get_additional_app_names(self) -> list[dict[str, Any]]:
"""Return list of additional apps that were included in configuration.yaml."""
@ -337,8 +337,8 @@ class VizioDevice(MediaPlayerEntity):
@property
def source(self) -> str | None:
"""Return current input of the device."""
if self._app_name is not None and self._current_input in INPUT_APPS:
return self._app_name
if self._attr_app_name is not None and self._current_input in INPUT_APPS:
return self._attr_app_name
return self._current_input
@ -364,14 +364,6 @@ class VizioDevice(MediaPlayerEntity):
return self._available_inputs
@property
def app_name(self) -> str | None:
"""Return the name of the current app."""
if self.source == self._app_name:
return self._app_name
return None
@property
def app_id(self) -> str | None:
"""Return the ID of the current app if it is unknown by pyvizio."""

View File

@ -8,9 +8,10 @@ from typing import Any
from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC, Platform
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .core import discovery
@ -96,7 +97,7 @@ class ZHAIdentifyButton(ZHAButton):
return cls(unique_id, zha_device, channels, **kwargs)
_attr_device_class: ButtonDeviceClass = ButtonDeviceClass.UPDATE
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
_attr_entity_category = EntityCategory.DIAGNOSTIC
_command_name = "identify"
def get_args(self) -> list[Any]:

View File

@ -8,9 +8,10 @@ from zigpy.zcl.clusters.security import IasWd
from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG, STATE_UNKNOWN, Platform
from homeassistant.const import STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .core import discovery
@ -46,7 +47,7 @@ async def async_setup_entry(
class ZHAEnumSelectEntity(ZhaEntity, SelectEntity):
"""Representation of a ZHA select entity."""
_attr_entity_category = ENTITY_CATEGORY_CONFIG
_attr_entity_category = EntityCategory.CONFIG
_enum: Enum = None
def __init__(

View File

@ -25,7 +25,6 @@ from homeassistant.const import (
ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT,
ENERGY_KILO_WATT_HOUR,
ENTITY_CATEGORY_DIAGNOSTIC,
LIGHT_LUX,
PERCENTAGE,
POWER_VOLT_AMPERE,
@ -699,7 +698,7 @@ class RSSISensor(Sensor, id_suffix="rssi"):
_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
_device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_entity_registry_enabled_default = False
@classmethod

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 2
PATCH_VERSION: Final = "6"
PATCH_VERSION: Final = "7"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)

View File

@ -114,7 +114,7 @@ adext==0.4.2
adguardhome==0.5.1
# homeassistant.components.advantage_air
advantage_air==0.2.5
advantage_air==0.3.0
# homeassistant.components.frontier_silicon
afsapi==0.0.4
@ -191,7 +191,7 @@ aiohomekit==0.6.11
aiohttp_cors==0.7.0
# homeassistant.components.hue
aiohue==4.0.1
aiohue==4.1.2
# homeassistant.components.homewizard
aiohwenergy==0.8.0
@ -681,7 +681,7 @@ fjaraskupan==1.0.2
flipr-api==1.4.1
# homeassistant.components.flux_led
flux_led==0.28.22
flux_led==0.28.26
# homeassistant.components.homekit
fnvhash==0.1.0

View File

@ -70,7 +70,7 @@ adext==0.4.2
adguardhome==0.5.1
# homeassistant.components.advantage_air
advantage_air==0.2.5
advantage_air==0.3.0
# homeassistant.components.agent_dvr
agent-py==0.0.23
@ -141,7 +141,7 @@ aiohomekit==0.6.11
aiohttp_cors==0.7.0
# homeassistant.components.hue
aiohue==4.0.1
aiohue==4.1.2
# homeassistant.components.homewizard
aiohwenergy==0.8.0
@ -427,7 +427,7 @@ fjaraskupan==1.0.2
flipr-api==1.4.1
# homeassistant.components.flux_led
flux_led==0.28.22
flux_led==0.28.26
# homeassistant.components.homekit
fnvhash==0.1.0

View File

@ -1,6 +1,6 @@
[metadata]
name = homeassistant
version = 2022.2.6
version = 2022.2.7
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0

View File

@ -8,7 +8,6 @@ from unittest.mock import AsyncMock, Mock, patch
import aiohue.v1 as aiohue_v1
import aiohue.v2 as aiohue_v2
from aiohue.v2.controllers.events import EventType
from aiohue.v2.models.clip import parse_clip_resource
import pytest
from homeassistant.components import hue
@ -187,7 +186,7 @@ def create_mock_api_v2(hass):
def emit_event(event_type, data):
"""Emit an event from a (hue resource) dict."""
api.events.emit(EventType(event_type), parse_clip_resource(data))
api.events.emit(EventType(event_type), data)
api.load_test_data = load_test_data
api.emit_event = emit_event

View File

@ -97,8 +97,12 @@ async def test_light_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
assert mock_bridge_v2.mock_requests[0]["json"]["color_temperature"]["mirek"] == 300
# Now generate update event by emitting the json we've sent as incoming event
mock_bridge_v2.mock_requests[0]["json"]["color_temperature"].pop("mirek_valid")
mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"])
event = {
"id": "3a6710fa-4474-4eba-b533-5e6e72968feb",
"type": "light",
**mock_bridge_v2.mock_requests[0]["json"],
}
mock_bridge_v2.api.emit_event("update", event)
await hass.async_block_till_done()
# the light should now be on
@ -186,7 +190,12 @@ async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_da
assert mock_bridge_v2.mock_requests[0]["json"]["on"]["on"] is False
# Now generate update event by emitting the json we've sent as incoming event
mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"])
event = {
"id": "02cba059-9c2c-4d45-97e4-4f79b1bfbaa1",
"type": "light",
**mock_bridge_v2.mock_requests[0]["json"],
}
mock_bridge_v2.api.emit_event("update", event)
await hass.async_block_till_done()
# the light should now be off
@ -377,10 +386,20 @@ async def test_grouped_lights(hass, mock_bridge_v2, v2_resources_test_data):
)
# Now generate update events by emitting the json we've sent as incoming events
for index in range(0, 3):
mock_bridge_v2.api.emit_event(
"update", mock_bridge_v2.mock_requests[index]["json"]
)
for index, light_id in enumerate(
[
"02cba059-9c2c-4d45-97e4-4f79b1bfbaa1",
"b3fe71ef-d0ef-48de-9355-d9e604377df0",
"8015b17f-8336-415b-966a-b364bd082397",
]
):
event = {
"id": light_id,
"type": "light",
**mock_bridge_v2.mock_requests[index]["json"],
}
mock_bridge_v2.api.emit_event("update", event)
await hass.async_block_till_done()
await hass.async_block_till_done()
# the light should now be on and have the properties we've set
@ -406,6 +425,12 @@ async def test_grouped_lights(hass, mock_bridge_v2, v2_resources_test_data):
assert mock_bridge_v2.mock_requests[0]["json"]["on"]["on"] is False
# Now generate update event by emitting the json we've sent as incoming event
event = {
"id": "f2416154-9607-43ab-a684-4453108a200e",
"type": "grouped_light",
**mock_bridge_v2.mock_requests[0]["json"],
}
mock_bridge_v2.api.emit_event("update", event)
mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"])
await hass.async_block_till_done()

View File

@ -69,7 +69,12 @@ async def test_switch_turn_off_service(hass, mock_bridge_v2, v2_resources_test_d
assert mock_bridge_v2.mock_requests[0]["json"]["enabled"] is False
# Now generate update event by emitting the json we've sent as incoming event
mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"])
event = {
"id": "b6896534-016d-4052-8cb4-ef04454df62c",
"type": "motion",
**mock_bridge_v2.mock_requests[0]["json"],
}
mock_bridge_v2.api.emit_event("update", event)
await hass.async_block_till_done()
# the switch should now be off

View File

@ -62,7 +62,12 @@ async def test_services(hass):
"source": "sensor.energy",
"cycle": "hourly",
"tariffs": ["peak", "offpeak"],
}
},
"energy_bill2": {
"source": "sensor.energy",
"cycle": "hourly",
"tariffs": ["peak", "offpeak"],
},
}
}
@ -153,6 +158,10 @@ async def test_services(hass):
state = hass.states.get("sensor.energy_bill_offpeak")
assert state.state == "0"
# meanwhile energy_bill2_peak accumulated all kWh
state = hass.states.get("sensor.energy_bill2_peak")
assert state.state == "4"
async def test_cron(hass, legacy_patchable_time):
"""Test cron pattern and offset fails."""

View File

@ -304,6 +304,10 @@ async def test_restore_state(hass):
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
},
),
State(
"sensor.energy_bill_midpeak",
"error",
),
State(
"sensor.energy_bill_offpeak",
"6",
@ -326,6 +330,9 @@ async def test_restore_state(hass):
assert state.attributes.get("last_reset") == last_reset
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
state = hass.states.get("sensor.energy_bill_midpeak")
assert state.state == STATE_UNKNOWN
state = hass.states.get("sensor.energy_bill_offpeak")
assert state.state == "6"
assert state.attributes.get("status") == COLLECTING
@ -530,7 +537,7 @@ async def _test_self_reset(hass, config, start_time, expect_reset=True):
assert state.attributes.get("last_reset") == now.isoformat()
assert state.state == "3"
else:
assert state.attributes.get("last_period") == 0
assert state.attributes.get("last_period") == "0"
assert state.state == "5"
start_time_str = dt_util.parse_datetime(start_time).isoformat()
assert state.attributes.get("last_reset") == start_time_str
@ -559,7 +566,7 @@ async def _test_self_reset(hass, config, start_time, expect_reset=True):
assert state.attributes.get("last_period") == "2"
assert state.state == "7"
else:
assert state.attributes.get("last_period") == 0
assert state.attributes.get("last_period") == "0"
assert state.state == "9"

View File

@ -764,6 +764,5 @@ async def test_vizio_update_with_apps_on_input(
)
await _add_config_entry_to_hass(hass, config_entry)
attr = _get_attr_and_assert_base_attr(hass, DEVICE_CLASS_TV, STATE_ON)
# App name and app ID should not be in the attributes
assert "app_name" not in attr
# app ID should not be in the attributes
assert "app_id" not in attr