mirror of
https://github.com/home-assistant/core.git
synced 2025-08-05 05:35:11 +02:00
Turn off not cancellable scripts automatically HomeKit (#17793)
This commit is contained in:
@@ -4,10 +4,12 @@ import logging
|
|||||||
from pyhap.const import (
|
from pyhap.const import (
|
||||||
CATEGORY_OUTLET, CATEGORY_SWITCH)
|
CATEGORY_OUTLET, CATEGORY_SWITCH)
|
||||||
|
|
||||||
|
from homeassistant.components.script import ATTR_CAN_CANCEL
|
||||||
from homeassistant.components.switch import DOMAIN
|
from homeassistant.components.switch import DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, CONF_TYPE, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
|
ATTR_ENTITY_ID, CONF_TYPE, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
|
from homeassistant.helpers.event import call_later
|
||||||
|
|
||||||
from . import TYPES
|
from . import TYPES
|
||||||
from .accessories import HomeAccessory
|
from .accessories import HomeAccessory
|
||||||
@@ -74,21 +76,48 @@ class Switch(HomeAccessory):
|
|||||||
self._domain = split_entity_id(self.entity_id)[0]
|
self._domain = split_entity_id(self.entity_id)[0]
|
||||||
self._flag_state = False
|
self._flag_state = False
|
||||||
|
|
||||||
|
self.activate_only = self.is_activate(
|
||||||
|
self.hass.states.get(self.entity_id))
|
||||||
|
|
||||||
serv_switch = self.add_preload_service(SERV_SWITCH)
|
serv_switch = self.add_preload_service(SERV_SWITCH)
|
||||||
self.char_on = serv_switch.configure_char(
|
self.char_on = serv_switch.configure_char(
|
||||||
CHAR_ON, value=False, setter_callback=self.set_state)
|
CHAR_ON, value=False, setter_callback=self.set_state)
|
||||||
|
|
||||||
|
def is_activate(self, state):
|
||||||
|
"""Check if entity is activate only."""
|
||||||
|
can_cancel = state.attributes.get(ATTR_CAN_CANCEL)
|
||||||
|
if self._domain == 'script' and not can_cancel:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reset_switch(self, *args):
|
||||||
|
"""Reset switch to emulate activate click."""
|
||||||
|
_LOGGER.debug('%s: Reset switch to off', self.entity_id)
|
||||||
|
self.char_on.set_value(0)
|
||||||
|
|
||||||
def set_state(self, value):
|
def set_state(self, value):
|
||||||
"""Move switch state to value if call came from HomeKit."""
|
"""Move switch state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug('%s: Set switch state to %s',
|
_LOGGER.debug('%s: Set switch state to %s',
|
||||||
self.entity_id, value)
|
self.entity_id, value)
|
||||||
|
if self.activate_only and value == 0:
|
||||||
|
_LOGGER.debug('%s: Ignoring turn_off call', self.entity_id)
|
||||||
|
return
|
||||||
self._flag_state = True
|
self._flag_state = True
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||||
self.call_service(self._domain, service, params)
|
self.call_service(self._domain, service, params)
|
||||||
|
|
||||||
|
if self.activate_only:
|
||||||
|
call_later(self.hass, 1, self.reset_switch)
|
||||||
|
|
||||||
def update_state(self, new_state):
|
def update_state(self, new_state):
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
|
self.activate_only = self.is_activate(new_state)
|
||||||
|
if self.activate_only:
|
||||||
|
_LOGGER.debug('%s: Ignore state change, entity is activate only',
|
||||||
|
self.entity_id)
|
||||||
|
return
|
||||||
|
|
||||||
current_state = (new_state.state == STATE_ON)
|
current_state = (new_state.state == STATE_ON)
|
||||||
if not self._flag_state:
|
if not self._flag_state:
|
||||||
_LOGGER.debug('%s: Set current state to %s',
|
_LOGGER.debug('%s: Set current state to %s',
|
||||||
|
@@ -1,14 +1,18 @@
|
|||||||
"""Test different accessory types: Switches."""
|
"""Test different accessory types: Switches."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.homekit.const import (
|
from homeassistant.components.homekit.const import (
|
||||||
ATTR_VALUE, TYPE_FAUCET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_VALVE)
|
ATTR_VALUE, TYPE_FAUCET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_VALVE)
|
||||||
from homeassistant.components.homekit.type_switches import (
|
from homeassistant.components.homekit.type_switches import (
|
||||||
Outlet, Switch, Valve)
|
Outlet, Switch, Valve)
|
||||||
|
from homeassistant.components.script import ATTR_CAN_CANCEL
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_TYPE, STATE_OFF, STATE_ON
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_TYPE, STATE_OFF, STATE_ON
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import async_mock_service
|
from tests.common import async_fire_time_changed, async_mock_service
|
||||||
|
|
||||||
|
|
||||||
async def test_outlet_set_state(hass, hk_driver, events):
|
async def test_outlet_set_state(hass, hk_driver, events):
|
||||||
@@ -54,18 +58,18 @@ async def test_outlet_set_state(hass, hk_driver, events):
|
|||||||
assert events[-1].data[ATTR_VALUE] is None
|
assert events[-1].data[ATTR_VALUE] is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('entity_id', [
|
@pytest.mark.parametrize('entity_id, attrs', [
|
||||||
'automation.test',
|
('automation.test', {}),
|
||||||
'input_boolean.test',
|
('input_boolean.test', {}),
|
||||||
'remote.test',
|
('remote.test', {}),
|
||||||
'script.test',
|
('script.test', {ATTR_CAN_CANCEL: True}),
|
||||||
'switch.test',
|
('switch.test', {}),
|
||||||
])
|
])
|
||||||
async def test_switch_set_state(hass, hk_driver, entity_id, events):
|
async def test_switch_set_state(hass, hk_driver, entity_id, attrs, events):
|
||||||
"""Test if accessory and HA are updated accordingly."""
|
"""Test if accessory and HA are updated accordingly."""
|
||||||
domain = split_entity_id(entity_id)[0]
|
domain = split_entity_id(entity_id)[0]
|
||||||
|
|
||||||
hass.states.async_set(entity_id, None)
|
hass.states.async_set(entity_id, None, attrs)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
acc = Switch(hass, hk_driver, 'Switch', entity_id, 2, None)
|
acc = Switch(hass, hk_driver, 'Switch', entity_id, 2, None)
|
||||||
await hass.async_add_job(acc.run)
|
await hass.async_add_job(acc.run)
|
||||||
@@ -74,13 +78,14 @@ async def test_switch_set_state(hass, hk_driver, entity_id, events):
|
|||||||
assert acc.aid == 2
|
assert acc.aid == 2
|
||||||
assert acc.category == 8 # Switch
|
assert acc.category == 8 # Switch
|
||||||
|
|
||||||
|
assert acc.activate_only is False
|
||||||
assert acc.char_on.value is False
|
assert acc.char_on.value is False
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_ON)
|
hass.states.async_set(entity_id, STATE_ON, attrs)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_on.value is True
|
assert acc.char_on.value is True
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_OFF)
|
hass.states.async_set(entity_id, STATE_OFF, attrs)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_on.value is False
|
assert acc.char_on.value is False
|
||||||
|
|
||||||
@@ -172,3 +177,65 @@ async def test_valve_set_state(hass, hk_driver, events):
|
|||||||
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
assert len(events) == 2
|
assert len(events) == 2
|
||||||
assert events[-1].data[ATTR_VALUE] is None
|
assert events[-1].data[ATTR_VALUE] is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('entity_id, attrs', [
|
||||||
|
('script.test', {}),
|
||||||
|
('script.test', {ATTR_CAN_CANCEL: False}),
|
||||||
|
])
|
||||||
|
async def test_reset_switch(hass, hk_driver, entity_id, attrs, events):
|
||||||
|
"""Test if switch accessory is reset correctly."""
|
||||||
|
domain = split_entity_id(entity_id)[0]
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, None, attrs)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Switch(hass, hk_driver, 'Switch', entity_id, 2, None)
|
||||||
|
await hass.async_add_job(acc.run)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.activate_only is True
|
||||||
|
assert acc.char_on.value is False
|
||||||
|
|
||||||
|
call_turn_on = async_mock_service(hass, domain, 'turn_on')
|
||||||
|
call_turn_off = async_mock_service(hass, domain, 'turn_off')
|
||||||
|
|
||||||
|
await hass.async_add_job(acc.char_on.client_update_value, True)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc.char_on.value is True
|
||||||
|
assert call_turn_on
|
||||||
|
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[-1].data[ATTR_VALUE] is None
|
||||||
|
|
||||||
|
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||||
|
async_fire_time_changed(hass, future)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc.char_on.value is False
|
||||||
|
assert len(events) == 1
|
||||||
|
assert not call_turn_off
|
||||||
|
|
||||||
|
await hass.async_add_job(acc.char_on.client_update_value, False)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc.char_on.value is False
|
||||||
|
assert len(events) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reset_switch_reload(hass, hk_driver, events):
|
||||||
|
"""Test reset switch after script reload."""
|
||||||
|
entity_id = 'script.test'
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, None)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Switch(hass, hk_driver, 'Switch', entity_id, 2, None)
|
||||||
|
await hass.async_add_job(acc.run)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.activate_only is True
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, None, {ATTR_CAN_CANCEL: True})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc.activate_only is False
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, None, {ATTR_CAN_CANCEL: False})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc.activate_only is True
|
||||||
|
Reference in New Issue
Block a user