mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
2024.7.1 (#121289)
This commit is contained in:
commit
1cf62916a7
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/anova",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["anova_wifi"],
|
||||
"requirements": ["anova-wifi==0.14.0"]
|
||||
"requirements": ["anova-wifi==0.15.0"]
|
||||
}
|
||||
|
@ -8,5 +8,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioaquacell"],
|
||||
"requirements": ["aioaquacell==0.1.7"]
|
||||
"requirements": ["aioaquacell==0.1.8"]
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from path import Path
|
||||
from yalexs.exceptions import AugustApiAIOHTTPError
|
||||
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
||||
from yalexs.manager.gateway import Config as YaleXSConfig
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||
"iot_class": "cloud_push",
|
||||
"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"]
|
||||
}
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20240703.0"]
|
||||
"requirements": ["home-assistant-frontend==20240705.0"]
|
||||
}
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.51", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.52", "babel==2.15.0"]
|
||||
}
|
||||
|
@ -216,14 +216,13 @@ class HomematicipGenericEntity(Entity):
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
suffix = ""
|
||||
if self._post is not None:
|
||||
suffix = f"_{self._post}"
|
||||
|
||||
unique_id = f"{self.__class__.__name__}_{self._device.id}"
|
||||
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
|
||||
def icon(self) -> str | None:
|
||||
|
@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from homematicip.aio.device import (
|
||||
@ -36,7 +35,6 @@ from homematicip.base.functionalChannels import FunctionalChannel
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -163,20 +161,29 @@ async def async_setup_entry(
|
||||
for ch in get_channels_from_device(
|
||||
device, FunctionalChannelType.ENERGY_SENSORS_INTERFACE_CHANNEL
|
||||
):
|
||||
if ch.connectedEnergySensorType not in SENSORS_ESI:
|
||||
continue
|
||||
|
||||
new_entities = [
|
||||
HmipEsiSensorEntity(hap, device, ch.index, description)
|
||||
for description in SENSORS_ESI[ch.connectedEnergySensorType]
|
||||
]
|
||||
|
||||
entities.extend(
|
||||
entity
|
||||
for entity in new_entities
|
||||
if entity.entity_description.exists_fn(ch)
|
||||
if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_IEC:
|
||||
if ch.currentPowerConsumption is not None:
|
||||
entities.append(HmipEsiIecPowerConsumption(hap, device))
|
||||
if ch.energyCounterOneType != ESI_TYPE_UNKNOWN:
|
||||
entities.append(HmipEsiIecEnergyCounterHighTariff(hap, device))
|
||||
if ch.energyCounterTwoType != ESI_TYPE_UNKNOWN:
|
||||
entities.append(HmipEsiIecEnergyCounterLowTariff(hap, device))
|
||||
if ch.energyCounterThreeType != ESI_TYPE_UNKNOWN:
|
||||
entities.append(
|
||||
HmipEsiIecEnergyCounterInputSingleTariff(hap, device)
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@ -434,132 +441,185 @@ class HomematicpTemperatureExternalSensorDelta(HomematicipGenericEntity, SensorE
|
||||
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):
|
||||
"""EntityDescription for HmIP-ESI Sensors."""
|
||||
|
||||
entity_description: HmipEsiSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hap: HomematicipHAP,
|
||||
device: HomematicipGenericEntity,
|
||||
channel_index: int,
|
||||
entity_description: HmipEsiSensorEntityDescription,
|
||||
key: str,
|
||||
value_fn: Callable[[FunctionalChannel], StateType],
|
||||
type_fn: Callable[[FunctionalChannel], str],
|
||||
) -> None:
|
||||
"""Initialize Sensor Entity."""
|
||||
super().__init__(
|
||||
hap=hap,
|
||||
device=device,
|
||||
channel=channel_index,
|
||||
post=entity_description.key,
|
||||
channel=1,
|
||||
post=key,
|
||||
is_multi_channel=False,
|
||||
)
|
||||
self.entity_description = entity_description
|
||||
|
||||
self._value_fn = value_fn
|
||||
self._type_fn = type_fn
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of the esi sensor."""
|
||||
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
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
"""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):
|
||||
|
@ -184,6 +184,8 @@ RESTRICTED_REASONS: list = [
|
||||
RestrictedReasons.WEEK_SCHEDULE.lower(),
|
||||
]
|
||||
|
||||
STATE_NO_WORK_AREA_ACTIVE = "no_work_area_active"
|
||||
|
||||
|
||||
@callback
|
||||
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:
|
||||
# Sensor does not get created if it is 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
|
||||
def _get_current_work_area_name(data: MowerAttributes) -> str:
|
||||
"""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:
|
||||
# Sensor does not get created if values are 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
|
||||
|
||||
|
||||
|
@ -252,7 +252,8 @@
|
||||
"work_area": {
|
||||
"name": "Work area",
|
||||
"state": {
|
||||
"my_lawn": "My lawn"
|
||||
"my_lawn": "My lawn",
|
||||
"no_work_area_active": "No work area active"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -28,5 +28,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/inkbird",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["inkbird-ble==0.5.7"]
|
||||
"requirements": ["inkbird-ble==0.5.8"]
|
||||
}
|
||||
|
@ -350,6 +350,7 @@ DISCOVERY_SCHEMAS = [
|
||||
clusters.Thermostat.Attributes.TemperatureSetpointHold,
|
||||
clusters.Thermostat.Attributes.UnoccupiedCoolingSetpoint,
|
||||
clusters.Thermostat.Attributes.UnoccupiedHeatingSetpoint,
|
||||
clusters.OnOff.Attributes.OnOff,
|
||||
),
|
||||
device_type=(device_types.Thermostat, device_types.RoomAirConditioner),
|
||||
),
|
||||
|
@ -313,6 +313,7 @@ DISCOVERY_SCHEMAS = [
|
||||
clusters.FanControl.Attributes.RockSetting,
|
||||
clusters.FanControl.Attributes.WindSetting,
|
||||
clusters.FanControl.Attributes.AirflowDirection,
|
||||
clusters.OnOff.Attributes.OnOff,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@ -446,6 +446,8 @@ DISCOVERY_SCHEMAS = [
|
||||
device_types.DimmablePlugInUnit,
|
||||
device_types.ExtendedColorLight,
|
||||
device_types.OnOffLight,
|
||||
device_types.DimmerSwitch,
|
||||
device_types.ColorDimmerSwitch,
|
||||
),
|
||||
),
|
||||
# Additional schema to match (HS Color) lights with incorrect/missing device type
|
||||
|
@ -90,6 +90,9 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
|
||||
async def async_lock(self, **kwargs: Any) -> None:
|
||||
"""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_bytes = code.encode() if code else None
|
||||
await self.send_device_command(
|
||||
@ -98,6 +101,9 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
|
||||
async def async_unlock(self, **kwargs: Any) -> None:
|
||||
"""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_bytes = code.encode() if code else None
|
||||
if self.supports_unbolt:
|
||||
@ -114,6 +120,9 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
|
||||
async def async_open(self, **kwargs: Any) -> None:
|
||||
"""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_bytes = code.encode() if code else None
|
||||
await self.send_device_command(
|
||||
@ -135,26 +144,23 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
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)
|
||||
|
||||
if lock_state is clusters.DoorLock.Enums.DlLockState.kLocked:
|
||||
self._attr_is_locked = True
|
||||
self._attr_is_locking = False
|
||||
self._attr_is_unlocking = False
|
||||
elif lock_state is clusters.DoorLock.Enums.DlLockState.kUnlocked:
|
||||
elif lock_state in (
|
||||
clusters.DoorLock.Enums.DlLockState.kUnlocked,
|
||||
clusters.DoorLock.Enums.DlLockState.kUnlatched,
|
||||
clusters.DoorLock.Enums.DlLockState.kNotFullyLocked,
|
||||
):
|
||||
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:
|
||||
# According to the matter docs a null state can happen during device startup.
|
||||
self._attr_is_locked = None
|
||||
self._attr_is_locking = None
|
||||
self._attr_is_unlocking = None
|
||||
|
||||
if self.supports_door_position_sensor:
|
||||
door_state = self.get_matter_attribute_value(
|
||||
|
@ -114,6 +114,7 @@ DISCOVERY_SCHEMAS = [
|
||||
device_types.ColorTemperatureLight,
|
||||
device_types.DimmableLight,
|
||||
device_types.ExtendedColorLight,
|
||||
device_types.DimmerSwitch,
|
||||
device_types.ColorDimmerSwitch,
|
||||
device_types.OnOffLight,
|
||||
device_types.AirPurifier,
|
||||
|
@ -44,5 +44,95 @@
|
||||
"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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -960,14 +960,18 @@ RPC_SENSORS: Final = {
|
||||
name="Analog input",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
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(
|
||||
key="input",
|
||||
sub_key="xpercent",
|
||||
name="Analog value",
|
||||
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(
|
||||
@ -977,7 +981,9 @@ RPC_SENSORS: Final = {
|
||||
native_unit_of_measurement="pulse",
|
||||
state_class=SensorStateClass.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(
|
||||
key="input",
|
||||
@ -985,26 +991,29 @@ RPC_SENSORS: Final = {
|
||||
name="Counter value",
|
||||
value=lambda status, _: status["xtotal"],
|
||||
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
|
||||
),
|
||||
),
|
||||
"counter_frequency": RpcSensorDescription(
|
||||
key="input",
|
||||
sub_key="counts",
|
||||
sub_key="freq",
|
||||
name="Pulse counter frequency",
|
||||
native_unit_of_measurement=UnitOfFrequency.HERTZ,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda status, _: status["freq"],
|
||||
removal_condition=lambda config, status, key: (config[key]["enable"] is False),
|
||||
removal_condition=lambda config, _, key: (
|
||||
config[key]["type"] != "count" or config[key]["enable"] is False
|
||||
),
|
||||
),
|
||||
"counter_frequency_value": RpcSensorDescription(
|
||||
key="input",
|
||||
sub_key="counts",
|
||||
sub_key="xfreq",
|
||||
name="Pulse counter frequency value",
|
||||
value=lambda status, _: status["xfreq"],
|
||||
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
|
||||
),
|
||||
),
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ from typing import Any
|
||||
from starline import StarlineApi, StarlineDevice
|
||||
|
||||
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.event import async_track_time_interval
|
||||
from homeassistant.util import dt as dt_util
|
||||
@ -65,9 +65,9 @@ class StarlineAccount:
|
||||
)
|
||||
self._api.set_slnet_token(slnet_token)
|
||||
self._api.set_user_id(user_id)
|
||||
self._hass.config_entries.async_update_entry(
|
||||
self._config_entry,
|
||||
data={
|
||||
self._hass.add_job(
|
||||
self._save_slnet_token,
|
||||
{
|
||||
**self._config_entry.data,
|
||||
DATA_SLNET_TOKEN: slnet_token,
|
||||
DATA_EXPIRES: slnet_token_expires,
|
||||
@ -77,6 +77,13 @@ class StarlineAccount:
|
||||
except Exception as err: # noqa: BLE001
|
||||
_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):
|
||||
"""Update StarLine data."""
|
||||
self._check_slnet_token(self._update_interval)
|
||||
|
@ -48,6 +48,14 @@ class StreamWorkerError(Exception):
|
||||
"""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):
|
||||
"""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)
|
||||
except av.AVError as err:
|
||||
raise StreamWorkerError(
|
||||
f"Error opening stream ({err.type}, {err.strerror})"
|
||||
f" {redact_credentials(str(source))}"
|
||||
f"Error opening stream ({redact_av_error_string(err)})"
|
||||
) from err
|
||||
try:
|
||||
video_stream = container.streams.video[0]
|
||||
@ -593,7 +600,7 @@ def stream_worker(
|
||||
except av.AVError as ex:
|
||||
container.close()
|
||||
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
|
||||
|
||||
muxer = StreamMuxer(
|
||||
@ -618,7 +625,9 @@ def stream_worker(
|
||||
except StopIteration as ex:
|
||||
raise StreamEndedError("Stream ended; no additional packets") from 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)
|
||||
|
||||
|
@ -382,7 +382,12 @@ class TPLinkLightEffectEntity(TPLinkLightEntity):
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the light on."""
|
||||
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(
|
||||
kwargs[ATTR_EFFECT], brightness=brightness, transition=transition
|
||||
)
|
||||
|
@ -297,5 +297,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["kasa"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["python-kasa[speedups]==0.7.0.2"]
|
||||
"requirements": ["python-kasa[speedups]==0.7.0.3"]
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
"velbus-packet",
|
||||
"velbus-protocol"
|
||||
],
|
||||
"requirements": ["velbus-aio==2024.5.1"],
|
||||
"requirements": ["velbus-aio==2024.7.5"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10CF",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiowebostv"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiowebostv==0.4.1"],
|
||||
"requirements": ["aiowebostv==0.4.2"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "urn:lge-com:service:webos-second-screen:1"
|
||||
|
@ -239,7 +239,8 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
|
||||
|
||||
self._attr_assumed_state = True
|
||||
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
|
||||
):
|
||||
self._attr_assumed_state = False
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.51"]
|
||||
"requirements": ["holidays==0.52"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/youless",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["youless_api"],
|
||||
"requirements": ["youless-api==2.1.0"]
|
||||
"requirements": ["youless-api==2.1.2"]
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2024
|
||||
MINOR_VERSION: Final = 7
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||
|
@ -32,7 +32,7 @@ habluetooth==3.1.3
|
||||
hass-nabucasa==0.81.1
|
||||
hassil==1.7.1
|
||||
home-assistant-bluetooth==1.12.2
|
||||
home-assistant-frontend==20240703.0
|
||||
home-assistant-frontend==20240705.0
|
||||
home-assistant-intents==2024.7.3
|
||||
httpx==0.27.0
|
||||
ifaddr==0.2.0
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2024.7.0"
|
||||
version = "2024.7.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -195,7 +195,7 @@ aioambient==2024.01.0
|
||||
aioapcaccess==0.4.2
|
||||
|
||||
# homeassistant.components.aquacell
|
||||
aioaquacell==0.1.7
|
||||
aioaquacell==0.1.8
|
||||
|
||||
# homeassistant.components.aseko_pool_live
|
||||
aioaseko==0.1.1
|
||||
@ -404,7 +404,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.4.1
|
||||
aiowebostv==0.4.2
|
||||
|
||||
# homeassistant.components.withings
|
||||
aiowithings==3.0.2
|
||||
@ -449,7 +449,7 @@ androidtvremote2==0.1.1
|
||||
anel-pwrctrl-homeassistant==0.0.1.dev2
|
||||
|
||||
# homeassistant.components.anova
|
||||
anova-wifi==0.14.0
|
||||
anova-wifi==0.15.0
|
||||
|
||||
# homeassistant.components.anthemav
|
||||
anthemav==1.4.1
|
||||
@ -709,7 +709,7 @@ debugpy==1.8.1
|
||||
# decora==0.6
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==8.1.0
|
||||
deebot-client==8.1.1
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@ -1087,10 +1087,10 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.51
|
||||
holidays==0.52
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240703.0
|
||||
home-assistant-frontend==20240705.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.7.3
|
||||
@ -1161,7 +1161,7 @@ influxdb-client==1.24.0
|
||||
influxdb==5.3.1
|
||||
|
||||
# homeassistant.components.inkbird
|
||||
inkbird-ble==0.5.7
|
||||
inkbird-ble==0.5.8
|
||||
|
||||
# homeassistant.components.insteon
|
||||
insteon-frontend-home-assistant==0.5.0
|
||||
@ -2275,7 +2275,7 @@ python-join-api==0.0.9
|
||||
python-juicenet==1.1.0
|
||||
|
||||
# homeassistant.components.tplink
|
||||
python-kasa[speedups]==0.7.0.2
|
||||
python-kasa[speedups]==0.7.0.3
|
||||
|
||||
# homeassistant.components.lirc
|
||||
# python-lirc==1.2.3
|
||||
@ -2830,7 +2830,7 @@ vallox-websocket-api==5.3.0
|
||||
vehicle==2.2.1
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2024.5.1
|
||||
velbus-aio==2024.7.5
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.19
|
||||
@ -2945,7 +2945,7 @@ yeelightsunflower==0.0.10
|
||||
yolink-api==0.4.4
|
||||
|
||||
# homeassistant.components.youless
|
||||
youless-api==2.1.0
|
||||
youless-api==2.1.2
|
||||
|
||||
# homeassistant.components.youtube
|
||||
youtubeaio==1.1.5
|
||||
|
@ -174,7 +174,7 @@ aioambient==2024.01.0
|
||||
aioapcaccess==0.4.2
|
||||
|
||||
# homeassistant.components.aquacell
|
||||
aioaquacell==0.1.7
|
||||
aioaquacell==0.1.8
|
||||
|
||||
# homeassistant.components.aseko_pool_live
|
||||
aioaseko==0.1.1
|
||||
@ -377,7 +377,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.4.1
|
||||
aiowebostv==0.4.2
|
||||
|
||||
# homeassistant.components.withings
|
||||
aiowithings==3.0.2
|
||||
@ -413,7 +413,7 @@ androidtv[async]==0.0.73
|
||||
androidtvremote2==0.1.1
|
||||
|
||||
# homeassistant.components.anova
|
||||
anova-wifi==0.14.0
|
||||
anova-wifi==0.15.0
|
||||
|
||||
# homeassistant.components.anthemav
|
||||
anthemav==1.4.1
|
||||
@ -590,7 +590,7 @@ dbus-fast==2.22.1
|
||||
debugpy==1.8.1
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==8.1.0
|
||||
deebot-client==8.1.1
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@ -892,10 +892,10 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.51
|
||||
holidays==0.52
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240703.0
|
||||
home-assistant-frontend==20240705.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.7.3
|
||||
@ -951,7 +951,7 @@ influxdb-client==1.24.0
|
||||
influxdb==5.3.1
|
||||
|
||||
# homeassistant.components.inkbird
|
||||
inkbird-ble==0.5.7
|
||||
inkbird-ble==0.5.8
|
||||
|
||||
# homeassistant.components.insteon
|
||||
insteon-frontend-home-assistant==0.5.0
|
||||
@ -1775,7 +1775,7 @@ python-izone==1.2.9
|
||||
python-juicenet==1.1.0
|
||||
|
||||
# homeassistant.components.tplink
|
||||
python-kasa[speedups]==0.7.0.2
|
||||
python-kasa[speedups]==0.7.0.3
|
||||
|
||||
# homeassistant.components.matter
|
||||
python-matter-server==6.2.2
|
||||
@ -2204,7 +2204,7 @@ vallox-websocket-api==5.3.0
|
||||
vehicle==2.2.1
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2024.5.1
|
||||
velbus-aio==2024.7.5
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.19
|
||||
@ -2301,7 +2301,7 @@ yeelight==0.7.14
|
||||
yolink-api==0.4.4
|
||||
|
||||
# homeassistant.components.youless
|
||||
youless-api==2.1.0
|
||||
youless-api==2.1.2
|
||||
|
||||
# homeassistant.components.youtube
|
||||
youtubeaio==1.1.5
|
||||
|
@ -7757,6 +7757,141 @@
|
||||
"serializedGlobalTradeItemNumber": "3014F711000000000ESIIEC2",
|
||||
"type": "ENERGY_SENSORS_INTERFACE",
|
||||
"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": {
|
||||
|
@ -26,7 +26,7 @@ async def test_hmip_load_all_supported_devices(
|
||||
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(
|
||||
|
@ -634,3 +634,39 @@ async def test_hmip_esi_gas_gas_volume(
|
||||
)
|
||||
|
||||
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"
|
||||
|
@ -1059,6 +1059,7 @@
|
||||
'Front lawn',
|
||||
'Back lawn',
|
||||
'my_lawn',
|
||||
'no_work_area_active',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@ -1097,6 +1098,7 @@
|
||||
'Front lawn',
|
||||
'Back lawn',
|
||||
'my_lawn',
|
||||
'no_work_area_active',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
@ -87,6 +87,38 @@ async def test_next_start_sensor(
|
||||
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(
|
||||
("sensor_to_test"),
|
||||
[
|
||||
|
@ -8,13 +8,11 @@ import pytest
|
||||
|
||||
from homeassistant.components.lock import (
|
||||
STATE_LOCKED,
|
||||
STATE_LOCKING,
|
||||
STATE_OPEN,
|
||||
STATE_UNLOCKED,
|
||||
STATE_UNLOCKING,
|
||||
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.exceptions import ServiceValidationError
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
@ -68,14 +66,14 @@ async def test_lock(
|
||||
|
||||
state = hass.states.get("lock.mock_door_lock_lock")
|
||||
assert state
|
||||
assert state.state == STATE_LOCKED
|
||||
assert state.state == STATE_LOCKING
|
||||
|
||||
set_node_attribute(door_lock, 1, 257, 0, 0)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("lock.mock_door_lock_lock")
|
||||
assert state
|
||||
assert state.state == STATE_UNLOCKING
|
||||
assert state.state == STATE_UNLOCKED
|
||||
|
||||
set_node_attribute(door_lock, 1, 257, 0, 2)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
@ -89,7 +87,7 @@ async def test_lock(
|
||||
|
||||
state = hass.states.get("lock.mock_door_lock_lock")
|
||||
assert state
|
||||
assert state.state == STATE_LOCKING
|
||||
assert state.state == STATE_UNLOCKED
|
||||
|
||||
set_node_attribute(door_lock, 1, 257, 0, None)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
@ -228,7 +228,9 @@ MOCK_STATUS_RPC = {
|
||||
"input:1": {"id": 1, "percent": 89, "xpercent": 8.9},
|
||||
"input: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:1": {"output": True, "brightness": 53.0},
|
||||
|
@ -828,3 +828,29 @@ async def test_rpc_pulse_counter_frequency_sensors(
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry
|
||||
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
|
||||
|
@ -772,12 +772,15 @@ async def test_worker_log(
|
||||
|
||||
with patch("av.open") as av_open:
|
||||
# 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:
|
||||
run_worker(hass, stream, stream_url)
|
||||
await hass.async_block_till_done()
|
||||
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
|
||||
|
||||
|
@ -210,7 +210,8 @@ def _mocked_device(
|
||||
|
||||
if 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:
|
||||
@ -298,7 +299,7 @@ def _mocked_feature(
|
||||
return feature
|
||||
|
||||
|
||||
def _mocked_light_module() -> Light:
|
||||
def _mocked_light_module(device) -> Light:
|
||||
light = MagicMock(spec=Light, name="Mocked light module")
|
||||
light.update = AsyncMock()
|
||||
light.brightness = 50
|
||||
@ -314,26 +315,58 @@ def _mocked_light_module() -> Light:
|
||||
light.hsv = (10, 30, 5)
|
||||
light.valid_temperature_range = ColorTempRange(min=4000, max=9000)
|
||||
light.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
|
||||
light.set_state = AsyncMock()
|
||||
light.set_brightness = AsyncMock()
|
||||
light.set_hsv = AsyncMock()
|
||||
light.set_color_temp = AsyncMock()
|
||||
|
||||
async def _set_state(state, *_, **__):
|
||||
light.state = state
|
||||
|
||||
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()
|
||||
return light
|
||||
|
||||
|
||||
def _mocked_light_effect_module() -> LightEffect:
|
||||
def _mocked_light_effect_module(device) -> LightEffect:
|
||||
effect = MagicMock(spec=LightEffect, name="Mocked light effect")
|
||||
effect.has_effects = True
|
||||
effect.has_custom_effects = True
|
||||
effect.effect = "Effect1"
|
||||
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()
|
||||
return effect
|
||||
|
||||
|
||||
def _mocked_fan_module() -> Fan:
|
||||
def _mocked_fan_module(effect) -> Fan:
|
||||
fan = MagicMock(auto_spec=Fan, name="Mocked fan")
|
||||
fan.fan_speed_level = 0
|
||||
fan.set_fan_speed_level = AsyncMock()
|
||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from kasa import (
|
||||
AuthenticationError,
|
||||
DeviceType,
|
||||
@ -36,7 +37,13 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.components.tplink.const import DOMAIN
|
||||
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.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@ -920,3 +927,82 @@ async def test_light_child(
|
||||
assert child_entity
|
||||
assert child_entity.unique_id == f"{DEVICE_ID}0{light_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
|
||||
|
@ -832,3 +832,7 @@ async def test_update_media_state(hass: HomeAssistant, client, monkeypatch) -> N
|
||||
monkeypatch.setattr(client, "media_state", data)
|
||||
await client.mock_state_update()
|
||||
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
|
||||
|
@ -47,7 +47,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unavailable',
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_delivery_low-entry]
|
||||
@ -98,7 +98,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unavailable',
|
||||
'state': '0.029',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_high-entry]
|
||||
@ -405,7 +405,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1234.564',
|
||||
'state': '1624.264',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.phase_1_current-entry]
|
||||
@ -967,6 +967,6 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unavailable',
|
||||
'state': '1234.564',
|
||||
})
|
||||
# ---
|
||||
|
Loading…
x
Reference in New Issue
Block a user