Add support for services to Home Connect (#58768)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
f6f7fa1c2d
commit
e6daed9719
5 changed files with 388 additions and 4 deletions
|
@ -1,4 +1,5 @@
|
||||||
"""Support for BSH Home Connect appliances."""
|
"""Support for BSH Home Connect appliances."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
@ -11,14 +12,39 @@ from homeassistant.components.application_credentials import (
|
||||||
async_import_client_credential,
|
async_import_client_credential,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
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.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.helpers.typing import ConfigType
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from . import api
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -39,9 +65,55 @@ CONFIG_SCHEMA = vol.Schema(
|
||||||
extra=vol.ALLOW_EXTRA,
|
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]
|
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:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up Home Connect component."""
|
"""Set up Home Connect component."""
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
|
@ -65,6 +137,121 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"configuration.yaml file"
|
"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
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,9 +288,23 @@ async def update_all_devices(hass, entry):
|
||||||
"""Update all the devices."""
|
"""Update all the devices."""
|
||||||
data = hass.data[DOMAIN]
|
data = hass.data[DOMAIN]
|
||||||
hc_api = data[entry.entry_id]
|
hc_api = data[entry.entry_id]
|
||||||
|
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(hc_api.get_devices)
|
await hass.async_add_executor_job(hc_api.get_devices)
|
||||||
for device_dict in hc_api.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:
|
except HTTPError as err:
|
||||||
_LOGGER.warning("Cannot update devices: %s", err.response.status_code)
|
_LOGGER.warning("Cannot update devices: %s", err.response.status_code)
|
||||||
|
|
|
@ -113,6 +113,7 @@ class HomeConnectDevice:
|
||||||
"""Initialize the device class."""
|
"""Initialize the device class."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.appliance = appliance
|
self.appliance = appliance
|
||||||
|
self.entities = []
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""Fetch the info needed to initialize the device."""
|
"""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_LOCKED = "BSH.Common.EnumType.DoorState.Locked"
|
||||||
BSH_DOOR_STATE_OPEN = "BSH.Common.EnumType.DoorState.Open"
|
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"
|
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_AMBIENT = "ambient"
|
||||||
ATTR_DESC = "desc"
|
ATTR_DESC = "desc"
|
||||||
ATTR_DEVICE = "device"
|
ATTR_DEVICE = "device"
|
||||||
ATTR_KEY = "key"
|
ATTR_KEY = "key"
|
||||||
|
ATTR_PROGRAM = "program"
|
||||||
ATTR_SENSOR_TYPE = "sensor_type"
|
ATTR_SENSOR_TYPE = "sensor_type"
|
||||||
ATTR_SIGN = "sign"
|
ATTR_SIGN = "sign"
|
||||||
ATTR_UNIT = "unit"
|
ATTR_UNIT = "unit"
|
||||||
|
|
|
@ -20,6 +20,7 @@ class HomeConnectEntity(Entity):
|
||||||
self.device = device
|
self.device = device
|
||||||
self.desc = desc
|
self.desc = desc
|
||||||
self._name = f"{self.device.appliance.name} {desc}"
|
self._name = f"{self.device.appliance.name} {desc}"
|
||||||
|
self.device.entities.append(self)
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
|
|
169
homeassistant/components/home_connect/services.yaml
Normal file
169
homeassistant/components/home_connect/services.yaml
Normal 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:
|
Loading…
Add table
Add a link
Reference in a new issue