Create a Modular Resource to Control a Rover like Intermode
Viam supports most rovers with builtin models. If your rover is not supported out of the box, this tutorial shows you how to add support for your rover or mobile robot.
To use a rover with the Viam platform, you have to configure the rover’s components. One of the components you need to configure is called a base, which allows you to control a mobile robot using commands like “move_straight”, “spin”, “set_velocity” and “stop”. You can think of the base component as an abstraction that coordinates the movement of the motors of your base for you so you can control the higher level object as a base.
For many robotic rovers you can use the wheeled
base model.
Once you specify the circumference of the wheels and how far they are apart, you can then control your rover with the base component.
However, some rovers or other mobile robots do not expose direct motor control.
For these types of machines, this tutorial shows you how to create a modular resource.
Creating a modular resource for your robot allows you to issue commands using the same base
interface as you would with builtin Viam components.
Once you have created the custom component, you can control both the Viam components and the modular resources using any of the Viam SDKs.
Even if your modular resource is built in Golang, you can use the Python, C++, or any other Viam SDK to issue commands.
Tip
You can follow the tutorial for any rover or mobile robot, but the tutorial will specifically use the Intermode rover as an example for creating a modular resource to control your rover.
Intermode aims to make the hardware aspects of a mobile-robot-based business simple and worry-free. This is a powerful pairing, since Viam simplifies the software aspects to revolutionize hardware.
The Intermode rover uses the CAN bus protocol, a robust and prevalent vehicle communication standard used in most modern vehicles. This tutorial shows you how to leverage this protocol and abstract it into the Viam base interface, so that your rover can be controlled securely from anywhere with the programming language of your choice.
Hardware requirements
The tutorial uses the following hardware:
- Raspberry Pi with microSD card, with
viam-server
installed per our Raspberry Pi setup guide. - An Intermode rover
- PiCAN 2 - Canbus interface for Raspberry Pi
- 12V to 5V Buck Converter
- USB-C Male Plug to Pigtail Cable
Setup
Machine setup
Before you can use Viam on your device, you must ensure it has a supported operating system. If you are using a Raspberry Pi, start by setting up your Raspberry Pi. For other single-board computers, see the installation guide.
Add a new machine in the Viam app.
Then follow the setup instructions to install viam-server
on the computer you’re using for your project and connect to the Viam app.
Wait until your machine has successfully connected.
Next, install the PiCAN 2 driver software following these instructions.
Tip
If you restart your Pi, you need to bring up the CAN interface again, as the above linked instructions do not set this process up to automatically start on system start.
Hardware
Caution
Always disconnect devices from power before plugging, unplugging or moving wires or otherwise modifying electrical circuits.
Power your Raspberry Pi off and attach the PiCAN 2 by aligning the 40 way connector and fitting it to the top of the Pi using a spacer and a screw.
Next, with the Intermode rover powered down, connect the 6-wire amphenol connector that comes with the rover to the 4 screw terminal on PiCAN bus:
- Connect one of the 12V wires (red) to the +12V terminal
- Connect one of the ground wires (black) to the GND terminal
- Connect the CAN low wire (blue) to the CAN_L terminal
- Connect the CAN high wire (white) to the CAN_H terminal.
You have two remaining wires (12V and ground).
Connect the remaining two wires to the + (red) and - (black) input terminals on your buck converter. Attach the USB-C adapter wires to the output of your buck converter, and plug the other end of the USB-C adapter into your Pi. You can now power up the rover, which also provides power to your Pi and allows it to communicate with the rover using CAN bus!
A modular resource for the Intermode base
The Viam platform provides APIs for common component types within viam-server
.
For controlling a mobile robot’s movements, the base component exposes a useful interface.
In the rest of this tutorial, you’ll learn how to use this API to create your own custom modular resource. If you want to directly configure this modular resource code with your robot, skip to using the intermode base resource
Create a custom model using the Viam RDK base API
The base component exposes an API for controlling a mobile robot’s movements. To use it for the Intermode rover, you must create a new model with its own implementation of each method.
Generally modular resources are made up of two parts:
- The first part is the entry point for the module. It creates the model and registers it with
viam-server
which makes it usable by the Viam SDKs. - The second part implements the functionality for the API.
The full code for the modular resource is available on GitHub. This is the code for the entry point:
// namespace, repo-name, model
var model = resource.NewModel("viamlabs", "tutorial", "intermode")
func main() {
goutils.ContextualMain(mainWithArgs, logging.NewLogger("intermodeBaseModule"))
}
func mainWithArgs(ctx context.Context, args []string, logger logging.Logger) (err error) {
registerBase()
modalModule, err := module.NewModuleFromArgs(ctx, logger)
if err != nil {
return err
}
modalModule.AddModelFromRegistry(ctx, base.Subtype, model)
err = modalModule.Start(ctx)
defer modalModule.Close(ctx)
if err != nil {
return err
}
<-ctx.Done()
return nil
}
// helper function to add the base's constructor and metadata to the component registry, so that we can later construct it.
func registerBase() {
registry.RegisterComponent(
base.Subtype, // the "base" API: "rdk:component:base"
model,
registry.Component{Constructor: func(
ctx context.Context,
deps registry.Dependencies,
config config.Component,
logger logging.Logger,
) (interface{}, error) {
return newBase(config.Name, logger) // note: newBase() is not shown in this tutorial
}})
}
To support this new model of a base that you are creating, you need to give the model a name.
This tutorial uses the namespace viamlabs
, an (arbitrary) repo-name called tutorial
and lastly, the model name intermode
.
The complete triplet is:
viamlabs:tutorial:intermode
.
The entry point code defines the model name and then registers it with viam-server
.
When registering it, the code also provides the API that the new model supports.
That means in this case that the base should support the default base API with methods such as MoveStraight
and Spin
.
The API of any Viam resource is also represented as colon-separated triplets where the first element is a namespace.
Since you are using the default Viam API for a base, the API you are using is:
rdk:component:base
.
In the code this is specified on line 30 as base.Subtype
.
Implement base methods
Now that the modular resource code has registered the API it is using and its custom model, you can implement the methods provided by the base API. Since the Intermode rover’s commands are in the CAN bus format, you need the modular resource code to translate any commands sent from the base API, like SetPower, SetVelocity, or Stop to CAN bus frames. For reference, Intermode provides documentation on how its CAN frames are formatted.
At a high level, the tutorial code does the following:
- The
SetPower
command implements the SetPower interface from the rdk:component:base API - The parameters sent to
SetPower
are formatted as a driveCommand - The driveCommand is converted to a CAN frame, and set as the next command
- publishThread runs a loop continuously, sending the current command every 10ms (the Intermode base will otherwise time out)
// this struct describes intermode base drive commands
type driveCommand struct {
Accelerator float64
Brake float64
SteeringAngle float64
Gear byte
SteerMode byte
}
func (base *interModeBase) setNextCommand(ctx context.Context, cmd modalCommand) error {
if err := ctx.Err(); err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case base.nextCommandCh <- cmd.toFrame(base.logger):
}
return nil
}
// toFrame convert the drive command to a CANBUS data frame.
func (cmd *driveCommand) toFrame(logger logging.Logger) canbus.Frame {
frame := canbus.Frame{
ID: driveId,
Data: make([]byte, 0, 8),
Kind: canbus.SFF,
}
frame.Data = append(frame.Data, calculateAccelAndBrakeBytes(cmd.Accelerator)...)
frame.Data = append(frame.Data, calculateSteeringAngleBytes(cmd.SteeringAngle)...)
if cmd.Accelerator < 0 {
cmd.Gear = gears[gearReverse]
}
frame.Data = append(frame.Data, cmd.Gear, cmd.SteerMode)
logger.Debugw("frame", "data", frame.Data)
return frame
}
func (base *interModeBase) SetPower(ctx context.Context, linear, angular r3.Vector, extra map[string]interface{}) error {
return base.setNextCommand(ctx, &driveCommand{
Accelerator: linear.Y * 100, // the base API provides linear.Y between -1 (full reverse) and 1 (full forward)
Brake: 0,
SteeringAngle: angular.Z * 100, // the base API provides angular.Z between -1 (full left) and 1 (full right)
Gear: gears[gearDrive],
SteerMode: steerModes[steerModeFourWheelDrive],
})
}
With this code, the intermode base can receive and execute SetPower
commands using any Viam SDK.
Leaving some methods unimplemented
In some cases, you may not want to implement specific methods provided by the resource type’s API. For example, some hardware may not support specific functionality. When you want to leave a method unimplemented you must still create that method, but return an appropriate error message.
In this tutorial, the code leaves the IsMoving method unimplemented (for illustrative purposes).
func (base *interModeBase) IsMoving(ctx context.Context) (bool, error) {
return false, errors.New("IsMoving(): unimplemented")
}
Use the Intermode base modular resource
Copy the modular resource binary
This tutorial’s modular resource code leverages libraries (specifically a CAN bus library) that run on Linux and interface with the PiCAN socket on your Raspberry Pi. The tutorial repository includes a compiled binary that is ready to run on 64-bit Raspberry Pi OS. If you make changes to the tutorial code, you’ll need to re-compile to create a new binary.
To run the modular resource, first copy the module binary to your Raspberry Pi.
Configure the Intermode base resource
You will now configure your base in the Viam app.
Go to the machine you added during setup.
To make your module accessible to viam-server
, you must add it as a local module.
- Navigate to the CONFIGURE tab of your machine’s page in the Viam app.
- Click the + (Create) icon next to your machine part in the left-hand menu and select Local module, then Local module.
- Enter a Name for this instance of your modular resource, for example
my-custom-base-module
. - Enter the module’s executable path. This path must be the absolute path to the executable on your machine’s filesystem. Add the path to where you downloaded the compiled binary.
- Then, click the Create button, and click Save in the upper right corner to save your config.
Now that viam-server
can find the module, you can add the base component it provides for your Intermode base:
On the CONFIGURE tab of your machine’s page on the Viam app:
- Click the + (Create) icon next to your machine part in the left-hand menu and select Local module.
- Then, select Local component.
On the Create menu for a Local component:
- Select the type of modular resource provided by your module: base, from the dropdown menu.
- Enter the model namespace triplet of your modular resource’s model:
viamlabs:tutorial:intermode
. - Enter a name for this instance of your base, for example
base-1
. This name must be different from the module name.
Click Create to create the modular resource provided by the local module.
Click Save in the top right corner.
For more information on modules and how they work, see the modular resources documentation.
Control the rover
After you configured the base, go to the CONTROL tab and expand the base component to view the controls to enable keyboard or discrete control over your machine’s movement.
Caution
Be careful, the Intermode is a large and powerful rover - make sure you have the shutoff key in hand for emergencies and make sure your rover has sufficient space to drive around without hitting anyone or anything.
On the Keyboard tab, you can toggle the keyboard control to active. With the Keyboard toggle active, use W and S to go forward and back, and A and D to arc and spin.
Try driving your base around using the WASD keyboard controls.
If you navigate to the Discrete tab, you can use movement modes such as Straight
and Spin
and different movement types such as Continuous
and Discrete
and directions such as Forwards
and Backwards
.
If you do not see the base card in the CONTROL tab, check the LOGS tab for possible setup or configuration errors.
Full code for the Intermode base modular resource
Check out this GitHub repository for the working modular resource implementation example which we use in this tutorial.
Next steps
Now that you have integrated your rover or mobile base with Viam, you can use the Viam SDKs to operate your rover.
If your rover has a camera
or a movement_sensor
, you can try the following tutorials:
You can also ask questions in the Community Discord and we will be happy to help.
Was this page helpful?
Glad to hear it! If you have any other feedback please let us know:
We're sorry about that. To help us improve, please tell us what we can do better:
Thank you!