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 robot, enabling you to control your robot’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 robots 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 robot.
  • A component that you can direct the input to control, like an arm or motor.

Configuration

Configuration depends on the model of your device.

For configuration information, click on one of the following models:

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.

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

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

Check out the Client SDK Libraries Quick Start documentation for an overview of how to get started connecting to your robot using these libraries.

The following example assumes you have a controller called “my_controller” configured as a component of your robot. If your input controller has a different name, change the name in the example.

from viam.components.input import Controller, Control, EventType
from viam.robot.client import RobotClient

# Define a function to connect to the robot.
async def connect_controller():
    creds = Credentials(
        type='robot-location-secret',
        payload= "xyzabclocationexample"), # ADD YOUR LOCATION SECRET VALUE. This can be found in the Code Sample tab of app.viam.com.
    opts = RobotClient.Options(
        refresh_interval=0,
        dial_options=DialOptions(credentials=creds)
    )
    return await RobotClient.at_address("robot123example.locationxyzexample.viam.com", # ADD YOUR ROBOT REMOTE ADDRESS. This can be found in the Code Sample tab of app.viam.com.
    opts)

# Define a function to handle the controller.
async def handleController(controller):

    # Get the most recent events on the controller.
    resp = await controller.get_events()

    # Print the event, showing Control types.
    print(f'Controls:\n{resp}')

    # Use register_control_callback() to define how to handle Events from Controls.
    ...

async def main():

    # Connect to your robot.
    myRobotWithController = await connect_controller()

    # Get your controller from the robot.
    myController = Controller.from_robot(robot=myRobotWithController, name='my_controller')

    # Log an info message with the names of the different resources that are connected to your robot.
    print('Resources:')
    print(myController.resource_names)

    # Get the controller's Controls.
    resp = await myController.get_controls()

    # Print out the controller's Controls.
    print(f'Controls:\n{resp}')

    # Run the handleController function.
    await handleController(myController)

    # Wait to disconnect from the robot.
    await myRobotWithController.close()

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

    "github.com/edaniels/golog"
    "github.com/pkg/errors"
    "go.viam.com/rdk/components/input"
    "go.viam.com/utils"
    "golang.org/x/exp/slices"
)

// Define a function to handle the controller.
func handleController(controller input.Controller) {
    # Get the most recent events on the controller.
    resp, err := controller.Events()

    // Use RegisterControlCallback() to define how to handle Events from Controls.
    ...
}

func main() {
    utils.ContextualMain(mainWithArgs, golog.NewDevelopmentLogger("client"))
}


func mainWithArgs(ctx context.Context, args []string, logger golog.Logger) (err error) {

    // Connect to your robot.
    myRobotWithController, err := client.New(
        context.Background(),
        "xyzabclocationexample", // ADD YOUR LOCATION SECRET VALUE. This can be found in the Code Sample tab of app.viam.com.
        logger,
        client.WithDialOptions(rpc.WithCredentials(rpc.Credentials{
            Type:    utils.CredentialsTypeRobotLocationSecret,
            Payload: "robot123example.locationxyzexample.viam.com" // ADD YOUR ROBOT REMOTE ADDRESS. This can be found in the Code Sample tab of app.viam.com.
        })),
    )

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

    // Log any errors that occur and exit if an error is found.
    if err != nil {
        logger.Fatalf("cannot get controller: %v", err)
    }

    // Log an info message with the names of the different resources that are connected to your robot.
    logger.Info("Resources:")
    logger.Info(myRobotWithController.ResourceNames())

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

    // Print out the controller's Controls.
    logger.Info("Controls:")
    logger.Info(resp)

    // Log any errors that occur.
    if err != nil {
        logger.Fatal(err)
    }

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

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

    // Watch for errors.
    if err != nil {
        return err
    }

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

    return nil

}

For more examples see Usage Examples.

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 (Bumper)

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)

API

The input controller component supports the following methods:

Method NameDescription
ControlsGet a list of input Controls that this Controller provides.
EventsGet 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.

| DoCommand | Sends or receives model-specific commands. |

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)

Parameters:

Returns:

  • error (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 := myController.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?")
    }
}

Events

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 most recent Event for each Control.
recent_events = await myController.get_events()

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

Parameters:

  • Context (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 most recent Event for each Control.
recent_events, err := myController.Events(context.Background(), nil)

// Log any errors that occur and exit if an error is found.
if err != nil {
  logger.Fatalf("cannot get list of recent events from controller: %v", err)
}

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

Controls

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.

my_input_controller = Controller.from_robot(robot=myRobotWithController, name='my_controller') ...

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

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

Parameters:

  • Context (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:

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

For more information, see the Go SDK Docs.

myController, err := controller.FromRobot(myRobotWithController, "my_controller")
if err != nil {
  logger.Fatalf("cannot get controller: %v", err)
} ...

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

// Log any errors that occur and exit if an error is found.
if err != nil {
  logger.Fatalf("cannot get controls provided by controller: %v", err)
}

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

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:

  • command (Dict[str, Any]): The command to execute.

Returns:

  • result (Dict[str, Any]): Result of the executed command.
my_input_controller = Controller.from_robot(robot, "my_controller")

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

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.
  • cmd (cmd map[string]interface{}): The command to execute.

Returns:

  • result (cmd map[string]interface{}): Result of the executed command.
  • error (error): An error, if one occurred.
  myController, err := input.FromRobot(robot, "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.

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, payload):
    creds = Credentials(
        type='robot-location-secret',
        payload=payload),
    opts = RobotClient.Options(
        refresh_interval=0,
        dial_options=DialOptions(credentials=creds)
    )
    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:
            respon = await modal.set_power(linear=Vector3(x=0,y=cmd["y"],z=0), angular=Vector3(x=0,y=0,z=turn_amt))
            cmd = {}
            print(respon)

async def main():
    # ADD YOUR ROBOT REMOTE ADDRESS and LOCATION SECRET VALUES.
    # This can be found in the Code Sample tab of app.viam.com.
    g920_robot = await connect_robot("robot123example.locationxyzexample.viam.com", "xyzabclocationexample")
    modal_robot = await connect_robot("robot123example.locationxyzexample.viam.com", "xyzabclocationexample")

    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 & 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)
}

SDK Documentation

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