-
Notifications
You must be signed in to change notification settings - Fork 2
Fix demo helper scripts that print null, fail on first run or watch the wrong entity #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
78a7d55
337c2ff
a207b6e
8555c5e
9291a08
fd3c9ed
eb6d75e
563e020
72d71e0
bd9892d
689b7a7
3ac180d
8fcf296
1b31245
b8ed687
414ad79
b1c545b
98b4983
3cd5208
b04d8f9
9631b69
a88ead7
afce7f1
cf23d16
2bf3e00
21ff2f7
dc7e47e
1c51000
ff9afc6
4228c98
7883fa4
2550a2e
2296fc1
d5a749b
81702ba
ae5b8e3
29441ce
8414038
0087513
2430b7d
b9e70a0
8615e9f
bb8fe33
2b2b002
5be1245
81b69d1
8c2a8d7
4cfaebd
fd13ceb
3c91cc0
216933e
605cac2
24650de
8718105
41683d8
80b0a41
b36e634
5fe2134
9ff2a4a
9e676a1
04a3b92
82402bd
c7d92d6
afe903f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,13 +12,19 @@ | |
|
|
||
| set -eu | ||
|
|
||
| CONTAINER="${CONTAINER_NAME:-$(docker ps --format '{{.Names}}' | grep -E '^moveit_medkit_demo(_nvidia)?(_local)?$' | head -n1)}" | ||
| ACTION="/panda_arm_controller/follow_joint_trajectory" | ||
| JOINT_NAMES='["panda_joint1","panda_joint2","panda_joint3","panda_joint4","panda_joint5","panda_joint6","panda_joint7"]' | ||
|
|
||
| # Duration in seconds for trajectory execution | ||
| DURATION_SEC=3 | ||
|
|
||
| # Time limit for one `ros2 action send_goal` run, and how many runs per goal. | ||
| # The controller can fail to deliver the goal response to a new CLI | ||
| # ("Failed to send goal response"). It then never runs the goal and the CLI | ||
| # waits forever, so a goal with no response is sent again. | ||
| SEND_TIMEOUT_SEC=30 | ||
| SEND_ATTEMPTS=3 | ||
|
|
||
| # --- Preset joint positions (radians) --- | ||
| # Ready: default MoveIt pose (from SRDF) | ||
| READY="[0.0, -0.785, 0.0, -2.356, 0.0, 1.571, 0.785]" | ||
|
|
@@ -45,6 +51,51 @@ RIGHT="[1.5, -0.785, 0.0, -2.356, 0.0, 1.571, 0.785]" | |
| WAVE="[0.0, -1.0, 0.0, -0.5, 0.0, 2.5, 0.785]" | ||
|
|
||
|
|
||
| have() { | ||
| command -v "$1" &> /dev/null | ||
| } | ||
|
|
||
| # True only if a LOCAL ros2 can actually reach the target action server. | ||
| # `ros2 node list` exits 0 even on an empty graph (wrong ROS_DOMAIN_ID, no | ||
| # multicast route), so a host with ROS 2 sourced but not connected to the | ||
| # demo looks identical to being inside the container. Checking that the | ||
| # action itself is listed avoids that false positive. A cold listing (no | ||
| # ros2 daemon yet) can miss a running server, so a miss is listed once more. | ||
| can_reach_action_locally() { | ||
| have ros2 || return 1 | ||
| ros2 action list 2> /dev/null | grep -qFx "${ACTION}" \ | ||
| || ros2 action list 2> /dev/null | grep -qFx "${ACTION}" | ||
| } | ||
|
|
||
| # Picks how goals are sent: USE_LOCAL=true for the local ros2, else | ||
| # `docker exec` into CONTAINER. Without a docker CLI the script runs inside | ||
| # the container or on a ROS host, so the local ros2 is the only way. | ||
| # On failure sets NOT_SENT_REASON and returns 1. | ||
| USE_LOCAL="" | ||
| CONTAINER="" | ||
| NOT_SENT_REASON="" | ||
| choose_transport() { | ||
| [[ -z "${USE_LOCAL}" ]] || return 0 | ||
| if ! have docker; then | ||
| if ! have ros2; then | ||
| NOT_SENT_REASON="needs the docker CLI or a sourced ROS 2 (ros2 CLI), found neither" | ||
| return 1 | ||
| fi | ||
| USE_LOCAL=true | ||
| return 0 | ||
| fi | ||
| if can_reach_action_locally; then | ||
| USE_LOCAL=true | ||
| return 0 | ||
| fi | ||
| CONTAINER="${CONTAINER_NAME:-$(docker ps --format '{{.Names}}' | grep -E '^moveit_medkit_demo(_nvidia)?(_local)?$' | head -n1)}" | ||
| if [[ -z "${CONTAINER}" ]]; then | ||
| NOT_SENT_REASON="no running moveit_medkit_demo container, start it with ./run-demo.sh or set CONTAINER_NAME" | ||
| return 1 | ||
| fi | ||
| USE_LOCAL=false | ||
| } | ||
|
|
||
| send_trajectory() { | ||
| local positions="$1" | ||
| local label="$2" | ||
|
|
@@ -64,27 +115,76 @@ send_trajectory() { | |
| } | ||
| }" | ||
|
|
||
| # Check if we're inside the container or outside | ||
| if command -v ros2 &> /dev/null && ros2 node list &> /dev/null 2>&1; then | ||
| # Inside the container (or ROS 2 env is set up) | ||
| ros2 action send_goal "${ACTION}" \ | ||
| control_msgs/action/FollowJointTrajectory \ | ||
| "${goal_msg}" \ | ||
| --feedback | ||
| else | ||
| # Outside — exec into container | ||
| docker exec -it "${CONTAINER}" bash -c " | ||
| source /opt/ros/jazzy/setup.bash && \ | ||
| source /root/demo_ws/install/setup.bash && \ | ||
| ros2 action send_goal ${ACTION} \ | ||
| control_msgs/action/FollowJointTrajectory \ | ||
| \"${goal_msg}\" \ | ||
| --feedback | ||
| " | ||
| if ! choose_transport; then | ||
| echo "Failed: ${label} (goal not sent: ${NOT_SENT_REASON})" >&2 | ||
| return 1 | ||
| fi | ||
|
|
||
| # `ros2 action send_goal` always exits 0, whatever the goal's outcome - | ||
| # the real result is in its own printed "Goal finished with status:" | ||
| # line, so capture output and parse that instead of the exit code. | ||
| # PYTHONUNBUFFERED keeps the lines printed before a timeout kills the CLI. | ||
| local attempt output rc | ||
| for ((attempt = 1; attempt <= SEND_ATTEMPTS; attempt++)); do | ||
| rc=0 | ||
| if [[ "${USE_LOCAL}" == true ]]; then | ||
| output=$(PYTHONUNBUFFERED=1 timeout "${SEND_TIMEOUT_SEC}" \ | ||
| ros2 action send_goal "${ACTION}" \ | ||
| control_msgs/action/FollowJointTrajectory \ | ||
| "${goal_msg}" \ | ||
| --feedback 2>&1) || rc=$? | ||
| else | ||
| # Outside the container: exec into it. No -it: this must also | ||
| # work without a TTY (CI, a pipe), and the command needs no stdin. | ||
| # timeout runs in the container: killing `docker exec` would | ||
| # leave the CLI running there. | ||
| output=$(docker exec "${CONTAINER}" bash -c " | ||
| source /opt/ros/jazzy/setup.bash && \ | ||
| source /root/demo_ws/install/setup.bash && \ | ||
| PYTHONUNBUFFERED=1 timeout ${SEND_TIMEOUT_SEC} \ | ||
| ros2 action send_goal ${ACTION} \ | ||
| control_msgs/action/FollowJointTrajectory \ | ||
| \"${goal_msg}\" \ | ||
| --feedback | ||
| " 2>&1) || rc=$? | ||
| fi | ||
| printf '%s\n' "${output}" | ||
| # An accepted or rejected goal has its answer. | ||
| if grep -qE '^(Goal accepted with ID|Goal was rejected)' <<< "${output}"; then | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Everything without a "Goal accepted/rejected" line is treated as a lost goal, including |
||
| break | ||
| fi | ||
| # Never sent (no container, no action server, a CLI error): sending | ||
| # again changes nothing. rc 124 is the timeout. | ||
| if ! grep -q '^Sending goal:' <<< "${output}"; then | ||
| if ((rc == 124)) && grep -q '^Waiting for an action server' <<< "${output}"; then | ||
| echo "Failed: ${label} (goal not sent: no action server ${ACTION} within ${SEND_TIMEOUT_SEC} s)" >&2 | ||
| elif ((rc == 124)); then | ||
| echo "Failed: ${label} (goal not sent: timed out after ${SEND_TIMEOUT_SEC} s)" >&2 | ||
| else | ||
| echo "Failed: ${label} (goal not sent: exit status ${rc})" >&2 | ||
| fi | ||
| return 1 | ||
| fi | ||
| # Sent with no response: the controller never runs it, so send again. | ||
| if ((attempt < SEND_ATTEMPTS)); then | ||
| if ((rc == 124)); then | ||
| echo "No goal response within ${SEND_TIMEOUT_SEC} s, sending the goal again" | ||
| else | ||
| echo "No goal response (exit status ${rc}), sending the goal again" | ||
| fi | ||
| fi | ||
| done | ||
|
|
||
| local status | ||
| status=$(printf '%s\n' "${output}" | grep -F 'Goal finished with status:' | tail -n1 | sed -E 's/.*status: *//') | ||
|
|
||
| echo "" | ||
| echo "✅ Done: ${label}" | ||
| if [[ "${status}" == "SUCCEEDED" ]]; then | ||
| echo "✅ Done: ${label}" | ||
| return 0 | ||
| fi | ||
| echo "Failed: ${label} (status: ${status:-UNKNOWN})" >&2 | ||
| return 1 | ||
| } | ||
|
|
||
| show_menu() { | ||
|
|
@@ -110,13 +210,19 @@ show_menu() { | |
| run_demo_cycle() { | ||
| echo "🔄 Running pick → place → home cycle..." | ||
| echo "" | ||
| send_trajectory "${PICK}" "pick" | ||
| local failed=0 | ||
| send_trajectory "${PICK}" "pick" || failed=1 | ||
| sleep 2 | ||
| send_trajectory "${PLACE}" "place" | ||
| send_trajectory "${PLACE}" "place" || failed=1 | ||
| sleep 2 | ||
| send_trajectory "${READY}" "ready (home)" | ||
| send_trajectory "${READY}" "ready (home)" || failed=1 | ||
| echo "" | ||
| echo "🔄 Cycle complete!" | ||
| if [[ "${failed}" -eq 0 ]]; then | ||
| echo "🔄 Cycle complete!" | ||
| else | ||
| echo "🔄 Cycle complete with failures" >&2 | ||
| fi | ||
| return "${failed}" | ||
| } | ||
|
|
||
| handle_choice() { | ||
|
|
@@ -138,15 +244,20 @@ handle_choice() { | |
|
|
||
| # --- Main --- | ||
|
|
||
| # If argument provided, run directly | ||
| # If argument provided, run directly. Reflect the goal's real result in the | ||
| # exit code instead of always exiting 0. | ||
| if [[ $# -gt 0 ]]; then | ||
| handle_choice "$1" | ||
| exit 0 | ||
| if handle_choice "$1"; then | ||
| exit 0 | ||
| else | ||
| exit 1 | ||
| fi | ||
| fi | ||
|
|
||
| # Interactive mode | ||
| # Interactive mode. A failed goal reports failure and the menu continues - | ||
| # it must not kill the session (set -e would, without this guard). | ||
| while true; do | ||
| show_menu | ||
| read -rp "Choose position (1-8, d, q): " choice | ||
| handle_choice "${choice}" | ||
| handle_choice "${choice}" || true | ||
| done | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same dead fallback in the joint-states block above: a 404 for
data/joint_statesprints"joint_names": nulland the hint never runs, because jq exits 0 on the error body. Usejq -e '.data | select(. != null) | {...}'.