Examples

Quickstart

#include <foxglove/channel.hpp>
#include <foxglove/context.hpp>
#include <foxglove/error.hpp>
#include <foxglove/foxglove.hpp>
#include <foxglove/mcap.hpp>
#include <foxglove/schemas.hpp>
#include <foxglove/server.hpp>

#include <atomic>
#include <chrono>
#include <cmath>
#include <csignal>
#include <functional>
#include <iostream>
#include <thread>

using namespace std::chrono_literals;

int main(int argc, const char* argv[]) {
  foxglove::setLogLevel(foxglove::LogLevel::Debug);

  static std::function<void()> sigint_handler;

  std::signal(SIGINT, [](int) {
    if (sigint_handler) {
      sigint_handler();
    }
  });

  foxglove::McapWriterOptions mcap_options = {};
  mcap_options.path = "quickstart-cpp.mcap";
  auto writer_result = foxglove::McapWriter::create(mcap_options);
  if (!writer_result.has_value()) {
    std::cerr << "Failed to create writer: " << foxglove::strerror(writer_result.error()) << '\n';
    return 1;
  }
  auto writer = std::move(writer_result.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 server_result = foxglove::WebSocketServer::create(std::move(ws_options));
  if (!server_result.has_value()) {
    std::cerr << "Failed to create server: " << foxglove::strerror(server_result.error()) << '\n';
    return 1;
  }
  auto server = std::move(server_result.value());
  std::cerr << "Server listening on port " << server.port() << '\n';

  // Create a schema for a JSON channel for logging {size: number}
  foxglove::Schema schema;
  schema.encoding = "jsonschema";
  std::string schema_data = R"({
        "type": "object",
        "properties": {
        "size": { "type": "number" }
        }
    })";
  schema.data = reinterpret_cast<const std::byte*>(schema_data.data());
  schema.data_len = schema_data.size();
  auto channel_result = foxglove::RawChannel::create("/size", "json", std::move(schema));
  if (!channel_result.has_value()) {
    std::cerr << "Failed to create channel: " << foxglove::strerror(channel_result.error()) << '\n';
    return 1;
  }
  auto size_channel = std::move(channel_result.value());

  // Create a SceneUpdateChannel for logging changes to a 3d scene
  auto scene_channel_result = foxglove::schemas::SceneUpdateChannel::create("/scene");
  if (!scene_channel_result.has_value()) {
    std::cerr << "Failed to create scene channel: "
              << foxglove::strerror(scene_channel_result.error()) << '\n';
    return 1;
  }
  auto scene_channel = std::move(scene_channel_result.value());

  std::atomic_bool done = false;
  sigint_handler = [&] {
    done = true;
  };

  while (!done) {
    auto now = std::chrono::duration_cast<std::chrono::duration<double>>(
                 std::chrono::system_clock::now().time_since_epoch()
    )
                 .count();
    double size = abs(sin(now)) + 1.0;
    std::string msg = "{\"size\": " + std::to_string(size) + "}";
    size_channel.log(reinterpret_cast<const std::byte*>(msg.data()), msg.size());

    foxglove::schemas::CubePrimitive cube;
    cube.size = foxglove::schemas::Vector3{size, size, size};
    cube.color = foxglove::schemas::Color{1, 0, 0, 1};

    foxglove::schemas::SceneEntity entity;
    entity.id = "box";
    entity.cubes.push_back(cube);

    foxglove::schemas::SceneUpdate scene_update;
    scene_update.entities.push_back(entity);

    scene_channel.log(scene_update);

    std::this_thread::sleep_for(33ms);
  }

  return 0;
}

Write to an MCAP file

#include <foxglove/channel.hpp>
#include <foxglove/mcap.hpp>

#include <iostream>

// NOLINTNEXTLINE(bugprone-exception-escape)
int main(int argc, const char* argv[]) {
  foxglove::McapWriterOptions options = {};
  options.path = "test.mcap";
  options.truncate = true;
  auto writer_result = foxglove::McapWriter::create(options);
  if (!writer_result.has_value()) {
    std::cerr << "Failed to create writer: " << foxglove::strerror(writer_result.error()) << '\n';
    return 1;
  }
  auto writer = std::move(writer_result.value());

  foxglove::Schema schema;
  schema.name = "Test";
  schema.encoding = "jsonschema";
  std::string schema_data = R"({
    "type": "object",
    "properties": {
        "val": { "type": "number" }
    }
    })";
  schema.data = reinterpret_cast<const std::byte*>(schema_data.data());
  schema.data_len = schema_data.size();
  auto channel_result = foxglove::RawChannel::create("example", "json", std::move(schema));
  if (!channel_result.has_value()) {
    std::cerr << "Failed to create channel: " << foxglove::strerror(channel_result.error()) << '\n';
    return 1;
  }
  auto channel = std::move(channel_result.value());

  for (int i = 0; i < 100; ++i) {
    std::string msg = "{\"val\": " + std::to_string(i) + "}";
    channel.log(reinterpret_cast<const std::byte*>(msg.data()), msg.size());
  }

  // Optional, if you want to check for or handle errors
  foxglove::FoxgloveError err = writer.close();
  if (err != foxglove::FoxgloveError::Ok) {
    std::cerr << "Failed to close writer: " << foxglove::strerror(err) << '\n';
    return 1;
  }
  return 0;
}

Stream to Foxglove

#include <foxglove/channel.hpp>
#include <foxglove/foxglove.hpp>
#include <foxglove/server.hpp>

#include <atomic>
#include <chrono>
#include <csignal>
#include <functional>
#include <iostream>
#include <memory>
#include <thread>

using namespace std::chrono_literals;

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::function<void()> sigint_handler;

// NOLINTNEXTLINE(bugprone-exception-escape)
int main(int argc, const char* argv[]) {
  std::signal(SIGINT, [](int) {
    if (sigint_handler) {
      sigint_handler();
    }
  });

  foxglove::setLogLevel(foxglove::LogLevel::Debug);

  foxglove::WebSocketServerOptions options = {};
  options.name = "ws-demo-cpp";
  options.host = "127.0.0.1";
  options.port = 8765;
  options.capabilities = foxglove::WebSocketServerCapabilities::ClientPublish;
  options.supported_encodings = {"json"};
  options.callbacks.onSubscribe = [](uint64_t channel_id) {
    std::cerr << "Subscribed to channel " << channel_id << '\n';
  };
  options.callbacks.onUnsubscribe = [](uint64_t channel_id) {
    std::cerr << "Unsubscribed from channel " << channel_id << '\n';
  };
  options.callbacks.onClientAdvertise = [](
                                          uint32_t client_id, const foxglove::ClientChannel& channel
                                        ) {
    std::cerr << "Client " << client_id << " advertised channel " << channel.id << ":\n";
    std::cerr << "  Topic: " << channel.topic << '\n';
    std::cerr << "  Encoding: " << channel.encoding << '\n';
    std::cerr << "  Schema name: " << channel.schema_name << '\n';
    std::cerr << "  Schema encoding: "
              << (!channel.schema_encoding.empty() ? channel.schema_encoding : "(none)") << '\n';
    std::cerr << "  Schema: "
              << (channel.schema != nullptr
                    ? std::string(reinterpret_cast<const char*>(channel.schema), channel.schema_len)
                    : "(none)")
              << '\n';
  };
  options.callbacks.onMessageData =
    [](uint32_t client_id, uint32_t client_channel_id, const std::byte* data, size_t data_len) {
      std::cerr << "Client " << client_id << " published on channel " << client_channel_id << ": "
                << std::string(reinterpret_cast<const char*>(data), data_len) << '\n';
    };
  options.callbacks.onClientUnadvertise = [](uint32_t client_id, uint32_t client_channel_id) {
    std::cerr << "Client " << client_id << " unadvertised channel " << client_channel_id << '\n';
  };
  auto server_result = foxglove::WebSocketServer::create(std::move(options));
  if (!server_result.has_value()) {
    std::cerr << "Failed to create server: " << foxglove::strerror(server_result.error()) << '\n';
    return 1;
  }
  auto server = std::move(server_result.value());

  std::atomic_bool done = false;
  sigint_handler = [&] {
    std::cerr << "Shutting down...\n";
    server.stop();
    done = true;
  };

  foxglove::Schema schema;
  schema.name = "Test";
  schema.encoding = "jsonschema";
  std::string schema_data = R"({
    "type": "object",
    "properties": {
      "val": { "type": "number" }
    }
  })";
  schema.data = reinterpret_cast<const std::byte*>(schema_data.data());
  schema.data_len = schema_data.size();
  auto channel_result = foxglove::RawChannel::create("example", "json", std::move(schema));
  if (!channel_result.has_value()) {
    std::cerr << "Failed to create channel: " << foxglove::strerror(channel_result.error()) << '\n';
    return 1;
  }
  auto channel = std::move(channel_result.value());

  uint32_t i = 0;
  while (!done) {
    std::this_thread::sleep_for(100ms);
    std::string msg = "{\"val\": " + std::to_string(i) + "}";
    auto now =
      std::chrono::nanoseconds(std::chrono::system_clock::now().time_since_epoch()).count();
    channel.log(reinterpret_cast<const std::byte*>(msg.data()), msg.size(), now);
    ++i;
  }

  std::cerr << "Done\n";
  return 0;
}

Connection Graph

#include <foxglove/channel.hpp>
#include <foxglove/foxglove.hpp>
#include <foxglove/server.hpp>

#include <atomic>
#include <chrono>
#include <csignal>
#include <functional>
#include <iostream>
#include <memory>
#include <thread>

using namespace std::chrono_literals;

/**
 * This example constructs a connection graph which can be viewed as a Topic Graph in Foxglove:
 * https://docs.foxglove.dev/docs/visualization/panels/topic-graph
 */

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::function<void()> sigint_handler;

// NOLINTNEXTLINE(bugprone-exception-escape)
int main(int argc, const char* argv[]) {
  std::signal(SIGINT, [](int) {
    if (sigint_handler) {
      sigint_handler();
    }
  });

  foxglove::setLogLevel(foxglove::LogLevel::Debug);

  foxglove::WebSocketServerOptions options = {};
  options.name = "ws-demo-cpp";
  options.host = "127.0.0.1";
  options.port = 8765;
  options.capabilities = foxglove::WebSocketServerCapabilities::ConnectionGraph;
  options.callbacks.onConnectionGraphSubscribe = []() {
    std::cerr << "Connection graph subscribed\n";
  };
  options.callbacks.onConnectionGraphUnsubscribe = []() {
    std::cerr << "Connection graph unsubscribed\n";
  };

  auto graph = foxglove::ConnectionGraph();
  auto err = graph.setPublishedTopic("/example-topic", {"1", "2"});
  if (err != foxglove::FoxgloveError::Ok) {
    std::cerr << "Failed to set published topic: " << foxglove::strerror(err) << '\n';
  }
  graph.setSubscribedTopic("/subscribed-topic", {"3", "4"});
  graph.setAdvertisedService("example-service", {"5", "6"});

  auto server_result = foxglove::WebSocketServer::create(std::move(options));
  if (!server_result.has_value()) {
    std::cerr << "Failed to create server: " << foxglove::strerror(server_result.error()) << '\n';
    return 1;
  }
  auto server = std::move(server_result.value());

  std::atomic_bool done = false;
  sigint_handler = [&] {
    std::cerr << "Shutting down...\n";
    server.stop();
    done = true;
  };

  while (!done) {
    server.publishConnectionGraph(graph);
    std::this_thread::sleep_for(1s);
  }

  std::cerr << "Done\n";
  return 0;
}