From 7062842e3c5432c919140d237445224273901b2c Mon Sep 17 00:00:00 2001 From: rgc99 Date: Sat, 28 Oct 2023 04:48:15 +0000 Subject: [PATCH] Add toggle option to check_back --- .../irrigation_unlimited/const.py | 1 + .../irrigation_unlimited.py | 14 +++- .../irrigation_unlimited/schema.py | 2 + tests/configs/test_check_back.yaml | 2 + tests/test_check_back.py | 64 +++++++++++++++++-- 5 files changed, 73 insertions(+), 10 deletions(-) diff --git a/custom_components/irrigation_unlimited/const.py b/custom_components/irrigation_unlimited/const.py index 89dc8bc..bc8c234 100644 --- a/custom_components/irrigation_unlimited/const.py +++ b/custom_components/irrigation_unlimited/const.py @@ -113,6 +113,7 @@ CONF_QUEUE = "queue" CONF_QUEUE_MANUAL = "queue_manual" CONF_USER = "user" +CONF_TOGGLE = "toggle" # Defaults DEFAULT_NAME = DOMAIN diff --git a/custom_components/irrigation_unlimited/irrigation_unlimited.py b/custom_components/irrigation_unlimited/irrigation_unlimited.py index 7eea496..92cc58e 100644 --- a/custom_components/irrigation_unlimited/irrigation_unlimited.py +++ b/custom_components/irrigation_unlimited/irrigation_unlimited.py @@ -198,6 +198,7 @@ CONF_QUEUE, CONF_QUEUE_MANUAL, CONF_USER, + CONF_TOGGLE, ) _LOGGER: Logger = getLogger(__package__) @@ -1372,6 +1373,7 @@ def __init__( self._state_on = STATE_ON self._state_off = STATE_OFF self._check_back_entity_id: str = None + self._check_back_toggle: bool = False # private variables self._state: bool = None # This parameter should mirror IUZone._is_on self._check_back_time: timedelta = None @@ -1379,7 +1381,6 @@ def __init__( def _set_switch(self, entity_id: str | list[str], state: bool) -> None: """Make the HA call to physically turn the switch on/off""" - self._state = state self._hass.async_create_task( self._hass.services.async_call( HADOMAIN, @@ -1436,6 +1437,7 @@ def load_params(config: OrderedDict) -> None: self._check_back_entity_id = config.get( CONF_ENTITY_ID, self._check_back_entity_id ) + self._check_back_toggle = config.get(CONF_TOGGLE, self._check_back_toggle) self.clear() self._switch_entity_id = config.get(CONF_ENTITY_ID) @@ -1471,17 +1473,22 @@ def _check_entity(entity_id: str, expected: str) -> bool: return is_valid + def do_resync(entity_id: str) -> None: + if self._check_back_toggle: + self._set_switch(entity_id, not self._state) + self._set_switch(entity_id, self._state) + if self._switch_entity_id is not None: expected = self._state_on if self._state else self._state_off if self._check_back_entity_id is None: for entity_id in self._switch_entity_id: if not _check_entity(entity_id, expected): if resync: - self._set_switch(entity_id, self._state) + do_resync(entity_id) else: if not _check_entity(self._check_back_entity_id, expected): if resync and len(self._switch_entity_id) == 1: - self._set_switch(self._switch_entity_id, self._state) + do_resync(self._switch_entity_id) return result def call_switch(self, state: bool, stime: datetime = None) -> None: @@ -1492,6 +1499,7 @@ def call_switch(self, state: bool, stime: datetime = None) -> None: # Switch state was changed before the recheck. Check now. self.check_switch(stime, False, True) self._check_back_time = None + self._state = state self._set_switch(self._switch_entity_id, state) if stime is not None and ( self._check_back_states == "all" diff --git a/custom_components/irrigation_unlimited/schema.py b/custom_components/irrigation_unlimited/schema.py index 0570e99..3e7ec94 100644 --- a/custom_components/irrigation_unlimited/schema.py +++ b/custom_components/irrigation_unlimited/schema.py @@ -86,6 +86,7 @@ CONF_QUEUE, CONF_QUEUE_MANUAL, CONF_USER, + CONF_TOGGLE, ) IU_ID = r"^[a-z0-9]+(_[a-z0-9]+)*$" @@ -203,6 +204,7 @@ def _parse_dd_mmm(value: str) -> date | None: vol.Optional(CONF_STATE_ON): cv.string, vol.Optional(CONF_STATE_OFF): cv.string, vol.Optional(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_TOGGLE): cv.boolean, } ) diff --git a/tests/configs/test_check_back.yaml b/tests/configs/test_check_back.yaml index 626f51f..2415b59 100644 --- a/tests/configs/test_check_back.yaml +++ b/tests/configs/test_check_back.yaml @@ -94,6 +94,8 @@ irrigation_unlimited: zones: - name: "Zone 1" entity_id: input_boolean.dummy_switch_z1 + check_back: + toggle: true - name: "Zone 2" entity_id: input_boolean.dummy_switch_z2,input_boolean.dummy_switch_z3,input_boolean.dummy_switch_z4 sequences: diff --git a/tests/test_check_back.py b/tests/test_check_back.py index bb35937..a787210 100644 --- a/tests/test_check_back.py +++ b/tests/test_check_back.py @@ -26,6 +26,7 @@ async def test_check_back(hass: ha.HomeAssistant, skip_dependencies, skip_histor sync_event_errors: list[ha.Event] = [] switch_event_errors: list[ha.Event] = [] + hass_events: list[ha.Event] = [] def handle_sync_events(event: ha.Event) -> None: nonlocal sync_event_errors @@ -39,14 +40,26 @@ def handle_switch_events(event: ha.Event) -> None: data["vtime"] = exam.virtual_time switch_event_errors.append(data) - hass.bus.async_listen(f"{DOMAIN}_{EVENT_SYNC_ERROR}", handle_sync_events) - hass.bus.async_listen(f"{DOMAIN}_{EVENT_SWITCH_ERROR}", handle_switch_events) + def handle_hass_events(event: ha.Event) -> None: + nonlocal hass_events + if event.data.get(ha.ATTR_DOMAIN) == ha.DOMAIN: + hass_events.append(event.data) + + remove_sync_events = hass.bus.async_listen( + f"{DOMAIN}_{EVENT_SYNC_ERROR}", handle_sync_events + ) + remove_switch_events = hass.bus.async_listen( + f"{DOMAIN}_{EVENT_SWITCH_ERROR}", handle_switch_events + ) + remove_hass_envents = hass.bus.async_listen( + ha.EVENT_CALL_SERVICE, handle_hass_events + ) async def kill_m(atime: datetime) -> None: """Turn off dummy_switch_m at the specified time""" await exam.run_until(atime) await hass.services.async_call( - "homeassistant", + "input_boolean", "turn_off", {"entity_id": "input_boolean.dummy_switch_m"}, True, @@ -56,7 +69,7 @@ async def kill_z1(atime: datetime) -> None: """Turn off dummy_switch_z1 at the specified time""" await exam.run_until(atime) await hass.services.async_call( - "homeassistant", + "input_boolean", "turn_off", {"entity_id": "input_boolean.dummy_switch_z1"}, True, @@ -66,7 +79,7 @@ async def kill_z3(atime: datetime) -> None: """Turn off dummy_switch_z3 at the specified time""" await exam.run_until(atime) await hass.services.async_call( - "homeassistant", + "input_boolean", "turn_off", {"entity_id": "input_boolean.dummy_switch_z3"}, True, @@ -75,7 +88,7 @@ async def kill_z3(atime: datetime) -> None: async def change_check_back_switch(state: bool) -> None: """Turn on/off dummy_switch_call_back_switch""" await hass.services.async_call( - "homeassistant", + "input_boolean", "turn_on" if state else "turn_off", {"entity_id": "input_boolean.dummy_check_back_switch"}, True, @@ -107,11 +120,27 @@ async def change_check_back_switch(state: bool) -> None: await exam.begin_test(2) await kill_m("2021-01-04 07:05:15") assert hass.states.get("input_boolean.dummy_switch_m").state == "off" + hass_events.clear() await exam.run_until("2021-01-04 07:05:45") + assert hass_events == [ + { + "domain": "homeassistant", + "service": "turn_on", + "service_data": {"entity_id": "input_boolean.dummy_switch_m"}, + } + ] assert hass.states.get("input_boolean.dummy_switch_m").state == "on" await kill_z3("2021-01-04 07:12:15") assert hass.states.get("input_boolean.dummy_switch_z3").state == "off" + hass_events.clear() await exam.run_until("2021-01-04 07:12:45") + assert hass_events == [ + { + "domain": "homeassistant", + "service": "turn_on", + "service_data": {"entity_id": "input_boolean.dummy_switch_z3"}, + } + ] assert hass.states.get("input_boolean.dummy_switch_z3").state == "on" await exam.finish_test() assert mock_sync_logger.call_count == 2 @@ -253,7 +282,24 @@ async def change_check_back_switch(state: bool) -> None: hass.states.get("input_boolean.dummy_check_back_switch").state == "off" ) + hass_events.clear() await exam.run_until("2021-01-04 20:05:20") + assert hass_events == [ + { + "domain": "homeassistant", + "service": "turn_off", + "service_data": { + "entity_id": ["input_boolean.dummy_switch_z1"] + }, + }, + { + "domain": "homeassistant", + "service": "turn_on", + "service_data": { + "entity_id": ["input_boolean.dummy_switch_z1"] + }, + }, + ] assert hass.states.get("input_boolean.dummy_switch_z1").state == "on" assert ( hass.states.get("input_boolean.dummy_check_back_switch").state @@ -273,10 +319,14 @@ async def change_check_back_switch(state: bool) -> None: == "off" ) await exam.finish_test() + assert mock_sync_logger.call_count == 10 assert mock_switch_logger.call_count == 2 assert len(sync_event_errors) == 10 assert len(switch_event_errors) == 2 - # Check the exam results exam.check_summary() + + remove_switch_events() + remove_sync_events() + remove_hass_envents()