Skip to content

Commit 50fc89b

Browse files
authored
Fix RTS duration injection and add comprehensive tests (#2068)
## Summary - **Fixes the RTS duration injection logic** to correctly handle multi-param commands like `tiltPositive` and `moveOf` based on the Overkiz API documentation - **Renames `rts_command_duration` to `default_rts_command_duration`** to make clear it's a default that users can override by passing the duration themselves - **Adds 50 tests** covering all RTS injection scenarios ## Problem The original implementation used `current_count < nparams` which would incorrectly inject duration into domain-specific parameter slots. For example, `tiltPositive(nparams=2)` called with only 1 param would get duration injected as the position value. ## Fix Per the Overkiz API docs, the **last parameter of every RTS command is always the execution duration**. The correct injection rule is `current_count == nparams - 1` — only inject when all domain-specific params are filled and exactly the duration slot remains empty. | Command | nparams | Last param | Injection behavior | |---------|---------|------------|--------------------| | `close`, `open`, `up`, `down`, `stop`, `my`, `rest` | 1 | duration (default 30s) | Injected when called with no args | | `tiltPositive`, `tiltNegative`, `moveOf` | 2 | duration (default 15s) | Injected when called with position arg only | | `identify`, `test` | 0 | — | Never injected | If a user explicitly provides the duration parameter, their value is never overwritten. ## Test plan - [x] All 50 RTS injection tests pass - [x] Full test suite (480 tests) passes - [x] mypy and ty type checking pass - [ ] Verify HA integration works with renamed `default_rts_command_duration` setting
1 parent d1d58be commit 50fc89b

6 files changed

Lines changed: 1010 additions & 152 deletions

File tree

docs/device-control.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,9 @@ trigger_id = await client.schedule_persisted_action_group(
294294

295295
## RTS command duration
296296

297-
RTS devices have a default execution duration of 30 seconds, which blocks consecutive commands until the duration expires. To avoid this, you can configure `rts_command_duration` in `OverkizClientSettings`. The client will automatically inject the configured duration into RTS commands that support it, based on the command definition (`nparams`).
297+
For RTS commands, the **last parameter is always the execution duration** (defaults to 15–30 seconds depending on the command). This blocks consecutive commands until the duration expires.
298+
299+
You can configure `default_rts_command_duration` in `OverkizClientSettings` to override this default. The client injects this value as the duration parameter only when the user has not already provided it explicitly.
298300

299301
```python
300302
from pyoverkiz.auth.credentials import UsernamePasswordCredentials
@@ -305,11 +307,19 @@ from pyoverkiz.enums import Server
305307
client = OverkizClient(
306308
server=Server.SOMFY_EUROPE,
307309
credentials=UsernamePasswordCredentials("user@example.com", "password"),
308-
settings=OverkizClientSettings(rts_command_duration=0),
310+
settings=OverkizClientSettings(default_rts_command_duration=0),
309311
)
310312
```
311313

312-
With `rts_command_duration=0`, the execution duration is set to 0 seconds for supported commands, allowing consecutive commands to be sent without delay. Commands that don't accept a duration parameter (like `identify` or `test`) are left unchanged.
314+
With `default_rts_command_duration=0`, consecutive commands can be sent without delay. If a user passes the duration explicitly (e.g., `Command(name="close", parameters=[5])`), their value takes precedence.
315+
316+
| Command | nparams | Last param | Injection behavior |
317+
|---------|---------|------------|--------------------|
318+
| `close`, `open`, `up`, `down`, `stop`, `my`, `rest` | 1 | duration (default 30s) | Injected when called with no args |
319+
| `tiltPositive`, `tiltNegative`, `moveOf` | 2 | duration (default 15s) | Injected when called with position arg only |
320+
| `identify`, `test` | 0 || Never injected (no parameters) |
321+
322+
The injection rule: duration is injected only when all domain-specific parameters have been provided but the duration slot is still empty (`current_count == nparams - 1`). If the user already provides the duration themselves, the configured default is not applied.
313323

314324
## Limitations and rate limits
315325

docs/migration-v2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ These are not breaking, but worth knowing about when migrating:
406406

407407
- **Client settings** — behavioral configuration is now grouped in `OverkizClientSettings`, passed via the `settings` parameter. This replaces standalone constructor parameters like `action_queue`.
408408
- **Action queue** — batch device executions automatically. See the [action queue guide](action-queue.md).
409-
- **RTS command duration**automatically inject execution duration into RTS commands to prevent the default 30-second blocking behavior. See [RTS command duration](device-control.md#rts-command-duration).
409+
- **RTS command duration**override the default execution duration for RTS commands (15–30s) to prevent blocking consecutive commands. See [RTS command duration](device-control.md#rts-command-duration).
410410
- **Device helpers**`Device.get_command_definition()` for looking up command metadata.
411411
- **Reference endpoints** — query server metadata: `get_reference_ui_classes()`, `get_reference_ui_widgets()`, `get_reference_ui_profile()`, `get_reference_controllable_types()`, etc.
412412
- **Firmware management**`get_devices_not_up_to_date()`, `get_device_firmware_status()`, `update_device_firmware()`.

pyoverkiz/client.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class OverkizClientSettings:
171171
"""
172172

173173
action_queue: ActionQueueSettings | None = None
174-
rts_command_duration: int | None = None
174+
default_rts_command_duration: int | None = None
175175

176176

177177
class OverkizClient:
@@ -513,14 +513,19 @@ async def get_api_version(self) -> str:
513513
return cast(str, response["protocolVersion"])
514514

515515
def _apply_rts_duration(self, actions: list[Action]) -> list[Action]:
516-
"""Set the execution duration for RTS commands that support it.
516+
"""Apply the default execution duration for RTS commands.
517517
518-
The default execution duration for RTS devices is 30 seconds, which
519-
blocks consecutive commands. This injects the configured duration
520-
(typically 0) into commands that accept it, based on the device
521-
command definition (nparams).
518+
For RTS commands, the last parameter is always the execution duration
519+
(default 15s for tilt commands, 30s for movement commands). This
520+
injects ``default_rts_command_duration`` as the last parameter only
521+
when the user has not already provided it — i.e., when the command
522+
has all domain-specific parameters filled but the duration slot is
523+
still empty (current_count == nparams - 1).
524+
525+
If the user explicitly passes the duration parameter themselves, it
526+
is left unchanged.
522527
"""
523-
duration = self.settings.rts_command_duration
528+
duration = self.settings.default_rts_command_duration
524529
if duration is None:
525530
return actions
526531

@@ -539,7 +544,11 @@ def _apply_rts_duration(self, actions: list[Action]) -> list[Action]:
539544
cmd_def = device.get_command_definition(str(cmd.name))
540545
current_count = len(cmd.parameters) if cmd.parameters else 0
541546

542-
if cmd_def and current_count < cmd_def.nparams:
547+
if (
548+
cmd_def
549+
and cmd_def.nparams > 0
550+
and current_count == cmd_def.nparams - 1
551+
):
543552
updated_commands.append(
544553
Command(
545554
name=cmd.name,

tests/test_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,17 +1235,17 @@ class TestOverkizClientSettings:
12351235
def test_client_with_settings_none(self, client: OverkizClient) -> None:
12361236
"""Client without settings has no action queue and no RTS duration."""
12371237
assert client._action_queue is None
1238-
assert client.settings.rts_command_duration is None
1238+
assert client.settings.default_rts_command_duration is None
12391239

12401240
@pytest.mark.asyncio
12411241
async def test_client_with_rts_duration(self) -> None:
12421242
"""Client stores RTS command duration from settings."""
12431243
client = OverkizClient(
12441244
server=Server.SOMFY_EUROPE,
12451245
credentials=UsernamePasswordCredentials("user", "pass"),
1246-
settings=OverkizClientSettings(rts_command_duration=0),
1246+
settings=OverkizClientSettings(default_rts_command_duration=0),
12471247
)
1248-
assert client.settings.rts_command_duration == 0
1248+
assert client.settings.default_rts_command_duration == 0
12491249

12501250
@pytest.mark.asyncio
12511251
async def test_client_with_action_queue_via_settings(self) -> None:

tests/test_client_settings.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ def test_defaults():
88
"""Default settings have no queue and no RTS duration."""
99
settings = OverkizClientSettings()
1010
assert settings.action_queue is None
11-
assert settings.rts_command_duration is None
11+
assert settings.default_rts_command_duration is None
1212

1313

1414
def test_with_rts_duration():
1515
"""RTS command duration can be set."""
16-
settings = OverkizClientSettings(rts_command_duration=0)
17-
assert settings.rts_command_duration == 0
16+
settings = OverkizClientSettings(default_rts_command_duration=0)
17+
assert settings.default_rts_command_duration == 0
1818

1919

2020
def test_with_action_queue_settings():

0 commit comments

Comments
 (0)