Remote Access Gateway
The Foxglove SDK can run a remote access gateway on a device, making it available for remote visualization and teleoperation through the Foxglove platform. The gateway connects to Foxglove so that organization members can access the device from anywhere, without direct network access. This is useful when the device is behind a firewall, on a cellular network, or otherwise unreachable from your local machine.
Starting the gateway
The gateway authenticates with a device token. You can provide the token directly or set the FOXGLOVE_DEVICE_TOKEN environment variable.
- Rust
- Python
- C++
use foxglove::remote_access::Gateway;
let handle = Gateway::new()
.device_token("fox_dt-...")
.start()
.expect("Failed to start remote access gateway");
If the device token is not provided, the gateway reads it from the FOXGLOVE_DEVICE_TOKEN environment variable.
For more options, see the Gateway reference.
import foxglove
gateway = foxglove.start_gateway(device_token="fox_dt-...")
If the device token is not provided, the gateway reads it from the FOXGLOVE_DEVICE_TOKEN environment variable.
For more options, see the start_gateway reference.
#include <foxglove/remote_access.hpp>
foxglove::RemoteAccessGatewayOptions options;
options.device_token = "fox_dt-...";
auto gateway_result = foxglove::RemoteAccessGateway::create(std::move(options));
if (!gateway_result.has_value()) {
std::cerr << "Failed to create gateway: "
<< foxglove::strerror(gateway_result.error()) << '\n';
return 1;
}
auto gateway = std::move(gateway_result.value());
If the device token is not provided, the gateway reads it from the FOXGLOVE_DEVICE_TOKEN environment variable.
For more options, see the RemoteAccessGatewayOptions reference.
Logging messages
The gateway is a sink: any messages logged to a channel in the gateway's context are streamed to connected clients. Log to channels as you would for any other sink. See the logging messages guide for how to define channels and schemas.
Status messages
You can publish a status message to the app, which will be displayed in the Problems panel. Use this to communicate warnings and errors within the app.
- Rust
- Python
- C++
handle.publish_status(foxglove::remote_access::Status::error("Error!"));
If you provide a status ID when publishing, you can later remove the status from the Problems panel by calling remove_status.
gateway.publish_status("Error!", foxglove.StatusLevel.Error)
If you provide a status ID when publishing, you can later remove the status from the Problems panel by calling remove_status.
gateway.publishStatus(foxglove::RemoteAccessStatusLevel::Error, "Error!");
If you provide a status ID when publishing, you can later remove the status from the Problems panel by calling removeStatus.
Capabilities
You can augment the gateway with additional capabilities that unlock more features in the app. To advertise capabilities, provide them when constructing the gateway.
- Rust
- Python
- C++
use foxglove::remote_access::{Capability, Gateway};
let handle = Gateway::new()
.capabilities([Capability::ClientPublish])
.supported_encodings(["json"])
.start()
.expect("Failed to start remote access gateway");
import foxglove
from foxglove.remote_access import Capability
gateway = foxglove.start_gateway(
capabilities=[Capability.ClientPublish],
supported_encodings=["json"],
)
foxglove::RemoteAccessGatewayOptions options;
options.capabilities = foxglove::RemoteAccessGatewayCapabilities::ClientPublish;
options.supported_encodings = {"json"};
auto gateway = foxglove::RemoteAccessGateway::create(std::move(options)).value();
Handling messages from the app
The Publish panel and Teleop panel can send messages to the gateway from the app.
In addition to advertising ClientPublish support, declare the encodings you support and implement a listener to receive the messages.
- Rust
- Python
- C++
use foxglove::ChannelDescriptor;
use foxglove::remote_access::{Capability, Client, Gateway, Listener};
use std::sync::Arc;
struct MessageHandler;
impl Listener for MessageHandler {
fn on_message_data(
&self,
client: &Client,
channel: &ChannelDescriptor,
message: &[u8],
) {
let json: serde_json::Value =
serde_json::from_slice(message).expect("Failed to parse message");
println!(
"Message from client {} on topic {}: {json}",
client.id(),
channel.topic()
);
}
}
let handle = Gateway::new()
.capabilities([Capability::ClientPublish])
.supported_encodings(["json"])
.listener(Arc::new(MessageHandler))
.start()
.expect("Failed to start remote access gateway");
The Listener trait provides additional callbacks for tracking subscriptions and client-advertised channels. For more detail, see the remote access example.
import json
import foxglove
from foxglove import ChannelDescriptor
from foxglove.remote_access import Capability, Client, RemoteAccessListener
class Listener(RemoteAccessListener):
def on_message_data(
self, client: Client, channel: ChannelDescriptor, data: bytes
) -> None:
msg = json.loads(data)
print(f"Message from client {client.id} on topic {channel.topic}: {msg}")
gateway = foxglove.start_gateway(
capabilities=[Capability.ClientPublish],
supported_encodings=["json"],
listener=Listener(),
)
The RemoteAccessListener protocol provides additional callbacks for tracking subscriptions and client-advertised channels. For more detail, see the remote access example.
#include <foxglove/remote_access.hpp>
foxglove::RemoteAccessGatewayOptions options;
options.capabilities = foxglove::RemoteAccessGatewayCapabilities::ClientPublish;
options.supported_encodings = {"json"};
options.callbacks.onMessageData =
[](uint32_t client_id, const foxglove::ChannelDescriptor& channel,
const std::byte* data, size_t data_len) {
std::cerr << "Message from client " << client_id
<< " on " << channel.topic() << ": "
<< std::string_view(reinterpret_cast<const char*>(data), data_len)
<< '\n';
};
auto gateway = foxglove::RemoteAccessGateway::create(std::move(options)).value();
The RemoteAccessGatewayCallbacks struct provides additional callbacks for tracking subscriptions and client-advertised channels. For more detail, see the remote access example.