> 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.

# Setting up GNSS / RTK on Windows

This guide explains how to connect a **GNSS receiver** to a **Windows** computer and read its data in a ZED SDK application. If you are new to GNSS, start with the [GNSS Overview](/docs/development/zed-sdk/modules/global-localization/gnss).

`gpsd`, used in the [Linux guide](/docs/development/zed-sdk/modules/global-localization/gnss/setting-up-gnss-rtk), does not run natively on Windows. You can either read the NMEA sentences directly from the serial port of the receiver, as described below, or [run gpsd in WSL 2](#alternative-running-gpsd-in-wsl-2) to reuse the Linux workflow.

## Connecting the receiver

Connect the antenna and the receiver, then open the **Device Manager** and expand **Ports (COM & LPT)**. The receiver appears as a **COM port**, for example `COM3`:

* Receivers with a native USB interface usually use the generic *USB Serial Device* driver included in Windows 10 and Windows 11.
* Receivers connected through a USB-to-serial adapter may need the driver of the adapter manufacturer.
* Some receiver vendors provide a driver that exposes the receiver as a Windows location sensor instead of a COM port. To read the data from your application, install the *virtual COM port* driver of the receiver.

For receivers connected through a serial adapter, check the **baud rate** configured on the receiver: your application must use the same value.

### Checking the data

You can check that the receiver streams NMEA sentences with a serial terminal, or with the following Windows PowerShell commands:

```powershell
$port = New-Object System.IO.Ports.SerialPort COM3,115200,None,8,One
$port.Open()
while ($true) { $port.ReadLine() }
```

Press `Ctrl+C` to stop, then release the port with `$port.Close()`.

> **Note**
>
> Replace `COM3` and `115200` with the COM port and the baud rate of your receiver. On Windows, a COM port can be opened by **only one application at a time**: close the terminal before starting your application.

The output should be similar to the one shown in the [Linux guide](/docs/development/zed-sdk/modules/global-localization/gnss/setting-up-gnss-rtk#checking-the-receiver). The position fields stay empty until the receiver gets a fix.

## Enabling RTK

To get [RTK](/docs/development/zed-sdk/modules/global-localization/gnss#rtk) accuracy, an **NTRIP client** must receive the corrections from an [NTRIP caster](/docs/development/zed-sdk/modules/global-localization/gnss#ntrip) and send them to the receiver. On Windows, you can use:

* the configuration software of your receiver vendor, which often includes an NTRIP client,
* a standalone NTRIP client, for example the open-source [RTKLIB](https://www.rtklib.com/) tools.

Configure it with the [NTRIP connection information](/docs/development/zed-sdk/modules/global-localization/gnss#ntrip) of your correction provider.

Since a COM port cannot be shared, the NTRIP client and your application cannot open the same port. Depending on your receiver and your NTRIP client, you can:

* send the corrections and read the NMEA sentences on **two different ports** of the receiver, for example the USB port and a UART through a serial adapter,
* use an NTRIP client that forwards the data of the receiver to a **TCP port**, and read the NMEA sentences from this TCP port in your application.

To benefit from the RTK accuracy, wait until the receiver reports an **RTK fixed** solution (`GGA` fix quality `4`) before starting your application.

## Reading GNSS data in your application

The example below reads the NMEA sentences of the receiver with [pyserial](https://pypi.org/project/pyserial/), decodes them with [pynmea2](https://pypi.org/project/pynmea2/), and converts them to `sl.GNSSData`:

* the `GGA` sentence provides the position and the fix quality,
* the `GST` sentence provides the standard deviation of the position, used to fill the covariance.

Install the dependencies with:

```bash
pip install pyserial pynmea2
```

**`Python`**

```python Python
import serial
import pynmea2
import pyzed.sl as sl

# GGA fix quality to sl.GNSS_STATUS
GGA_STATUS = {1: sl.GNSS_STATUS.SINGLE, 2: sl.GNSS_STATUS.DGNSS,
              4: sl.GNSS_STATUS.RTK_FIX, 5: sl.GNSS_STATUS.RTK_FLOAT}

class NMEAReader:
    def __init__(self, port, baudrate=115200):
        self.serial = serial.Serial(port, baudrate, timeout=1)
        self.gga = None  # last GGA sentence and its reception time
        self.gst = None  # last GST sentence

    def read(self):
        """Read one NMEA sentence. Return a sl.GNSSData when a complete measurement is available, None otherwise."""
        line = self.serial.readline().decode("ascii", errors="ignore").strip()
        if not line.startswith("$"):
            return None
        try:
            msg = pynmea2.parse(line)
        except pynmea2.ParseError:
            return None

        if msg.sentence_type == "GGA":
            self.gga = (msg, sl.get_current_timestamp())
        elif msg.sentence_type == "GST":
            self.gst = msg
        else:
            return None

        # Wait for the GGA and GST sentences of the same measurement
        if self.gga is None or self.gst is None or self.gga[0].timestamp != self.gst.timestamp:
            return None
        gga, reception_ts = self.gga
        gst = self.gst
        self.gga = self.gst = None

        if gga.gps_qual not in GGA_STATUS or gga.altitude is None:
            return None  # no valid fix
        if None in (gst.std_dev_latitude, gst.std_dev_longitude, gst.std_dev_altitude):
            return None  # no uncertainty estimate

        gnss_data = sl.GNSSData()
        gnss_data.set_coordinates(gga.latitude, gga.longitude, gga.altitude, False)
        gnss_data.latitude_std = gst.std_dev_latitude
        gnss_data.longitude_std = gst.std_dev_longitude
        gnss_data.altitude_std = gst.std_dev_altitude
        # Covariance in the ENU frame: East (longitude), North (latitude), Up (altitude)
        gnss_data.position_covariances = [gst.std_dev_longitude ** 2, 0.0, 0.0,
                                          0.0, gst.std_dev_latitude ** 2, 0.0,
                                          0.0, 0.0, gst.std_dev_altitude ** 2]
        gnss_data.ts = reception_ts
        gnss_data.gnss_mode = sl.GNSS_MODE.FIX_3D.value
        gnss_data.gnss_status = GGA_STATUS[gga.gps_qual].value
        return gnss_data
```

Call `read()` in a dedicated thread, and ingest each returned `sl.GNSSData` into the Fusion module with `ingest_gnss_data`, as shown in the [Global Localization overview](/docs/development/zed-sdk/modules/global-localization#retrieving-fused-data).

> **Note**
>
> Not all receivers output the `GST` sentence by default. Enable it with the configuration software of your receiver. If your receiver cannot output it, use its proprietary messages to get the position uncertainty: do not ingest a constant covariance.

### Timestamps

The example timestamps each measurement with its **reception time**, given by `sl.get_current_timestamp()`. This keeps GNSS data and camera images on the same time base, even if the Windows clock is not precisely synchronized with UTC, at the cost of the latency of the receiver and of the serial link, typically a few tens of milliseconds.

The time contained in the NMEA sentences can be used instead only if the Windows clock is accurately synchronized with UTC. With its default configuration, the Windows time service does not guarantee this level of accuracy. See [Using GNSS Data in Your Application](/docs/development/zed-sdk/modules/global-localization/gnss/using-gnss-data#timestamps) for more details.

## Alternative: running gpsd in WSL 2

If you prefer to use `gpsd`, including its NTRIP client, you can run it in **WSL 2** (Windows Subsystem for Linux) and attach the USB receiver to it with [usbipd-win](https://github.com/dorssel/usbipd-win):

1. Install `usbipd-win` from an administrator PowerShell:

   ```powershell
   winget install --interactive --exact dorssel.usbipd-win
   ```

2. List the USB devices and note the `BUSID` of the receiver:

   ```powershell
   usbipd list
   ```

3. Share the device, then attach it to WSL. The `bind` command requires administrator rights and is needed only once per device:

   ```powershell
   usbipd bind --busid <BUSID>
   usbipd attach --wsl --busid <BUSID>
   ```

4. In WSL, check that the receiver is detected as `/dev/ttyACM0` or `/dev/ttyUSB0`, then install and run `gpsd` as described in the [Linux guide](/docs/development/zed-sdk/modules/global-localization/gnss/setting-up-gnss-rtk). Keep the `-G` option of the `gpsd` commands, which allows connections from Windows.

Your Windows application can then read the data from `gpsd` on `localhost`, port `2947`, for example with the `gpsdclient` Python library shown in [Using GNSS Data in Your Application](/docs/development/zed-sdk/modules/global-localization/gnss/using-gnss-data#python).

> **Note**
>
> The device must be attached again with `usbipd attach` every time it is unplugged or the computer restarts, and `gpsd` must be started again in WSL. As above, timestamp the data with its reception time unless the Windows clock is synchronized with UTC.