Skip to content

Commit c70de93

Browse files
committed
Support program budget in update_program service
Adds an optional `budget` field (0-200%) to the update_program service, which scales zone run times. Addresses #345.
1 parent 1bd2cda commit c70de93

6 files changed

Lines changed: 58 additions & 7 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ This integration provides the following services:
185185
| `bhyve.set_manual_preset_runtime` | `entity_id` - zone(s) entity to set the preset runtime. This should be a reference to a zone switch entity <br/> `minutes` - number of minutes to water for | Set the default time a switch is activated for when enabled. Support for this service appears to be patchy, and it has been difficult to identify the devices or under which conditions it works |
186186
| `bhyve.set_smart_watering_soil_moisture` | `entity_id` - zone(s) entity to set the moisture level for. This should be a reference to a zone switch entity <br/> `percentage` - soil moisture level between 0 - 100 | Set Smart Watering soil moisture level for a zone |
187187
| `bhyve.start_program` | `entity_id` - program entity to start. This should be a reference to a program switch entity | Starts a pre-configured watering program. Watering programs cannot be created via this integration and must first be set up in the B-Hyve app |
188-
| `bhyve.update_program` | `entity_id` - program switch to update <br/> `start_times` - _(optional)_ list of watering start times in `HH:MM` format <br/> `frequency` - _(optional)_ frequency configuration object (must include a `type`, e.g. `days`, `interval`, `even`, `odd`) | Update the `start_times` and/or `frequency` of an existing non-smart program. At least one of `start_times` or `frequency` must be provided |
188+
| `bhyve.update_program` | `entity_id` - program switch to update <br/> `start_times` - _(optional)_ list of watering start times in `HH:MM` format <br/> `frequency` - _(optional)_ frequency configuration object (must include a `type`, e.g. `days`, `interval`, `even`, `odd`) <br/> `budget` - _(optional)_ watering budget as a percentage (0-200) | Update the configuration of an existing non-smart program. At least one of `start_times`, `frequency` or `budget` must be provided |
189189

190190
### `bhyve.update_program` example
191191

@@ -201,6 +201,7 @@ data:
201201
days: [1, 3, 5]
202202
interval: 1
203203
interval_hours: 0
204+
budget: 75
204205
```
205206
206207
The `frequency` object mirrors the B-Hyve API structure. Common `type` values:
@@ -209,6 +210,8 @@ The `frequency` object mirrors the B-Hyve API structure. Common `type` values:
209210
- `interval` with `interval: N` to water every N days
210211
- `even` / `odd` to water on even or odd calendar days
211212

213+
The `budget` is a percentage that scales each zone's run time. `100` means unchanged, `50` halves every run time, `200` doubles it. Valid range is 0&ndash;200.
214+
212215
## Python Script
213216

214217
> [!CAUTION]

custom_components/bhyve/services.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ start_program:
6060
example: "valve.backyard_zone"
6161

6262
update_program:
63-
description: Update a program's configuration. Provide at least one of start_times or frequency
63+
description: Update a program's configuration. Provide at least one of start_times, frequency or budget
6464
fields:
6565
entity_id:
6666
description: Program switch
@@ -71,3 +71,6 @@ update_program:
7171
frequency:
7272
description: Frequency configuration. `type` is required (e.g. days, interval, even, odd)
7373
example: '{"type": "days", "days": [1, 3, 5], "interval": 1, "interval_hours": 0}'
74+
budget:
75+
description: Watering budget as a percentage (0-200). Scales run times up or down
76+
example: 50

custom_components/bhyve/strings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
},
137137
"update_program": {
138138
"name": "Update program",
139-
"description": "Update a program's configuration. Provide at least one of start_times or frequency",
139+
"description": "Update a program's configuration. Provide at least one of start_times, frequency or budget",
140140
"fields": {
141141
"entity_id": {
142142
"name": "Program switch",
@@ -149,6 +149,10 @@
149149
"frequency": {
150150
"name": "Frequency",
151151
"description": "Frequency configuration object. Must include a 'type' key (e.g. days, interval, even, odd)"
152+
},
153+
"budget": {
154+
"name": "Budget",
155+
"description": "Watering budget as a percentage (0-200). Scales run times up or down"
152156
}
153157
}
154158
}

custom_components/bhyve/switch.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@
8787
SERVICE_UPDATE_PROGRAM = "update_program"
8888
ATTR_START_TIMES = "start_times"
8989
ATTR_FREQUENCY = "frequency"
90+
ATTR_BUDGET = "budget"
91+
92+
BUDGET_MIN = 0
93+
BUDGET_MAX = 200
9094

9195
_TIME_RE = re.compile(r"^([01]\d|2[0-3]):[0-5]\d$")
9296

@@ -116,6 +120,9 @@ def _validate_time_string(value: Any) -> str:
116120
cv.ensure_list, [_validate_time_string]
117121
),
118122
vol.Optional(ATTR_FREQUENCY): FREQUENCY_SCHEMA,
123+
vol.Optional(ATTR_BUDGET): vol.All(
124+
vol.Coerce(int), vol.Range(min=BUDGET_MIN, max=BUDGET_MAX)
125+
),
119126
}
120127
)
121128

@@ -330,14 +337,15 @@ async def async_update_program_config(
330337
self,
331338
start_times: list[str] | None = None,
332339
frequency: dict | None = None,
340+
budget: int | None = None,
333341
) -> None:
334-
"""Update start times and/or frequency on a non-smart program."""
342+
"""Update configurable fields on a non-smart program."""
335343
if self.program_data.get("is_smart_program"):
336344
msg = "Cannot update configuration of a smart program"
337345
raise ServiceValidationError(msg)
338346

339-
if start_times is None and frequency is None:
340-
msg = "At least one of start_times or frequency must be provided"
347+
if start_times is None and frequency is None and budget is None:
348+
msg = "At least one of start_times, frequency or budget must be provided"
341349
raise ServiceValidationError(msg)
342350

343351
program = BHyveTimerProgram(
@@ -350,6 +358,9 @@ async def async_update_program_config(
350358
if frequency is not None:
351359
program["frequency"] = frequency
352360
changed.append("frequency")
361+
if budget is not None:
362+
program["budget"] = budget
363+
changed.append("budget")
353364

354365
_LOGGER.info(
355366
"Updating program %s, changed fields: %s", self._program_id, changed

custom_components/bhyve/translations/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
},
137137
"update_program": {
138138
"name": "Update program",
139-
"description": "Update a program's configuration. Provide at least one of start_times or frequency",
139+
"description": "Update a program's configuration. Provide at least one of start_times, frequency or budget",
140140
"fields": {
141141
"entity_id": {
142142
"name": "Program switch",
@@ -149,6 +149,10 @@
149149
"frequency": {
150150
"name": "Frequency",
151151
"description": "Frequency configuration object. Must include a 'type' key (e.g. days, interval, even, odd)"
152+
},
153+
"budget": {
154+
"name": "Budget",
155+
"description": "Watering budget as a percentage (0-200). Scales run times up or down"
152156
}
153157
}
154158
}

tests/test_switch.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,32 @@ async def test_update_program_config_both_fields(
604604
assert payload["id"] == TEST_PROGRAM_ID
605605
assert payload["device_id"] == TEST_DEVICE_ID
606606

607+
async def test_update_program_config_budget_only(
608+
self,
609+
mock_sprinkler_device: BHyveDevice,
610+
mock_timer_program: BHyveTimerProgram,
611+
mock_coordinator: MagicMock,
612+
) -> None:
613+
"""Test updating only the budget overrides that field only."""
614+
description = create_program_switch_description()
615+
switch = BHyveProgramSwitch(
616+
coordinator=mock_coordinator,
617+
device=mock_sprinkler_device,
618+
program=mock_timer_program,
619+
description=description,
620+
)
621+
622+
await switch.async_update_program_config(budget=150)
623+
624+
mock_coordinator.client.update_program.assert_called_once()
625+
program_id, payload = mock_coordinator.client.update_program.call_args[0]
626+
assert program_id == TEST_PROGRAM_ID
627+
assert payload["budget"] == 150
628+
# Unchanged fields are preserved
629+
assert payload["start_times"] == mock_timer_program["start_times"]
630+
assert payload["frequency"] == mock_timer_program["frequency"]
631+
assert payload["enabled"] is True
632+
607633
async def test_update_program_config_requires_at_least_one_field(
608634
self,
609635
mock_sprinkler_device: BHyveDevice,

0 commit comments

Comments
 (0)