Add support for cover positions in bond (#72180)

This commit is contained in:
J. Nick Koston 2022-05-20 09:49:26 -05:00 committed by GitHub
parent 614c44b9f0
commit e9c861f2b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 3 deletions

View File

@ -223,6 +223,20 @@ BUTTONS: tuple[BondButtonEntityDescription, ...] = (
mutually_exclusive=Action.OPEN,
argument=None,
),
BondButtonEntityDescription(
key=Action.INCREASE_POSITION,
name="Increase Position",
icon="mdi:plus-box",
mutually_exclusive=Action.SET_POSITION,
argument=STEP_SIZE,
),
BondButtonEntityDescription(
key=Action.DECREASE_POSITION,
name="Decrease Position",
icon="mdi:minus-box",
mutually_exclusive=Action.SET_POSITION,
argument=STEP_SIZE,
),
)

View File

@ -6,6 +6,7 @@ from typing import Any
from bond_api import Action, BPUPSubscriptions, DeviceType
from homeassistant.components.cover import (
ATTR_POSITION,
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
@ -20,6 +21,16 @@ from .entity import BondEntity
from .utils import BondDevice, BondHub
def _bond_to_hass_position(bond_position: int) -> int:
"""Convert bond 0-open 100-closed to hass 0-closed 100-open."""
return abs(bond_position - 100)
def _hass_to_bond_position(hass_position: int) -> int:
"""Convert hass 0-closed 100-open to bond 0-open 100-closed."""
return 100 - hass_position
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
@ -50,6 +61,8 @@ class BondCover(BondEntity, CoverEntity):
"""Create HA entity representing Bond cover."""
super().__init__(hub, device, bpup_subs)
supported_features = 0
if self._device.supports_set_position():
supported_features |= CoverEntityFeature.SET_POSITION
if self._device.supports_open():
supported_features |= CoverEntityFeature.OPEN
if self._device.supports_close():
@ -67,8 +80,15 @@ class BondCover(BondEntity, CoverEntity):
def _apply_state(self, state: dict) -> None:
cover_open = state.get("open")
self._attr_is_closed = (
True if cover_open == 0 else False if cover_open == 1 else None
self._attr_is_closed = None if cover_open is None else cover_open == 0
if (bond_position := state.get("position")) is not None:
self._attr_current_cover_position = _bond_to_hass_position(bond_position)
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Set the cover position."""
await self._hub.bond.action(
self._device.device_id,
Action.set_position(_hass_to_bond_position(kwargs[ATTR_POSITION])),
)
async def async_open_cover(self, **kwargs: Any) -> None:

View File

@ -82,6 +82,10 @@ class BondDevice:
"""Return True if this device supports any of the direction related commands."""
return self._has_any_action({Action.SET_DIRECTION})
def supports_set_position(self) -> bool:
"""Return True if this device supports setting the position."""
return self._has_any_action({Action.SET_POSITION})
def supports_open(self) -> bool:
"""Return True if this device supports opening."""
return self._has_any_action({Action.OPEN})

View File

@ -4,15 +4,22 @@ from datetime import timedelta
from bond_api import Action, DeviceType
from homeassistant import core
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN, STATE_CLOSED
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_POSITION,
DOMAIN as COVER_DOMAIN,
STATE_CLOSED,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER,
SERVICE_CLOSE_COVER_TILT,
SERVICE_OPEN_COVER,
SERVICE_OPEN_COVER_TILT,
SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER,
SERVICE_STOP_COVER_TILT,
STATE_OPEN,
STATE_UNKNOWN,
)
from homeassistant.helpers import entity_registry as er
@ -38,6 +45,15 @@ def shades(name: str):
}
def shades_with_position(name: str):
"""Create motorized shades that supports set position."""
return {
"name": name,
"type": DeviceType.MOTORIZED_SHADES,
"actions": [Action.OPEN, Action.CLOSE, Action.HOLD, Action.SET_POSITION],
}
def tilt_only_shades(name: str):
"""Create motorized shades that only tilt."""
return {
@ -236,3 +252,64 @@ async def test_cover_available(hass: core.HomeAssistant):
await help_test_entity_available(
hass, COVER_DOMAIN, shades("name-1"), "cover.name_1"
)
async def test_set_position_cover(hass: core.HomeAssistant):
"""Tests that set position cover command delegates to API."""
await setup_platform(
hass,
COVER_DOMAIN,
shades_with_position("name-1"),
bond_device_id="test-device-id",
)
with patch_bond_action() as mock_hold, patch_bond_device_state(
return_value={"position": 0, "open": 1}
):
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.name_1", ATTR_POSITION: 100},
blocking=True,
)
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
mock_hold.assert_called_once_with("test-device-id", Action.set_position(0))
entity_state = hass.states.get("cover.name_1")
assert entity_state.state == STATE_OPEN
assert entity_state.attributes[ATTR_CURRENT_POSITION] == 100
with patch_bond_action() as mock_hold, patch_bond_device_state(
return_value={"position": 100, "open": 0}
):
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.name_1", ATTR_POSITION: 0},
blocking=True,
)
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
mock_hold.assert_called_once_with("test-device-id", Action.set_position(100))
entity_state = hass.states.get("cover.name_1")
assert entity_state.state == STATE_CLOSED
assert entity_state.attributes[ATTR_CURRENT_POSITION] == 0
with patch_bond_action() as mock_hold, patch_bond_device_state(
return_value={"position": 40, "open": 1}
):
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.name_1", ATTR_POSITION: 60},
blocking=True,
)
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
mock_hold.assert_called_once_with("test-device-id", Action.set_position(40))
entity_state = hass.states.get("cover.name_1")
assert entity_state.state == STATE_OPEN
assert entity_state.attributes[ATTR_CURRENT_POSITION] == 60