Skip to content

Commit

Permalink
Allow sequence_id to accept a list on service calls
Browse files Browse the repository at this point in the history
  • Loading branch information
rgc99 committed Sep 5, 2024
1 parent 05cd591 commit bc72195
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 32 deletions.
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -744,32 +744,30 @@ Enables/disables/toggles the controller, zone, sequence or sequence zone respect
| Service data attribute | Type | Required | Description |
| ---------------------- | ---- | -------- | ----------- |
| `entity_id` | [string/list](#141-irrigation-unlimited-entities) | yes | Controller or zone to enable/disable/toggle. |
| `sequence_id` | [number](#145-sequence) | no | Sequence to enable/disable/toggle. |
| `sequence_id` | [number/list](#145-sequence) | no | Sequences to enable/disable/toggle. Entity must be a controller. |
| `zones` | [number/list](#146-zones) | no | Sequence zones to enable/disable/toggle. |

### 7.2. Services `pause` and `resume`

Pauses/resumes a sequence. This service call "stops the clock" when `paused` so to speak and "continues to run it" upon `resume`.

This is particularly helpful in a use case scenario where a main water supply is used for both irrigation and ie filling a domestic cold water tank at the same time. If the water pressure is only sufficient for either irrigaton or tank filling, this service call allows to `pause` the irrigation whilst a tank is filled, and then `resumes` irrigation without interrupting the time allocated to the sequences or zones thereof.
This is particularly helpful in a use case scenario where a main water supply is used for both irrigation and ie filling a domestic cold water tank at the same time. If the water pressure is only sufficient for either irrigaton or tank filling, this service call allows to `pause` the irrigation whilst a tank is filled, and then `resumes` irrigation without interrupting the time allocated to the sequences or zones thereof.

| Service data attribute | Type | Required | Description |
| ---------------------- | ---- | -------- | ----------- |
| `entity_id` | [string/list](#141-irrigation-unlimited-entities) | yes | Entity_id of a controller or sequence to pause/resume. If a controller entity is selected it will target the sequence or all sequences (see next parameter). If more than one entity_id are to be targeted a group integration helper may be used. |
| `sequence_id` | [number](#145-sequence) | only if entity_id represents a controller | Sequence to pause/resume. The sequence_id is only used when the entity_id is the controller. If sequence_id is set to 0 then all sequences of the controller will be effected. |
| `sequence_id` | [number/list](#145-sequence) | only if entity_id represents a controller | Sequence to pause/resume. The sequence_id is only used when the entity_id is the controller. If sequence_id is set to 0 then all sequences of the controller will be effected. |

There is an example for a [pause-resume button](#86-pause-resume-button) that targets all sequences within all controllers creating a globla `pause` and `resume` button.

### 7.3. Service `suspend`

NOTE: Available from release 2023.9.0.

Suspend operation of a controller, zone, sequence or sequence zone for a period of time. This is like a temporary `disable` that will automatically reset.

| Service data attribute | Type | Required | Description |
| ---------------------- | ---- | -------- | ----------- |
| `entity_id` | [string/list](#141-irrigation-unlimited-entities) | yes | Controller or zone to run. |
| `sequence_id` | [number](#145-sequence) | no | Sequence to suspend. |
| `sequence_id` | [number/list](#145-sequence) | no | Sequences to suspend. Entity must be a controller. |
| `zones` | [number/list](#146-zones) | no | Sequence zones to suspend. |
| `for` | [duration](#142-duration-time-period) | see below* | Suspend for a period of time. Supports [templating](#144-templating). |
| `until` | string | see below* | Suspend until a point in time. Format is `%Y-%m-%d %H:%M:%S` for example `2023-08-01 07:30:00`. |
Expand All @@ -795,7 +793,7 @@ Turn on the controller or zone for a period of time. When a sequence is specifie
| `time` | [duration](#142-duration-time-period) | no | Total time to run. Supports [templating](#144-templating). If not provided or is "0:00:00" then adjusted defaults will be applied |
| `delay` | [duration](#142-duration-time-period) | no | Delay between runs when queued |
| `queue` | boolean | no | Queue or run immediately. |
| `sequence_id` | [number](#145-sequence) | no | Sequence to run. Each zone duration will be adjusted to fit the allocated time, delays are not effected. Note: The time parameter _includes_ inter zone delays. If the total delays are greater than the specified time then the sequence will not run. |
| `sequence_id` | [number/list](#145-sequences) | no | Sequences to run. Each zone duration will be adjusted to fit the allocated time, delays are not effected. Note: The time parameter _includes_ inter zone delays. If the total delays are greater than the specified time then the sequence will not run. Entity must be a controller. |

### 7.6. Service `adjust_time`

Expand All @@ -815,7 +813,7 @@ Tip: Use forecast and observation data collected by weather integrations in auto
| `reset` | none | see below* | Reset adjustment back to the original schedule time (Does not effect minimum or maximum settings). |
| `minimum` | [duration](#142-duration-time-period) | no | Set the minimum run time. Supports [templating](#144-templating). |
| `maximum` | [duration](#142-duration-time-period) | no | Set the maximum run time. Note: The default is no limit. Supports [templating](#144-templating). |
| `sequence_id` | [number](#145-sequence) | no | Sequence to adjust. |
| `sequence_id` | [number/list](#145-sequence) | no | Sequences to adjust. Entity must be a controller. |
| `zones` | [number/list](#146-zones) | no | Zones to adjust. |

\* Must have one and only one of `actual`, `percentage`, `increase`, `decrease` or `reset`.
Expand Down
6 changes: 3 additions & 3 deletions custom_components/irrigation_unlimited/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def _restore_enabled(
svc = SERVICE_ENABLE if data.get(CONF_ENABLED) else SERVICE_DISABLE
svd = {}
if sequence is not None:
svd[CONF_SEQUENCE_ID] = sequence.index + 1
svd[CONF_SEQUENCE_ID] = [sequence.index + 1]
if sequence_zone is not None:
svd[CONF_ZONES] = [sequence_zone.index + 1]
self._coordinator.service_call(svc, controller, zone, None, svd)
Expand All @@ -168,7 +168,7 @@ def _restore_suspend(
else:
svd[CONF_RESET] = None
if sequence is not None:
svd[CONF_SEQUENCE_ID] = sequence.index + 1
svd[CONF_SEQUENCE_ID] = [sequence.index + 1]
if sequence_zone is not None:
svd[CONF_ZONES] = [sequence_zone.index + 1]
self._coordinator.service_call(SERVICE_SUSPEND, controller, zone, None, svd)
Expand All @@ -187,7 +187,7 @@ def _restore_adjustment(
if (svd := IUAdjustment(data.get(ATTR_ADJUSTMENT)).to_dict()) == {}:
svd[CONF_RESET] = None
if sequence is not None:
svd[CONF_SEQUENCE_ID] = sequence.index + 1
svd[CONF_SEQUENCE_ID] = [sequence.index + 1]
if sequence_zone is not None:
svd[CONF_ZONES] = [sequence_zone.index + 1]
self._coordinator.service_call(SERVICE_TIME_ADJUST, controller, zone, None, svd)
Expand Down
25 changes: 13 additions & 12 deletions custom_components/irrigation_unlimited/irrigation_unlimited.py
Original file line number Diff line number Diff line change
Expand Up @@ -4887,20 +4887,23 @@ def call_switch(self, state: bool, stime: datetime = None) -> None:
self._coordinator.status_changed(stime, self, None, state)

def decode_sequence_id(
self, stime: datetime, sequence_id: int | None
self, stime: datetime, sequences: list | None
) -> list[int] | None:
"""Convert supplied 1's based id into a list of sequence
indexes and validate"""
if sequence_id is None:
if sequences is None:
return None
sequence_list: list[int] = []
if sequence_id == 0:
if sequences == [0]:
sequence_list.extend(sequence.index for sequence in self._sequences)
else:
if self.get_sequence(sequence_id - 1) is not None:
sequence_list.append(sequence_id - 1)
else:
self._coordinator.logger.log_invalid_sequence(stime, self, sequence_id)
for sequence_id in sequences:
if self.get_sequence(sequence_id - 1) is not None:
sequence_list.append(sequence_id - 1)
else:
self._coordinator.logger.log_invalid_sequence(
stime, self, sequence_id
)
return sequence_list

def manual_run_start(
Expand Down Expand Up @@ -4981,17 +4984,15 @@ def service_adjust_time(self, data: MappingProxyType, stime: datetime) -> bool:

def service_manual_run(self, data: MappingProxyType, stime: datetime) -> None:
"""Handler for the manual_run service call"""
sequence_id = data.get(CONF_SEQUENCE_ID, None)
if sequence_id is None:
sequence_list = self.decode_sequence_id(stime, data.get(CONF_SEQUENCE_ID))
if sequence_list is None:
zone_list: list[int] = data.get(CONF_ZONES, None)
for zone in self._zones:
if zone_list is None or zone.index + 1 in zone_list:
zone.service_manual_run(data, stime)
else:
if (sequence := self.get_sequence(sequence_id - 1)) is not None:
for sequence in (self.get_sequence(sqid) for sqid in sequence_list):
sequence.service_manual_run(data, stime)
else:
self._coordinator.logger.log_invalid_sequence(stime, self, sequence_id)

def service_cancel(self, data: MappingProxyType, stime: datetime) -> bool:
"""Handler for the cancel service call"""
Expand Down
14 changes: 8 additions & 6 deletions custom_components/irrigation_unlimited/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ def _parse_dd_mmm(value: str) -> date | None:
ENABLE_DISABLE_SCHEMA = {
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_ZONES): cv.ensure_list,
vol.Optional(CONF_SEQUENCE_ID): cv.positive_int,
vol.Optional(CONF_SEQUENCE_ID): cv.ensure_list,
}

TIME_ADJUST_SCHEMA = vol.All(
Expand All @@ -403,7 +403,7 @@ def _parse_dd_mmm(value: str) -> date | None:
vol.Optional(CONF_MINIMUM): cv.positive_time_period_template,
vol.Optional(CONF_MAXIMUM): cv.positive_time_period_template,
vol.Optional(CONF_ZONES): cv.ensure_list,
vol.Optional(CONF_SEQUENCE_ID): cv.positive_int,
vol.Optional(CONF_SEQUENCE_ID): cv.ensure_list,
}
),
cv.has_at_least_one_key(
Expand All @@ -417,7 +417,7 @@ def _parse_dd_mmm(value: str) -> date | None:
vol.Optional(CONF_DELAY): cv.time_period,
vol.Optional(CONF_QUEUE): cv.boolean,
vol.Optional(CONF_ZONES): cv.ensure_list,
vol.Optional(CONF_SEQUENCE_ID): cv.positive_int,
vol.Optional(CONF_SEQUENCE_ID): cv.ensure_list,
}

SUSPEND_SCHEMA = vol.All(
Expand All @@ -428,7 +428,7 @@ def _parse_dd_mmm(value: str) -> date | None:
vol.Exclusive(CONF_UNTIL, "time_method"): cv.datetime,
vol.Exclusive(CONF_RESET, "time_method"): None,
vol.Optional(CONF_ZONES): cv.ensure_list,
vol.Optional(CONF_SEQUENCE_ID): cv.positive_int,
vol.Optional(CONF_SEQUENCE_ID): cv.ensure_list,
}
),
cv.has_at_least_one_key(CONF_FOR, CONF_UNTIL, CONF_RESET),
Expand All @@ -437,12 +437,14 @@ def _parse_dd_mmm(value: str) -> date | None:
CANCEL_SCHEMA = {
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_ZONES): cv.ensure_list,
vol.Optional(CONF_SEQUENCE_ID): cv.positive_int,
vol.Optional(CONF_SEQUENCE_ID): cv.ensure_list,
}

PAUSE_RESUME_SCHEMA = {
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_SEQUENCE_ID): cv.positive_int,
vol.Optional(CONF_SEQUENCE_ID): cv.ensure_list,
}

}

RELOAD_SERVICE_SCHEMA = vol.Schema({})
82 changes: 82 additions & 0 deletions tests/configs/service_sequence_id_list.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
irrigation_unlimited:
refresh_interval: 2000
testing:
enabled: true
speed: 1.0
output_events: true
show_log: false
autoplay: false
times:
- name: "1-Normal run"
start: "2024-09-04 06:00"
end: "2024-09-04 07:00"
results:
- {t: '2024-09-04 06:05:00', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:05:00', c: 1, z: 1, s: 1}
- {t: '2024-09-04 06:11:00', c: 1, z: 1, s: 0}
- {t: '2024-09-04 06:11:00', c: 1, z: 0, s: 0}
- {t: '2024-09-04 06:12:00', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:12:00', c: 1, z: 2, s: 1}
- {t: '2024-09-04 06:12:00', c: 1, z: 3, s: 1}
- {t: '2024-09-04 06:24:00', c: 1, z: 2, s: 0}
- {t: '2024-09-04 06:24:00', c: 1, z: 3, s: 0}
- {t: '2024-09-04 06:24:00', c: 1, z: 0, s: 0}
- {t: '2024-09-04 06:25:00', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:25:00', c: 1, z: 4, s: 1}
- {t: '2024-09-04 06:43:00', c: 1, z: 4, s: 0}
- {t: '2024-09-04 06:43:00', c: 1, z: 0, s: 0}
- name: "2-Sequence 1 adjust 50 percent"
start: "2024-09-04 06:00"
end: "2024-09-04 07:00"
results:
- {t: '2024-09-04 06:05:00', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:05:00', c: 1, z: 1, s: 1}
- {t: '2024-09-04 06:08:00', c: 1, z: 1, s: 0}
- {t: '2024-09-04 06:08:00', c: 1, z: 0, s: 0}
- {t: '2024-09-04 06:09:00', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:09:00', c: 1, z: 2, s: 1}
- {t: '2024-09-04 06:09:00', c: 1, z: 3, s: 1}
- {t: '2024-09-04 06:15:00', c: 1, z: 2, s: 0}
- {t: '2024-09-04 06:15:00', c: 1, z: 3, s: 0}
- {t: '2024-09-04 06:15:00', c: 1, z: 0, s: 0}
- {t: '2024-09-04 06:16:00', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:16:00', c: 1, z: 4, s: 1}
- {t: '2024-09-04 06:25:00', c: 1, z: 4, s: 0}
- {t: '2024-09-04 06:25:00', c: 1, z: 0, s: 0}
- name: "3-All sequences by 25 percent"
start: "2024-09-04 06:00"
end: "2024-09-04 07:00"
results:
- {t: '2024-09-04 06:05:00', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:05:00', c: 1, z: 1, s: 1}
- {t: '2024-09-04 06:06:30', c: 1, z: 1, s: 0}
- {t: '2024-09-04 06:06:30', c: 1, z: 0, s: 0}
- {t: '2024-09-04 06:07:30', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:07:30', c: 1, z: 2, s: 1}
- {t: '2024-09-04 06:07:30', c: 1, z: 3, s: 1}
- {t: '2024-09-04 06:10:30', c: 1, z: 2, s: 0}
- {t: '2024-09-04 06:10:30', c: 1, z: 3, s: 0}
- {t: '2024-09-04 06:10:30', c: 1, z: 0, s: 0}
- {t: '2024-09-04 06:11:30', c: 1, z: 0, s: 1}
- {t: '2024-09-04 06:11:30', c: 1, z: 4, s: 1}
- {t: '2024-09-04 06:16:00', c: 1, z: 4, s: 0}
- {t: '2024-09-04 06:16:00', c: 1, z: 0, s: 0}
controllers:
- name: "Test controller 1"
zones:
- name: "Zone 1"
- name: "Zone 2"
- name: "Zone 3"
- name: "Zone 4"
sequences:
- name: "Seq 1"
delay: "0:01:00"
schedules:
- time: "06:05"
zones:
- zone_id: 1
duration: "0:06:00"
- zone_id: [2, 3]
duration: "0:12:00"
- zone_id: 4
duration: "0:18:00"
37 changes: 34 additions & 3 deletions tests/test_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Test integration_unlimited service calls."""

# pylint: disable=too-many-lines
from unittest.mock import patch
import homeassistant.core as ha
Expand Down Expand Up @@ -1082,11 +1083,41 @@ async def test_service_suspend_sequence_bad(
print(mock.call_args_list)
assert (
sum(
1
for call in mock.call_args_list
if call.args[0] == 20 and call.args[1] == "CALL"
1
for call in mock.call_args_list
if call.args[0] == 20 and call.args[1] == "CALL"
)
== 1
)


async def test_service_sequence_id_list(
hass: ha.HomeAssistant, skip_dependencies, skip_history
):
"""Test service calls using sequence_id as a list"""
async with IUExam(hass, "service_sequence_id_list.yaml") as exam:
await exam.run_test(1)

await exam.begin_test(2)
await exam.call(
SERVICE_TIME_ADJUST,
{
"entity_id": "binary_sensor.irrigation_unlimited_c1_m",
"sequence_id": [1],
"percentage": 50,
},
)
await exam.finish_test()

await exam.begin_test(3)
await exam.call(
SERVICE_TIME_ADJUST,
{
"entity_id": "binary_sensor.irrigation_unlimited_c1_m",
"sequence_id": [0],
"percentage": 25,
},
)
await exam.finish_test()

exam.check_summary()

0 comments on commit bc72195

Please sign in to comment.