Migrate device models to entity descriptions and add localization & icons at Home Connect (#127870)
* Delete device models and use entity descriptions * Home Connect localization & icons * Update homeassistant/components/home_connect/strings.json * Update homeassistant/components/home_connect/icons.json * Fix tests --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>pull/128177/head
parent
1739647768
commit
6a12a24d73
|
@ -10,7 +10,7 @@ from requests import HTTPError
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_DEVICE_ID, CONF_DEVICE, Platform
|
||||
from homeassistant.const import ATTR_DEVICE_ID, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
config_entry_oauth2_flow,
|
||||
|
@ -87,8 +87,7 @@ def _get_appliance_by_device_id(
|
|||
) -> api.HomeConnectDevice:
|
||||
"""Return a Home Connect appliance instance given an device_id."""
|
||||
for hc_api in hass.data[DOMAIN].values():
|
||||
for dev_dict in hc_api.devices:
|
||||
device = dev_dict[CONF_DEVICE]
|
||||
for device in hc_api.devices:
|
||||
if device.device_id == device_id:
|
||||
return device.appliance
|
||||
raise ValueError(f"Appliance for device id {device_id} not found")
|
||||
|
@ -255,9 +254,7 @@ async def update_all_devices(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|||
device_registry = dr.async_get(hass)
|
||||
try:
|
||||
await hass.async_add_executor_job(hc_api.get_devices)
|
||||
for device_dict in hc_api.devices:
|
||||
device = device_dict["device"]
|
||||
|
||||
for device in hc_api.devices:
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, device.appliance.haId)},
|
||||
|
|
|
@ -1,50 +1,17 @@
|
|||
"""API for Home Connect bound to HASS OAuth."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from asyncio import run_coroutine_threadsafe
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import homeconnect
|
||||
from homeconnect.api import HomeConnectAppliance, HomeConnectError
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ICON,
|
||||
CONF_DEVICE,
|
||||
CONF_ENTITIES,
|
||||
PERCENTAGE,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
from .const import (
|
||||
ATTR_AMBIENT,
|
||||
ATTR_BSH_KEY,
|
||||
ATTR_DESC,
|
||||
ATTR_DEVICE,
|
||||
ATTR_KEY,
|
||||
ATTR_SENSOR_TYPE,
|
||||
ATTR_SIGN,
|
||||
ATTR_UNIT,
|
||||
ATTR_VALUE,
|
||||
BSH_ACTIVE_PROGRAM,
|
||||
BSH_AMBIENT_LIGHT_ENABLED,
|
||||
BSH_COMMON_OPTION_DURATION,
|
||||
BSH_COMMON_OPTION_PROGRAM_PROGRESS,
|
||||
BSH_OPERATION_STATE,
|
||||
BSH_POWER_OFF,
|
||||
BSH_POWER_STANDBY,
|
||||
BSH_REMAINING_PROGRAM_TIME,
|
||||
BSH_REMOTE_CONTROL_ACTIVATION_STATE,
|
||||
BSH_REMOTE_START_ALLOWANCE_STATE,
|
||||
COOKING_LIGHTING,
|
||||
SIGNAL_UPDATE_ENTITIES,
|
||||
)
|
||||
from .const import ATTR_KEY, ATTR_VALUE, BSH_ACTIVE_PROGRAM, SIGNAL_UPDATE_ENTITIES
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -65,7 +32,7 @@ class ConfigEntryAuth(homeconnect.HomeConnectAPI):
|
|||
hass, config_entry, implementation
|
||||
)
|
||||
super().__init__(self.session.token)
|
||||
self.devices: list[dict[str, Any]] = []
|
||||
self.devices: list[HomeConnectDevice] = []
|
||||
|
||||
def refresh_tokens(self) -> dict:
|
||||
"""Refresh and return new Home Connect tokens using Home Assistant OAuth2 session."""
|
||||
|
@ -75,55 +42,16 @@ class ConfigEntryAuth(homeconnect.HomeConnectAPI):
|
|||
|
||||
return self.session.token
|
||||
|
||||
def get_devices(self) -> list[dict[str, Any]]:
|
||||
def get_devices(self) -> list[HomeConnectAppliance]:
|
||||
"""Get a dictionary of devices."""
|
||||
appl = self.get_appliances()
|
||||
devices = []
|
||||
for app in appl:
|
||||
device: HomeConnectDevice
|
||||
if app.type == "Dryer":
|
||||
device = Dryer(self.hass, app)
|
||||
elif app.type == "Washer":
|
||||
device = Washer(self.hass, app)
|
||||
elif app.type == "WasherDryer":
|
||||
device = WasherDryer(self.hass, app)
|
||||
elif app.type == "Dishwasher":
|
||||
device = Dishwasher(self.hass, app)
|
||||
elif app.type == "FridgeFreezer":
|
||||
device = FridgeFreezer(self.hass, app)
|
||||
elif app.type == "Refrigerator":
|
||||
device = Refrigerator(self.hass, app)
|
||||
elif app.type == "Freezer":
|
||||
device = Freezer(self.hass, app)
|
||||
elif app.type == "Oven":
|
||||
device = Oven(self.hass, app)
|
||||
elif app.type == "CoffeeMaker":
|
||||
device = CoffeeMaker(self.hass, app)
|
||||
elif app.type == "Hood":
|
||||
device = Hood(self.hass, app)
|
||||
elif app.type == "Hob":
|
||||
device = Hob(self.hass, app)
|
||||
elif app.type == "CookProcessor":
|
||||
device = CookProcessor(self.hass, app)
|
||||
else:
|
||||
_LOGGER.warning("Appliance type %s not implemented", app.type)
|
||||
continue
|
||||
devices.append(
|
||||
{CONF_DEVICE: device, CONF_ENTITIES: device.get_entity_info()}
|
||||
)
|
||||
self.devices = devices
|
||||
return devices
|
||||
appl: list[HomeConnectAppliance] = self.get_appliances()
|
||||
self.devices = [HomeConnectDevice(self.hass, app) for app in appl]
|
||||
return self.devices
|
||||
|
||||
|
||||
class HomeConnectDevice:
|
||||
"""Generic Home Connect device."""
|
||||
|
||||
# for some devices, this is instead BSH_POWER_STANDBY
|
||||
# see https://developer.home-connect.com/docs/settings/power_state
|
||||
power_off_state = BSH_POWER_OFF
|
||||
hass: HomeAssistant
|
||||
appliance: HomeConnectAppliance
|
||||
|
||||
def __init__(self, hass: HomeAssistant, appliance: HomeConnectAppliance) -> None:
|
||||
"""Initialize the device class."""
|
||||
self.hass = hass
|
||||
|
@ -155,378 +83,3 @@ class HomeConnectDevice:
|
|||
_LOGGER.debug("Update triggered on %s", appliance.name)
|
||||
_LOGGER.debug(self.appliance.status)
|
||||
dispatcher_send(self.hass, SIGNAL_UPDATE_ENTITIES, appliance.haId)
|
||||
|
||||
@abstractmethod
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with info about the associated entities."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DeviceWithPrograms(HomeConnectDevice):
|
||||
"""Device with programs."""
|
||||
|
||||
def get_programs_available(self) -> list:
|
||||
"""Get the available programs."""
|
||||
try:
|
||||
programs_available = self.appliance.get_programs_available()
|
||||
except (HomeConnectError, ValueError):
|
||||
_LOGGER.debug("Unable to fetch available programs. Probably offline")
|
||||
programs_available = []
|
||||
return programs_available
|
||||
|
||||
def get_program_switches(self) -> list[dict[str, Any]]:
|
||||
"""Get a dictionary with info about program switches.
|
||||
|
||||
There will be one switch for each program.
|
||||
"""
|
||||
programs = self.get_programs_available()
|
||||
return [{ATTR_DEVICE: self, "program_name": p} for p in programs]
|
||||
|
||||
def get_program_sensors(self) -> list[dict[str, Any]]:
|
||||
"""Get a dictionary with info about program sensors.
|
||||
|
||||
There will be one of the four types of sensors for each
|
||||
device.
|
||||
"""
|
||||
sensors = {
|
||||
BSH_REMAINING_PROGRAM_TIME: (
|
||||
"Remaining Program Time",
|
||||
None,
|
||||
None,
|
||||
SensorDeviceClass.TIMESTAMP,
|
||||
1,
|
||||
),
|
||||
BSH_COMMON_OPTION_DURATION: (
|
||||
"Duration",
|
||||
UnitOfTime.SECONDS,
|
||||
"mdi:update",
|
||||
None,
|
||||
1,
|
||||
),
|
||||
BSH_COMMON_OPTION_PROGRAM_PROGRESS: (
|
||||
"Program Progress",
|
||||
PERCENTAGE,
|
||||
"mdi:progress-clock",
|
||||
None,
|
||||
1,
|
||||
),
|
||||
}
|
||||
return [
|
||||
{
|
||||
ATTR_DEVICE: self,
|
||||
ATTR_BSH_KEY: k,
|
||||
ATTR_DESC: desc,
|
||||
ATTR_UNIT: unit,
|
||||
ATTR_ICON: icon,
|
||||
ATTR_DEVICE_CLASS: device_class,
|
||||
ATTR_SIGN: sign,
|
||||
}
|
||||
for k, (desc, unit, icon, device_class, sign) in sensors.items()
|
||||
]
|
||||
|
||||
|
||||
class DeviceWithOpState(HomeConnectDevice):
|
||||
"""Device that has an operation state sensor."""
|
||||
|
||||
def get_opstate_sensor(self) -> list[dict[str, Any]]:
|
||||
"""Get a list with info about operation state sensors."""
|
||||
|
||||
return [
|
||||
{
|
||||
ATTR_DEVICE: self,
|
||||
ATTR_BSH_KEY: BSH_OPERATION_STATE,
|
||||
ATTR_DESC: "Operation State",
|
||||
ATTR_UNIT: None,
|
||||
ATTR_ICON: "mdi:state-machine",
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_SIGN: 1,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class DeviceWithDoor(HomeConnectDevice):
|
||||
"""Device that has a door sensor."""
|
||||
|
||||
def get_door_entity(self) -> dict[str, Any]:
|
||||
"""Get a dictionary with info about the door binary sensor."""
|
||||
return {
|
||||
ATTR_DEVICE: self,
|
||||
ATTR_BSH_KEY: "Door",
|
||||
ATTR_DESC: "Door",
|
||||
ATTR_SENSOR_TYPE: "door",
|
||||
ATTR_DEVICE_CLASS: "door",
|
||||
}
|
||||
|
||||
|
||||
class DeviceWithLight(HomeConnectDevice):
|
||||
"""Device that has lighting."""
|
||||
|
||||
def get_light_entity(self) -> dict[str, Any]:
|
||||
"""Get a dictionary with info about the lighting."""
|
||||
return {
|
||||
ATTR_DEVICE: self,
|
||||
ATTR_BSH_KEY: COOKING_LIGHTING,
|
||||
ATTR_DESC: "Light",
|
||||
ATTR_AMBIENT: None,
|
||||
}
|
||||
|
||||
|
||||
class DeviceWithAmbientLight(HomeConnectDevice):
|
||||
"""Device that has ambient lighting."""
|
||||
|
||||
def get_ambientlight_entity(self) -> dict[str, Any]:
|
||||
"""Get a dictionary with info about the ambient lighting."""
|
||||
return {
|
||||
ATTR_DEVICE: self,
|
||||
ATTR_BSH_KEY: BSH_AMBIENT_LIGHT_ENABLED,
|
||||
ATTR_DESC: "AmbientLight",
|
||||
ATTR_AMBIENT: True,
|
||||
}
|
||||
|
||||
|
||||
class DeviceWithRemoteControl(HomeConnectDevice):
|
||||
"""Device that has Remote Control binary sensor."""
|
||||
|
||||
def get_remote_control(self) -> dict[str, Any]:
|
||||
"""Get a dictionary with info about the remote control sensor."""
|
||||
return {
|
||||
ATTR_DEVICE: self,
|
||||
ATTR_BSH_KEY: BSH_REMOTE_CONTROL_ACTIVATION_STATE,
|
||||
ATTR_DESC: "Remote Control",
|
||||
ATTR_SENSOR_TYPE: "remote_control",
|
||||
}
|
||||
|
||||
|
||||
class DeviceWithRemoteStart(HomeConnectDevice):
|
||||
"""Device that has a Remote Start binary sensor."""
|
||||
|
||||
def get_remote_start(self) -> dict[str, Any]:
|
||||
"""Get a dictionary with info about the remote start sensor."""
|
||||
return {
|
||||
ATTR_DEVICE: self,
|
||||
ATTR_BSH_KEY: BSH_REMOTE_START_ALLOWANCE_STATE,
|
||||
ATTR_DESC: "Remote Start",
|
||||
ATTR_SENSOR_TYPE: "remote_start",
|
||||
}
|
||||
|
||||
|
||||
class Dryer(
|
||||
DeviceWithDoor,
|
||||
DeviceWithOpState,
|
||||
DeviceWithPrograms,
|
||||
DeviceWithRemoteControl,
|
||||
DeviceWithRemoteStart,
|
||||
):
|
||||
"""Dryer class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
remote_control = self.get_remote_control()
|
||||
remote_start = self.get_remote_start()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [door_entity, remote_control, remote_start],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
}
|
||||
|
||||
|
||||
class Dishwasher(
|
||||
DeviceWithDoor,
|
||||
DeviceWithAmbientLight,
|
||||
DeviceWithOpState,
|
||||
DeviceWithPrograms,
|
||||
DeviceWithRemoteControl,
|
||||
DeviceWithRemoteStart,
|
||||
):
|
||||
"""Dishwasher class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
remote_control = self.get_remote_control()
|
||||
remote_start = self.get_remote_start()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [door_entity, remote_control, remote_start],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
}
|
||||
|
||||
|
||||
class Oven(
|
||||
DeviceWithDoor,
|
||||
DeviceWithOpState,
|
||||
DeviceWithPrograms,
|
||||
DeviceWithRemoteControl,
|
||||
DeviceWithRemoteStart,
|
||||
):
|
||||
"""Oven class."""
|
||||
|
||||
power_off_state = BSH_POWER_STANDBY
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
remote_control = self.get_remote_control()
|
||||
remote_start = self.get_remote_start()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [door_entity, remote_control, remote_start],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
}
|
||||
|
||||
|
||||
class Washer(
|
||||
DeviceWithDoor,
|
||||
DeviceWithOpState,
|
||||
DeviceWithPrograms,
|
||||
DeviceWithRemoteControl,
|
||||
DeviceWithRemoteStart,
|
||||
):
|
||||
"""Washer class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
remote_control = self.get_remote_control()
|
||||
remote_start = self.get_remote_start()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [door_entity, remote_control, remote_start],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
}
|
||||
|
||||
|
||||
class WasherDryer(
|
||||
DeviceWithDoor,
|
||||
DeviceWithOpState,
|
||||
DeviceWithPrograms,
|
||||
DeviceWithRemoteControl,
|
||||
DeviceWithRemoteStart,
|
||||
):
|
||||
"""WasherDryer class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
remote_control = self.get_remote_control()
|
||||
remote_start = self.get_remote_start()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [door_entity, remote_control, remote_start],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
}
|
||||
|
||||
|
||||
class CoffeeMaker(DeviceWithOpState, DeviceWithPrograms, DeviceWithRemoteStart):
|
||||
"""Coffee maker class."""
|
||||
|
||||
power_off_state = BSH_POWER_STANDBY
|
||||
|
||||
def get_entity_info(self):
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
remote_start = self.get_remote_start()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [remote_start],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
}
|
||||
|
||||
|
||||
class Hood(
|
||||
DeviceWithLight,
|
||||
DeviceWithAmbientLight,
|
||||
DeviceWithOpState,
|
||||
DeviceWithPrograms,
|
||||
DeviceWithRemoteControl,
|
||||
DeviceWithRemoteStart,
|
||||
):
|
||||
"""Hood class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
remote_control = self.get_remote_control()
|
||||
remote_start = self.get_remote_start()
|
||||
light_entity = self.get_light_entity()
|
||||
ambientlight_entity = self.get_ambientlight_entity()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [remote_control, remote_start],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
"light": [light_entity, ambientlight_entity],
|
||||
}
|
||||
|
||||
|
||||
class FridgeFreezer(DeviceWithDoor):
|
||||
"""Fridge/Freezer class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
return {"binary_sensor": [door_entity]}
|
||||
|
||||
|
||||
class Refrigerator(DeviceWithDoor):
|
||||
"""Refrigerator class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
return {"binary_sensor": [door_entity]}
|
||||
|
||||
|
||||
class Freezer(DeviceWithDoor):
|
||||
"""Freezer class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
door_entity = self.get_door_entity()
|
||||
return {"binary_sensor": [door_entity]}
|
||||
|
||||
|
||||
class Hob(DeviceWithOpState, DeviceWithPrograms, DeviceWithRemoteControl):
|
||||
"""Hob class."""
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
remote_control = self.get_remote_control()
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
program_sensors = self.get_program_sensors()
|
||||
program_switches = self.get_program_switches()
|
||||
return {
|
||||
"binary_sensor": [remote_control],
|
||||
"switch": program_switches,
|
||||
"sensor": program_sensors + op_state_sensor,
|
||||
}
|
||||
|
||||
|
||||
class CookProcessor(DeviceWithOpState):
|
||||
"""CookProcessor class."""
|
||||
|
||||
power_off_state = BSH_POWER_STANDBY
|
||||
|
||||
def get_entity_info(self) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get a dictionary with infos about the associated entities."""
|
||||
op_state_sensor = self.get_opstate_sensor()
|
||||
return {"sensor": op_state_sensor}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Provides a binary sensor for Home Connect."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
|
@ -9,13 +9,11 @@ from homeassistant.components.binary_sensor import (
|
|||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ENTITIES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .api import HomeConnectDevice
|
||||
from .const import (
|
||||
ATTR_DEVICE,
|
||||
ATTR_VALUE,
|
||||
BSH_DOOR_STATE,
|
||||
BSH_DOOR_STATE_CLOSED,
|
||||
|
@ -33,34 +31,80 @@ from .const import (
|
|||
from .entity import HomeConnectEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REFRIGERATION_DOOR_BOOLEAN_MAP = {
|
||||
REFRIGERATION_STATUS_DOOR_CLOSED: False,
|
||||
REFRIGERATION_STATUS_DOOR_OPEN: True,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class HomeConnectBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Entity Description class for binary sensors."""
|
||||
|
||||
desc: str
|
||||
device_class: BinarySensorDeviceClass | None = BinarySensorDeviceClass.DOOR
|
||||
boolean_map: dict[str, bool] = field(
|
||||
default_factory=lambda: {
|
||||
REFRIGERATION_STATUS_DOOR_CLOSED: False,
|
||||
REFRIGERATION_STATUS_DOOR_OPEN: True,
|
||||
}
|
||||
)
|
||||
boolean_map: dict[str, bool] | None = None
|
||||
|
||||
|
||||
BINARY_SENSORS: tuple[HomeConnectBinarySensorEntityDescription, ...] = (
|
||||
BINARY_SENSORS = (
|
||||
BinarySensorEntityDescription(
|
||||
key=BSH_REMOTE_CONTROL_ACTIVATION_STATE,
|
||||
translation_key="remote_control",
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key=BSH_REMOTE_START_ALLOWANCE_STATE,
|
||||
translation_key="remote_start",
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="BSH.Common.Status.LocalControlActive",
|
||||
translation_key="local_control",
|
||||
),
|
||||
HomeConnectBinarySensorEntityDescription(
|
||||
key="BSH.Common.Status.BatteryChargingState",
|
||||
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
||||
boolean_map={
|
||||
"BSH.Common.EnumType.BatteryChargingState.Charging": True,
|
||||
"BSH.Common.EnumType.BatteryChargingState.Discharging": False,
|
||||
},
|
||||
translation_key="battery_charging_state",
|
||||
),
|
||||
HomeConnectBinarySensorEntityDescription(
|
||||
key="BSH.Common.Status.ChargingConnection",
|
||||
device_class=BinarySensorDeviceClass.PLUG,
|
||||
boolean_map={
|
||||
"BSH.Common.EnumType.ChargingConnection.Connected": True,
|
||||
"BSH.Common.EnumType.ChargingConnection.Disconnected": False,
|
||||
},
|
||||
translation_key="charging_connection",
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="ConsumerProducts.CleaningRobot.Status.DustBoxInserted",
|
||||
translation_key="dust_box_inserted",
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="ConsumerProducts.CleaningRobot.Status.Lifted",
|
||||
translation_key="lifted",
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key="ConsumerProducts.CleaningRobot.Status.Lost",
|
||||
translation_key="lost",
|
||||
),
|
||||
HomeConnectBinarySensorEntityDescription(
|
||||
key=REFRIGERATION_STATUS_DOOR_CHILLER,
|
||||
desc="Chiller Door",
|
||||
boolean_map=REFRIGERATION_DOOR_BOOLEAN_MAP,
|
||||
device_class=BinarySensorDeviceClass.DOOR,
|
||||
translation_key="chiller_door",
|
||||
),
|
||||
HomeConnectBinarySensorEntityDescription(
|
||||
key=REFRIGERATION_STATUS_DOOR_FREEZER,
|
||||
desc="Freezer Door",
|
||||
boolean_map=REFRIGERATION_DOOR_BOOLEAN_MAP,
|
||||
device_class=BinarySensorDeviceClass.DOOR,
|
||||
translation_key="freezer_door",
|
||||
),
|
||||
HomeConnectBinarySensorEntityDescription(
|
||||
key=REFRIGERATION_STATUS_DOOR_REFRIGERATOR,
|
||||
desc="Refrigerator Door",
|
||||
boolean_map=REFRIGERATION_DOOR_BOOLEAN_MAP,
|
||||
device_class=BinarySensorDeviceClass.DOOR,
|
||||
translation_key="refrigerator_door",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -75,18 +119,14 @@ async def async_setup_entry(
|
|||
def get_entities() -> list[BinarySensorEntity]:
|
||||
entities: list[BinarySensorEntity] = []
|
||||
hc_api = hass.data[DOMAIN][config_entry.entry_id]
|
||||
for device_dict in hc_api.devices:
|
||||
entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("binary_sensor", [])
|
||||
entities += [HomeConnectBinarySensor(**d) for d in entity_dicts]
|
||||
device: HomeConnectDevice = device_dict[ATTR_DEVICE]
|
||||
# Auto-discover entities
|
||||
for device in hc_api.devices:
|
||||
entities.extend(
|
||||
HomeConnectFridgeDoorBinarySensor(
|
||||
device=device, entity_description=description
|
||||
)
|
||||
HomeConnectBinarySensor(device, description)
|
||||
for description in BINARY_SENSORS
|
||||
if description.key in device.appliance.status
|
||||
)
|
||||
if BSH_DOOR_STATE in device.appliance.status:
|
||||
entities.append(HomeConnectDoorBinarySensor(device))
|
||||
return entities
|
||||
|
||||
async_add_entities(await hass.async_add_executor_job(get_entities), True)
|
||||
|
@ -95,28 +135,7 @@ async def async_setup_entry(
|
|||
class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity):
|
||||
"""Binary sensor for Home Connect."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: HomeConnectDevice,
|
||||
bsh_key: str,
|
||||
desc: str,
|
||||
sensor_type: str,
|
||||
device_class: BinarySensorDeviceClass | None = None,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(device, bsh_key, desc)
|
||||
self._attr_device_class = device_class
|
||||
self._type = sensor_type
|
||||
self._false_value_list = None
|
||||
self._true_value_list = None
|
||||
if self._type == "door":
|
||||
self._update_key = BSH_DOOR_STATE
|
||||
self._false_value_list = [BSH_DOOR_STATE_CLOSED, BSH_DOOR_STATE_LOCKED]
|
||||
self._true_value_list = [BSH_DOOR_STATE_OPEN]
|
||||
elif self._type == "remote_control":
|
||||
self._update_key = BSH_REMOTE_CONTROL_ACTIVATION_STATE
|
||||
elif self._type == "remote_start":
|
||||
self._update_key = BSH_REMOTE_START_ALLOWANCE_STATE
|
||||
entity_description: HomeConnectBinarySensorEntityDescription
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
@ -125,59 +144,41 @@ class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity):
|
|||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the binary sensor's status."""
|
||||
state = self.device.appliance.status.get(self._update_key, {})
|
||||
if not state:
|
||||
if not self.device.appliance.status or not (
|
||||
status := self.device.appliance.status.get(self.bsh_key, {}).get(ATTR_VALUE)
|
||||
):
|
||||
self._attr_is_on = None
|
||||
return
|
||||
|
||||
value = state.get(ATTR_VALUE)
|
||||
if self._false_value_list and self._true_value_list:
|
||||
if value in self._false_value_list:
|
||||
self._attr_is_on = False
|
||||
elif value in self._true_value_list:
|
||||
self._attr_is_on = True
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Unexpected value for HomeConnect %s state: %s", self._type, state
|
||||
)
|
||||
self._attr_is_on = None
|
||||
elif isinstance(value, bool):
|
||||
self._attr_is_on = value
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Unexpected value for HomeConnect %s state: %s", self._type, state
|
||||
)
|
||||
if self.entity_description.boolean_map:
|
||||
self._attr_is_on = self.entity_description.boolean_map.get(status)
|
||||
elif status not in [True, False]:
|
||||
self._attr_is_on = None
|
||||
else:
|
||||
self._attr_is_on = status
|
||||
_LOGGER.debug("Updated, new state: %s", self._attr_is_on)
|
||||
|
||||
|
||||
class HomeConnectFridgeDoorBinarySensor(HomeConnectEntity, BinarySensorEntity):
|
||||
"""Binary sensor for Home Connect Fridge Doors."""
|
||||
class HomeConnectDoorBinarySensor(HomeConnectBinarySensor):
|
||||
"""Binary sensor for Home Connect Generic Door."""
|
||||
|
||||
entity_description: HomeConnectBinarySensorEntityDescription
|
||||
_attr_has_entity_name = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: HomeConnectDevice,
|
||||
entity_description: HomeConnectBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.entity_description = entity_description
|
||||
super().__init__(device, entity_description.key, entity_description.desc)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the binary sensor's status."""
|
||||
_LOGGER.debug(
|
||||
"Updating: %s, cur state: %s",
|
||||
self._attr_unique_id,
|
||||
self.state,
|
||||
)
|
||||
self._attr_is_on = self.entity_description.boolean_map.get(
|
||||
self.device.appliance.status.get(self.bsh_key, {}).get(ATTR_VALUE)
|
||||
)
|
||||
self._attr_available = self._attr_is_on is not None
|
||||
_LOGGER.debug(
|
||||
"Updated: %s, new state: %s",
|
||||
self._attr_unique_id,
|
||||
self.state,
|
||||
super().__init__(
|
||||
device,
|
||||
HomeConnectBinarySensorEntityDescription(
|
||||
key=BSH_DOOR_STATE,
|
||||
device_class=BinarySensorDeviceClass.DOOR,
|
||||
boolean_map={
|
||||
BSH_DOOR_STATE_CLOSED: False,
|
||||
BSH_DOOR_STATE_LOCKED: False,
|
||||
BSH_DOOR_STATE_OPEN: True,
|
||||
},
|
||||
),
|
||||
)
|
||||
self._attr_unique_id = f"{device.appliance.haId}-Door"
|
||||
self._attr_name = f"{device.appliance.name} Door"
|
||||
|
|
|
@ -5,7 +5,7 @@ import logging
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
|
||||
from .api import HomeConnectDevice
|
||||
from .const import DOMAIN, SIGNAL_UPDATE_ENTITIES
|
||||
|
@ -17,13 +17,13 @@ class HomeConnectEntity(Entity):
|
|||
"""Generic Home Connect entity (base class)."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, device: HomeConnectDevice, bsh_key: str, desc: str) -> None:
|
||||
def __init__(self, device: HomeConnectDevice, desc: EntityDescription) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.device = device
|
||||
self.bsh_key = bsh_key
|
||||
self._attr_name = f"{device.appliance.name} {desc}"
|
||||
self._attr_unique_id = f"{device.appliance.haId}-{bsh_key}"
|
||||
self.entity_description = desc
|
||||
self._attr_unique_id = f"{device.appliance.haId}-{self.bsh_key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, device.appliance.haId)},
|
||||
manufacturer=device.appliance.brand,
|
||||
|
@ -50,3 +50,8 @@ class HomeConnectEntity(Entity):
|
|||
"""Update the entity."""
|
||||
_LOGGER.debug("Entity update triggered on %s", self)
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
@property
|
||||
def bsh_key(self) -> str:
|
||||
"""Return the BSH key."""
|
||||
return self.entity_description.key
|
||||
|
|
|
@ -23,43 +23,127 @@
|
|||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"remote_control": {
|
||||
"default": "mdi:remote",
|
||||
"state": {
|
||||
"off": "mdi:remote-off"
|
||||
}
|
||||
},
|
||||
"remote_start": {
|
||||
"default": "mdi:remote",
|
||||
"state": {
|
||||
"off": "mdi:remote-off"
|
||||
}
|
||||
},
|
||||
"dust_box_inserted": {
|
||||
"default": "mdi:download"
|
||||
},
|
||||
"lifted": {
|
||||
"default": "mdi:arrow-up-right-bold"
|
||||
},
|
||||
"lost": {
|
||||
"default": "mdi:map-marker-remove-variant"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"alarm_sensor_fridge": {
|
||||
"operation_state": {
|
||||
"default": "mdi:state-machine",
|
||||
"state": {
|
||||
"inactive": "mdi:stop",
|
||||
"ready": "mdi:check-circle",
|
||||
"delayedstart": "mdi:progress-clock",
|
||||
"run": "mdi:play",
|
||||
"pause": "mdi:pause",
|
||||
"actionrequired": "mdi:gesture-tap",
|
||||
"finished": "mdi:flag-checkered",
|
||||
"error": "mdi:alert-circle",
|
||||
"aborting": "mdi:close-circle"
|
||||
}
|
||||
},
|
||||
"program_progress": {
|
||||
"default": "mdi:progress-clock"
|
||||
},
|
||||
"coffee_counter": {
|
||||
"default": "mdi:coffee"
|
||||
},
|
||||
"powder_coffee_counter": {
|
||||
"default": "mdi:coffee"
|
||||
},
|
||||
"hot_water_counter": {
|
||||
"default": "mdi:cup-water"
|
||||
},
|
||||
"hot_water_cups_counter": {
|
||||
"default": "mdi:cup"
|
||||
},
|
||||
"hot_milk_counter": {
|
||||
"default": "mdi:cup"
|
||||
},
|
||||
"frothy_milk_counter": {
|
||||
"default": "mdi:cup"
|
||||
},
|
||||
"milk_counter": {
|
||||
"default": "mdi:cup"
|
||||
},
|
||||
"coffee_and_milk": {
|
||||
"default": "mdi:coffee"
|
||||
},
|
||||
"ristretto_espresso_counter": {
|
||||
"default": "mdi:coffee"
|
||||
},
|
||||
"camera_state": {
|
||||
"default": "mdi:camera",
|
||||
"state": {
|
||||
"disabled": "mdi:camera-off",
|
||||
"sleeping": "mdi:sleep",
|
||||
"error": "mdi:alert-circle-outline"
|
||||
}
|
||||
},
|
||||
"last_selected_map": {
|
||||
"default": "mdi:map",
|
||||
"state": {
|
||||
"tempmap": "mdi:map-clock-outline",
|
||||
"map1": "mdi:numeric-1",
|
||||
"map2": "mdi:numeric-2",
|
||||
"map3": "mdi:numeric-3"
|
||||
}
|
||||
},
|
||||
"refrigerator_door_alarm": {
|
||||
"default": "mdi:fridge",
|
||||
"state": {
|
||||
"confirmed": "mdi:fridge-alert-outline",
|
||||
"present": "mdi:fridge-alert"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_freezer": {
|
||||
"freezer_door_alarm": {
|
||||
"default": "mdi:snowflake",
|
||||
"state": {
|
||||
"confirmed": "mdi:snowflake-check",
|
||||
"present": "mdi:snowflake-alert"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_temp": {
|
||||
"freezer_temperature_alarm": {
|
||||
"default": "mdi:thermometer",
|
||||
"state": {
|
||||
"confirmed": "mdi:thermometer-check",
|
||||
"present": "mdi:thermometer-alert"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_coffee_bean_container": {
|
||||
"bean_container_empty": {
|
||||
"default": "mdi:coffee-maker",
|
||||
"state": {
|
||||
"confirmed": "mdi:coffee-maker-check",
|
||||
"present": "mdi:coffee-maker-outline"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_coffee_water_tank": {
|
||||
"water_tank_empty": {
|
||||
"default": "mdi:water",
|
||||
"state": {
|
||||
"confirmed": "mdi:water-check",
|
||||
"present": "mdi:water-alert"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_coffee_drip_tray": {
|
||||
"drip_tray_full": {
|
||||
"default": "mdi:tray",
|
||||
"state": {
|
||||
"confirmed": "mdi:tray-full",
|
||||
|
@ -68,11 +152,51 @@
|
|||
}
|
||||
},
|
||||
"switch": {
|
||||
"refrigeration_dispenser": {
|
||||
"power": {
|
||||
"default": "mdi:power"
|
||||
},
|
||||
"child_lock": {
|
||||
"default": "mdi:lock",
|
||||
"state": {
|
||||
"on": "mdi:lock",
|
||||
"off": "mdi:lock-off"
|
||||
}
|
||||
},
|
||||
"cup_warmer": {
|
||||
"default": "mdi:heat-wave"
|
||||
},
|
||||
"refrigerator_super_mode": {
|
||||
"default": "mdi:speedometer"
|
||||
},
|
||||
"freezer_super_mode": {
|
||||
"default": "mdi:speedometer"
|
||||
},
|
||||
"eco_mode": {
|
||||
"default": "mdi:sprout"
|
||||
},
|
||||
"cooking-oven-setting-sabbath_mode": {
|
||||
"default": "mdi:volume-mute"
|
||||
},
|
||||
"sabbath_mode": {
|
||||
"default": "mdi:volume-mute"
|
||||
},
|
||||
"vacation_mode": {
|
||||
"default": "mdi:beach"
|
||||
},
|
||||
"fresh_mode": {
|
||||
"default": "mdi:leaf"
|
||||
},
|
||||
"dispenser_enabled": {
|
||||
"default": "mdi:snowflake",
|
||||
"state": {
|
||||
"off": "mdi:snowflake-off"
|
||||
}
|
||||
},
|
||||
"door-assistant_fridge": {
|
||||
"default": "mdi:door"
|
||||
},
|
||||
"door-assistant_freezer": {
|
||||
"default": "mdi:door"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ from homeassistant.components.light import (
|
|||
LightEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DEVICE, CONF_ENTITIES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
import homeassistant.util.color as color_util
|
||||
|
@ -27,6 +26,8 @@ from .const import (
|
|||
BSH_AMBIENT_LIGHT_COLOR,
|
||||
BSH_AMBIENT_LIGHT_COLOR_CUSTOM_COLOR,
|
||||
BSH_AMBIENT_LIGHT_CUSTOM_COLOR,
|
||||
BSH_AMBIENT_LIGHT_ENABLED,
|
||||
COOKING_LIGHTING,
|
||||
COOKING_LIGHTING_BRIGHTNESS,
|
||||
DOMAIN,
|
||||
REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS,
|
||||
|
@ -43,20 +44,19 @@ _LOGGER = logging.getLogger(__name__)
|
|||
class HomeConnectLightEntityDescription(LightEntityDescription):
|
||||
"""Light entity description."""
|
||||
|
||||
desc: str
|
||||
brightness_key: str | None
|
||||
|
||||
|
||||
LIGHTS: tuple[HomeConnectLightEntityDescription, ...] = (
|
||||
HomeConnectLightEntityDescription(
|
||||
key=REFRIGERATION_INTERNAL_LIGHT_POWER,
|
||||
desc="Internal Light",
|
||||
brightness_key=REFRIGERATION_INTERNAL_LIGHT_BRIGHTNESS,
|
||||
translation_key="internal_light",
|
||||
),
|
||||
HomeConnectLightEntityDescription(
|
||||
key=REFRIGERATION_EXTERNAL_LIGHT_POWER,
|
||||
desc="External Light",
|
||||
brightness_key=REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS,
|
||||
translation_key="external_light",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -72,11 +72,29 @@ async def async_setup_entry(
|
|||
"""Get a list of entities."""
|
||||
entities: list[LightEntity] = []
|
||||
hc_api = hass.data[DOMAIN][config_entry.entry_id]
|
||||
for device_dict in hc_api.devices:
|
||||
entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("light", [])
|
||||
entity_list = [HomeConnectLight(**d) for d in entity_dicts]
|
||||
device: HomeConnectDevice = device_dict[CONF_DEVICE]
|
||||
# Auto-discover entities
|
||||
for device in hc_api.devices:
|
||||
if COOKING_LIGHTING in device.appliance.status:
|
||||
entities.append(
|
||||
HomeConnectLight(
|
||||
device,
|
||||
LightEntityDescription(
|
||||
key=COOKING_LIGHTING,
|
||||
translation_key="cooking_lighting",
|
||||
),
|
||||
False,
|
||||
)
|
||||
)
|
||||
if BSH_AMBIENT_LIGHT_ENABLED in device.appliance.status:
|
||||
entities.append(
|
||||
HomeConnectLight(
|
||||
device,
|
||||
LightEntityDescription(
|
||||
key=BSH_AMBIENT_LIGHT_ENABLED,
|
||||
translation_key="ambient_light",
|
||||
),
|
||||
True,
|
||||
)
|
||||
)
|
||||
entities.extend(
|
||||
HomeConnectCoolingLight(
|
||||
device=device,
|
||||
|
@ -86,7 +104,6 @@ async def async_setup_entry(
|
|||
for description in LIGHTS
|
||||
if description.key in device.appliance.status
|
||||
)
|
||||
entities.extend(entity_list)
|
||||
return entities
|
||||
|
||||
async_add_entities(await hass.async_add_executor_job(get_entities), True)
|
||||
|
@ -95,11 +112,16 @@ async def async_setup_entry(
|
|||
class HomeConnectLight(HomeConnectEntity, LightEntity):
|
||||
"""Light for Home Connect."""
|
||||
|
||||
entity_description: LightEntityDescription
|
||||
|
||||
def __init__(
|
||||
self, device: HomeConnectDevice, bsh_key: str, desc: str, ambient: bool
|
||||
self,
|
||||
device: HomeConnectDevice,
|
||||
desc: LightEntityDescription,
|
||||
ambient: bool,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(device, bsh_key, desc)
|
||||
super().__init__(device, desc)
|
||||
self._ambient = ambient
|
||||
self._percentage_scale = (10, 100)
|
||||
self._brightness_key: str | None
|
||||
|
@ -255,9 +277,7 @@ class HomeConnectCoolingLight(HomeConnectLight):
|
|||
entity_description: HomeConnectLightEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Cooling Light Entity."""
|
||||
super().__init__(
|
||||
device, entity_description.key, entity_description.desc, ambient
|
||||
)
|
||||
super().__init__(device, entity_description, ambient)
|
||||
self.entity_description = entity_description
|
||||
self._brightness_key = entity_description.brightness_key
|
||||
self._percentage_scale = (1, 100)
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
"""Provides a sensor for Home Connect."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import contextlib
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from homeconnect.api import HomeConnectError
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ENTITIES
|
||||
from homeassistant.const import PERCENTAGE, UnitOfTime, UnitOfVolume
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import slugify
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .api import ConfigEntryAuth, HomeConnectDevice
|
||||
from .api import ConfigEntryAuth
|
||||
from .const import (
|
||||
ATTR_DEVICE,
|
||||
ATTR_VALUE,
|
||||
BSH_EVENT_PRESENT_STATE_OFF,
|
||||
BSH_OPERATION_STATE,
|
||||
BSH_OPERATION_STATE_FINISHED,
|
||||
BSH_OPERATION_STATE_PAUSE,
|
||||
|
@ -38,47 +41,182 @@ from .entity import HomeConnectEntity
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
EVENT_OPTIONS = ["confirmed", "off", "present"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class HomeConnectSensorEntityDescription(SensorEntityDescription):
|
||||
"""Entity Description class for sensors."""
|
||||
|
||||
device_class: SensorDeviceClass | None = SensorDeviceClass.ENUM
|
||||
options: list[str] | None = field(
|
||||
default_factory=lambda: ["confirmed", "off", "present"]
|
||||
)
|
||||
desc: str
|
||||
appliance_types: tuple[str, ...]
|
||||
default_value: str | None = None
|
||||
appliance_types: tuple[str, ...] | None = None
|
||||
sign: int = 1
|
||||
|
||||
|
||||
SENSORS: tuple[HomeConnectSensorEntityDescription, ...] = (
|
||||
BSH_PROGRAM_SENSORS = (
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="BSH.Common.Option.RemainingProgramTime",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
sign=1,
|
||||
translation_key="program_finish_time",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="BSH.Common.Option.Duration",
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
sign=1,
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="BSH.Common.Option.ProgramProgress",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
sign=1,
|
||||
translation_key="program_progress",
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS = (
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=BSH_OPERATION_STATE,
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[
|
||||
"inactive",
|
||||
"ready",
|
||||
"delayedstart",
|
||||
"run",
|
||||
"pause",
|
||||
"actionrequired",
|
||||
"finished",
|
||||
"error",
|
||||
"aborting",
|
||||
],
|
||||
translation_key="operation_state",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterCoffee",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="coffee_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterPowderCoffee",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="powder_coffee_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterHotWater",
|
||||
native_unit_of_measurement=UnitOfVolume.MILLILITERS,
|
||||
device_class=SensorDeviceClass.VOLUME,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="hot_water_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterHotWaterCups",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="hot_water_cups_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterHotMilk",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="hot_milk_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterFrothyMilk",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="frothy_milk_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterMilk",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="milk_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterCoffeeAndMilk",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="coffee_and_milk_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Status.BeverageCounterRistrettoEspresso",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="ristretto_espresso_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="BSH.Common.Status.BatteryLevel",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
translation_key="battery_level",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="BSH.Common.Status.Video.CameraState",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[
|
||||
"disabled",
|
||||
"sleeping",
|
||||
"ready",
|
||||
"streaminglocal",
|
||||
"streamingcloud",
|
||||
"streaminglocalancloud",
|
||||
"error",
|
||||
],
|
||||
translation_key="camera_state",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key="ConsumerProducts.CleaningRobot.Status.LastSelectedMap",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[
|
||||
"tempmap",
|
||||
"map1",
|
||||
"map2",
|
||||
"map3",
|
||||
],
|
||||
translation_key="last_selected_map",
|
||||
),
|
||||
)
|
||||
|
||||
EVENT_SENSORS = (
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=REFRIGERATION_EVENT_DOOR_ALARM_FREEZER,
|
||||
desc="Door Alarm Freezer",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=EVENT_OPTIONS,
|
||||
default_value="off",
|
||||
translation_key="freezer_door_alarm",
|
||||
appliance_types=("FridgeFreezer", "Freezer"),
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=REFRIGERATION_EVENT_DOOR_ALARM_REFRIGERATOR,
|
||||
desc="Door Alarm Refrigerator",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=EVENT_OPTIONS,
|
||||
default_value="off",
|
||||
translation_key="refrigerator_door_alarm",
|
||||
appliance_types=("FridgeFreezer", "Refrigerator"),
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=REFRIGERATION_EVENT_TEMP_ALARM_FREEZER,
|
||||
desc="Temperature Alarm Freezer",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=EVENT_OPTIONS,
|
||||
default_value="off",
|
||||
translation_key="freezer_temperature_alarm",
|
||||
appliance_types=("FridgeFreezer", "Freezer"),
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=COFFEE_EVENT_BEAN_CONTAINER_EMPTY,
|
||||
desc="Bean Container Empty",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=EVENT_OPTIONS,
|
||||
default_value="off",
|
||||
translation_key="bean_container_empty",
|
||||
appliance_types=("CoffeeMaker",),
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=COFFEE_EVENT_WATER_TANK_EMPTY,
|
||||
desc="Water Tank Empty",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=EVENT_OPTIONS,
|
||||
default_value="off",
|
||||
translation_key="water_tank_empty",
|
||||
appliance_types=("CoffeeMaker",),
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=COFFEE_EVENT_DRIP_TRAY_FULL,
|
||||
desc="Drip Tray Full",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=EVENT_OPTIONS,
|
||||
default_value="off",
|
||||
translation_key="drip_tray_full",
|
||||
appliance_types=("CoffeeMaker",),
|
||||
),
|
||||
)
|
||||
|
@ -95,18 +233,25 @@ async def async_setup_entry(
|
|||
"""Get a list of entities."""
|
||||
entities: list[SensorEntity] = []
|
||||
hc_api: ConfigEntryAuth = hass.data[DOMAIN][config_entry.entry_id]
|
||||
for device_dict in hc_api.devices:
|
||||
entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("sensor", [])
|
||||
entities += [HomeConnectSensor(**d) for d in entity_dicts]
|
||||
device: HomeConnectDevice = device_dict[ATTR_DEVICE]
|
||||
# Auto-discover entities
|
||||
for device in hc_api.devices:
|
||||
entities.extend(
|
||||
HomeConnectAlarmSensor(
|
||||
HomeConnectSensor(
|
||||
device,
|
||||
entity_description=description,
|
||||
description,
|
||||
)
|
||||
for description in EVENT_SENSORS
|
||||
if description.appliance_types
|
||||
and device.appliance.type in description.appliance_types
|
||||
)
|
||||
with contextlib.suppress(HomeConnectError):
|
||||
if device.appliance.get_programs_available():
|
||||
entities.extend(
|
||||
HomeConnectSensor(device, desc) for desc in BSH_PROGRAM_SENSORS
|
||||
)
|
||||
entities.extend(
|
||||
HomeConnectSensor(device, description)
|
||||
for description in SENSORS
|
||||
if device.appliance.type in description.appliance_types
|
||||
if description.key in device.appliance.status
|
||||
)
|
||||
return entities
|
||||
|
||||
|
@ -116,25 +261,7 @@ async def async_setup_entry(
|
|||
class HomeConnectSensor(HomeConnectEntity, SensorEntity):
|
||||
"""Sensor class for Home Connect."""
|
||||
|
||||
_key: str
|
||||
_sign: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: HomeConnectDevice,
|
||||
bsh_key: str,
|
||||
desc: str,
|
||||
unit: str,
|
||||
icon: str,
|
||||
device_class: SensorDeviceClass,
|
||||
sign: int = 1,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(device, bsh_key, desc)
|
||||
self._sign = sign
|
||||
self._attr_native_unit_of_measurement = unit
|
||||
self._attr_icon = icon
|
||||
self._attr_device_class = device_class
|
||||
entity_description: HomeConnectSensorEntityDescription
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
@ -143,78 +270,52 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
|
|||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the sensor's status."""
|
||||
status = self.device.appliance.status
|
||||
if self.bsh_key not in status:
|
||||
self._attr_native_value = None
|
||||
elif self.device_class == SensorDeviceClass.TIMESTAMP:
|
||||
if ATTR_VALUE not in status[self.bsh_key]:
|
||||
self._attr_native_value = None
|
||||
elif (
|
||||
self._attr_native_value is not None
|
||||
and self._sign == 1
|
||||
and isinstance(self._attr_native_value, datetime)
|
||||
and self._attr_native_value < dt_util.utcnow()
|
||||
):
|
||||
# if the date is supposed to be in the future but we're
|
||||
# already past it, set state to None.
|
||||
self._attr_native_value = None
|
||||
elif (
|
||||
BSH_OPERATION_STATE in status
|
||||
and ATTR_VALUE in status[BSH_OPERATION_STATE]
|
||||
and status[BSH_OPERATION_STATE][ATTR_VALUE]
|
||||
in [
|
||||
BSH_OPERATION_STATE_RUN,
|
||||
BSH_OPERATION_STATE_PAUSE,
|
||||
BSH_OPERATION_STATE_FINISHED,
|
||||
]
|
||||
):
|
||||
seconds = self._sign * float(status[self.bsh_key][ATTR_VALUE])
|
||||
self._attr_native_value = dt_util.utcnow() + timedelta(seconds=seconds)
|
||||
else:
|
||||
self._attr_native_value = None
|
||||
else:
|
||||
self._attr_native_value = status[self.bsh_key].get(ATTR_VALUE)
|
||||
if self.bsh_key == BSH_OPERATION_STATE:
|
||||
appliance_status = self.device.appliance.status
|
||||
if (
|
||||
self.bsh_key not in appliance_status
|
||||
or ATTR_VALUE not in appliance_status[self.bsh_key]
|
||||
):
|
||||
self._attr_native_value = self.entity_description.default_value
|
||||
_LOGGER.debug("Updated, new state: %s", self._attr_native_value)
|
||||
return
|
||||
status = appliance_status[self.bsh_key]
|
||||
match self.device_class:
|
||||
case SensorDeviceClass.TIMESTAMP:
|
||||
if ATTR_VALUE not in status:
|
||||
self._attr_native_value = None
|
||||
elif (
|
||||
self._attr_native_value is not None
|
||||
and self.entity_description.sign == 1
|
||||
and isinstance(self._attr_native_value, datetime)
|
||||
and self._attr_native_value < dt_util.utcnow()
|
||||
):
|
||||
# if the date is supposed to be in the future but we're
|
||||
# already past it, set state to None.
|
||||
self._attr_native_value = None
|
||||
elif (
|
||||
BSH_OPERATION_STATE
|
||||
in (appliance_status := self.device.appliance.status)
|
||||
and ATTR_VALUE in appliance_status[BSH_OPERATION_STATE]
|
||||
and appliance_status[BSH_OPERATION_STATE][ATTR_VALUE]
|
||||
in [
|
||||
BSH_OPERATION_STATE_RUN,
|
||||
BSH_OPERATION_STATE_PAUSE,
|
||||
BSH_OPERATION_STATE_FINISHED,
|
||||
]
|
||||
):
|
||||
seconds = self.entity_description.sign * float(status[ATTR_VALUE])
|
||||
self._attr_native_value = dt_util.utcnow() + timedelta(
|
||||
seconds=seconds
|
||||
)
|
||||
else:
|
||||
self._attr_native_value = None
|
||||
case SensorDeviceClass.ENUM:
|
||||
# Value comes back as an enum, we only really care about the
|
||||
# last part, so split it off
|
||||
# https://developer.home-connect.com/docs/status/operation_state
|
||||
self._attr_native_value = cast(str, self._attr_native_value).split(".")[
|
||||
-1
|
||||
]
|
||||
self._attr_native_value = slugify(
|
||||
cast(str, status.get(ATTR_VALUE)).split(".")[-1]
|
||||
)
|
||||
case _:
|
||||
self._attr_native_value = status.get(ATTR_VALUE)
|
||||
_LOGGER.debug("Updated, new state: %s", self._attr_native_value)
|
||||
|
||||
|
||||
class HomeConnectAlarmSensor(HomeConnectEntity, SensorEntity):
|
||||
"""Sensor entity setup using SensorEntityDescription."""
|
||||
|
||||
entity_description: HomeConnectSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: HomeConnectDevice,
|
||||
entity_description: HomeConnectSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.entity_description = entity_description
|
||||
super().__init__(
|
||||
device, self.entity_description.key, self.entity_description.desc
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return true if the sensor is available."""
|
||||
return self._attr_native_value is not None
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the sensor's status."""
|
||||
self._attr_native_value = (
|
||||
self.device.appliance.status.get(self.bsh_key, {})
|
||||
.get(ATTR_VALUE, BSH_EVENT_PRESENT_STATE_OFF)
|
||||
.rsplit(".", maxsplit=1)[-1]
|
||||
.lower()
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Updated: %s, new state: %s",
|
||||
self._attr_unique_id,
|
||||
self._attr_native_value,
|
||||
)
|
||||
|
|
|
@ -135,43 +135,220 @@
|
|||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"remote_control": {
|
||||
"name": "Remote control"
|
||||
},
|
||||
"remote_start": {
|
||||
"name": "Remote start"
|
||||
},
|
||||
"local_control": {
|
||||
"name": "Local control"
|
||||
},
|
||||
"battery_charging_state": {
|
||||
"name": "Battery charging state"
|
||||
},
|
||||
"charging_connection": {
|
||||
"name": "Charging connection"
|
||||
},
|
||||
"dust_box_inserted": {
|
||||
"name": "Dust box",
|
||||
"state": {
|
||||
"on": "Inserted",
|
||||
"off": "Not inserted"
|
||||
}
|
||||
},
|
||||
"lifted": {
|
||||
"name": "Lifted"
|
||||
},
|
||||
"lost": {
|
||||
"name": "Lost"
|
||||
},
|
||||
"chiller_door": {
|
||||
"name": "Chiller door"
|
||||
},
|
||||
"freezer_door": {
|
||||
"name": "Freezer door"
|
||||
},
|
||||
"refrigerator_door": {
|
||||
"name": "Refrigerator door"
|
||||
}
|
||||
},
|
||||
"light": {
|
||||
"cooking_lighting": {
|
||||
"name": "Functional light"
|
||||
},
|
||||
"ambient_light": {
|
||||
"name": "Ambient light"
|
||||
},
|
||||
"external_light": {
|
||||
"name": "External light"
|
||||
},
|
||||
"internal_light": {
|
||||
"name": "Internal light"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"alarm_sensor_fridge": {
|
||||
"program_progress": {
|
||||
"name": "Program progress"
|
||||
},
|
||||
"program_finish_time": {
|
||||
"name": "Program finish time"
|
||||
},
|
||||
"operation_state": {
|
||||
"name": "Operation state",
|
||||
"state": {
|
||||
"inactive": "Inactive",
|
||||
"ready": "Ready",
|
||||
"delayedstart": "Delayed start",
|
||||
"run": "Run",
|
||||
"pause": "[%key:common::state::paused%]",
|
||||
"actionrequired": "Action required",
|
||||
"finished": "Finished",
|
||||
"error": "Error",
|
||||
"aborting": "Aborting"
|
||||
}
|
||||
},
|
||||
"coffee_counter": {
|
||||
"name": "Coffees"
|
||||
},
|
||||
"powder_coffee_counter": {
|
||||
"name": "Powder coffees"
|
||||
},
|
||||
"hot_water_counter": {
|
||||
"name": "Hot water"
|
||||
},
|
||||
"hot_water_cups_counter": {
|
||||
"name": "Hot water cups"
|
||||
},
|
||||
"hot_milk_counter": {
|
||||
"name": "Hot milk cups"
|
||||
},
|
||||
"frothy_milk_counter": {
|
||||
"name": "Frothy milk cups"
|
||||
},
|
||||
"milk_counter": {
|
||||
"name": "Milk cups"
|
||||
},
|
||||
"coffee_and_milk_counter": {
|
||||
"name": "Coffee and milk cups"
|
||||
},
|
||||
"ristretto_espresso_counter": {
|
||||
"name": "Ristretto espresso cups"
|
||||
},
|
||||
"battery_level": {
|
||||
"name": "Battery level"
|
||||
},
|
||||
"camera_state": {
|
||||
"name": "Camera state",
|
||||
"state": {
|
||||
"disabled": "[%key:common::state::disabled%]",
|
||||
"sleeping": "Sleeping",
|
||||
"ready": "Ready",
|
||||
"streaminglocal": "Streaming local",
|
||||
"streamingcloud": "Streaming cloud",
|
||||
"streaminglocal_and_cloud": "Streaming local and cloud",
|
||||
"error": "Error"
|
||||
}
|
||||
},
|
||||
"last_selected_map": {
|
||||
"name": "Last selected map",
|
||||
"state": {
|
||||
"tempmap": "Temporary map",
|
||||
"map1": "Map 1",
|
||||
"map2": "Map 2",
|
||||
"map3": "Map 3"
|
||||
}
|
||||
},
|
||||
"freezer_door_alarm": {
|
||||
"name": "Freezer door alarm",
|
||||
"state": {
|
||||
"confirmed": "[%key:component::home_connect::common::confirmed%]",
|
||||
"present": "[%key:component::home_connect::common::present%]"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_freezer": {
|
||||
"refrigerator_door_alarm": {
|
||||
"name": "Refrigerator door alarm",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"confirmed": "[%key:component::home_connect::common::confirmed%]",
|
||||
"present": "[%key:component::home_connect::common::present%]"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_temp": {
|
||||
"freezer_temperature_alarm": {
|
||||
"name": "Freezer temperature alarm",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"confirmed": "[%key:component::home_connect::common::confirmed%]",
|
||||
"present": "[%key:component::home_connect::common::present%]"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_coffee_bean_container": {
|
||||
"bean_container_empty": {
|
||||
"name": "Bean container empty",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"confirmed": "[%key:component::home_connect::common::confirmed%]",
|
||||
"present": "[%key:component::home_connect::common::present%]"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_coffee_water_tank": {
|
||||
"water_tank_empty": {
|
||||
"name": "Water tank empty",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"confirmed": "[%key:component::home_connect::common::confirmed%]",
|
||||
"present": "[%key:component::home_connect::common::present%]"
|
||||
}
|
||||
},
|
||||
"alarm_sensor_coffee_drip_tray": {
|
||||
"drip_tray_full": {
|
||||
"name": "Drip tray full",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"confirmed": "[%key:component::home_connect::common::confirmed%]",
|
||||
"present": "[%key:component::home_connect::common::present%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"power": {
|
||||
"name": "Power"
|
||||
},
|
||||
"child_lock": {
|
||||
"name": "Child lock"
|
||||
},
|
||||
"cup_warmer": {
|
||||
"name": "Cup warmer"
|
||||
},
|
||||
"refrigerator_super_mode": {
|
||||
"name": "Refrigerator super mode"
|
||||
},
|
||||
"freezer_super_mode": {
|
||||
"name": "Freezer super mode"
|
||||
},
|
||||
"eco_mode": {
|
||||
"name": "Eco mode"
|
||||
},
|
||||
"sabbath_mode": {
|
||||
"name": "Sabbath mode"
|
||||
},
|
||||
"vacation_mode": {
|
||||
"name": "Vacation mode"
|
||||
},
|
||||
"fresh_mode": {
|
||||
"name": "Fresh mode"
|
||||
},
|
||||
"dispenser_enabled": {
|
||||
"name": "Dispenser",
|
||||
"state": {
|
||||
"off": "[%key:common::state::disabled%]",
|
||||
"on": "[%key:common::state::enabled%]"
|
||||
}
|
||||
},
|
||||
"door_assistant_fridge": {
|
||||
"name": "Fridge door assistant"
|
||||
},
|
||||
"door_assistant_freezer": {
|
||||
"name": "Freezer door assistant"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Provides a switch for Home Connect."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
|
@ -8,7 +8,6 @@ from homeconnect.api import HomeConnectError
|
|||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DEVICE, CONF_ENTITIES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
|
@ -18,7 +17,9 @@ from .const import (
|
|||
BSH_ACTIVE_PROGRAM,
|
||||
BSH_CHILD_LOCK_STATE,
|
||||
BSH_OPERATION_STATE,
|
||||
BSH_POWER_OFF,
|
||||
BSH_POWER_ON,
|
||||
BSH_POWER_STANDBY,
|
||||
BSH_POWER_STATE,
|
||||
DOMAIN,
|
||||
REFRIGERATION_DISPENSER,
|
||||
|
@ -29,26 +30,71 @@ from .entity import HomeConnectDevice, HomeConnectEntity
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class HomeConnectSwitchEntityDescription(SwitchEntityDescription):
|
||||
"""Switch entity description."""
|
||||
|
||||
desc: str
|
||||
APPLIANCES_WITH_PROGRAMS = (
|
||||
"CleaningRobot",
|
||||
"CoffeeMachine",
|
||||
"Dishwasher",
|
||||
"Dryer",
|
||||
"Hood",
|
||||
"Oven",
|
||||
"WarmingDrawer",
|
||||
"Washer",
|
||||
"WasherDryer",
|
||||
)
|
||||
|
||||
|
||||
SWITCHES: tuple[HomeConnectSwitchEntityDescription, ...] = (
|
||||
HomeConnectSwitchEntityDescription(
|
||||
key=REFRIGERATION_SUPERMODEFREEZER,
|
||||
desc="Supermode Freezer",
|
||||
SWITCHES = (
|
||||
SwitchEntityDescription(
|
||||
key=BSH_CHILD_LOCK_STATE,
|
||||
translation_key="child_lock",
|
||||
),
|
||||
HomeConnectSwitchEntityDescription(
|
||||
SwitchEntityDescription(
|
||||
key="ConsumerProducts.CoffeeMaker.Setting.CupWarmer",
|
||||
translation_key="cup_warmer",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=REFRIGERATION_SUPERMODEREFRIGERATOR,
|
||||
desc="Supermode Refrigerator",
|
||||
translation_key="cup_warmer",
|
||||
),
|
||||
HomeConnectSwitchEntityDescription(
|
||||
SwitchEntityDescription(
|
||||
key=REFRIGERATION_SUPERMODEFREEZER,
|
||||
translation_key="freezer_super_mode",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=REFRIGERATION_SUPERMODEREFRIGERATOR,
|
||||
translation_key="refrigerator_super_mode",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key="Refrigeration.Common.Setting.EcoMode",
|
||||
translation_key="eco_mode",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key="Cooking.Oven.Setting.SabbathMode",
|
||||
translation_key="sabbath_mode",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key="Refrigeration.Common.Setting.SabbathMode",
|
||||
translation_key="sabbath_mode",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key="Refrigeration.Common.Setting.VacationMode",
|
||||
translation_key="vacation_mode",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key="Refrigeration.Common.Setting.FreshMode",
|
||||
translation_key="fresh_mode",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=REFRIGERATION_DISPENSER,
|
||||
desc="Dispenser Enabled",
|
||||
translation_key="dispenser_enabled",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key="Refrigeration.Common.Setting.Door.AssistantFridge",
|
||||
translation_key="door_assistant_fridge",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key="Refrigeration.Common.Setting.Door.AssistantFreezer",
|
||||
translation_key="door_assistant_freezer",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -64,17 +110,20 @@ async def async_setup_entry(
|
|||
"""Get a list of entities."""
|
||||
entities: list[SwitchEntity] = []
|
||||
hc_api: ConfigEntryAuth = hass.data[DOMAIN][config_entry.entry_id]
|
||||
for device_dict in hc_api.devices:
|
||||
entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("switch", [])
|
||||
entities.extend(HomeConnectProgramSwitch(**d) for d in entity_dicts)
|
||||
entities.append(HomeConnectPowerSwitch(device_dict[CONF_DEVICE]))
|
||||
entities.append(HomeConnectChildLockSwitch(device_dict[CONF_DEVICE]))
|
||||
# Auto-discover entities
|
||||
hc_device: HomeConnectDevice = device_dict[CONF_DEVICE]
|
||||
for device in hc_api.devices:
|
||||
if device.appliance.type in APPLIANCES_WITH_PROGRAMS:
|
||||
with contextlib.suppress(HomeConnectError):
|
||||
programs = device.appliance.get_programs_available()
|
||||
if programs:
|
||||
entities.extend(
|
||||
HomeConnectProgramSwitch(device, program)
|
||||
for program in programs
|
||||
)
|
||||
entities.append(HomeConnectPowerSwitch(device))
|
||||
entities.extend(
|
||||
HomeConnectSwitch(device=hc_device, entity_description=description)
|
||||
HomeConnectSwitch(device, description)
|
||||
for description in SWITCHES
|
||||
if description.key in hc_device.appliance.status
|
||||
if description.key in device.appliance.status
|
||||
)
|
||||
|
||||
return entities
|
||||
|
@ -85,18 +134,6 @@ async def async_setup_entry(
|
|||
class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
|
||||
"""Generic switch class for Home Connect Binary Settings."""
|
||||
|
||||
entity_description: HomeConnectSwitchEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: HomeConnectDevice,
|
||||
entity_description: HomeConnectSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.entity_description = entity_description
|
||||
self._attr_available = False
|
||||
super().__init__(device, entity_description.key, entity_description.desc)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on setting."""
|
||||
|
||||
|
@ -153,7 +190,9 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
|
|||
desc = " ".join(
|
||||
["Program", program_name.split(".")[-3], program_name.split(".")[-1]]
|
||||
)
|
||||
super().__init__(device, desc, desc)
|
||||
super().__init__(device, SwitchEntityDescription(key=program_name))
|
||||
self._attr_name = f"{device.appliance.name} {desc}"
|
||||
self._attr_has_entity_name = False
|
||||
self.program_name = program_name
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
|
@ -189,9 +228,27 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
|
|||
class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
|
||||
"""Power switch class for Home Connect."""
|
||||
|
||||
power_off_state: str | None
|
||||
|
||||
def __init__(self, device: HomeConnectDevice) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(device, BSH_POWER_STATE, "Power")
|
||||
super().__init__(
|
||||
device,
|
||||
SwitchEntityDescription(key=BSH_POWER_STATE, translation_key="power"),
|
||||
)
|
||||
match device.appliance.type:
|
||||
case "Dishwasher" | "Cooktop" | "Hood":
|
||||
self.power_off_state = BSH_POWER_OFF
|
||||
case (
|
||||
"Oven"
|
||||
| "WarmDrawer"
|
||||
| "CoffeeMachine"
|
||||
| "CleaningRobot"
|
||||
| "CookProcessor"
|
||||
):
|
||||
self.power_off_state = BSH_POWER_STANDBY
|
||||
case _:
|
||||
self.power_off_state = None
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Switch the device on."""
|
||||
|
@ -207,12 +264,15 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
|
|||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Switch the device off."""
|
||||
if self.power_off_state is None:
|
||||
_LOGGER.debug("This appliance type does not support turning off")
|
||||
return
|
||||
_LOGGER.debug("tried to switch off %s", self.name)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.appliance.set_setting,
|
||||
BSH_POWER_STATE,
|
||||
self.device.power_off_state,
|
||||
self.power_off_state,
|
||||
)
|
||||
except HomeConnectError as err:
|
||||
_LOGGER.error("Error while trying to turn off device: %s", err)
|
||||
|
@ -228,7 +288,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
|
|||
self._attr_is_on = True
|
||||
elif (
|
||||
self.device.appliance.status.get(BSH_POWER_STATE, {}).get(ATTR_VALUE)
|
||||
== self.device.power_off_state
|
||||
== self.power_off_state
|
||||
):
|
||||
self._attr_is_on = False
|
||||
elif self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get(
|
||||
|
@ -251,44 +311,3 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
|
|||
else:
|
||||
self._attr_is_on = None
|
||||
_LOGGER.debug("Updated, new state: %s", self._attr_is_on)
|
||||
|
||||
|
||||
class HomeConnectChildLockSwitch(HomeConnectEntity, SwitchEntity):
|
||||
"""Child lock switch class for Home Connect."""
|
||||
|
||||
def __init__(self, device: HomeConnectDevice) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(device, BSH_CHILD_LOCK_STATE, "ChildLock")
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Switch child lock on."""
|
||||
_LOGGER.debug("Tried to switch child lock on device: %s", self.name)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.appliance.set_setting, BSH_CHILD_LOCK_STATE, True
|
||||
)
|
||||
except HomeConnectError as err:
|
||||
_LOGGER.error("Error while trying to turn on child lock on device: %s", err)
|
||||
self._attr_is_on = False
|
||||
self.async_entity_update()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Switch child lock off."""
|
||||
_LOGGER.debug("Tried to switch off child lock on device: %s", self.name)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.appliance.set_setting, BSH_CHILD_LOCK_STATE, False
|
||||
)
|
||||
except HomeConnectError as err:
|
||||
_LOGGER.error(
|
||||
"Error while trying to turn off child lock on device: %s", err
|
||||
)
|
||||
self._attr_is_on = True
|
||||
self.async_entity_update()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the switch's status."""
|
||||
self._attr_is_on = False
|
||||
if self.device.appliance.status.get(BSH_CHILD_LOCK_STATE, {}).get(ATTR_VALUE):
|
||||
self._attr_is_on = True
|
||||
_LOGGER.debug("Updated child lock, new state: %s", self._attr_is_on)
|
||||
|
|
|
@ -68,9 +68,9 @@ async def test_binary_sensors_door_states(
|
|||
entity_id = "binary_sensor.washer_door"
|
||||
get_appliances.return_value = [appliance]
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
appliance.status.update({BSH_DOOR_STATE: {"value": state}})
|
||||
assert await integration_setup()
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
appliance.status.update({BSH_DOOR_STATE: {"value": state}})
|
||||
await async_update_entity(hass, entity_id)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.is_state(entity_id, expected)
|
||||
|
|
|
@ -67,7 +67,7 @@ async def test_light(
|
|||
("entity_id", "status", "service", "service_data", "state", "appliance"),
|
||||
[
|
||||
(
|
||||
"light.hood_light",
|
||||
"light.hood_functional_light",
|
||||
{
|
||||
COOKING_LIGHTING: {
|
||||
"value": True,
|
||||
|
@ -79,7 +79,7 @@ async def test_light(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_light",
|
||||
"light.hood_functional_light",
|
||||
{
|
||||
COOKING_LIGHTING: {
|
||||
"value": True,
|
||||
|
@ -92,7 +92,7 @@ async def test_light(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_light",
|
||||
"light.hood_functional_light",
|
||||
{
|
||||
COOKING_LIGHTING: {"value": False},
|
||||
COOKING_LIGHTING_BRIGHTNESS: {"value": 70},
|
||||
|
@ -103,7 +103,7 @@ async def test_light(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_light",
|
||||
"light.hood_functional_light",
|
||||
{
|
||||
COOKING_LIGHTING: {
|
||||
"value": None,
|
||||
|
@ -116,7 +116,7 @@ async def test_light(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_ambientlight",
|
||||
"light.hood_ambient_light",
|
||||
{
|
||||
BSH_AMBIENT_LIGHT_ENABLED: {
|
||||
"value": True,
|
||||
|
@ -129,7 +129,7 @@ async def test_light(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_ambientlight",
|
||||
"light.hood_ambient_light",
|
||||
{
|
||||
BSH_AMBIENT_LIGHT_ENABLED: {"value": False},
|
||||
BSH_AMBIENT_LIGHT_BRIGHTNESS: {"value": 70},
|
||||
|
@ -140,7 +140,7 @@ async def test_light(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_ambientlight",
|
||||
"light.hood_ambient_light",
|
||||
{
|
||||
BSH_AMBIENT_LIGHT_ENABLED: {"value": True},
|
||||
BSH_AMBIENT_LIGHT_CUSTOM_COLOR: {},
|
||||
|
@ -218,7 +218,7 @@ async def test_light_functionality(
|
|||
),
|
||||
[
|
||||
(
|
||||
"light.hood_light",
|
||||
"light.hood_functional_light",
|
||||
{
|
||||
COOKING_LIGHTING: {
|
||||
"value": False,
|
||||
|
@ -231,7 +231,7 @@ async def test_light_functionality(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_light",
|
||||
"light.hood_functional_light",
|
||||
{
|
||||
COOKING_LIGHTING: {
|
||||
"value": True,
|
||||
|
@ -245,7 +245,7 @@ async def test_light_functionality(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_light",
|
||||
"light.hood_functional_light",
|
||||
{
|
||||
COOKING_LIGHTING: {"value": False},
|
||||
},
|
||||
|
@ -256,7 +256,7 @@ async def test_light_functionality(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_ambientlight",
|
||||
"light.hood_ambient_light",
|
||||
{
|
||||
BSH_AMBIENT_LIGHT_ENABLED: {
|
||||
"value": True,
|
||||
|
@ -270,7 +270,7 @@ async def test_light_functionality(
|
|||
"Hood",
|
||||
),
|
||||
(
|
||||
"light.hood_ambientlight",
|
||||
"light.hood_ambient_light",
|
||||
{
|
||||
BSH_AMBIENT_LIGHT_ENABLED: {
|
||||
"value": True,
|
||||
|
|
|
@ -26,14 +26,14 @@ TEST_HC_APP = "Dishwasher"
|
|||
|
||||
EVENT_PROG_DELAYED_START = {
|
||||
"BSH.Common.Status.OperationState": {
|
||||
"value": "BSH.Common.EnumType.OperationState.Delayed"
|
||||
"value": "BSH.Common.EnumType.OperationState.DelayedStart"
|
||||
},
|
||||
}
|
||||
|
||||
EVENT_PROG_REMAIN_NO_VALUE = {
|
||||
"BSH.Common.Option.RemainingProgramTime": {},
|
||||
"BSH.Common.Status.OperationState": {
|
||||
"value": "BSH.Common.EnumType.OperationState.Delayed"
|
||||
"value": "BSH.Common.EnumType.OperationState.DelayedStart"
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -103,13 +103,13 @@ PROGRAM_SEQUENCE_EVENTS = (
|
|||
# Entity mapping to expected state at each program sequence.
|
||||
ENTITY_ID_STATES = {
|
||||
"sensor.dishwasher_operation_state": (
|
||||
"Delayed",
|
||||
"Run",
|
||||
"Run",
|
||||
"Run",
|
||||
"Ready",
|
||||
"delayedstart",
|
||||
"run",
|
||||
"run",
|
||||
"run",
|
||||
"ready",
|
||||
),
|
||||
"sensor.dishwasher_remaining_program_time": (
|
||||
"sensor.dishwasher_program_finish_time": (
|
||||
"unavailable",
|
||||
"2021-01-09T12:00:00+00:00",
|
||||
"2021-01-09T12:00:00+00:00",
|
||||
|
@ -158,6 +158,8 @@ async def test_event_sensors(
|
|||
get_appliances.return_value = [appliance]
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
appliance.get_programs_available = MagicMock(return_value=["dummy_program"])
|
||||
appliance.status.update(EVENT_PROG_DELAYED_START)
|
||||
assert await integration_setup()
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
@ -198,11 +200,13 @@ async def test_remaining_prog_time_edge_cases(
|
|||
) -> None:
|
||||
"""Run program sequence to test edge cases for the remaining_prog_time entity."""
|
||||
get_appliances.return_value = [appliance]
|
||||
entity_id = "sensor.dishwasher_remaining_program_time"
|
||||
entity_id = "sensor.dishwasher_program_finish_time"
|
||||
time_to_freeze = "2021-01-09 12:00:00+00:00"
|
||||
freezer.move_to(time_to_freeze)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
appliance.get_programs_available = MagicMock(return_value=["dummy_program"])
|
||||
appliance.status.update(EVENT_PROG_REMAIN_NO_VALUE)
|
||||
assert await integration_setup()
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
@ -221,28 +225,28 @@ async def test_remaining_prog_time_edge_cases(
|
|||
("entity_id", "status_key", "event_value_update", "expected", "appliance"),
|
||||
[
|
||||
(
|
||||
"sensor.fridgefreezer_door_alarm_freezer",
|
||||
"sensor.fridgefreezer_freezer_door_alarm",
|
||||
"EVENT_NOT_IN_STATUS_YET_SO_SET_TO_OFF",
|
||||
"",
|
||||
"off",
|
||||
"FridgeFreezer",
|
||||
),
|
||||
(
|
||||
"sensor.fridgefreezer_door_alarm_freezer",
|
||||
"sensor.fridgefreezer_freezer_door_alarm",
|
||||
REFRIGERATION_EVENT_DOOR_ALARM_FREEZER,
|
||||
BSH_EVENT_PRESENT_STATE_OFF,
|
||||
"off",
|
||||
"FridgeFreezer",
|
||||
),
|
||||
(
|
||||
"sensor.fridgefreezer_door_alarm_freezer",
|
||||
"sensor.fridgefreezer_freezer_door_alarm",
|
||||
REFRIGERATION_EVENT_DOOR_ALARM_FREEZER,
|
||||
BSH_EVENT_PRESENT_STATE_PRESENT,
|
||||
"present",
|
||||
"FridgeFreezer",
|
||||
),
|
||||
(
|
||||
"sensor.fridgefreezer_door_alarm_freezer",
|
||||
"sensor.fridgefreezer_freezer_door_alarm",
|
||||
REFRIGERATION_EVENT_DOOR_ALARM_FREEZER,
|
||||
BSH_EVENT_PRESENT_STATE_CONFIRMED,
|
||||
"confirmed",
|
||||
|
|
|
@ -34,7 +34,7 @@ from tests.common import MockConfigEntry, load_json_object_fixture
|
|||
SETTINGS_STATUS = {
|
||||
setting.pop("key"): setting
|
||||
for setting in load_json_object_fixture("home_connect/settings.json")
|
||||
.get("Washer")
|
||||
.get("Dishwasher")
|
||||
.get("data")
|
||||
.get("settings")
|
||||
}
|
||||
|
@ -64,34 +64,38 @@ async def test_switches(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "status", "service", "state"),
|
||||
("entity_id", "status", "service", "state", "appliance"),
|
||||
[
|
||||
(
|
||||
"switch.washer_program_mix",
|
||||
"switch.dishwasher_program_mix",
|
||||
{BSH_ACTIVE_PROGRAM: {"value": PROGRAM}},
|
||||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_program_mix",
|
||||
"switch.dishwasher_program_mix",
|
||||
{BSH_ACTIVE_PROGRAM: {"value": ""}},
|
||||
SERVICE_TURN_OFF,
|
||||
STATE_OFF,
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_power",
|
||||
"switch.dishwasher_power",
|
||||
{BSH_POWER_STATE: {"value": BSH_POWER_ON}},
|
||||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_power",
|
||||
"switch.dishwasher_power",
|
||||
{BSH_POWER_STATE: {"value": BSH_POWER_OFF}},
|
||||
SERVICE_TURN_OFF,
|
||||
STATE_OFF,
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_power",
|
||||
"switch.dishwasher_power",
|
||||
{
|
||||
BSH_POWER_STATE: {"value": ""},
|
||||
BSH_OPERATION_STATE: {
|
||||
|
@ -100,20 +104,24 @@ async def test_switches(
|
|||
},
|
||||
SERVICE_TURN_OFF,
|
||||
STATE_OFF,
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_childlock",
|
||||
"switch.dishwasher_child_lock",
|
||||
{BSH_CHILD_LOCK_STATE: {"value": True}},
|
||||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_childlock",
|
||||
"switch.dishwasher_child_lock",
|
||||
{BSH_CHILD_LOCK_STATE: {"value": False}},
|
||||
SERVICE_TURN_OFF,
|
||||
STATE_OFF,
|
||||
"Dishwasher",
|
||||
),
|
||||
],
|
||||
indirect=["appliance"],
|
||||
)
|
||||
async def test_switch_functionality(
|
||||
entity_id: str,
|
||||
|
@ -145,45 +153,52 @@ async def test_switch_functionality(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "status", "service", "mock_attr"),
|
||||
("entity_id", "status", "service", "mock_attr", "problematic_appliance"),
|
||||
[
|
||||
(
|
||||
"switch.washer_program_mix",
|
||||
"switch.dishwasher_program_mix",
|
||||
{BSH_ACTIVE_PROGRAM: {"value": PROGRAM}},
|
||||
SERVICE_TURN_ON,
|
||||
"start_program",
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_program_mix",
|
||||
"switch.dishwasher_program_mix",
|
||||
{BSH_ACTIVE_PROGRAM: {"value": PROGRAM}},
|
||||
SERVICE_TURN_OFF,
|
||||
"stop_program",
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_power",
|
||||
"switch.dishwasher_power",
|
||||
{BSH_POWER_STATE: {"value": ""}},
|
||||
SERVICE_TURN_ON,
|
||||
"set_setting",
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_power",
|
||||
"switch.dishwasher_power",
|
||||
{BSH_POWER_STATE: {"value": ""}},
|
||||
SERVICE_TURN_OFF,
|
||||
"set_setting",
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_childlock",
|
||||
"switch.dishwasher_child_lock",
|
||||
{BSH_CHILD_LOCK_STATE: {"value": ""}},
|
||||
SERVICE_TURN_ON,
|
||||
"set_setting",
|
||||
"Dishwasher",
|
||||
),
|
||||
(
|
||||
"switch.washer_childlock",
|
||||
"switch.dishwasher_child_lock",
|
||||
{BSH_CHILD_LOCK_STATE: {"value": ""}},
|
||||
SERVICE_TURN_OFF,
|
||||
"set_setting",
|
||||
"Dishwasher",
|
||||
),
|
||||
],
|
||||
indirect=["problematic_appliance"],
|
||||
)
|
||||
async def test_switch_exception_handling(
|
||||
entity_id: str,
|
||||
|
@ -204,6 +219,7 @@ async def test_switch_exception_handling(
|
|||
get_appliances.return_value = [problematic_appliance]
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
problematic_appliance.status.update(status)
|
||||
assert await integration_setup()
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
@ -211,7 +227,6 @@ async def test_switch_exception_handling(
|
|||
with pytest.raises(HomeConnectError):
|
||||
getattr(problematic_appliance, mock_attr)()
|
||||
|
||||
problematic_appliance.status.update(status)
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, service, {"entity_id": entity_id}, blocking=True
|
||||
)
|
||||
|
@ -222,14 +237,14 @@ async def test_switch_exception_handling(
|
|||
("entity_id", "status", "service", "state", "appliance"),
|
||||
[
|
||||
(
|
||||
"switch.fridgefreezer_supermode_freezer",
|
||||
"switch.fridgefreezer_freezer_super_mode",
|
||||
{REFRIGERATION_SUPERMODEFREEZER: {"value": True}},
|
||||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
"FridgeFreezer",
|
||||
),
|
||||
(
|
||||
"switch.fridgefreezer_supermode_freezer",
|
||||
"switch.fridgefreezer_freezer_super_mode",
|
||||
{REFRIGERATION_SUPERMODEFREEZER: {"value": False}},
|
||||
SERVICE_TURN_OFF,
|
||||
STATE_OFF,
|
||||
|
@ -277,14 +292,14 @@ async def test_ent_desc_switch_functionality(
|
|||
("entity_id", "status", "service", "mock_attr", "problematic_appliance"),
|
||||
[
|
||||
(
|
||||
"switch.fridgefreezer_supermode_freezer",
|
||||
"switch.fridgefreezer_freezer_super_mode",
|
||||
{REFRIGERATION_SUPERMODEFREEZER: {"value": ""}},
|
||||
SERVICE_TURN_ON,
|
||||
"set_setting",
|
||||
"FridgeFreezer",
|
||||
),
|
||||
(
|
||||
"switch.fridgefreezer_supermode_freezer",
|
||||
"switch.fridgefreezer_freezer_super_mode",
|
||||
{REFRIGERATION_SUPERMODEFREEZER: {"value": ""}},
|
||||
SERVICE_TURN_OFF,
|
||||
"set_setting",
|
||||
|
|
Loading…
Reference in New Issue