Files
HomeAssistant/custom_components/home_connect_alt/button.py
T
2025-09-11 10:47:34 +03:00

335 lines
13 KiB
Python

import json
import logging
from home_connect_async import Appliance, HomeConnect, HomeConnectError, Events
from homeassistant.components.button import ButtonEntity
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from .common import Configuration, EntityBase, EntityManager
from .const import DOMAIN, HOME_CONNECT_DEVICE
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass:HomeAssistant , config_entry:ConfigType, async_add_entities:AddEntitiesCallback) -> None:
""" Add buttons for passed config_entry in HA """
#homeconnect:HomeConnect = hass.data[DOMAIN]['homeconnect']
entry_conf:Configuration = hass.data[DOMAIN][config_entry.entry_id]
homeconnect:HomeConnect = entry_conf["homeconnect"]
entity_manager = EntityManager(async_add_entities, "Button")
def add_appliance(appliance:Appliance) -> None:
conf = entry_conf.get_config()
if appliance.available_programs:
entity_manager.add(StartButton(appliance, None, conf))
entity_manager.add(StopButton(appliance, None, conf))
if appliance.commands:
for command in appliance.commands.values():
# The "BSH.Common.Command.AcknowledgeEvent" command is used to acknowledge the ProgramFinished state
if command.key not in ["BSH.Common.Command.PauseProgram", "BSH.Common.Command.ResumeProgram", "BSH.Common.Command.AcknowledgeEvent"]:
button = CommandButton(appliance, command.key, conf, hc_obj=command)
entity_manager.add(button)
entity_manager.register()
def remove_appliance(appliance:Appliance) -> None:
entity_manager.remove_appliance(appliance)
# First add the integration button
button_name_suffix = "" if entry_conf["primary_config_entry"] else "_"+config_entry.entry_id
async_add_entities([HomeConnectRefreshButton(homeconnect, button_name_suffix), HomeConnectDebugButton(homeconnect, button_name_suffix)])
# Subscribe for events and register existing appliances
homeconnect.register_callback(add_appliance, [Events.PAIRED, Events.DATA_CHANGED, Events.PROGRAM_STARTED, Events.PROGRAM_SELECTED])
homeconnect.register_callback(remove_appliance, Events.DEPAIRED)
for appliance in homeconnect.appliances.values():
add_appliance(appliance)
class StartButton(EntityBase, ButtonEntity):
""" Class for buttons that start the selected program """
@property
def unique_id(self) -> str:
return f'{self.haId}_start_pause'
@property
def name_ext(self) -> str:
match self.translation_key:
case "pause_program":
return "Pause"
case "resume_program":
return "Resume"
return "Start"
@property
def translation_key(self) -> str:
op_state = self._appliance.status.get("BSH.Common.Status.OperationState")
if op_state and op_state.value == "BSH.Common.EnumType.OperationState.Run" \
and "BSH.Common.Command.PauseProgram" in self._appliance.commands:
return "pause_program"
if op_state and op_state.value == "BSH.Common.EnumType.OperationState.Pause" \
and "BSH.Common.Command.ResumeProgram" in self._appliance.commands:
return "resume_program"
return "start_program"
@property
def available(self) -> bool:
op_state = self._appliance.status.get("BSH.Common.Status.OperationState")
return super().available and op_state and \
(
(
op_state.value in ["BSH.Common.EnumType.OperationState.Ready", "BSH.Common.EnumType.OperationState.Inactive" ]
and (
"BSH.Common.Status.RemoteControlStartAllowed" not in self._appliance.status or
self._appliance.status["BSH.Common.Status.RemoteControlStartAllowed"].value
)
and (
(self._appliance.selected_program or self._appliance.startonly_program)
and not self._appliance.active_program
# and self._appliance.available_programs and
# self._appliance.selected_program.key in self._appliance.available_programs
)
)
or (
op_state.value == "BSH.Common.EnumType.OperationState.Run"
and "BSH.Common.Command.PauseProgram" in self._appliance.commands
)
or (
op_state.value == "BSH.Common.EnumType.OperationState.Pause"
and "BSH.Common.Command.ResumeProgram" in self._appliance.commands
)
)
@property
def icon(self) -> str:
if "BSH.Common.Command.PauseProgram" in self._appliance.commands \
and "BSH.Common.Status.OperationState" in self._appliance.status \
and self._appliance.status["BSH.Common.Status.OperationState"].value == "BSH.Common.EnumType.OperationState.Run":
return "mdi:pause"
return "mdi:play"
async def async_press(self) -> None:
""" Handle button press """
try:
op_state = self._appliance.status.get("BSH.Common.Status.OperationState")
if op_state and op_state.value in ["BSH.Common.EnumType.OperationState.Ready", "BSH.Common.EnumType.OperationState.Inactive" ]:
await self._appliance.async_start_program()
elif op_state and op_state.value == "BSH.Common.EnumType.OperationState.Run":
await self._appliance.async_pause_active_program()
elif op_state and op_state.value == "BSH.Common.EnumType.OperationState.Pause":
await self._appliance.async_resume_paused_program()
except HomeConnectError as ex:
if ex.error_description:
raise HomeAssistantError(f"Failed to start the selected program: {ex.error_description} ({ex.code})")
raise HomeAssistantError(f"Failed to start the selected program ({ex.code})")
async def async_added_to_hass(self):
"""Run when this Entity has been added to HA."""
events = [ Events.CONNECTION_CHANGED,
Events.DATA_CHANGED,
Events.PROGRAM_SELECTED,
Events.PROGRAM_STARTED,
Events.PROGRAM_FINISHED,
"BSH.Common.Status.*",
"BSH.Common.Setting.PowerState"
]
self._appliance.register_callback(self.async_on_update, events)
async def async_will_remove_from_hass(self):
"""Entity being removed from hass."""
events = [ Events.CONNECTION_CHANGED,
Events.DATA_CHANGED,
Events.PROGRAM_SELECTED,
Events.PROGRAM_STARTED,
Events.PROGRAM_FINISHED,
"BSH.Common.Status.*",
"BSH.Common.Setting.PowerState"
]
self._appliance.deregister_callback(self.async_on_update, events)
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
self.async_write_ha_state()
class StopButton(EntityBase, ButtonEntity):
""" Class for buttons that start the selected program """
@property
def unique_id(self) -> str:
return f'{self.haId}_stop'
@property
def name_ext(self) -> str:
return "Stop"
@property
def translation_key(self) -> str:
return "stop_program"
@property
def available(self) -> bool:
return super().available \
and self._appliance.active_program \
and (
"BSH.Common.Status.RemoteControlStartAllowed" not in self._appliance.status or
self._appliance.status["BSH.Common.Status.RemoteControlStartAllowed"].value
)
@property
def icon(self) -> str:
return "mdi:stop"
async def async_press(self) -> None:
""" Handle button press """
try:
await self._appliance.async_stop_active_program()
except HomeConnectError as ex:
if ex.error_description:
raise HomeAssistantError(f"Failed to stop the selected program: {ex.error_description} ({ex.code})")
raise HomeAssistantError(f"Failed to stop the selected program ({ex.code})")
async def async_added_to_hass(self):
"""Run when this Entity has been added to HA."""
events = [ Events.CONNECTION_CHANGED,
Events.DATA_CHANGED,
Events.PROGRAM_SELECTED,
Events.PROGRAM_STARTED,
Events.PROGRAM_FINISHED,
"BSH.Common.Status.*",
"BSH.Common.Setting.PowerState"
]
self._appliance.register_callback(self.async_on_update, events)
async def async_will_remove_from_hass(self):
"""Entity being removed from hass."""
events = [ Events.CONNECTION_CHANGED,
Events.DATA_CHANGED,
Events.PROGRAM_SELECTED,
Events.PROGRAM_STARTED,
Events.PROGRAM_FINISHED,
"BSH.Common.Status.*",
"BSH.Common.Setting.PowerState"
]
self._appliance.deregister_callback(self.async_on_update, events)
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
self.async_write_ha_state()
class CommandButton(EntityBase, ButtonEntity):
""" Class for running a HC command """
@property
def name_ext(self) -> str|None:
return self._hc_obj.name
@property
def icon(self) -> str:
return self.get_entity_setting('icon', "mdi:button-pointer")
@property
def available(self) -> bool:
return super().available and self._appliance.commands and self._key in self._appliance.commands
async def async_press(self) -> None:
""" Handle button press """
try:
await self._appliance.async_send_command(self._key, True)
except HomeConnectError as ex:
if ex.error_description:
raise HomeAssistantError(f"Failed to stop the selected program: {ex.error_description} ({ex.code})")
raise HomeAssistantError(f"Failed to stop the selected program ({ex.code})")
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
self.async_write_ha_state()
class HomeConnectRefreshButton(ButtonEntity):
""" Class for a button to trigger a global refresh of Home Connect data """
_attr_has_entity_name = True
def __init__(self, homeconnect:HomeConnect, name_suffix:str) -> None:
self._homeconnect = homeconnect
self._name_suffix = name_suffix
self.entity_id = f'home_connect.{self.unique_id}'
@property
def device_info(self):
"""Return information to link this entity with the correct device."""
return HOME_CONNECT_DEVICE
@property
def unique_id(self) -> str:
return "homeconnect_refresh" + self._name_suffix
@property
def translation_key(self) -> str:
return "homeconnect_refresh"
@property
def icon(self) -> str:
return "mdi:cloud-refresh"
@property
def available(self) -> bool:
return True
async def async_press(self) -> None:
""" Handle button press """
try:
self._homeconnect.start_load_data_task(refresh=HomeConnect.RefreshMode.ALL)
except HomeConnectError as ex:
if ex.error_description:
raise HomeAssistantError(f"Failed to refresh the Home Connect data: {ex.error_description} ({ex.code})")
raise HomeAssistantError(f"Failed to refresh the Home Connect data ({ex.code})")
class HomeConnectDebugButton(ButtonEntity):
""" Class for a button to trigger a global refresh of Home Connect data """
_attr_has_entity_name = True
def __init__(self, homeconnect:HomeConnect, name_suffix:str) -> None:
self._homeconnect = homeconnect
self._name_suffix = name_suffix
self.entity_id = f'home_connect.{self.unique_id}'
@property
def device_info(self):
"""Return information to link this entity with the correct device."""
return HOME_CONNECT_DEVICE
@property
def unique_id(self) -> str:
return "homeconnect_debug" + self._name_suffix
# @property
# def name(self) -> str:
# return "homeconnect_debug"
# return None
#return "Home Connect Debug Info"
@property
def translation_key(self) -> str:
return "homeconnect_debug"
@property
def icon(self) -> str:
return "mdi:bug-check"
@property
def available(self) -> bool:
return True
async def async_press(self) -> None:
""" Handle button press """
try:
conf = {k:v for (k,v) in self.hass.data[DOMAIN].items() if isinstance(v, (str, int, float, dict, list)) and k not in [CONF_CLIENT_ID, CONF_CLIENT_SECRET] }
js=json.dumps(conf, indent=2, default=lambda o: '<not serializable>')
#js=json.dumps(self.hass.data[DOMAIN], indent=2, default=lambda o: '<not serializable>')
_LOGGER.error(js)
js=self._homeconnect.to_json(indent=2)
_LOGGER.error(js)
except Exception as ex:
raise HomeAssistantError("Failed to serialize to JSON")