Set up GPS for navigation

Before you can use the navigation service, you need a movement sensor that provides GPS position and compass heading. This guide walks you through choosing a GPS module, configuring it, and verifying the data is accurate enough for navigation.

Choose a GPS module

Browse GPS-capable movement sensor models in the Viam registry. Search for your GPS hardware by manufacturer or chip name.

GPS accuracy and what it means for navigation

Standard GPS is accurate to approximately 3 meters under open sky. In environments with trees, buildings, or uneven terrain, accuracy degrades due to signal interference.

The navigation service’s plan_deviation_m parameter controls how far the robot can drift from its planned path before replanning. If your GPS error is 3 meters and plan_deviation_m is 2.6 meters (the default), the robot will replan frequently because normal GPS jitter exceeds the threshold.

GPS typeTypical accuracyGood plan_deviation_m range
Standard GPS2-5 meters5-10 meters
SBAS-enhanced GPS1-3 meters3-5 meters
RTK GPS1-10 centimeters0.5-2 meters

If your application requires sub-meter accuracy (following a precise path, working near obstacles), look for RTK GPS modules in the registry.

Configure the movement sensor

  1. Open your machine in the Viam app.
  2. Click the + button and select Configuration block.
  3. Search for the model that matches your GPS hardware.
  4. Name it (for example, my-gps) and click Create.
  5. Configure the attributes as required by your module (serial port, baud rate, I2C address, or network settings).
  6. Click Save.

Verify GPS position

Before configuring navigation, confirm the movement sensor reports accurate GPS data.

  1. Find your movement sensor in the configuration view.
  2. Expand the TEST section.
  3. Check that position shows a latitude and longitude close to your actual location.
  4. Check that compass heading updates when you rotate the robot. 0 degrees is north, 90 is east, 180 is south, 270 is west.

If the position is significantly wrong or the compass heading doesn’t change when you rotate the robot, check your wiring and module configuration before proceeding.

Compass interference

The compass (magnetometer) in many GPS/IMU modules is sensitive to magnetic interference from motors, metal structures, and power wiring. Common symptoms:

  • Heading doesn’t change when the robot rotates.
  • Heading jumps erratically.
  • Heading is consistently offset by a fixed amount.

To reduce interference:

  • Mount the GPS module as far from motors and power wiring as practical.
  • Run magnetometer calibration if your module supports it (check the module’s documentation in the registry).
  • Consider the merged movement sensor model, which combines data from multiple sensors. For example, use GPS for position and a separately mounted IMU for heading.

Verify with code

Read position and heading programmatically to confirm the data is available to your application code.

import asyncio
from viam.robot.client import RobotClient
from viam.components.movement_sensor import MovementSensor


async def main():
    opts = RobotClient.Options.with_api_key(
        api_key="YOUR-API-KEY",
        api_key_id="YOUR-API-KEY-ID"
    )
    robot = await RobotClient.at_address("YOUR-MACHINE-ADDRESS", opts)

    gps = MovementSensor.from_robot(robot, "my-gps")

    position, altitude = await gps.get_position()
    heading = await gps.get_compass_heading()
    properties = await gps.get_properties()

    print(f"Latitude:  {position.latitude}")
    print(f"Longitude: {position.longitude}")
    print(f"Altitude:  {altitude}m")
    print(f"Heading:   {heading} degrees")
    print(f"Supports position: {properties.position_supported}")
    print(f"Supports heading:  {properties.compass_heading_supported}")

    await robot.close()

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

import (
    "context"
    "fmt"

    "go.viam.com/rdk/components/movementsensor"
    "go.viam.com/rdk/logging"
    "go.viam.com/rdk/robot/client"
    "go.viam.com/rdk/utils"
)

func main() {
    ctx := context.Background()
    logger := logging.NewLogger("gps-test")

    robot, err := client.New(ctx, "YOUR-MACHINE-ADDRESS", logger,
        client.WithCredentials(utils.Credentials{
            Type:    utils.CredentialsTypeAPIKey,
            Payload: "YOUR-API-KEY",
        }),
        client.WithAPIKeyID("YOUR-API-KEY-ID"),
    )
    if err != nil {
        logger.Fatal(err)
    }
    defer robot.Close(ctx)

    gps, err := movementsensor.FromProvider(robot, "my-gps")
    if err != nil {
        logger.Fatal(err)
    }

    pos, alt, err := gps.Position(ctx, nil)
    if err != nil {
        logger.Fatal(err)
    }

    heading, err := gps.CompassHeading(ctx, nil)
    if err != nil {
        logger.Fatal(err)
    }

    props, err := gps.Properties(ctx, nil)
    if err != nil {
        logger.Fatal(err)
    }

    fmt.Printf("Latitude:  %f\n", pos.Lat())
    fmt.Printf("Longitude: %f\n", pos.Lng())
    fmt.Printf("Altitude:  %fm\n", alt)
    fmt.Printf("Heading:   %f degrees\n", heading)
    fmt.Printf("Supports position: %v\n", props.PositionSupported)
    fmt.Printf("Supports heading:  %v\n", props.CompassHeadingSupported)
}

What’s next