Using Navigation, you can queue up user-defined waypoints and your robot will move to them 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.
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.
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.
Copy and paste the following into your board’s Attributes 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 Motors and Left Motors and set the wheel circumference and width 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.
Click on Add frame on the Config tab, and, if your movement sensor is mounted on top of the rover like ours is, set Orientation’s Z to 1 and Th 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 Raw JSON mode in your robot’s Config 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.
Click on Add frame on the Config tab, and, if your movement sensor is mounted on top of the rover like ours is, set Orientation’s Z to 1.
Select the base as the parent frame.
In the Raw JSON mode in your robot’s Config 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:
On your robot’s Config page, navigate to the Services tab.
At the bottom of the page, create a service.
Choose Navigation as the type.
Then click Create Service.
Select Raw JSON mode.
Copy and paste the following into your new service’s "attributes":
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 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.
Select Obstacles on the upper-left corner menu of the navigation card.
Zoom in on your current location and click on the map to add 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.
You can alternatively use an ultrasonic sensor configured as a camera.
Attribute information for an ultrasoniccamera is the same as for a sensor.
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 robot's api key
api_key='<API-KEY>',
# Replace "<API-KEY-ID>" (including brackets) with your robot'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: