Skip to content

Add a self-running demo and a one-step Windows start - #34

Merged
yassinsolim merged 58 commits into
mainfrom
feature/autoplay-demo
Oct 10, 2026
Merged

yassinsolim merged 58 commits into
mainfrom
feature/autoplay-demo

Conversation

@yassinsolim

Copy link
Copy Markdown
Member

Covers item 7 from the Oct 3 list: a demo that runs on its own, gives way when someone touches the controller, resumes when the controller is left idle, and starts in one step on Windows. Biomedical's project manager asked for this for orientation. Stacked on #31, because autoplay checks that drive_interface is sim.

What changes

  • autoplay:=true (with teleop:=true): the controller source now publishes joy_operator, and a new autoplay node passes it through to teleop's joy.
    • When the demo starts: after idle_s (30 s) without a button, stick or trigger moving.
    • What it plays: it enables teleop, sets 50% speed, holds A to go home, and lifts the arm in the upper group. In the Cartesian group it then draws a square and a vertical line and tilts the tool about its tip, the same sequence as the recorded clip.
    • In RViz: the tool path, the fixed tip point and a caption.
    • Starting the same way each loop: teleop's diagnostics (teleop.state, teleop.group) close the loop, and a second of silence makes teleop disable itself. So every loop starts the same way, whatever group, speed or pose the last person left behind.
    • Takeover: touching the controller stops the demo at once and passes the controller through, so the visitor drives from where the demo stopped.
  • Launch checks: autoplay refuses to run without teleop or with real drives.
  • scripts/windows-demo.cmd (calls windows-demo.ps1):
    • starts Docker Desktop if it isn't running;
    • opens the controller bridge in its own window when a working Python 3 exists (it tries py, python, python3, because the Store placeholder python.exe fails);
    • runs the new wslg-demo Compose service.
  • .gitattributes keeps .cmd files in CRLF.
  • BuildInstructions has a "Self-Running Demo" section.

How I tested it

  • test_demo_script.py runs the player against ArmTeleop, with teleop's 0.5 s input timeout and 2 Hz state reports:

    • From a fresh start: two loops each draw the first square edge at 38–44 mm in the Cartesian group at 50%, and the second square starts within 1 mm of the first.
    • From a messy state (enabled, Cartesian group, 10% speed, arm away from home): the square matches the fresh start within 1 mm.
  • test_autoplay_launch.py: with no controller, the demo reaches the enabled Cartesian group with the shoulder lifted. Then B on /joy_operator disables teleop, and the arm stays put for 3 s.

  • docker compose --progress plain build test: all 779 tests pass.

  • Windows script, short of the GUI run:

    • the PowerShell parser reports no errors;
    • the wslg-demo service renders with docker compose config;
    • on this PC, Python detection picked the Store Python 3.13, and the bridge started and ran.

    I didn't run the full RViz window here.

Replace the placeholder with the five-joint arm exported from
full-arm-smaller.SLDASM. The tools in scripts/cad regenerate the URDF
and meshes after CAD changes. demo_mode:=true sweeps each joint in turn
and reports a pass/fail row per joint on /diagnostics.

Supersedes #7.
Drive the five joints with an Xbox controller through placeholder MKS
SERVO42D/57D CAN drives, simulated until the real drives are connected.
On Windows, a Python-only XInput bridge sends the controller state to
the new wslg-teleop Compose service over UDP. The RViz camera now
follows the tool so the end of the arm stays in view.
# Conflicts:
#	waybionic_bringup/launch/ground_station.launch.py
# Conflicts:
#	waybionic_bringup/CMakeLists.txt
demo_mode:=True or 1 started joint_state_publisher_gui next to the demo,
because the launch compared the raw text with 'true'. And/Not substitutions
parse booleans the same way IfCondition does. The joint demo test now uses
True and fails without this change.

joint_demo rejects a speed_deg_s of zero or less at startup instead of never
finishing a move, and receives use_sim_time like the other nodes. The CAD
script's help now says which export --check-moves compares.
# Conflicts:
#	waybionic_bringup/launch/ground_station.launch.py
follow_camera:=false stopped the camera follower, so RViz lost the
view_focus frame it orbits. The follower now always runs and holds its
default focus when follow is false, and a launch test checks the frame.

teleop and joy_source use And/Or/Equals substitutions, so teleop:=True
works like IfCondition; the teleop test now uses True. The new nodes
receive use_sim_time, and the UDP receiver reads at most 100 packets per
poll so a flood cannot starve its other callbacks.
The arm is a base yaw, three parallel pitch joints and a roll through the
tool tip, so kinematics.py solves any tip position and tool tilt in closed
form from the URDF. The new Cartesian group (Y) moves the tip along straight
lines in base_link with the tilt fixed; LB keeps only the strongest axis.
At a joint speed limit or the workspace edge the whole step shrinks, so the
tip stops on the line, and the roll turns against the yaw to keep the tool's
heading.

Each drive now gets the next setpoint one period ahead and the speed that
reaches it in exactly one period, measured from its encoder, so all drives
arrive together. acc is 255 because a gentle drive ramp adds the same rpm/s
to every drive and the one with the largest change falls behind. In
simulation a sideways cut at 30:1 gearing stays within 7-17 um of the line,
compared with up to 186 um with the old 1.5x speed rule. At the 1:1
placeholder gearing it stays within about 0.13 mm, one encoder count at the
tip.
D-pad left/right changes the tool pitch while the tip stays in place, so
the shoulder, elbow and wrist move around a fixed point. The tilt uses the
same closed-form step as straight moves, so a joint limit stops it with the
tip still in place. It runs at up to 30 deg/s, scaled by the speed level.
- Start is refused while a tilt button or A is held, like the sticks.
- Homing clears the Cartesian rates, so releasing A can't resume the last move.
- Cartesian roll ramps with the acceleration limit and scales with the accepted step.
- The drives' setpoint one period ahead stays within the URDF joint limits, which the
  drive node now reads from robot_description.
- If one drive would pass max_rpm, every drive slows by the same factor, so they still
  arrive together.
- The roll compensation is documented as stopping the tool spinning about its own axis;
  that keeps a blade's heading only when the tool points straight down.
- The Cartesian launch test is registered where it won't conflict with #22.
Reviewed in #24, which was merged into its stacked base after #23 had
already been squash-merged into main, so it never reached main. This is
the same change, applied to main.

Drive the five joints with an Xbox controller through placeholder MKS
SERVO42D/57D CAN drives, simulated until the real drives are connected.
On Windows, a Python-only XInput bridge sends the controller state to
the new wslg-teleop Compose service over UDP. The RViz camera follows
the tool so the end of the arm stays in view; follow_camera:=false keeps
a fixed view.

teleop and joy_source use And/Or/Equals substitutions, so teleop:=True
works like IfCondition. The new nodes receive use_sim_time, and the UDP
receiver reads at most 100 packets per poll so a flood cannot starve its
other callbacks.
The drive node can now send its frames to real MKS SERVO42D/57D drives
through any python-can interface, chosen with the drive_interface and
drive_channel launch arguments. The simulation stays the default.

Because the encoders count from power-on, nothing is published or
driven until the arm is zeroed (92h) through ~/zero. Simulated drives
are zeroed at start-up. A drive that stops answering stops every
drive and must be zeroed again. Stale commands hold the last target,
and the drives are stopped when the node exits.

mks_drive_sim answers on a CAN interface like the drives do. A new CI
job runs the end-to-end test over vcan0. UDP multicast hands every
frame back to its sender, and MKS replies reuse the command's CAN ID,
so the bus drops its own echoes.
autoplay:=true puts a node between the controller and teleop. After 30
s without input, it plays the controller: it enables teleop, sets 50%
speed, homes, lifts the arm in the upper group, then draws a square
and a vertical line and tilts the tool about its tip in the Cartesian
group. RViz shows the path and a caption. Teleop's diagnostics close
the loop, so every run starts the same way whatever group, speed or
pose was left behind. Touching the controller stops the demo and
hands the arm over. Autoplay only runs with simulated drives.

scripts/windows-demo.cmd starts Docker Desktop if needed, the
controller bridge when Python is installed, and the new wslg-demo
service.
Copilot AI balanced review requested due to automatic review settings October 3, 2026 19:08
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: eeaf33bc-3200-4876-9083-1f1d15812592

📥 Commits

Reviewing files that changed from the base of the PR and between 55ab8b3 and f59771d.


📒 Files selected for processing (18)
  • .gitattributes
  • BuildInstructions.md
  • compose.yaml
  • scripts/windows-demo.cmd
  • scripts/windows-demo.ps1
  • waybionic_bringup/CMakeLists.txt
  • waybionic_bringup/launch/ground_station.launch.py
  • waybionic_bringup/package.xml
  • waybionic_bringup/test/test_autoplay_launch.py
  • waybionic_bringup/test/test_launch_checks.py
  • waybionic_teleop/package.xml
  • waybionic_teleop/setup.py
  • waybionic_teleop/test/test_autoplay_node.py
  • waybionic_teleop/test/test_can_drives.py
  • waybionic_teleop/test/test_demo_script.py
  • waybionic_teleop/waybionic_teleop/autoplay_node.py
  • waybionic_teleop/waybionic_teleop/demo_script.py
  • waybionic_teleop/waybionic_teleop/sim_arm_drives_node.py

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Startup bypasses the documented idle delay, and Python detection can select Python 2 instead of an available Python 3 installation.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds a self-running simulated-arm demo with controller takeover and one-step Windows startup.

Changes:

  • Adds autoplay sequencing, takeover logic, RViz markers, and tests.
  • Integrates autoplay into ROS launch and Docker Compose.
  • Adds Windows launch scripts and usage documentation.
File Description
waybionic_teleop/​waybionic_teleop/​demo_script.py Defines the scripted demonstration.
waybionic_teleop/​waybionic_teleop/​autoplay_node.py Switches between autoplay and operator input.
waybionic_teleop/​test/​test_demo_script.py Tests sequencing and state recovery.
waybionic_teleop/​setup.py Registers the autoplay executable.
waybionic_teleop/​package.xml Adds the geometry message dependency.
waybionic_bringup/​test/​test_autoplay_launch.py Tests launch-level playback and takeover.
waybionic_bringup/​launch/​ground_station.launch.py Adds autoplay launch configuration and validation.
waybionic_bringup/​CMakeLists.txt Registers the launch test.
scripts/​windows-demo.ps1 Starts Docker, controller bridge, and demo.
scripts/​windows-demo.cmd Provides a double-clickable Windows entry point.
compose.yaml Adds the WSLg demo service.
BuildInstructions.md Documents autoplay startup and operation.
.gitattributes Enforces CRLF for command scripts.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/windows-demo.ps1
Comment thread waybionic_teleop/waybionic_teleop/autoplay_node.py
yassinsolim and others added 11 commits October 3, 2026 13:46
Each drive must now confirm its mode, reply, enable and heartbeat
settings before the arm moves. Unconfirmed settings are sent again
every 0.5 s, so a lost frame can't leave a drive running without its
heartbeat stop. Both buses report whether a frame went out, and a
target the interface refused is not recorded as sent, so the next tick
sends it again.
windows-demo.ps1 now asks each candidate (py -3, python, python3)
whether it is Python 3, instead of accepting any interpreter that
prints a version. The docs and the node's docstring now say the demo
starts as soon as teleop is ready, and again after 30 s of idle.
actionlint (shellcheck SC2046) flagged the unquoted uname expansion.
On a busy CI runner the teleop node could still be loading the robot description when the test pressed Start, so the press was lost and the arm never moved. The test now sends idle packets until teleop reports its group and fresh controller input.
The tests could press Start before the teleop node had loaded the robot description or received controller input, so the press was lost on a busy machine. They now send idle packets until teleop reports its group and fresh input. Same fix as on #28.
- Joint commands that stop mid-move, say because teleop exited, now stop every drive where it is instead of letting it run on to the last target.
- The simulated drives integrate the full time since the last tick in 10 ms steps, so a pause longer than the heartbeat stops a moving drive, as on hardware.
- Axis coordinates take the whole int24 range, down to -0x800000.
A second robot_description replaced an enabled teleop with a disabled one without sending a hold, so the drives kept their last target.
A position-only JointState leaves every velocity at zero, so the stale-command
check never fired for an external publisher: the drives ran on to the last
target after the publisher died. The check now looks only at how long it has
been since the last command, latched to one stop and one warning so start-up
and idle holds stay quiet.
absolute_axis rejected -0x800000, which the manual's 3-byte signed coordinate
encodes. The node also capped the elapsed time it handed the drives at 100 ms,
so a scheduler stall longer than the heartbeat never stopped a moving drive the
way hardware would. The drive splits a long step into 10 ms motion steps and
counts all of it toward the heartbeat.
@yassinsolim
yassinsolim changed the base branch from feature/mks-can-host to main October 10, 2026 21:30
@yassinsolim
yassinsolim merged commit b80d6c2 into main Oct 10, 2026
13 checks passed
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