Fixed runtime adapter for Foxglove.
FoxgloveBridge does not own parameters, define robot TF, or discover arbitrary
LibXR topics. It adapts Zeq's fixed runtime surfaces to Foxglove:
- Runtime parameters are read/written through Zeq's RamFS-backed
RuntimeParametersobject. TransformTopic::TOPIC_NAMEis encoded as native Foxglove protobufFrameTransformsamples on the conventional/tfchannel.- Configured visual models are published on
/sceneasSceneUpdateentities that reference assets by URI.
There is intentionally no foxglove/announce topic. The runtime topic surface
is convention-based: parameters and TF have stable topic names, and Foxglove is
just one backend consuming them.
Instantiate FoxgloveBridge like any other runtime module. With no constructor
arguments it starts the parameter API and /tf bridge, while assets and scene
models remain empty:
modules:
- id: foxglove
name: FoxgloveBridge
constructor_args: {}Robot-specific startup code can pass FoxgloveBridge::Config to set host/port
and provide scene assets.
OnMonitor() flushes queued /tf messages to the Foxglove WebSocket server. If
scene models were configured, it also republishes the small /scene URI update
at low rate so newly opened Foxglove panels can discover the model. Parameter
get/set requests are handled through the Foxglove SDK parameter API.
The bridge directly subscribes to the transport-neutral
TransformTopic::TOPIC_NAME LibXR topic from TransformTopics. Each
TransformTopic::TransformStamped sample is encoded as a native Foxglove
protobuf FrameTransform on /tf, which the 3D panel recognizes.
The generic zeq::io::TfPublisher<TfTree> component consumes Zeq TF state,
walks that tree, and publishes plain transform samples through LibXR Topic. The
concrete omni infantry robot model lives in
User/description/omni_infantry_description.hpp; its .cpp file only binds
that model to the runtime zeq::io::TfPublisher component name. Neither side
has a Foxglove dependency.
Removing FoxgloveBridge disables Foxglove visualization but does not change TF
state publication or the generic runtime topic stream.
The bridge follows the ROS-style model flow: /scene carries lightweight
ModelPrimitive::url references, and configured assets are served through
Foxglove's Assets capability only when the client requests them. The bridge
does not embed large mesh payloads inside each SceneUpdate.
Resources are supplied through FoxgloveBridge::Config, usually from
User/app_main.cpp or another robot-specific startup file:
static constexpr std::array<FoxgloveBridge::Asset, 1> assets = {{
{
.uri = "package://robot/meshes/base_link.stl",
.path = "webots/meshes/robot/base_link.stl",
.media_type = "model/stl",
},
}};
static constexpr std::array<FoxgloveBridge::SceneModel, 1> scene_models = {{
{
.id = "robot/base_link",
.frame_id = "base_link",
.asset_uri = "package://robot/meshes/base_link.stl",
},
}};Asset paths may be absolute or relative to the workspace/current directory. Scene entities are frame-locked to the configured TF frames.
With the Parameters capability, Foxglove clients can get/set named scalar
parameters such as live PID gains. zeq::io::ParameterServer owns the actual
runtime parameter state and exposes the same zeq::io::RuntimeParameters
object through a RamFS command file and a RamFS custom node.
FoxgloveBridge looks up that object from RamFS and directly adapts Foxglove
get/set requests to SnapshotParameters(), GetParameter(), and
SetParameter(). Terminal, Foxglove, and future tools therefore share the same
control surface instead of routing parameter state through a topic protocol.
The bridge reads LibXR::Timebase::GetMicroseconds() directly and converts it
to nanoseconds for Foxglove log time. When the timebase still reports zero, it
falls back to the message timestamp and then wall time. Simulation-specific
clock ownership remains in LibXR's Webots backend; no Foxglove-specific clock
callback is required.