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:

<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):

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:

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>:

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.

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: 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):

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.

Last updated

Was this helpful?