Gazebo Custom Sensor Plugin: Architecture and Trust Boundaries
Design a decoupled Gazebo sensor plugin that publishes synthetic data through transport without modifying the core physics engine.
22 Apr 2026, 09:49 UTC

Problem: You Need Synthetic Sensor Data Without Forking Gazebo
You are simulating a robot and need a camera, lidar, or custom range sensor to emit data, but you do not want to modify Gazebo's core source code or run every sensor calculation inside the physics step. The physics loop runs at a fixed step (dt), and coupling sensor generation to it wastes CPU when the sensor should publish slower or faster than the simulation clock.
Useful Takeaway
Build the smallest possible plugin: a world plugin that hooks into Gazebo's update loop, generates synthetic readings on its own schedule, and publishes them through Gazebo's transport layer. The physics engine never sees the sensor logic, and client tools can inspect the topic independently.
Requirements
- Sensor data must be available to other processes or plugins without blocking physics.
- Plugin must load from a shared object without recompiling Gazebo.
- Update rate must be configurable independently of physics dt.
- Design must work within Gazebo Classic (version 11 and below) or Gazebo (formerly Ignition), but not assume both share the same plugin API.
Version assumption: This note targets Gazebo Classic. The newer Gazebo (Ignition) series uses a different plugin and transport system; do not copy class names or command flags between them without checking the release documentation.
Smallest Suitable Design
Implement one plugin with three responsibilities:
- Hook into the world update loop. Register a callback that Gazebo invokes each simulation iteration. This callback is the clock source, not the physics solver.
- Throttle or trigger generation. Compare the simulated time against the last publish time. Only generate a reading when the sensor's target interval has elapsed.
- Publish through transport. Serialize the reading (commonly using Protobufs) and send it on a named topic. Other nodes subscribe to that topic; the plugin does not need to know who is listening.
This keeps the sensor decoupled from the physics engine. If the physics step slows down, the sensor can still publish on schedule, or skip cycles, depending on how you write the time check.
Example configuration sketch
<plugin name="custom_sensor_plugin" filename="libcustom_sensor.so"> <topic>/gazebo/my_robot/custom_sensor</topic> <update_rate>30</update_rate> </plugin>
Run the world from the directory that contains the plugin shared object:
# User-level permissions are sufficient; no sudo required. gz server -v 4 my_world.world
Expected check: the console should emit lines indicating the plugin is loading during world initialization. If you do not see a load message, the plugin path or filename is likely wrong. Risk: an incorrect path may produce no visible error in the GUI, so always check stdout/stderr.
Trust and Data Boundaries
Gazebo separates the simulation server from the client. The plugin lives in the server process and writes to the transport layer. Clients, including visualizers and external robot code, read topics over that transport. The trust boundary is the topic name: any process that can subscribe to the topic receives the data, and any process that can publish to it could impersonate the sensor if authentication is not enabled.
Treat sensor topics as untrusted input downstream. If a controller consumes /gazebo/my_robot/custom_sensor, validate message fields and timestamps before using them in control logic.
Operational Checks
- Verify plugin loading: watch console output for plugin load logs when the world starts.
- Verify topic publication: run
gz topic -e /gazebo/my_robot/custom_sensorand confirm messages appear at the configured rate. - Verify physics stability: run a static world with the plugin loaded and vary physics dt. Object jitter indicates solver instability, not necessarily a sensor bug.
Failure Modes
| Failure | Likely Cause | Check |
|---|---|---|
| Plugin does not appear | Wrong filename path or missing GAZEBO_PLUGIN_PATH | Inspect console for load errors; echo $GAZEBO_PLUGIN_PATH |
| Topic silent | Update rate gate never triggers or topic name mismatch | Compare plugin topic string with gz topic -l list |
| Fast objects pass through colliders | Physics tunneling from large dt or thin mesh colliders | Reduce dt or replace mesh colliders with primitive boxes/spheres |
| CPU spikes | Complex mesh colliders or sensor generating at physics frequency | Profile with primitive colliders; throttle sensor loop |
Conditions That Would Change the Design
- If you migrate to Gazebo (Ignition), rewrite the plugin; the API and transport layer are not drop-in compatible.
- If the sensor needs ray casting against actual geometry, use Gazebo's native ray sensor plugin instead of synthetic injection.
- If you require hardware-exact timing, a Gazebo plugin is insufficient; use an external process synchronized to simulation time.
Limitation and Practical Verification
This is an architectural pattern, not a tested implementation. Before relying on it, build a minimal world containing only the plugin, confirm the load log, and echo the topic with gz topic -e. If the topic publishes at the expected interval and physics objects remain stable under your chosen dt, the design is sound for your environment.
Rollback: delete the compiled shared object (rm libcustom_sensor.so). No system configuration or Gazebo core files are modified.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.