Skip to content

PoC of streaming API for external_control#528

Open
acmorrow wants to merge 5 commits into
UniversalRobots:masterfrom
acmorrow:streaming
Open

PoC of streaming API for external_control#528
acmorrow wants to merge 5 commits into
UniversalRobots:masterfrom
acmorrow:streaming

Conversation

@acmorrow

@acmorrow acmorrow commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

@urfeex - Here is what I had Claude put together to demonstrate the possibility of keeping open trajectories, per #524.

It includes:

  • The addition of the STREAM_START and STREAM_END control messages to the TrajectoryControlMesssage enumeration.
  • An integration test for streaming and an example program.
  • Support for configuring the cmake build with a target IP for integration testing. Not perfect since it gets baked into the build, but not terrible either.
  • Argument parsing fixes such that setting the robot IP on the command line works for various integration tests.
  • Updates to the external_contro.urscript to support stream start and stream end.

I'm using this right now and it seems to work for some basic cases. I haven't stress tested it or thought deeply about the various edge cases or error scenarios that this may need to cover.

The goal here is really just to put some code behind the ideas over in #524 so we can discuss further. I'm happy to rework any of this as necessary.

I will also leave some comments directly on the review for a few things I want to call out.


Note

High Risk
Changes real-time robot motion control in external_control.urscript and the trajectory protocol; incorrect streaming semantics or edge cases could abort trajectories or leave motion in an unexpected state.

Overview
Adds open-ended trajectory streaming for external control: producers can send spline points without declaring the total count up front, then finish with TRAJECTORY_STREAM_END and the total point count.

The C++ API gains TrajectoryControlMessage::TRAJECTORY_STREAM_START and TRAJECTORY_STREAM_END, with expanded docs on ReverseInterface / UrDriver::writeTrajectoryControlMessage. external_control.urscript handles stream start (sentinel trajectory_points_left, trajectory_streaming flag), stream end (recompute remaining points from the producer’s total), and cleaner failure vs post-STREAM_END drain in trajectoryThread.

Ships trajectory_streaming example and headless integration tests (test_trajectory_streaming.cpp) for success, underrun failure, and mid-stream cancel. Integration test CMake gains INTEGRATION_TESTS_ROBOT_IP and passes --robot_ip via ctest; several test main() parsers no longer stop after the first CLI flag.

Reviewed by Cursor Bugbot for commit 6f44bdf. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 6f44bdf. Configure here.

# unread remainder still in the OS socket buffer is therefore
# the producer's total minus the consumer's consumed count.
trajectory_points_left = params_mult[3] - (STREAMING_SENTINEL - trajectory_points_left)
trajectory_streaming = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

STREAM_END lacks streaming guard

High Severity

TRAJECTORY_MODE_STREAM_END always recomputes trajectory_points_left using STREAMING_SENTINEL, without checking that an open stream is active. If STREAM_END arrives during a finite TRAJECTORY_START trajectory, after cancel, or twice, trajectory_points_left is no longer sentinel-based and the update can jump to a huge negative value and abort or truncate motion while still reporting success.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f44bdf. Configure here.

# Producer signalled STREAM_END between the predicate check and
# the socket read. Drop any pending unread points and exit cleanly
# rather than spin one timeout per missing point.
trajectory_points_left = 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

False success after stream end

Medium Severity

After STREAM_END clears trajectory_streaming, a failed trajectory-socket read with trajectory_points_left still positive forces trajectory_points_left to zero and leaves trajectory_result as success. That matches a post-STREAM_END producer underrun or an incorrect total in STREAM_END, so the client can get success while motion stops before the declared point count is executed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f44bdf. Configure here.

# unread remainder still in the OS socket buffer is therefore
# the producer's total minus the consumer's consumed count.
trajectory_points_left = params_mult[3] - (STREAMING_SENTINEL - trajectory_points_left)
trajectory_streaming = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Streaming flag survives cancel

Low Severity

trajectory_streaming is set on TRAJECTORY_MODE_STREAM_START and cleared only on STREAM_END, not on TRAJECTORY_MODE_CANCEL or finite TRAJECTORY_MODE_RECEIVE. After cancel or switching to a declared-length trajectory without STREAM_END, the global stays true and can skew streaming-specific read-failure handling on later runs until another STREAM_END clears it.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f44bdf. Configure here.

}
const auto stream_end_send_wall = std::chrono::steady_clock::now();
g_my_robot->getUrDriver()->writeTrajectoryControlMessage(
urcl::control::TrajectoryControlMessage::TRAJECTORY_STREAM_END, k_num_points);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is an important note: the client is obligated to provide the total number of points streamed when calling TRAJECTORY_STREAM_END, so that we can properly reset the counter so it counts down to zero and the trajectory ends naturally.

Comment thread tests/CMakeLists.txt
option(INTEGRATION_TESTS "Build the integration tests that require a running robot / URSim" OFF)
option(POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL "Run Polyscope X tests that require remote control" OFF)
option(CHECK_RTDE_DOCS_RECIPE "Fetch the RTDE documentation to auto-generate a recipe containing all output fields and check that with the RTDE client. Requires python3 and pandas and an internet connection." OFF)
set(INTEGRATION_TESTS_ROBOT_IP "" CACHE STRING "If set, override the default robot IP (192.168.56.101) compiled into each integration test main() by passing --robot_ip <ip> to every ctest-registered integration test.")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure why this is CACHE - it probably doesn't need to be. I'm also happy to find another mechanism to inject this state rather than a CMake variable. This was just an easy way to get it started. The really relevant change is computing INTEGRATION_TESTS_ROBOT_IP_ARG and then plumbing that through via EXTRA_ARGS on the test invocations

@acmorrow

Copy link
Copy Markdown
Contributor Author

@urfeex - I see the bot has flagged a few things, I'll take a look at those and post an update.

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