# Add a movement sensor

Add a movement sensor to your machine’s configuration so you can track position, velocity, and orientation from the Viam app and from code.

## Concepts

Unlike a generic sensor (which returns arbitrary readings), a movement sensor has a structured API with specific methods for spatial data:

- `GetPosition`: latitude, longitude, altitude.
- `GetLinearVelocity`: speed in X, Y, Z.
- `GetAngularVelocity`: rotation rate around each axis.
- `GetCompassHeading`: heading in degrees.
- `GetOrientation`: orientation as Euler angles or quaternion.

Not every movement sensor supports every method. A GPS provides position and compass heading but not angular velocity. An IMU provides orientation and angular velocity but not GPS position. Use `GetProperties` to check which methods a particular sensor supports.

### Built-in models

- [`wheeled-odometry`](https://docs.viam.com/reference/components/movement-sensor/wheeled-odometry/) — Estimates position and velocity from motor encoders on a wheeled base. No additional hardware needed.
- [`merged`](https://docs.viam.com/reference/components/movement-sensor/merged/) — Combines data from multiple movement sensors into one. For example, GPS position + IMU orientation.
- [`fake`](https://docs.viam.com/reference/components/movement-sensor/fake/) — A model for testing, with no physical hardware.

Micro-RDK:

- [`accel-adxl345`](https://docs.viam.com/reference/components/movement-sensor/micro-rdk/accel-adxl345/) — Analog Devices ADXL345 accelerometer.
- [`gyro-mpu6050`](https://docs.viam.com/reference/components/movement-sensor/micro-rdk/gyro-mpu6050/) — InvenSense MPU-6050 gyroscope and accelerometer.

### Registry modules

Viam-maintained movement-sensor modules:

| Module | Sensors supported |
| --- | --- |
| [`viam:analog-devices`](https://app.viam.com/module/viam/analog-devices) | Analog Devices ADXL345 accelerometer and similar IMUs |
| [`viam:tdk-invensense`](https://app.viam.com/module/viam/tdk-invensense) | TDK InvenSense MPU-6050 IMU |
| [`viam:gps`](https://app.viam.com/module/viam/gps) | NMEA GPS, RTK GPS (serial / PMTK), and dual-antenna RTK |
| [`viam:wit-motion`](https://app.viam.com/module/viam/wit-motion) | Wit-Motion multi-axis tilt and IMU sensors |

For movement sensors not covered above, search for `movement sensor` in the [Viam registry](https://app.viam.com/registry).

## Steps

### Option A: Wheeled odometry (no extra hardware)

If you have a wheeled base with encoders on the motors, you can get position and velocity estimates without any additional sensors.

#### 1. Prerequisites

- A [wheeled base](https://docs.viam.com/hardware/common-components/add-a-base/) with motors that have [encoders](https://docs.viam.com/hardware/common-components/add-an-encoder/) configured.

#### 2. Add the movement sensor

1. Click the **+** button.
2. Select **Blocks**.
3. Search for **wheeled-odometry**. This is the built-in model that computes position from wheel encoder data.
4. Name it (for example, `odometry`) and click **Add to machine**.

#### 3. Configure attributes

```json
{
  "base": "my-base",
  "left_motors": ["left-motor"],
  "right_motors": ["right-motor"]
}
```

| Attribute | Type | Required | Description |
| --- | --- | --- | --- |
| `base` | string | Yes | Name of the wheeled base component. |
| `left_motors` | list of strings | Yes | Left motor names (must have encoders). |
| `right_motors` | list of strings | Yes | Right motor names (must have encoders). |
| `time_interval_msec` | float | No | How often to recalculate. Default: `500`. |

### Option B: Hardware sensor (GPS, IMU)

#### 1. Add the component

1. Click the **+** button.
2. Select **Blocks**.
3. Search for the model that matches your sensor hardware. Search by sensor name or chip (for example, **NMEA GPS**, **BNO055**, **MPU6050**).
4. Name it and click **Add to machine**.
5. Configure attributes per the model’s documentation (typically I2C address or serial port).

### Option C: Merged sensor

Combine data from multiple movement sensors into one.

```json
{
  "position": "gps-sensor",
  "orientation": "imu-sensor",
  "compass_heading": "gps-sensor",
  "angular_velocity": "imu-sensor",
  "linear_velocity": "odometry"
}
```

Each field names the movement sensor to use for that type of data. This lets you build a complete spatial picture from multiple hardware sources.

### Save and test

Click **Save**, then expand the **Test** section.

- The test panel shows all available data: position, velocity, orientation, and heading.
- Methods the sensor doesn’t support will show as unavailable.

## Try it

Read position and velocity data from the movement sensor.

To get the credentials for the code below, go to your machine’s page in the Viam app, click the **CONNECT** tab, and select **API keys**. Copy the **Key** and **ID**. Then click the **CONFIGURE** tab, and click **Details**, and copy the **Remote address**. When you run the code below, you’ll see which methods your sensor supports and the current readings for each.

- [Python](https://docs.viam.com/hardware/common-components/add-a-movement-sensor/#tabset-hardwarecommon-componentsadd-a-movement-sensor-1-0)
- [Go](https://docs.viam.com/hardware/common-components/add-a-movement-sensor/#tabset-hardwarecommon-componentsadd-a-movement-sensor-1-1)

Install the Viam Python SDK in a virtual environment by following [Install the Python SDK](https://docs.viam.com/reference/sdks/python/python-venv/).

Save this as `movement_sensor_test.py`:

```python
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)

sensor = MovementSensor.from_robot(robot, "odometry")

# Check which methods this sensor supports
    properties = await sensor.get_properties()
    print(f"Supports position: {properties.position_supported}")
    print(f"Supports linear velocity: {properties.linear_velocity_supported}")
    print(f"Supports angular velocity: {properties.angular_velocity_supported}")
    print(f"Supports compass heading: {properties.compass_heading_supported}")

if properties.position_supported:
        position = await sensor.get_position()
        print(f"Position: {position}")

if properties.linear_velocity_supported:
        velocity = await sensor.get_linear_velocity()
        print(f"Linear velocity: {velocity}")

await robot.close()

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

Run it:

```bash
python movement_sensor_test.py
```

```bash
mkdir movement-sensor-test && cd movement-sensor-test
go mod init movement-sensor-test
go get go.viam.com/rdk
```

Save this as `main.go`:

```go
package main

import (
    "context"
    "fmt"

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

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

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

sensor, err := movementsensor.FromProvider(robot, "odometry")
    if err != nil {
        logger.Fatal(err)
    }

// Check which methods this sensor supports
    properties, err := sensor.Properties(ctx, nil)
    if err != nil {
        logger.Fatal(err)
    }
    fmt.Printf("Supports position: %v\n", properties.PositionSupported)
    fmt.Printf("Supports linear velocity: %v\n", properties.LinearVelocitySupported)
    fmt.Printf("Supports angular velocity: %v\n", properties.AngularVelocitySupported)
    fmt.Printf("Supports compass heading: %v\n", properties.CompassHeadingSupported)

if properties.PositionSupported {
        pos, alt, err := sensor.Position(ctx, nil)
        if err != nil {
            logger.Fatal(err)
        }
        fmt.Printf("Position: %v, altitude: %.2f\n", pos, alt)
    }

if properties.LinearVelocitySupported {
        vel, err := sensor.LinearVelocity(ctx, nil)
        if err != nil {
            logger.Fatal(err)
        }
        fmt.Printf("Linear velocity: %v\n", vel)
    }
}
```

Run it:

```bash
go run main.go
```

## Troubleshooting

##### Odometry position drifts over time

- This is inherent to wheel odometry. Small errors accumulate. For better accuracy, add a GPS or IMU and use the `merged` model.
- Check that `ticks_per_rotation` is accurate on your encoders.
- Verify `wheel_circumference_mm` and `width_mm` on your base.

##### GPS shows no fix

- GPS modules need a clear view of the sky. They don’t work indoors.
- Initial fix can take 30-60 seconds (cold start) or longer in urban areas.

##### Some methods return errors

- Not all movement sensors support all methods. This is expected. Call `GetProperties` to see which methods your sensor supports.

If your movement sensor is not working as expected, follow these steps:

1. Check your machine logs on the **LOGS** tab to check for errors.
2. Review this movement sensor model’s documentation to ensure you have configured all required attributes.
3. Click on the **TEST** panel on the **CONFIGURE** or **CONTROL** tab and test if you can use the movement sensor there.

If none of these steps work, reach out to us on the [Community Discord](https://discord.gg/viam) and we will be happy to help.
