The navigation service allows you to queue up user-defined waypoints for your machine to move to in the order that you specify.
You can also add obstacles or set linear and angular velocity targets in your navigation service config.
Viam’s motion planner will plan routes that avoid those obstacles and attempt to keep the robot at your specified velocity.
Learning Goals
After following this tutorial, you will be able to:
add waypoints to specify locations you want your machine to move to, both in the app and programmatically
use the navigation service to move your machine along those waypoints
To try it out yourself, you need a mobile base and a movement sensor that can track the robot’s GPS coordinates and angular and linear velocity.
Follow this tutorial to get started using Viam’s Navigation service to help your wheeled base navigate across space with our recommended stack.
Requirements
A base
We used a LEO rover, configured as a wheeled base, but you can use whatever model of rover base you have on hand:
A movement sensor with GPS position, compass heading, and angular and linear velocity readings
We used three movement sensors to satisfy these requirements:
A wheeled-odometry model gathering angular and linear velocity information from the encoders wired to our base’s motors.
A merged model aggregating the readings together for the navigation service to consume.
You can use any combo of movement sensors you want as long as you are getting all the types of measurements required.
See the navigation service for more info on movement sensor requirements.
Tip
If you are using different hardware, the navigation setup process will be mostly the same, but you will need to substitute your robot’s components.
Before you start, make sure to create a machine in the Viam app and install viam-server on your robot.
Also, make sure to physically connect your components together to your machine’s computer and power it on.
Configure the components you need
First, configure the components of your robot.
If you are using different hardware, configure them according to the instructions for the component model that supports your hardware.
First, configure the board local to your rover.
Follow these instructions to configure your board model.
We used a jetson board, but you can use any model of board you have on hand, as the resource’s API is hardware agnostic.
Configure a board named local as shown below:
Configure digital interrupts on your board to signal precise GPIO state changes to the encoders on your rover base.
Find your board on the CONFIGURE tab in Builder mode.
Click the {} (Switch to advanced) button on the right side of your board’s card to switch to JSON attributes editing mode.
Copy and paste the following JSON into your board’s attributes field to add digital interrupts on pins 31, 29, 23, and 21:
Assign the pins as the digital interrupts you configured for the board, and wire the encoders accordingly to pins numbered31, 29, 23, and 21 on your local board.
Refer to the incremental encoder documentation for attribute information.
Wire the motors accordingly to the GPIO pins numbered35, 35, 15, 38, 40, and 33 on your local board.
Refer to the gpio motor documentation for attribute information.
Finally, configure whatever rover you have as a wheeled model of base, bringing the motion produced by these motors together on one platform:
Make sure to select each of your right and left motors as right and left, as well as set the wheel_circumference_mm and width_mm of each of the wheels the motors are attached to.
Configure the frame system for this wheeled base so that the navigation service knows where it is in relation to the movement sensor.
Switch to Frame mode on the CONFIGURE tab and select your base.
If your movement sensor is mounted on top of the rover like ours is, set Orientation’s third input field, Z, to 1 and its fourth input field, theta, to 90.
Be sure to wire the board to the encoders and motors on your base matching this configuration.
If you choose to wire your components differently, adjust your pin assignment configuration from these instructions according to your wiring.
In the JSON mode in your machine’s CONFIGURE tab, add the following JSON objects to the "components" array:
Now that you’ve got movement sensors which can give you GPS position and linear and angular velocity readings, configure a merged movement sensor to aggregate the readings from our other movement sensors into a singular sensor:
Make sure your merged movement sensor is configured to gather "position" readings from the gps movement sensor.
Configure the frame system for this movement sensor so that the navigation service knows where it is in relation to the base.
Switch to Frame mode on the CONFIGURE tab and select your movement sensor.
If your movement sensor is mounted on top of the rover like ours is, set Orientation’s third input field, Z, to 1.
Select the base as the parent frame.
In the JSON mode in your machine’s CONFIGURE tab, add the following JSON objects to the "components" array:
Add the navigation service so that your wheeled base can navigate between waypoints and avoid obstacles.
To add the navigation service to your robot, do the following:
Navigate to the CONFIGURE tab of your machine’s page in the Viam app.
Click the + icon next to your machine part in the left-hand menu and select Service.
Select the navigation type.
Enter a name or use the suggested name for your service and click Create.
Select JSON mode.
Copy and paste the following into your new service’s attributes field:
Click Save in the top right corner of the screen to save your changes.
Start navigating with the navigation service
Now that you have configured your navigation service, add waypoints to your navigation service.
You can add waypoints from the CONTROL tab or programmatically.
Control tab method
Go to the CONTROL tab of your robot in the Viam app, and open the navigation service card.
From there, ensure that Navigation mode is selected as Manual, so your robot will not begin navigation while you add waypoints.
Add waypoints
Select Waypoints on the upper-left corner menu of the navigation card.
Zoom in on your current location and click on the map to add a waypoint.
Add as many waypoints as you desire.
Hover over a waypoint in the left-hand menu and click the trash icon to delete a waypoint.
(Optional) Add obstacles
If you want your robot to avoid certain obstacles in its path while navigating, you can also add obstacles.
In the CONFIGURE tab, select the Obstacles subtab on the navigation card.
Zoom in on your current location, then hold shift and drag on the map to draw an obstacle.
Add as many obstacles as you desire.
Hover over an obstacle in the left-hand menu and click the trash icon to delete an obstacle.
Begin navigation
Toggle Navigation mode to Waypoint.
Your rover will begin navigating between waypoints.
myNav, err := navigation.FromRobot(robot, "my_nav_service")
// Create a new waypoint at the specified latitude and longitude
location = geo.NewPoint(40.76275, -73.96)
// Add your waypoint to the service's data storage
err := myNav.AddWaypoint(context.Background(), location, nil)
my_nav = NavigationClient.from_robot(robot=robot, name="my_nav_service")
# Create a new waypoint at the specified latitude and longitude
location = GeoPoint(latitude=40.76275, longitude=-73.96)
# Add your waypoint to the service's data storage
await my_nav.add_waypoint(point=location)
Begin navigation
To start navigating, set your service to MODE_WAYPOINT with the service’s API method SetMode():
myNav, err := navigation.FromRobot(robot, "my_nav_service")
// Set the Mode the service is operating in to MODE_WAYPOINT and begin navigation
mode, err := myNav.SetMode(context.Background(), Mode.MODE_WAYPOINT, nil)
my_nav = NavigationClient.from_robot(robot=robot, name="my_nav_service")
# Set the Mode the service is operating in to MODE_WAYPOINT and begin
# navigation
await my_nav.set_mode(Mode.ValueType.MODE_WAYPOINT)
Next steps: automate obstacle detection
In this tutorial, you have learned how to use Navigation to navigate across waypoints.
Now, you can make navigation even better with automated obstacle detection.
First, configure a depth camera that your robot can sense how far away it is from obstacles.
If you want the robot to be able to automatically detect obstacles in front of it, configure a Vision service segmenter.
For example, configure the Vision service model obstacles_depth to detect obstacles in front of the robot.
Then, use one of Viam’s client SDKs to automate obstacle avoidance with the navigation service like in the following Python program:
Click to view full example of automated obstacle avoidance with the Python SDK
import asyncio
import time
from viam.robot.client import RobotClient
from viam.rpc.dial import Credentials, DialOptions
from viam.components.base import Base
from viam.services.vision import VisionClient
from viam.proto.common import GeoPoint
from viam.services.navigation import NavigationClient
MANUAL_MODE = 1
DRIVE_MODE = 2
SECONDS_TO_RUN = 60 * 15
async def connect():
opts = RobotClient.Options.with_api_key(
# Replace "<API-KEY>" (including brackets) with your machine's API key
api_key='<API-KEY>',
# Replace "<API-KEY-ID>" (including brackets) with your machine's
# API key ID
api_key_id='<API-KEY-ID>'
)
return await RobotClient.at_address('<INSERT REMOTE ADDRESS>', opts)
async def nav_avoid_obstacles(
base: Base,
nav_service: NavigationClient,
obstacle_detection_service: VisionClient
):
while True:
obstacle = await obstacle_detection_service.get_object_point_clouds(
"myRealSense"
)
print("obstacle: ", obstacle)
z = obstacle[0].geometries.geometries[0].center.z
print(z)
r = await nav_service.get_mode()
if z < 1000:
if r != MANUAL_MODE:
await nav_service.set_mode(MANUAL_MODE)
else:
if r != DRIVE_MODE:
await nav_service.set_mode(DRIVE_MODE)
async def main():
robot = await connect()
# Get base component and services from the robot
base = Base.from_robot(robot, "base")
obstacle_detection_service = VisionClient.from_robot(robot, "myObsDepth")
nav_service = NavigationClient.from_robot(robot, "nav")
# Get waypoints and add a new waypoint
waypoints = await nav_service.get_waypoints()
assert (len(waypoints) == 0)
await nav_service.add_waypoint(GeoPoint(latitude=0.00006, longitude=0))
# Get waypoints again, check to see that one has been added
waypoints = await nav_service.get_waypoints()
assert (len(waypoints) == 1)
# Avoid obstacles
await nav_avoid_obstacles(base, nav_service, obstacle_detection_service)
if __name__ == '__main__':
asyncio.run(main())
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: