Add support for services to Home Connect (#58768)
Co-authored-by: Erik Montnemery <erik@montnemery.com>pull/73928/head
parent
f6f7fa1c2d
commit
e6daed9719
|
@ -1,4 +1,5 @@
|
|||
"""Support for BSH Home Connect appliances."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
@ -11,14 +12,39 @@ from homeassistant.components.application_credentials import (
|
|||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_ID,
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
CONF_DEVICE,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
|
||||
from homeassistant.helpers import (
|
||||
config_entry_oauth2_flow,
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import api
|
||||
from .const import DOMAIN
|
||||
from .const import (
|
||||
ATTR_KEY,
|
||||
ATTR_PROGRAM,
|
||||
ATTR_UNIT,
|
||||
ATTR_VALUE,
|
||||
BSH_PAUSE,
|
||||
BSH_RESUME,
|
||||
DOMAIN,
|
||||
SERVICE_OPTION_ACTIVE,
|
||||
SERVICE_OPTION_SELECTED,
|
||||
SERVICE_PAUSE_PROGRAM,
|
||||
SERVICE_RESUME_PROGRAM,
|
||||
SERVICE_SELECT_PROGRAM,
|
||||
SERVICE_SETTING,
|
||||
SERVICE_START_PROGRAM,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -39,9 +65,55 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
SERVICE_SETTING_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
vol.Required(ATTR_KEY): str,
|
||||
vol.Required(ATTR_VALUE): vol.Any(str, int, bool),
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_OPTION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
vol.Required(ATTR_KEY): str,
|
||||
vol.Required(ATTR_VALUE): vol.Any(str, int, bool),
|
||||
vol.Optional(ATTR_UNIT): str,
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_PROGRAM_SCHEMA = vol.Any(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
vol.Required(ATTR_PROGRAM): str,
|
||||
vol.Required(ATTR_KEY): str,
|
||||
vol.Required(ATTR_VALUE): vol.Any(int, str),
|
||||
vol.Optional(ATTR_UNIT): str,
|
||||
},
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
vol.Required(ATTR_PROGRAM): str,
|
||||
},
|
||||
)
|
||||
|
||||
SERVICE_COMMAND_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_ID): str})
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
|
||||
def _get_appliance_by_device_id(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> api.HomeConnectDevice | None:
|
||||
"""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]
|
||||
if device.device_id == device_id:
|
||||
return device.appliance
|
||||
_LOGGER.error("Appliance for device id %s not found", device_id)
|
||||
return None
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Home Connect component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
|
@ -65,6 +137,121 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
"configuration.yaml file"
|
||||
)
|
||||
|
||||
async def _async_service_program(call, method):
|
||||
"""Execute calls to services taking a program."""
|
||||
program = call.data[ATTR_PROGRAM]
|
||||
device_id = call.data[ATTR_DEVICE_ID]
|
||||
options = {
|
||||
ATTR_KEY: call.data.get(ATTR_KEY),
|
||||
ATTR_VALUE: call.data.get(ATTR_VALUE),
|
||||
ATTR_UNIT: call.data.get(ATTR_UNIT),
|
||||
}
|
||||
|
||||
appliance = _get_appliance_by_device_id(hass, device_id)
|
||||
if appliance is not None:
|
||||
await hass.async_add_executor_job(
|
||||
getattr(appliance, method), program, options
|
||||
)
|
||||
|
||||
async def _async_service_command(call, command):
|
||||
"""Execute calls to services executing a command."""
|
||||
device_id = call.data[ATTR_DEVICE_ID]
|
||||
|
||||
appliance = _get_appliance_by_device_id(hass, device_id)
|
||||
if appliance is not None:
|
||||
await hass.async_add_executor_job(appliance.execute_command, command)
|
||||
|
||||
async def _async_service_key_value(call, method):
|
||||
"""Execute calls to services taking a key and value."""
|
||||
key = call.data[ATTR_KEY]
|
||||
value = call.data[ATTR_VALUE]
|
||||
unit = call.data.get(ATTR_UNIT)
|
||||
device_id = call.data[ATTR_DEVICE_ID]
|
||||
|
||||
appliance = _get_appliance_by_device_id(hass, device_id)
|
||||
if appliance is not None:
|
||||
if unit is not None:
|
||||
await hass.async_add_executor_job(
|
||||
getattr(appliance, method),
|
||||
key,
|
||||
value,
|
||||
unit,
|
||||
)
|
||||
else:
|
||||
await hass.async_add_executor_job(
|
||||
getattr(appliance, method),
|
||||
key,
|
||||
value,
|
||||
)
|
||||
|
||||
async def async_service_option_active(call):
|
||||
"""Service for setting an option for an active program."""
|
||||
await _async_service_key_value(call, "set_options_active_program")
|
||||
|
||||
async def async_service_option_selected(call):
|
||||
"""Service for setting an option for a selected program."""
|
||||
await _async_service_key_value(call, "set_options_selected_program")
|
||||
|
||||
async def async_service_setting(call):
|
||||
"""Service for changing a setting."""
|
||||
await _async_service_key_value(call, "set_setting")
|
||||
|
||||
async def async_service_pause_program(call):
|
||||
"""Service for pausing a program."""
|
||||
await _async_service_command(call, BSH_PAUSE)
|
||||
|
||||
async def async_service_resume_program(call):
|
||||
"""Service for resuming a paused program."""
|
||||
await _async_service_command(call, BSH_RESUME)
|
||||
|
||||
async def async_service_select_program(call):
|
||||
"""Service for selecting a program."""
|
||||
await _async_service_program(call, "select_program")
|
||||
|
||||
async def async_service_start_program(call):
|
||||
"""Service for starting a program."""
|
||||
await _async_service_program(call, "start_program")
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_OPTION_ACTIVE,
|
||||
async_service_option_active,
|
||||
schema=SERVICE_OPTION_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_OPTION_SELECTED,
|
||||
async_service_option_selected,
|
||||
schema=SERVICE_OPTION_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SETTING, async_service_setting, schema=SERVICE_SETTING_SCHEMA
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_PAUSE_PROGRAM,
|
||||
async_service_pause_program,
|
||||
schema=SERVICE_COMMAND_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_RESUME_PROGRAM,
|
||||
async_service_resume_program,
|
||||
schema=SERVICE_COMMAND_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_PROGRAM,
|
||||
async_service_select_program,
|
||||
schema=SERVICE_PROGRAM_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_START_PROGRAM,
|
||||
async_service_start_program,
|
||||
schema=SERVICE_PROGRAM_SCHEMA,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -101,9 +288,23 @@ async def update_all_devices(hass, entry):
|
|||
"""Update all the devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
hc_api = data[entry.entry_id]
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
try:
|
||||
await hass.async_add_executor_job(hc_api.get_devices)
|
||||
for device_dict in hc_api.devices:
|
||||
await hass.async_add_executor_job(device_dict["device"].initialize)
|
||||
device = device_dict["device"]
|
||||
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, device.appliance.haId)},
|
||||
name=device.appliance.name,
|
||||
manufacturer=device.appliance.brand,
|
||||
model=device.appliance.vib,
|
||||
)
|
||||
|
||||
device.device_id = device_entry.id
|
||||
|
||||
await hass.async_add_executor_job(device.initialize)
|
||||
except HTTPError as err:
|
||||
_LOGGER.warning("Cannot update devices: %s", err.response.status_code)
|
||||
|
|
|
@ -113,6 +113,7 @@ class HomeConnectDevice:
|
|||
"""Initialize the device class."""
|
||||
self.hass = hass
|
||||
self.appliance = appliance
|
||||
self.entities = []
|
||||
|
||||
def initialize(self):
|
||||
"""Fetch the info needed to initialize the device."""
|
||||
|
|
|
@ -30,12 +30,24 @@ BSH_DOOR_STATE_CLOSED = "BSH.Common.EnumType.DoorState.Closed"
|
|||
BSH_DOOR_STATE_LOCKED = "BSH.Common.EnumType.DoorState.Locked"
|
||||
BSH_DOOR_STATE_OPEN = "BSH.Common.EnumType.DoorState.Open"
|
||||
|
||||
BSH_PAUSE = "BSH.Common.Command.PauseProgram"
|
||||
BSH_RESUME = "BSH.Common.Command.ResumeProgram"
|
||||
|
||||
SIGNAL_UPDATE_ENTITIES = "home_connect.update_entities"
|
||||
|
||||
SERVICE_OPTION_ACTIVE = "set_option_active"
|
||||
SERVICE_OPTION_SELECTED = "set_option_selected"
|
||||
SERVICE_PAUSE_PROGRAM = "pause_program"
|
||||
SERVICE_RESUME_PROGRAM = "resume_program"
|
||||
SERVICE_SELECT_PROGRAM = "select_program"
|
||||
SERVICE_SETTING = "change_setting"
|
||||
SERVICE_START_PROGRAM = "start_program"
|
||||
|
||||
ATTR_AMBIENT = "ambient"
|
||||
ATTR_DESC = "desc"
|
||||
ATTR_DEVICE = "device"
|
||||
ATTR_KEY = "key"
|
||||
ATTR_PROGRAM = "program"
|
||||
ATTR_SENSOR_TYPE = "sensor_type"
|
||||
ATTR_SIGN = "sign"
|
||||
ATTR_UNIT = "unit"
|
||||
|
|
|
@ -20,6 +20,7 @@ class HomeConnectEntity(Entity):
|
|||
self.device = device
|
||||
self.desc = desc
|
||||
self._name = f"{self.device.appliance.name} {desc}"
|
||||
self.device.entities.append(self)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
start_program:
|
||||
name: Start program
|
||||
description: Selects a program and starts it.
|
||||
fields:
|
||||
device_id:
|
||||
name: Device ID
|
||||
description: Id of the device.
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
program:
|
||||
name: Program
|
||||
description: Program to select
|
||||
example: "Dishcare.Dishwasher.Program.Auto2"
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
key:
|
||||
name: Option key
|
||||
description: Key of the option.
|
||||
example: "BSH.Common.Option.StartInRelative"
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
name: Option value
|
||||
description: Value of the option.
|
||||
example: 1800
|
||||
selector:
|
||||
object:
|
||||
unit:
|
||||
name: Option unit
|
||||
description: Unit for the option.
|
||||
example: "seconds"
|
||||
selector:
|
||||
text:
|
||||
select_program:
|
||||
name: Select program
|
||||
description: Selects a program without starting it.
|
||||
fields:
|
||||
device_id:
|
||||
name: Device ID
|
||||
description: Id of the device.
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
program:
|
||||
name: Program
|
||||
description: Program to select
|
||||
example: "Dishcare.Dishwasher.Program.Auto2"
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
key:
|
||||
name: Option key
|
||||
description: Key of the option.
|
||||
example: "BSH.Common.Option.StartInRelative"
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
name: Option value
|
||||
description: Value of the option.
|
||||
example: 1800
|
||||
selector:
|
||||
object:
|
||||
unit:
|
||||
name: Option unit
|
||||
description: Unit for the option.
|
||||
example: "seconds"
|
||||
selector:
|
||||
text:
|
||||
pause_program:
|
||||
name: Pause program
|
||||
description: Pauses the current running program.
|
||||
fields:
|
||||
device_id:
|
||||
name: Device ID
|
||||
description: Id of the device.
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
resume_program:
|
||||
name: Resume program
|
||||
description: Resumes a paused program.
|
||||
fields:
|
||||
device_id:
|
||||
name: Device ID
|
||||
description: Id of the device.
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
set_option_active:
|
||||
name: Set active program option
|
||||
description: Sets an option for the active program.
|
||||
fields:
|
||||
device_id:
|
||||
name: Device ID
|
||||
description: Id of the device.
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
key:
|
||||
name: Key
|
||||
description: Key of the option.
|
||||
example: "LaundryCare.Dryer.Option.DryingTarget"
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
name: Value
|
||||
description: Value of the option.
|
||||
example: "LaundryCare.Dryer.EnumType.DryingTarget.IronDry"
|
||||
required: true
|
||||
selector:
|
||||
object:
|
||||
set_option_selected:
|
||||
name: Set selected program option
|
||||
description: Sets an option for the selected program.
|
||||
fields:
|
||||
device_id:
|
||||
name: Device ID
|
||||
description: Id of the device.
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
key:
|
||||
name: Key
|
||||
description: Key of the option.
|
||||
example: "LaundryCare.Dryer.Option.DryingTarget"
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
name: Value
|
||||
description: Value of the option.
|
||||
example: "LaundryCare.Dryer.EnumType.DryingTarget.IronDry"
|
||||
required: true
|
||||
selector:
|
||||
object:
|
||||
change_setting:
|
||||
name: Change setting
|
||||
description: Changes a setting.
|
||||
fields:
|
||||
device_id:
|
||||
name: Device ID
|
||||
description: Id of the device.
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
key:
|
||||
name: Key
|
||||
description: Key of the setting.
|
||||
example: "BSH.Common.Setting.ChildLock"
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
value:
|
||||
name: Value
|
||||
description: Value of the setting.
|
||||
example: "true"
|
||||
required: true
|
||||
selector:
|
||||
object:
|
Loading…
Reference in New Issue