<!-- Source: https://medius.k4tech.net/bindings/c/usage -->
# Calls & errors

_The C-specific shapes: status codes, handle lifecycle, builders_

This page covers what's specific to C: how a failed call surfaces, who frees a handle, and how you build the generic [`inject`](/library/inject.md) / [`move`](/library/move.md) / [`lock`](/library/lock.md) targets. What each call _does_ lives in the [Rust Library](/library.md) and [Native API](/native.md) sections. The full call list is on the [API index](/bindings/c/api.md); structs and enums are on [Types & errors](/bindings/c/types.md).

_Fire-and-forget_ calls return as soon as the [frame](/native/frame.md) is queued; _Blocks_ calls wait for the [box](/native/hardware.md)'s reply. Both return a [`MediusStatus`](/bindings/c/types.md#errors).

## Errors

_MediusStatus + a thread-local last error_

Every fallible call returns a `MediusStatus` and writes its real result through an out-param. `MEDIUS_STATUS_OK` is `0`; anything else is a failure and the out-param is untouched. Fetch the human-readable detail separately. What each code means lives on [Errors](/library/types/errors.md).

```
  call ──▶ MediusStatus
             │
             ├─ == OK ──▶ the out-param is valid, carry on
             └─ != OK ──▶ medius_last_error_message(buf, cap)   text (this thread)
                          medius_last_error_proto_ver()         byte (BadProtoVer only)
```

| MediusStatus | Value | Means |
| --- | --- | --- |
| `MEDIUS_STATUS_OK` | 0 | Success; the out-param is written. |
| `MEDIUS_STATUS_ERR_IO` | 1 | Serial I/O failed on the link. |
| `MEDIUS_STATUS_ERR_NOT_FOUND` | 2 | No box found ([`open`](/bindings/c/api.md#connect) / [`find`](/bindings/c/api.md#connect)). |
| `MEDIUS_STATUS_ERR_NO_REPLY` | 3 | A query got no [RESP](/native/commands/requests.md#resp) frame. |
| `MEDIUS_STATUS_ERR_BAD_PROTO_VER` | 4 | Protocol mismatch at the [handshake](/native/connection.md#handshake); read `medius_last_error_proto_ver()`. |
| `MEDIUS_STATUS_ERR_QUERY_TIMEOUT` | 5 | The RESP wait elapsed. |
| `MEDIUS_STATUS_ERR_DISCONNECTED` | 6 | Link dropped or a stream closed (see below). |
| `MEDIUS_STATUS_ERR_FRAME_TOO_LONG` | 7 | Payload exceeded the wire limit. |
| `MEDIUS_STATUS_ERR_FLASH_TOOL` | 8 | [esptool](https://github.com/espressif/esptool) failed ([flash](/library/features/flash.md) feature). |
| `MEDIUS_STATUS_ERR_INVALID_ARG` | 9 | A bad argument (e.g. a null handle). |
| `MEDIUS_STATUS_ERR_PANIC` | 10 | An internal panic was caught at the boundary. |
| `MEDIUS_STATUS_ERR_UNKNOWN` | 11 | Unspecified, or a platform-gated call on an unsupported OS. |

#### READING THE DETAIL

```text
uintptr_t medius_last_error_message(char *buf, uintptr_t cap); uint8_t medius_last_error_proto_ver(void);
```

```c
MediusDevice *dev = NULL;
if (medius_device_find(&dev) != MEDIUS_STATUS_OK) {
    char buf[256];
    medius_last_error_message(buf, sizeof buf);   /* NUL-terminated, truncated to cap */
    fprintf(stderr, "open failed: %s\n", buf);
    return 1;
}
```

[`medius_last_error_message`](/bindings/c/api.md#module) returns the full message length in bytes (excluding the NUL), so a caller can size a buffer and retry on truncation. [`medius_last_error_proto_ver`](/bindings/c/api.md#module) returns the offending version byte after a `BadProtoVer`, else `0`.

> **Warning**
>
> The last error is **thread-local and overwritten by the next `medius_*` call on that thread**. Read it right after the call that failed, before doing anything else on the same thread.

> **Note**
>
> A device call returns `MEDIUS_STATUS_ERR_DISCONNECTED` once the link drops, and a stream's blocking `recv` returns it when the stream closes (after a reset or link loss). Recover with [`medius_device_reconnect`](/bindings/c/api.md#led-admin-options) or by re-opening (see [Lifecycle](/library/lifecycle.md)).

## Lifecycle

_Opaque pointers, manual free, no RAII or GC_

Handles are opaque pointers you own. There's no destructor or [RAII](https://en.cppreference.com/w/cpp/language/raii): a constructor hands you a pointer, `medius_*_clone` makes another owner of the _same_ underlying object (reference-counted, like `Device::clone` in Rust), and you must call the matching `medius_*_free` on every handle you hold. See [Connection](/library/connection.md) and [Lifecycle](/library/lifecycle.md) for what the link does between open and free.

```
  medius_device_open / _find  ──▶  MediusDevice *    (you own it)
                                        │  medius_device_clone
                                        ▼
                                   MediusDevice *    (2nd owner, same link)

  medius_device_free(handle)  ──▶  drop one owner    (NULL = no-op)
  free the last owner         ──▶  joins the reader + keepalive threads
```

| Handle | Create | Clone | Free |
| --- | --- | --- | --- |
| [`MediusDevice`](/library/connection.md) | `medius_device_open` · `_find` · `_with_mock` | `medius_device_clone` | `medius_device_free` |
| [`MediusEventStream`](/library/catch.md) | `medius_device_catch_events` | `medius_event_stream_clone` | `medius_event_stream_free` |
| [`MediusLogStream`](/library/diagnostics.md) | `medius_device_logs` | `medius_log_stream_clone` | `medius_log_stream_free` |
| [`MediusMockBox`](/library/features/mock.md) | `medius_mock_new` | `medius_mock_clone` | `medius_mock_free` |
| [`MediusClipBuilder`](/library/clip.md#builder) | `medius_clip_builder_new` | \- | `medius_clip_builder_free` |
| [`MediusClip`](/library/clip.md#handle) | `medius_device_clip` | \- | `medius_clip_free` |

```c
MediusDevice *dev = NULL;
if (medius_device_find(&dev) != MEDIUS_STATUS_OK) { return 1; }

MediusDevice *worker = medius_device_clone(dev);  /* same link, ref-counted */
/* ... use either handle from either thread ... */

medius_device_free(worker);   /* drop one owner */
medius_device_free(dev);      /* last owner -> joins the background threads */
```

> **Note**
>
> `clone(NULL)` returns `NULL` and every `*_free(NULL)` is a no-op, so cleanup paths don't need null checks. Freeing a stream unsubscribes when its last handle drops; catch events and log lines are fixed-size structs written into your buffer, so there's nothing to free per event.

## Building targets

_Usage, Motion, LockTarget for the generic verbs_

Rust's generic [`inject`](/library/inject.md) / [`move_axis`](/library/move.md) / [`lock`](/library/lock.md) targets are built structs in C: [`MediusUsage`](/bindings/c/types.md#input), [`MediusMotion`](/bindings/c/types.md#motion), and [`MediusLockTarget`](/bindings/c/types.md#lock-target), each with a helper constructor. A `MediusUsage` holds a [button id](/native/commands/usage.md#buttons), [keycode](/native/commands/usage.md#keycodes), or [Consumer usage](/native/commands/usage.md#consumer), and the same value drives an inject, a lock, or a catch test.

| Builder | Returns | For |
| --- | --- | --- |
| `medius_usage_button(MediusButton)` | `MediusUsage` | [inject](/library/inject.md) / [injection model](/native/injection.md) |
| `medius_usage_key(MediusKey)` | `MediusUsage` | [inject](/library/inject.md) / [injection model](/native/injection.md) |
| `medius_usage_media(MediusMediaKey)` | `MediusUsage` | [inject](/library/inject.md) / [injection model](/native/injection.md) |
| `medius_motion_cursor(dx, dy)` | `MediusMotion` | [move](/library/move.md) / [MOVE](/native/commands/move.md#move) |
| `medius_motion_wheel(delta)` | `MediusMotion` | [move](/library/move.md) / [MOVE](/native/commands/move.md#move) |
| `medius_lock_target_axis(MediusLockTargetKind)` | `MediusLockTarget` | [lock](/library/lock.md) / [LOCK](/native/commands/lock.md) |
| `medius_lock_target_usage(MediusUsage)` | `MediusLockTarget` | [lock](/library/lock.md) / [LOCK](/native/commands/lock.md) |

```c
/* inject: build a usage, then apply an Action */
MediusUsage lmb = medius_usage_button(MEDIUS_BUTTON_LEFT);
medius_device_inject(dev, lmb, MEDIUS_ACTION_PRESS);
medius_device_press(dev, medius_usage_key(MEDIUS_KEY_W));   /* keys and media inject the same way */

/* move: build a motion arm */
MediusMotion m = medius_motion_cursor(100, -50);
medius_device_move_axis(dev, m);

/* lock: an axis, or any usage */
MediusLockTarget x = medius_lock_target_axis(MEDIUS_LOCK_TARGET_KIND_X);
medius_device_lock(dev, x, MEDIUS_LOCK_DIRECTION_BOTH);

MediusLockTarget side = medius_lock_target_usage(medius_usage_button(MEDIUS_BUTTON_SIDE1));
medius_device_lock(dev, side, MEDIUS_LOCK_DIRECTION_BOTH);
```

> **Note**
>
> A button, key, and media usage all lock the same way: `medius_lock_target_usage(medius_usage_key(...))` locks a key, `medius_lock_target_axis(...)` an axis or the wheel. The struct fields are on [Types & errors](/bindings/c/types.md#lock-target).

## Fire-and-forget vs blocking

_The two call-kind badges_

Two return shapes, both yielding a `MediusStatus`. The badge on each row of the [API index](/bindings/c/api.md) says which is which.

| Badge | Behaviour | Fails with |
| --- | --- | --- |
| _Fire-and-forget_ | Returns once the frame is queued for the wire; no reply is awaited. Move, inject, lock, [LED](/library/led.md), [options](/library/options.md), [clip playback](/library/clip.md). See [fire-and-forget](/native/injection.md#fire-and-forget). | `MEDIUS_STATUS_ERR_IO` / `MEDIUS_STATUS_ERR_DISCONNECTED` if the link is down. |
| _Blocks_ | Sends a QUERY and waits for the box's RESP, up to the query timeout. The [`medius_device_query_*`](/bindings/c/api.md#queries) reads and the open/handshake calls. See [Requests](/native/commands/requests.md#requests). | `MEDIUS_STATUS_ERR_NO_REPLY` / `MEDIUS_STATUS_ERR_QUERY_TIMEOUT`. |

> **Note**
>
> A _Fire-and-forget_ call returning `MEDIUS_STATUS_OK` means the frame was handed to the writer, not that the box acted on it; there's no acknowledgement. The default reply wait is [`medius_default_query_timeout_ms()`](/bindings/c/api.md#module); the held-override keepalive cadence is [`medius_default_keepalive_cadence_ms()`](/bindings/c/api.md#module) (see [keepalive](/library/guides/connection.md#keepalive)).
