> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.stereolabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.stereolabs.com/_mcp/server.

# Using GNSS with ZED Box Orin

The **GNSS module** is an optional component of the **ZED Box Orin NX**, which can greatly enhance the precision of positioning data when used with **Real-Time Kinematic** (RTK) technology. With RTK, the GNSS module can achieve **centimeter-level accuracy**, significantly improving upon the typical few-meter accuracy of standard GNSS modules. This makes the GNSS module ideal for a variety of applications that require highly precise positioning information, such as autonomous vehicles, surveying, and precision agriculture.

## Installation

### Antenna

Your GNSS needs a male antenna to get a signal. It must be plugged into port 5 on the side of the ZED Box.

You can use the antenna we sell in our store, or any other GNSS antenna. However, the Wi-Fi antenna that also comes with the ZED Box will not work.

### gpsd

`gpsd` is the simplest way to retrieve the GNSS data on your ZED Box.

Please follow [this guide](/docs/development/zed-sdk/modules/global-localization/setting-up-gnss-rtk) to install the correct version of gpsd on your device.

You can test that `gpsd` is working properly with the tool installed with `gpsd`:

```bash
# GNSS and satellite data in graphical interface
xgps
```

![xgps screenshot](/_fern-files/stereolabs.docs.buildwithfern.com/b5ff2e3425587e96b0bad2d0cc59514f2d3d3e9dfd8a9072f3c887fddaf02583/docs/products/embedded/zed-box-orin/images/xgps.webp)

To verify that the GNSS module is properly detected, you can also use the following command:

```bash
ls /dev/tty*
```

You should see a file named `/dev/ttyACM0` or `/dev/ttyUSB0` containing the GNSS raw data. The streamed data should look as such:

```bash
cat /dev/ttyACM0
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,1*33
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,2*30
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,3*31
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,4*36
$GPGSV,1,1,00,0*65
$GLGSV,1,1,00,0*79
$GAGSV,1,1,00,0*74
$GBGSV,2,1,05,10,,,29,11,,,28,12,,,28,13,,,29,3*72
$GBGSV,2,2,05,14,,,29,3*7F
$GNGLL,,,,,082018.00,V,N*57
$GNTXT,01,01,01,NMEA unknown msg*46
$GNTXT,01,01,01,NMEA unknown msg*46
$GNRMC,082019.00,V,,,,,,,230623,,,N,V*1D
$GNVTG,,,,,,,,,N*2E
$GNGGA,082019.00,,,,,0,00,99.99,,,,,,*7A
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,1*33
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,2*30
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,3*31
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,4*36
```

## Enabling RTK on your GNSS module

### Using NTRIP

`gpsd` can act as an NTRIP client in order to retrieve RTK corrections from an RTK base station.

We will go through how to set up the NTRIP connection in order to get RTK *centimeter-level accuracy*.

For this you will need:

* **url**: the NTRIP URL address of the base station
* **port**: the NTRIP port of the base station
* **mountpoint:** the mountpoint you choose to use (**Note**: for good performance, the base station is recommended to be less than *25km* away from the rover)
* **username**: the username used for the NTRIP connection (optional)
* **password**: the password used for the NTRIP connection (optional)

You can now run `gpsd` as an NTRIP client with:

```bash
sudo pkill gpsd # Kill gpsd if it is already running
gpsd -nG ntrip://<username>:<password>@<url>:<port>/<mountpoint> -s 115200 /dev/ttyACM0
```

You can now run `xgps` and wait for the GNSS to get an **RTK fix**. (The *ECEF pAcc* gives the 3D position accuracy; with an RTK FIX status this value should be of about **a few centimeters**)

### Set RTK configuration at boot

`gpsd` runs as a cronjob set to run at the ZED Box's startup. Update the cron job with the following command, so that `gpsd` will connect to the base station at every boot:

```bash
crontab -e
```

And replace the gpsd line already present with the following:

```bash
@reboot sleep 10 && /usr/local/sbin/gpsd -nG ntrip://<username>:<password>@<url>:<port>/<mountpoint> -s 115200 /dev/ttyACM0
```

## PPS signal

Besides the position data, the GNSS module provides a **PPS** (*Pulse Per Second*) signal: a hardware pulse emitted at the beginning of every UTC second. On the **ZED Box Orin**, this signal is wired directly to the `PA.06` GPIO of the NVIDIA® Jetson™ module and is handled by the standard Linux `pps-gpio` driver, so the operating system can use it as a high precision time reference.

The time contained in the NMEA sentences is affected by the latency and the jitter of the serial link, which limits its accuracy to a few milliseconds. The PPS edge, instead, is aligned to UTC with an accuracy in the order of tens of nanoseconds, making it the recommended reference whenever your application requires accurate timestamps, for example to align camera frames with GNSS positions.

> **Note**
>
> The PPS signal is generated only when the GNSS receiver has acquired a valid fix. If no antenna is connected, or if the sky visibility is poor, no pulse is produced.

### Checking the PPS device

PPS support is enabled by default on the ZED Box Orin: no device tree modification and no additional driver are required. The kernel exposes the signal as a PPS character device:

```bash
ls /dev/pps*
```

You should get `/dev/pps0`.

A quick check that does not require any additional package is to read the timestamp of the last pulse:

```bash
cat /sys/class/pps/pps0/assert
```

```text
1758012345.000000123#42
```

The value is the system time of the last rising edge, followed by the number of pulses received since boot. Reading it twice one second apart should show the counter increasing.

To monitor the pulses continuously, install the `pps-tools` package:

```bash
sudo apt install pps-tools
```

Then monitor the device:

```bash
sudo ppstest /dev/pps0
```

The output should look as such:

```bash
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1758012345.000000123, sequence: 42 - clear  0.000000000, sequence: 0
source 0 - assert 1758012346.000000118, sequence: 43 - clear  0.000000000, sequence: 0
```

The `assert` value is the system time at which the kernel detected the rising edge of the pulse, and `sequence` increments once per second. A fractional part close to zero means that the system clock is already well aligned to UTC.

#### Device tree configuration

The PPS input is declared in the device tree shipped with the ZED Box Orin:

```c
pps {
    gpios = <&gpio TEGRA234_MAIN_GPIO(A, 6) GPIO_ACTIVE_HIGH>;
    compatible = "pps-gpio";
    status = "okay";
};
```

* `compatible = "pps-gpio"` binds the pin to the Linux `pps-gpio` client driver, which is what creates the `/dev/pps0` device.
* `TEGRA234_MAIN_GPIO(A, 6)` is pin 6 of port A of the Jetson main GPIO controller, that is `PA.06`.
* `GPIO_ACTIVE_HIGH`, with no `assert-falling-edge` property, means that the *assert* event reported by the driver is the **rising edge** of the pulse.

If you build a custom kernel or a custom device tree, for example when flashing a [Real-Time Kernel](/docs/products/embedded/zed-box-orin/real-time-kernel), make sure that this node is preserved, otherwise `/dev/pps0` will not be created.

### Synchronizing the system clock with PPS

`gpsd` can read the PPS device together with the serial port of the GNSS module. Add `/dev/pps0` to the list of devices passed to `gpsd`:

```bash
sudo pkill gpsd # Kill gpsd if it is already running
sudo gpsd -n -s 115200 /dev/ttyACM0 /dev/pps0
```

If you are also using **RTK corrections** through NTRIP, the PPS device is simply appended to the command described [above](#using-ntrip):

```bash
sudo gpsd -nG ntrip://<username>:<password>@<url>:<port>/<mountpoint> -s 115200 /dev/ttyACM0 /dev/pps0
```

You can check that `gpsd` is actually receiving the pulses with:

```bash
ntpshmmon
```

Samples labelled `NTP0` come from the NMEA time, while the ones labelled `NTP1` come from the PPS signal.

`gpsd` publishes these two time sources in shared memory, where an NTP daemon such as [Chrony](https://chrony-project.org/) can read them to discipline the system clock. Install it with:

```bash
sudo apt install chrony
```

And add the two reference clocks to `/etc/chrony/chrony.conf`:

```bash
# Time from the NMEA sentences, coarse but always available
refclock SHM 0 refid NMEA offset 0.0 delay 0.2 poll 3
# Time from the PPS signal, high precision
refclock SHM 1 refid PPS precision 1e-7 poll 3 prefer
```

Restart the service and verify that the reference clocks are used:

```bash
sudo systemctl restart chrony
chronyc sources -v
```

The `PPS` source should be marked as the selected one (`*`) after a few tens of seconds.

> **Note**
>
> Shared memory segments 0 and 1 are readable by the `root` user only. If `chronyd` does not run as `root` on your system, configure `gpsd` and Chrony to use segments 2 and 3 instead, which are created world readable.

To make the configuration persistent, update the `gpsd` line of the cron job described in [Set RTK configuration at boot](#set-rtk-configuration-at-boot) so that it also includes `/dev/pps0`.

### Using PPS for camera and sensor synchronization

The PPS signal is not routed to an external connector of the ZED Box Orin, so it cannot be used to trigger external hardware directly. Synchronization is instead achieved through the **system clock**: once the clock is disciplined by PPS, every timestamp generated on the device shares the same UTC aligned time base with a sub-microsecond accuracy.

This matters because the ZED SDK [Fusion](/docs/development/zed-sdk/modules/global-localization) module matches camera data and GNSS data by timestamp. A drifting clock produces timestamps that do not match the moment the data was actually acquired, which degrades the fusion result or causes the data to be dropped. See [Data Synchronization](/docs/development/zed-sdk/modules/global-localization/data-synchronization) for more details on how the matching is performed.

Disciplining the system clock with PPS is therefore recommended when:

* you fuse camera and GNSS data with the **Global Localization** module,
* you record datasets that must be replayed or compared with data coming from other machines,
* you run a **multi-camera** setup where several ZED Boxes must share a common time base.

## Using GNSS in your applications

### Python

To access GNSS data in Python, you can use the [gpsdclient](https://github.com/tfeldmann/gpsdclient) library. This library provides a Python interface to the `gpsd` daemon, allowing you to easily retrieve GNSS data in your Python scripts. With `gpsdclient`, you can access a range of GNSS data, including latitude, longitude, altitude, speed, and more. This makes it a powerful tool for building GNSS-enabled applications in Python.

You can install `gpsdclient` with:

```bash
pip install gpsdclient
```

Here is an example of a simple script using `gpsdclient`:

**`Python`**

```python Python
from gpsdclient import GPSDClient

# get your data as json strings:
with GPSDClient(host="127.0.0.1") as client:
    for result in client.json_stream():
        print(result)

# or as python dicts (optionally convert time information to `datetime` objects)
with GPSDClient() as client:
    for result in client.dict_stream(convert_datetime=True, filter=["TPV"]):
        print("Latitude: %s" % result.get("lat", "n/a"))
        print("Longitude: %s" % result.get("lon", "n/a"))

# you can optionally filter by report class
with GPSDClient() as client:
    for result in client.dict_stream(filter=["TPV", "SKY"]):
        print(result)
```

You can find a full code example of how to use the library in our [Geotracking samples on GitHub](https://github.com/stereolabs/zed-sdk/tree/master/global%20localization/live).

### C++

To access GNSS data in C++, you can use the [libgpsmm](https://gpsd.io/libgpsmm.html) library.

You can install `libgpsmm` with:

```bash
sudo apt install libgps-dev
```

Here is an example of a simple script using `libgpsmm`:

**`C++`**

```cpp C++
#include <libgpsmm.h>
#include <iostream>

int main() {
    gpsmm gps_data("localhost", DEFAULT_GPSD_PORT);

    if (gps_data.stream(WATCH_ENABLE | WATCH_JSON) == nullptr) {
        std::cerr << "Failed to open GPS connection." << std::endl;
        return 1;
    }

    while (true) {
        if (gps_data.waiting(500)) {
            struct gps_data_t* data = gps_data.read();
            if (data == nullptr) {
                std::cerr << "Error while reading GPS data." << std::endl;
            } else {
                std::cout << "Latitude: " << data->fix.latitude << ", Longitude: " << data->fix.longitude << std::endl;
            }
        }
    }

    gps_data.stream(WATCH_DISABLE);
    gps_data.close();

    return 0;
}
```

You can find a full code example of how to use the library in our [Geotracking samples on GitHub](https://github.com/stereolabs/zed-sdk/tree/master/global%20localization/live).

### Install gpsd from scratch

If for some reason `gpsd` is not installed on your ZED Box, you can follow the following guide to install and use `gpsd`:

[Using GNSS / RTK](/docs/development/zed-sdk/modules/global-localization/setting-up-gnss-rtk)