<!-- Source: https://medius.k4tech.net/bindings/python/quickstart -->
# First program

_Connect, move, click, read one event_

One file that finds [the box](/native/hardware.md), reads its version, moves the cursor, clicks the left button, reads one physical event, then cleans up. Install it with [pip](https://pip.pypa.io) (`pip install medius`, see [Install](/bindings/python.md)). For what each call does, follow the links to the [Rust Library](/library.md) and [Native API](/native.md).

> **Note**
>
> _Fire-and-forget_ calls return the moment they're queued ([the injection model](/native/injection.md#fire-and-forget)); _Blocks_ calls wait for the box's [reply](/native/commands/requests.md). Either kind raises a [`MediusError`](/bindings/python/types.md#errors) on failure.

## The program

_The full listing_

```python
import time
from medius import Device, Usage, Button, CatchMask, NotFoundError

try:
    with Device.find() as dev:                       # open first box + handshake
        v = dev.query_version()                      # blocks for one reply
        print(f"firmware {v.fw_major}.{v.fw_minor}.{v.fw_patch}, proto {v.proto_ver}")

        dev.move_rel(100, 0)                         # nudge cursor 100 right
        dev.press(Usage.button(Button.LEFT))         # hold left down
        time.sleep(0.02)
        dev.soft_release(Usage.button(Button.LEFT))  # let it back up

        with dev.catch_events(CatchMask.ALL) as stream:   # subscribe to physical input
            event = stream.recv_timeout(5000)             # one event, or None after 5 s
            if event is None:
                print("no physical input within 5 s")
            elif event.motion:
                m = event.motion
                print(f"motion  dx={m.dx} dy={m.dy} wheel={m.dz}")
            elif event.usages and event.usages.is_held(Usage.button(Button.LEFT)):
                print("left button held")
            else:
                print(f"event  {event.kind.name}")
        # stream + link are closed here, on block exit
except NotFoundError:
    raise SystemExit("no medius box found: check the control-port cable")
```

> **Note**
>
> The cursor won't move and the click won't land on this machine, and that's by design: injection only reaches the _game_ PC through the clone port, not the control PC running this script. See [Hardware](/native/hardware.md) for the port map.

## Walkthrough

_What each call is and where its meaning lives_

| Call | Kind | Does (follow the link) |
| --- | --- | --- |
| [`Device.find()`](/bindings/python/api.md#connect) | _Blocks_ | Open the first box and run the [handshake](/native/connection.md#handshake). See [Connection](/library/connection.md). |
| [`dev.query_version()`](/bindings/python/api.md#queries) | _Blocks_ | Read a [`Version`](/bindings/python/types.md#version). See [Requests](/library/requests.md). |
| [`dev.move_rel(100, 0)`](/bindings/python/api.md#move) | _Fire-and-forget_ | Relative cursor move. See [MOVE](/native/commands/move.md#move) / [Move](/library/move.md). |
| [`dev.press(Usage.button(Button.LEFT))`](/bindings/python/api.md#inject) | _Fire-and-forget_ | Hold a button. See the [injection model](/native/injection.md#fire-and-forget) / [Inject](/library/inject.md). |
| [`dev.soft_release(Usage.button(Button.LEFT))`](/bindings/python/api.md#inject) | _Fire-and-forget_ | Release, unless the user is physically holding it. See [Injection](/native/injection.md). |
| [`dev.catch_events(CatchMask.ALL)`](/bindings/python/api.md#streams) | _Fire-and-forget_ | Subscribe; returns an [`EventStream`](/bindings/python/streams.md). See [Catch](/library/catch.md). |
| [`stream.recv_timeout(5000)`](/bindings/python/streams.md) | _Blocks_ | Wait up to 5 s for one [`CatchEvent`](/bindings/python/types.md#catchevent), or `None`. More on [Streams](/bindings/python/streams.md). |

[Button](/bindings/python/types.md#button) ids are on [Usage IDs](/native/commands/usage.md#buttons); the full call surface is the [API index](/bindings/python/api.md).

## Run it

_One command, expected output_

```bash
python first.py
# firmware 3.0.0, proto 3
# motion  dx=8 dy=-3 wheel=0
```

The `motion` line appears once you move or click the real mouse within the 5-second window; otherwise you get `no physical input within 5 s`.

## Reading events

_Four ways to pull from a stream_

The program uses `recv_timeout` so it can't hang. The readers differ in how they handle an empty queue and a dropped link:

| Reader | Empty queue | Link dropped |
| --- | --- | --- |
| [`stream.recv()`](/bindings/python/streams.md) | blocks forever | raises [`DisconnectedError`](/bindings/python/types.md#subclasses) |
| [`stream.recv_timeout(ms)`](/bindings/python/streams.md) | returns `None` after `ms` | returns `None` (same as a timeout) |
| [`stream.try_recv()`](/bindings/python/streams.md) | returns `None` at once | returns `None` (same as empty) |
| `for ev in stream:` | blocks for each | loop ends cleanly |

A [`CatchEvent`](/bindings/python/types.md#catchevent) carries one of `.motion` / `.usages` (the other is `None`); a [`UsageSnapshot`](/bindings/python/types.md#usagesnapshot) has `is_held(usage)` for any built [`Usage`](/bindings/python/types.md#input). Full payload shapes on [Streams](/bindings/python/streams.md).

## Errors

_Failures raise; they're never a return code_

Every `Device` and stream call raises on failure; a plain call site is the success path. Each exception is a [`MediusError`](/bindings/python/types.md#errors) subclass carrying `.status`, `.message`, and `.proto_ver`.

| When | Raises |
| --- | --- |
| `find()` with no box present | [`NotFoundError`](/bindings/python/types.md#subclasses) |
| a `query_*` gets no reply in time | [`QueryTimeoutError`](/bindings/python/types.md#subclasses) |
| the box speaks a different protocol | [`BadProtoVerError`](/bindings/python/types.md#subclasses) (check `.proto_ver`) |
| a stream read after the link drops | [`DisconnectedError`](/bindings/python/types.md#subclasses) |
| any other failure | a [`MediusError`](/bindings/python/types.md#errors) subclass |

Catch the base `MediusError` to handle them all at once. Full list and the [`Status`](/bindings/python/types.md#status) codes on [Types & errors](/bindings/python/types.md); patterns on [Calls & errors](/bindings/python/usage.md).

## Closing

_A with-block, close(), or garbage collection_

A `Device` and each stream hold a live connection. Close it three ways, all safe to combine:

| Mechanism | Frees when |
| --- | --- |
| `with Device.find() as dev:` | the [context manager](https://docs.python.org/3/reference/datamodel.html#context-managers) exits (used twice above, for the link and the stream) |
| `dev.close()` | you call it; idempotent, a second call is a no-op |
| [garbage collection](https://docs.python.org/3/glossary.html#term-garbage-collection) | the object is collected, via [`__del__`](https://docs.python.org/3/reference/datamodel.html#object.__del__), if you forgot |

[`dev.clone()`](/bindings/python/api.md#connect) returns another handle to the same link; the connection lives until the last handle is freed. See [Lifecycle](/library/lifecycle.md) and the [keepalive](/library/guides/connection.md#keepalive) thread.

## Next steps

_Further reading_

| Page | For |
| --- | --- |
| [Calls & errors](/bindings/python/usage.md) | how calls, enums, and error handling work in Python |
| [Streams](/bindings/python/streams.md) | consuming catch events and device logs |
| [API index](/bindings/python/api.md) | every call, one line each, linked to what it does |
| [Types & errors](/bindings/python/types.md) | the [dataclasses](https://docs.python.org/3/library/dataclasses.html), [enums](https://docs.python.org/3/library/enum.html), and exception tree |
