Add support for services to Home Connect (#58768)

Co-authored-by: Erik Montnemery <erik@montnemery.com>
pull/73928/head
Frank 2022-06-29 12:45:55 +02:00 committed by GitHub
parent f6f7fa1c2d
commit e6daed9719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 388 additions and 4 deletions

View File

@ -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)

View File

@ -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."""

View File

@ -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"

View File

@ -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."""

View File

@ -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: