> For the complete documentation index, see [llms.txt](https://docs.ndi.video/all/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ndi.video/all/developing-with-ndi/metadata-labs/gpio-over-ndi-v1.0.md).

# GPIO over NDI v1.0

> Metadata Specification (v1.0)
>
> Roberto Musso, December 2025

### Purpose

Defines a small XML metadata format to carry GPIO / GPI/O control information over NDI metadata streams, keeping the on-wire payload protocol-neutral.

### 1. Goals

* Interoperability between different NDI senders/receivers.
* Protocol independence (NDI only sees XML metadata).
* Flexibility for endpoints to map logical GPIO commands to their own hardware/protocols (Modbus, contact closures, HTTP, tally, etc.).
* Support for multi-controller scenarios through an explicit `controllerId` concept.

### 2. Design Overview

GPIO commands travel as NDI metadata frames whose root element is:

```xml
<ndi_metadata type="gpio" version="1.0"> ... </ndi_metadata>
```

Inside the root, each metadata frame lists one or more logical **devices**, each containing one or more **channel** commands.

#### High-level model

**`<Device>`**\
Logical GPIO endpoint or group (e.g. a relay bank, tally unit, router GPI interface).

**`<Channel>`**\
One controllable line or function within that device (e.g. `GPO_1`, `TALLY_PREVIEW`, `CONTACT_3`).

Example (single device, two channels):

```xml
<ndi_metadata type="gpio" version="1.0">

<Device id="gpio_device_1" controllerId="panel_A">

<Channel>

<Id>GPO_1</Id>

<Direction>output</Direction>

<Action>set</Action>

<State>on</State>

<Value>1</Value>

<DurationMs>0</DurationMs>

<TimestampUtc>2025-12-03T10:15:30.123Z</TimestampUtc>

</Channel>

<Channel>

<Id>GPO_2</Id>

<Direction>output</Direction>

<Action>set</Action>

<State>off</State>

<Value>0</Value>

<DurationMs>0</DurationMs>

<TimestampUtc>2025-12-03T10:15:30.123Z</TimestampUtc>

</Channel>

</Device>

</ndi_metadata>
```

Receivers/gateways decide how to interpret `Device.id`, `controllerId` and `Channel.Id` and how to map them onto their own hardware or software.

### 3. Root Element

#### 3.1 `<ndi_metadata>`

* **Element name**: `ndi_metadata`
* **Attributes**:
  * `type` (required): MUST be `"gpio"`.
  * `version` (optional): schema version. Current: `"1.0"`.

**Constraints**:

* Exactly one root element per NDI metadata frame.
* No XML prolog (metadata is embedded directly as NDI payload).
* Implementations SHOULD ignore NDI metadata frames whose `type` is not `"gpio"`.

### 4. Device and Channel Structure

#### 4.1 `<Device>`

Represents a logical GPIO-capable endpoint or group of channels.

* **Parent**: `<ndi_metadata>`
* **Attributes**:
  * **`id`** (required): string identifier of the device. SHOULD be stable and unique within the sender’s or facility’s context. Recommended format: lower-case snake\_case `[a-z0-9_]+` (e.g. **`gpio_device_1`**, **`router_gpio_bank_a`**, **`studio_a_panel`**). Used by receivers/gateways to route commands to the correct hardware or logical endpoint.
  * **`controllerId`** (optional): logical identifier of the controller issuing the commands for this device. Free-form UTF-8 string. SHOULD be stable over time for a given controller. SHOULD be unique within the GPIO control domain (facility, production, NDI “universe”). Examples: **`panel_A`**, **`studio_switcher_gpio`**, **`automation_1`**, **`safety_override`**.

Multiple `<Device>` elements MAY appear in one frame to address several logical devices at once.

Example:

```xml
<ndi_metadata type="gpio" version="1.0">

<Device id="modbus_relay_bank_1" controllerId="panel_OBtruck_A">

...

</Device>

<Device id="studio_tally_unit" controllerId="panel_OBtruck_A">

...

</Device>

</ndi_metadata>
```

If `controllerId` is omitted, the command is considered to come from an **anonymous controller**. Implementations MAY accept or reject anonymous commands depending on policy (see Section 6).

#### 4.2 `<Channel>`

Represents a single logical GPIO line or function within a **`<Device>`**.

* **Parent**: `<Device>`
* **Children** (standard elements): `<Id>`, `<Direction>`, `<Action>`, `<State>`, `<Value>`, `<DurationMs>`, `<TimestampUtc>`

Order of child elements is not semantically significant, but the examples in this document use a consistent order for readability.

### 5. Standard Elements

This section defines the standard child elements of **`<Channel>`**.

#### 5.1 `<Id>`

Logical channel identifier.

* **Parent**: `<Channel>`
* **Type**: string
* **Examples**: `GPO_1`, `GPI_3`, `TALLY_PGM`, `CONTACT_4`

This is a logical name. Mapping from `<Id>` to physical pins, coils, or protocol addresses is local to each implementation or gateway.

#### 5.2 `<Direction>`

Direction of the channel relative to the device.

* **Parent**: `<Channel>`
* **Enum values**:
  * **`input`** – The device exposes this as an input (from external world into device).
  * **`output`** – The device drives this outward (device to external world).
  * **`bidirectional`** – The channel can be used in both directions.

Implementations MAY ignore `Direction` if not relevant. However, for safety:

* Commands that attempt to **drive** an input channel SHOULD be ignored or logged as warnings.
* **output** channels are typically where **set**, **pulse**, **toggle** actions make sense.

#### 5.3 `<Action>`

Action requested for this channel.

* **Parent**: `<Channel>`
* **Enum values**:
  * **`set`** – Set the channel to a specific state/value.
  * **`pulse`** – Drive the channel to a state for `DurationMs`, then return to a default state.
  * **`toggle`** – Invert the current state (implementation may need to read current state).
  * **`query`** – Request a state readback (implementation-specific response).

Receivers SHOULD support **`set`**. Other actions MAY be ignored if not supported.

Typical semantics:

* **set + State/Value** → immediate set.
* **pulse + DurationMs + State/Value** → temporary set, then auto-restore.
* **toggle** → read current state, invert, write back.
* **query** → trigger a read and possibly a separate metadata frame with the result.

#### 5.4 `<State>`

Symbolic state.

* **Parent**: `<Channel>`
* **Type**: string
* **Recommended baseline vocabularies**:
  * **Digital GPIO**: on / off, high / low.
  * **Contacts**: open / closed.

Implementations MAY define additional symbolic states as needed but SHOULD maintain on/off compatibility for simple binary lines.

#### 5.5 `<Value>`

Numeric state.

* **Parent**: `<Channel>`
* **Type**: integer (32-bit signed is usually sufficient)
* **Conventions for digital GPIO**:
  * **`0`** = off/low/open
  * **`1`** = on/high/closed

For analog or multi-state lines, Value can encode raw or scaled numeric values. Interpretation is implementation-specific.

State and Value can both be present; for simple digital lines, they SHOULD be consistent (e.g. **`State="on"`** and **`Value=1`**).

#### 5.6 `<DurationMs>`

Pulse duration in milliseconds.

* **Parent**: `<Channel>`
* **Type**: non-negative integer
* **Default**: `0` (no timed behavior; treat as immediate set)

Used mainly when **`Action="pulse"`**:

* **DurationMs > 0**: set to requested state/value, hold for that time, then revert.
* **DurationMs = 0**: receiver MAY ignore or treat as a simple set depending on policy.

#### 5.7 `<TimestampUtc>`

Timestamp of when the command was generated (UTC).

* **Parent**: `<Channel>`
* **Type**: ISO 8601 / RFC 3339 UTC string (Z suffix)
* **Example**: `2025-12-03T10:15:30.123Z`

This is primarily for logging, ordering, and debugging. Receivers MAY ignore it for core behavior but SHOULD keep it when re-emitting or persisting commands/events.

### 6. Controller Identity & Arbitration

#### 6.1 Purpose of `controllerId`

In many NDI workflows, a single GPIO-capable source can have multiple concurrent receivers (multi-viewers, remote panels, automation, etc.). Each receiver could send return GPIO metadata back to the same source, potentially generating conflicting commands on the same Device and Channel.

To address **“who is controlling this device?”**, the schema introduces an optional `controllerId` attribute on **`<Device>`**:

```xml
<Device id="gpio_device_1" controllerId="panel_A">

...

</Device>
```

`controllerId` is:

* An **application-level identity** of the controller (panel, automation, UI), not tied to any low-level NDI connection.
* Intended to be:
  * **Stable** over time for a given controller.
  * **Unique** within a given control domain (e.g. facility).

Examples:

* **`panel_OBtruck_A`**
* **`studio_switcher_gpio`**
* **`automation_Playout1`**
* **`safety_override`**

If `controllerId` is absent, the command is considered “anonymous”. Implementations MAY accept or reject anonymous commands depending on configuration.

#### 6.2 Recommended single-controller-per-device policy

To keep behavior deterministic, implementations are strongly encouraged to ensure that, for a given `Device.id`, at most **one logical controller** is actively driving GPIO at any time.

A simple policy:

1. Maintain an internal mapping:\
   \&#xNAN;**`activeController[Device.id] → controllerId (or anonymous)`**
2. When a command is received for **`Device.id = D`**:
   * If no **`activeController[D]`** is set: Accept the command. If `controllerId` is present, set **`activeController[D] = controllerId`**.
   * If **`activeController[D] == controllerId`** (same controller): Accept the command as normal.
   * If **`activeController[D] != controllerId`** (different controller): By default, **ignore** the command for safety. Optionally log a warning and/or emit diagnostic metadata.
3. Provide a mechanism to **release or reassign** control:
   * Timeout / lease (e.g. if the controller is inactive for N seconds).
   * Explicit reset via configuration or management interface.
   * Manual override by an operator.

For anonymous commands (no `controllerId`):

* In simple environments, implementations MAY accept them and treat them as if they came from a default controller.
* In protected environments, implementations SHOULD allow operators to configure whether anonymous commands are:
  * fully rejected, or
  * allowed only when no other controller is active.

#### 6.3 Interaction with NDI connection count (optional)

The NDI SDK allows a sender to know how many receivers are currently connected (e.g. via `NDIlib_send_get_no_connections`). Implementations MAY combine this information with `controllerId` to enforce additional safety rules, for example:

* Only honor GPIO commands when **`no_connections == 1`** (exactly one receiver).
* If **`no_connections > 1`**, treat the device as being in a **multi-view** state and:
  * ignore all control commands, or
  * accept commands only from a specific `controllerId` configured as the “master”.

This is an **implementation detail**, not part of the XML schema, but fits naturally with the `controllerId` concept.

#### 6.4 Backward compatibility

Existing implementations that do not send or expect `controllerId` remain compatible:

* Receivers SHOULD ignore unknown attributes (as per Section 9).
* Gateways can treat missing `controllerId` as anonymous and:
  * accept commands in simple setups, or
  * require explicit configuration to trust anonymous controllers.

Over time, controllers can adopt `controllerId` without breaking older receivers.

### 7. Vendor/Protocol Hints

The core schema intentionally avoids embedding vendor-specific or protocol-specific details directly into the XML. Instead:

The on-wire metadata remains **neutral and generic** (`Device.id`, `Channel.Id`, **`Action`**, **`State`**, **`Value`**, etc.).

Detailed mapping to physical hardware (GPIO pins, Modbus coils, HTTP endpoints, tally channels, etc.) SHOULD be handled by:

* local configuration files, or
* gateway components, or
* out-of-band management systems.

If necessary, implementations MAY add their own extension elements/attributes in a private namespace, but:

* Such extensions MUST NOT break receivers that ignore them.
* The recommended pattern is to keep core GPIO intent (`set`/`pulse`/`toggle`/`query` and basic state) independent from any particular transport or hardware protocol.

### 8. Mapping to Modbus

This section is non-normative and describes one possible way to map GPIO over NDI to Modbus TCP (or RTU) through a gateway.

#### 8.1 Device mapping

* `Device.id` → logical Modbus endpoint.
* A configuration file (e.g. JSON/YAML) associates `Device.id` values with:
  * `modbus_host`
  * `modbus_port`
  * `unit_id` (slave address)
  * per-channel address mapping

Example config snippet (informative):

```json
{
  "devices": [
    {
      "ndi_device_id": "modbus_relay_bank_1",
      "modbus_host": "192.168.1.50",
      "modbus_port": 502,
      "unit_id": 1,
      "channels": [
        { "id": "GPO_1", "type": "coil", "address": 0 },
        { "id": "GPO_2", "type": "coil", "address": 1 }
      ]
    }
  ]
}
```

#### 8.2 Channel mapping

For simple digital outputs:

* **`<Id>`** → coil address (via configuration).
* **`<Action>`**:
  * **`set`** → Write Single Coil.
  * **`pulse`** → Write coil, wait `DurationMs`, write back default.
  * **`toggle`** → Read coil, invert, write new state.
  * **`query`** → Read coil and optionally emit a new GPIO metadata frame reflecting the state.
* **`<State> / <Value>`**:
  * on or 1 → `0xFF00` (Modbus ON).
  * off or 0 → `0x0000` (Modbus OFF).

Informative example mapping flow:

1. Receive GPIO metadata with:
   * `Device.id = "modbus_relay_bank_1"`
   * `Channel.Id = "GPO_1"`
   * **`Action = "set"`**
   * **`State = "on"`** (and/or `Value = 1`)
2. Look up the Modbus mapping for **`modbus_relay_bank_1`** and **`GPO_1`**.
3. Issue a Modbus Write Single Coil to the configured address with value **`0xFF00`**.

Other receivers might instead map:

* to physical GPIO pins,
* to HTTP APIs,
* to tally or router control protocols,
* etc., without requiring changes to the GPIO over NDI schema.

### 9. Evolution

Senders SHOULD include a **`version`** attribute on **`<ndi_metadata>`** (e.g. `"1.0"`).

Receivers SHOULD:

* gracefully ignore unknown elements and attributes, and
* use sensible defaults for missing optional fields.

Future versions may:

* add optional elements/attributes (e.g. richer timing, batching, or acknowledgement mechanisms)
* refine enums or recommended vocabularies
* extend controller-related behavior without breaking existing behavior.

Implementations SHOULD be written to be **forward-compatible**, especially around:

* new action types
* new direction values
* additional device/channel attributes.

### 10. Summary

The GPIO over NDI metadata format defines:

* A root **`<ndi_metadata type="gpio">`** element.
* A flexible hierarchy of **`<Device>`** and **`<Channel>`** elements.
* Core channel elements: **`<Id>`**, **`<Direction>`**, **`<Action>`**, **`<State>`**, **`<Value>`**, **`<DurationMs>`**, **`<TimestampUtc>`**.
* An optional `controllerId` attribute on **`<Device>`** to support clear, application-level controller identity and simple arbitration.

On top of this neutral, NDI-friendly schema, endpoints and gateways are free to:

* Map logical GPIO commands to Modbus, physical GPIO pins, HTTP, tally, router control, or other protocols.
* Implement policies such as single-controller-per-device, anonymous command handling, and multi-receiver safety.
* Evolve their internal behavior without changing the on-wire XML contract.

This approach keeps NDI metadata clean and protocol-agnostic, while giving implementers the tools they need to build robust, interoperable GPIO control workflows.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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.ndi.video/all/developing-with-ndi/metadata-labs/gpio-over-ndi-v1.0.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.
