Skip to content

Commit a6a8f50

Browse files
committed
Refactor documentation for action groups and executions; clarify command execution details and improve method docstrings
1 parent b732de4 commit a6a8f50

File tree

3 files changed

+175
-67
lines changed

3 files changed

+175
-67
lines changed

docs/core-concepts.md

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,73 @@ A server describes where the API calls go. A gateway is the physical hub in your
88

99
The setup describes the current gateway configuration and device inventory. Devices expose metadata like `uiClass` and `widget`, plus a list of current `states`.
1010

11-
## Actions, action groups, and commands
11+
## Commands, actions, and action groups
1212

13-
Commands are sent as `Action` objects, grouped into an action group. Each action targets a device URL and a set of commands with parameters.
13+
The Overkiz API uses a three-level hierarchy to control devices:
14+
15+
- **Command** — A single instruction for a device, like `open`, `close`, or `setClosure(50)`. A command has a name and optional parameters.
16+
- **Action** — One or more commands targeting a **single device** (identified by its device URL). The gateway allows at most one action per device in each action group.
17+
- **Action group** — A batch of actions submitted to the gateway as a single execution. An action group can target multiple devices at once.
18+
19+
```
20+
ActionGroup
21+
├── Action (device A)
22+
│ ├── Command("open")
23+
│ └── Command("setClosure", [50])
24+
└── Action (device B)
25+
└── Command("close")
26+
```
27+
28+
Action groups come in two flavors:
29+
30+
- **Ad-hoc** — Built on the fly from `Action` and `Command` objects and executed via `execute_action_group()`.
31+
- **Persisted** — Stored on the server (like saved scenes). Retrieved with `get_action_groups()` and executed by OID via `execute_persisted_action_group()`, or scheduled for a future timestamp via `schedule_persisted_action_group()`.
32+
33+
## Executions
34+
35+
When an action group is submitted, the server returns an `exec_id` identifying the **execution**. An execution tracks the lifecycle of a submitted action group — from queued, to running, to completed or failed.
36+
37+
- **Track** running executions with `get_current_executions()` or `get_current_execution(exec_id)`.
38+
- **Cancel** a running execution with `cancel_execution(exec_id)`.
39+
- **Review** past results with `get_execution_history()`.
40+
41+
Executions run asynchronously on the gateway. State changes are delivered through events, so you typically combine execution with an event listener to know when commands finish.
42+
43+
## Execution modes
44+
45+
An optional `ExecutionMode` can be passed when executing an action group:
46+
47+
- `HIGH_PRIORITY` — Bypasses the normal execution queue.
48+
- `GEOLOCATED` — Triggered by geolocation rules.
49+
- `INTERNAL` — Used for internal/system executions.
1450

1551
## States
1652

1753
States are name/value pairs that represent the current device status, such as closure position or temperature.
1854

1955
## Events and listeners
2056

21-
The API uses an event listener that you register once per session. Fetching events drains the server-side buffer.
22-
23-
## Execution model
24-
25-
Commands are executed asynchronously by the platform. You can poll execution state via events or refresh device states after a delay.
57+
The API uses an event listener that you register once per session. Fetching events drains the server-side buffer. Events include execution state changes, device state updates, and other notifications.
2658

2759
## Relationship diagram
2860

2961
```
3062
Client
31-
|
32-
|-- Server (cloud or local)
33-
|
34-
|-- Gateway
35-
|
36-
|-- Setup
37-
| |
38-
| |-- Devices
39-
| |
40-
| |-- States
41-
| |-- Actions -> Commands -> Parameters
42-
|
43-
|-- Event Listener -> Events
63+
64+
├── Server (cloud or local)
65+
66+
└── Gateway
67+
68+
├── Setup
69+
│ │
70+
│ └── Devices
71+
│ │
72+
│ ├── States (name/value pairs)
73+
│ └── Actions ──► Commands ──► Parameters
74+
75+
├── Action Groups (persisted)
76+
77+
├── Executions (running action groups)
78+
79+
└── Event Listener ──► Events
4480
```

docs/device-control.md

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -142,61 +142,138 @@ if device.identifier.is_sub_device:
142142
print(f"Sub-device ID: {device.identifier.subsystem_id}")
143143
```
144144

145-
## Send a command
145+
## Send a single command to a device
146+
147+
Create an `Action` with the target device URL and one or more `Command` objects, then wrap it in an action group. The method returns an `exec_id` you can use to track or cancel the execution.
146148

147149
```python
148150
from pyoverkiz.enums import OverkizCommand
149151
from pyoverkiz.models import Action, Command
150152

151-
await client.execute_action_group(
153+
exec_id = await client.execute_action_group(
152154
actions=[
153155
Action(
154156
device_url="io://1234-5678-1234/12345678",
155157
commands=[
156-
Command(
157-
name=OverkizCommand.SET_CLOSURE,
158-
parameters=[50],
159-
)
158+
Command(name=OverkizCommand.SET_CLOSURE, parameters=[50])
160159
],
161160
)
162-
],
163-
label="Execution: set closure",
161+
],
162+
label="Set closure to 50%",
164163
)
165164
```
166165

167-
## Action groups and common patterns
166+
## Send multiple commands to one device
168167

169-
- Use a single action group to batch multiple device commands.
168+
A single action can hold multiple commands. They are executed in order on the device.
170169

171170
```python
172171
from pyoverkiz.enums import OverkizCommand, OverkizCommandParam
173172
from pyoverkiz.models import Action, Command
174173

175-
await client.execute_action_group(
174+
exec_id = await client.execute_action_group(
176175
actions=[
177176
Action(
178177
device_url="io://1234-5678-1234/12345678",
179178
commands=[
180179
Command(
181180
name=OverkizCommand.SET_DEROGATION,
182181
parameters=[21.5, OverkizCommandParam.FURTHER_NOTICE],
183-
)
184-
],
185-
),
186-
Action(
187-
device_url="io://1234-5678-1234/12345678",
188-
commands=[
182+
),
189183
Command(
190184
name=OverkizCommand.SET_MODE_TEMPERATURE,
191185
parameters=[OverkizCommandParam.MANUAL_MODE, 21.5],
192-
)
186+
),
193187
],
194188
)
195189
],
196-
label="Execution: multiple commands",
190+
label="Set temperature derogation",
197191
)
198192
```
199193

194+
## Control multiple devices at once
195+
196+
An action group can contain one action per device. All actions in the group are submitted as a single execution.
197+
198+
```python
199+
from pyoverkiz.enums import OverkizCommand
200+
from pyoverkiz.models import Action, Command
201+
202+
exec_id = await client.execute_action_group(
203+
actions=[
204+
Action(
205+
device_url="io://1234-5678-1234/11111111",
206+
commands=[Command(name=OverkizCommand.CLOSE)],
207+
),
208+
Action(
209+
device_url="io://1234-5678-1234/22222222",
210+
commands=[Command(name=OverkizCommand.OPEN)],
211+
),
212+
],
213+
label="Close blinds, open garage",
214+
)
215+
```
216+
217+
!!! note
218+
The gateway allows at most **one action per device** in each action group.
219+
If you need to send commands to the same device in separate executions, use
220+
separate `execute_action_group()` calls.
221+
222+
## Execution modes
223+
224+
Pass an `ExecutionMode` to change how the gateway processes the action group:
225+
226+
```python
227+
from pyoverkiz.enums import ExecutionMode, OverkizCommand
228+
from pyoverkiz.models import Action, Command
229+
230+
exec_id = await client.execute_action_group(
231+
actions=[
232+
Action(
233+
device_url="io://1234-5678-1234/12345678",
234+
commands=[Command(name=OverkizCommand.OPEN)],
235+
)
236+
],
237+
mode=ExecutionMode.HIGH_PRIORITY,
238+
)
239+
```
240+
241+
Available modes: `HIGH_PRIORITY`, `GEOLOCATED`, `INTERNAL`. When omitted, the default execution mode is used.
242+
243+
## Track and cancel executions
244+
245+
```python
246+
# List all running executions
247+
executions = await client.get_current_executions()
248+
249+
# Get a specific execution
250+
execution = await client.get_current_execution(exec_id)
251+
252+
# Cancel a running execution
253+
await client.cancel_execution(exec_id)
254+
255+
# Review past executions
256+
history = await client.get_execution_history()
257+
```
258+
259+
## Persisted action groups
260+
261+
Action groups can be stored on the server (like saved scenes). Use these methods to list and execute them:
262+
263+
```python
264+
# List all persisted action groups
265+
action_groups = await client.get_action_groups()
266+
267+
for ag in action_groups:
268+
print(f"{ag.label} (OID: {ag.oid})")
269+
270+
# Execute a persisted action group by OID
271+
exec_id = await client.execute_persisted_action_group(ag.oid)
272+
273+
# Schedule for future execution (Unix timestamp)
274+
trigger_id = await client.schedule_persisted_action_group(ag.oid, timestamp=1735689600)
275+
```
276+
200277
## Limitations and rate limits
201278

202279
Gateways impose limits on how many executions can run or be queued simultaneously. If the execution queue is full, the API will raise an `ExecutionQueueFullError`. Most gateways allow up to 10 concurrent executions.

pyoverkiz/client.py

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ async def get_gateways(self, refresh: bool = False) -> list[Gateway]:
373373

374374
@retry_on_auth_error
375375
async def get_execution_history(self) -> list[HistoryExecution]:
376-
"""List execution history."""
376+
"""List past executions and their outcomes."""
377377
response = await self._get("history/executions")
378378
return [HistoryExecution(**h) for h in decamelize(response)]
379379

@@ -449,13 +449,13 @@ async def unregister_event_listener(self) -> None:
449449

450450
@retry_on_auth_error
451451
async def get_current_execution(self, exec_id: str) -> Execution:
452-
"""Get an action group execution currently running."""
452+
"""Get a currently running execution by its exec_id."""
453453
response = await self._get(f"exec/current/{exec_id}")
454454
return Execution(**decamelize(response))
455455

456456
@retry_on_auth_error
457457
async def get_current_executions(self) -> list[Execution]:
458-
"""Get all action groups executions currently running."""
458+
"""Get all currently running executions."""
459459
response = await self._get("exec/current")
460460
return [Execution(**e) for e in decamelize(response)]
461461

@@ -492,34 +492,29 @@ async def execute_action_group(
492492
mode: ExecutionMode | None = None,
493493
label: str | None = "python-overkiz-api",
494494
) -> str:
495-
"""Execute a non-persistent action group.
495+
"""Execute an ad-hoc action group built from the given actions.
496496
497-
When action queue is enabled, actions will be batched with other actions
498-
executed within the configured delay window. The method will wait for the
499-
batch to execute and return the exec_id.
497+
An action group is a batch of device actions submitted as a single
498+
execution. Each ``Action`` targets one device and contains one or more
499+
``Command`` instances (e.g. ``open``, ``setClosure(50)``). The gateway
500+
allows at most one action per device per action group.
500501
501-
Gateways only allow a single action per device in each action group. The
502-
action queue enforces this by merging commands for the same device into
503-
a single action in the batch.
502+
When the action queue is enabled, actions are held for a short delay
503+
and merged with other actions submitted in the same window. Commands
504+
targeting the same device are combined into a single action. The method
505+
blocks until the batch executes and returns the resulting exec_id.
504506
505-
When action queue is disabled, executes immediately and returns exec_id.
506-
507-
The API is consistent regardless of queue configuration - always returns
508-
exec_id string directly.
507+
When the action queue is disabled, the action group is sent immediately.
509508
510509
Args:
511-
actions: List of actions to execute.
512-
mode: Execution mode (`GEOLOCATED`, `INTERNAL`, `HIGH_PRIORITY`,
513-
or `None`).
514-
label: Label for the action group.
510+
actions: One or more actions to execute. Each action targets a
511+
single device and holds one or more commands.
512+
mode: Optional execution mode (``HIGH_PRIORITY``, ``GEOLOCATED``,
513+
or ``INTERNAL``).
514+
label: Human-readable label for the execution.
515515
516516
Returns:
517-
The `exec_id` string from the executed action group.
518-
519-
Example:
520-
```python
521-
exec_id = await client.execute_action_group([action])
522-
```
517+
The ``exec_id`` identifying the execution on the server.
523518
"""
524519
if self._action_queue:
525520
queued = await self._action_queue.add(actions, mode, label)
@@ -548,12 +543,12 @@ def get_pending_actions_count(self) -> int:
548543

549544
@retry_on_auth_error
550545
async def cancel_execution(self, exec_id: str) -> None:
551-
"""Cancel a running setup-level execution."""
546+
"""Cancel a running execution by its exec_id."""
552547
await self._delete(f"exec/current/setup/{exec_id}")
553548

554549
@retry_on_auth_error
555550
async def get_action_groups(self) -> list[ActionGroup]:
556-
"""List the persisted action groups."""
551+
"""List action groups persisted on the server."""
557552
response = await self._get("actionGroups")
558553
return [ActionGroup(**action_group) for action_group in decamelize(response)]
559554

@@ -574,13 +569,13 @@ async def get_places(self) -> Place:
574569

575570
@retry_on_auth_error
576571
async def execute_persisted_action_group(self, oid: str) -> str:
577-
"""Execute a persisted action group by its OID."""
572+
"""Execute a server-side action group by its OID (see ``get_action_groups``)."""
578573
response = await self._post(f"exec/{oid}")
579574
return cast(str, response["execId"])
580575

581576
@retry_on_auth_error
582577
async def schedule_persisted_action_group(self, oid: str, timestamp: int) -> str:
583-
"""Schedule a persisted action group for future execution."""
578+
"""Schedule a server-side action group for execution at the given timestamp."""
584579
response = await self._post(f"exec/schedule/{oid}/{timestamp}")
585580
return cast(str, response["triggerId"])
586581

0 commit comments

Comments
 (0)