はじめに
Foxgloveがビジュアライザとして面白そうなので、入門して簡単なビジュアライズに挑戦してみます。ROS2を使わずC++のみで実装した場合について説明します。
Foxgloveとは
Foxgloveは、ロボットなどのフィジカルAIのデータを視覚化・記録・デバッグするためのオープンソースのツールです。もっと簡単にいえば、ビジュアライザとロガーが合わさったようなツールです。
Foxgloveでは3D空間・グラフ・動画などを表示するパネルを組み合わせることで自由度の高い視覚化ができます。
なお、チーム内ユーザが3人以下などの制限はありますが、無料で利用することができます(アカデミック用途であれば無料かつ大半の制限なしで使えます)。
やること
- Foxgloveのパネル・レイアウト(各パネルの配置)作成
- データの表示・グラフ化
やらないこと
- ログファイルへの記録・読み出し
- ROS・ROS2の使用
- 3Dモデルなどの複雑なビジュアライズ
環境
- CPU i5-1240P(アーキテクチャ x86_64)
- WSL2 (Ubuntu24.04)
- gcc version 13.3.0
- cmake version 3.28.3
- foxglove-sdk v0.13.0
インストール
- C++のコンパイラ
- cmake
- Foxglove:Web版とデスクトップ版がありますが、今回の使用用途ではどちらでも大丈夫です。リンク先からログインorインストールしてください。今回はWeb版で説明します
- foxglove-sdk:後述します
コンパイラとcmakeのインストール手順は調べれば出るので省略します。
レイアウトを作成する
Foxgloveにログインするとダッシュボードが開きます。まずは可視化用のデータを受け取るために新しく接続を開きましょう。ダッシュボード左側から接続を開く
を選択します。
WebSocket URLの入力を求められますが、デフォルトのws://localhost:8765
でOK。
接続を開くと、おそらくパネルの選択を求められると思います。グラフ用にプロット
パネルを選択。
せっかくなのでパネルを追加してみます。画面右上の+
アイコンを押してパネルを追加。選択肢がいくつか出てきますが、今回は生データのメッセージ
パネルを選択。
また、各パネルのケバブメニュー(縦3点ドット)をクリックするとパネルの変更や削除などができます。
データを送る・確認する
C++のプログラムを作成して、Foxgloveにデータを送り、Foxgloveで確認します。先に完成品を見せるとこんな感じ。
cmakeの準備
まずはcmakeの準備から。CMakeLists.txt
を作成し、以下の内容をコピペします。
cmake_minimum_required(VERSION 3.20)
# TODO: replace "my_program" throughout with the name of your project
project(my_program LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
# Fetch Foxglove SDK
include(FetchContent)
FetchContent_Declare(
foxglove
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
# TODO: Copy the URL and SHA for the appropriate .zip file from here:
# https://github.com/foxglove/foxglove-sdk/releases?q=sdk%2F&expanded=true
URL <download URL from the GitHub release>
URL_HASH SHA256=<sha from the GitHub release>
)
FetchContent_MakeAvailable(foxglove)
# Assuming the project consists of a single source file, main.cpp
add_executable(my_program main.cpp)
# Add include directory for Foxglove SDK
target_include_directories(my_program PRIVATE ${foxglove_SOURCE_DIR}/include)
target_include_directories(my_program PRIVATE ${foxglove_SOURCE_DIR}/include/foxglove)
# Find all Foxglove SDK source files
file(GLOB FOXGLOVE_SOURCES CONFIGURE_DEPENDS
"${foxglove_SOURCE_DIR}/src/*.cpp"
"${foxglove_SOURCE_DIR}/src/server/*.cpp"
)
# Add Foxglove SDK source files
target_sources(my_program PRIVATE ${FOXGLOVE_SOURCES})
# Link against libfoxglove.a
target_link_libraries(my_program PRIVATE ${foxglove_SOURCE_DIR}/lib/libfoxglove.a)
次に、foxglove-sdkをインストールするためにリリースページへ飛び、最新バージョンのAssetsから自分の環境にあったものを見つけます。自分の場合はCPUアーキテクチャがx86_64でOSがLinux(WSL2)なのでfoxglove-v0.13.0-cpp-x86_64-unknown-linux-gnu.zip
になります。
見つけたzipファイルのダウンロードリンクと、その横のsha256の値をそれぞれコピーしてCMakeLists.txt
に貼り付けます。
cmake_minimum_required(VERSION 3.20)
# TODO: replace "my_program" throughout with the name of your project
project(my_program LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
# Fetch Foxglove SDK
include(FetchContent)
FetchContent_Declare(
foxglove
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
# TODO: Copy the URL and SHA for the appropriate .zip file from here:
# https://github.com/foxglove/foxglove-sdk/releases?q=sdk%2F&expanded=true
- URL <download URL from the GitHub release>
- URL_HASH SHA256=<sha from the GitHub release>
+ URL https://github.com/foxglove/foxglove-sdk/releases/download/sdk%2Fv0.13.0/foxglove-v0.13.0-cpp-x86_64-unknown-linux-gnu.zip
+ URL_HASH SHA256=609761f7e76c464835d82b8d2abd02d9b4c975a77cbc12515ebdd0567e87023e
)
FetchContent_MakeAvailable(foxglove)
# Assuming the project consists of a single source file, main.cpp
add_executable(my_program main.cpp)
# Add include directory for Foxglove SDK
target_include_directories(my_program PRIVATE ${foxglove_SOURCE_DIR}/include)
target_include_directories(my_program PRIVATE ${foxglove_SOURCE_DIR}/include/foxglove)
# Find all Foxglove SDK source files
file(GLOB FOXGLOVE_SOURCES CONFIGURE_DEPENDS
"${foxglove_SOURCE_DIR}/src/*.cpp"
"${foxglove_SOURCE_DIR}/src/server/*.cpp"
)
# Add Foxglove SDK source files
target_sources(my_program PRIVATE ${FOXGLOVE_SOURCES})
# Link against libfoxglove.a
target_link_libraries(my_program PRIVATE ${foxglove_SOURCE_DIR}/lib/libfoxglove.a)
main.cppの準備
Foxgloveにデータを送信するプログラムであるmain.cpp
を作成します。ほとんどドキュメントの内容そのままですが、自分の環境だと#include <atomic>
がないと動かなかったので足しています。
内容は、プログラム実行時からの経過時間をjson形式でFoxgloveに送信するものです。なお、チャンネル・トピックなどの用語が出てきますが、この記事の最後でまとめて解説します。
#include <foxglove/foxglove.hpp>
#include <foxglove/server.hpp>
#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>
#include <atomic> //ドキュメントにはないので追加
using namespace std::chrono_literals;
int main(int argc, const char *argv[]) {
foxglove::WebSocketServerOptions options;
auto serverResult = foxglove::WebSocketServer::create(std::move(options));
if (!serverResult.has_value()) {
std::cerr << foxglove::strerror(serverResult.error()) << '\n';
return 1;
}
auto server = std::move(serverResult.value());
// トピック名を/helloとしてjson形式で書き込むチャンネルを作成
auto channel = foxglove::RawChannel::create("/hello", "json").value();
auto start = std::chrono::steady_clock::now();
// Log until interrupted
static std::function<void()> sigint_handler;
std::atomic_bool done = false;
sigint_handler = [&] { done = true; };
std::signal(SIGINT, [](int) {
if (sigint_handler) {
sigint_handler();
}
});
while (!done) {
auto dur = std::chrono::steady_clock::now() - start;
float elapsed_seconds = std::chrono::duration<float>(dur).count();
// "elapsed"をキーとするjson形式のデータを作成。値は経過時間。
std::string msg = "{\"elapsed\": " + std::to_string(elapsed_seconds) + "}";
// msgをfoxgloveに送信する。
channel.log(reinterpret_cast<const std::byte *>(msg.data()), msg.size());
std::this_thread::sleep_for(33ms);
}
return 0;
}
コンパイル
cmakeとmakeを実行してバイナリファイルを作成します。初回はfoxglove-sdkのインストールも行われると思います。
sin471@LAPTOP-SISACVID:~/foxglove$ ls
CMakeLists.txt main.cpp
sin471@LAPTOP-SISACVID:~/foxglove$ mkdir build
sin471@LAPTOP-SISACVID:~/foxglove$ cd build/
sin471@LAPTOP-SISACVID:~/foxglove/build$ cmake ..
-- The CXX compiler identification is GNU 13.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (7.1s)
-- Generating done (0.0s)
-- Build files have been written to: /home/sin471/foxglove/build
sin471@LAPTOP-SISACVID:~/foxglove/build$ make -j8
[ 7%] Building CXX object CMakeFiles/my_program.dir/main.cpp.o
[ 23%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/error.cpp.o
[ 30%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/context.cpp.o
[ 30%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/channel.cpp.o
[ 38%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/foxglove.cpp.o
[ 46%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/mcap.cpp.o
[ 53%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/schemas.cpp.o
[ 61%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/server.cpp.o
[ 69%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/server/connection_graph.cpp.o
[ 76%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/server/fetch_asset.cpp.o
[ 84%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/server/parameter.cpp.o
[ 92%] Building CXX object CMakeFiles/my_program.dir/_deps/foxglove-src/src/server/service.cpp.o
[100%] Linking CXX executable my_program
[100%] Built target my_program
バイナリの実行
コンパイルが終わるとbuild
ディレクトリ内にmy_program
というバイナリができているので実行します。
sin471@LAPTOP-SISACVID:~/foxglove/build$ ./my_program
Foxgloveで確認
バイナリを実行したままもう一度Foxgloveを開き、ページをリロードします。そして生データのメッセージ
パネルのEnter a message path
の部分に/hello
と入れると/hello
トピックの内容、すなわち経過時間が表示されます。
また、プロット
パネルのシリーズを追加するにはクリック
をクリックし、出てきたサイドパネルのシリーズ
→Y-value path
に/hello.elapsed
と入力すると、経過時間がグラフで確認できます。
JSONを使わずに送る
先程は送信データをJSON形式にしてから送信していましたが、JSONを使わずに送信することをもできます。今回はLog
クラスを使って2種類のメッセージをログとして送ってみます。
#include <foxglove/foxglove.hpp>
#include <foxglove/server.hpp>
#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>
#include <atomic> //ドキュメントにはないので追加
using namespace std::chrono_literals;
int main(int argc, const char *argv[]) {
foxglove::WebSocketServerOptions options;
auto serverResult = foxglove::WebSocketServer::create(std::move(options));
if (!serverResult.has_value()) {
std::cerr << foxglove::strerror(serverResult.error()) << '\n';
return 1;
}
auto server = std::move(serverResult.value());
// トピック名を/infoとしてLog構造体を書き込むチャンネルを作成
auto channel = foxglove::schemas::LogChannel::create("/info").value();
// トピック名を/warnとしてLog構造体を書き込むチャンネルを作成
auto channel2 = foxglove::schemas::LogChannel::create("/warn").value();
auto start = std::chrono::steady_clock::now();
// Log until interrupted
static std::function<void()> sigint_handler;
std::atomic_bool done = false;
sigint_handler = [&] { done = true; };
std::signal(SIGINT, [](int) {
if (sigint_handler) {
sigint_handler();
}
});
while (!done) {
// ログレベルとメッセージを指定
foxglove::schemas::Log log;
foxglove::schemas::Log log2;
log.level = foxglove::schemas::Log::LogLevel::INFO;
log.message = "Hello, world!";
// 構造体を直接渡して書き込む
channel.log(log);
log2.level = foxglove::schemas::Log::LogLevel::WARNING;
log2.message = "Warning: Something might be wrong.";
channel2.log(log2);
std::this_thread::sleep_for(33ms);
}
return 0;
}
先ほどはfoxglove::schemas::RawChannel::create
だったのがfoxglove::schemas::LogChannel::create
に変化していることに注意してください。このように、送るデータ形式にあったチャンネルをcreate()
することでクラスのインスタンスを直接送信することができます。今回の場合はLog
クラスを送信するのでLogChannel
を使用してチャンネルを作成しました。
なお、ほかにも2次元座標を表すPoint2D
とPoint2DChannel
など、さまざまなクラスとチャンネルが用意されているので用途に合わせて適切なものを選択しましょう。
さて、先ほどと同様にコンパイル・実行ののちFoxgloveで確認します。
生データのメッセージ
パネルを2つにしてそれぞれ/info
,/warn
トピックを指定するとこんな感じ。メッセージやログレベルなどの内容が表示されていることがわかります。
シンク・チャンネル・トピック
最後に、説明していなかったシンク・チャンネル・トピックについて説明します。まだ理解しきれてないので少し曖昧です。
まず、シンクはデータの書き込み先です。今回はFoxgloveのWebソケットサーバにデータを送信していたのでシンクはWebソケットサーバです。ところで、Foxgloveではデータをmcapファイルとして保存することも可能であり、その場合のシンクはmcapファイルになります。
つぎに、チャンネルはデータを送信するためのものです。各データは最終的にチャンネルの機能を通じてシンクへと書き込まれます。今回見たようにチャンネルには複数の種類があり、JSONなどでデータを送信できるfoxglove::RawChannel
やログ形式のデータ(foxglove::schemas::Log
)を送信できるfoxglove::schemas::LogChannel
などがあります。
最後に、トピックはチャンネルのインスタンスを区別するための名前です。各トピック名はチャンネルのインスタンスごとに一意で、かつ/
ではじまる必要があります。今回の記事でいえば/hello
,/info
,/warn
がトピック名になります。Foxgloveではトピック名を指定することで可視化したいデータを指定します。
今回の例を使ってチャンネル・トピック・シンクの関係を図にするとこんな感じ。
参考