Add open
state to LockEntity (#111968)
* Add `open` state to LockEntity * Add tests * Fixes * Fix tests * strings and icons * Adjust demo open lock * Fix lock and tests * fix import * Fix strings * mute ruff * Change sequence * Sequence2 * Group on states * Fix ruff * Fix tests * Add more test cases * Sorting
This commit is contained in:
parent
189c07d502
commit
7862596ef3
18 changed files with 377 additions and 37 deletions
|
@ -11,6 +11,8 @@ from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
|
@ -76,6 +78,16 @@ class DemoLock(LockEntity):
|
||||||
"""Return true if lock is locked."""
|
"""Return true if lock is locked."""
|
||||||
return self._state == STATE_LOCKED
|
return self._state == STATE_LOCKED
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_open(self) -> bool:
|
||||||
|
"""Return true if lock is open."""
|
||||||
|
return self._state == STATE_OPEN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_opening(self) -> bool:
|
||||||
|
"""Return true if lock is opening."""
|
||||||
|
return self._state == STATE_OPENING
|
||||||
|
|
||||||
async def async_lock(self, **kwargs: Any) -> None:
|
async def async_lock(self, **kwargs: Any) -> None:
|
||||||
"""Lock the device."""
|
"""Lock the device."""
|
||||||
self._state = STATE_LOCKING
|
self._state = STATE_LOCKING
|
||||||
|
@ -97,5 +109,8 @@ class DemoLock(LockEntity):
|
||||||
|
|
||||||
async def async_open(self, **kwargs: Any) -> None:
|
async def async_open(self, **kwargs: Any) -> None:
|
||||||
"""Open the door latch."""
|
"""Open the door latch."""
|
||||||
self._state = STATE_UNLOCKED
|
self._state = STATE_OPENING
|
||||||
|
self.async_write_ha_state()
|
||||||
|
await asyncio.sleep(LOCK_UNLOCK_DELAY)
|
||||||
|
self._state = STATE_OPEN
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
|
@ -25,6 +25,8 @@ from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
|
@ -175,12 +177,16 @@ class LockGroup(GroupEntity, LockEntity):
|
||||||
# Set as unknown if any member is unknown or unavailable
|
# Set as unknown if any member is unknown or unavailable
|
||||||
self._attr_is_jammed = None
|
self._attr_is_jammed = None
|
||||||
self._attr_is_locking = None
|
self._attr_is_locking = None
|
||||||
|
self._attr_is_opening = None
|
||||||
|
self._attr_is_open = None
|
||||||
self._attr_is_unlocking = None
|
self._attr_is_unlocking = None
|
||||||
self._attr_is_locked = None
|
self._attr_is_locked = None
|
||||||
else:
|
else:
|
||||||
# Set attributes based on member states and let the lock entity sort out the correct state
|
# Set attributes based on member states and let the lock entity sort out the correct state
|
||||||
self._attr_is_jammed = STATE_JAMMED in states
|
self._attr_is_jammed = STATE_JAMMED in states
|
||||||
self._attr_is_locking = STATE_LOCKING in states
|
self._attr_is_locking = STATE_LOCKING in states
|
||||||
|
self._attr_is_opening = STATE_OPENING in states
|
||||||
|
self._attr_is_open = STATE_OPEN in states
|
||||||
self._attr_is_unlocking = STATE_UNLOCKING in states
|
self._attr_is_unlocking = STATE_UNLOCKING in states
|
||||||
self._attr_is_locked = all(state == STATE_LOCKED for state in states)
|
self._attr_is_locked = all(state == STATE_LOCKED for state in states)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.lock import LockEntity, LockEntityFeature
|
from homeassistant.components.lock import LockEntity, LockEntityFeature
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
|
from homeassistant.const import STATE_LOCKED, STATE_OPEN, STATE_UNLOCKED
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
@ -79,6 +79,11 @@ class DemoLock(LockEntity):
|
||||||
"""Return true if lock is locked."""
|
"""Return true if lock is locked."""
|
||||||
return self._state == STATE_LOCKED
|
return self._state == STATE_LOCKED
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_open(self) -> bool:
|
||||||
|
"""Return true if lock is open."""
|
||||||
|
return self._state == STATE_OPEN
|
||||||
|
|
||||||
async def async_lock(self, **kwargs: Any) -> None:
|
async def async_lock(self, **kwargs: Any) -> None:
|
||||||
"""Lock the device."""
|
"""Lock the device."""
|
||||||
self._attr_is_locking = True
|
self._attr_is_locking = True
|
||||||
|
@ -97,5 +102,5 @@ class DemoLock(LockEntity):
|
||||||
|
|
||||||
async def async_open(self, **kwargs: Any) -> None:
|
async def async_open(self, **kwargs: Any) -> None:
|
||||||
"""Open the door latch."""
|
"""Open the door latch."""
|
||||||
self._state = STATE_UNLOCKED
|
self._state = STATE_OPEN
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
|
@ -22,6 +22,8 @@ from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
|
@ -121,6 +123,8 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
|
||||||
"is_locked",
|
"is_locked",
|
||||||
"is_locking",
|
"is_locking",
|
||||||
"is_unlocking",
|
"is_unlocking",
|
||||||
|
"is_open",
|
||||||
|
"is_opening",
|
||||||
"is_jammed",
|
"is_jammed",
|
||||||
"supported_features",
|
"supported_features",
|
||||||
}
|
}
|
||||||
|
@ -134,6 +138,8 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
_attr_code_format: str | None = None
|
_attr_code_format: str | None = None
|
||||||
_attr_is_locked: bool | None = None
|
_attr_is_locked: bool | None = None
|
||||||
_attr_is_locking: bool | None = None
|
_attr_is_locking: bool | None = None
|
||||||
|
_attr_is_open: bool | None = None
|
||||||
|
_attr_is_opening: bool | None = None
|
||||||
_attr_is_unlocking: bool | None = None
|
_attr_is_unlocking: bool | None = None
|
||||||
_attr_is_jammed: bool | None = None
|
_attr_is_jammed: bool | None = None
|
||||||
_attr_state: None = None
|
_attr_state: None = None
|
||||||
|
@ -202,6 +208,16 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
"""Return true if the lock is unlocking."""
|
"""Return true if the lock is unlocking."""
|
||||||
return self._attr_is_unlocking
|
return self._attr_is_unlocking
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def is_open(self) -> bool | None:
|
||||||
|
"""Return true if the lock is open."""
|
||||||
|
return self._attr_is_open
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def is_opening(self) -> bool | None:
|
||||||
|
"""Return true if the lock is opening."""
|
||||||
|
return self._attr_is_opening
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_jammed(self) -> bool | None:
|
def is_jammed(self) -> bool | None:
|
||||||
"""Return true if the lock is jammed (incomplete locking)."""
|
"""Return true if the lock is jammed (incomplete locking)."""
|
||||||
|
@ -262,8 +278,12 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
if self.is_jammed:
|
if self.is_jammed:
|
||||||
return STATE_JAMMED
|
return STATE_JAMMED
|
||||||
|
if self.is_opening:
|
||||||
|
return STATE_OPENING
|
||||||
if self.is_locking:
|
if self.is_locking:
|
||||||
return STATE_LOCKING
|
return STATE_LOCKING
|
||||||
|
if self.is_open:
|
||||||
|
return STATE_OPEN
|
||||||
if self.is_unlocking:
|
if self.is_unlocking:
|
||||||
return STATE_UNLOCKING
|
return STATE_UNLOCKING
|
||||||
if (locked := self.is_locked) is None:
|
if (locked := self.is_locked) is None:
|
||||||
|
|
|
@ -14,6 +14,8 @@ from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
|
@ -31,11 +33,13 @@ from . import DOMAIN
|
||||||
# mypy: disallow-any-generics
|
# mypy: disallow-any-generics
|
||||||
|
|
||||||
CONDITION_TYPES = {
|
CONDITION_TYPES = {
|
||||||
"is_locked",
|
|
||||||
"is_unlocked",
|
|
||||||
"is_locking",
|
|
||||||
"is_unlocking",
|
|
||||||
"is_jammed",
|
"is_jammed",
|
||||||
|
"is_locked",
|
||||||
|
"is_locking",
|
||||||
|
"is_open",
|
||||||
|
"is_opening",
|
||||||
|
"is_unlocked",
|
||||||
|
"is_unlocking",
|
||||||
}
|
}
|
||||||
|
|
||||||
CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
||||||
|
@ -78,8 +82,12 @@ def async_condition_from_config(
|
||||||
"""Create a function to test a device condition."""
|
"""Create a function to test a device condition."""
|
||||||
if config[CONF_TYPE] == "is_jammed":
|
if config[CONF_TYPE] == "is_jammed":
|
||||||
state = STATE_JAMMED
|
state = STATE_JAMMED
|
||||||
|
elif config[CONF_TYPE] == "is_opening":
|
||||||
|
state = STATE_OPENING
|
||||||
elif config[CONF_TYPE] == "is_locking":
|
elif config[CONF_TYPE] == "is_locking":
|
||||||
state = STATE_LOCKING
|
state = STATE_LOCKING
|
||||||
|
elif config[CONF_TYPE] == "is_open":
|
||||||
|
state = STATE_OPEN
|
||||||
elif config[CONF_TYPE] == "is_unlocking":
|
elif config[CONF_TYPE] == "is_unlocking":
|
||||||
state = STATE_UNLOCKING
|
state = STATE_UNLOCKING
|
||||||
elif config[CONF_TYPE] == "is_locked":
|
elif config[CONF_TYPE] == "is_locked":
|
||||||
|
|
|
@ -16,6 +16,8 @@ from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
|
@ -26,7 +28,15 @@ from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from . import DOMAIN
|
from . import DOMAIN
|
||||||
|
|
||||||
TRIGGER_TYPES = {"locked", "unlocked", "locking", "unlocking", "jammed"}
|
TRIGGER_TYPES = {
|
||||||
|
"jammed",
|
||||||
|
"locked",
|
||||||
|
"locking",
|
||||||
|
"open",
|
||||||
|
"opening",
|
||||||
|
"unlocked",
|
||||||
|
"unlocking",
|
||||||
|
}
|
||||||
|
|
||||||
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
|
@ -84,8 +94,12 @@ async def async_attach_trigger(
|
||||||
"""Attach a trigger."""
|
"""Attach a trigger."""
|
||||||
if config[CONF_TYPE] == "jammed":
|
if config[CONF_TYPE] == "jammed":
|
||||||
to_state = STATE_JAMMED
|
to_state = STATE_JAMMED
|
||||||
|
elif config[CONF_TYPE] == "opening":
|
||||||
|
to_state = STATE_OPENING
|
||||||
elif config[CONF_TYPE] == "locking":
|
elif config[CONF_TYPE] == "locking":
|
||||||
to_state = STATE_LOCKING
|
to_state = STATE_LOCKING
|
||||||
|
elif config[CONF_TYPE] == "open":
|
||||||
|
to_state = STATE_OPEN
|
||||||
elif config[CONF_TYPE] == "unlocking":
|
elif config[CONF_TYPE] == "unlocking":
|
||||||
to_state = STATE_UNLOCKING
|
to_state = STATE_UNLOCKING
|
||||||
elif config[CONF_TYPE] == "locked":
|
elif config[CONF_TYPE] == "locked":
|
||||||
|
|
|
@ -2,7 +2,14 @@
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
|
from homeassistant.const import (
|
||||||
|
STATE_LOCKED,
|
||||||
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
|
STATE_UNLOCKED,
|
||||||
|
STATE_UNLOCKING,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
@ -16,4 +23,15 @@ def async_describe_on_off_states(
|
||||||
hass: HomeAssistant, registry: "GroupIntegrationRegistry"
|
hass: HomeAssistant, registry: "GroupIntegrationRegistry"
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Describe group on off states."""
|
"""Describe group on off states."""
|
||||||
registry.on_off_states(DOMAIN, {STATE_UNLOCKED}, STATE_UNLOCKED, STATE_LOCKED)
|
registry.on_off_states(
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
|
STATE_UNLOCKED,
|
||||||
|
STATE_UNLOCKING,
|
||||||
|
},
|
||||||
|
STATE_UNLOCKED,
|
||||||
|
STATE_LOCKED,
|
||||||
|
)
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
"state": {
|
"state": {
|
||||||
"jammed": "mdi:lock-alert",
|
"jammed": "mdi:lock-alert",
|
||||||
"locking": "mdi:lock-clock",
|
"locking": "mdi:lock-clock",
|
||||||
|
"open": "mdi:lock-open-variant",
|
||||||
|
"opening": "mdi:lock-clock",
|
||||||
"unlocked": "mdi:lock-open-variant",
|
"unlocked": "mdi:lock-open-variant",
|
||||||
"unlocking": "mdi:lock-clock"
|
"unlocking": "mdi:lock-clock"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,12 @@ from typing import Any
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
SERVICE_LOCK,
|
SERVICE_LOCK,
|
||||||
|
SERVICE_OPEN,
|
||||||
SERVICE_UNLOCK,
|
SERVICE_UNLOCK,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
|
@ -22,7 +25,14 @@ from . import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
VALID_STATES = {STATE_LOCKED, STATE_UNLOCKED, STATE_LOCKING, STATE_UNLOCKING}
|
VALID_STATES = {
|
||||||
|
STATE_LOCKED,
|
||||||
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
|
STATE_UNLOCKED,
|
||||||
|
STATE_UNLOCKING,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def _async_reproduce_state(
|
async def _async_reproduce_state(
|
||||||
|
@ -53,6 +63,8 @@ async def _async_reproduce_state(
|
||||||
service = SERVICE_LOCK
|
service = SERVICE_LOCK
|
||||||
elif state.state in {STATE_UNLOCKED, STATE_UNLOCKING}:
|
elif state.state in {STATE_UNLOCKED, STATE_UNLOCKING}:
|
||||||
service = SERVICE_UNLOCK
|
service = SERVICE_UNLOCK
|
||||||
|
elif state.state in {STATE_OPEN, STATE_OPENING}:
|
||||||
|
service = SERVICE_OPEN
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, service, service_data, context=context, blocking=True
|
DOMAIN, service, service_data, context=context, blocking=True
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
},
|
},
|
||||||
"condition_type": {
|
"condition_type": {
|
||||||
"is_locked": "{entity_name} is locked",
|
"is_locked": "{entity_name} is locked",
|
||||||
"is_unlocked": "{entity_name} is unlocked"
|
"is_unlocked": "{entity_name} is unlocked",
|
||||||
|
"is_open": "{entity_name} is open"
|
||||||
},
|
},
|
||||||
"trigger_type": {
|
"trigger_type": {
|
||||||
"locked": "{entity_name} locked",
|
"locked": "{entity_name} locked",
|
||||||
"unlocked": "{entity_name} unlocked"
|
"unlocked": "{entity_name} unlocked",
|
||||||
|
"open": "{entity_name} opened"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity_component": {
|
"entity_component": {
|
||||||
|
@ -22,6 +24,8 @@
|
||||||
"jammed": "Jammed",
|
"jammed": "Jammed",
|
||||||
"locked": "[%key:common::state::locked%]",
|
"locked": "[%key:common::state::locked%]",
|
||||||
"locking": "Locking",
|
"locking": "Locking",
|
||||||
|
"open": "[%key:common::state::open%]",
|
||||||
|
"opening": "Opening",
|
||||||
"unlocked": "[%key:common::state::unlocked%]",
|
"unlocked": "[%key:common::state::unlocked%]",
|
||||||
"unlocking": "Unlocking"
|
"unlocking": "Unlocking"
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,13 @@ from homeassistant.components.lock import (
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, EVENT_STATE_CHANGED, Platform
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
EVENT_STATE_CHANGED,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
@ -87,6 +93,26 @@ async def test_unlocking(hass: HomeAssistant) -> None:
|
||||||
assert state_changes[1].data["new_state"].state == STATE_UNLOCKED
|
assert state_changes[1].data["new_state"].state == STATE_UNLOCKED
|
||||||
|
|
||||||
|
|
||||||
|
@patch.object(demo_lock, "LOCK_UNLOCK_DELAY", 0)
|
||||||
|
async def test_opening(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the opening of a lock."""
|
||||||
|
state = hass.states.get(OPENABLE_LOCK)
|
||||||
|
assert state.state == STATE_LOCKED
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state_changes = async_capture_events(hass, EVENT_STATE_CHANGED)
|
||||||
|
await hass.services.async_call(
|
||||||
|
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: OPENABLE_LOCK}, blocking=False
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert state_changes[0].data["entity_id"] == OPENABLE_LOCK
|
||||||
|
assert state_changes[0].data["new_state"].state == STATE_OPENING
|
||||||
|
|
||||||
|
assert state_changes[1].data["entity_id"] == OPENABLE_LOCK
|
||||||
|
assert state_changes[1].data["new_state"].state == STATE_OPEN
|
||||||
|
|
||||||
|
|
||||||
@patch.object(demo_lock, "LOCK_UNLOCK_DELAY", 0)
|
@patch.object(demo_lock, "LOCK_UNLOCK_DELAY", 0)
|
||||||
async def test_jammed_when_locking(hass: HomeAssistant) -> None:
|
async def test_jammed_when_locking(hass: HomeAssistant) -> None:
|
||||||
"""Test the locking of a lock jams."""
|
"""Test the locking of a lock jams."""
|
||||||
|
@ -114,12 +140,3 @@ async def test_opening_mocked(hass: HomeAssistant) -> None:
|
||||||
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: OPENABLE_LOCK}, blocking=True
|
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: OPENABLE_LOCK}, blocking=True
|
||||||
)
|
)
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_opening(hass: HomeAssistant) -> None:
|
|
||||||
"""Test the opening of a lock."""
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: OPENABLE_LOCK}, blocking=True
|
|
||||||
)
|
|
||||||
state = hass.states.get(OPENABLE_LOCK)
|
|
||||||
assert state.state == STATE_UNLOCKED
|
|
||||||
|
|
|
@ -19,13 +19,17 @@ from homeassistant.const import (
|
||||||
SERVICE_RELOAD,
|
SERVICE_RELOAD,
|
||||||
STATE_CLOSED,
|
STATE_CLOSED,
|
||||||
STATE_HOME,
|
STATE_HOME,
|
||||||
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
|
STATE_LOCKING,
|
||||||
STATE_NOT_HOME,
|
STATE_NOT_HOME,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_OPEN,
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
from homeassistant.core import CoreState, HomeAssistant
|
from homeassistant.core import CoreState, HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
@ -769,6 +773,48 @@ async def test_is_on(hass: HomeAssistant) -> None:
|
||||||
(STATE_ON, True),
|
(STATE_ON, True),
|
||||||
(STATE_OFF, False),
|
(STATE_OFF, False),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
("lock", "lock"),
|
||||||
|
(STATE_OPEN, STATE_LOCKED),
|
||||||
|
(STATE_LOCKED, STATE_LOCKED),
|
||||||
|
(STATE_UNLOCKED, True),
|
||||||
|
(STATE_LOCKED, False),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
("lock", "lock"),
|
||||||
|
(STATE_OPENING, STATE_LOCKED),
|
||||||
|
(STATE_LOCKED, STATE_LOCKED),
|
||||||
|
(STATE_UNLOCKED, True),
|
||||||
|
(STATE_LOCKED, False),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
("lock", "lock"),
|
||||||
|
(STATE_UNLOCKING, STATE_LOCKED),
|
||||||
|
(STATE_LOCKED, STATE_LOCKED),
|
||||||
|
(STATE_UNLOCKED, True),
|
||||||
|
(STATE_LOCKED, False),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
("lock", "lock"),
|
||||||
|
(STATE_LOCKING, STATE_LOCKED),
|
||||||
|
(STATE_LOCKED, STATE_LOCKED),
|
||||||
|
(STATE_UNLOCKED, True),
|
||||||
|
(STATE_LOCKED, False),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
("lock", "lock"),
|
||||||
|
(STATE_JAMMED, STATE_LOCKED),
|
||||||
|
(STATE_LOCKED, STATE_LOCKED),
|
||||||
|
(STATE_LOCKED, False),
|
||||||
|
(STATE_LOCKED, False),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
("cover", "lock"),
|
||||||
|
(STATE_OPEN, STATE_OPEN),
|
||||||
|
(STATE_CLOSED, STATE_LOCKED),
|
||||||
|
(STATE_ON, True),
|
||||||
|
(STATE_OFF, False),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_is_on_and_state_mixed_domains(
|
async def test_is_on_and_state_mixed_domains(
|
||||||
|
@ -1247,6 +1293,8 @@ async def test_group_mixed_domains_off(hass: HomeAssistant) -> None:
|
||||||
[
|
[
|
||||||
(("locked", "locked", "unlocked"), "unlocked"),
|
(("locked", "locked", "unlocked"), "unlocked"),
|
||||||
(("locked", "locked", "locked"), "locked"),
|
(("locked", "locked", "locked"), "locked"),
|
||||||
|
(("locked", "locked", "open"), "unlocked"),
|
||||||
|
(("locked", "unlocked", "open"), "unlocked"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_group_locks(hass: HomeAssistant, states, group_state) -> None:
|
async def test_group_locks(hass: HomeAssistant, states, group_state) -> None:
|
||||||
|
|
|
@ -18,6 +18,7 @@ from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
|
@ -204,8 +205,8 @@ async def test_service_calls_openable(hass: HomeAssistant) -> None:
|
||||||
{ATTR_ENTITY_ID: "lock.lock_group"},
|
{ATTR_ENTITY_ID: "lock.lock_group"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
assert hass.states.get("lock.openable_lock").state == STATE_UNLOCKED
|
assert hass.states.get("lock.openable_lock").state == STATE_OPEN
|
||||||
assert hass.states.get("lock.another_openable_lock").state == STATE_UNLOCKED
|
assert hass.states.get("lock.another_openable_lock").state == STATE_OPEN
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LOCK_DOMAIN,
|
LOCK_DOMAIN,
|
||||||
|
|
|
@ -16,7 +16,12 @@ from homeassistant.components.lock import (
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, EVENT_STATE_CHANGED, Platform
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
EVENT_STATE_CHANGED,
|
||||||
|
STATE_OPEN,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
@ -103,4 +108,4 @@ async def test_opening(hass: HomeAssistant) -> None:
|
||||||
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: OPENABLE_LOCK}, blocking=True
|
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: OPENABLE_LOCK}, blocking=True
|
||||||
)
|
)
|
||||||
state = hass.states.get(OPENABLE_LOCK)
|
state = hass.states.get(OPENABLE_LOCK)
|
||||||
assert state.state == STATE_UNLOCKED
|
assert state.state == STATE_OPEN
|
||||||
|
|
|
@ -10,6 +10,8 @@ from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
|
@ -32,7 +34,7 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def calls(hass):
|
def calls(hass: HomeAssistant):
|
||||||
"""Track calls to a mock service."""
|
"""Track calls to a mock service."""
|
||||||
return async_mock_service(hass, "test", "automation")
|
return async_mock_service(hass, "test", "automation")
|
||||||
|
|
||||||
|
@ -67,6 +69,8 @@ async def test_get_conditions(
|
||||||
"is_unlocking",
|
"is_unlocking",
|
||||||
"is_locking",
|
"is_locking",
|
||||||
"is_jammed",
|
"is_jammed",
|
||||||
|
"is_open",
|
||||||
|
"is_opening",
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
conditions = await async_get_device_automations(
|
conditions = await async_get_device_automations(
|
||||||
|
@ -121,6 +125,8 @@ async def test_get_conditions_hidden_auxiliary(
|
||||||
"is_unlocking",
|
"is_unlocking",
|
||||||
"is_locking",
|
"is_locking",
|
||||||
"is_jammed",
|
"is_jammed",
|
||||||
|
"is_open",
|
||||||
|
"is_opening",
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
conditions = await async_get_device_automations(
|
conditions = await async_get_device_automations(
|
||||||
|
@ -243,6 +249,42 @@ async def test_if_state(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event6"},
|
||||||
|
"condition": [
|
||||||
|
{
|
||||||
|
"condition": "device",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"device_id": device_entry.id,
|
||||||
|
"entity_id": entry.id,
|
||||||
|
"type": "is_opening",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {
|
||||||
|
"some": "is_opening - {{ trigger.platform }} - {{ trigger.event.event_type }}"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event7"},
|
||||||
|
"condition": [
|
||||||
|
{
|
||||||
|
"condition": "device",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"device_id": device_entry.id,
|
||||||
|
"entity_id": entry.id,
|
||||||
|
"type": "is_open",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {
|
||||||
|
"some": "is_open - {{ trigger.platform }} - {{ trigger.event.event_type }}"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -277,6 +319,18 @@ async def test_if_state(
|
||||||
assert len(calls) == 5
|
assert len(calls) == 5
|
||||||
assert calls[4].data["some"] == "is_jammed - event - test_event5"
|
assert calls[4].data["some"] == "is_jammed - event - test_event5"
|
||||||
|
|
||||||
|
hass.states.async_set(entry.entity_id, STATE_OPENING)
|
||||||
|
hass.bus.async_fire("test_event6")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 6
|
||||||
|
assert calls[5].data["some"] == "is_opening - event - test_event6"
|
||||||
|
|
||||||
|
hass.states.async_set(entry.entity_id, STATE_OPEN)
|
||||||
|
hass.bus.async_fire("test_event7")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 7
|
||||||
|
assert calls[6].data["some"] == "is_open - event - test_event7"
|
||||||
|
|
||||||
|
|
||||||
async def test_if_state_legacy(
|
async def test_if_state_legacy(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|
|
@ -7,11 +7,13 @@ from pytest_unordered import unordered
|
||||||
|
|
||||||
from homeassistant.components import automation
|
from homeassistant.components import automation
|
||||||
from homeassistant.components.device_automation import DeviceAutomationType
|
from homeassistant.components.device_automation import DeviceAutomationType
|
||||||
from homeassistant.components.lock import DOMAIN
|
from homeassistant.components.lock import DOMAIN, LockEntityFeature
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
|
STATE_OPEN,
|
||||||
|
STATE_OPENING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
|
@ -37,7 +39,7 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def calls(hass):
|
def calls(hass: HomeAssistant):
|
||||||
"""Track calls to a mock service."""
|
"""Track calls to a mock service."""
|
||||||
return async_mock_service(hass, "test", "automation")
|
return async_mock_service(hass, "test", "automation")
|
||||||
|
|
||||||
|
@ -55,7 +57,11 @@ async def test_get_triggers(
|
||||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||||
)
|
)
|
||||||
entity_entry = entity_registry.async_get_or_create(
|
entity_entry = entity_registry.async_get_or_create(
|
||||||
DOMAIN, "test", "5678", device_id=device_entry.id
|
DOMAIN,
|
||||||
|
"test",
|
||||||
|
"5678",
|
||||||
|
device_id=device_entry.id,
|
||||||
|
supported_features=LockEntityFeature.OPEN,
|
||||||
)
|
)
|
||||||
expected_triggers = [
|
expected_triggers = [
|
||||||
{
|
{
|
||||||
|
@ -66,7 +72,15 @@ async def test_get_triggers(
|
||||||
"entity_id": entity_entry.id,
|
"entity_id": entity_entry.id,
|
||||||
"metadata": {"secondary": False},
|
"metadata": {"secondary": False},
|
||||||
}
|
}
|
||||||
for trigger in ["locked", "unlocked", "unlocking", "locking", "jammed"]
|
for trigger in [
|
||||||
|
"locked",
|
||||||
|
"unlocked",
|
||||||
|
"unlocking",
|
||||||
|
"locking",
|
||||||
|
"jammed",
|
||||||
|
"open",
|
||||||
|
"opening",
|
||||||
|
]
|
||||||
]
|
]
|
||||||
triggers = await async_get_device_automations(
|
triggers = await async_get_device_automations(
|
||||||
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
||||||
|
@ -104,6 +118,7 @@ async def test_get_triggers_hidden_auxiliary(
|
||||||
device_id=device_entry.id,
|
device_id=device_entry.id,
|
||||||
entity_category=entity_category,
|
entity_category=entity_category,
|
||||||
hidden_by=hidden_by,
|
hidden_by=hidden_by,
|
||||||
|
supported_features=LockEntityFeature.OPEN,
|
||||||
)
|
)
|
||||||
expected_triggers = [
|
expected_triggers = [
|
||||||
{
|
{
|
||||||
|
@ -114,7 +129,15 @@ async def test_get_triggers_hidden_auxiliary(
|
||||||
"entity_id": entity_entry.id,
|
"entity_id": entity_entry.id,
|
||||||
"metadata": {"secondary": True},
|
"metadata": {"secondary": True},
|
||||||
}
|
}
|
||||||
for trigger in ["locked", "unlocked", "unlocking", "locking", "jammed"]
|
for trigger in [
|
||||||
|
"locked",
|
||||||
|
"unlocked",
|
||||||
|
"unlocking",
|
||||||
|
"locking",
|
||||||
|
"jammed",
|
||||||
|
"open",
|
||||||
|
"opening",
|
||||||
|
]
|
||||||
]
|
]
|
||||||
triggers = await async_get_device_automations(
|
triggers = await async_get_device_automations(
|
||||||
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
||||||
|
@ -141,7 +164,7 @@ async def test_get_trigger_capabilities(
|
||||||
triggers = await async_get_device_automations(
|
triggers = await async_get_device_automations(
|
||||||
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
||||||
)
|
)
|
||||||
assert len(triggers) == 5
|
assert len(triggers) == 7
|
||||||
for trigger in triggers:
|
for trigger in triggers:
|
||||||
capabilities = await async_get_device_automation_capabilities(
|
capabilities = await async_get_device_automation_capabilities(
|
||||||
hass, DeviceAutomationType.TRIGGER, trigger
|
hass, DeviceAutomationType.TRIGGER, trigger
|
||||||
|
@ -172,7 +195,7 @@ async def test_get_trigger_capabilities_legacy(
|
||||||
triggers = await async_get_device_automations(
|
triggers = await async_get_device_automations(
|
||||||
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
hass, DeviceAutomationType.TRIGGER, device_entry.id
|
||||||
)
|
)
|
||||||
assert len(triggers) == 5
|
assert len(triggers) == 7
|
||||||
for trigger in triggers:
|
for trigger in triggers:
|
||||||
trigger["entity_id"] = entity_registry.async_get(trigger["entity_id"]).entity_id
|
trigger["entity_id"] = entity_registry.async_get(trigger["entity_id"]).entity_id
|
||||||
capabilities = await async_get_device_automation_capabilities(
|
capabilities = await async_get_device_automation_capabilities(
|
||||||
|
@ -247,6 +270,25 @@ async def test_if_fires_on_state_change(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"trigger": {
|
||||||
|
"platform": "device",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"device_id": device_entry.id,
|
||||||
|
"entity_id": entry.id,
|
||||||
|
"type": "open",
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {
|
||||||
|
"some": (
|
||||||
|
"open - {{ trigger.platform}} - "
|
||||||
|
"{{ trigger.entity_id}} - {{ trigger.from_state.state}} - "
|
||||||
|
"{{ trigger.to_state.state}} - {{ trigger.for }}"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -269,6 +311,15 @@ async def test_if_fires_on_state_change(
|
||||||
== f"unlocked - device - {entry.entity_id} - locked - unlocked - None"
|
== f"unlocked - device - {entry.entity_id} - locked - unlocked - None"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Fake that the entity is opens.
|
||||||
|
hass.states.async_set(entry.entity_id, STATE_OPEN)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 3
|
||||||
|
assert (
|
||||||
|
calls[2].data["some"]
|
||||||
|
== f"open - device - {entry.entity_id} - unlocked - open - None"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fires_on_state_change_legacy(
|
async def test_if_fires_on_state_change_legacy(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -439,6 +490,28 @@ async def test_if_fires_on_state_change_with_for(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"trigger": {
|
||||||
|
"platform": "device",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"device_id": device_entry.id,
|
||||||
|
"entity_id": entry.id,
|
||||||
|
"type": "opening",
|
||||||
|
"for": {"seconds": 5},
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {
|
||||||
|
"some": (
|
||||||
|
"turn_on {{ trigger.platform }}"
|
||||||
|
" - {{ trigger.entity_id }}"
|
||||||
|
" - {{ trigger.from_state.state }}"
|
||||||
|
" - {{ trigger.to_state.state }}"
|
||||||
|
" - {{ trigger.for }}"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -492,3 +565,15 @@ async def test_if_fires_on_state_change_with_for(
|
||||||
calls[3].data["some"]
|
calls[3].data["some"]
|
||||||
== f"turn_on device - {entry.entity_id} - jammed - locking - 0:00:05"
|
== f"turn_on device - {entry.entity_id} - jammed - locking - 0:00:05"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.states.async_set(entry.entity_id, STATE_OPENING)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 4
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=27))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 5
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert (
|
||||||
|
calls[4].data["some"]
|
||||||
|
== f"turn_on device - {entry.entity_id} - locking - opening - 0:00:05"
|
||||||
|
)
|
||||||
|
|
|
@ -22,6 +22,7 @@ from homeassistant.components.lock import (
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
LockEntityFeature,
|
LockEntityFeature,
|
||||||
)
|
)
|
||||||
|
from homeassistant.const import STATE_OPEN, STATE_OPENING
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
import homeassistant.helpers.entity_registry as er
|
import homeassistant.helpers.entity_registry as er
|
||||||
|
@ -55,6 +56,8 @@ async def test_lock_default(hass: HomeAssistant, mock_lock_entity: MockLock) ->
|
||||||
assert mock_lock_entity.is_locked is None
|
assert mock_lock_entity.is_locked is None
|
||||||
assert mock_lock_entity.is_locking is None
|
assert mock_lock_entity.is_locking is None
|
||||||
assert mock_lock_entity.is_unlocking is None
|
assert mock_lock_entity.is_unlocking is None
|
||||||
|
assert mock_lock_entity.is_opening is None
|
||||||
|
assert mock_lock_entity.is_open is None
|
||||||
|
|
||||||
|
|
||||||
async def test_lock_states(hass: HomeAssistant, mock_lock_entity: MockLock) -> None:
|
async def test_lock_states(hass: HomeAssistant, mock_lock_entity: MockLock) -> None:
|
||||||
|
@ -85,6 +88,19 @@ async def test_lock_states(hass: HomeAssistant, mock_lock_entity: MockLock) -> N
|
||||||
assert mock_lock_entity.state == STATE_JAMMED
|
assert mock_lock_entity.state == STATE_JAMMED
|
||||||
assert not mock_lock_entity.is_locked
|
assert not mock_lock_entity.is_locked
|
||||||
|
|
||||||
|
mock_lock_entity._attr_is_jammed = False
|
||||||
|
mock_lock_entity._attr_is_opening = True
|
||||||
|
assert mock_lock_entity.is_opening
|
||||||
|
assert mock_lock_entity.state == STATE_OPENING
|
||||||
|
assert mock_lock_entity.is_opening
|
||||||
|
|
||||||
|
mock_lock_entity._attr_is_opening = False
|
||||||
|
mock_lock_entity._attr_is_open = True
|
||||||
|
assert not mock_lock_entity.is_opening
|
||||||
|
assert mock_lock_entity.state == STATE_OPEN
|
||||||
|
assert not mock_lock_entity.is_opening
|
||||||
|
assert mock_lock_entity.is_open
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("code_format", "supported_features"),
|
("code_format", "supported_features"),
|
||||||
|
|
|
@ -14,9 +14,11 @@ async def test_reproducing_states(
|
||||||
"""Test reproducing Lock states."""
|
"""Test reproducing Lock states."""
|
||||||
hass.states.async_set("lock.entity_locked", "locked", {})
|
hass.states.async_set("lock.entity_locked", "locked", {})
|
||||||
hass.states.async_set("lock.entity_unlocked", "unlocked", {})
|
hass.states.async_set("lock.entity_unlocked", "unlocked", {})
|
||||||
|
hass.states.async_set("lock.entity_opened", "open", {})
|
||||||
|
|
||||||
lock_calls = async_mock_service(hass, "lock", "lock")
|
lock_calls = async_mock_service(hass, "lock", "lock")
|
||||||
unlock_calls = async_mock_service(hass, "lock", "unlock")
|
unlock_calls = async_mock_service(hass, "lock", "unlock")
|
||||||
|
open_calls = async_mock_service(hass, "lock", "open")
|
||||||
|
|
||||||
# These calls should do nothing as entities already in desired state
|
# These calls should do nothing as entities already in desired state
|
||||||
await async_reproduce_state(
|
await async_reproduce_state(
|
||||||
|
@ -24,11 +26,13 @@ async def test_reproducing_states(
|
||||||
[
|
[
|
||||||
State("lock.entity_locked", "locked"),
|
State("lock.entity_locked", "locked"),
|
||||||
State("lock.entity_unlocked", "unlocked", {}),
|
State("lock.entity_unlocked", "unlocked", {}),
|
||||||
|
State("lock.entity_opened", "open", {}),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(lock_calls) == 0
|
assert len(lock_calls) == 0
|
||||||
assert len(unlock_calls) == 0
|
assert len(unlock_calls) == 0
|
||||||
|
assert len(open_calls) == 0
|
||||||
|
|
||||||
# Test invalid state is handled
|
# Test invalid state is handled
|
||||||
await async_reproduce_state(hass, [State("lock.entity_locked", "not_supported")])
|
await async_reproduce_state(hass, [State("lock.entity_locked", "not_supported")])
|
||||||
|
@ -36,13 +40,15 @@ async def test_reproducing_states(
|
||||||
assert "not_supported" in caplog.text
|
assert "not_supported" in caplog.text
|
||||||
assert len(lock_calls) == 0
|
assert len(lock_calls) == 0
|
||||||
assert len(unlock_calls) == 0
|
assert len(unlock_calls) == 0
|
||||||
|
assert len(open_calls) == 0
|
||||||
|
|
||||||
# Make sure correct services are called
|
# Make sure correct services are called
|
||||||
await async_reproduce_state(
|
await async_reproduce_state(
|
||||||
hass,
|
hass,
|
||||||
[
|
[
|
||||||
State("lock.entity_locked", "unlocked"),
|
State("lock.entity_locked", "open"),
|
||||||
State("lock.entity_unlocked", "locked"),
|
State("lock.entity_unlocked", "locked"),
|
||||||
|
State("lock.entity_opened", "unlocked"),
|
||||||
# Should not raise
|
# Should not raise
|
||||||
State("lock.non_existing", "on"),
|
State("lock.non_existing", "on"),
|
||||||
],
|
],
|
||||||
|
@ -54,4 +60,8 @@ async def test_reproducing_states(
|
||||||
|
|
||||||
assert len(unlock_calls) == 1
|
assert len(unlock_calls) == 1
|
||||||
assert unlock_calls[0].domain == "lock"
|
assert unlock_calls[0].domain == "lock"
|
||||||
assert unlock_calls[0].data == {"entity_id": "lock.entity_locked"}
|
assert unlock_calls[0].data == {"entity_id": "lock.entity_opened"}
|
||||||
|
|
||||||
|
assert len(open_calls) == 1
|
||||||
|
assert open_calls[0].domain == "lock"
|
||||||
|
assert open_calls[0].data == {"entity_id": "lock.entity_locked"}
|
||||||
|
|
Loading…
Add table
Reference in a new issue