Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion homeassistant/components/airobot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"iot_class": "local_polling",
"loggers": ["pyairobotrest"],
"quality_scale": "silver",
"requirements": ["pyairobotrest==0.1.0"]
"requirements": ["pyairobotrest==0.2.0"]
}
3 changes: 3 additions & 0 deletions homeassistant/components/bsblan/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"services": {
"set_hot_water_schedule": {
"service": "mdi:calendar-clock"
},
"sync_time": {
"service": "mdi:timer-sync-outline"
}
}
}
79 changes: 78 additions & 1 deletion homeassistant/components/bsblan/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.util import dt as dt_util

from .const import DOMAIN

Expand All @@ -30,8 +31,9 @@
ATTR_SATURDAY_SLOTS = "saturday_slots"
ATTR_SUNDAY_SLOTS = "sunday_slots"

# Service name
# Service names
SERVICE_SET_HOT_WATER_SCHEDULE = "set_hot_water_schedule"
SERVICE_SYNC_TIME = "sync_time"


# Schema for a single time slot
Expand Down Expand Up @@ -203,6 +205,74 @@ async def set_hot_water_schedule(service_call: ServiceCall) -> None:
await entry.runtime_data.slow_coordinator.async_request_refresh()


async def async_sync_time(service_call: ServiceCall) -> None:
"""Synchronize BSB-LAN device time with Home Assistant."""
device_id: str = service_call.data[ATTR_DEVICE_ID]

# Get the device and config entry
device_registry = dr.async_get(service_call.hass)
device_entry = device_registry.async_get(device_id)

if device_entry is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_device_id",
translation_placeholders={"device_id": device_id},
)

# Find the config entry for this device
matching_entries: list[BSBLanConfigEntry] = [
entry
for entry in service_call.hass.config_entries.async_entries(DOMAIN)
if entry.entry_id in device_entry.config_entries
]

if not matching_entries:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="no_config_entry_for_device",
translation_placeholders={"device_id": device_entry.name or device_id},
)

entry = matching_entries[0]

# Verify the config entry is loaded
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="config_entry_not_loaded",
translation_placeholders={"device_name": device_entry.name or device_id},
)

client = entry.runtime_data.client

try:
# Get current device time
device_time = await client.time()
current_time = dt_util.now()
current_time_str = current_time.strftime("%d.%m.%Y %H:%M:%S")

# Only sync if device time differs from HA time
if device_time.time.value != current_time_str:
await client.set_time(current_time_str)
except BSBLANError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="sync_time_failed",
translation_placeholders={
"device_name": device_entry.name or device_id,
"error": str(err),
},
) from err


SYNC_TIME_SCHEMA = vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): cv.string,
}
)


@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Register the BSB-Lan services."""
Expand All @@ -212,3 +282,10 @@ def async_setup_services(hass: HomeAssistant) -> None:
set_hot_water_schedule,
schema=SERVICE_SET_HOT_WATER_SCHEDULE_SCHEMA,
)

hass.services.async_register(
DOMAIN,
SERVICE_SYNC_TIME,
async_sync_time,
schema=SYNC_TIME_SCHEMA,
)
9 changes: 9 additions & 0 deletions homeassistant/components/bsblan/services.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
sync_time:
fields:
device_id:
required: true
example: "abc123device456"
selector:
device:
integration: bsblan

set_hot_water_schedule:
fields:
device_id:
Expand Down
16 changes: 13 additions & 3 deletions homeassistant/components/bsblan/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,6 @@
"invalid_device_id": {
"message": "Invalid device ID: {device_id}"
},
"invalid_time_format": {
"message": "Invalid time format provided"
},
"no_config_entry_for_device": {
"message": "No configuration entry found for device: {device_id}"
},
Expand All @@ -108,6 +105,9 @@
},
"setup_general_error": {
"message": "An unknown error occurred while retrieving static device data"
},
"sync_time_failed": {
"message": "Failed to sync time for {device_name}: {error}"
}
},
"services": {
Expand Down Expand Up @@ -148,6 +148,16 @@
}
},
"name": "Set hot water schedule"
},
"sync_time": {
"description": "Synchronize Home Assistant time to the BSB-Lan device. Only updates if device time differs from Home Assistant time.",
"fields": {
"device_id": {
"description": "The BSB-LAN device to sync time for.",
"name": "Device"
}
},
"name": "Sync time"
}
}
}
27 changes: 18 additions & 9 deletions homeassistant/components/miele/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@

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] = [
Expand Down Expand Up @@ -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
Expand All @@ -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
)
2 changes: 1 addition & 1 deletion homeassistant/components/miele/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/miele/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/miele/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
68 changes: 57 additions & 11 deletions homeassistant/components/miele/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
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
Expand All @@ -20,7 +26,16 @@
_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
Expand All @@ -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]] = []
Expand Down Expand Up @@ -66,6 +88,7 @@ async def _async_update_data(self) -> MieleCoordinatorData:
}
self.devices = devices
actions = {}

for device_id in devices:
try:
actions_json = await self.api.get_actions(device_id)
Expand Down Expand Up @@ -99,10 +122,7 @@ async def callback_update_data(self, devices_json: dict[str, dict]) -> None:
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:
Expand All @@ -111,8 +131,34 @@ async def callback_update_actions(self, actions_json: dict[str, dict]) -> None:
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
)
Loading
Loading