Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ async def main() -> None:
)
],
label="Execution via Python",
# mode=CommandMode.HIGH_PRIORITY
# mode=ExecutionMode.HIGH_PRIORITY
)
Comment thread
iMicknl marked this conversation as resolved.

while True:
Expand Down
76 changes: 56 additions & 20 deletions docs/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,73 @@ A server describes where the API calls go. A gateway is the physical hub in your

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

## Actions, action groups, and commands
## Commands, actions, and action groups

Commands are sent as `Action` objects, grouped into an action group. Each action targets a device URL and a set of commands with parameters.
The Overkiz API uses a three-level hierarchy to control devices:

- **Command** — A single instruction for a device, like `open`, `close`, or `setClosure(50)`. A command has a name and optional parameters.
- **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.
- **Action group** — A batch of actions submitted to the gateway as a single execution. An action group can target multiple devices at once.

```
ActionGroup
├── Action (device A)
│ ├── Command("open")
│ └── Command("setClosure", [50])
└── Action (device B)
└── Command("close")
```

Action groups come in two flavors:

- **Ad-hoc** — Built on the fly from `Action` and `Command` objects and executed via `execute_action_group()`.
- **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()`.

## Executions

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.

- **Track** running executions with `get_current_executions()` or `get_current_execution(exec_id)`.
- **Cancel** a running execution with `cancel_execution(exec_id)`.
- **Review** past results with `get_execution_history()`.

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.

## Execution modes

An optional `ExecutionMode` can be passed when executing an action group:

- `HIGH_PRIORITY` — Bypasses the normal execution queue.
- `GEOLOCATED` — Triggered by geolocation rules.
- `INTERNAL` — Used for internal/system executions.

## States

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

## Events and listeners

The API uses an event listener that you register once per session. Fetching events drains the server-side buffer.

## Execution model

Commands are executed asynchronously by the platform. You can poll execution state via events or refresh device states after a delay.
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.

## Relationship diagram

```
Client
|
|-- Server (cloud or local)
|
|-- Gateway
|
|-- Setup
| |
| |-- Devices
| |
| |-- States
| |-- Actions -> Commands -> Parameters
|
|-- Event Listener -> Events
├── Server (cloud or local)
└── Gateway
├── Setup
│ │
│ └── Devices
│ │
│ ├── States (name/value pairs)
│ └── Actions ──► Commands ──► Parameters
├── Action Groups (persisted)
├── Executions (running action groups)
└── Event Listener ──► Events
```
115 changes: 96 additions & 19 deletions docs/device-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,61 +142,138 @@ if device.identifier.is_sub_device:
print(f"Sub-device ID: {device.identifier.subsystem_id}")
```

## Send a command
## Send a single command to a device

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.

```python
from pyoverkiz.enums import OverkizCommand
from pyoverkiz.models import Action, Command

await client.execute_action_group(
exec_id = await client.execute_action_group(
actions=[
Action(
device_url="io://1234-5678-1234/12345678",
commands=[
Command(
name=OverkizCommand.SET_CLOSURE,
parameters=[50],
)
Command(name=OverkizCommand.SET_CLOSURE, parameters=[50])
],
)
],
label="Execution: set closure",
],
label="Set closure to 50%",
)
```

## Action groups and common patterns
## Send multiple commands to one device

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

```python
from pyoverkiz.enums import OverkizCommand, OverkizCommandParam
from pyoverkiz.models import Action, Command

await client.execute_action_group(
exec_id = await client.execute_action_group(
actions=[
Action(
device_url="io://1234-5678-1234/12345678",
commands=[
Command(
name=OverkizCommand.SET_DEROGATION,
parameters=[21.5, OverkizCommandParam.FURTHER_NOTICE],
)
],
),
Action(
device_url="io://1234-5678-1234/12345678",
commands=[
),
Command(
name=OverkizCommand.SET_MODE_TEMPERATURE,
parameters=[OverkizCommandParam.MANUAL_MODE, 21.5],
)
),
],
)
],
label="Execution: multiple commands",
label="Set temperature derogation",
)
```

## Control multiple devices at once

An action group can contain one action per device. All actions in the group are submitted as a single execution.

```python
from pyoverkiz.enums import OverkizCommand
from pyoverkiz.models import Action, Command

exec_id = await client.execute_action_group(
actions=[
Action(
device_url="io://1234-5678-1234/11111111",
commands=[Command(name=OverkizCommand.CLOSE)],
),
Action(
device_url="io://1234-5678-1234/22222222",
commands=[Command(name=OverkizCommand.OPEN)],
),
],
label="Close blinds, open garage",
)
```

!!! note
The gateway allows at most **one action per device** in each action group.
If you need to send commands to the same device in separate executions, use
separate `execute_action_group()` calls.

## Execution modes

Pass an `ExecutionMode` to change how the gateway processes the action group:

```python
from pyoverkiz.enums import ExecutionMode, OverkizCommand
from pyoverkiz.models import Action, Command

exec_id = await client.execute_action_group(
actions=[
Action(
device_url="io://1234-5678-1234/12345678",
commands=[Command(name=OverkizCommand.OPEN)],
)
],
mode=ExecutionMode.HIGH_PRIORITY,
)
```

Available modes: `HIGH_PRIORITY`, `GEOLOCATED`, `INTERNAL`. When omitted, the default execution mode is used.

## Track and cancel executions

```python
# List all running executions
executions = await client.get_current_executions()

# Get a specific execution
execution = await client.get_current_execution(exec_id)

# Cancel a running execution
await client.cancel_execution(exec_id)

# Review past executions
history = await client.get_execution_history()
```

## Persisted action groups

Action groups can be stored on the server (like saved scenes). Use these methods to list and execute them:

```python
# List all persisted action groups
action_groups = await client.get_action_groups()

for ag in action_groups:
print(f"{ag.label} (OID: {ag.oid})")

# Execute a persisted action group by OID
exec_id = await client.execute_persisted_action_group(ag.oid)

# Schedule for future execution (Unix timestamp)
trigger_id = await client.schedule_persisted_action_group(ag.oid, timestamp=1735689600)
```
Comment thread
iMicknl marked this conversation as resolved.
Outdated

## Limitations and rate limits

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.
Expand Down
18 changes: 9 additions & 9 deletions pyoverkiz/action_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pyoverkiz.models import Action

if TYPE_CHECKING:
from pyoverkiz.enums import CommandMode
from pyoverkiz.enums import ExecutionMode


@dataclass(frozen=True, slots=True)
Expand Down Expand Up @@ -78,15 +78,15 @@ class ActionQueue:
The batch is flushed when:
- The delay timer expires
- The max actions limit is reached
- The command mode changes
- The execution mode changes
- The label changes
- Manual flush is requested
"""

def __init__(
self,
executor: Callable[
[list[Action], CommandMode | None, str | None], Coroutine[None, None, str]
[list[Action], ExecutionMode | None, str | None], Coroutine[None, None, str]
],
delay: float = 0.5,
max_actions: int = 20,
Expand All @@ -102,7 +102,7 @@ def __init__(
self._max_actions = max_actions

self._pending_actions: list[Action] = []
self._pending_mode: CommandMode | None = None
self._pending_mode: ExecutionMode | None = None
self._pending_label: str | None = None
self._pending_waiters: list[QueuedExecution] = []

Expand All @@ -121,7 +121,7 @@ def _copy_action(action: Action) -> Action:
async def add(
self,
actions: list[Action],
mode: CommandMode | None = None,
mode: ExecutionMode | None = None,
label: str | None = None,
) -> QueuedExecution:
"""Add actions to the queue.
Expand All @@ -132,7 +132,7 @@ async def add(

Args:
actions: Actions to queue.
mode: Command mode, which triggers a flush if it differs from the
mode: Execution mode, which triggers a flush if it differs from the
pending mode.
label: Label for the action group.

Expand All @@ -141,7 +141,7 @@ async def add(
executes.
"""
batches_to_execute: list[
tuple[list[Action], CommandMode | None, str | None, list[QueuedExecution]]
tuple[list[Action], ExecutionMode | None, str | None, list[QueuedExecution]]
] = []

if not actions:
Expand Down Expand Up @@ -235,7 +235,7 @@ async def _delayed_flush(self) -> None:

def _prepare_flush(
self,
) -> tuple[list[Action], CommandMode | None, str | None, list[QueuedExecution]]:
) -> tuple[list[Action], ExecutionMode | None, str | None, list[QueuedExecution]]:
"""Prepare a flush by taking snapshot and clearing state (must be called with lock held).

Returns a tuple of (actions, mode, label, waiters) that should be executed
Expand Down Expand Up @@ -266,7 +266,7 @@ def _prepare_flush(
async def _execute_batch(
self,
actions: list[Action],
mode: CommandMode | None,
mode: ExecutionMode | None,
label: str | None,
waiters: list[QueuedExecution],
) -> None:
Expand Down
Loading
Loading