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
controllerIdconcept.
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>
<ndi_metadata>Element name:
ndi_metadataAttributes:
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
typeis not"gpio".
4. Device and Channel Structure
4.1 <Device>
<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>
<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>
<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>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>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 forDurationMs, 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>
<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>
<Value>Numeric state.
Parent:
<Channel>Type: integer (32-bit signed is usually sufficient)
Conventions for digital GPIO:
0= off/low/open1= 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>
<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>
<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
controllerIdIn 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_Astudio_switcher_gpioautomation_Playout1safety_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:
Maintain an internal mapping:
activeController[Device.id] β controllerId (or anonymous)When a command is received for
Device.id = D:If no
activeController[D]is set: Accept the command. IfcontrollerIdis present, setactiveController[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.
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
controllerIdconfigured 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
controllerIdas 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/queryand 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.idvalues with:modbus_hostmodbus_portunit_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, waitDurationMs, 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:
Receive GPIO metadata with:
Device.id = "modbus_relay_bank_1"Channel.Id = "GPO_1"Action = "set"State = "on"(and/orValue = 1)
Look up the Modbus mapping for
modbus_relay_bank_1andGPO_1.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
controllerIdattribute 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?

