Add button platform to bond to replace custom services (#64725)
This commit is contained in:
parent
5d753abd79
commit
15532c38d7
4 changed files with 361 additions and 2 deletions
|
@ -24,6 +24,7 @@ from .const import BPUP_SUBS, BRIDGE_MAKE, DOMAIN, HUB
|
||||||
from .utils import BondHub
|
from .utils import BondHub
|
||||||
|
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
|
Platform.BUTTON,
|
||||||
Platform.COVER,
|
Platform.COVER,
|
||||||
Platform.FAN,
|
Platform.FAN,
|
||||||
Platform.LIGHT,
|
Platform.LIGHT,
|
||||||
|
|
238
homeassistant/components/bond/button.py
Normal file
238
homeassistant/components/bond/button.py
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
"""Support for bond buttons."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from bond_api import Action, BPUPSubscriptions
|
||||||
|
|
||||||
|
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import BPUP_SUBS, DOMAIN, HUB
|
||||||
|
from .entity import BondEntity
|
||||||
|
from .utils import BondDevice, BondHub
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BondButtonEntityDescriptionMixin:
|
||||||
|
"""Mixin to describe a Bond Button entity."""
|
||||||
|
|
||||||
|
mutually_exclusive: Action | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BondButtonEntityDescription(
|
||||||
|
ButtonEntityDescription, BondButtonEntityDescriptionMixin
|
||||||
|
):
|
||||||
|
"""Class to describe a Bond Button entity."""
|
||||||
|
|
||||||
|
|
||||||
|
BUTTONS: tuple[BondButtonEntityDescription, ...] = (
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.STOP,
|
||||||
|
name="Stop Actions",
|
||||||
|
icon="mdi:stop-circle-outline",
|
||||||
|
mutually_exclusive=None,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.TOGGLE_POWER,
|
||||||
|
name="Toggle Power",
|
||||||
|
icon="mdi:power-cycle",
|
||||||
|
mutually_exclusive=Action.TURN_ON,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.TOGGLE_LIGHT,
|
||||||
|
name="Toggle Light",
|
||||||
|
icon="mdi:lightbulb",
|
||||||
|
mutually_exclusive=Action.TURN_LIGHT_ON,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.INCREASE_BRIGHTNESS,
|
||||||
|
name="Increase Brightness",
|
||||||
|
icon="mdi:brightness-7",
|
||||||
|
mutually_exclusive=Action.SET_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.DECREASE_BRIGHTNESS,
|
||||||
|
name="Decrease Brightness",
|
||||||
|
icon="mdi:brightness-1",
|
||||||
|
mutually_exclusive=Action.SET_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.TOGGLE_UP_LIGHT,
|
||||||
|
name="Toggle Up Light",
|
||||||
|
icon="mdi:lightbulb",
|
||||||
|
mutually_exclusive=Action.TURN_UP_LIGHT_ON,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.TOGGLE_DOWN_LIGHT,
|
||||||
|
name="Toggle Down Light",
|
||||||
|
icon="mdi:lightbulb",
|
||||||
|
mutually_exclusive=Action.TURN_DOWN_LIGHT_ON,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.START_UP_LIGHT_DIMMER,
|
||||||
|
name="Start Up Light Dimmer",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.START_DOWN_LIGHT_DIMMER,
|
||||||
|
name="Start Down Light Dimmer",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.START_INCREASING_BRIGHTNESS,
|
||||||
|
name="Start Increasing Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.START_DECREASING_BRIGHTNESS,
|
||||||
|
name="Start Decreasing Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.INCREASE_UP_LIGHT_BRIGHTNESS,
|
||||||
|
name="Increase Up Light Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.DECREASE_UP_LIGHT_BRIGHTNESS,
|
||||||
|
name="Decrease Up Light Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.INCREASE_DOWN_LIGHT_BRIGHTNESS,
|
||||||
|
name="Increase Down Light Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.DECREASE_DOWN_LIGHT_BRIGHTNESS,
|
||||||
|
name="Decrease Down Light Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.CYCLE_UP_LIGHT_BRIGHTNESS,
|
||||||
|
name="Cycle Up Light Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.CYCLE_DOWN_LIGHT_BRIGHTNESS,
|
||||||
|
name="Cycle Down Light Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.CYCLE_BRIGHTNESS,
|
||||||
|
name="Cycle Brightness",
|
||||||
|
icon="mdi:brightness-percent",
|
||||||
|
mutually_exclusive=Action.SET_BRIGHTNESS,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.INCREASE_SPEED,
|
||||||
|
name="Increase Speed",
|
||||||
|
icon="mdi:skew-more",
|
||||||
|
mutually_exclusive=Action.SET_SPEED,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.DECREASE_SPEED,
|
||||||
|
name="Decrease Speed",
|
||||||
|
icon="mdi:skew-less",
|
||||||
|
mutually_exclusive=Action.SET_SPEED,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.TOGGLE_DIRECTION,
|
||||||
|
name="Toggle Direction",
|
||||||
|
icon="mdi:directions-fork",
|
||||||
|
mutually_exclusive=Action.SET_DIRECTION,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.INCREASE_TEMPERATURE,
|
||||||
|
name="Increase Temperature",
|
||||||
|
icon="mdi:thermometer-plus",
|
||||||
|
mutually_exclusive=None,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.DECREASE_TEMPERATURE,
|
||||||
|
name="Decrease Temperature",
|
||||||
|
icon="mdi:thermometer-minus",
|
||||||
|
mutually_exclusive=None,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.INCREASE_FLAME,
|
||||||
|
name="Increase Flame",
|
||||||
|
icon="mdi:fire",
|
||||||
|
mutually_exclusive=None,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.DECREASE_FLAME,
|
||||||
|
name="Decrease Flame",
|
||||||
|
icon="mdi:fire-off",
|
||||||
|
mutually_exclusive=None,
|
||||||
|
),
|
||||||
|
BondButtonEntityDescription(
|
||||||
|
key=Action.TOGGLE_OPEN, name="Toggle Open", mutually_exclusive=Action.OPEN
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Bond button devices."""
|
||||||
|
data = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
hub: BondHub = data[HUB]
|
||||||
|
bpup_subs: BPUPSubscriptions = data[BPUP_SUBS]
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
BondButtonEntity(hub, device, bpup_subs, description)
|
||||||
|
for device in hub.devices
|
||||||
|
for description in BUTTONS
|
||||||
|
if device.has_action(description.key)
|
||||||
|
and (
|
||||||
|
description.mutually_exclusive is None
|
||||||
|
or not device.has_action(description.mutually_exclusive)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BondButtonEntity(BondEntity, ButtonEntity):
|
||||||
|
"""Bond Button Device."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hub: BondHub,
|
||||||
|
device: BondDevice,
|
||||||
|
bpup_subs: BPUPSubscriptions,
|
||||||
|
description: ButtonEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Init Bond button."""
|
||||||
|
super().__init__(
|
||||||
|
hub, device, bpup_subs, description.name, description.key.lower()
|
||||||
|
)
|
||||||
|
self.entity_description = description
|
||||||
|
|
||||||
|
async def async_press(self, **kwargs: Any) -> None:
|
||||||
|
"""Press the button."""
|
||||||
|
await self._hub.bond.action(
|
||||||
|
self._device.device_id, Action(self.entity_description.key)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _apply_state(self, state: dict) -> None:
|
||||||
|
"""Apply the state."""
|
|
@ -41,6 +41,7 @@ class BondEntity(Entity):
|
||||||
device: BondDevice,
|
device: BondDevice,
|
||||||
bpup_subs: BPUPSubscriptions,
|
bpup_subs: BPUPSubscriptions,
|
||||||
sub_device: str | None = None,
|
sub_device: str | None = None,
|
||||||
|
sub_device_id: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize entity with API and device info."""
|
"""Initialize entity with API and device info."""
|
||||||
self._hub = hub
|
self._hub = hub
|
||||||
|
@ -51,7 +52,12 @@ class BondEntity(Entity):
|
||||||
self._bpup_subs = bpup_subs
|
self._bpup_subs = bpup_subs
|
||||||
self._update_lock: Lock | None = None
|
self._update_lock: Lock | None = None
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
sub_device_id: str = f"_{sub_device}" if sub_device else ""
|
if sub_device_id:
|
||||||
|
sub_device_id = f"_{sub_device_id}"
|
||||||
|
elif sub_device:
|
||||||
|
sub_device_id = f"_{sub_device}"
|
||||||
|
else:
|
||||||
|
sub_device_id = ""
|
||||||
self._attr_unique_id = f"{hub.bond_id}_{device.device_id}{sub_device_id}"
|
self._attr_unique_id = f"{hub.bond_id}_{device.device_id}{sub_device_id}"
|
||||||
if sub_device:
|
if sub_device:
|
||||||
sub_device_name = sub_device.replace("_", " ").title()
|
sub_device_name = sub_device.replace("_", " ").title()
|
||||||
|
@ -69,7 +75,7 @@ class BondEntity(Entity):
|
||||||
configuration_url=f"http://{self._hub.host}",
|
configuration_url=f"http://{self._hub.host}",
|
||||||
)
|
)
|
||||||
if self.name is not None:
|
if self.name is not None:
|
||||||
device_info[ATTR_NAME] = self.name
|
device_info[ATTR_NAME] = self._device.name
|
||||||
if self._hub.bond_id is not None:
|
if self._hub.bond_id is not None:
|
||||||
device_info[ATTR_VIA_DEVICE] = (DOMAIN, self._hub.bond_id)
|
device_info[ATTR_VIA_DEVICE] = (DOMAIN, self._hub.bond_id)
|
||||||
if self._device.location is not None:
|
if self._device.location is not None:
|
||||||
|
|
114
tests/components/bond/test_button.py
Normal file
114
tests/components/bond/test_button.py
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
"""Tests for the Bond button device."""
|
||||||
|
|
||||||
|
from bond_api import Action, DeviceType
|
||||||
|
|
||||||
|
from homeassistant import core
|
||||||
|
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
|
||||||
|
from homeassistant.components.button.const import SERVICE_PRESS
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||||
|
|
||||||
|
from .common import patch_bond_action, patch_bond_device_state, setup_platform
|
||||||
|
|
||||||
|
|
||||||
|
def light_brightness_increase_decrease_only(name: str):
|
||||||
|
"""Create a light that can only increase or decrease brightness."""
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"type": DeviceType.LIGHT,
|
||||||
|
"actions": [
|
||||||
|
Action.TURN_LIGHT_ON,
|
||||||
|
Action.TURN_LIGHT_OFF,
|
||||||
|
Action.START_INCREASING_BRIGHTNESS,
|
||||||
|
Action.START_DECREASING_BRIGHTNESS,
|
||||||
|
Action.STOP,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fireplace_increase_decrease_only(name: str):
|
||||||
|
"""Create a fireplace that can only increase or decrease flame."""
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"type": DeviceType.LIGHT,
|
||||||
|
"actions": [
|
||||||
|
Action.INCREASE_FLAME,
|
||||||
|
Action.DECREASE_FLAME,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def light(name: str):
|
||||||
|
"""Create a light with a given name."""
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"type": DeviceType.LIGHT,
|
||||||
|
"actions": [Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF, Action.SET_BRIGHTNESS],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entity_registry(hass: core.HomeAssistant):
|
||||||
|
"""Tests that the devices are registered in the entity registry."""
|
||||||
|
await setup_platform(
|
||||||
|
hass,
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
light_brightness_increase_decrease_only("name-1"),
|
||||||
|
bond_version={"bondid": "test-hub-id"},
|
||||||
|
bond_device_id="test-device-id",
|
||||||
|
)
|
||||||
|
|
||||||
|
registry: EntityRegistry = er.async_get(hass)
|
||||||
|
entity = registry.entities["button.name_1_stop_actions"]
|
||||||
|
assert entity.unique_id == "test-hub-id_test-device-id_stop"
|
||||||
|
entity = registry.entities["button.name_1_start_increasing_brightness"]
|
||||||
|
assert entity.unique_id == "test-hub-id_test-device-id_startincreasingbrightness"
|
||||||
|
entity = registry.entities["button.name_1_start_decreasing_brightness"]
|
||||||
|
assert entity.unique_id == "test-hub-id_test-device-id_startdecreasingbrightness"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mutually_exclusive_actions(hass: core.HomeAssistant):
|
||||||
|
"""Tests we do not create the button when there is a mutually exclusive action."""
|
||||||
|
await setup_platform(
|
||||||
|
hass,
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
light("name-1"),
|
||||||
|
bond_device_id="test-device-id",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not hass.states.async_all("button")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_press_button(hass: core.HomeAssistant):
|
||||||
|
"""Tests we can press a button."""
|
||||||
|
await setup_platform(
|
||||||
|
hass,
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
fireplace_increase_decrease_only("name-1"),
|
||||||
|
bond_device_id="test-device-id",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.states.get("button.name_1_increase_flame")
|
||||||
|
assert hass.states.get("button.name_1_decrease_flame")
|
||||||
|
|
||||||
|
with patch_bond_action() as mock_action, patch_bond_device_state():
|
||||||
|
await hass.services.async_call(
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
SERVICE_PRESS,
|
||||||
|
{ATTR_ENTITY_ID: "button.name_1_increase_flame"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
mock_action.assert_called_once_with("test-device-id", Action(Action.INCREASE_FLAME))
|
||||||
|
|
||||||
|
with patch_bond_action() as mock_action, patch_bond_device_state():
|
||||||
|
await hass.services.async_call(
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
SERVICE_PRESS,
|
||||||
|
{ATTR_ENTITY_ID: "button.name_1_decrease_flame"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
mock_action.assert_called_once_with("test-device-id", Action(Action.DECREASE_FLAME))
|
Loading…
Add table
Add a link
Reference in a new issue