1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Add filling level sensors to miele (#157858)

This commit is contained in:
Åke Strandberg
2026-01-02 15:57:15 +01:00
committed by GitHub
parent 9539a612a6
commit 8f8f896675
20 changed files with 2839 additions and 55 deletions

View File

@@ -19,7 +19,12 @@ from homeassistant.helpers.typing import ConfigType
from .api import AsyncConfigEntryAuth
from .const import DOMAIN
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
from .coordinator import (
MieleAuxDataUpdateCoordinator,
MieleConfigEntry,
MieleDataUpdateCoordinator,
MieleRuntimeData,
)
from .services import async_setup_services
PLATFORMS: list[Platform] = [
@@ -75,19 +80,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: MieleConfigEntry) -> boo
) from err
# Setup MieleAPI and coordinator for data fetch
api = MieleAPI(auth)
coordinator = MieleDataUpdateCoordinator(hass, entry, api)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
_api = MieleAPI(auth)
_coordinator = MieleDataUpdateCoordinator(hass, entry, _api)
await _coordinator.async_config_entry_first_refresh()
_aux_coordinator = MieleAuxDataUpdateCoordinator(hass, entry, _api)
await _aux_coordinator.async_config_entry_first_refresh()
entry.runtime_data = MieleRuntimeData(_api, _coordinator, _aux_coordinator)
entry.async_create_background_task(
hass,
coordinator.api.listen_events(
data_callback=coordinator.callback_update_data,
actions_callback=coordinator.callback_update_actions,
entry.runtime_data.api.listen_events(
data_callback=_coordinator.callback_update_data,
actions_callback=_coordinator.callback_update_actions,
),
"pymiele event listener",
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@@ -107,5 +116,5 @@ async def async_remove_config_entry_device(
identifier
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN
and identifier[1] in config_entry.runtime_data.data.devices
and identifier[1] in config_entry.runtime_data.coordinator.data.devices
)

View File

@@ -264,7 +264,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the binary sensor platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:

View File

@@ -112,7 +112,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the button platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:

View File

@@ -138,7 +138,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the climate platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:

View File

@@ -9,7 +9,13 @@ from datetime import timedelta
import logging
from aiohttp import ClientResponseError
from pymiele import MieleAction, MieleAPI, MieleDevice
from pymiele import (
MieleAction,
MieleAPI,
MieleDevice,
MieleFillingLevel,
MieleFillingLevels,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@@ -20,7 +26,16 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
type MieleConfigEntry = ConfigEntry[MieleDataUpdateCoordinator]
@dataclass
class MieleRuntimeData:
"""Runtime data for the Miele integration."""
api: MieleAPI
coordinator: MieleDataUpdateCoordinator
aux_coordinator: MieleAuxDataUpdateCoordinator
type MieleConfigEntry = ConfigEntry[MieleRuntimeData]
@dataclass
@@ -31,8 +46,15 @@ class MieleCoordinatorData:
actions: dict[str, MieleAction]
@dataclass
class MieleAuxCoordinatorData:
"""Data class for storing auxiliary coordinator data."""
filling_levels: dict[str, MieleFillingLevel]
class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
"""Coordinator for Miele data."""
"""Main coordinator for Miele data."""
config_entry: MieleConfigEntry
new_device_callbacks: list[Callable[[dict[str, MieleDevice]], None]] = []
@@ -66,6 +88,7 @@ class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
}
self.devices = devices
actions = {}
for device_id in devices:
try:
actions_json = await self.api.get_actions(device_id)
@@ -99,10 +122,7 @@ class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
device_id: MieleDevice(device) for device_id, device in devices_json.items()
}
self.async_set_updated_data(
MieleCoordinatorData(
devices=devices,
actions=self.data.actions,
)
MieleCoordinatorData(devices=devices, actions=self.data.actions)
)
async def callback_update_actions(self, actions_json: dict[str, dict]) -> None:
@@ -111,8 +131,34 @@ class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
device_id: MieleAction(action) for device_id, action in actions_json.items()
}
self.async_set_updated_data(
MieleCoordinatorData(
devices=self.data.devices,
actions=actions,
)
MieleCoordinatorData(devices=self.data.devices, actions=actions)
)
class MieleAuxDataUpdateCoordinator(DataUpdateCoordinator[MieleAuxCoordinatorData]):
"""Coordinator for Miele data for slowly polled endpoints."""
config_entry: MieleConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: MieleConfigEntry,
api: MieleAPI,
) -> None:
"""Initialize the Miele data coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=timedelta(seconds=60),
)
self.api = api
async def _async_update_data(self) -> MieleAuxCoordinatorData:
"""Fetch data from the Miele API."""
filling_levels_json = await self.api.get_filling_levels()
return MieleAuxCoordinatorData(
filling_levels=MieleFillingLevels(filling_levels_json).filling_levels
)

View File

@@ -38,13 +38,19 @@ async def async_get_config_entry_diagnostics(
"devices": redact_identifiers(
{
device_id: device_data.raw
for device_id, device_data in config_entry.runtime_data.data.devices.items()
for device_id, device_data in config_entry.runtime_data.coordinator.data.devices.items()
}
),
"filling_levels": redact_identifiers(
{
device_id: filling_level_data.raw
for device_id, filling_level_data in config_entry.runtime_data.aux_coordinator.data.filling_levels.items()
}
),
"actions": redact_identifiers(
{
device_id: action_data.raw
for device_id, action_data in config_entry.runtime_data.data.actions.items()
for device_id, action_data in config_entry.runtime_data.coordinator.data.actions.items()
}
),
}
@@ -68,13 +74,19 @@ async def async_get_device_diagnostics(
"model_id": device.model_id,
}
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
aux_coordinator = config_entry.runtime_data.aux_coordinator
device_id = cast(str, device.serial_number)
miele_data: dict[str, Any] = {
"devices": {
hash_identifier(device_id): coordinator.data.devices[device_id].raw
},
"filling_levels": {
hash_identifier(device_id): aux_coordinator.data.filling_levels[
device_id
].raw
},
"actions": {
hash_identifier(device_id): coordinator.data.actions[device_id].raw
},

View File

@@ -1,16 +1,18 @@
"""Entity base class for the Miele integration."""
from pymiele import MieleAction, MieleAPI, MieleDevice
from pymiele import MieleAction, MieleAPI, MieleDevice, MieleFillingLevel
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEVICE_TYPE_TAGS, DOMAIN, MANUFACTURER, MieleAppliance, StateStatus
from .coordinator import MieleDataUpdateCoordinator
from .coordinator import MieleAuxDataUpdateCoordinator, MieleDataUpdateCoordinator
class MieleEntity(CoordinatorEntity[MieleDataUpdateCoordinator]):
class MieleBaseEntity[
_MieleCoordinatorT: MieleDataUpdateCoordinator | MieleAuxDataUpdateCoordinator
](CoordinatorEntity[_MieleCoordinatorT]):
"""Base class for Miele entities."""
_attr_has_entity_name = True
@@ -22,7 +24,7 @@ class MieleEntity(CoordinatorEntity[MieleDataUpdateCoordinator]):
def __init__(
self,
coordinator: MieleDataUpdateCoordinator,
coordinator: _MieleCoordinatorT,
device_id: str,
description: EntityDescription,
) -> None:
@@ -30,7 +32,26 @@ class MieleEntity(CoordinatorEntity[MieleDataUpdateCoordinator]):
super().__init__(coordinator)
self._device_id = device_id
self.entity_description = description
self._attr_unique_id = MieleEntity.get_unique_id(device_id, description)
self._attr_unique_id = MieleBaseEntity.get_unique_id(device_id, description)
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_id)})
@property
def api(self) -> MieleAPI:
"""Return the api object."""
return self.coordinator.api
class MieleEntity(MieleBaseEntity[MieleDataUpdateCoordinator]):
"""Base class for Miele entities that use the main data coordinator."""
def __init__(
self,
coordinator: MieleDataUpdateCoordinator,
device_id: str,
description: EntityDescription,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, device_id, description)
device = self.device
appliance_type = DEVICE_TYPE_TAGS.get(MieleAppliance(device.device_type))
@@ -61,11 +82,6 @@ class MieleEntity(CoordinatorEntity[MieleDataUpdateCoordinator]):
"""Return the actions object."""
return self.coordinator.data.actions[self._device_id]
@property
def api(self) -> MieleAPI:
"""Return the api object."""
return self.coordinator.api
@property
def available(self) -> bool:
"""Return the availability of the entity."""
@@ -75,3 +91,12 @@ class MieleEntity(CoordinatorEntity[MieleDataUpdateCoordinator]):
and self._device_id in self.coordinator.data.devices
and (self.device.state_status is not StateStatus.not_connected)
)
class MieleAuxEntity(MieleBaseEntity[MieleAuxDataUpdateCoordinator]):
"""Base class for Miele entities that use the auxiliary data coordinator."""
@property
def levels(self) -> MieleFillingLevel:
"""Return the filling levels object."""
return self.coordinator.data.filling_levels[self._device_id]

View File

@@ -66,7 +66,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the fan platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:

View File

@@ -71,6 +71,9 @@
"plate_step_warming": "mdi:alpha-w-circle-outline"
}
},
"power_disk_level": {
"default": "mdi:car-coolant-level"
},
"program_id": {
"default": "mdi:selection-ellipse-arrow-inside"
},
@@ -83,6 +86,12 @@
"remaining_time": {
"default": "mdi:clock-end"
},
"rinse_aid_level": {
"default": "mdi:water-opacity"
},
"salt_level": {
"default": "mdi:shaker-outline"
},
"spin_speed": {
"default": "mdi:sync"
},
@@ -95,6 +104,12 @@
"target_temperature": {
"default": "mdi:thermometer-check"
},
"twin_dos_1_level": {
"default": "mdi:car-coolant-level"
},
"twin_dos_2_level": {
"default": "mdi:car-coolant-level"
},
"water_forecast": {
"default": "mdi:water-outline"
}

View File

@@ -86,7 +86,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the light platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:

View File

@@ -71,7 +71,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the select platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:

View File

@@ -8,7 +8,7 @@ from datetime import datetime, timedelta
import logging
from typing import Any, Final, cast
from pymiele import MieleDevice, MieleTemperature
from pymiele import MieleDevice, MieleFillingLevel, MieleTemperature
from homeassistant.components.sensor import (
RestoreSensor,
@@ -44,8 +44,12 @@ from .const import (
StateProgramType,
StateStatus,
)
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
from .entity import MieleEntity
from .coordinator import (
MieleAuxDataUpdateCoordinator,
MieleConfigEntry,
MieleDataUpdateCoordinator,
)
from .entity import MieleAuxEntity, MieleEntity
PARALLEL_UPDATES = 0
@@ -139,10 +143,13 @@ def _convert_finish_timestamp(
@dataclass(frozen=True, kw_only=True)
class MieleSensorDescription(SensorEntityDescription):
class MieleSensorDescription[T: (MieleDevice, MieleFillingLevel)](
SensorEntityDescription
):
"""Class describing Miele sensor entities."""
value_fn: Callable[[MieleDevice], StateType | datetime]
value_fn: Callable[[T], StateType | datetime]
end_value_fn: Callable[[StateType | datetime], StateType | datetime] | None = None
extra_attributes: dict[str, Callable[[MieleDevice], StateType]] | None = None
zone: int | None = None
@@ -150,14 +157,14 @@ class MieleSensorDescription(SensorEntityDescription):
@dataclass
class MieleSensorDefinition:
class MieleSensorDefinition[T: (MieleDevice, MieleFillingLevel)]:
"""Class for defining sensor entities."""
types: tuple[MieleAppliance, ...]
description: MieleSensorDescription
description: MieleSensorDescription[T]
SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
SENSOR_TYPES: Final[tuple[MieleSensorDefinition[MieleDevice], ...]] = (
MieleSensorDefinition(
types=(
MieleAppliance.WASHING_MACHINE,
@@ -689,6 +696,59 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
),
)
POLLED_SENSOR_TYPES: Final[tuple[MieleSensorDefinition[MieleFillingLevel], ...]] = (
MieleSensorDefinition(
types=(MieleAppliance.WASHING_MACHINE,),
description=MieleSensorDescription[MieleFillingLevel](
key="twin_dos_1_level",
translation_key="twin_dos_1_level",
value_fn=lambda value: value.twin_dos_container_1_filling_level,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
),
MieleSensorDefinition(
types=(MieleAppliance.WASHING_MACHINE,),
description=MieleSensorDescription[MieleFillingLevel](
key="twin_dos_2_level",
translation_key="twin_dos_2_level",
value_fn=lambda value: value.twin_dos_container_2_filling_level,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
),
MieleSensorDefinition(
types=(MieleAppliance.DISHWASHER,),
description=MieleSensorDescription[MieleFillingLevel](
key="power_disk_level",
translation_key="power_disk_level",
value_fn=lambda value: None,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
),
MieleSensorDefinition(
types=(MieleAppliance.DISHWASHER,),
description=MieleSensorDescription[MieleFillingLevel](
key="salt_level",
translation_key="salt_level",
value_fn=lambda value: value.salt_filling_level,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
),
MieleSensorDefinition(
types=(MieleAppliance.DISHWASHER,),
description=MieleSensorDescription[MieleFillingLevel](
key="rinse_aid_level",
translation_key="rinse_aid_level",
value_fn=lambda value: value.rinse_aid_filling_level,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
),
)
async def async_setup_entry(
hass: HomeAssistant,
@@ -696,11 +756,14 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the sensor platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
aux_coordinator = config_entry.runtime_data.aux_coordinator
added_devices: set[str] = set() # device_id
added_entities: set[str] = set() # unique_id
def _get_entity_class(definition: MieleSensorDefinition) -> type[MieleSensor]:
def _get_entity_class(
definition: MieleSensorDefinition[MieleDevice],
) -> type[MieleSensor]:
"""Get the entity class for the sensor."""
return {
"state_status": MieleStatusSensor,
@@ -725,7 +788,7 @@ async def async_setup_entry(
)
def _is_sensor_enabled(
definition: MieleSensorDefinition,
definition: MieleSensorDefinition[MieleDevice],
device: MieleDevice,
unique_id: str,
) -> bool:
@@ -748,6 +811,15 @@ async def async_setup_entry(
return False
return True
def _enabled_aux_sensor(
definition: MieleSensorDefinition[MieleFillingLevel], level: MieleFillingLevel
) -> bool:
"""Check if aux sensors are enabled."""
return not (
definition.description.value_fn is not None
and definition.description.value_fn(level) is None
)
def _async_add_devices() -> None:
nonlocal added_devices, added_entities
entities: list = []
@@ -775,7 +847,11 @@ async def async_setup_entry(
continue
# sensors is not enabled, skip
if not _is_sensor_enabled(definition, device, unique_id):
if not _is_sensor_enabled(
definition,
device,
unique_id,
):
continue
added_entities.add(unique_id)
@@ -787,6 +863,15 @@ async def async_setup_entry(
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_devices))
_async_add_devices()
async_add_entities(
MieleAuxSensor(aux_coordinator, device_id, definition.description)
for device_id in aux_coordinator.data.filling_levels
for definition in POLLED_SENSOR_TYPES
if _enabled_aux_sensor(
definition, aux_coordinator.data.filling_levels[device_id]
)
)
APPLIANCE_ICONS = {
MieleAppliance.WASHING_MACHINE: "mdi:washing-machine",
@@ -885,6 +970,32 @@ class MieleRestorableSensor(MieleSensor, RestoreSensor):
super()._handle_coordinator_update()
class MieleAuxSensor(MieleAuxEntity, SensorEntity):
"""Representation of a filling level Sensor."""
entity_description: MieleSensorDescription
def __init__(
self,
coordinator: MieleAuxDataUpdateCoordinator,
device_id: str,
description: MieleSensorDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator, device_id, description)
if description.unique_id_fn is not None:
self._attr_unique_id = description.unique_id_fn(device_id, description)
@property
def native_value(self) -> StateType | datetime:
"""Return the state of the level sensor."""
return (
self.entity_description.value_fn(self.levels)
if self.entity_description.value_fn is not None
else None
)
class MielePlateSensor(MieleSensor):
"""Representation of a Sensor."""

View File

@@ -257,6 +257,9 @@
"plate_step_warm": "Warming"
}
},
"power_disk_level": {
"name": "PowerDisk level"
},
"program_id": {
"name": "Program",
"state": {
@@ -1038,6 +1041,12 @@
"remaining_time": {
"name": "Remaining time"
},
"rinse_aid_level": {
"name": "Rinse aid level"
},
"salt_level": {
"name": "Salt level"
},
"spin_speed": {
"name": "Spin speed"
},
@@ -1080,6 +1089,12 @@
"temperature_zone_3": {
"name": "Temperature zone 3"
},
"twin_dos_1_level": {
"name": "TwinDos 1 level"
},
"twin_dos_2_level": {
"name": "TwinDos 2 level"
},
"water_consumption": {
"name": "Water consumption"
},

View File

@@ -117,7 +117,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the switch platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:

View File

@@ -128,7 +128,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the vacuum platform."""
coordinator = config_entry.runtime_data
coordinator = config_entry.runtime_data.coordinator
async_add_entities(
MieleVacuum(coordinator, device_id, definition.description)

View File

@@ -111,11 +111,26 @@ async def programs_fixture(
return load_json_value_fixture(load_programs_file, DOMAIN)
@pytest.fixture(scope="package")
def load_filling_levels_file() -> str:
"""Fixture for loading filling levels file."""
return "filling_levels.json"
@pytest.fixture
async def filling_levels_fixture(
hass: HomeAssistant, load_filling_levels_file: str
) -> JsonValueType:
"""Fixture for filling levels."""
return load_json_value_fixture(load_filling_levels_file, DOMAIN)
@pytest.fixture
def mock_miele_client(
device_fixture,
action_fixture,
programs_fixture,
filling_levels_fixture,
) -> Generator[MagicMock]:
"""Mock a Miele client."""
@@ -127,6 +142,7 @@ def mock_miele_client(
client.get_devices.return_value = device_fixture
client.get_actions.return_value = action_fixture
client.get_filling_levels.return_value = filling_levels_fixture
client.get_programs.return_value = programs_fixture
client.set_program.return_value = None

View File

@@ -0,0 +1,257 @@
[
{
"deviceId": "Dummy_Appliance_3",
"fillingLevels": {
"twinDosContainer1FillingLevel": 63,
"twinDosContainer2FillingLevel": 20,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyWasher",
"fillingLevels": {
"twinDosContainer1FillingLevel": 63,
"twinDosContainer2FillingLevel": 20,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "Dummy_Appliance_5",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": 80.5,
"saltFillingLevel": 75,
"rinseAidFillingLevel": 25,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_18",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": 33,
"fatFilterSaturation": 66,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "Dummy_Appliance_4",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_12",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_74",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_74_off",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "Dummy_Appliance_1",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "Dummy_Appliance_2",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyDryer",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyOven",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "Dummy_Vacuum_1",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_Fridge_Freezer",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_hob_w_extr",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_Fridge_Freezer_Offline",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": null,
"degreasingCounter": null,
"milkCleaningCounter": null
}
},
{
"deviceId": "DummyAppliance_CoffeeSystem",
"fillingLevels": {
"twinDosContainer1FillingLevel": null,
"twinDosContainer2FillingLevel": null,
"powerDiscFillingLevel": null,
"saltFillingLevel": null,
"rinseAidFillingLevel": null,
"coalFilterSaturation": null,
"fatFilterSaturation": null,
"descalingCounter": 1,
"degreasingCounter": 2,
"milkCleaningCounter": 3
}
}
]

View File

@@ -830,6 +830,212 @@
}),
}),
}),
'filling_levels': dict({
'**REDACTED_019aa577ad1c330d': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_10582eaebd067ad3': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_262ba14529681b23': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': 80.5,
'rinseAidFillingLevel': 25,
'saltFillingLevel': 75,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_43eee10f37037b43': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_4b870e84d3e80013': dict({
'coalFilterSaturation': 33,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': 66,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_4b9caddd6c8a75c4': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_4c9255e388336dab': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_53a963893a01dc04': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_57d53e72806e88b4': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_5aed38e9def460e6': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_9c332e79e3cb1202': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_a367c0b42fc12284': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_afeb069d963cd921': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': 63,
'twinDosContainer2FillingLevel': 20,
}),
'**REDACTED_bd62a0d31ec35172': dict({
'coalFilterSaturation': None,
'degreasingCounter': 2,
'descalingCounter': 1,
'fatFilterSaturation': None,
'milkCleaningCounter': 3,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_c9fe55cdf70786ca': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': 63,
'twinDosContainer2FillingLevel': 20,
}),
'**REDACTED_e7bc6793e305bf53': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
'**REDACTED_ecf06220046f162f': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
}),
'missing_code_warnings': list([
'None',
]),
@@ -1014,6 +1220,20 @@
}),
}),
}),
'filling_levels': dict({
'**REDACTED_57d53e72806e88b4': dict({
'coalFilterSaturation': None,
'degreasingCounter': None,
'descalingCounter': None,
'fatFilterSaturation': None,
'milkCleaningCounter': None,
'powerDiscFillingLevel': None,
'rinseAidFillingLevel': None,
'saltFillingLevel': None,
'twinDosContainer1FillingLevel': None,
'twinDosContainer2FillingLevel': None,
}),
}),
'missing_code_warnings': list([
'None',
]),

File diff suppressed because it is too large Load Diff

View File

@@ -109,7 +109,7 @@ async def test_devices_multiple_created_count(
"""Test that multiple devices are created."""
await setup_integration(hass, mock_config_entry)
assert len(device_registry.devices) == 5
assert len(device_registry.devices) == 7
async def test_device_info(
@@ -205,7 +205,7 @@ async def test_setup_all_platforms(
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert len(device_registry.devices) == prev_devices + 2
assert len(device_registry.devices) == prev_devices + 1
# Check a sample sensor for each new device
assert hass.states.get("sensor.dishwasher").state == "in_use"