Input Controller Component

You are likely already familiar with human-interface devices, like keyboards and mice, elevator button panels, light power switches, joysticks, and gamepads, or, video game controllers, from your daily life.

Configuring an input component allows you to use devices like these with your machine, enabling you to control your machine’s actions by interacting with the device.

This component currently supports devices like gamepads and joysticks that contain one or more Controls representing the individual axes and buttons on the device. To use the controller’s inputs, you must register callback functions to the Controls with the input API.

The callback functions can then handle the Events that are sent when the Control is activated or moved. For example, when a specific button is pushed, the callback function registered to it can move another component, or print a specific output.

Most machines with an input controller need at least the following hardware:

  • A computer capable of running viam-server.
  • A power supply cable or batteries for the input device and the machine.
  • A component that you can direct the input to control, like an arm or motor.

Supported models

To use your input controller with Viam, check whether one of the following built-in models supports your input controller.

Built-in models

For configuration information, click on the model name:

ModelDescription
gamepadX-box, Playstation, and similar controllers with Linux support.
gpioCustomizable GPIO/ADC based device using a board component.
muxMultiplexed controller, combining multiple sources of input.
webgamepadA remote, web based gamepad.
fakeA model for testing, with no physical hardware - see GitHub.

Control your machine with an input controller with Viam’s client SDK libraries

Once you’ve configured your input controller according to model type, you can write code to define how your machine processes the input from the controller.

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

When executed, this sample code will create a connection to your machine as a client. Then control your machine programmatically by adding API method calls as shown in the following examples.

These 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.

Be sure to 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

The input controller component supports the following methods:

Method NameDescription
GetControlsGet a list of input Controls that this Controller provides.
GetEventsGet the current state of the Controller as a map of the most recent Event for each Control.
RegisterControlCallbackDefine a callback function to execute whenever one of the EventTypes selected occurs on the given Control.
GetGeometriesGet all the geometries associated with the input controller in its current configuration, in the frame of the input controller.
TriggerEventTrigger an event on the controller.
DoCommandSend or receive model-specific commands.
CloseSafely shut down the resource and prevent further use.

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

For more information, see the Python SDK Docs.

# 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:\n{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=myRobotWithController, name="my_controller")

    # Run the handleController function.
    await handleController(my_controller)

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

Parameters:

Returns:

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

For more information, see the Go SDK Docs.


// Define a function that handles the controller.
func handleController(controller input.Controller) {

    // Define a function to handle pressing the Start Menu Button "ButtonStart" on your controller, 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)
    }

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

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

    // If the "ButtonStart" Control is found, register the function printStartTime to fire when "ButtonStart" has the event "ButtonPress" occur.
    if slices.Contains(controls, Control.ButtonStart) {
        err := controller.RegisterControlCallback(context.Background(), Control: input.ButtonStart, triggers, printStartTime, nil)
    }
     else {
        logger.Fatalf("Oops! Couldn't find the start button control! Is your controller connected?")
    }
}

func main() {
    utils.ContextualMain(mainWithArgs, logging.NewLogger("client"))
}


func mainWithArgs(ctx context.Context, args []string, logger logging.Logger) (err error) {
    // ... < INSERT CONNECTION CODE FROM MACHINE'S CODE SAMPLE TAB >

    // Get the controller from the machine.
    myController, err := input.FromRobot(myRobotWithController, "my_controller")

    // Run the handleController function.
    err := HandleController(myController)

    // Delay closing your connection to your machine.
    err = myRobotWithController.Start(ctx)
    defer myRobotWithController.Close(ctx)

    // Wait to exit mainWithArgs() until Context is Done.
    <-ctx.Done()

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

    return nil
}

GetEvents

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

Parameters:

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

Returns:

For more information, see the Python SDK Docs.

# Get the controller from the machine.
my_controller = Controller.from_robot(
    robot=myRobotWithController, name="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:\n{recent_events}")

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:

For more information, see the Go SDK Docs.

// Get the controller from the machine.
myController, err := input.FromRobot(myRobotWithController, "my_controller")

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

// Log the most recent Event for each Control.
logger.Info("Recent Events: %v", recent_events)

GetControls

Get a list of the Controls that your controller provides.

Parameters:

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

Returns:

For more information, see the Python SDK Docs.

# Get the controller from the machine.
my_controller = Controller.from_robot(
    robot=myRobotWithController, name="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:\n{controls}")

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:

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

For more information, see the Go SDK Docs.

// Get the controller from the machine.
myController, err := input.FromRobot(myRobotWithController, "my_controller")

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

// Log the list of Controls provided by the controller.
logger.Info("Controls:")
logger.Info(controls)

TriggerEvent

Directly send an Event Object from external code.

Parameters:

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

Returns:

  • None

For more information, see the Python SDK Docs.

# 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 myController.trigger_event(event=my_event, timeout=7.0)

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.

// Define a "Button is Pressed" event for the control ButtonStart.
buttonIsPressedEvent := input.Event{Time: time.Now(), Event: input.ButtonPress, Control: input.ButtonStart, Value: 1.0}

// Trigger the event on your controller.
err := myController.TriggerEvent(ctx Context.background(), buttonIsPressedEvent, nil)

// Log any errors that occur.
if err != nil {
  logger.Fatalf("cannot trigger event on controller: %v", err)
}

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 (Optional[Dict[str, Any]]): Extra options to pass to the underlying RPC call.
  • timeout (Optional[float]): An option to set how long to wait (in seconds) before calling a time-out and closing the underlying RPC call.

Returns:

  • (List[Geometry]): The geometries associated with the input controller, in any order.

For more information, see the Python SDK Docs.

my_controller = Controller.from_robot(
    robot=myRobotWithController,
    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}")

DoCommand

Execute model-specific commands that are not otherwise defined by the component API. For built-in models, model-specific commands are covered with each model’s documentation. If you are implementing your own input controller and add features that have no built-in API method, you can access them with DoCommand.

Parameters:

Returns:

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

command = {"cmd": "test", "data1": 500}
result = my_controller.do(command)

For more information, see the Python SDK Docs.

Parameters:

Returns:

// Get the controller from the machine.
myController, err := input.FromRobot(myRobotWithController, "my_controller")

command := map[string]interface{}{"cmd": "test", "data1": 500}
result, err := myController.DoCommand(context.Background(), command)

For more information, see the Go SDK Code.

Close

Safely shut down the resource and prevent further use.

Parameters:

  • None

Returns:

  • None
my_controller = Controller.from_robot(myRobotWithController, "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.
myController, err := input.FromRobot(myRobotWithController, "my_controller")

err := myController.Close(ctx)

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 the Code sample tab of app.viam.com.
    # Toggle 'Include secret' 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)
}

Troubleshooting

You can find additional assistance in the Troubleshooting section.

You can also ask questions in the Community Discord and we will be happy to help.

Next steps