Skip to content

Plugin configs (PLUGIN_REGISTRY) + ROS2 clock/gantry/odometry plugins#160

Open
juan-g-bonilla wants to merge 4 commits into
dev/gjuaga/hook-systemfrom
dev/gjuaga/custom-hooks
Open

Plugin configs (PLUGIN_REGISTRY) + ROS2 clock/gantry/odometry plugins#160
juan-g-bonilla wants to merge 4 commits into
dev/gjuaga/hook-systemfrom
dev/gjuaga/custom-hooks

Conversation

@juan-g-bonilla

@juan-g-bonilla juan-g-bonilla commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Builds on the lifecycle hook registry (base branch #158): the step loop emits phase events, and behavior attaches as callbacks. Core features (video recording, simulator bridge, virtual gantry) already run this way. This PR lets config/CLI attach the same kind of behavior without subclassing a backend, and ships the ROS2 example plugins.

What this adds

  • A PluginConfig family (PLUGIN_REGISTRY, group holosoma.config.plugin) selectable as the dynamic-dict CLI field plugin.<key>:<preset>.
  • A plugin is any class constructed as cls(cfg, simulator) that registers hooks on simulator.hooks in __init__duck-typed, no base class. It pairs with a PluginConfig whose get_cls() returns the runtime class. Same registry the core features use.
  • Plugins are instantiated in BaseSimulator.__init__ (from FullSimConfig.plugin), so their hooks are registered before the loop emits any phase.
  • Threaded through both entry points: RunSimConfig.plugin (sim2sim) and ExperimentConfig.pluginbase_taskFullSimConfig (training).
  • Shipped presets: none (no-op), clock_publish + gantry_control (ROS2), and odometry — a self-sourced (non-camera) egress plugin that reads robot_root_states each control step and publishes nav_msgs/Odometry.

Why: one mechanism for step-loop side effects, core and custom alike — added logging, ROS2 bridges, teleop, egress, etc. become config presets (packaged entry point or --import-file) instead of loop forks.

API changes

  • New field FullSimConfig.plugin: dict[str, PluginConfig] (default {}), plus registry-bound RunSimConfig.plugin and ExperimentConfig.plugin (and EnvConfig.plugin, threaded through get_tyro_env_config). Additive — existing configs unaffected.
  • New public PluginConfig ABC (holosoma.config_types.plugin): subclass with your knobs as dataclass fields + implement get_cls(). Also NoOpPluginConfig, ClockPublishPluginConfig, GantryControlPluginConfig, ROS2OdometryPluginConfig.
  • New entry-point group holosoma.config.plugin for packaged plugin presets.
  • New extra holosoma[ros2] — opt-in marker for the ROS2 plugins (install rclpy from your ROS2 distro / RoboStack; the impls import it lazily so core stays importable without ROS).

Behavior changes

  • None by default. The plugin field is empty unless you select plugins, so no existing run changes.
  • When selected, a plugin's hooks fire at lifecycle phases (from the base branch): FRAME_BEGIN, PRE_STEP, POST_STEP, FRAME_END (periodic), EPISODE_START / EPISODE_END, CLOSE (once, reverse order).
  • Rate fields accept an int decimation or a frequency string ("100Hz", ">100Hz", "<100Hz"), resolved against the phase's base rate; the registry sub-samples natively.

Usage

# ROS2: publish /clock at 100 Hz + drive the gantry over three topics
python -m holosoma.run_sim plugin.clk:clock_publish --plugin.clk.publish-every=100Hz plugin.g:gantry_control

# ROS2 odometry: publish the robot base pose/velocity as nav_msgs/Odometry
python -m holosoma.run_sim plugin.odom:odometry --plugin.odom.topic=/odom

Write-your-own (config + duck-typed class), phases, and cadence are documented in docs/writing-extensions.md (Plugins section).


Stacked on #158 (lifecycle hook system). Review after / alongside it; the diff here is the plugin family only.

@juan-g-bonilla juan-g-bonilla self-assigned this Jul 11, 2026

@samuelgundry samuelgundry left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great to have examples that demonstrate the power of the hook-plugin system. Per comments below, I'll sync first with you and brain storm whether we want refinements now or deferred.

Comment thread src/holosoma/holosoma/simulator/shared/ros2_plugins.py
Comment thread src/holosoma/holosoma/config_types/hook.py Outdated
Comment on lines +60 to +62
@dataclass(frozen=True)
class LogRobotStateHookConfig(HookConfig):
"""Reference hook config: periodically log the robot base pose.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dislike single config_types/hook.py for everything. It's mixes framework (HookConfig) with and forces coupling of otherwise unrelated user-facing code but anyways, it is what it is with our current config system.

Comment thread src/holosoma/holosoma/simulator/base_simulator/base_simulator.py Outdated
@juan-g-bonilla juan-g-bonilla force-pushed the dev/gjuaga/custom-hooks branch from f45769d to 76b63ed Compare July 13, 2026 22:39
@juan-g-bonilla juan-g-bonilla force-pushed the dev/gjuaga/hook-system branch from 2457087 to 1c4d0c5 Compare July 14, 2026 23:38
@juan-g-bonilla juan-g-bonilla force-pushed the dev/gjuaga/custom-hooks branch from 76b63ed to 77c2d97 Compare July 14, 2026 23:38
@juan-g-bonilla juan-g-bonilla changed the base branch from dev/gjuaga/hook-system to main July 14, 2026 23:45
@juan-g-bonilla juan-g-bonilla force-pushed the dev/gjuaga/custom-hooks branch from 77c2d97 to a85eeb4 Compare July 14, 2026 23:53
@juan-g-bonilla juan-g-bonilla changed the base branch from main to dev/gjuaga/hook-system July 14, 2026 23:54
@juan-g-bonilla juan-g-bonilla changed the title Hook configs + ROS2 gantry control + ROS2 /clock topic Plugin config family (PLUGIN_REGISTRY) + ROS2 clock/gantry/odometry plugins Jul 14, 2026
@juan-g-bonilla juan-g-bonilla changed the title Plugin config family (PLUGIN_REGISTRY) + ROS2 clock/gantry/odometry plugins Plugin configs (PLUGIN_REGISTRY) + ROS2 clock/gantry/odometry plugins Jul 15, 2026
Add a plugin family on top of the lifecycle hook registry (simulator.hooks, Phase). A
plugin is any class constructed as cls(cfg, simulator) that registers hooks on
simulator.hooks in __init__ (duck-typed — no base class). Each pairs with a PluginConfig
(CLI-visible knobs, get_cls() returns the runtime class), registered in PLUGIN_REGISTRY and
selectable as the dynamic-dict field plugin.<key>:<preset>. Plugin configs flow through
FullSimConfig.plugin and are instantiated in BaseSimulator.__init__.

Ships the reference plugins: the none no-op (builtin_plugins.py) and the ROS2 examples
clock_publish / gantry_control (ros2_plugins.py; rclpy imported lazily so core stays
ROS-free). Documents the pattern (write-your-own plugin, phases, cadence) in
docs/writing-extensions.md.
A plain (non-camera) plugin that reads robot_root_states each control step and publishes
nav_msgs/Odometry on FRAME_END, mirroring ClockPublishPlugin. World->body twist via
quat_rotate_inverse. Registered as the 'odometry' preset. rclpy/nav_msgs imported lazily so
the config layer stays ROS-free. Ported from dev/gabmerri/sim-cam-service-update, re-homed
from the old SensorEgressDriver onto the plugin/hook system.
ExperimentConfig gains a plugin dict field (resolved from PLUGIN_REGISTRY); EnvConfig carries
it through get_tyro_env_config; base_task passes plugin=tyro_config.plugin into FullSimConfig.
Previously plugin was wired only on RunSimConfig->FullSimConfig, so plugins (clock/gantry/
odometry and, on the camera branch, egress) did not run during training. Now they do.
@juan-g-bonilla juan-g-bonilla force-pushed the dev/gjuaga/custom-hooks branch from a85eeb4 to 8998403 Compare July 15, 2026 00:30
@juan-g-bonilla juan-g-bonilla marked this pull request as ready for review July 15, 2026 01:36
…e objects

scene_spawn_assert.py spawns a full but UN-ACTUATED robot alongside the scene; it collapses
under gravity and its falling limbs strike any free object spawned near the origin (e.g.
physics-box's pbox at [0,0,0.6], multibody's bodies at [0.4,0,0.6]). That produced chaotic
per-env/per-run displacement that failed the fall check non-deterministically on the isaacgym
lane (physics-box / multibody-override): a free body would barely move or get nudged upward in
one of the 4 envs, so fell_all_envs went False. Park the robot at y=-20 (clear of every scene
object and off the +x per-env spread axis) so no scene preset can be contaminated. Verified on
gjuaga-test1: physics-box/multibody/multibody-override + the full isaacgym scene-spawn file now
pass deterministically (13 passed, repeated runs). Test-harness only; no product code changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants