diff --git a/custom_components/irrigation_unlimited/irrigation_unlimited.py b/custom_components/irrigation_unlimited/irrigation_unlimited.py index 7a78686..e4453ab 100644 --- a/custom_components/irrigation_unlimited/irrigation_unlimited.py +++ b/custom_components/irrigation_unlimited/irrigation_unlimited.py @@ -2878,6 +2878,10 @@ def update_pause(self, stime: datetime) -> bool: return True return False + def cancel(self, stime: datetime) -> None: + """Cancel the sequence run""" + self.advance(stime, -(self._end_time - stime)) + def update(self) -> bool: """Update the status of the sequence""" @@ -3724,6 +3728,13 @@ def service_pause(self, data: MappingProxyType, stime: datetime) -> bool: if sqr.running: sqr.pause(stime) + def service_cancel(self, data: MappingProxyType, stime: datetime) -> bool: + """Cancel the sequence""" + # pylint: disable=unused-argument + for sqr in self._run_queue: + if sqr.running: + sqr.cancel(stime) + class IUController(IUBase): """Irrigation Unlimited Controller (Master) class""" @@ -6098,7 +6109,9 @@ def service_call( changed = controller.service_suspend(data1, stime) elif service == SERVICE_CANCEL: - if zone is not None: + if sequence is not None: + sequence.service_cancel(data1, stime) + elif zone is not None: zone.service_cancel(data1, stime) else: controller.service_cancel(data1, stime) diff --git a/tests/configs/service_sequence_cancel.yaml b/tests/configs/service_sequence_cancel.yaml new file mode 100644 index 0000000..e8077cb --- /dev/null +++ b/tests/configs/service_sequence_cancel.yaml @@ -0,0 +1,143 @@ +irrigation_unlimited: + refresh_interval: 2000 + controllers: + - name: Test Controller 1 + preamble: "0:01:00" + postamble: "0:01:00" + zones: + - name: 'Zone 1' + - name: 'Zone 2' + - name: 'Zone 3' + - name: 'Zone 4' + sequences: + - name: "Seq 1" + duration: "0:05:00" + delay: "0:01:00" + repeat: 2 + schedules: + - time: "06:05" + zones: + - zone_id: 1 + repeat: 2 + - zone_id: [2,3] + delay: "-0:01:00" + - zone_id: 4 + testing: + enabled: true + speed: 1.0 + output_events: false + show_log: false + autoplay: false + times: + - name: '1-Normal run' + start: '2023-11-28 06:00' + end: '2023-11-28 07:00' + results: + - {t: '2023-11-28 06:04:00', c: 1, z: 0, s: 1} + - {t: '2023-11-28 06:05:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:10:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:11:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:16:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:17:00', c: 1, z: 2, s: 1} + - {t: '2023-11-28 06:17:00', c: 1, z: 3, s: 1} + - {t: '2023-11-28 06:21:00', c: 1, z: 4, s: 1} + - {t: '2023-11-28 06:22:00', c: 1, z: 2, s: 0} + - {t: '2023-11-28 06:22:00', c: 1, z: 3, s: 0} + - {t: '2023-11-28 06:26:00', c: 1, z: 4, s: 0} + - {t: '2023-11-28 06:27:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:32:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:33:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:38:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:39:00', c: 1, z: 2, s: 1} + - {t: '2023-11-28 06:39:00', c: 1, z: 3, s: 1} + - {t: '2023-11-28 06:43:00', c: 1, z: 4, s: 1} + - {t: '2023-11-28 06:44:00', c: 1, z: 2, s: 0} + - {t: '2023-11-28 06:44:00', c: 1, z: 3, s: 0} + - {t: '2023-11-28 06:48:00', c: 1, z: 4, s: 0} + - {t: '2023-11-28 06:49:00', c: 1, z: 0, s: 0} + - name: '2-Cancel in first zone' + start: '2023-11-28 06:00' + end: '2023-11-28 07:00' + results: + - {t: '2023-11-28 06:04:00', c: 1, z: 0, s: 1} + - {t: '2023-11-28 06:05:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:07:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:08:00', c: 1, z: 0, s: 0} + - name: '3-Cancel between first and second zone' + start: '2023-11-28 06:00' + end: '2023-11-28 07:00' + results: + - {t: '2023-11-28 06:04:00', c: 1, z: 0, s: 1} + - {t: '2023-11-28 06:05:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:10:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:11:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:16:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:17:30', c: 1, z: 0, s: 0} + - name: '4-Cancel between second and third zone' + start: '2023-11-28 06:00' + end: '2023-11-28 07:00' + results: + - {t: '2023-11-28 06:04:00', c: 1, z: 0, s: 1} + - {t: '2023-11-28 06:05:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:10:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:11:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:16:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:17:00', c: 1, z: 2, s: 1} + - {t: '2023-11-28 06:17:00', c: 1, z: 3, s: 1} + - {t: '2023-11-28 06:21:00', c: 1, z: 4, s: 1} + - {t: '2023-11-28 06:21:30', c: 1, z: 2, s: 0} + - {t: '2023-11-28 06:21:30', c: 1, z: 3, s: 0} + - {t: '2023-11-28 06:21:30', c: 1, z: 4, s: 0} + - {t: '2023-11-28 06:22:30', c: 1, z: 0, s: 0} + - name: '5-Cancel last zone at overlap' + start: '2023-11-28 06:00' + end: '2023-11-28 07:00' + results: + - {t: '2023-11-28 06:04:00', c: 1, z: 0, s: 1} + - {t: '2023-11-28 06:05:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:10:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:11:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:16:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:17:00', c: 1, z: 2, s: 1} + - {t: '2023-11-28 06:17:00', c: 1, z: 3, s: 1} + - {t: '2023-11-28 06:21:00', c: 1, z: 4, s: 1} + - {t: '2023-11-28 06:22:00', c: 1, z: 2, s: 0} + - {t: '2023-11-28 06:22:00', c: 1, z: 3, s: 0} + - {t: '2023-11-28 06:26:00', c: 1, z: 4, s: 0} + - {t: '2023-11-28 06:27:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:32:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:33:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:38:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:39:00', c: 1, z: 2, s: 1} + - {t: '2023-11-28 06:39:00', c: 1, z: 3, s: 1} + - {t: '2023-11-28 06:43:00', c: 1, z: 4, s: 1} + - {t: '2023-11-28 06:43:30', c: 1, z: 2, s: 0} + - {t: '2023-11-28 06:43:30', c: 1, z: 3, s: 0} + - {t: '2023-11-28 06:43:30', c: 1, z: 4, s: 0} + - {t: '2023-11-28 06:44:30', c: 1, z: 0, s: 0} + - name: '6-Cancel last zone' + start: '2023-11-28 06:00' + end: '2023-11-28 07:00' + results: + - {t: '2023-11-28 06:04:00', c: 1, z: 0, s: 1} + - {t: '2023-11-28 06:05:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:10:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:11:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:16:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:17:00', c: 1, z: 2, s: 1} + - {t: '2023-11-28 06:17:00', c: 1, z: 3, s: 1} + - {t: '2023-11-28 06:21:00', c: 1, z: 4, s: 1} + - {t: '2023-11-28 06:22:00', c: 1, z: 2, s: 0} + - {t: '2023-11-28 06:22:00', c: 1, z: 3, s: 0} + - {t: '2023-11-28 06:26:00', c: 1, z: 4, s: 0} + - {t: '2023-11-28 06:27:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:32:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:33:00', c: 1, z: 1, s: 1} + - {t: '2023-11-28 06:38:00', c: 1, z: 1, s: 0} + - {t: '2023-11-28 06:39:00', c: 1, z: 2, s: 1} + - {t: '2023-11-28 06:39:00', c: 1, z: 3, s: 1} + - {t: '2023-11-28 06:43:00', c: 1, z: 4, s: 1} + - {t: '2023-11-28 06:44:00', c: 1, z: 2, s: 0} + - {t: '2023-11-28 06:44:00', c: 1, z: 3, s: 0} + - {t: '2023-11-28 06:46:00', c: 1, z: 4, s: 0} + - {t: '2023-11-28 06:47:00', c: 1, z: 0, s: 0} diff --git a/tests/test_service_sequence_cancel.py b/tests/test_service_sequence_cancel.py new file mode 100644 index 0000000..85475b0 --- /dev/null +++ b/tests/test_service_sequence_cancel.py @@ -0,0 +1,70 @@ +"""irrigation_unlimited service cancel tester""" +import homeassistant.core as ha +from custom_components.irrigation_unlimited.const import ( + SERVICE_CANCEL, +) +from tests.iu_test_support import IUExam + +IUExam.quiet_mode() + + +async def test_service_sequence_cancel( + hass: ha.HomeAssistant, skip_dependencies, skip_history +): + """Test service calls to sequence""" + # pylint: disable=unused-argument + + async with IUExam(hass, "service_sequence_cancel.yaml") as exam: + await exam.run_test(1) + + await exam.begin_test(2) + await exam.run_until("2023-11-28 06:07") + await exam.call( + SERVICE_CANCEL, + { + "entity_id": "binary_sensor.irrigation_unlimited_c1_s1", + }, + ) + await exam.finish_test() + + await exam.begin_test(3) + await exam.run_until("2023-11-28 06:16:30") + await exam.call( + SERVICE_CANCEL, + { + "entity_id": "binary_sensor.irrigation_unlimited_c1_s1", + }, + ) + await exam.finish_test() + + await exam.begin_test(4) + await exam.run_until("2023-11-28 06:21:30") + await exam.call( + SERVICE_CANCEL, + { + "entity_id": "binary_sensor.irrigation_unlimited_c1_s1", + }, + ) + await exam.finish_test() + + await exam.begin_test(5) + await exam.run_until("2023-11-28 06:43:30") + await exam.call( + SERVICE_CANCEL, + { + "entity_id": "binary_sensor.irrigation_unlimited_c1_s1", + }, + ) + await exam.finish_test() + + await exam.begin_test(6) + await exam.run_until("2023-11-28 06:46:00") + await exam.call( + SERVICE_CANCEL, + { + "entity_id": "binary_sensor.irrigation_unlimited_c1_s1", + }, + ) + await exam.finish_test() + + exam.check_summary()