Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
```
119 changes: 100 additions & 19 deletions docs/device-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,58 +142,139 @@ 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
import time

# 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 (e.g. 1 hour from now)
trigger_id = await client.schedule_persisted_action_group(
ag.oid, timestamp=int(time.time()) + 3600
)
```

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