Introduction

rosbridge_suite offers provides JSON interface to ROS via WebSockets, allowing flexibility to interact with ROS through APIs.

ngrok, on the other hand, allows you to expose a local development server to the internet.

Combining them allows you to have access to ROS on a remote client that does not have ROS installed over the internet.

In this post, two methods would be described. The results of this post was obtained on Ubuntu 20.04 with ROS Noetic.

Setup

First, you need to have ngrok set up. The getting started guide can be found here. Note that you would need to sign up an account to have access to an auth token that is required to start the ngrok service.

Second, make sure to have rosbridge_suite installed. You can install it with:

sudo apt install ros-noetic-rosbridge-suite

For the example client here, the roslibpy is needed. The example publisher running on the machine with ROS (roscore) is:

rostopic pub /chatter std_msgs/String "data: 'test'" -r1

TCP Tunnel

The TCP tunnel method was first described by ibrahimessam. Chiefly, as the name would suggest, ngrok acts as a tunnel, funnelling websocket packets through it. Indeed, you may follow the steps in ibrahimessam’s post to achieve the same result. Here, a simplified approach is described to demonstrate the principle.

On the machine running ROS, start ngrok with the intention to forward tcp traffic to port 9090:

ngrok tcp 9090

The window should show some statistics about the connection and more importantly, the Forwarding address. In my case it was tcp://0.tcp.ap.ngrok.io:12032 -> localhost:9090

and then start rosbridge_server on port 9090 and expecting traffic from port 12032 with:

roslaunch rosbridge_server rosbridge_websocket.launch _port:=9090 websocket_external_port:=12032

An example remote client would be:

import roslibpy
import time

client = roslibpy.Ros(host="0.tcp.ap.ngrok.io", port=12032)
client.run()

listener = roslibpy.Topic(client, '/chatter', 'std_msgs/String')
listener.subscribe(lambda message: print('Heard talking: ' + message['data']))

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    client.terminate()

Indeed, the down side to this approach is that it may not be trivial to use the same rosbridge_server to handle both local and remote connections, because all connections need to connect to _port while declaring that they are on websocket_external_port. For the remote client, this is trivial, because they are connecting via the port ngrok specified, which is then used as the websocket_external_port number. For local connections, the port 9090. If only one rosbridge_server is to handle both local and remote clients, another approach is required.

Reverse Proxy

In this approach, ngrok acts as a reverse proxy.

On the machine running ROS, start ngrok with the intention to forward https traffic to port 9090:

ngrok http 9090 --host-header "something:9090"
Note: It is important to set the port in host-header here so that rosbridge_server accepts both local and remote connections on one port (9090 in this case). The host portion of the host header does not seem to be used by rosbrige_server, but one may still consider putting in something descriptive. If you wish to use this method BUT keep local and remote rosbridge_server seperate, launch the remote rosbridge_server to have websocket_external_port at 443 instead.

The window should show some statistics about the connection and more importantly, the Forwarding address. In my case it was https://6eae-118-200-93-224.ap.ngrok.io -> localhost:9090

and then start rosbridge_server on port 9090 with:

roslaunch rosbridge_server rosbridge_websocket.launch _port:=9090

An example remote client would be:

import roslibpy
import time

client = roslibpy.Ros(host="6eae-118-200-93-224.ap.ngrok.io", port=443, is_secure=True)
client.run()

listener = roslibpy.Topic(client, '/chatter', 'std_msgs/String')
listener.subscribe(lambda message: print('Heard talking: ' + message['data']))

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    client.terminate()

Note the inclusion of is_secure=True as compared to the TCP tunnel case.

Conclusion

In this post, use of ngork as tcp tunnel and reverse proxy is described to expose ROS to remote clients over the internet, with the latter approach potentially allowing the use of one rosbridge_server to handle both local and remote connections. Securing the webbridge remains a challenge though, and do consider using rosauth as described by this post. roslibpy users should also be aware that the authenticate function has yet to be implemented, so do keep this in consideration during planning. Finally, since roslibpy uses autobahn, it is unlikely that wss://username:password@address:port would work as authentication without catering for additional development effort.

Do take note of the bandwidth and domain limitations for ngrok on the free tier. “Static” domains are also available as part of the paid plan, and you might be able to automate some of the configurations by getting the public_url in the ngrok agent api.

Comments or discussions may be posted here.