Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Changelog for package ntrip_client
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1.5.0 (2026-06-08)
------------------
* Fixes to make rtk2go work: configurable and lower default rate (1 Hz) for querying caster
* Support persistent reconnect attempts to cope with extended caster outages
* Change the user agent name to one that's not blocked by rtk2go
* Allow setting the default user agent name
* Python3 support
* Change NMEA_DEFAULT_MAX_LENGTH to 150 per ponderbotics

1.4.1 (2025-04-15)
------------------
* Fix log message (`#61 <https://github.com/LORD-MicroStrain/ntrip_client/issues/61>`_)
Expand Down
126 changes: 93 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,70 @@

## Description

ROS node that will communicate with an NTRP caster to receive RTCM connections and publish them on a ROS topic. Also works with virtual NTRIP servers by subscribing to NMEA
messages and sending them to the NTRIP server

#### Important Branches
There are two important branches that you may want to checkout:

* [ros](https://github.com/LORD-MicroStrain/ntrip_client/tree/ros) -- Contains ROS1 implementation for this node.
* [ros2](https://github.com/LORD-MicroStrain/ntrip_client/tree/ros2) -- Contains ROS2 implementation for this node.
ROS node that will communicate with an NTRIP caster to receive RTCM corrections and publish them on a ROS topic. Also works with virtual/relayed NTRIP servers by subscribing to NMEA
messages and sending them to the NTRIP server.

## Build Instructions

#### Building from source
1. Install ROS2 and create a workspace: [Installing and Configuring Your ROS2 Environment](https://docs.ros.org/en/foxy/Tutorials/Configuring-ROS2-Environment.html)
It is assumed you have already installed ROS2. This package is not tied to any particular release of ROS2.
Build this package from source as follows:

2. Move the entire ntrip_client folder to the your_workspace/src directory.
1. Clone this repo into the your_workspace/src directory and check out the ros2 branch.

3. Install rosdeps for this package: `rosdep install --from-paths ~/your_workspace/src --ignore-src -r -y`
2. Install rosdeps for this package: `rosdep install --from-paths ~/your_workspace/src --ignore-src -r -y`

4. Build your workspace:
3. Build your workspace:
```bash
cd ~/your_workspace
colcon build
source ~/your_workspace/install/setup.bash
```
The source command may need to be run in each terminal prior to launching a ROS node.

#### Connect to a NTRIP caster or server
## Connect to a NTRIP caster or server

This is useful if you have access to an NTRIP caster or server that you want to connect to over the internet.

```bash
ros2 launch ntrip_client ntrip_client_launch.py
```

Optional launch parameters:
- **host**: Hostname or IP address of the NTRIP server to connect to.
-- or override defaults from cmd line (example for rtk2go) --

```bash
ros2 launch ntrip_client ntrip_client_launch.py host:=rtk2go.com mountpoint:=MyRealMtPt authenticate:=true username:=myrealemail@provider.com password:=none send_nmea:=false
```

Launch arguments (all overridable as `name:=value`; defaults shown):

Connection:
- **host**: Hostname or IP address of the NTRIP server to connect to and receive corrections from
- **port**: Port to connect to on the server. Default: `2101`
- **mountpoint**: Mountpoint to connect to on the NTRIP server.
- **ntrip_version**: Value to use for the `Ntrip-Version` header in the initial HTTP request to the caster.
- **authenticate**: Whether to authenticate with the server, or send an unauthenticated request. If set to true, `username`, and `password` must be supplied.
- **username**: Username to use when authenticating with the NTRIP server. Only used if `authenticate` is true
- **password**: Password to use when authenticating with the NTRIP server. Only used if `authenticate` is true
- **ssl**: Whether to connect with SSL. cert, key, and ca_cert options will only take effect if this is true
- **cert**: If the NTRIP caster is configured to use cert based authentication, you can use this option to specify the client certificate
- **key**: If the NTRIP caster is configured to use cert based authentication, you can use this option to specify the private key
- **ca_cert**: If the NTRIP caster uses self signed certs, or you need to use a different CA chain, this option can be used to specify a CA file
- **rtcm_message_packege**: Changes the type of ROS RTCM message published by this node. Defaults to `mavros_msgs`, but also supports `rtcm_msgs`
- **mountpoint**: Mountpoint to connect to on the NTRIP server
- **ntrip_version**: Value sent in the `Ntrip-Version` request header. Default: `None` (header omitted; NTRIP rev1/ICY request)
- **user_agent**: HTTP `User-Agent` sent to the caster. **Must start with `NTRIP `.** Default: `NTRIP ponderbotics_ntrip_client`. Do not use the stock `NTRIP ntrip_client_ros` — rtk2go blocks it (see [rtk2go notes](#rtk2go-and-reconnect-behavior)).
- **send_nmea**: Whether to forward NMEA from the `nmea` topic up to the caster. Needed for virtual/relayed (VRS) mountpoints. For a plain base station (e.g. rtk2go's fixed mountpoints) set `send_nmea:=false` — the node then skips the `nmea` subscription entirely and never uploads your position. Default: `true`.

Authentication:
- **authenticate**: Whether to authenticate with the server, or send an unauthenticated request. If `true`, `username` and `password` must be supplied.
- **username**: Username used when authenticating. For rtk2go this is your registered email. Only used if `authenticate` is true.
- **password**: Password used when authenticating. For rtk2go use `none`. Default: `none`. Only used if `authenticate` is true.

Rate & reconnect:
- **ntrip_server_hz**: Frequency (Hz) to communicate with the NTRIP server. Some servers, like rtk2go.com, will ban you if you request data too frequently — for rtk2go use `ntrip_server_hz:=1`. Default: `10`.
- **reconnect_attempt_wait_max_seconds**: Ceiling for the exponential reconnect backoff. Reconnects are persistent (the node never gives up); raise this (e.g. `:=600`) to reduce footprint during long caster outages. Default: `120` (2-minute steady-state cadence). The starting wait (`reconnect_attempt_wait_seconds`, 10s) and `rtcm_timeout_seconds` (10s) are set in the launch file's parameter block.

SSL (only used if `ssl:=true`):
- **ssl**: Connect to the caster over TLS. Default: `False`
- **cert** / **key**: Client certificate and key for cert-based auth. Default: `None`
- **ca_cert**: CA chain to use for self-signed casters. Default: `None`

#### Connect to a NTRIP "device"
Output / namespacing:
- **rtcm_message_package**: ROS message package used for published RTCM. `rtcm_msgs` (publishes `rtcm_msgs/msg/Message`) or `mavros_msgs` (publishes `mavros_msgs/msg/RTCM`). Default: `rtcm_msgs`.
- **namespace** / **group** / **node_name**: Namespace, optional sub-group, and node name.
- **debug**: Enable debug-level logging. Default: `false`

## Connect to a NTRIP "device"

This is useful if you do not have an internet connection, but do have an NTRIP "device" that you want to receive connections from, such as the [MicroStrain 3DM-RTK](https://www.microstrain.com/inertial-sensors/3dm-rtk).

Expand All @@ -63,14 +78,59 @@ Optional launch parameters:
- **baudrate**: Baudrate to connect to the serial port at. Default 115200
- **rtcm_message_packege**: Changes the type of ROS RTCM message published by this node. Defaults to `mavros_msgs`, but also supports `rtcm_msgs`

#### Topics

This node currently only has three topics of interest:
## Topics

* **/rtcm**: This node will publish the RTCM corrections received from the server to this topic as [RTCM messages](http://docs.ros.org/en/noetic/api/mavros_msgs/html/msg/RTCM.html). These messages can be consumed by nodes such as the [microstrain_inertial_driver](https://github.com/LORD-MicroStrain/microstrain_inertial)
* **/rtcm** (publish): RTCM corrections received from the server. Message type depends on `rtcm_message_package` — `rtcm_msgs/msg/Message` by default, or `mavros_msgs/msg/RTCM`. Consumed by GNSS drivers (e.g. ublox_gps, [microstrain_inertial_driver](https://github.com/LORD-MicroStrain/microstrain_inertial)).
* **NOTE**: The type of message can be switched between [`mavros_msgs/RTCM`](https://github.com/mavlink/mavros/blob/ros2/mavros_msgs/msg/RTCM.msg) and [`rtcm_msgs/Message`](https://github.com/tilk/rtcm_msgs/blob/master/msg/Message.msg) using the `rtcm_message_package` parameter
* **/nmea**: This node will subscribe on this topic and receive [NMEA sentence messages](http://docs.ros.org/en/api/nmea_msgs/html/msg/Sentence.html) which it will forward to the NTRIP server. This is always needed when using a virtual NTRIP server or an NTRIP device
* **/fix**: This serves the same exact purpose as `/nmea`, but facilitates receiving global position that is not in NMEA format
* **/nmea** (subscribe): [NMEA sentence messages](http://docs.ros.org/en/api/nmea_msgs/html/msg/Sentence.html) forwarded to the NTRIP server. Needed for virtual/relayed (VRS) mountpoints. The node subscribes to the topic `/nmea`; remap it (e.g. in the launch file's `remappings`) to your NMEA source if it differs. Set `send_nmea:=false` to disable forwarding entirely (the subscription is then not created). Note: there is no `nmea_topic` launch argument — passing one has no effect.
* **/fix**: This serves the same exact purpose as `/nmea`, but facilitates receiving global position that is not in NMEA format by monitoring NavSatFix mesages on the /fix topic.
* **/ntrip_server_hz** (publish): A `std_msgs/String` confirmation published each communication cycle, to help verify compliance with caster usage policies.

## rtk2go and reconnect behavior

[rtk2go.com](http://rtk2go.com) runs the SNIP caster software and enforces usage policies that this release complies with:

* **User-Agent blocking.** rtk2go maintains a block list of client signatures. The stock LORD-MicroStrain `User-Agent: NTRIP ntrip_client_ros` is blocked. A blocked client does **not** get a clear error — the caster returns a `SOURCETABLE 200 OK` response instead of the data stream, which the node logs as a sourcetable/invalid-response error. If you see a sourcetable response for a mountpoint you know is valid, suspect a client-side block, not a bad mountpoint. The default `user_agent` (`NTRIP ros_ntrip_client`) is accepted; if you change it, keep the mandatory `NTRIP ` prefix and avoid the stock string.
* **Request rate.** Use `ntrip_server_hz:=1` (the default) for rtk2go. Higher rates can get you banned.
* **Persistent reconnect.** On any connection loss or failed initial connect, the node schedules a non-blocking reconnect and retries indefinitely. The wait starts at `reconnect_attempt_wait_seconds` (10s) and doubles on each failure up to `reconnect_attempt_wait_max_seconds` (default 120s), then holds at that ceiling. It never gives up, so the node recovers on its own from extended rtk2go outages (DDoS) or a mountpoint taken down for maintenance. To shrink your footprint during long outages, raise the ceiling (e.g. `reconnect_attempt_wait_max_seconds:=600`). Note: only the network client retries forever; the serial client retries a fixed number of times then exits.
* **First-connect timeout is normal.** With rtk2go the very first connect attempt frequently times out and then succeeds on the first backoff retry, even on healthy connections. This is expected.

### Known behavior: NMEA upload to a connected-but-silent mountpoint

The dead-connection watchdog (`rtcm_timeout_seconds`) only arms **after the first RTCM packet arrives**. If a caster *accepts* the connection but then never delivers RTCM — e.g. a mountpoint that is **down for maintenance** while the caster still completes the GET request — the node believes it is connected and, if `send_nmea` is `true`, keeps uploading NMEA every cycle to a stream that is dead. On rtk2go this continuous upload to a silent mountpoint can trigger a ban.

Mitigations by deployment type:

* **Fixed-base mountpoints (e.g. rtk2go):** set `send_nmea:=false`. These mountpoints don't use your position, so no NMEA should be sent in the first place — this removes the problem at the source.
* **VRS / virtual mountpoints:** NMEA is required (the network needs your position to synthesize the virtual base), so `send_nmea:=false` is not an option. The proper fix is to baseline the watchdog off the connect time so a connected-but-silent stream triggers reconnect even before the first packet. This is **not yet implemented** — left as documented behavior pending a VRS caster to validate against.

## Docker Integration

### VSCode

The easiest way to use docker while still using an IDE is to use VSCode as an IDE. Follow the steps below to develop on this repo in a docker container

1. Install the following dependencies:
1. [VSCode](https://code.visualstudio.com/)
1. [Docker](https://docs.docker.com/get-docker/)
1. Open VSCode and install the following [plugins](https://code.visualstudio.com/docs/editor/extension-marketplace):
1. [VSCode Remote Containers plugin](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
1. Open this directory in a container by following [this guide](https://code.visualstudio.com/docs/remote/containers#_quick-start-open-an-existing-folder-in-a-container)

### Make

If you are comfortable working from the command line, the [Makefile](./devcontainer/Makefile) in the [.devcontainer](./devcontainer) directory
can be used to build a development image, and run a shell inside the docker image. Follow the steps below to setup your environment to use the `Makefile`

1. Install the following dependencies:
1. [Make](https://www.gnu.org/software/make/)
1. [Docker](https://docs.docker.com/get-docker/)
1. [qemu-user-static](https://packages.ubuntu.com/bionic/qemu-user-static) (for multiarch builds)
1. Run the following command to register the qemu binaries with docker: `docker run --rm --privileged multiarch/qemu-user-static:register`

The `Makefile` exposes the following tasks. They can all be run from the `.devcontainer` directory:
* `make build-shell` - Builds the docker image and starts a shell session in the image allowing the user to develop and build the ROS project using common commands such as `catkin_make`
* `make clean` - Cleans up after the above two tasks

## License
ntrip_client is released under the MIT License - see the `LICENSE` file in the source distribution.
Expand Down
26 changes: 23 additions & 3 deletions launch/ntrip_client_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ def generate_launch_description():
DeclareLaunchArgument('namespace', default_value='/'),
DeclareLaunchArgument('node_name', default_value='ntrip_client'),
DeclareLaunchArgument('debug', default_value='false'),
DeclareLaunchArgument('host', default_value='20.185.11.35'),
DeclareLaunchArgument('host', default_value='20.185.11.35'),
DeclareLaunchArgument('port', default_value='2101'),
DeclareLaunchArgument('mountpoint', default_value='VRS_RTCM3'),
DeclareLaunchArgument('ntrip_version', default_value='None'),
DeclareLaunchArgument('user_agent', default_value='NTRIP ros_ntrip_client', description='HTTP User-Agent sent to the caster. Must start with "NTRIP ". rtk2go blocks the stock "NTRIP ntrip_client_ros".'),
DeclareLaunchArgument('ntrip_server_hz', default_value='1'), # set this to 1 for rtk2go
DeclareLaunchArgument('send_nmea', default_value='true', description='Forward NMEA from the "nmea" topic up to the caster. Needed for virtual/relayed (VRS) mountpoints; set false for plain base stations to skip the subscription and avoid uploading position.'),
DeclareLaunchArgument('authenticate', default_value='True'),
DeclareLaunchArgument('username', default_value='user'),
DeclareLaunchArgument('password', default_value='pass'),
Expand All @@ -22,10 +25,12 @@ def generate_launch_description():
DeclareLaunchArgument('key', default_value='None'),
DeclareLaunchArgument('ca_cert', default_value='None'),
DeclareLaunchArgument('rtcm_message_package', default_value='rtcm_msgs'),
DeclareLaunchArgument('reconnect_attempt_wait_max_seconds', default_value='120', description='Ceiling for the exponential reconnect backoff. Retries are persistent (never give up); raise this to reduce footprint during long caster outages (e.g. 600 for rtk2go DDoS/maintenance downtime).'),

# Pass an environment variable to the node
SetEnvironmentVariable(name='NTRIP_CLIENT_DEBUG', value=LaunchConfiguration('debug')),


# ******************************************************************
# NTRIP Client Node
# ******************************************************************
Expand All @@ -44,6 +49,13 @@ def generate_launch_description():
# Optional parameter that will set the NTRIP version in the initial HTTP request to the NTRIP caster.
'ntrip_version': LaunchConfiguration('ntrip_version'),

# User-Agent presented to the caster. Must start with "NTRIP ".
# rtk2go blocks the stock "NTRIP ntrip_client_ros" and refuses such clients with a sourcetable response.
'user_agent': LaunchConfiguration('user_agent'),

# Rate to request correction messages. Some servers will sandbox clients that request too often
'ntrip_server_hz': LaunchConfiguration('ntrip_server_hz'),

# If this is set to true, we will read the username and password and attempt to authenticate. If not, we will attempt to connect unauthenticated
'authenticate': LaunchConfiguration('authenticate'),

Expand All @@ -64,6 +76,10 @@ def generate_launch_description():
# Not sure if this will be looked at by other ndoes, but this frame ID will be added to the RTCM messages published by this node
'rtcm_frame_id': 'odom',

# Whether to forward NMEA from the "nmea" topic up to the caster.
# Needed for virtual/relayed (VRS) mountpoints; disable for plain base stations.
'send_nmea': LaunchConfiguration('send_nmea'),

# Optional parameters that will allow for longer or shorter NMEA messages. Standard max length for NMEA is 82
'nmea_max_length': 128,
'nmea_min_length': 3,
Expand All @@ -73,10 +89,14 @@ def generate_launch_description():

# Will affect how many times the node will attempt to reconnect before exiting, and how long it will wait in between attempts when a reconnect occurs
'reconnect_attempt_max': 10,
'reconnect_attempt_wait_seconds': 5,
'reconnect_attempt_wait_seconds': 10,
# Reconnect backoff: wait doubles from reconnect_attempt_wait_seconds up to
# reconnect_attempt_wait_max_seconds, then holds at that ceiling. Retries are
# persistent (no give-up) so the node recovers from long caster outages on its own.
'reconnect_attempt_wait_max_seconds': LaunchConfiguration('reconnect_attempt_wait_max_seconds'),

# How many seconds is acceptable in between receiving RTCM. If RTCM is not received for this duration, the node will attempt to reconnect
'rtcm_timeout_seconds': 4
'rtcm_timeout_seconds': 10 #was 4 changed for rtk2go reqs
}
],
# Uncomment the following section and replace "/gx5/nmea/sentence" with the topic you are sending NMEA on if it is not the one we requested
Expand Down
Loading