Skip to content

fix: stop ctrl write from fighting MuJoCo viewer when no controller is connected#121

Open
SHABRAWii wants to merge 3 commits into
unitreerobotics:mainfrom
SHABRAWii:fix/lowcmd-ctrl-timeout
Open

fix: stop ctrl write from fighting MuJoCo viewer when no controller is connected#121
SHABRAWii wants to merge 3 commits into
unitreerobotics:mainfrom
SHABRAWii:fix/lowcmd-ctrl-timeout

Conversation

@SHABRAWii
Copy link
Copy Markdown

Problem

In RobotBridge::run(), mj_data_->ctrl[] was written unconditionally
every 1ms regardless of whether a LowCmd message had ever been received.
This meant the bridge was resetting all joints to zero 1000 times/second,
constantly fighting the MuJoCo viewer's control tab sliders.

This is unlike the Python implementation where LowCmdHandler is a DDS
callback — it only writes ctrl when a message actually arrives.

Fix

Guard the ctrl write with !lowcmd->isTimeout(), which returns true
when no message has been received within the timeout window (default 1s).
Joints are now only driven when a real command is present, matching the
Python behavior.

Testing

  • Launch simulator with no controller connected → viewer control sliders
    are no longer overridden
  • Connect a controller and send LowCmd → joints respond normally

SHABRAWii added 3 commits May 22, 2026 04:32
Two race conditions crash the simulator on Reload:

1. PhysicsLoop calls mj_deleteData/mj_deleteModel while the bridge
   threads are still reading from those pointers. Fix: add two
   std::atomic<bool> globals (g_bridge_pause_request / g_bridge_paused)
   as a pause/resume handshake. PhysicsLoop waits for the bridge to stop
   before freeing memory. UnitreeSdk2BridgeThread destroys and recreates
   the bridge on each reload instead of holding stale pointers forever.

2. RecurrentThread::~RecurrentThread() destroys mFunc (the run() lambda)
   before sending pthread_cancel, and never sets mQuit. If the thread
   wakes between those two steps it calls the destroyed std::function and
   hits std::terminate(). Fix: replace RecurrentThread with a std::thread
   owned by RobotBridge, controlled by a std::atomic<bool> stop_flag_.
   pre_destroy() sets the flag and joins the thread — guaranteed clean
   exit before any destructor runs.
…mbers

The private section still declared the old `thread_` after the method
bodies were updated to use `stop_flag_` and `run_thread_`, leaving the
class in a non-compiling state.
Without this, run() unconditionally writes zeros to mj_data_->ctrl[]
1000 times/second even before any controller connects, constantly
resetting joints and fighting the MuJoCo viewer control tab.
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.

1 participant