This commit is contained in:
Paulus Schoutsen 2023-02-15 15:30:55 -05:00 committed by GitHub
commit a7e42798da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 259 additions and 83 deletions

View file

@ -2,7 +2,7 @@
"domain": "aladdin_connect",
"name": "Aladdin Connect",
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
"requirements": ["AIOAladdinConnect==0.1.55"],
"requirements": ["AIOAladdinConnect==0.1.56"],
"codeowners": ["@mkmer"],
"iot_class": "cloud_polling",
"loggers": ["aladdin_connect"],

View file

@ -22,6 +22,7 @@ from .const import DOMAIN, PRODUCT
SCAN_INTERVAL = timedelta(seconds=5)
BLEBOX_TO_HVACMODE = {
None: None,
0: HVACMode.OFF,
1: HVACMode.HEAT,
2: HVACMode.COOL,
@ -58,13 +59,15 @@ class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEn
@property
def hvac_modes(self):
"""Return list of supported HVAC modes."""
return [HVACMode.OFF, self.hvac_mode]
return [HVACMode.OFF, BLEBOX_TO_HVACMODE[self._feature.mode]]
@property
def hvac_mode(self):
"""Return the desired HVAC mode."""
if self._feature.is_on is None:
return None
if not self._feature.is_on:
return HVACMode.OFF
if self._feature.mode is not None:
return BLEBOX_TO_HVACMODE[self._feature.mode]
return HVACMode.HEAT if self._feature.is_on else HVACMode.OFF

View file

@ -3,7 +3,7 @@
"name": "Matter (BETA)",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/matter",
"requirements": ["python-matter-server==2.0.2"],
"requirements": ["python-matter-server==2.1.0"],
"dependencies": ["websocket_api"],
"codeowners": ["@home-assistant/matter"],
"iot_class": "local_push"

View file

@ -3,7 +3,7 @@
"name": "OctoPrint",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/octoprint",
"requirements": ["pyoctoprintapi==0.1.9"],
"requirements": ["pyoctoprintapi==0.1.11"],
"codeowners": ["@rfleming71"],
"zeroconf": ["_octoprint._tcp.local."],
"ssdp": [

View file

@ -15,7 +15,7 @@ An overview of the areas and the devices in this smart home:
{{ area.name }}:
{%- set area_info.printed = true %}
{%- endif %}
- {{ device_attr(device, "name") }}{% if device_attr(device, "model") and device_attr(device, "model") not in device_attr(device, "name") %} ({{ device_attr(device, "model") }}){% endif %}
- {{ device_attr(device, "name") }}{% if device_attr(device, "model") and (device_attr(device, "model") | string) not in (device_attr(device, "name") | string) %} ({{ device_attr(device, "model") }}){% endif %}
{%- endif %}
{%- endfor %}
{%- endfor %}

View file

@ -6,7 +6,7 @@ from typing import Any, cast
from pyopenuv.errors import InvalidApiKeyError, OpenUvError
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.debounce import Debouncer
@ -60,14 +60,4 @@ class OpenUvCoordinator(DataUpdateCoordinator):
except OpenUvError as err:
raise UpdateFailed(str(err)) from err
# OpenUV uses HTTP 403 to indicate both an invalid API key and an API key that
# has hit its daily/monthly limit; both cases will result in a reauth flow. If
# coordinator update succeeds after a reauth flow has been started, terminate
# it:
if reauth_flow := next(
iter(self._entry.async_get_active_flows(self.hass, {SOURCE_REAUTH})),
None,
):
self.hass.config_entries.flow.async_abort(reauth_flow["flow_id"])
return cast(dict[str, Any], data["result"])

View file

@ -3,7 +3,7 @@
"name": "OpenUV",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/openuv",
"requirements": ["pyopenuv==2023.01.0"],
"requirements": ["pyopenuv==2023.02.0"],
"codeowners": ["@bachya"],
"iot_class": "cloud_polling",
"loggers": ["pyopenuv"],

View file

@ -3,7 +3,7 @@
"name": "Reolink IP NVR/camera",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/reolink",
"requirements": ["reolink-aio==0.4.0"],
"requirements": ["reolink-aio==0.4.2"],
"dependencies": ["webhook"],
"codeowners": ["@starkillerOG"],
"iot_class": "local_polling",

View file

@ -587,7 +587,7 @@ class SensorEntity(Entity):
numerical_value: int | float | Decimal
if not isinstance(value, (int, float, Decimal)):
try:
if isinstance(value, str) and "." not in value:
if isinstance(value, str) and "." not in value and "e" not in value:
numerical_value = int(value)
else:
numerical_value = float(value) # type:ignore[arg-type]

View file

@ -5,15 +5,17 @@ from collections import deque
from collections.abc import Callable
import contextlib
from datetime import datetime, timedelta
from enum import Enum
import logging
import statistics
from typing import Any, Literal, cast
from typing import Any, Literal, TypeVar, cast
import voluptuous as vol
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.recorder import get_instance, history
from homeassistant.components.sensor import (
from homeassistant.components.sensor import ( # pylint: disable=hass-deprecated-import
DEVICE_CLASS_STATE_CLASSES,
PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
@ -144,7 +146,7 @@ STATS_DATETIME = {
}
# Statistics which retain the unit of the source entity
STAT_NUMERIC_RETAIN_UNIT = {
STATS_NUMERIC_RETAIN_UNIT = {
STAT_AVERAGE_LINEAR,
STAT_AVERAGE_STEP,
STAT_AVERAGE_TIMELESS,
@ -166,7 +168,7 @@ STAT_NUMERIC_RETAIN_UNIT = {
}
# Statistics which produce percentage ratio from binary_sensor source entity
STAT_BINARY_PERCENTAGE = {
STATS_BINARY_PERCENTAGE = {
STAT_AVERAGE_STEP,
STAT_AVERAGE_TIMELESS,
STAT_MEAN,
@ -296,15 +298,9 @@ class StatisticsSensor(SensorEntity):
self.ages: deque[datetime] = deque(maxlen=self._samples_max_buffer_size)
self.attributes: dict[str, StateType] = {}
self._state_characteristic_fn: Callable[[], StateType | datetime]
if self.is_binary:
self._state_characteristic_fn = getattr(
self, f"_stat_binary_{self._state_characteristic}"
)
else:
self._state_characteristic_fn = getattr(
self, f"_stat_{self._state_characteristic}"
)
self._state_characteristic_fn: Callable[
[], StateType | datetime
] = self._callable_characteristic_fn(self._state_characteristic)
self._update_listener: CALLBACK_TYPE | None = None
@ -368,11 +364,11 @@ class StatisticsSensor(SensorEntity):
def _derive_unit_of_measurement(self, new_state: State) -> str | None:
base_unit: str | None = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
unit: str | None
if self.is_binary and self._state_characteristic in STAT_BINARY_PERCENTAGE:
if self.is_binary and self._state_characteristic in STATS_BINARY_PERCENTAGE:
unit = PERCENTAGE
elif not base_unit:
unit = None
elif self._state_characteristic in STAT_NUMERIC_RETAIN_UNIT:
elif self._state_characteristic in STATS_NUMERIC_RETAIN_UNIT:
unit = base_unit
elif self._state_characteristic in STATS_NOT_A_NUMBER:
unit = None
@ -393,11 +389,24 @@ class StatisticsSensor(SensorEntity):
@property
def device_class(self) -> SensorDeviceClass | None:
"""Return the class of this device."""
if self._state_characteristic in STAT_NUMERIC_RETAIN_UNIT:
_state = self.hass.states.get(self._source_entity_id)
return None if _state is None else _state.attributes.get(ATTR_DEVICE_CLASS)
if self._state_characteristic in STATS_DATETIME:
return SensorDeviceClass.TIMESTAMP
if self._state_characteristic in STATS_NUMERIC_RETAIN_UNIT:
source_state = self.hass.states.get(self._source_entity_id)
if source_state is None:
return None
source_device_class = source_state.attributes.get(ATTR_DEVICE_CLASS)
if source_device_class is None:
return None
sensor_device_class = try_parse_enum(SensorDeviceClass, source_device_class)
if sensor_device_class is None:
return None
sensor_state_classes = DEVICE_CLASS_STATE_CLASSES.get(
sensor_device_class, set()
)
if SensorStateClass.MEASUREMENT not in sensor_state_classes:
return None
return sensor_device_class
return None
@property
@ -472,8 +481,8 @@ class StatisticsSensor(SensorEntity):
if timestamp := self._next_to_purge_timestamp():
_LOGGER.debug("%s: scheduling update at %s", self.entity_id, timestamp)
if self._update_listener:
self._update_listener()
self._update_listener = None
self._update_listener() # pragma: no cover
self._update_listener = None # pragma: no cover
@callback
def _scheduled_update(now: datetime) -> None:
@ -563,6 +572,18 @@ class StatisticsSensor(SensorEntity):
value = int(value)
self._value = value
def _callable_characteristic_fn(
self, characteristic: str
) -> Callable[[], StateType | datetime]:
"""Return the function callable of one characteristic function."""
function: Callable[[], StateType | datetime] = getattr(
self,
f"_stat_binary_{characteristic}"
if self.is_binary
else f"_stat_{characteristic}",
)
return function
# Statistics for numeric sensor
def _stat_average_linear(self) -> StateType:
@ -748,3 +769,16 @@ class StatisticsSensor(SensorEntity):
if len(self.states) > 0:
return 100.0 / len(self.states) * self.states.count(True)
return None
_EnumT = TypeVar("_EnumT", bound=Enum)
def try_parse_enum(cls: type[_EnumT], value: Any) -> _EnumT | None:
"""Try to parse the value into an Enum.
Return None if parsing fails.
"""
with contextlib.suppress(ValueError):
return cls(value)
return None

View file

@ -3,7 +3,7 @@
"domain": "tibber",
"name": "Tibber",
"documentation": "https://www.home-assistant.io/integrations/tibber",
"requirements": ["pyTibber==0.26.12"],
"requirements": ["pyTibber==0.26.13"],
"codeowners": ["@danielhiversen"],
"quality_scale": "silver",
"config_flow": true,

View file

@ -119,11 +119,12 @@ SENSORS: tuple[WhirlpoolSensorEntityDescription, ...] = (
key="DispenseLevel",
name="Detergent Level",
translation_key="whirlpool_tank",
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.ENUM,
options=list(TANK_FILL.values()),
value_fn=lambda WasherDryer: TANK_FILL[
value_fn=lambda WasherDryer: TANK_FILL.get(
WasherDryer.get_attribute("WashCavity_OpStatusBulkDispense1Level")
],
),
),
)
@ -265,6 +266,7 @@ class WasherDryerTimeClass(RestoreSensor):
async def async_will_remove_from_hass(self) -> None:
"""Close Whrilpool Appliance sockets before removing."""
self._wd.unregister_attr_callback(self.update_from_latest_data)
await self._wd.disconnect()
@property

View file

@ -8,7 +8,7 @@ from .backports.enum import StrEnum
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2023
MINOR_VERSION: Final = 2
PATCH_VERSION: Final = "4"
PATCH_VERSION: Final = "5"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)

View file

@ -30,7 +30,7 @@ ifaddr==0.1.7
janus==1.0.0
jinja2==3.1.2
lru-dict==1.1.8
orjson==3.8.5
orjson==3.8.6
paho-mqtt==1.6.1
pillow==9.4.0
pip>=21.0,<22.4

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2023.2.4"
version = "2023.2.5"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"
@ -44,7 +44,7 @@ dependencies = [
"cryptography==39.0.1",
# pyOpenSSL 23.0.0 is required to work with cryptography 39+
"pyOpenSSL==23.0.0",
"orjson==3.8.5",
"orjson==3.8.6",
"pip>=21.0,<22.4",
"python-slugify==4.0.1",
"pyyaml==6.0",

View file

@ -18,7 +18,7 @@ lru-dict==1.1.8
PyJWT==2.5.0
cryptography==39.0.1
pyOpenSSL==23.0.0
orjson==3.8.5
orjson==3.8.6
pip>=21.0,<22.4
python-slugify==4.0.1
pyyaml==6.0

View file

@ -5,7 +5,7 @@
AEMET-OpenData==0.2.1
# homeassistant.components.aladdin_connect
AIOAladdinConnect==0.1.55
AIOAladdinConnect==0.1.56
# homeassistant.components.adax
Adax-local==0.1.5
@ -1470,7 +1470,7 @@ pyRFXtrx==0.30.0
pySwitchmate==0.5.1
# homeassistant.components.tibber
pyTibber==0.26.12
pyTibber==0.26.13
# homeassistant.components.dlink
pyW215==0.7.0
@ -1828,13 +1828,13 @@ pynzbgetapi==0.2.0
pyobihai==1.3.2
# homeassistant.components.octoprint
pyoctoprintapi==0.1.9
pyoctoprintapi==0.1.11
# homeassistant.components.ombi
pyombi==0.1.10
# homeassistant.components.openuv
pyopenuv==2023.01.0
pyopenuv==2023.02.0
# homeassistant.components.opnsense
pyopnsense==0.2.0
@ -2072,7 +2072,7 @@ python-kasa==0.5.0
# python-lirc==1.2.3
# homeassistant.components.matter
python-matter-server==2.0.2
python-matter-server==2.1.0
# homeassistant.components.xiaomi_miio
python-miio==0.5.12
@ -2227,7 +2227,7 @@ regenmaschine==2022.11.0
renault-api==0.1.11
# homeassistant.components.reolink
reolink-aio==0.4.0
reolink-aio==0.4.2
# homeassistant.components.python_script
restrictedpython==6.0

View file

@ -7,7 +7,7 @@
AEMET-OpenData==0.2.1
# homeassistant.components.aladdin_connect
AIOAladdinConnect==0.1.55
AIOAladdinConnect==0.1.56
# homeassistant.components.adax
Adax-local==0.1.5
@ -1073,7 +1073,7 @@ pyMetno==0.9.0
pyRFXtrx==0.30.0
# homeassistant.components.tibber
pyTibber==0.26.12
pyTibber==0.26.13
# homeassistant.components.dlink
pyW215==0.7.0
@ -1317,10 +1317,10 @@ pynx584==0.5
pynzbgetapi==0.2.0
# homeassistant.components.octoprint
pyoctoprintapi==0.1.9
pyoctoprintapi==0.1.11
# homeassistant.components.openuv
pyopenuv==2023.01.0
pyopenuv==2023.02.0
# homeassistant.components.opnsense
pyopnsense==0.2.0
@ -1468,7 +1468,7 @@ python-juicenet==1.1.0
python-kasa==0.5.0
# homeassistant.components.matter
python-matter-server==2.0.2
python-matter-server==2.1.0
# homeassistant.components.xiaomi_miio
python-miio==0.5.12
@ -1572,7 +1572,7 @@ regenmaschine==2022.11.0
renault-api==0.1.11
# homeassistant.components.reolink
reolink-aio==0.4.0
reolink-aio==0.4.2
# homeassistant.components.python_script
restrictedpython==6.0

View file

@ -5,6 +5,7 @@ import asyncio
from collections.abc import AsyncGenerator, Generator
from unittest.mock import AsyncMock, MagicMock, patch
from matter_server.common.const import SCHEMA_VERSION
from matter_server.common.models.server_information import ServerInfo
import pytest
@ -45,6 +46,7 @@ async def matter_client_fixture() -> AsyncGenerator[MagicMock, None]:
sdk_version="2022.11.1",
wifi_credentials_set=True,
thread_credentials_set=True,
min_supported_schema_version=SCHEMA_VERSION,
)
yield client

View file

@ -5,7 +5,8 @@
"schema_version": 1,
"sdk_version": "2022.12.0",
"wifi_credentials_set": true,
"thread_credentials_set": false
"thread_credentials_set": false,
"min_supported_schema_version": 1
},
"nodes": [
{

View file

@ -6,7 +6,8 @@
"schema_version": 1,
"sdk_version": "2022.12.0",
"wifi_credentials_set": true,
"thread_credentials_set": false
"thread_credentials_set": false,
"min_supported_schema_version": 1
},
"nodes": [
{

View file

@ -67,14 +67,21 @@ async def test_default_prompt(hass, mock_init_component):
device_reg.async_update_device(
device.id, disabled_by=device_registry.DeviceEntryDisabler.USER
)
device = device_reg.async_get_or_create(
device_reg.async_get_or_create(
config_entry_id="1234",
connections={("test", "9876-no-name")},
manufacturer="Test Manufacturer NoName",
model="Test Model NoName",
suggested_area="Test Area 2",
)
device_reg.async_get_or_create(
config_entry_id="1234",
connections={("test", "9876-integer-values")},
name=1,
manufacturer=2,
model=3,
suggested_area="Test Area 2",
)
with patch("openai.Completion.create") as mock_create:
result = await conversation.async_converse(hass, "hello", None, Context())
@ -93,6 +100,7 @@ Test Area 2:
- Test Device 2
- Test Device 3 (Test Model 3A)
- Test Device 4
- 1 (3)
Answer the users questions about the world truthfully.

View file

@ -1548,6 +1548,7 @@ async def test_non_numeric_validation_raise(
[
(13, "13"),
(17.50, "17.5"),
("1e-05", "1e-05"),
(Decimal(18.50), "18.5"),
("19.70", "19.70"),
(None, STATE_UNKNOWN),

View file

@ -21,6 +21,7 @@ from homeassistant.const import (
SERVICE_RELOAD,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfEnergy,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@ -250,6 +251,63 @@ async def test_sensor_source_with_force_update(hass: HomeAssistant):
assert state_force.attributes.get("buffer_usage_ratio") == round(9 / 20, 2)
async def test_sampling_boundaries_given(hass: HomeAssistant):
"""Test if either sampling_size or max_age are given."""
assert await async_setup_component(
hass,
"sensor",
{
"sensor": [
{
"platform": "statistics",
"name": "test_boundaries_none",
"entity_id": "sensor.test_monitored",
"state_characteristic": "mean",
},
{
"platform": "statistics",
"name": "test_boundaries_size",
"entity_id": "sensor.test_monitored",
"state_characteristic": "mean",
"sampling_size": 20,
},
{
"platform": "statistics",
"name": "test_boundaries_age",
"entity_id": "sensor.test_monitored",
"state_characteristic": "mean",
"max_age": {"minutes": 4},
},
{
"platform": "statistics",
"name": "test_boundaries_both",
"entity_id": "sensor.test_monitored",
"state_characteristic": "mean",
"sampling_size": 20,
"max_age": {"minutes": 4},
},
]
},
)
await hass.async_block_till_done()
hass.states.async_set(
"sensor.test_monitored",
str(VALUES_NUMERIC[0]),
{ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS},
)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_boundaries_none")
assert state is None
state = hass.states.get("sensor.test_boundaries_size")
assert state is not None
state = hass.states.get("sensor.test_boundaries_age")
assert state is not None
state = hass.states.get("sensor.test_boundaries_both")
assert state is not None
async def test_sampling_size_reduced(hass: HomeAssistant):
"""Test limited buffer size."""
assert await async_setup_component(
@ -514,9 +572,9 @@ async def test_device_class(hass: HomeAssistant):
{
"sensor": [
{
# Device class is carried over from source sensor for characteristics with same unit
# Device class is carried over from source sensor for characteristics which retain unit
"platform": "statistics",
"name": "test_source_class",
"name": "test_retain_unit",
"entity_id": "sensor.test_monitored",
"state_characteristic": "mean",
"sampling_size": 20,
@ -537,6 +595,14 @@ async def test_device_class(hass: HomeAssistant):
"state_characteristic": "datetime_oldest",
"sampling_size": 20,
},
{
# Device class is set to None for any source sensor with TOTAL state class
"platform": "statistics",
"name": "test_source_class_total",
"entity_id": "sensor.test_monitored_total",
"state_characteristic": "mean",
"sampling_size": 20,
},
]
},
)
@ -549,11 +615,21 @@ async def test_device_class(hass: HomeAssistant):
{
ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS,
ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
},
)
hass.states.async_set(
"sensor.test_monitored_total",
str(value),
{
ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.WATT_HOUR,
ATTR_DEVICE_CLASS: SensorDeviceClass.ENERGY,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
},
)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_source_class")
state = hass.states.get("sensor.test_retain_unit")
assert state is not None
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
state = hass.states.get("sensor.test_none")
@ -562,6 +638,9 @@ async def test_device_class(hass: HomeAssistant):
state = hass.states.get("sensor.test_timestamp")
assert state is not None
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP
state = hass.states.get("sensor.test_source_class_total")
assert state is not None
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
async def test_state_class(hass: HomeAssistant):
@ -572,6 +651,15 @@ async def test_state_class(hass: HomeAssistant):
{
"sensor": [
{
# State class is None for datetime characteristics
"platform": "statistics",
"name": "test_nan",
"entity_id": "sensor.test_monitored",
"state_characteristic": "datetime_oldest",
"sampling_size": 20,
},
{
# State class is MEASUREMENT for all other characteristics
"platform": "statistics",
"name": "test_normal",
"entity_id": "sensor.test_monitored",
@ -579,10 +667,12 @@ async def test_state_class(hass: HomeAssistant):
"sampling_size": 20,
},
{
# State class is MEASUREMENT, even when the source sensor
# is of state class TOTAL
"platform": "statistics",
"name": "test_nan",
"entity_id": "sensor.test_monitored",
"state_characteristic": "datetime_oldest",
"name": "test_total",
"entity_id": "sensor.test_monitored_total",
"state_characteristic": "count",
"sampling_size": 20,
},
]
@ -596,14 +686,28 @@ async def test_state_class(hass: HomeAssistant):
str(value),
{ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS},
)
hass.states.async_set(
"sensor.test_monitored_total",
str(value),
{
ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
},
)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_normal")
assert state is not None
assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT
state = hass.states.get("sensor.test_nan")
assert state is not None
assert state.attributes.get(ATTR_STATE_CLASS) is None
state = hass.states.get("sensor.test_normal")
assert state is not None
assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT
state = hass.states.get("sensor.test_monitored_total")
assert state is not None
assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL
state = hass.states.get("sensor.test_total")
assert state is not None
assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT
async def test_unitless_source_sensor(hass: HomeAssistant):

View file

@ -49,6 +49,21 @@ def fixture_mock_appliances_manager_api():
yield mock_appliances_manager
@pytest.fixture(name="mock_appliances_manager_laundry_api")
def fixture_mock_appliances_manager_laundry_api():
"""Set up AppliancesManager fixture."""
with mock.patch(
"homeassistant.components.whirlpool.AppliancesManager"
) as mock_appliances_manager:
mock_appliances_manager.return_value.fetch_appliances = AsyncMock()
mock_appliances_manager.return_value.aircons = None
mock_appliances_manager.return_value.washer_dryers = [
{"SAID": MOCK_SAID3, "NAME": "washer"},
{"SAID": MOCK_SAID4, "NAME": "dryer"},
]
yield mock_appliances_manager
@pytest.fixture(name="mock_backend_selector_api")
def fixture_mock_backend_selector_api():
"""Set up BackendSelector fixture."""
@ -115,8 +130,6 @@ def side_effect_function(*args, **kwargs):
return "0"
if args[0] == "WashCavity_OpStatusBulkDispense1Level":
return "3"
if args[0] == "Cavity_TimeStatusEstTimeRemaining":
return "4000"
def get_sensor_mock(said):
@ -141,13 +154,13 @@ def get_sensor_mock(said):
@pytest.fixture(name="mock_sensor1_api", autouse=False)
def fixture_mock_sensor1_api(mock_auth_api, mock_appliances_manager_api):
def fixture_mock_sensor1_api(mock_auth_api, mock_appliances_manager_laundry_api):
"""Set up sensor API fixture."""
yield get_sensor_mock(MOCK_SAID3)
@pytest.fixture(name="mock_sensor2_api", autouse=False)
def fixture_mock_sensor2_api(mock_auth_api, mock_appliances_manager_api):
def fixture_mock_sensor2_api(mock_auth_api, mock_appliances_manager_laundry_api):
"""Set up sensor API fixture."""
yield get_sensor_mock(MOCK_SAID4)
@ -161,5 +174,7 @@ def fixture_mock_sensor_api_instances(mock_sensor1_api, mock_sensor2_api):
mock_sensor_api.side_effect = [
mock_sensor1_api,
mock_sensor2_api,
mock_sensor1_api,
mock_sensor2_api,
]
yield mock_sensor_api

View file

@ -17,7 +17,7 @@ async def update_sensor_state(
hass: HomeAssistant,
entity_id: str,
mock_sensor_api_instance: MagicMock,
):
) -> None:
"""Simulate an update trigger from the API."""
for call in mock_sensor_api_instance.register_attr_callback.call_args_list:
@ -44,7 +44,7 @@ async def test_dryer_sensor_values(
hass: HomeAssistant,
mock_sensor_api_instances: MagicMock,
mock_sensor2_api: MagicMock,
):
) -> None:
"""Test the sensor value callbacks."""
hass.state = CoreState.not_running
thetimestamp: datetime = datetime(2022, 11, 29, 00, 00, 00, 00, timezone.utc)
@ -108,7 +108,7 @@ async def test_washer_sensor_values(
hass: HomeAssistant,
mock_sensor_api_instances: MagicMock,
mock_sensor1_api: MagicMock,
):
) -> None:
"""Test the sensor value callbacks."""
hass.state = CoreState.not_running
thetimestamp: datetime = datetime(2022, 11, 29, 00, 00, 00, 00, timezone.utc)
@ -147,6 +147,21 @@ async def test_washer_sensor_values(
assert state.state == thetimestamp.isoformat()
state_id = f"{entity_id.split('_')[0]}_detergent_level"
registry = entity_registry.async_get(hass)
entry = registry.async_get(state_id)
assert entry
assert entry.disabled
assert entry.disabled_by is entity_registry.RegistryEntryDisabler.INTEGRATION
update_entry = registry.async_update_entity(entry.entity_id, disabled_by=None)
await hass.async_block_till_done()
assert update_entry != entry
assert update_entry.disabled is False
state = hass.states.get(state_id)
assert state is None
await hass.config_entries.async_reload(entry.config_entry_id)
state = hass.states.get(state_id)
assert state is not None
assert state.state == "50"
@ -253,7 +268,7 @@ async def test_washer_sensor_values(
async def test_restore_state(
hass: HomeAssistant,
mock_sensor_api_instances: MagicMock,
):
) -> None:
"""Test sensor restore state."""
# Home assistant is not running yet
hass.state = CoreState.not_running
@ -288,7 +303,7 @@ async def test_callback(
hass: HomeAssistant,
mock_sensor_api_instances: MagicMock,
mock_sensor1_api: MagicMock,
):
) -> None:
"""Test callback timestamp callback function."""
hass.state = CoreState.not_running
thetimestamp: datetime = datetime(2022, 11, 29, 00, 00, 00, 00, timezone.utc)
@ -314,9 +329,9 @@ async def test_callback(
# restore from cache
state = hass.states.get("sensor.washer_end_time")
assert state.state == thetimestamp.isoformat()
callback = mock_sensor1_api.register_attr_callback.call_args_list[2][0][0]
callback = mock_sensor1_api.register_attr_callback.call_args_list[1][0][0]
callback()
# await hass.async_block_till_done()
state = hass.states.get("sensor.washer_end_time")
assert state.state == thetimestamp.isoformat()
mock_sensor1_api.get_machine_state.return_value = MachineState.RunningMainCycle