This tutorial provides a detailed overview of the Foxglove SDK, enabling efficient understanding and utilization for both theoretical and practical applications.
The Foxglove SDK facilitates:
Available in C++, Python, and Rust, the SDK is open-source under the MIT license.
A message is a single timestamped log entry, which can be sent to the Foxglove app or written to an MCAP file.
A schema describes the structure of a message’s contents. Foxglove defines several schemas with visualization support, and users can define custom schemas using supported encodings.
A channel provides a way to log related messages sharing the same schema. Each channel is identified by a unique topic name. For Foxglove schemas, the SDK offers type-safe channels for logging messages with a known, matching schema (not yet supported in C++).
A sink is a destination for logged messages, such as a WebSocket client or an MCAP writer. Without a configured sink, log messages are dropped. Multiple sinks can be configured and managed dynamically at runtime.
MCAP is a container file format for multimodal log data, supported by Foxglove.
Install via crates.io:
cargo add foxglove
Install via PyPI:
pip install foxglove-sdk
The C++ SDK is a wrapper around a C library. To build it:
This example demonstrates logging to an MCAP file and streaming to the Foxglove app using the SDK.
import math
import time
import foxglove
from foxglove import Channel
from foxglove.channels import SceneUpdateChannel
from foxglove.schemas import (
Color,
CubePrimitive,
SceneEntity,
SceneUpdate,
Vector3,
)
foxglove.set_log_level("DEBUG")
# Our example logs data on a couple of different topics, so we'll create a
# channel for each. We can use a channel like SceneUpdateChannel to log
# Foxglove schemas, or a generic Channel to log custom data.
scene_channel = SceneUpdateChannel("/scene")
size_channel = Channel("/size", message_encoding="json")
# We'll log to both an MCAP file, and to a running Foxglove app via a server.
file_name = "quickstart-python.mcap"
writer = foxglove.open_mcap(file_name)
server = foxglove.start_server()
while True:
size = abs(math.sin(time.time())) + 1
# Log messages on both channels until interrupted. By default, each message
# is stamped with the current time.
size_channel.log({"size": size})
scene_channel.log(
SceneUpdate(
entities=[
SceneEntity(
cubes=[
CubePrimitive(
size=Vector3(x=size, y=size, z=size),
color=Color(r=1.0, g=0, b=0, a=1.0),
)
],
),
]
)
)
time.sleep(0.033)
use std::ops::Add;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use foxglove::schemas::{Color, CubePrimitive, SceneEntity, SceneUpdate, Vector3};
use foxglove::{LazyChannel, LazyRawChannel, McapWriter};
const FILE_NAME: &str = "quickstart-rust.mcap";
// Our example logs data on a couple of different topics, so we'll create a
// channel for each. We can use a channel like Channel<SceneUpdate> to log
// Foxglove schemas, or a generic RawChannel to log custom data.
static SCENE: LazyChannel<SceneUpdate> = LazyChannel::new("/scene");
static SIZE: LazyRawChannel = LazyRawChannel::new("/size", "json");
fn main() {
let env = env_logger::Env::default().default_filter_or("debug");
env_logger::init_from_env(env);
let done = Arc::new(AtomicBool::default());
ctrlc::set_handler({
let done = done.clone();
move || {
done.store(true, Ordering::Relaxed);
}
})
.expect("Failed to set SIGINT handler");
// We'll log to both an MCAP file, and to a running Foxglove app via a server.
let mcap = McapWriter::new()
.create_new_buffered_file(FILE_NAME)
.expect("Failed to start mcap writer");
// Start a server to communicate with the Foxglove app. This will run indefinitely, even if
// references are dropped.
foxglove::WebSocketServer::new()
.start_blocking()
.expect("Server failed to start");
while !done.load(Ordering::Relaxed) {
let size = SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs_f64()
.sin()
.abs()
.add(1.0);
// Log messages on the channel until interrupted. By default, each message
// is stamped with the current time.
SIZE.log(format!("{{\"size\": {size}}}").as_bytes());
SCENE.log(&SceneUpdate {
deletions: vec![],
entities: vec![SceneEntity {
id: "box".to_string(),
cubes: vec![CubePrimitive {
size: Some(Vector3 {
x: size,
y: size,
z: size,
}),
color: Some(Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
}),
..Default::default()
}],
..Default::default()
}],
});
std::thread::sleep(std::time::Duration::from_millis(33));
}
mcap.close().expect("Failed to close mcap writer");
}
#include <foxglove/channel.hpp>
#include <foxglove/mcap.hpp>
#include <foxglove/server.hpp>
#include <atomic>
#include <chrono>
#include <csignal>
#include <functional>
#include <iostream>
#include <thread>
using namespace std::chrono_literals;
// This example logs custom data on an "example" topic. Open Foxglove and connect to the running
// server. Then add a Raw Message panel, and choose the "example" topic.
int main(int argc, const char* argv[]) {
static std::function<void()> sigintHandler;
std::signal(SIGINT, [](int) {
if (sigintHandler) {
sigintHandler();
}
});
// We'll log to both an MCAP file, and to a running Foxglove app.
foxglove::McapWriterOptions mcap_options = {};
mcap_options.path = "quickstart-cpp.mcap";
auto writerResult = foxglove::McapWriter::create(mcap_options);
if (!writerResult.has_value()) {
std::cerr << "Failed to create writer: " << foxglove::strerror(writerResult.error()) << '\n';
return 1;
}
auto writer = std::move(writerResult.value());
// Start a server to communicate with the Foxglove app.
foxglove::WebSocketServerOptions ws_options;
ws_options.host = "127.0.0.1";
ws_options.port = 8765;
auto serverResult = foxglove::WebSocketServer::create(std::move(ws_options));
if (!serverResult.has_value()) {
std::cerr << "Failed to create server: " << foxglove::strerror(serverResult.error()) << '\n';
return 1;
}
auto server = std::move(serverResult.value());
std::cerr << "Server listening on port " << server.port() << '\n';
std::atomic_bool done = false;
sigintHandler = [&] {
done = true;
};
// Our example logs custom data on an "example" topic, so we'll create a channel for that.
foxglove::Schema schema;
schema.encoding = "jsonschema";
std::string schemaData = R"({
"type": "object",
"properties": {
"val": { "type": "number" }
}
})";
schema.data = reinterpret_cast<const std::byte*>(schemaData.data());
schema.dataLen = schemaData.size();
auto channelResult = foxglove::Channel::create("example", "json", std::move(schema));
if (!channelResult.has_value()) {
std::cerr << "Failed to create channel: " << foxglove::strerror(channelResult.error()) << '\n';
return 1;
}
auto channel = std::move(channelResult.value());
while (!done) {
// Log messages on the channel until interrupted. By default, each message
// is stamped with the current time.
std::this_thread::sleep_for(33ms);
auto now = std::chrono::system_clock::now().time_since_epoch().count();
std::string msg = "{\"val\": " + std::to_string(now) + "}";
channel.log(reinterpret_cast<const std::byte*>(msg.data()), msg.size());
}
return 0;
}
The C++ example follows a similar structure, utilizing the SDK’s C++ API to log data and stream to the Foxglove app.
To view the live visualization:
Foxglove supports defining custom schemas using various encodings:
For Protobuf, include the .proto files in your project and use them to publish data via a live Foxglove WebSocket connection or log to an MCAP file.