Skip to content

Create miele devices dynamically #143804

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions homeassistant/components/miele/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,16 @@ async def async_setup_entry(
"""Set up the binary sensor platform."""
coordinator = config_entry.runtime_data

async_add_entities(
MieleBinarySensor(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items()
for definition in BINARY_SENSOR_TYPES
if device.device_type in definition.types
)
def _async_add_new_devices(new_devices: dict[str, MieleDevice]) -> None:
async_add_entities(
MieleBinarySensor(coordinator, device_id, definition.description)
for device_id, device in new_devices.items()
for definition in BINARY_SENSOR_TYPES
if device.device_type in definition.types
)

coordinator.new_device_callbacks.append(_async_add_new_devices)
_async_add_new_devices(coordinator.data.devices)


class MieleBinarySensor(MieleEntity, BinarySensorEntity):
Expand Down
17 changes: 11 additions & 6 deletions homeassistant/components/miele/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Final

import aiohttp
from pymiele import MieleDevice

from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant
Expand Down Expand Up @@ -112,12 +113,16 @@ async def async_setup_entry(
"""Set up the button platform."""
coordinator = config_entry.runtime_data

async_add_entities(
MieleButton(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items()
for definition in BUTTON_TYPES
if device.device_type in definition.types
)
def _async_add_new_devices(new_devices: dict[str, MieleDevice]) -> None:
async_add_entities(
MieleButton(coordinator, device_id, definition.description)
for device_id, device in new_devices.items()
for definition in BUTTON_TYPES
if device.device_type in definition.types
)

coordinator.new_device_callbacks.append(_async_add_new_devices)
_async_add_new_devices(coordinator.data.devices)


class MieleButton(MieleEntity, ButtonEntity):
Expand Down
23 changes: 15 additions & 8 deletions homeassistant/components/miele/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,22 @@ async def async_setup_entry(
"""Set up the climate platform."""
coordinator = config_entry.runtime_data

async_add_entities(
MieleClimate(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items()
for definition in CLIMATE_TYPES
if (
device.device_type in definition.types
and (definition.description.value_fn(device) not in DISABLED_TEMP_ENTITIES)
def _async_add_new_devices(new_devices: dict[str, MieleDevice]) -> None:
async_add_entities(
MieleClimate(coordinator, device_id, definition.description)
for device_id, device in new_devices.items()
for definition in CLIMATE_TYPES
if (
device.device_type in definition.types
and (
definition.description.value_fn(device)
not in DISABLED_TEMP_ENTITIES
)
)
)
)

coordinator.new_device_callbacks.append(_async_add_new_devices)
_async_add_new_devices(coordinator.data.devices)


class MieleClimate(MieleEntity, ClimateEntity):
Expand Down
21 changes: 21 additions & 0 deletions homeassistant/components/miele/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import asyncio.timeouts
from collections.abc import Callable
from dataclasses import dataclass
from datetime import timedelta
import logging
Expand Down Expand Up @@ -33,6 +34,10 @@
class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
"""Coordinator for Miele data."""

new_device_callbacks: list[Callable[[dict[str, MieleDevice]], None]] = []
known_devices: set[str] = set()
devices: dict[str, MieleDevice] = {}

def __init__(
self,
hass: HomeAssistant,
Expand Down Expand Up @@ -60,8 +65,24 @@
for device_id in devices:
actions_json = await self.api.get_actions(device_id)
actions[device_id] = MieleAction(actions_json)
self.devices = devices
self._async_add_devices()
return MieleCoordinatorData(devices=devices, actions=actions)

def _async_add_devices(self) -> None:
"""Add new devices."""
current_devices = set(self.devices)
new_devices = current_devices - self.known_devices
if new_devices:
self.known_devices.update(new_devices)
for callback in self.new_device_callbacks:
callback(

Check warning on line 79 in homeassistant/components/miele/coordinator.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/miele/coordinator.py#L79

Added line #L79 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should test those (e.g. that really all entities get added automatically and we didn't miss/mess up a platform). Set up the integration, add a device, let the freezer tick, then check we have the new devices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick test that all intended platforms are loaded by the default data fixture. To add an extra device after a delay needs some more thinking and I don't think I can make that before beta cut.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really that complicated? Don't you just need to add a device in the get_devices fixture dict (e.g. by copying one of those in there and changing the name&id) after integration setup, then let the freezer tick?

Copy link
Contributor Author

@astrandb astrandb Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might not be complicated, but it is beyond my comfort zone... And I have some other commitments away from my desk tomorrow.

You mean that I first load default 4_devices.json and then after setup the next get_devices() should get another file (5_devices.json)? Can you point me at an example?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think you'll even need a full fixture file just basically get_devices.return_value["deviceB"] = get_devices.return_value["deviceA"] and then adapt its identifier to make sure we have unique ids. I think I'm doing something like this in tedee

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am looking into this PR now. There is a race problem in that coordinator.data must be complete with new data before kicking off the platform callbacks. It works fine directly after setup but not when I find added devices within async_update_data(). I have to find a way to move the hunt for new devices a bit later in the chain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just write to self.data during async_update_data if that helps

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made it work nice and clean IRL. I have issues with the tests though. If I run a single test with pytest ... -k test_function, it works as intended, but when I run a whole file or all of the tests for the integration it will fail. It seems to be related to the loading of alternate fixture files. It works for a single test, but the alternate is not loaded if other tests have been run before.

{
device_id: self.data.devices[device_id]
for device_id in new_devices
}
)

async def callback_update_data(self, devices_json: dict[str, dict]) -> None:
"""Handle data update from the API."""
devices = {
Expand Down
16 changes: 10 additions & 6 deletions homeassistant/components/miele/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,16 @@ async def async_setup_entry(
"""Set up the light platform."""
coordinator = config_entry.runtime_data

async_add_entities(
MieleLight(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items()
for definition in LIGHT_TYPES
if device.device_type in definition.types
)
def _async_add_new_devices(new_devices: dict[str, MieleDevice]) -> None:
async_add_entities(
MieleLight(coordinator, device_id, definition.description)
for device_id, device in new_devices.items()
for definition in LIGHT_TYPES
if device.device_type in definition.types
)

coordinator.new_device_callbacks.append(_async_add_new_devices)
_async_add_new_devices(coordinator.data.devices)


class MieleLight(MieleEntity, LightEntity):
Expand Down
34 changes: 19 additions & 15 deletions homeassistant/components/miele/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,25 @@ async def async_setup_entry(
"""Set up the sensor platform."""
coordinator = config_entry.runtime_data

entities: list = []
entity_class: type[MieleSensor]
for device_id, device in coordinator.data.devices.items():
for definition in SENSOR_TYPES:
if device.device_type in definition.types:
match definition.description.key:
case "state_status":
entity_class = MieleStatusSensor
case _:
entity_class = MieleSensor
entities.append(
entity_class(coordinator, device_id, definition.description)
)

async_add_entities(entities)
def _async_add_new_devices(new_devices: dict[str, MieleDevice]) -> None:
entities: list = []
entity_class: type[MieleSensor]
for device_id, device in new_devices.items():
for definition in SENSOR_TYPES:
if device.device_type in definition.types:
match definition.description.key:
case "state_status":
entity_class = MieleStatusSensor
case _:
entity_class = MieleSensor
entities.append(
entity_class(coordinator, device_id, definition.description)
)

async_add_entities(entities)

coordinator.new_device_callbacks.append(_async_add_new_devices)
_async_add_new_devices(coordinator.data.devices)


APPLIANCE_ICONS = {
Expand Down
34 changes: 19 additions & 15 deletions homeassistant/components/miele/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,25 @@ async def async_setup_entry(
"""Set up the switch platform."""
coordinator = config_entry.runtime_data

entities: list = []
entity_class: type[MieleSwitch]
for device_id, device in coordinator.data.devices.items():
for definition in SWITCH_TYPES:
if device.device_type in definition.types:
match definition.description.key:
case "poweronoff":
entity_class = MielePowerSwitch
case "supercooling" | "superfreezing":
entity_class = MieleSuperSwitch

entities.append(
entity_class(coordinator, device_id, definition.description)
)
async_add_entities(entities)
def _async_add_new_devices(new_devices: dict[str, MieleDevice]) -> None:
entities: list = []
entity_class: type[MieleSwitch]
for device_id, device in new_devices.items():
for definition in SWITCH_TYPES:
if device.device_type in definition.types:
match definition.description.key:
case "poweronoff":
entity_class = MielePowerSwitch
case "supercooling" | "superfreezing":
entity_class = MieleSuperSwitch

entities.append(
entity_class(coordinator, device_id, definition.description)
)
async_add_entities(entities)

coordinator.new_device_callbacks.append(_async_add_new_devices)
_async_add_new_devices(coordinator.data.devices)


class MieleSwitch(MieleEntity, SwitchEntity):
Expand Down