Drive the Viam Rover with the Viam SDK

The Viam SDKs allow you to write code in Python, Go, or TypeScript to control a Viam-connected robot like the Viam Rover. You can follow this tutorial with a rented Viam Rover or with your own Viam Rover.

Install a Viam SDK

Install either the Viam Python SDK, the Viam Go SDK, or the TypeScript SDK on your local computer.

Connect to your Viam Rover

The easiest way to get started writing an application with Viam is to navigate to the robot page on the Viam app, select the Code Sample tab, then select Python and copy the boilerplate code.

This code snippet imports all the necessary libraries and sets up a connection with the Viam app in the cloud.

Next, create a file named square.py and paste the boilerplate code from the Code Sample tab of the Viam app into your file. Then, save your file.

Run the code to verify that the Viam SDK is properly installed and that the viam-server instance on your robot is live.

You can run your code by typing the following into your terminal:

python3 square.py

The program prints an array of resources. These are the components and services that the robot is configured with in the Viam app.

python3 square.py
2023-05-12 11:33:21,045      INFO    viam.rpc.dial (dial.py:211)    Connecting to socket: /tmp/proxy-Dome34KJ.sock
Resources:
[namespace: "rdk"
type: "component"
subtype: "motor"
name: "left"
, namespace: "rdk"
type: "component"
subtype: "camera"
name: "cam"
, ...
]

The easiest way to get started writing an application with Viam is to navigate to the robot page on the Viam app, select the Code Sample tab, then select Go and copy the boilerplate code.

This code snippet imports all the necessary libraries and sets up a connection with the Viam app in the cloud.

Next, create a file named square.go and paste the boilerplate code from the Code Sample tab of the Viam app into your file. Then, save your file.

Initialize your project, and install the necessary libraries, and then run the program to verify that the Viam SDK is properly installed and that the viam-server instance on your robot is live:

go mod init square
go mod tidy
go run square.go

The program prints an array of resources. These are the components and services that the robot is configured with in the Viam app.

go run square.go
2023-05-12T11:28:00.383+0200 INFO    client    rover/square.go:40 
   Resources:
2023-05-12T11:28:00.383+0200 INFO    client    rover/square.go:41 
   [rdk:component:camera/fakeCam rdk:service:data_manager/overhead-cam:dm rdk:component:motor/left rdk:component:camera/cam rdk:component:encoder/Lenc rdk:component:encoder/Renc rdk:service:base_remote_control/base_rc rdk:service:sensors/builtin rdk:component:motor/right rdk:service:sensors/overhead-cam:builtin rdk:service:motion/overhead-cam:builtin rdk:component:input_controller/WebGamepad rdk:component:camera/overhead-cam:cam rdk:service:data_manager/builtin rdk:service:motion/builtin rdk:component:board/local rdk:component:base/viam_base] 

The easiest way to get started writing an application with Viam is to navigate to the robot page on the Viam app, select the Code Sample tab, then select TypeScript and copy the boilerplate code.

This code snippet imports all the necessary libraries and sets up a connection with the Viam app in the cloud.

Next, create a file named main.ts and paste the boilerplate code from the Code Sample tab of the Viam app into your file. Then, save your file.

Create another file named package.json with the following contents:

{
  "name": "test-rover",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "esbuild ./main.ts --bundle --outfile=static/main.js --servedir=static",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Viam Docs Team",
  "license": "ISC",
  "devDependencies": {
    "esbuild": "0.16.12"
  },
  "dependencies": {
    "@viamrobotics/sdk": "*"
  }
}

Create a folder static and inside it another file named index.html. Add the following markup:

<!DOCTYPE html>
<html>
  <head>
    <title>Drive a Viam Rover</title>
    <link rel="icon" href="favicon.ico" />
  </head>
  <body>
    <div id="main">
      <button id="main-button" disabled=true>
        Click me
      </button>
    </div>
    <script type="module" src="main.js">
    </script>
  </body>
</html>

Run the following commands to install the necessary libraries, and then run the program to verify that the Viam SDK is properly installed and that the viam-server instance on your robot is live:

npm install
npm start

Open a web browser and visit localhost:8000. You should see a disabled button that says Click me. If you successfully configured your robot and it is able to connect to the Viam app, the button will become enabled. If you open the developer console, you should see some output including the names of your rover’s resources. These are the components and services that the robot is configured with in the Viam app.

Drive your rover in a square

Now that you have connected the rover to Viam with the SDK, you can start writing code to control the Viam Rover. The following code moves the Viam Rover in a square:

The first thing you need to do is import the base component. The base is responsible for controlling the motors attached to the base of the rover. Add the following line of code to your imports:

from viam.components.base import Base

Next, you need to initialize your Viam Rover base.

In the main function, after you connect, paste the code from line 5. By default, the base name is viam_base. If you have changed the base name, update the name in your code.

Your main function should look like this:

async def main():
    robot = await connect()

    # Get the base component from the Viam Rover
    roverBase = Base.from_robot(robot, 'viam_base')

    await robot.close()

Now that your Viam Rover base is initialized, you can write code to drive it in a square. Paste this snippet above your main() function:

async def moveInSquare(base):
    for _ in range(4):
        # moves the Viam Rover forward 500mm at 500mm/s
        await base.move_straight(velocity=500, distance=500)
        print("move straight")
        # spins the Viam Rover 90 degrees at 100 degrees per second
        await base.spin(velocity=100, angle=90)
        print("spin 90 degrees")

Invoke the moveInSquare() function in your main function after initializing your base.

Your main function should now look like this:

async def main():
    robot = await connect()

    # Get the base component from the Viam Rover
    roverBase = Base.from_robot(robot, 'viam_base')

    # Move the Viam Rover in a square
    await moveInSquare(roverBase)

    await robot.close()

The first thing you need to do is import the base component. The base is responsible for controlling the motors attached to the base of the rover. Add the following line of code to your imports before:

import (
    // Be sure to keep all of the other imported libraries
    "go.viam.com/rdk/components/base"
)

Next, you need to initialize your Viam Rover base.

In the main function, after you connect, paste the code from lines 19-22. By default, the base name is viam_base. If you have changed the base name, update the name in your code.

Your main function should look like this:

func main() {
    logger := golog.NewDevelopmentLogger("client")
    robot, err := client.New(
        context.Background(),
        "ADDRESS_FROM_VIAM_APP",
        logger,
        client.WithDialOptions(rpc.WithCredentials(rpc.Credentials{
            Type: utils.CredentialsTypeRobotLocationSecret,
            Payload: "SECRET_FROM_VIAM_APP",
        })),
    )
    if err != nil {
        logger.Fatal(err)
    }

    defer robot.Close(context.Background())

    // Get the base from the Viam Rover
    roverBase, err := base.FromRobot(robot, "viam_base")
    if err != nil {
        logger.Fatalf("cannot get base: %v", err)
    }
}

Now that your Viam Rover base has been initialized, you can write code to drive it in a square. Paste this snippet above your main() function:

func moveInSquare(ctx context.Context, base base.Base, logger golog.Logger) {
    for i := 0; i < 4; i++ {
        // moves the Viam Rover forward 600mm at 500mm/s
        base.MoveStraight(ctx, 600, 500.0, nil)
        logger.Info("move straight")
        // spins the Viam Rover 90 degrees at 100 degrees per second
        base.Spin(ctx, 90, 100.0, nil)
        logger.Info("spin 90 degrees")
  }
}

Invoke the moveInSquare() function in your main function after initializing your base.

Your main function should now look like this:

func main() {
    // Connect rover to Viam...
    // Get the base from the Viam Rover
    roverBase, err := base.FromRobot(robot, "viam_base")
    if err != nil {
        logger.Fatalf("cannot get base: %v", err)
    }

    // Move the Viam Rover in a square
    moveInSquare(context.Background(), roverBase, logger)
}

Your main function should look similar to this but only the first few lines that connect to your rover are important for us:

async function main() {
  const host = 'ADDRESS_FROM_VIAM_APP';

  const robot = await VIAM.createRobotClient({
    host,
    credential: {
      type: 'robot-location-secret',
      payload: 'SECRET_FROM_VIAM_APP',
    },
    authEntity: host,
    signalingAddress: 'https://app.viam.com:443',
  });

  // Note that the pin supplied is a placeholder. Please change this to a valid pin you are using.
  // local
  const localClient = new VIAM.BoardClient(robot, 'local');
  const localReturnValue = await localClient.getGPIO('16');
  console.log('local getGPIO return value:', localReturnValue);

  // right
  const rightClient = new VIAM.MotorClient(robot, 'right');
  const rightReturnValue = await rightClient.isMoving();
  console.log('right isMoving return value:', rightReturnValue);

  // left
  const leftClient = new VIAM.MotorClient(robot, 'left');
  const leftReturnValue = await leftClient.isMoving();
  console.log('left isMoving return value:', leftReturnValue);

  // viam_base
  const viamBaseClient = new VIAM.BaseClient(robot, 'viam_base');
  const viamBaseReturnValue = await viamBaseClient.isMoving();
  console.log('viam_base isMoving return value:', viamBaseReturnValue);

  // cam
  const camClient = new VIAM.CameraClient(robot, 'cam');
  const camReturnValue = await camClient.getImage();
  console.log('cam getImage return value:', camReturnValue);

  console.log('Resources:');
  console.log(await robot.resourceNames());
}

Underneath the main function, add the following function that initializes your Viam Rover base client and drives it in a square:

// This function moves a base component in a square.
async function moveInSquare(client: VIAM.RobotClient) {
  // Replace with the name of a motor on your robot.
  const name = 'viam_base';
  const baseClient = new VIAM.BaseClient(client, name);

  try {
    button().disabled = true;
    for(let i=0; i<4; i++) {
      console.log("move straight");
      await baseClient.moveStraight(500, 500);
      console.log("spin 90 degrees");
      await baseClient.spin(90, 100);
    }
  } finally {
    button().disabled = false;
  }
}

Underneath the moveInSquare function, add this button function which gets the main-button button from the page loaded from index.html:

// This function gets the button element
function button() {
    return <HTMLButtonElement>document.getElementById('main-button');
}

Next, register a listener on the button you obtain from the button fuction and make it invoke the moveInSquare function. Place this code after the rover connection code: You can keep or remove the additional boilerplate code as you wish.

Your main function should now look like this:

async function main() {
  const host = 'ADDRESS_FROM_VIAM_APP';

  const robot = await VIAM.createRobotClient({
    host,
    credential: {
      type: 'robot-location-secret',
      payload: 'SECRET_FROM_VIAM_APP',
    },
    authEntity: host,
    signalingAddress: 'https://app.viam.com:443',
  });

  button().onclick = async () => {
    await moveInSquare(robot);
  };
  button().disabled = false;
}

When you run your code, your robot moves in a square.

Complete Code

This is the complete code for the tutorial:

import asyncio

from viam.components.base import Base
from viam.robot.client import RobotClient
from viam.rpc.dial import Credentials, DialOptions

async def connect():
    creds = Credentials(
        type='robot-location-secret',
        payload='SECRET_FROM_VIAM_APP')
    opts = RobotClient.Options(
        refresh_interval=0,
        dial_options=DialOptions(credentials=creds)
    )
    return await RobotClient.at_address('ADDRESS_FROM_VIAM_APP', opts)

async def moveInSquare(base):
    for _ in range(4):
        # moves the Viam Rover forward 500mm at 500mm/s
        await base.move_straight(velocity=500, distance=500)
        print("move straight")
        # spins the Viam Rover 90 degrees at 100 degrees per second
        await base.spin(velocity=100, angle=90)
        print("spin 90 degrees")

async def main():
    robot = await connect()

    print('Resources:')
    print(robot.resource_names)

    roverBase = Base.from_robot(robot, 'viam_base')

    # Move the Viam Rover in a square
    await moveInSquare(roverBase)

    await robot.close()

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

import (
    "context"

    "github.com/edaniels/golog"
    "go.viam.com/rdk/components/base"
    "go.viam.com/rdk/robot/client"
    "go.viam.com/rdk/utils"
    "go.viam.com/utils/rpc"
)

func moveInSquare(ctx context.Context, base base.Base, logger golog.Logger) {
    for i := 0; i < 4; i++ {
        // moves the Viam Rover forward 600mm at 500mm/s
        base.MoveStraight(ctx, 600, 500.0, nil)
        logger.Info("move straight")
        // spins the Viam Rover 90 degrees at 100 degrees per second
        base.Spin(ctx, 90, 100.0, nil)
        logger.Info("spin 90 degrees")
    }
}

func main() {
    logger := golog.NewDevelopmentLogger("client")
    robot, err := client.New(
        context.Background(),
        "ADDRESS_FROM_VIAM_APP",
        logger,
        client.WithDialOptions(rpc.WithCredentials(rpc.Credentials{
            Type:    utils.CredentialsTypeRobotLocationSecret,
            Payload: "SECRET_FROM_VIAM_APP",
        })),
    )
    if err != nil {
        logger.Fatal(err)
    }
    defer robot.Close(context.Background())

    // Get the base from the Viam Rover
    roverBase, err := base.FromRobot(robot, "viam_base")
    if err != nil {
        logger.Fatalf("cannot get base: %v", err)
    }

    moveInSquare(context.Background(), roverBase, logger)
}

package.json:

{
  "name": "test-rover",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "esbuild ./main.ts --bundle --outfile=static/main.js --servedir=static",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Viam Docs Team",
  "license": "ISC",
  "devDependencies": {
    "esbuild": "0.16.12"
  },
  "dependencies": {
    "@viamrobotics/sdk": "^0.0.28"
  }
}

static/index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>Drive a Viam Rover</title>
    <link rel="icon" href="favicon.ico" />
  </head>
  <body>
    <div id="main">
      <button id="main-button" disabled=true>
        Click me
      </button>
    </div>
    <script type="module" src="main.js">
    </script>
  </body>
</html>

main.ts:

// This code must be run in a browser environment.

import * as VIAM from '@viamrobotics/sdk';

async function main() {
  const host = 'ADDRESS_FROM_VIAM_APP';

  const robot = await VIAM.createRobotClient({
    host,
    credential: {
      type: 'robot-location-secret',
      payload: 'SECRET_FROM_VIAM_APP',
    },
    authEntity: host,
    signalingAddress: 'https://app.viam.com:443',
  });

  button().onclick = async () => {
    await moveInSquare(robot);
  };
  button().disabled = false;
}

// This function moves a base component in a square.
async function moveInSquare(client: VIAM.RobotClient) {
    // Replace with the name of a motor on your robot.
    const name = 'viam_base';
    const baseClient = new VIAM.BaseClient(client, name);

    try {
      button().disabled = true;
      for(let i=0; i<4; i++) {
        console.log("move straight");
        await baseClient.moveStraight(500, 500);
        console.log("spin 90 degrees");
        await baseClient.spin(90, 100);
      }
    } finally {
      button().disabled = false;
    }
}

function button() {
    return <HTMLButtonElement>document.getElementById('main-button');
}

main().catch((error) => {
  console.error('encountered an error:', error)
});

Next Steps

If you’re ready for more, try making your rover move in different ways. Can you make it move in a circle? A figure-eight? You could also write some code to control the other components on the Viam Rover, like the camera, or the rover’s motors.

You could also control Viam’s services, by adding data management to collect data in real time or Vision Services to add color detection to your Rover.

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



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