Input controller API

The input controller API allows you to give commands to your input controller components for configuring callbacks for events, allowing you to configure input devices to control your machines.

The input controller component supports the following methods:

Method NameDescription
GetControlsGet a list of the Controls that your controller provides.
GetEventsThis method returns the current state of the controller as a map of Event Objects, representing the most recent event that has occurred on each available Control.
TriggerEventDirectly send an Event Object from external code.
GetGeometriesGet all the geometries associated with the input controller in its current configuration, in the frame of the input controller.
RegisterControlCallbackDefines a callback function to execute whenever one of the EventTypes selected occurs on the given Control.
ReconfigureReconfigure this resource.
GetResourceNameGet the ResourceName for this input controller with the given name.
CloseSafely shut down the resource and prevent further use.

Establish a connection

To get started using Viam’s SDKs to connect to and control your controller and the rest of your machine, go to your machine’s page on the Viam app, Navigate to the CONNECT tab’s Code sample page, select your preferred programming language, and copy the sample code.

To show your machine’s API key in the sample code, toggle Include API key.

When executed, this sample code creates a connection to your machine as a client.

The following examples assume you have an input controller called "my_controller" configured as a component of your machine. If your input controller has a different name, change the name in the code.

Import the input controller package for the SDK you are using:

from viam.components.input import Control, Controller, EventType
import (
  "go.viam.com/rdk/components/input"
)

API

GetControls

Get a list of the Controls that your controller provides.

Parameters:

  • extra (Mapping[str, Any]) (optional): Extra options to pass to the underlying RPC call.
  • timeout (float) (optional): An option to set how long to wait (in seconds) before calling a time-out and closing the underlying RPC call.

Returns:

Example:

# Get the controller from the machine.
my_controller = Controller.from_robot(
    robot=machine, "my_controller")

# Get the list of Controls provided by the controller.
controls = await my_controller.get_controls()

# Print the list of Controls provided by the controller.
print(f"Controls: {controls}")

For more information, see the Python SDK Docs.

Parameters:

  • ctx (Context): A Context carries a deadline, a cancellation signal, and other values across API boundaries.
  • extra (map[string]interface{}): Extra options to pass to the underlying RPC call.

Returns:

  • ([]Control): List of Controls provided by the controller.
  • (error): An error, if one occurred.

Example:

myController, err := input.FromRobot(machine, "my_input_controller")

// Get the list of Controls provided by the controller.
controls, err := myController.Controls(context.Background(), nil)

For more information, see the Go SDK Docs.

GetEvents

This method returns the current state of the controller as a map of Event Objects, representing the most recent event that has occurred on each available Control.

Parameters:

  • extra (Mapping[str, Any]) (optional): Extra options to pass to the underlying RPC call.
  • timeout (float) (optional): An option to set how long to wait (in seconds) before calling a time-out and closing the underlying RPC call.

Returns:

Example:

# Get the controller from the machine.
my_controller = Controller.from_robot(
    robot=machine, "my_controller")

# Get the most recent Event for each Control.
recent_events = await my_controller.get_events()

# Print out the most recent Event for each Control.
print(f"Recent Events: {recent_events}")

For more information, see the Python SDK Docs.

Parameters:

  • ctx (Context): A Context carries a deadline, a cancellation signal, and other values across API boundaries.
  • extra (map[string]interface{}): Extra options to pass to the underlying RPC call.

Returns:

Example:

myController, err := input.FromRobot(machine, "my_input_controller")

// Get the most recent Event for each Control.
recent_events, err := myController.Events(context.Background(), nil)

For more information, see the Go SDK Docs.

TriggerEvent

Directly send an Event Object from external code.

Parameters:

  • event (viam.components.input.input.Event) (required): The event to trigger.
  • extra (Mapping[str, Any]) (optional): Extra options to pass to the underlying RPC call.
  • timeout (float) (optional): An option to set how long to wait (in seconds) before calling a time-out and closing the underlying RPC call.

Returns:

  • None.

Example:

# Get your controller from the machine.
my_controller = Controller.from_robot(
    robot=machine, "my_controller")

# Define a "Button is Pressed" event for the control BUTTON_START.
button_is_pressed_event = Event(
    time(), EventType.BUTTON_PRESS, Control.BUTTON_START, 1.0)

# Trigger the event on your controller. Set this trigger to timeout if it has
# not completed in 7 seconds.
await my_controller.trigger_event(event=button_is_pressed_event, timeout=7.0)

For more information, see the Python SDK Docs.

Parameters:

  • ctx (Context): A Context carries a deadline, a cancellation signal, and other values across API boundaries.
  • event (Event): The Event to trigger on the controller.
  • extra (map[string]interface{}): Extra options to pass to the underlying RPC call.

Returns:

  • (error): An error, if one occurred.

For more information, see the Go SDK Docs.

GetGeometries

Get all the geometries associated with the input controller in its current configuration, in the frame of the input controller. The motion and navigation services use the relative position of inherent geometries to configured geometries representing obstacles for collision detection and obstacle avoidance while motion planning.

Parameters:

  • extra (Mapping[str, Any]) (optional): Extra options to pass to the underlying RPC call.
  • timeout (float) (optional): An option to set how long to wait (in seconds) before calling a time-out and closing the underlying RPC call.

Returns:

Example:

my_controller = Controller.from_robot(robot=machine, name="my_controller")
geometries = await my_controller.get_geometries()

if geometries:
    # Get the center of the first geometry
    print(f"Pose of the first geometry's centerpoint: {geometries[0].center}")

For more information, see the Python SDK Docs.

RegisterControlCallback

Defines a callback function to execute whenever one of the EventTypes selected occurs on the given Control.

You can only register one callback function per Event for each Control. A second call to register a callback function for a EventType on a Control replaces any function that was already registered.

You can pass a nil function here to “deregister” a callback.

Parameters:

Returns:

  • None.

Example:

from viam.components.input import Control, EventType

# Define a function to handle pressing the Start Menu Button "BUTTON_START" on
# your controller, printing out the start time.
def print_start_time(event):
    print(f"Start Menu Button was pressed at this time: {event.time}")


# Define a function that handles the controller.
async def handle_controller(controller):
    # Get the list of Controls on the controller.
    controls = await controller.get_controls()

    # If the "BUTTON_START" Control is found, register the function
    # print_start_time to fire when "BUTTON_START" has the event "ButtonPress"
    # occur.
    if Control.BUTTON_START in controls:
        controller.register_control_callback(
            Control.BUTTON_START, [EventType.BUTTON_PRESS], print_start_time)
    else:
        print("Oops! Couldn't find the start button control! Is your "
            "controller connected?")
        exit()

    while True:
        await asyncio.sleep(1.0)


async def main():
    # ... < INSERT CONNECTION CODE FROM MACHINE'S CODE SAMPLE TAB >

    # Get your controller from the machine.
    my_controller = Controller.from_robot(
        robot=machine, "my_controller")

    # Run the handleController function.
    await handle_controller(my_controller)

    # ... < INSERT ANY OTHER CODE FOR MAIN FUNCTION >

For more information, see the Python SDK Docs.

Parameters:

Returns:

  • (error): An error, if one occurred.

Example:

// Define a function to handle pressing the Start Menu button, "ButtonStart", on your controller and logging the start time
printStartTime := func(ctx context.Context, event input.Event) {
    logger.Info("Start Menu Button was pressed at this time: %v", event.Time)
}

myController, err := input.FromRobot(machine, "my_input_controller")

// Define the EventType "ButtonPress" to serve as the trigger for printStartTime.
triggers := []input.EventType{input.ButtonPress}

// Get the controller's Controls.
controls, err := myController.Controls(context.Background(), nil)

// If the "ButtonStart" Control is found, trigger printStartTime when on "ButtonStart" the event "ButtonPress" occurs.
if !slices.Contains(controls, input.ButtonStart) {
    logger.Error("button 'ButtonStart' not found; controller may be disconnected")
    return
}

myController.RegisterControlCallback(context.Background(), input.ButtonStart, triggers, printStartTime, nil)

For more information, see the Go SDK Docs.

Reconfigure

Reconfigure this resource. Reconfigure must reconfigure the resource atomically and in place.

Parameters:

  • ctx (Context): A Context carries a deadline, a cancellation signal, and other values across API boundaries.
  • deps (Dependencies): The resource dependencies.
  • conf (Config): The resource configuration.

Returns:

  • (error): An error, if one occurred.

For more information, see the Go SDK Docs.

GetResourceName

Get the ResourceName for this input controller with the given name.

Parameters:

  • name (str) (required): The name of the Resource.

Returns:

Example:

my_input_controller_name = Controller.get_resource_name("my_input_controller")

For more information, see the Python SDK Docs.

Close

Safely shut down the resource and prevent further use.

Parameters:

  • None.

Returns:

  • None.

Example:

my_controller = Controller.from_robot(robot=machine, name="my_controller")
await my_controller.close()

For more information, see the Python SDK Docs.

Parameters:

  • ctx (Context): A Context carries a deadline, a cancellation signal, and other values across API boundaries.

Returns:

  • (error): An error, if one occurred.

Example:

myInputController, err := input.FromRobot(machine, "my_input_controller")

err = myInputController.Close(context.Background())

For more information, see the Go SDK Docs.

API types

The input API defines the following types:

Event object

Each Event object represents a singular event from the input device, and has four fields:

  1. Time: time.Time the event occurred.
  2. Event: EventType indicating the type of event (for example, a specific button press or axis movement).
  3. Control: Control indicating which Axis, Button, or Pedal on the controller has been changed.
  4. Value: float64 indicating the position of an Axis or the state of a Button on the specified control.

EventType field

A string-like type indicating the specific type of input event, such as a button press or axis movement.

  • To select for events of all type when registering callback function with RegisterControlCallback, you can use AllEvents as your EventType.
  • The registered function is then called in addition to any other callback functions you’ve registered, every time an Event happens on your controller. This is useful for debugging without interrupting normal controls, or for capturing extra or unknown events.

Registered EventTypes definitions:

ALL_EVENTS = "AllEvents"
"""
Callbacks registered for this event will be called in ADDITION to other
registered event callbacks.
"""

CONNECT = "Connect"
"""
Sent at controller initialization, and on reconnects.
"""

DISCONNECT = "Disconnect"
"""
If unplugged, or wireless/network times out.
"""

BUTTON_PRESS = "ButtonPress"
"""
Typical key press.
"""

BUTTON_RELEASE = "ButtonRelease"
"""
Key release.
"""

BUTTON_HOLD = "ButtonHold"
"""
Key is held down. This will likely be a repeated event.
"""

BUTTON_CHANGE = "ButtonChange"
"""
Both up and down for convenience during registration, not typically emitted.
"""

POSITION_CHANGE_ABSOLUTE = "PositionChangeAbs"
"""
Absolute position is reported via Value, a la joysticks.
"""

POSITION_CHANGE_RELATIVE = "PositionChangeRel"
"""
Relative position is reported via Value, a la mice, or simulating axes with
up/down buttons.
"""

See the Python SDK Docs for the most current version of supported EventTypes.

 // Callbacks registered for this event will be called in ADDITION to other registered event callbacks.
AllEvents EventType = "AllEvents"

// Sent at controller initialization, and on reconnects.
Connect EventType = "Connect"

// If unplugged, or wireless/network times out.
Disconnect EventType = "Disconnect"

// Typical key press.
ButtonPress EventType = "ButtonPress"

// Key release.
ButtonRelease EventType = "ButtonRelease"

// Key is held down. This will likely be a repeated event.
ButtonHold EventType = "ButtonHold"

// Both up and down for convenience during registration, not typically emitted.
ButtonChange EventType = "ButtonChange"

// Absolute position is reported via Value, a la joysticks.
PositionChangeAbs EventType = "PositionChangeAbs"

// Relative position is reported via Value, a la mice, or simulating axes with up/down buttons.
PositionChangeRel EventType = "PositionChangeRel"

See the Viam RDK for the most current version of supported EventTypes.

Control field

A string representing the physical input location, like a specific axis or button, of your Controller that the Event Object is coming from.

Registered Control types are defined as follows:

# Axes
ABSOLUTE_X = "AbsoluteX"
ABSOLUTE_Y = "AbsoluteY"
ABSOLUTE_Z = "AbsoluteZ"
ABSOLUTE_RX = "AbsoluteRX"
ABSOLUTE_RY = "AbsoluteRY"
ABSOLUTE_RZ = "AbsoluteRZ"
ABSOLUTE_HAT0_X = "AbsoluteHat0X"
ABSOLUTE_HAT0_Y = "AbsoluteHat0Y"

# Buttons
BUTTON_SOUTH = "ButtonSouth"
BUTTON_EAST = "ButtonEast"
BUTTON_WEST = "ButtonWest"
BUTTON_NORTH = "ButtonNorth"
BUTTON_LT = "ButtonLT"
BUTTON_RT = "ButtonRT"
BUTTON_LT2 = "ButtonLT2"
BUTTON_RT2 = "ButtonRT2"
BUTTON_L_THUMB = "ButtonLThumb"
BUTTON_R_THUMB = "ButtonRThumb"
BUTTON_SELECT = "ButtonSelect"
BUTTON_START = "ButtonStart"
BUTTON_MENU = "ButtonMenu"
BUTTON_RECORD = "ButtonRecord"
BUTTON_E_STOP = "ButtonEStop"

# Pedals
ABSOLUTE_PEDAL_ACCELERATOR = "AbsolutePedalAccelerator"
ABSOLUTE_PEDAL_BRAKE = "AbsolutePedalBrake"
ABSOLUTE_PEDAL_CLUTCH = "AbsolutePedalClutch"

See the Python SDK Docs for the most current version of supported Control types.

// Axes.
AbsoluteX     Control = "AbsoluteX"
AbsoluteY     Control = "AbsoluteY"
AbsoluteZ     Control = "AbsoluteZ"
AbsoluteRX    Control = "AbsoluteRX"
AbsoluteRY    Control = "AbsoluteRY"
AbsoluteRZ    Control = "AbsoluteRZ"
AbsoluteHat0X Control = "AbsoluteHat0X"
AbsoluteHat0Y Control = "AbsoluteHat0Y"

// Buttons.
ButtonSouth  Control = "ButtonSouth"
ButtonEast   Control = "ButtonEast"
ButtonWest   Control = "ButtonWest"
ButtonNorth  Control = "ButtonNorth"
ButtonLT     Control = "ButtonLT"
ButtonRT     Control = "ButtonRT"
ButtonLT2    Control = "ButtonLT2"
ButtonRT2    Control = "ButtonRT2"
ButtonLThumb Control = "ButtonLThumb"
ButtonRThumb Control = "ButtonRThumb"
ButtonSelect Control = "ButtonSelect"
ButtonStart  Control = "ButtonStart"
ButtonMenu   Control = "ButtonMenu"
ButtonRecord Control = "ButtonRecord"
ButtonEStop  Control = "ButtonEStop"

// Pedals.
AbsolutePedalAccelerator Control = "AbsolutePedalAccelerator"
AbsolutePedalBrake       Control = "AbsolutePedalBrake"
AbsolutePedalClutch      Control = "AbsolutePedalClutch"

See GitHub for the most current version of supported Control types.

Axis controls

Analog devices like joysticks and thumbsticks which return to center/neutral on their own use Absolute axis control types.

These controls report a PositionChangeAbs EventType.

Value: A float64 between -1.0 and +1.0.

  • 1.0: Maximum position in the positive direction.
  • 0.0: Center, neutral position.
  • -1.0: Maximum position in the negative direction.

AbsoluteXY axes

If your input controller has an analog stick, this is what the stick’s controls report as.

Alternatively, if your input controller has two analog sticks, this is what the left joystick’s controls report as.

Name-1.00.01.0
AbsoluteXStick LeftNeutralStick Right
AbsoluteYStick ForwardNeutralStick Backwards

AbsoluteR-XY axes

If your input controller has two analog sticks, this is what the right joystick’s controls report as.

Name-1.00.01.0
AbsoluteRXStick LeftNeutralStick Right
AbsoluteRYStick ForwardNeutralStick Backwards
  • For Y axes, the positive direction is “nose up,” and indicates pulling back on the joystick.

Hat/D-Pad axes

If your input controller has a directional pad with analog buttons on the pad, this is what those controls report as.

Name-1.00.01.0
AbsoluteHat0XLeft DPAD Button PressNeutralRight DPAD Button Press
AbsoluteHat0YUp DPAD Button PressNeutralDown DPAD Button Press

Z axes (analog trigger sticks)

Name-1.00.01.0
AbsoluteZNeutralStick Pulled
AbsoluteRZNeutralStick Pulled

Z axes are usually not present on most controller joysticks.

If present, they are typically analog trigger sticks, and unidirectional, scaling only from 0 to 1.0 as they are pulled, as shown above.

AbsoluteZ is reported if there is one trigger stick, and AbsoluteZ (left) and AbsoluteRZ (right) is reported if there are two trigger sticks.

Z axes can be present on flight-style joysticks, reporting yaw, or left/right rotation, as shown below. This is not common.

Name-1.00.01.0
AbsoluteZStick Left YawNeutralStick Right Yaw
AbsoluteRZStick Left YawNeutralStick Right Yaw

Button controls

Button Controls report either ButtonPress or ButtonRelease as their EventType.

Value:

  • 0: released
  • 1: pressed

Action buttons (ABXY)

If your input controller is a gamepad with digital action buttons, this is what the controls for these buttons report as.

Diamond 4-Action Button PadRectangle 4-Action Button Pad
NameDescription
ButtonNorthTop
ButtonSouthBottom
ButtonEastRight
ButtonWestLeft
NameDescription
ButtonNorthTop-left
ButtonSouthBottom-right
ButtonEastTop-right
ButtonWestBottom-left
Horizontal 3-Action Button PadVertical 3-Action Button Pad
NameDescription
ButtonWestLeft
ButtonSouthCenter
ButtonEastRight
NameDescription
ButtonWestTop
ButtonSouthCenter
ButtonEastBottom
Horizontal 2-Action Button PadVertical 2-Action Button Pad
NameDescription
ButtonEastRight
ButtonSouthLeft
NameDescription
ButtonEastTop
ButtonSouthBottom

Trigger buttons (bumpers)

If your input controller is a gamepad with digital trigger buttons, this is what the controls for those buttons report as.

2-Trigger Button Pad4-Trigger Button Pad
NameDescription
ButtonLTLeft
ButtonRTRight
NameDescription
ButtonLTTop-left
ButtonRTTop-right
ButtonLT2Bottom-left
ButtonRT2Bottom-right

Digital buttons for sticks

If your input controller is a gamepad with “clickable” thumbsticks, this is what thumbstick presses report as.

NameDescription
ButtonLThumbLeft or upper button for stick
ButtonRThumbRight or lower button for stick

Miscellaneous buttons

Many devices have additional buttons. If your input controller is a gamepad with these common buttons, this is what the controls for those buttons report as.

NameDescription
ButtonSelectSelect or -
ButtonStartStart or +
ButtonMenuUsually the central “Home” or Xbox/PS “Logo” button
ButtonRecordRecording
ButtonEStopEmergency Stop (on some industrial controllers)

Usage examples

Control a wheeled base with a Logitech G920 steering wheel controller

The following Python code is an example of controlling a wheeled base with a Logitech G920 steering wheel controller, configured as a gamepad input controller.

import asyncio

from viam.components.base import Base
from viam.components.input import Control, Controller, EventType
from viam.proto.common import Vector3
from viam.robot.client import RobotClient
from viam.rpc.dial import Credentials, DialOptions

turn_amt = 0
modal = 0
cmd = {}


async def connect_robot(host, api_key, api_key_id):
    opts = RobotClient.Options.with_api_key(
      api_key=api_key,
      api_key_id=api_key_id
    )
    return await RobotClient.at_address(host, opts)


def handle_turning(event):
    global turn_amt
    turn_amt = -event.value
    print("turning:", turn_amt)


def handle_brake(event):
    if event.value != 0:
        print("braking!:", event.value)
        global cmd
        cmd = {"y": 0}
        print("broke")


def handle_accelerator(event):
    print("moving!:", event.value)
    global cmd
    accel = (event.value - 0.1) / 0.9
    if event.value < 0.1:
        accel = 0

    cmd = {"y": accel}


def handle_clutch(event):
    print("moving!:", event.value)
    global cmd
    accel = (event.value - 0.1) / 0.9
    if event.value < 0.1:
        accel = 0

    cmd = {"y": -accel}


async def handleController(controller):
    resp = await controller.get_events()
    # Show the input controller's buttons/axes
    print(f'Controls:\n{resp}')

    if Control.ABSOLUTE_PEDAL_ACCELERATOR in resp:
        controller.register_control_callback(
            Control.ABSOLUTE_PEDAL_ACCELERATOR,
            [EventType.POSITION_CHANGE_ABSOLUTE],
            handle_accelerator)
    else:
        print("Accelerator Pedal not found! Exiting! Are your steering wheel" +
              " and pedals hooked up?")
        exit()

    if Control.ABSOLUTE_PEDAL_BRAKE in resp:
        controller.register_control_callback(
            Control.ABSOLUTE_PEDAL_BRAKE,
            [EventType.POSITION_CHANGE_ABSOLUTE],
            handle_brake)
    else:
        print("Brake Pedal not found! Exiting!")
        exit()

    if Control.ABSOLUTE_PEDAL_CLUTCH in resp:
        controller.register_control_callback(
            Control.ABSOLUTE_PEDAL_CLUTCH,
            [EventType.POSITION_CHANGE_ABSOLUTE],
            handle_clutch)
    else:
        print("Accelerator Pedal not found! Exiting! Are your steering wheel" +
              " and pedals hooked up?")
        exit()

    if Control.ABSOLUTE_X in resp:
        controller.register_control_callback(
            Control.ABSOLUTE_X,
            [EventType.POSITION_CHANGE_ABSOLUTE],
            handle_turning)
    else:
        print("Wheel not found! Exiting!")
        exit()

    while True:
        await asyncio.sleep(0.01)
        global cmd
        if "y" in cmd:
            res = await modal.set_power(
                linear=Vector3(x=0, y=cmd["y"], z=0),
                angular=Vector3(x=0, y=0, z=turn_amt))
            cmd = {}
            print(res)


async def main():
    # ADD YOUR MACHINE REMOTE ADDRESS and API KEY VALUES.
    # These can be found in app.viam.com's CONNECT tab's Code sample page.
    # Toggle 'Include API key' to show the API key values.
    g920_robot = await connect_robot(
        "robot123example.locationxyzexample.viam.com", "API_KEY", "API_KEY_ID")
    modal_robot = await connect_robot(
        "robot123example.locationxyzexample.viam.com", "API_KEY", "API_KEY_ID")

    g920 = Controller.from_robot(g920_robot, 'wheel')
    global modal
    modal = Base.from_robot(modal_robot, 'modal-base-server:base')

    await handleController(g920)

    await g920_robot.close()
    await modal_robot.close()

if __name__ == '__main__':
    asyncio.run(main())

Drive a robot with four wheels and a skid steer platform

The following Go code is part of an example of using an input controller to drive a robot with four wheels & a skid steer platform.

The motorCtl callback function controls 5 motors: left front & back FL BL, right front & back FL BL, and a winder motor that raises and lowers a front-end like a bulldozer.

The event.Control logic is registered as a callback function to determine the case for setting the power of each motor from which button is pressed on the input controller.

// Define a single callback function
motorCtl := func(ctx context.Context, event input.Event) {
    if event.Event != input.PositionChangeAbs {
        return
    }

    speed := float32(math.Abs(event.Value))

    // Handle input events, commands to set the power of motor components (SetPower method)
    switch event.Control {
        case input.AbsoluteY:
            motorFL.SetPower(ctx, speed, nil)
            motorBL.SetPower(ctx, speed, nil)
        case input.AbsoluteRY:
            motorFR.SetPower(ctx, speed * -1, nil)
            motorBR.SetPower(ctx, speed * -1, nil)
        case input.AbsoluteZ:
            motorWinder.SetPower(ctx, speed, nil)
        case input.AbsoluteRZ:
            motorWinder.SetPower(ctx, speed * -1, nil)
    }
}

// Registers callback from motorCtl for a selected set of axes
for _, control := range []input.Control{input.AbsoluteY, input.AbsoluteRY, input.AbsoluteZ, input.AbsoluteRZ} {
    err = g.RegisterControlCallback(ctx, control, []input.EventType{input.PositionChangeAbs}, motorCtl)
}

Have questions, or want to meet other people working on robots? Join our Community Discord.

If you notice any issues with the documentation, feel free to file an issue or edit this file.