# UDP Command API

Exaplay 3 exposes a UDP command interface that carries the same command set as the [TCP Command API](/v3/developer-reference/tcp-api.md). It is aimed at show control systems, touch panels, and embedded controllers that prefer the simplicity of fire-and-forget datagrams over a persistent TCP connection.

## Connection

| Parameter           | Value                                                          |
| ------------------- | -------------------------------------------------------------- |
| **Protocol**        | UDP (datagram)                                                 |
| **Default Port**    | `8200`                                                         |
| **Configuration**   | Config → Network → UDP Listen                                  |
| **Line terminator** | `\r\n` (CRLF) recommended; bare `\r` or `\n` are also accepted |
| **Encoding**        | ASCII / UTF-8                                                  |

Each datagram contains exactly one command. The engine sends the complete response — which may span multiple logical lines — back to the sender's IP and port as a **single reply datagram**. There is no persistent session; each command/response exchange is independent.

> **Note:** Because UDP does not guarantee delivery or ordering, avoid sending commands faster than you can process the responses. For reliable bidirectional communication (status polling, multi-step workflows) the [TCP Command API](/v3/developer-reference/tcp-api.md) is preferable.

### Changing the Listen Address/Port

The **UDP Listen** config field accepts one or more entries (one per line), each in `ADDRESS:PORT` format. Use `*` as the address to listen on all network interfaces:

```
*:8200
```

To listen on a specific interface only:

```
192.168.1.5:8200
```

Leave the field empty to disable UDP control.

## Command Format

```
COMMAND,COMPOSITION_ID[,ARGUMENT]
```

* Fields are separated by commas.
* The first field is always the command name.
* Commands that target a composition require the composition's **variable name** (e.g. `comp_1`) as the second field.
* Retrieve variable names with `get:complist`.
* The response is returned as a single UDP datagram. Multi-line responses (list commands) have each line delimited by `\r\n` within that datagram and are terminated by a final `END\r\n` line.

## Global Commands

These commands do not require a composition ID.

| Command        | Response                             | Description                  |
| -------------- | ------------------------------------ | ---------------------------- |
| `hello`        | `hallo`                              | Connection test / keep-alive |
| `get:ver`      | `Exaplay,<version>`                  | Get engine version string    |
| `get:complist` | One line per composition, then `END` | List all compositions        |
| `get:projname` | `<project name>`                     | Get the loaded project name  |

### `get:complist` response format

```
<varname>,<display name>\r\n
<varname>,<display name>\r\n
...
END\r\n
```

**Example:**

```
→ get:complist
← comp_main,Main Show\r\ncomp_lobby,Lobby Loop\r\nEND\r\n
```

***

## Composition Commands

All composition commands use the format `COMMAND,COMPID[,ARGUMENT]`.

### Transport

| Command                      | Arguments         | Response | Description                |
| ---------------------------- | ----------------- | -------- | -------------------------- |
| `play,COMPID`                | —                 | `OK`     | Start playback             |
| `pause,COMPID`               | —                 | `OK`     | Pause playback             |
| `stop,COMPID`                | —                 | `OK`     | Stop playback              |
| `set:cuetime,COMPID,SECONDS` | `SECONDS` — float | `OK`     | Seek to position (seconds) |

### Audio

| Command                | Arguments               | Response  | Description            |
| ---------------------- | ----------------------- | --------- | ---------------------- |
| `get:vol,COMPID`       | —                       | `<0–100>` | Get volume (integer %) |
| `set:vol,COMPID,VALUE` | `VALUE` — integer 0–100 | `OK`      | Set volume             |

### Display

| Command                  | Arguments               | Response  | Description                  |
| ------------------------ | ----------------------- | --------- | ---------------------------- |
| `get:alpha,COMPID`       | —                       | `<0–100>` | Get master alpha (integer %) |
| `set:alpha,COMPID,VALUE` | `VALUE` — integer 0–100 | `OK`      | Set master alpha             |

### Playback Options

| Command                 | Arguments            | Response   | Description            |
| ----------------------- | -------------------- | ---------- | ---------------------- |
| `get:loop,COMPID`       | —                    | `0` or `1` | Get loop state         |
| `set:loop,COMPID,VALUE` | `VALUE` — `0` or `1` | `OK`       | Enable/disable looping |

### Status and Info

| Command                  | Response format                         | Description                      |
| ------------------------ | --------------------------------------- | -------------------------------- |
| `get:name,COMPID`        | `<name>`                                | Get display name                 |
| `get:type,COMPID`        | `timeline`, `cuelist`, or `composition` | Get composition type             |
| `get:itemcount,COMPID`   | `<count>`                               | Number of media items            |
| `get:status,COMPID`      | `STATUS,TIME,FRAME,CUEINDEX,TOTAL`      | Full playback status (see below) |
| `get:duration,COMPID`    | Depends on type (see below)             | Total duration                   |
| `get:playingitem,COMPID` | Depends on type (see below)             | Currently playing item           |
| `get:cuename,COMPID`     | `<name>`                                | Name of the currently active cue |

**`get:status` response fields:**

| Field      | Values                               | Description                                                    |
| ---------- | ------------------------------------ | -------------------------------------------------------------- |
| `STATUS`   | `0` stopped, `1` playing, `2` paused | Playback state                                                 |
| `TIME`     | float (seconds)                      | Current playback position                                      |
| `FRAME`    | integer                              | Current frame number (`TIME × 60`)                             |
| `CUEINDEX` | integer                              | Active cue index (timeline) or current item position (cuelist) |
| `TOTAL`    | float (seconds)                      | Total duration of current item (cuelist only)                  |

**`get:duration` response:**

* **Timeline:** `DURATION` — total timeline length in seconds (float)
* **CueList:** `COUNT,TOTAL` — number of items and sum of all clip lengths (float seconds)

**`get:playingitem` response:**

* **CueList:** `INDEX,NAME,FILE` — 1-based item index, friendly name, source file path
* **Timeline:** `INDEX,CUENAME,TIME` — cue variable index, cue name, current time (float seconds)

***

## Timeline Commands

These commands are only valid when the composition is a **Timeline**.

| Command                    | Arguments                    | Response                     | Description                         |
| -------------------------- | ---------------------------- | ---------------------------- | ----------------------------------- |
| `set:cue,COMPID,INDEX`     | `INDEX` — cue variable index | `OK`                         | Jump to cue by index and trigger it |
| `get:cuelist,COMPID`       | —                            | One line per cue, then `END` | List all cues                       |
| `get:cueinfo,COMPID,INDEX` | `INDEX` — cue variable index | `INDEX,NAME,OFFSET,ACTIVE`   | Details about a specific cue        |

**`get:cuelist` (Timeline) response format:**

```
<index>,<name>,<time-offset seconds>\r\n
...
END\r\n
```

**`get:cueinfo` (Timeline) response fields:** `INDEX,NAME,OFFSET_SECONDS,IS_ACTIVE`

***

## CueList Commands

These commands are only valid when the composition is a **CueList**.

| Command                    | Arguments                     | Response                      | Description                     |
| -------------------------- | ----------------------------- | ----------------------------- | ------------------------------- |
| `set:cue,COMPID,INDEX`     | `INDEX` — 1-based item number | `OK`                          | Play item at the given position |
| `next,COMPID`              | —                             | `OK`                          | Advance to the next item        |
| `prev,COMPID`              | —                             | `OK`                          | Go back to the previous item    |
| `get:cuelist,COMPID`       | —                             | One line per item, then `END` | List all items                  |
| `get:cueinfo,COMPID,INDEX` | `INDEX` — 1-based item number | `INDEX,NAME,FILE,DURATION`    | Details about a specific item   |

**`get:cuelist` (CueList) response format:**

```
<1-based index>,<name>,<file path>\r\n
...
END\r\n
```

> **Note:** Timeline cue indices match the cue's **variable index** (arbitrary integer). CueList item indices are always **1-based sequential** numbers.

***

## Error Responses

All error responses begin with `ERR,`:

| Error                       | Meaning                                                     |
| --------------------------- | ----------------------------------------------------------- |
| `ERR,unknown_command`       | Command string not recognised                               |
| `ERR,composition_not_found` | No composition with the given variable name exists          |
| `ERR,missing_argument`      | Required argument was not supplied                          |
| `ERR,cue_not_found`         | The cue/item index does not exist                           |
| `ERR,none_playing`          | Requested info about a playing item, but nothing is playing |
| `ERR,no_next_item`          | `next` reached the end of the cue list                      |
| `ERR,already_at_first`      | `prev` called when the first item is already playing        |
| `ERR,cuelist_empty`         | `next`/`prev` called on an empty cue list                   |
| `ERR,unsupported_type`      | Command is not applicable to this composition type          |
| `ERR,no_project`            | No project is currently loaded                              |
| `ERR,exception`             | An internal error occurred                                  |

***

## Example Sessions

### Connection test

```
→ hello
← hallo\r\n
```

### Discover and play a composition

```
→ get:complist
← comp_main,Main Show\r\ncomp_lobby,Lobby Loop\r\nEND\r\n

→ play,comp_main
← OK\r\n

→ get:status,comp_main
← 1,12.3456,740,0,0.0000\r\n
```

### Work with a CueList

```
→ set:cue,comp_lobby,2
← OK\r\n

→ next,comp_lobby
← OK\r\n
```

### Work with a Timeline

```
→ set:cue,comp_main,2
← OK\r\n
```

***

## Sending Commands from Common Environments

### Python (testing / scripting)

```python
import socket

HOST = "192.168.1.5"
PORT = 8200

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(2.0)

def send(cmd):
    sock.sendto((cmd + "\r\n").encode(), (HOST, PORT))
    try:
        data, _ = sock.recvfrom(65536)
        return data.decode().strip()
    except socket.timeout:
        return "(no response)"

print(send("hello"))             # hallo
print(send("get:complist"))      # comp_main,Main Show\r\nEND
print(send("play,comp_main"))    # OK
```

### Node.js

```js
const dgram = require('dgram');
const client = dgram.createSocket('udp4');

const HOST = '192.168.1.5';
const PORT = 8200;

function send(cmd) {
  return new Promise((resolve) => {
    client.once('message', (msg) => resolve(msg.toString().trim()));
    client.send(cmd + '\r\n', PORT, HOST);
  });
}

(async () => {
  console.log(await send('hello'));           // hallo
  console.log(await send('play,comp_main'));  // OK
  client.close();
})();
```

### netcat / socat (quick diagnostics)

```bash
# Send a single command and print the response
echo -ne "hello\r\n" | socat - UDP:192.168.1.5:8200

# Interactive session (socat keeps the socket open)
socat - UDP:192.168.1.5:8200
```

***

## TCP vs UDP — When to Use Which

| Scenario                                            | Recommended |
| --------------------------------------------------- | ----------- |
| Show control system with persistent connection      | **TCP**     |
| Embedded controller / microcontroller with UDP only | **UDP**     |
| Single fire-and-forget trigger commands             | **UDP**     |
| Polling status at high frequency                    | **TCP**     |
| Multi-step workflows requiring ordered responses    | **TCP**     |

***

## Firewall

Ensure UDP port `8200` is open on the Exaplay machine:

```batch
netsh advfirewall firewall add rule name="Exaplay UDP Control" ^
  dir=in action=allow protocol=UDP localport=8200
```

***

## See Also

* [HTTP REST API](https://github.com/vioso/docs/blob/main/exaplay3/developer/rest-api.md) — JSON-based HTTP API (port 8123) with equivalent seeking and transport commands
* [TCP Command API](/v3/developer-reference/tcp-api.md) — persistent connection variant of this API


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.exaplay.one/v3/developer-reference/udp-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
