# WashData - Home Assistant integration for appliance cycle monitoring via smart plugs. # Copyright (C) 2026 Lukas Bandura # SPDX-License-Identifier: AGPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . """Select entity for WashData.""" from __future__ import annotations import logging from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DOMAIN, SIGNAL_WASHER_UPDATE from .manager import WashDataManager from .profile_store import profile_sort_key _LOGGER = logging.getLogger(__name__) OPTION_AUTO = "auto_detect" async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the select entity.""" manager: WashDataManager = hass.data[DOMAIN][config_entry.entry_id] async_add_entities([WashDataProgramSelect(manager, config_entry)]) class WashDataProgramSelect(SelectEntity): """Select entity to manually choose the running program.""" _attr_has_entity_name = True _attr_translation_key = "program_select" def __init__(self, manager: WashDataManager, config_entry: ConfigEntry) -> None: """Initialize the select entity.""" self._manager = manager self._config_entry = config_entry self._attr_unique_id = f"{config_entry.entry_id}_program_select" self._attr_device_info = { "identifiers": {(DOMAIN, config_entry.entry_id)}, "name": config_entry.title, "manufacturer": "WashData", } # Determine icon based on device type dtype = getattr(manager, "device_type", "washing_machine") if dtype == "dryer": self._attr_icon = "mdi:tumble-dryer" elif dtype == "dishwasher": self._attr_icon = "mdi:dishwasher" elif dtype == "air_fryer": self._attr_icon = "mdi:pot-steam" else: self._attr_icon = "mdi:washing-machine" # Default and washing_machine self._update_options() @callback def _update_options(self) -> None: """Update the list of available options from profiles.""" profiles = self._manager.profile_store.list_profiles() # Sort profiles by name profile_names = sorted([p["name"] for p in profiles], key=profile_sort_key) self._attr_options = [OPTION_AUTO] + profile_names async def async_added_to_hass(self) -> None: """Register callbacks.""" self.async_on_remove( async_dispatcher_connect( self.hass, SIGNAL_WASHER_UPDATE.format(self._manager.entry_id), self._update_state, ) ) self._update_state() @callback def _update_state(self) -> None: """Update state from manager.""" # Refresh options in case new profiles were created self._update_options() current = self._manager.current_program manual_active = getattr(self._manager, "manual_program_active", False) if manual_active and current: self._attr_current_option = current elif current in ("detecting...", "off", "restored..."): self._attr_current_option = OPTION_AUTO elif current: # It detected a program, but it's not "manual override" mode. # Should we show the detected program or "Auto"? # Showing "Auto" implies "I am in auto mode". # But user might want to see what is detected here too? # Standard pattern: Select shows target/mode. # If we are in auto mode, show Auto. The sensor shows the detected program. self._attr_current_option = OPTION_AUTO else: self._attr_current_option = OPTION_AUTO self.async_write_ha_state() def select_option(self, option: str) -> None: """Handle the option selection (sync wrapper).""" raise NotImplementedError("Use async_select_option instead") async def async_select_option(self, option: str) -> None: """Change the selected option.""" if option == OPTION_AUTO: self._manager.clear_manual_program() else: self._manager.set_manual_program(option)