This commit is contained in:
Franck Nijhof 2024-07-05 17:25:40 +02:00 committed by GitHub
commit 1cf62916a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 770 additions and 214 deletions

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/anova", "documentation": "https://www.home-assistant.io/integrations/anova",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["anova_wifi"], "loggers": ["anova_wifi"],
"requirements": ["anova-wifi==0.14.0"] "requirements": ["anova-wifi==0.15.0"]
} }

View File

@ -8,5 +8,5 @@
"integration_type": "device", "integration_type": "device",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aioaquacell"], "loggers": ["aioaquacell"],
"requirements": ["aioaquacell==0.1.7"] "requirements": ["aioaquacell==0.1.8"]
} }

View File

@ -2,10 +2,10 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
from typing import cast from typing import cast
from aiohttp import ClientResponseError from aiohttp import ClientResponseError
from path import Path
from yalexs.exceptions import AugustApiAIOHTTPError from yalexs.exceptions import AugustApiAIOHTTPError
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
from yalexs.manager.gateway import Config as YaleXSConfig from yalexs.manager.gateway import Config as YaleXSConfig

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/ecovacs", "documentation": "https://www.home-assistant.io/integrations/ecovacs",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"], "loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.10", "deebot-client==8.1.0"] "requirements": ["py-sucks==0.9.10", "deebot-client==8.1.1"]
} }

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend", "documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system", "integration_type": "system",
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["home-assistant-frontend==20240703.0"] "requirements": ["home-assistant-frontend==20240705.0"]
} }

View File

@ -5,5 +5,5 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday", "documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["holidays==0.51", "babel==2.15.0"] "requirements": ["holidays==0.52", "babel==2.15.0"]
} }

View File

@ -216,14 +216,13 @@ class HomematicipGenericEntity(Entity):
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return a unique ID.""" """Return a unique ID."""
suffix = "" unique_id = f"{self.__class__.__name__}_{self._device.id}"
if self._post is not None:
suffix = f"_{self._post}"
if self._is_multi_channel: if self._is_multi_channel:
return f"{self.__class__.__name__}_Channel{self._channel}_{self._device.id}{suffix}" unique_id = (
f"{self.__class__.__name__}_Channel{self._channel}_{self._device.id}"
)
return f"{self.__class__.__name__}_{self._device.id}{suffix}" return unique_id
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:

View File

@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass
from typing import Any from typing import Any
from homematicip.aio.device import ( from homematicip.aio.device import (
@ -36,7 +35,6 @@ from homematicip.base.functionalChannels import FunctionalChannel
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -163,20 +161,29 @@ async def async_setup_entry(
for ch in get_channels_from_device( for ch in get_channels_from_device(
device, FunctionalChannelType.ENERGY_SENSORS_INTERFACE_CHANNEL device, FunctionalChannelType.ENERGY_SENSORS_INTERFACE_CHANNEL
): ):
if ch.connectedEnergySensorType not in SENSORS_ESI: if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_IEC:
continue if ch.currentPowerConsumption is not None:
entities.append(HmipEsiIecPowerConsumption(hap, device))
new_entities = [ if ch.energyCounterOneType != ESI_TYPE_UNKNOWN:
HmipEsiSensorEntity(hap, device, ch.index, description) entities.append(HmipEsiIecEnergyCounterHighTariff(hap, device))
for description in SENSORS_ESI[ch.connectedEnergySensorType] if ch.energyCounterTwoType != ESI_TYPE_UNKNOWN:
] entities.append(HmipEsiIecEnergyCounterLowTariff(hap, device))
if ch.energyCounterThreeType != ESI_TYPE_UNKNOWN:
entities.extend( entities.append(
entity HmipEsiIecEnergyCounterInputSingleTariff(hap, device)
for entity in new_entities
if entity.entity_description.exists_fn(ch)
) )
if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_GAS:
if ch.currentGasFlow is not None:
entities.append(HmipEsiGasCurrentGasFlow(hap, device))
if ch.gasVolume is not None:
entities.append(HmipEsiGasGasVolume(hap, device))
if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_LED:
if ch.currentPowerConsumption is not None:
entities.append(HmipEsiLedCurrentPowerConsumption(hap, device))
entities.append(HmipEsiLedEnergyCounterHighTariff(hap, device))
async_add_entities(entities) async_add_entities(entities)
@ -434,132 +441,185 @@ class HomematicpTemperatureExternalSensorDelta(HomematicipGenericEntity, SensorE
return self._device.temperatureExternalDelta return self._device.temperatureExternalDelta
@dataclass(kw_only=True, frozen=True)
class HmipEsiSensorEntityDescription(SensorEntityDescription):
"""SensorEntityDescription for HmIP Sensors."""
value_fn: Callable[[AsyncEnergySensorsInterface], StateType]
exists_fn: Callable[[FunctionalChannel], bool]
type_fn: Callable[[AsyncEnergySensorsInterface], str]
SENSORS_ESI = {
ESI_CONNECTED_SENSOR_TYPE_IEC: [
HmipEsiSensorEntityDescription(
key=ESI_TYPE_CURRENT_POWER_CONSUMPTION,
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda device: device.functional_channel.currentPowerConsumption,
exists_fn=lambda channel: channel.currentPowerConsumption is not None,
type_fn=lambda device: "CurrentPowerConsumption",
),
HmipEsiSensorEntityDescription(
key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda device: device.functional_channel.energyCounterOne,
exists_fn=lambda channel: channel.energyCounterOneType != ESI_TYPE_UNKNOWN,
type_fn=lambda device: device.functional_channel.energyCounterOneType,
),
HmipEsiSensorEntityDescription(
key=ESI_TYPE_ENERGY_COUNTER_USAGE_LOW_TARIFF,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda device: device.functional_channel.energyCounterTwo,
exists_fn=lambda channel: channel.energyCounterTwoType != ESI_TYPE_UNKNOWN,
type_fn=lambda device: device.functional_channel.energyCounterTwoType,
),
HmipEsiSensorEntityDescription(
key=ESI_TYPE_ENERGY_COUNTER_INPUT_SINGLE_TARIFF,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda device: device.functional_channel.energyCounterThree,
exists_fn=lambda channel: channel.energyCounterThreeType
!= ESI_TYPE_UNKNOWN,
type_fn=lambda device: device.functional_channel.energyCounterThreeType,
),
],
ESI_CONNECTED_SENSOR_TYPE_LED: [
HmipEsiSensorEntityDescription(
key=ESI_TYPE_CURRENT_POWER_CONSUMPTION,
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda device: device.functional_channel.currentPowerConsumption,
exists_fn=lambda channel: channel.currentPowerConsumption is not None,
type_fn=lambda device: "CurrentPowerConsumption",
),
HmipEsiSensorEntityDescription(
key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda device: device.functional_channel.energyCounterOne,
exists_fn=lambda channel: channel.energyCounterOne is not None,
type_fn=lambda device: ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF,
),
],
ESI_CONNECTED_SENSOR_TYPE_GAS: [
HmipEsiSensorEntityDescription(
key=ESI_TYPE_CURRENT_GAS_FLOW,
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda device: device.functional_channel.currentGasFlow,
exists_fn=lambda channel: channel.currentGasFlow is not None,
type_fn=lambda device: "CurrentGasFlow",
),
HmipEsiSensorEntityDescription(
key=ESI_TYPE_CURRENT_GAS_VOLUME,
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
device_class=SensorDeviceClass.VOLUME,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda device: device.functional_channel.gasVolume,
exists_fn=lambda channel: channel.gasVolume is not None,
type_fn=lambda device: "GasVolume",
),
],
}
class HmipEsiSensorEntity(HomematicipGenericEntity, SensorEntity): class HmipEsiSensorEntity(HomematicipGenericEntity, SensorEntity):
"""EntityDescription for HmIP-ESI Sensors.""" """EntityDescription for HmIP-ESI Sensors."""
entity_description: HmipEsiSensorEntityDescription
def __init__( def __init__(
self, self,
hap: HomematicipHAP, hap: HomematicipHAP,
device: HomematicipGenericEntity, device: HomematicipGenericEntity,
channel_index: int, key: str,
entity_description: HmipEsiSensorEntityDescription, value_fn: Callable[[FunctionalChannel], StateType],
type_fn: Callable[[FunctionalChannel], str],
) -> None: ) -> None:
"""Initialize Sensor Entity.""" """Initialize Sensor Entity."""
super().__init__( super().__init__(
hap=hap, hap=hap,
device=device, device=device,
channel=channel_index, channel=1,
post=entity_description.key, post=key,
is_multi_channel=False, is_multi_channel=False,
) )
self.entity_description = entity_description
self._value_fn = value_fn
self._type_fn = type_fn
@property @property
def extra_state_attributes(self) -> dict[str, Any]: def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes of the esi sensor.""" """Return the state attributes of the esi sensor."""
state_attr = super().extra_state_attributes state_attr = super().extra_state_attributes
state_attr[ATTR_ESI_TYPE] = self.entity_description.type_fn(self) state_attr[ATTR_ESI_TYPE] = self._type_fn(self.functional_channel)
return state_attr return state_attr
@property @property
def native_value(self) -> str | None: def native_value(self) -> str | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return str(self.entity_description.value_fn(self)) return str(self._value_fn(self.functional_channel))
class HmipEsiIecPowerConsumption(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI IEC currentPowerConsumption sensor."""
_attr_device_class = SensorDeviceClass.POWER
_attr_native_unit_of_measurement = UnitOfPower.WATT
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key="CurrentPowerConsumption",
value_fn=lambda channel: channel.currentPowerConsumption,
type_fn=lambda channel: "CurrentPowerConsumption",
)
class HmipEsiIecEnergyCounterHighTariff(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI IEC energyCounterOne sensor."""
_attr_device_class = SensorDeviceClass.ENERGY
_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
_attr_state_class = SensorStateClass.TOTAL_INCREASING
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF,
value_fn=lambda channel: channel.energyCounterOne,
type_fn=lambda channel: channel.energyCounterOneType,
)
class HmipEsiIecEnergyCounterLowTariff(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI IEC energyCounterTwo sensor."""
_attr_device_class = SensorDeviceClass.ENERGY
_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
_attr_state_class = SensorStateClass.TOTAL_INCREASING
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key=ESI_TYPE_ENERGY_COUNTER_USAGE_LOW_TARIFF,
value_fn=lambda channel: channel.energyCounterTwo,
type_fn=lambda channel: channel.energyCounterTwoType,
)
class HmipEsiIecEnergyCounterInputSingleTariff(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI IEC energyCounterThree sensor."""
_attr_device_class = SensorDeviceClass.ENERGY
_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
_attr_state_class = SensorStateClass.TOTAL_INCREASING
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key=ESI_TYPE_ENERGY_COUNTER_INPUT_SINGLE_TARIFF,
value_fn=lambda channel: channel.energyCounterThree,
type_fn=lambda channel: channel.energyCounterThreeType,
)
class HmipEsiGasCurrentGasFlow(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI Gas currentGasFlow sensor."""
_attr_device_class = SensorDeviceClass.VOLUME_FLOW_RATE
_attr_native_unit_of_measurement = UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key="CurrentGasFlow",
value_fn=lambda channel: channel.currentGasFlow,
type_fn=lambda channel: "CurrentGasFlow",
)
class HmipEsiGasGasVolume(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI Gas gasVolume sensor."""
_attr_device_class = SensorDeviceClass.GAS
_attr_native_unit_of_measurement = UnitOfVolume.CUBIC_METERS
_attr_state_class = SensorStateClass.TOTAL_INCREASING
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key="GasVolume",
value_fn=lambda channel: channel.gasVolume,
type_fn=lambda channel: "GasVolume",
)
class HmipEsiLedCurrentPowerConsumption(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI LED currentPowerConsumption sensor."""
_attr_device_class = SensorDeviceClass.POWER
_attr_native_unit_of_measurement = UnitOfPower.WATT
_attr_state_class = SensorStateClass.MEASUREMENT
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key="CurrentPowerConsumption",
value_fn=lambda channel: channel.currentPowerConsumption,
type_fn=lambda channel: "CurrentPowerConsumption",
)
class HmipEsiLedEnergyCounterHighTariff(HmipEsiSensorEntity):
"""Representation of the Hmip-ESI LED energyCounterOne sensor."""
_attr_device_class = SensorDeviceClass.ENERGY
_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
_attr_state_class = SensorStateClass.TOTAL_INCREASING
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the device."""
super().__init__(
hap,
device,
key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF,
value_fn=lambda channel: channel.energyCounterOne,
type_fn=lambda channel: ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF,
)
class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity, SensorEntity): class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity, SensorEntity):

View File

@ -184,6 +184,8 @@ RESTRICTED_REASONS: list = [
RestrictedReasons.WEEK_SCHEDULE.lower(), RestrictedReasons.WEEK_SCHEDULE.lower(),
] ]
STATE_NO_WORK_AREA_ACTIVE = "no_work_area_active"
@callback @callback
def _get_work_area_names(data: MowerAttributes) -> list[str]: def _get_work_area_names(data: MowerAttributes) -> list[str]:
@ -191,16 +193,21 @@ def _get_work_area_names(data: MowerAttributes) -> list[str]:
if TYPE_CHECKING: if TYPE_CHECKING:
# Sensor does not get created if it is None # Sensor does not get created if it is None
assert data.work_areas is not None assert data.work_areas is not None
return [data.work_areas[work_area_id].name for work_area_id in data.work_areas] work_area_list = [
data.work_areas[work_area_id].name for work_area_id in data.work_areas
]
work_area_list.append(STATE_NO_WORK_AREA_ACTIVE)
return work_area_list
@callback @callback
def _get_current_work_area_name(data: MowerAttributes) -> str: def _get_current_work_area_name(data: MowerAttributes) -> str:
"""Return the name of the current work area.""" """Return the name of the current work area."""
if data.mower.work_area_id is None:
return STATE_NO_WORK_AREA_ACTIVE
if TYPE_CHECKING: if TYPE_CHECKING:
# Sensor does not get created if values are None # Sensor does not get created if values are None
assert data.work_areas is not None assert data.work_areas is not None
assert data.mower.work_area_id is not None
return data.work_areas[data.mower.work_area_id].name return data.work_areas[data.mower.work_area_id].name

View File

@ -252,7 +252,8 @@
"work_area": { "work_area": {
"name": "Work area", "name": "Work area",
"state": { "state": {
"my_lawn": "My lawn" "my_lawn": "My lawn",
"no_work_area_active": "No work area active"
} }
} }
}, },

View File

@ -28,5 +28,5 @@
"dependencies": ["bluetooth_adapters"], "dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/inkbird", "documentation": "https://www.home-assistant.io/integrations/inkbird",
"iot_class": "local_push", "iot_class": "local_push",
"requirements": ["inkbird-ble==0.5.7"] "requirements": ["inkbird-ble==0.5.8"]
} }

View File

@ -350,6 +350,7 @@ DISCOVERY_SCHEMAS = [
clusters.Thermostat.Attributes.TemperatureSetpointHold, clusters.Thermostat.Attributes.TemperatureSetpointHold,
clusters.Thermostat.Attributes.UnoccupiedCoolingSetpoint, clusters.Thermostat.Attributes.UnoccupiedCoolingSetpoint,
clusters.Thermostat.Attributes.UnoccupiedHeatingSetpoint, clusters.Thermostat.Attributes.UnoccupiedHeatingSetpoint,
clusters.OnOff.Attributes.OnOff,
), ),
device_type=(device_types.Thermostat, device_types.RoomAirConditioner), device_type=(device_types.Thermostat, device_types.RoomAirConditioner),
), ),

View File

@ -313,6 +313,7 @@ DISCOVERY_SCHEMAS = [
clusters.FanControl.Attributes.RockSetting, clusters.FanControl.Attributes.RockSetting,
clusters.FanControl.Attributes.WindSetting, clusters.FanControl.Attributes.WindSetting,
clusters.FanControl.Attributes.AirflowDirection, clusters.FanControl.Attributes.AirflowDirection,
clusters.OnOff.Attributes.OnOff,
), ),
), ),
] ]

View File

@ -446,6 +446,8 @@ DISCOVERY_SCHEMAS = [
device_types.DimmablePlugInUnit, device_types.DimmablePlugInUnit,
device_types.ExtendedColorLight, device_types.ExtendedColorLight,
device_types.OnOffLight, device_types.OnOffLight,
device_types.DimmerSwitch,
device_types.ColorDimmerSwitch,
), ),
), ),
# Additional schema to match (HS Color) lights with incorrect/missing device type # Additional schema to match (HS Color) lights with incorrect/missing device type

View File

@ -90,6 +90,9 @@ class MatterLock(MatterEntity, LockEntity):
async def async_lock(self, **kwargs: Any) -> None: async def async_lock(self, **kwargs: Any) -> None:
"""Lock the lock with pin if needed.""" """Lock the lock with pin if needed."""
# optimistically signal locking to state machine
self._attr_is_locking = True
self.async_write_ha_state()
code: str | None = kwargs.get(ATTR_CODE) code: str | None = kwargs.get(ATTR_CODE)
code_bytes = code.encode() if code else None code_bytes = code.encode() if code else None
await self.send_device_command( await self.send_device_command(
@ -98,6 +101,9 @@ class MatterLock(MatterEntity, LockEntity):
async def async_unlock(self, **kwargs: Any) -> None: async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the lock with pin if needed.""" """Unlock the lock with pin if needed."""
# optimistically signal unlocking to state machine
self._attr_is_unlocking = True
self.async_write_ha_state()
code: str | None = kwargs.get(ATTR_CODE) code: str | None = kwargs.get(ATTR_CODE)
code_bytes = code.encode() if code else None code_bytes = code.encode() if code else None
if self.supports_unbolt: if self.supports_unbolt:
@ -114,6 +120,9 @@ class MatterLock(MatterEntity, LockEntity):
async def async_open(self, **kwargs: Any) -> None: async def async_open(self, **kwargs: Any) -> None:
"""Open the door latch.""" """Open the door latch."""
# optimistically signal unlocking to state machine
self._attr_is_unlocking = True
self.async_write_ha_state()
code: str | None = kwargs.get(ATTR_CODE) code: str | None = kwargs.get(ATTR_CODE)
code_bytes = code.encode() if code else None code_bytes = code.encode() if code else None
await self.send_device_command( await self.send_device_command(
@ -135,26 +144,23 @@ class MatterLock(MatterEntity, LockEntity):
clusters.DoorLock.Attributes.LockState clusters.DoorLock.Attributes.LockState
) )
# always reset the optimisically (un)locking state on state update
self._attr_is_locking = False
self._attr_is_unlocking = False
LOGGER.debug("Lock state: %s for %s", lock_state, self.entity_id) LOGGER.debug("Lock state: %s for %s", lock_state, self.entity_id)
if lock_state is clusters.DoorLock.Enums.DlLockState.kLocked: if lock_state is clusters.DoorLock.Enums.DlLockState.kLocked:
self._attr_is_locked = True self._attr_is_locked = True
self._attr_is_locking = False elif lock_state in (
self._attr_is_unlocking = False clusters.DoorLock.Enums.DlLockState.kUnlocked,
elif lock_state is clusters.DoorLock.Enums.DlLockState.kUnlocked: clusters.DoorLock.Enums.DlLockState.kUnlatched,
clusters.DoorLock.Enums.DlLockState.kNotFullyLocked,
):
self._attr_is_locked = False self._attr_is_locked = False
self._attr_is_locking = False
self._attr_is_unlocking = False
elif lock_state is clusters.DoorLock.Enums.DlLockState.kNotFullyLocked:
if self.is_locked is True:
self._attr_is_unlocking = True
elif self.is_locked is False:
self._attr_is_locking = True
else: else:
# According to the matter docs a null state can happen during device startup. # According to the matter docs a null state can happen during device startup.
self._attr_is_locked = None self._attr_is_locked = None
self._attr_is_locking = None
self._attr_is_unlocking = None
if self.supports_door_position_sensor: if self.supports_door_position_sensor:
door_state = self.get_matter_attribute_value( door_state = self.get_matter_attribute_value(

View File

@ -114,6 +114,7 @@ DISCOVERY_SCHEMAS = [
device_types.ColorTemperatureLight, device_types.ColorTemperatureLight,
device_types.DimmableLight, device_types.DimmableLight,
device_types.ExtendedColorLight, device_types.ExtendedColorLight,
device_types.DimmerSwitch,
device_types.ColorDimmerSwitch, device_types.ColorDimmerSwitch,
device_types.OnOffLight, device_types.OnOffLight,
device_types.AirPurifier, device_types.AirPurifier,

View File

@ -44,5 +44,95 @@
"title": "[%key:component::random::config::step::sensor::title%]" "title": "[%key:component::random::config::step::sensor::title%]"
} }
} }
},
"selector": {
"binary_sensor_device_class": {
"options": {
"battery": "[%key:component::binary_sensor::entity_component::battery::name%]",
"battery_charging": "[%key:component::binary_sensor::entity_component::battery_charging::name%]",
"carbon_monoxide": "[%key:component::binary_sensor::entity_component::carbon_monoxide::name%]",
"connectivity": "[%key:component::binary_sensor::entity_component::connectivity::name%]",
"cold": "[%key:component::binary_sensor::entity_component::cold::name%]",
"door": "[%key:component::binary_sensor::entity_component::door::name%]",
"garage_door": "[%key:component::binary_sensor::entity_component::garage_door::name%]",
"gas": "[%key:component::binary_sensor::entity_component::gas::name%]",
"heat": "[%key:component::binary_sensor::entity_component::heat::name%]",
"light": "[%key:component::binary_sensor::entity_component::light::name%]",
"lock": "[%key:component::binary_sensor::entity_component::lock::name%]",
"moisture": "[%key:component::binary_sensor::entity_component::moisture::name%]",
"motion": "[%key:component::binary_sensor::entity_component::motion::name%]",
"moving": "[%key:component::binary_sensor::entity_component::moving::name%]",
"occupancy": "[%key:component::binary_sensor::entity_component::occupancy::name%]",
"opening": "[%key:component::binary_sensor::entity_component::opening::name%]",
"plug": "[%key:component::binary_sensor::entity_component::plug::name%]",
"power": "[%key:component::binary_sensor::entity_component::power::name%]",
"presence": "[%key:component::binary_sensor::entity_component::presence::name%]",
"problem": "[%key:component::binary_sensor::entity_component::problem::name%]",
"running": "[%key:component::binary_sensor::entity_component::running::name%]",
"safety": "[%key:component::binary_sensor::entity_component::safety::name%]",
"smoke": "[%key:component::binary_sensor::entity_component::smoke::name%]",
"sound": "[%key:component::binary_sensor::entity_component::sound::name%]",
"tamper": "[%key:component::binary_sensor::entity_component::tamper::name%]",
"update": "[%key:component::binary_sensor::entity_component::update::name%]",
"vibration": "[%key:component::binary_sensor::entity_component::vibration::name%]",
"window": "[%key:component::binary_sensor::entity_component::window::name%]"
}
},
"sensor_device_class": {
"options": {
"apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]",
"aqi": "[%key:component::sensor::entity_component::aqi::name%]",
"atmospheric_pressure": "[%key:component::sensor::entity_component::atmospheric_pressure::name%]",
"battery": "[%key:component::sensor::entity_component::battery::name%]",
"carbon_dioxide": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
"carbon_monoxide": "[%key:component::sensor::entity_component::carbon_monoxide::name%]",
"conductivity": "[%key:component::sensor::entity_component::conductivity::name%]",
"current": "[%key:component::sensor::entity_component::current::name%]",
"data_rate": "[%key:component::sensor::entity_component::data_rate::name%]",
"data_size": "[%key:component::sensor::entity_component::data_size::name%]",
"date": "[%key:component::sensor::entity_component::date::name%]",
"distance": "[%key:component::sensor::entity_component::distance::name%]",
"duration": "[%key:component::sensor::entity_component::duration::name%]",
"energy": "[%key:component::sensor::entity_component::energy::name%]",
"energy_storage": "[%key:component::sensor::entity_component::energy_storage::name%]",
"frequency": "[%key:component::sensor::entity_component::frequency::name%]",
"gas": "[%key:component::sensor::entity_component::gas::name%]",
"humidity": "[%key:component::sensor::entity_component::humidity::name%]",
"illuminance": "[%key:component::sensor::entity_component::illuminance::name%]",
"irradiance": "[%key:component::sensor::entity_component::irradiance::name%]",
"moisture": "[%key:component::sensor::entity_component::moisture::name%]",
"monetary": "[%key:component::sensor::entity_component::monetary::name%]",
"nitrogen_dioxide": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]",
"nitrogen_monoxide": "[%key:component::sensor::entity_component::nitrogen_monoxide::name%]",
"nitrous_oxide": "[%key:component::sensor::entity_component::nitrous_oxide::name%]",
"ozone": "[%key:component::sensor::entity_component::ozone::name%]",
"ph": "[%key:component::sensor::entity_component::ph::name%]",
"pm1": "[%key:component::sensor::entity_component::pm1::name%]",
"pm10": "[%key:component::sensor::entity_component::pm10::name%]",
"pm25": "[%key:component::sensor::entity_component::pm25::name%]",
"power": "[%key:component::sensor::entity_component::power::name%]",
"power_factor": "[%key:component::sensor::entity_component::power_factor::name%]",
"precipitation": "[%key:component::sensor::entity_component::precipitation::name%]",
"precipitation_intensity": "[%key:component::sensor::entity_component::precipitation_intensity::name%]",
"pressure": "[%key:component::sensor::entity_component::pressure::name%]",
"reactive_power": "[%key:component::sensor::entity_component::reactive_power::name%]",
"signal_strength": "[%key:component::sensor::entity_component::signal_strength::name%]",
"sound_pressure": "[%key:component::sensor::entity_component::sound_pressure::name%]",
"speed": "[%key:component::sensor::entity_component::speed::name%]",
"sulphur_dioxide": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]",
"temperature": "[%key:component::sensor::entity_component::temperature::name%]",
"timestamp": "[%key:component::sensor::entity_component::timestamp::name%]",
"volatile_organic_compounds": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]",
"volatile_organic_compounds_parts": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]",
"voltage": "[%key:component::sensor::entity_component::voltage::name%]",
"volume": "[%key:component::sensor::entity_component::volume::name%]",
"volume_flow_rate": "[%key:component::sensor::entity_component::volume_flow_rate::name%]",
"volume_storage": "[%key:component::sensor::entity_component::volume_storage::name%]",
"water": "[%key:component::sensor::entity_component::water::name%]",
"weight": "[%key:component::sensor::entity_component::weight::name%]",
"wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]"
}
}
} }
} }

View File

@ -960,14 +960,18 @@ RPC_SENSORS: Final = {
name="Analog input", name="Analog input",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
removal_condition=lambda config, _status, key: (config[key]["enable"] is False), removal_condition=lambda config, _, key: (
config[key]["type"] != "analog" or config[key]["enable"] is False
),
), ),
"analoginput_xpercent": RpcSensorDescription( "analoginput_xpercent": RpcSensorDescription(
key="input", key="input",
sub_key="xpercent", sub_key="xpercent",
name="Analog value", name="Analog value",
removal_condition=lambda config, status, key: ( removal_condition=lambda config, status, key: (
config[key]["enable"] is False or status[key].get("xpercent") is None config[key]["type"] != "analog"
or config[key]["enable"] is False
or status[key].get("xpercent") is None
), ),
), ),
"pulse_counter": RpcSensorDescription( "pulse_counter": RpcSensorDescription(
@ -977,7 +981,9 @@ RPC_SENSORS: Final = {
native_unit_of_measurement="pulse", native_unit_of_measurement="pulse",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value=lambda status, _: status["total"], value=lambda status, _: status["total"],
removal_condition=lambda config, _status, key: (config[key]["enable"] is False), removal_condition=lambda config, _status, key: (
config[key]["type"] != "count" or config[key]["enable"] is False
),
), ),
"counter_value": RpcSensorDescription( "counter_value": RpcSensorDescription(
key="input", key="input",
@ -985,26 +991,29 @@ RPC_SENSORS: Final = {
name="Counter value", name="Counter value",
value=lambda status, _: status["xtotal"], value=lambda status, _: status["xtotal"],
removal_condition=lambda config, status, key: ( removal_condition=lambda config, status, key: (
config[key]["enable"] is False config[key]["type"] != "count"
or config[key]["enable"] is False
or status[key]["counts"].get("xtotal") is None or status[key]["counts"].get("xtotal") is None
), ),
), ),
"counter_frequency": RpcSensorDescription( "counter_frequency": RpcSensorDescription(
key="input", key="input",
sub_key="counts", sub_key="freq",
name="Pulse counter frequency", name="Pulse counter frequency",
native_unit_of_measurement=UnitOfFrequency.HERTZ, native_unit_of_measurement=UnitOfFrequency.HERTZ,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda status, _: status["freq"], removal_condition=lambda config, _, key: (
removal_condition=lambda config, status, key: (config[key]["enable"] is False), config[key]["type"] != "count" or config[key]["enable"] is False
),
), ),
"counter_frequency_value": RpcSensorDescription( "counter_frequency_value": RpcSensorDescription(
key="input", key="input",
sub_key="counts", sub_key="xfreq",
name="Pulse counter frequency value", name="Pulse counter frequency value",
value=lambda status, _: status["xfreq"],
removal_condition=lambda config, status, key: ( removal_condition=lambda config, status, key: (
config[key]["enable"] is False or status[key]["counts"].get("xfreq") is None config[key]["type"] != "count"
or config[key]["enable"] is False
or status[key].get("xfreq") is None
), ),
), ),
} }

View File

@ -9,7 +9,7 @@ from typing import Any
from starline import StarlineApi, StarlineDevice from starline import StarlineApi, StarlineDevice
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -65,9 +65,9 @@ class StarlineAccount:
) )
self._api.set_slnet_token(slnet_token) self._api.set_slnet_token(slnet_token)
self._api.set_user_id(user_id) self._api.set_user_id(user_id)
self._hass.config_entries.async_update_entry( self._hass.add_job(
self._config_entry, self._save_slnet_token,
data={ {
**self._config_entry.data, **self._config_entry.data,
DATA_SLNET_TOKEN: slnet_token, DATA_SLNET_TOKEN: slnet_token,
DATA_EXPIRES: slnet_token_expires, DATA_EXPIRES: slnet_token_expires,
@ -77,6 +77,13 @@ class StarlineAccount:
except Exception as err: # noqa: BLE001 except Exception as err: # noqa: BLE001
_LOGGER.error("Error updating SLNet token: %s", err) _LOGGER.error("Error updating SLNet token: %s", err)
@callback
def _save_slnet_token(self, data) -> None:
self._hass.config_entries.async_update_entry(
self._config_entry,
data=data,
)
def _update_data(self): def _update_data(self):
"""Update StarLine data.""" """Update StarLine data."""
self._check_slnet_token(self._update_interval) self._check_slnet_token(self._update_interval)

View File

@ -48,6 +48,14 @@ class StreamWorkerError(Exception):
"""An exception thrown while processing a stream.""" """An exception thrown while processing a stream."""
def redact_av_error_string(err: av.AVError) -> str:
"""Return an error string with credentials redacted from the url."""
parts = [str(err.type), err.strerror]
if err.filename is not None:
parts.append(redact_credentials(err.filename))
return ", ".join(parts)
class StreamEndedError(StreamWorkerError): class StreamEndedError(StreamWorkerError):
"""Raised when the stream is complete, exposed for facilitating testing.""" """Raised when the stream is complete, exposed for facilitating testing."""
@ -517,8 +525,7 @@ def stream_worker(
container = av.open(source, options=pyav_options, timeout=SOURCE_TIMEOUT) container = av.open(source, options=pyav_options, timeout=SOURCE_TIMEOUT)
except av.AVError as err: except av.AVError as err:
raise StreamWorkerError( raise StreamWorkerError(
f"Error opening stream ({err.type}, {err.strerror})" f"Error opening stream ({redact_av_error_string(err)})"
f" {redact_credentials(str(source))}"
) from err ) from err
try: try:
video_stream = container.streams.video[0] video_stream = container.streams.video[0]
@ -593,7 +600,7 @@ def stream_worker(
except av.AVError as ex: except av.AVError as ex:
container.close() container.close()
raise StreamWorkerError( raise StreamWorkerError(
f"Error demuxing stream while finding first packet: {ex!s}" f"Error demuxing stream while finding first packet ({redact_av_error_string(ex)})"
) from ex ) from ex
muxer = StreamMuxer( muxer = StreamMuxer(
@ -618,7 +625,9 @@ def stream_worker(
except StopIteration as ex: except StopIteration as ex:
raise StreamEndedError("Stream ended; no additional packets") from ex raise StreamEndedError("Stream ended; no additional packets") from ex
except av.AVError as ex: except av.AVError as ex:
raise StreamWorkerError(f"Error demuxing stream: {ex!s}") from ex raise StreamWorkerError(
f"Error demuxing stream ({redact_av_error_string(ex)})"
) from ex
muxer.mux_packet(packet) muxer.mux_packet(packet)

View File

@ -382,7 +382,12 @@ class TPLinkLightEffectEntity(TPLinkLightEntity):
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on.""" """Turn the light on."""
brightness, transition = self._async_extract_brightness_transition(**kwargs) brightness, transition = self._async_extract_brightness_transition(**kwargs)
if ATTR_EFFECT in kwargs: if (
(effect := kwargs.get(ATTR_EFFECT))
# Effect is unlikely to be LIGHT_EFFECTS_OFF but check for it anyway
and effect not in {LightEffect.LIGHT_EFFECTS_OFF, EFFECT_OFF}
and effect in self._effect_module.effect_list
):
await self._effect_module.set_effect( await self._effect_module.set_effect(
kwargs[ATTR_EFFECT], brightness=brightness, transition=transition kwargs[ATTR_EFFECT], brightness=brightness, transition=transition
) )

View File

@ -297,5 +297,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["kasa"], "loggers": ["kasa"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["python-kasa[speedups]==0.7.0.2"] "requirements": ["python-kasa[speedups]==0.7.0.3"]
} }

View File

@ -13,7 +13,7 @@
"velbus-packet", "velbus-packet",
"velbus-protocol" "velbus-protocol"
], ],
"requirements": ["velbus-aio==2024.5.1"], "requirements": ["velbus-aio==2024.7.5"],
"usb": [ "usb": [
{ {
"vid": "10CF", "vid": "10CF",

View File

@ -7,7 +7,7 @@
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["aiowebostv"], "loggers": ["aiowebostv"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["aiowebostv==0.4.1"], "requirements": ["aiowebostv==0.4.2"],
"ssdp": [ "ssdp": [
{ {
"st": "urn:lge-com:service:webos-second-screen:1" "st": "urn:lge-com:service:webos-second-screen:1"

View File

@ -239,7 +239,8 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
self._attr_assumed_state = True self._attr_assumed_state = True
if ( if (
self._client.media_state is not None self._client.is_on
and self._client.media_state is not None
and self._client.media_state.get("foregroundAppInfo") is not None and self._client.media_state.get("foregroundAppInfo") is not None
): ):
self._attr_assumed_state = False self._attr_assumed_state = False

View File

@ -7,5 +7,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["holidays"], "loggers": ["holidays"],
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["holidays==0.51"] "requirements": ["holidays==0.52"]
} }

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/youless", "documentation": "https://www.home-assistant.io/integrations/youless",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["youless_api"], "loggers": ["youless_api"],
"requirements": ["youless-api==2.1.0"] "requirements": ["youless-api==2.1.2"]
} }

View File

@ -24,7 +24,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant" APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2024 MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 7 MINOR_VERSION: Final = 7
PATCH_VERSION: Final = "0" PATCH_VERSION: Final = "1"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)

View File

@ -32,7 +32,7 @@ habluetooth==3.1.3
hass-nabucasa==0.81.1 hass-nabucasa==0.81.1
hassil==1.7.1 hassil==1.7.1
home-assistant-bluetooth==1.12.2 home-assistant-bluetooth==1.12.2
home-assistant-frontend==20240703.0 home-assistant-frontend==20240705.0
home-assistant-intents==2024.7.3 home-assistant-intents==2024.7.3
httpx==0.27.0 httpx==0.27.0
ifaddr==0.2.0 ifaddr==0.2.0

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "homeassistant" name = "homeassistant"
version = "2024.7.0" version = "2024.7.1"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3." description = "Open-source home automation platform running on Python 3."
readme = "README.rst" readme = "README.rst"

View File

@ -195,7 +195,7 @@ aioambient==2024.01.0
aioapcaccess==0.4.2 aioapcaccess==0.4.2
# homeassistant.components.aquacell # homeassistant.components.aquacell
aioaquacell==0.1.7 aioaquacell==0.1.8
# homeassistant.components.aseko_pool_live # homeassistant.components.aseko_pool_live
aioaseko==0.1.1 aioaseko==0.1.1
@ -404,7 +404,7 @@ aiowaqi==3.1.0
aiowatttime==0.1.1 aiowatttime==0.1.1
# homeassistant.components.webostv # homeassistant.components.webostv
aiowebostv==0.4.1 aiowebostv==0.4.2
# homeassistant.components.withings # homeassistant.components.withings
aiowithings==3.0.2 aiowithings==3.0.2
@ -449,7 +449,7 @@ androidtvremote2==0.1.1
anel-pwrctrl-homeassistant==0.0.1.dev2 anel-pwrctrl-homeassistant==0.0.1.dev2
# homeassistant.components.anova # homeassistant.components.anova
anova-wifi==0.14.0 anova-wifi==0.15.0
# homeassistant.components.anthemav # homeassistant.components.anthemav
anthemav==1.4.1 anthemav==1.4.1
@ -709,7 +709,7 @@ debugpy==1.8.1
# decora==0.6 # decora==0.6
# homeassistant.components.ecovacs # homeassistant.components.ecovacs
deebot-client==8.1.0 deebot-client==8.1.1
# homeassistant.components.ihc # homeassistant.components.ihc
# homeassistant.components.namecheapdns # homeassistant.components.namecheapdns
@ -1087,10 +1087,10 @@ hole==0.8.0
# homeassistant.components.holiday # homeassistant.components.holiday
# homeassistant.components.workday # homeassistant.components.workday
holidays==0.51 holidays==0.52
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20240703.0 home-assistant-frontend==20240705.0
# homeassistant.components.conversation # homeassistant.components.conversation
home-assistant-intents==2024.7.3 home-assistant-intents==2024.7.3
@ -1161,7 +1161,7 @@ influxdb-client==1.24.0
influxdb==5.3.1 influxdb==5.3.1
# homeassistant.components.inkbird # homeassistant.components.inkbird
inkbird-ble==0.5.7 inkbird-ble==0.5.8
# homeassistant.components.insteon # homeassistant.components.insteon
insteon-frontend-home-assistant==0.5.0 insteon-frontend-home-assistant==0.5.0
@ -2275,7 +2275,7 @@ python-join-api==0.0.9
python-juicenet==1.1.0 python-juicenet==1.1.0
# homeassistant.components.tplink # homeassistant.components.tplink
python-kasa[speedups]==0.7.0.2 python-kasa[speedups]==0.7.0.3
# homeassistant.components.lirc # homeassistant.components.lirc
# python-lirc==1.2.3 # python-lirc==1.2.3
@ -2830,7 +2830,7 @@ vallox-websocket-api==5.3.0
vehicle==2.2.1 vehicle==2.2.1
# homeassistant.components.velbus # homeassistant.components.velbus
velbus-aio==2024.5.1 velbus-aio==2024.7.5
# homeassistant.components.venstar # homeassistant.components.venstar
venstarcolortouch==0.19 venstarcolortouch==0.19
@ -2945,7 +2945,7 @@ yeelightsunflower==0.0.10
yolink-api==0.4.4 yolink-api==0.4.4
# homeassistant.components.youless # homeassistant.components.youless
youless-api==2.1.0 youless-api==2.1.2
# homeassistant.components.youtube # homeassistant.components.youtube
youtubeaio==1.1.5 youtubeaio==1.1.5

View File

@ -174,7 +174,7 @@ aioambient==2024.01.0
aioapcaccess==0.4.2 aioapcaccess==0.4.2
# homeassistant.components.aquacell # homeassistant.components.aquacell
aioaquacell==0.1.7 aioaquacell==0.1.8
# homeassistant.components.aseko_pool_live # homeassistant.components.aseko_pool_live
aioaseko==0.1.1 aioaseko==0.1.1
@ -377,7 +377,7 @@ aiowaqi==3.1.0
aiowatttime==0.1.1 aiowatttime==0.1.1
# homeassistant.components.webostv # homeassistant.components.webostv
aiowebostv==0.4.1 aiowebostv==0.4.2
# homeassistant.components.withings # homeassistant.components.withings
aiowithings==3.0.2 aiowithings==3.0.2
@ -413,7 +413,7 @@ androidtv[async]==0.0.73
androidtvremote2==0.1.1 androidtvremote2==0.1.1
# homeassistant.components.anova # homeassistant.components.anova
anova-wifi==0.14.0 anova-wifi==0.15.0
# homeassistant.components.anthemav # homeassistant.components.anthemav
anthemav==1.4.1 anthemav==1.4.1
@ -590,7 +590,7 @@ dbus-fast==2.22.1
debugpy==1.8.1 debugpy==1.8.1
# homeassistant.components.ecovacs # homeassistant.components.ecovacs
deebot-client==8.1.0 deebot-client==8.1.1
# homeassistant.components.ihc # homeassistant.components.ihc
# homeassistant.components.namecheapdns # homeassistant.components.namecheapdns
@ -892,10 +892,10 @@ hole==0.8.0
# homeassistant.components.holiday # homeassistant.components.holiday
# homeassistant.components.workday # homeassistant.components.workday
holidays==0.51 holidays==0.52
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20240703.0 home-assistant-frontend==20240705.0
# homeassistant.components.conversation # homeassistant.components.conversation
home-assistant-intents==2024.7.3 home-assistant-intents==2024.7.3
@ -951,7 +951,7 @@ influxdb-client==1.24.0
influxdb==5.3.1 influxdb==5.3.1
# homeassistant.components.inkbird # homeassistant.components.inkbird
inkbird-ble==0.5.7 inkbird-ble==0.5.8
# homeassistant.components.insteon # homeassistant.components.insteon
insteon-frontend-home-assistant==0.5.0 insteon-frontend-home-assistant==0.5.0
@ -1775,7 +1775,7 @@ python-izone==1.2.9
python-juicenet==1.1.0 python-juicenet==1.1.0
# homeassistant.components.tplink # homeassistant.components.tplink
python-kasa[speedups]==0.7.0.2 python-kasa[speedups]==0.7.0.3
# homeassistant.components.matter # homeassistant.components.matter
python-matter-server==6.2.2 python-matter-server==6.2.2
@ -2204,7 +2204,7 @@ vallox-websocket-api==5.3.0
vehicle==2.2.1 vehicle==2.2.1
# homeassistant.components.velbus # homeassistant.components.velbus
velbus-aio==2024.5.1 velbus-aio==2024.7.5
# homeassistant.components.venstar # homeassistant.components.venstar
venstarcolortouch==0.19 venstarcolortouch==0.19
@ -2301,7 +2301,7 @@ yeelight==0.7.14
yolink-api==0.4.4 yolink-api==0.4.4
# homeassistant.components.youless # homeassistant.components.youless
youless-api==2.1.0 youless-api==2.1.2
# homeassistant.components.youtube # homeassistant.components.youtube
youtubeaio==1.1.5 youtubeaio==1.1.5

View File

@ -7757,6 +7757,141 @@
"serializedGlobalTradeItemNumber": "3014F711000000000ESIIEC2", "serializedGlobalTradeItemNumber": "3014F711000000000ESIIEC2",
"type": "ENERGY_SENSORS_INTERFACE", "type": "ENERGY_SENSORS_INTERFACE",
"updateState": "UP_TO_DATE" "updateState": "UP_TO_DATE"
},
"3014F7110000000000ESIIE3": {
"availableFirmwareVersion": "0.0.0",
"connectionType": "HMIP_RF",
"deviceArchetype": "HMIP",
"firmwareVersion": "1.0.6",
"firmwareVersionInteger": 65542,
"functionalChannels": {
"0": {
"busConfigMismatch": null,
"coProFaulty": false,
"coProRestartNeeded": false,
"coProUpdateFailure": false,
"configPending": false,
"controlsMountingOrientation": null,
"daliBusState": null,
"defaultLinkedGroup": [],
"deviceCommunicationError": null,
"deviceDriveError": null,
"deviceDriveModeError": null,
"deviceId": "3014F7110000000000ESIIE3",
"deviceOperationMode": null,
"deviceOverheated": false,
"deviceOverloaded": false,
"devicePowerFailureDetected": false,
"deviceUndervoltage": false,
"displayContrast": null,
"dutyCycle": false,
"functionalChannelType": "DEVICE_BASE",
"groupIndex": 0,
"groups": ["00000000-0000-0000-0000-000000000031"],
"index": 0,
"label": "",
"lockJammed": null,
"lowBat": false,
"mountingOrientation": null,
"multicastRoutingEnabled": false,
"particulateMatterSensorCommunicationError": null,
"particulateMatterSensorError": null,
"powerShortCircuit": null,
"profilePeriodLimitReached": null,
"routerModuleEnabled": false,
"routerModuleSupported": false,
"rssiDeviceValue": -94,
"rssiPeerValue": null,
"sensorCommunicationError": false,
"sensorError": true,
"shortCircuitDataLine": null,
"supportedOptionalFeatures": {
"IFeatureBusConfigMismatch": false,
"IFeatureDeviceCoProError": false,
"IFeatureDeviceCoProRestart": false,
"IFeatureDeviceCoProUpdate": false,
"IFeatureDeviceCommunicationError": false,
"IFeatureDeviceDaliBusError": false,
"IFeatureDeviceDriveError": false,
"IFeatureDeviceDriveModeError": false,
"IFeatureDeviceIdentify": false,
"IFeatureDeviceOverheated": false,
"IFeatureDeviceOverloaded": false,
"IFeatureDeviceParticulateMatterSensorCommunicationError": false,
"IFeatureDeviceParticulateMatterSensorError": false,
"IFeatureDevicePowerFailure": false,
"IFeatureDeviceSensorCommunicationError": true,
"IFeatureDeviceSensorError": true,
"IFeatureDeviceTemperatureHumiditySensorCommunicationError": false,
"IFeatureDeviceTemperatureHumiditySensorError": false,
"IFeatureDeviceTemperatureOutOfRange": false,
"IFeatureDeviceUndervoltage": false,
"IFeatureMulticastRouter": false,
"IFeaturePowerShortCircuit": false,
"IFeatureProfilePeriodLimit": false,
"IFeatureRssiValue": true,
"IFeatureShortCircuitDataLine": false,
"IOptionalFeatureDefaultLinkedGroup": false,
"IOptionalFeatureDeviceErrorLockJammed": false,
"IOptionalFeatureDeviceOperationMode": false,
"IOptionalFeatureDisplayContrast": false,
"IOptionalFeatureDutyCycle": true,
"IOptionalFeatureLowBat": true,
"IOptionalFeatureMountingOrientation": false
},
"temperatureHumiditySensorCommunicationError": null,
"temperatureHumiditySensorError": null,
"temperatureOutOfRange": false,
"unreach": false
},
"1": {
"channelRole": "ENERGY_SENSOR",
"connectedEnergySensorType": "ES_LED",
"currentGasFlow": null,
"currentPowerConsumption": 189.15,
"deviceId": "3014F7110000000000ESIIE3",
"energyCounterOne": 23825.748,
"energyCounterOneType": "UNKNOWN",
"energyCounterThree": null,
"energyCounterThreeType": "UNKNOWN",
"energyCounterTwo": null,
"energyCounterTwoType": "UNKNOWN",
"functionalChannelType": "ENERGY_SENSORS_INTERFACE_CHANNEL",
"gasVolume": null,
"gasVolumePerImpulse": 0.01,
"groupIndex": 1,
"groups": ["00000000-0000-0000-0000-000000000057"],
"impulsesPerKWH": 1000,
"index": 1,
"label": "",
"supportedOptionalFeatures": {
"IOptionalFeatureCounterOffset": true,
"IOptionalFeatureCurrentGasFlow": false,
"IOptionalFeatureCurrentPowerConsumption": true,
"IOptionalFeatureEnergyCounterOne": true,
"IOptionalFeatureEnergyCounterThree": false,
"IOptionalFeatureEnergyCounterTwo": false,
"IOptionalFeatureGasVolume": false,
"IOptionalFeatureGasVolumePerImpulse": false,
"IOptionalFeatureImpulsesPerKWH": true
}
}
},
"homeId": "00000000-0000-0000-0000-000000000001",
"id": "3014F7110000000000ESIIE3",
"label": "esi_led",
"lastStatusUpdate": 1702420986697,
"liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED",
"manuallyUpdateForced": false,
"manufacturerCode": 1,
"measuredAttributes": {},
"modelId": 509,
"modelType": "HmIP-ESI",
"oem": "eQ-3",
"permanentlyReachable": false,
"serializedGlobalTradeItemNumber": "3014F7110000000000ESIIE3",
"type": "ENERGY_SENSORS_INTERFACE",
"updateState": "UP_TO_DATE"
} }
}, },
"groups": { "groups": {

View File

@ -26,7 +26,7 @@ async def test_hmip_load_all_supported_devices(
test_devices=None, test_groups=None test_devices=None, test_groups=None
) )
assert len(mock_hap.hmip_device_by_entity_id) == 290 assert len(mock_hap.hmip_device_by_entity_id) == 293
async def test_hmip_remove_device( async def test_hmip_remove_device(

View File

@ -634,3 +634,39 @@ async def test_hmip_esi_gas_gas_volume(
) )
assert ha_state.state == "1019.26" assert ha_state.state == "1019.26"
async def test_hmip_esi_led_current_power_consumption(
hass: HomeAssistant, default_mock_hap_factory
) -> None:
"""Test ESI-IEC currentPowerConsumption Sensor."""
entity_id = "sensor.esi_led_currentPowerConsumption"
entity_name = "esi_led CurrentPowerConsumption"
device_model = "HmIP-ESI"
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
test_devices=["esi_led"]
)
ha_state, hmip_device = get_and_check_entity_basics(
hass, mock_hap, entity_id, entity_name, device_model
)
assert ha_state.state == "189.15"
async def test_hmip_esi_led_energy_counter_usage_high_tariff(
hass: HomeAssistant, default_mock_hap_factory
) -> None:
"""Test ESI-IEC ENERGY_COUNTER_USAGE_HIGH_TARIFF."""
entity_id = "sensor.esi_led_energy_counter_usage_high_tariff"
entity_name = "esi_led ENERGY_COUNTER_USAGE_HIGH_TARIFF"
device_model = "HmIP-ESI"
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
test_devices=["esi_led"]
)
ha_state, hmip_device = get_and_check_entity_basics(
hass, mock_hap, entity_id, entity_name, device_model
)
assert ha_state.state == "23825.748"

View File

@ -1059,6 +1059,7 @@
'Front lawn', 'Front lawn',
'Back lawn', 'Back lawn',
'my_lawn', 'my_lawn',
'no_work_area_active',
]), ]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
@ -1097,6 +1098,7 @@
'Front lawn', 'Front lawn',
'Back lawn', 'Back lawn',
'my_lawn', 'my_lawn',
'no_work_area_active',
]), ]),
}), }),
'context': <ANY>, 'context': <ANY>,

View File

@ -87,6 +87,38 @@ async def test_next_start_sensor(
assert state.state == STATE_UNKNOWN assert state.state == STATE_UNKNOWN
async def test_work_area_sensor(
hass: HomeAssistant,
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test the work area sensor."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get("sensor.test_mower_1_work_area")
assert state is not None
assert state.state == "Front lawn"
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
values[TEST_MOWER_ID].mower.work_area_id = None
mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_mower_1_work_area")
assert state.state == "no_work_area_active"
values[TEST_MOWER_ID].mower.work_area_id = 0
mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_mower_1_work_area")
assert state.state == "my_lawn"
@pytest.mark.parametrize( @pytest.mark.parametrize(
("sensor_to_test"), ("sensor_to_test"),
[ [

View File

@ -8,13 +8,11 @@ import pytest
from homeassistant.components.lock import ( from homeassistant.components.lock import (
STATE_LOCKED, STATE_LOCKED,
STATE_LOCKING,
STATE_OPEN, STATE_OPEN,
STATE_UNLOCKED, STATE_UNLOCKED,
STATE_UNLOCKING,
LockEntityFeature, LockEntityFeature,
) )
from homeassistant.const import ATTR_CODE, STATE_UNKNOWN from homeassistant.const import ATTR_CODE, STATE_LOCKING, STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
import homeassistant.helpers.entity_registry as er import homeassistant.helpers.entity_registry as er
@ -68,14 +66,14 @@ async def test_lock(
state = hass.states.get("lock.mock_door_lock_lock") state = hass.states.get("lock.mock_door_lock_lock")
assert state assert state
assert state.state == STATE_LOCKED assert state.state == STATE_LOCKING
set_node_attribute(door_lock, 1, 257, 0, 0) set_node_attribute(door_lock, 1, 257, 0, 0)
await trigger_subscription_callback(hass, matter_client) await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("lock.mock_door_lock_lock") state = hass.states.get("lock.mock_door_lock_lock")
assert state assert state
assert state.state == STATE_UNLOCKING assert state.state == STATE_UNLOCKED
set_node_attribute(door_lock, 1, 257, 0, 2) set_node_attribute(door_lock, 1, 257, 0, 2)
await trigger_subscription_callback(hass, matter_client) await trigger_subscription_callback(hass, matter_client)
@ -89,7 +87,7 @@ async def test_lock(
state = hass.states.get("lock.mock_door_lock_lock") state = hass.states.get("lock.mock_door_lock_lock")
assert state assert state
assert state.state == STATE_LOCKING assert state.state == STATE_UNLOCKED
set_node_attribute(door_lock, 1, 257, 0, None) set_node_attribute(door_lock, 1, 257, 0, None)
await trigger_subscription_callback(hass, matter_client) await trigger_subscription_callback(hass, matter_client)

View File

@ -228,7 +228,9 @@ MOCK_STATUS_RPC = {
"input:1": {"id": 1, "percent": 89, "xpercent": 8.9}, "input:1": {"id": 1, "percent": 89, "xpercent": 8.9},
"input:2": { "input:2": {
"id": 2, "id": 2,
"counts": {"total": 56174, "xtotal": 561.74, "freq": 208.00, "xfreq": 6.11}, "counts": {"total": 56174, "xtotal": 561.74},
"freq": 208.00,
"xfreq": 6.11,
}, },
"light:0": {"output": True, "brightness": 53.0}, "light:0": {"output": True, "brightness": 53.0},
"light:1": {"output": True, "brightness": 53.0}, "light:1": {"output": True, "brightness": 53.0},

View File

@ -828,3 +828,29 @@ async def test_rpc_pulse_counter_frequency_sensors(
entry = entity_registry.async_get(entity_id) entry = entity_registry.async_get(entity_id)
assert entry assert entry
assert entry.unique_id == "123456789ABC-input:2-counter_frequency_value" assert entry.unique_id == "123456789ABC-input:2-counter_frequency_value"
async def test_rpc_disabled_xfreq(
hass: HomeAssistant,
mock_rpc_device: Mock,
entity_registry: EntityRegistry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test RPC input with the xfreq sensor disabled."""
status = deepcopy(mock_rpc_device.status)
status["input:2"] = {
"id": 2,
"counts": {"total": 56174, "xtotal": 561.74},
"freq": 208.00,
}
monkeypatch.setattr(mock_rpc_device, "status", status)
await init_integration(hass, 2)
entity_id = f"{SENSOR_DOMAIN}.gas_pulse_counter_frequency_value"
state = hass.states.get(entity_id)
assert not state
entry = entity_registry.async_get(entity_id)
assert not entry

View File

@ -772,12 +772,15 @@ async def test_worker_log(
with patch("av.open") as av_open: with patch("av.open") as av_open:
# pylint: disable-next=c-extension-no-member # pylint: disable-next=c-extension-no-member
av_open.side_effect = av.error.InvalidDataError(-2, "error") av_open.side_effect = av.error.InvalidDataError(
code=-2, message="Invalid data", filename=stream_url
)
with pytest.raises(StreamWorkerError) as err: with pytest.raises(StreamWorkerError) as err:
run_worker(hass, stream, stream_url) run_worker(hass, stream, stream_url)
await hass.async_block_till_done() await hass.async_block_till_done()
assert ( assert (
str(err.value) == f"Error opening stream (ERRORTYPE_-2, error) {redacted_url}" str(err.value)
== f"Error opening stream (ERRORTYPE_-2, Invalid data, {redacted_url})"
) )
assert stream_url not in caplog.text assert stream_url not in caplog.text

View File

@ -210,7 +210,8 @@ def _mocked_device(
if modules: if modules:
device.modules = { device.modules = {
module_name: MODULE_TO_MOCK_GEN[module_name]() for module_name in modules module_name: MODULE_TO_MOCK_GEN[module_name](device)
for module_name in modules
} }
if features: if features:
@ -298,7 +299,7 @@ def _mocked_feature(
return feature return feature
def _mocked_light_module() -> Light: def _mocked_light_module(device) -> Light:
light = MagicMock(spec=Light, name="Mocked light module") light = MagicMock(spec=Light, name="Mocked light module")
light.update = AsyncMock() light.update = AsyncMock()
light.brightness = 50 light.brightness = 50
@ -314,26 +315,58 @@ def _mocked_light_module() -> Light:
light.hsv = (10, 30, 5) light.hsv = (10, 30, 5)
light.valid_temperature_range = ColorTempRange(min=4000, max=9000) light.valid_temperature_range = ColorTempRange(min=4000, max=9000)
light.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"} light.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
light.set_state = AsyncMock()
light.set_brightness = AsyncMock() async def _set_state(state, *_, **__):
light.set_hsv = AsyncMock() light.state = state
light.set_color_temp = AsyncMock()
light.set_state = AsyncMock(wraps=_set_state)
async def _set_brightness(brightness, *_, **__):
light.state.brightness = brightness
light.state.light_on = brightness > 0
light.set_brightness = AsyncMock(wraps=_set_brightness)
async def _set_hsv(h, s, v, *_, **__):
light.state.hue = h
light.state.saturation = s
light.state.brightness = v
light.state.light_on = True
light.set_hsv = AsyncMock(wraps=_set_hsv)
async def _set_color_temp(temp, *_, **__):
light.state.color_temp = temp
light.state.light_on = True
light.set_color_temp = AsyncMock(wraps=_set_color_temp)
light.protocol = _mock_protocol() light.protocol = _mock_protocol()
return light return light
def _mocked_light_effect_module() -> LightEffect: def _mocked_light_effect_module(device) -> LightEffect:
effect = MagicMock(spec=LightEffect, name="Mocked light effect") effect = MagicMock(spec=LightEffect, name="Mocked light effect")
effect.has_effects = True effect.has_effects = True
effect.has_custom_effects = True effect.has_custom_effects = True
effect.effect = "Effect1" effect.effect = "Effect1"
effect.effect_list = ["Off", "Effect1", "Effect2"] effect.effect_list = ["Off", "Effect1", "Effect2"]
effect.set_effect = AsyncMock()
async def _set_effect(effect_name, *_, **__):
assert (
effect_name in effect.effect_list
), f"set_effect '{effect_name}' not in {effect.effect_list}"
assert device.modules[
Module.Light
], "Need a light module to test set_effect method"
device.modules[Module.Light].state.light_on = True
effect.effect = effect_name
effect.set_effect = AsyncMock(wraps=_set_effect)
effect.set_custom_effect = AsyncMock() effect.set_custom_effect = AsyncMock()
return effect return effect
def _mocked_fan_module() -> Fan: def _mocked_fan_module(effect) -> Fan:
fan = MagicMock(auto_spec=Fan, name="Mocked fan") fan = MagicMock(auto_spec=Fan, name="Mocked fan")
fan.fan_speed_level = 0 fan.fan_speed_level = 0
fan.set_fan_speed_level = AsyncMock() fan.set_fan_speed_level = AsyncMock()

View File

@ -5,6 +5,7 @@ from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
from freezegun.api import FrozenDateTimeFactory
from kasa import ( from kasa import (
AuthenticationError, AuthenticationError,
DeviceType, DeviceType,
@ -36,7 +37,13 @@ from homeassistant.components.light import (
) )
from homeassistant.components.tplink.const import DOMAIN from homeassistant.components.tplink.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, STATE_OFF, STATE_ON from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -920,3 +927,82 @@ async def test_light_child(
assert child_entity assert child_entity
assert child_entity.unique_id == f"{DEVICE_ID}0{light_id}" assert child_entity.unique_id == f"{DEVICE_ID}0{light_id}"
assert child_entity.device_id == entity.device_id assert child_entity.device_id == entity.device_id
async def test_scene_effect_light(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test activating a scene works with effects.
i.e. doesn't try to set the effect to 'off'
"""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
device = _mocked_device(
modules=[Module.Light, Module.LightEffect], alias="my_light"
)
light_effect = device.modules[Module.LightEffect]
light_effect.effect = LightEffect.LIGHT_EFFECTS_OFF
with _patch_discovery(device=device), _patch_connect(device=device):
assert await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
assert await async_setup_component(hass, "scene", {})
await hass.async_block_till_done()
entity_id = "light.my_light"
await hass.services.async_call(
LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
)
await hass.async_block_till_done()
freezer.tick(5)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state is STATE_ON
assert state.attributes["effect"] is EFFECT_OFF
await hass.services.async_call(
"scene",
"create",
{"scene_id": "effect_off_scene", "snapshot_entities": [entity_id]},
blocking=True,
)
await hass.async_block_till_done()
scene_state = hass.states.get("scene.effect_off_scene")
assert scene_state.state is STATE_UNKNOWN
await hass.services.async_call(
LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
)
await hass.async_block_till_done()
freezer.tick(5)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state is STATE_OFF
await hass.services.async_call(
"scene",
"turn_on",
{
"entity_id": "scene.effect_off_scene",
},
blocking=True,
)
await hass.async_block_till_done()
scene_state = hass.states.get("scene.effect_off_scene")
assert scene_state.state is not STATE_UNKNOWN
freezer.tick(5)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state is STATE_ON
assert state.attributes["effect"] is EFFECT_OFF

View File

@ -832,3 +832,7 @@ async def test_update_media_state(hass: HomeAssistant, client, monkeypatch) -> N
monkeypatch.setattr(client, "media_state", data) monkeypatch.setattr(client, "media_state", data)
await client.mock_state_update() await client.mock_state_update()
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.IDLE assert hass.states.get(ENTITY_ID).state == MediaPlayerState.IDLE
monkeypatch.setattr(client, "is_on", False)
await client.mock_state_update()
assert hass.states.get(ENTITY_ID).state == STATE_OFF

View File

@ -47,7 +47,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': 'unavailable', 'state': '0.0',
}) })
# --- # ---
# name: test_sensors[sensor.energy_delivery_low-entry] # name: test_sensors[sensor.energy_delivery_low-entry]
@ -98,7 +98,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': 'unavailable', 'state': '0.029',
}) })
# --- # ---
# name: test_sensors[sensor.energy_high-entry] # name: test_sensors[sensor.energy_high-entry]
@ -405,7 +405,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': '1234.564', 'state': '1624.264',
}) })
# --- # ---
# name: test_sensors[sensor.phase_1_current-entry] # name: test_sensors[sensor.phase_1_current-entry]
@ -967,6 +967,6 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': 'unavailable', 'state': '1234.564',
}) })
# --- # ---