Module reference · Uplink

Input Provider

The uplink's first stage. Reads one device's operator signal on every fixed tick and publishes it as a typed contract — nothing more.

Layer Unite.Kernel
Direction Uplink · operator → robot
Base type InputProvider<TOutputContract>
Selection Required — exactly one per study
Demo implementation KeyboardArrowProvider
Paper §5.1

What

An Input Provider is a TeleroboticsModule that captures one device's operator signal each fixed tick and publishes it as a typed snapshot. It is the entry point of the uplink — the first thing that happens on the path from operator to robot.

Concretely, it is InputProvider<TOutputContract>, an abstract class in Unite.Kernel (Assets/UNITE/Kernel/Uplink/InputProvider.cs). A study implements exactly one input device by subclassing it for a study-defined TOutputContract and filling in a single method.

Why

UNITE's scoping review found operator input to be one of the recurring elements of the apparatus, and that device choice and control mapping are instantiated and reported inconsistently across studies — one of the sources of variation that makes a setup hard to reconstruct or compare. Giving input capture its own module makes that choice explicit and swappable: a study can move from keyboard to gamepad, joystick, or a custom rig without touching command mapping, delay, or vehicle logic.

Splitting capture from meaning also keeps the boundary between what the operator did and what it means for the robot enforceable rather than conventional. The Kernel owns when a provider is asked to read the device — once per fixed tick, at a Kernel-provided timestamp — so every provider in every study is sampled the same way; only what gets read is left to the concrete implementation.

How

On each fixed tick, the provider calls TryCaptureInput with the current timestamp:

C#
protected abstract bool TryCaptureInput(
    double timestampSeconds,
    out TOutputContract output);

Return false to skip publishing this tick — a disconnected device, for instance. Return true with a contract to publish it. Before publishing, the Kernel checks that:

  • the returned contract is not null, and
  • the contract's TimestampSeconds equals the timestamp the Kernel handed in — so pass it straight through to the contract's constructor unchanged.

Either check failing logs an error and drops the tick rather than publish a malformed contract. Return true with a valid contract and that's the entire job done — the next stage, Command Mapping & Encoding, is already subscribed and picks it up automatically.

Design the contract first — it determines exactly what TryCaptureInput has to capture, so deciding its shape before writing the method is what keeps that method from turning into a grab-bag of "might be useful" fields. Contract covers what a contract must look like; Examples shows how the shipped keyboard provider fills it in.

Boundary

Owns

  • Reading whatever device — or devices — it's built for, every tick it's asked to.
  • Constructing its declared contract with the Kernel-given timestamp.
  • Deciding whether there's a new input worth publishing this tick.

Does not own

  • Interpreting what the input means to the robot — Command Mapping & Encoding's job.
  • Constraining or filtering the resulting command — Operator-side Assistance (uplink)'s job.
  • Transmitting anything over a communication channel — Uplink Communication's job.
  • Controlling the vehicle.

Contract

Every provider publishes a subclass of InputProviderOutputContract (Unite.Core). It's nine lines — one property, one validated constructor:

C#
public abstract class InputProviderOutputContract
{
    public double TimestampSeconds { get; }

    protected InputProviderOutputContract(double timestampSeconds)
    {
        if (double.IsNaN(timestampSeconds) ||
            double.IsInfinity(timestampSeconds) ||
            timestampSeconds < 0d)
        {
            throw new ArgumentOutOfRangeException(
                nameof(timestampSeconds),
                timestampSeconds,
                "The capture timestamp must be a finite, non-negative value.");
        }

        TimestampSeconds = timestampSeconds;
    }
}

A study-specific subclass adds whatever domain fields the device needs, and must forward the exact timestamp it received in TryCaptureInput to the base constructor unchanged. The examples below show how the shipped keyboard provider fills that contract.

Declaring the exact type. This is why TOutputContract matters beyond your own class: downstream, Command Mapping & Encoding declares the same contract type and the Kernel resolves your provider against it at startup. Pairing a provider with a downstream module that declares a different contract type fails fast with a clear log message — you never have to debug it by tracing runtime behaviour. That resolution logic lives in the Kernel; you don't write or see it, only its two possible outcomes.

Examples

The tabs show keyboard and analog joystick input providers.

A keyboard reports four discrete key states, so its contract is a set of booleans:

C#
public sealed class ArrowInputPackage : InputProviderOutputContract
{
    public bool Up { get; }
    public bool Down { get; }
    public bool Left { get; }
    public bool Right { get; }
    public float DeltaTime { get; }

    public ArrowInputPackage(double timestamp, float dt, bool up, bool down,
        bool left, bool right)
        : base(timestamp)
    {
        DeltaTime = dt; Up = up; Down = down; Left = left; Right = right;
    }
}

With the contract decided, the provider does exactly three things: read the device, construct ArrowInputPackage, and return true:

C#
public sealed class KeyboardArrowProvider
    : InputProvider<ArrowInputPackage>
{
    [SerializeField] private int declaredPollRateHz = 50;

    protected override bool TryCaptureInput(
        double timestamp, out ArrowInputPackage output)
    {
        Keyboard keyboard = Keyboard.current;
        if (keyboard == null)
        {
            output = null;
            return false;
        }

        bool u = keyboard.upArrowKey.isPressed;
        bool d = keyboard.downArrowKey.isPressed;
        bool l = keyboard.leftArrowKey.isPressed;
        bool r = keyboard.rightArrowKey.isPressed;

        output = new ArrowInputPackage(timestamp, Time.deltaTime, u, d, l, r);
        return true;
    }
}

Config

An Input Provider is a Unity component, so it's configured like the rest of a study: in the Inspector, not in code.

  1. Add a concrete provider to the agent. Add a component such as KeyboardArrowProvider or the study example's JoystickInputProvider to the same GameObject as TeleroboticsAgent. Every selected module must live on that one GameObject.
  2. Assign it to the agent's Input Provider field. It's required — the agent refuses to start (logs an error and disables itself) if this field, Command Mapping & Encoding, Uplink Communication, or the Vehicle/Robot Model is left empty.
  3. Configure the provider's own settings, if it has any. The base class defines no configuration surface beyond the contract type — whatever a device needs (poll rate, key bindings, dead zones) is the concrete provider's own serialized fields.

The demo provider exposes one such field:

Field Type Default Description
Declared Poll Rate Hz int 50 Recorded alongside the study, not enforced — the Kernel always samples the provider at the agent's Update Rate. This field documents the device's real polling rate for a methods section.

Same GameObject, one instance. The agent's Awake validates that every selected module — including the Input Provider — is attached to its own GameObject; a reference to a component elsewhere in the scene fails this check and disables the agent.