Skip to content

Robot api 10.14 - #549

Draft
urfeex wants to merge 39 commits into
UniversalRobots:masterfrom
urfeex:robot_api_10.14
Draft

Robot api 10.14#549
urfeex wants to merge 39 commits into
UniversalRobots:masterfrom
urfeex:robot_api_10.14

Conversation

@urfeex

@urfeex urfeex commented Aug 7, 2026

Copy link
Copy Markdown
Member

This adds the new RobotAPI endpoints coming up in PolyScope X 10.14.

This implements the following commands:

  • commandClosePopup()
  • commandCloseSafetyPopup()
  • commandShutdown()
  • commandPopup(text, title)
  • commandAddToLog(text)
  • commandPolyscopeVersion()
  • commandGetRobotModel()
  • commandGetSerialNumber()
  • commandGenerateFlightReport()
  • commandDownloadSupportFiles(save_path)

Note

Medium Risk
Touches the dashboard/Robot API client used for robot control, including shutdown and streamed file downloads. Version gating and HTTP handling changes can affect existing PolyScope X integrations.

Overview
Adds PolyScope X 10.14 Robot API coverage to the dashboard client so commands that previously threw NotImplementedException now work when the robot API is new enough.

Newly implemented on X: popup open/close (optional title), close safety popup, shutdown, add-to-log, PolyScope version / robot model / serial number, generate flight report, and commandDownloadSupportFiles (X-only; streams a zip to disk via a temp file). Existing commands now go through a shared g_command_list + assertHasCommand check against Robot API vs marketing versions.

HTTP handling treats any 2xx as success, adds DELETE, and tracks connection state. CI gains a 10.14 preview URSim matrix entry; docs list the 10.14 feature groups.

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

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 54.11765% with 78 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.92%. Comparing base (dde4869) to head (e426a64).

Files with missing lines Patch % Lines
src/ur/dashboard_client_implementation_x.cpp 50.96% 62 Missing and 14 partials ⚠️
...lient_library/ur/dashboard_client_implementation.h 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #549      +/-   ##
==========================================
- Coverage   80.40%   79.92%   -0.49%     
==========================================
  Files         116      117       +1     
  Lines        6976     7098     +122     
  Branches     3083     3164      +81     
==========================================
+ Hits         5609     5673      +64     
- Misses        987     1034      +47     
- Partials      380      391      +11     
Flag Coverage Δ
check_version_ur10-3.15.8 11.31% <0.58%> (-0.44%) ⬇️
check_version_ur10e-10.11.0 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur10e-5.15.2 11.50% <0.58%> (-0.05%) ⬇️
check_version_ur12e-10.12.1 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur12e-5.25.1 11.35% <0.58%> (-0.16%) ⬇️
check_version_ur15-10.12.1 11.35% <0.58%> (-0.20%) ⬇️
check_version_ur15-5.25.1 11.31% <0.58%> (-0.40%) ⬇️
check_version_ur16e-10.12.1 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur16e-5.25.1 11.50% <0.58%> (-0.20%) ⬇️
check_version_ur18-10.12.1 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur18-5.25.1 11.31% <0.58%> (-0.40%) ⬇️
check_version_ur20-10.12.1 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur20-5.25.1 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur3-3.14.3 11.50% <0.58%> (-0.05%) ⬇️
check_version_ur30-10.12.1 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur30-5.25.1 11.50% <0.58%> (-0.01%) ⬇️
check_version_ur3e-10.11.0 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur3e-5.9.4 11.35% <0.58%> (-0.16%) ⬇️
check_version_ur5-3.15.8 11.35% <0.58%> (-0.38%) ⬇️
check_version_ur5e-10.11.0 11.35% <0.58%> (-0.25%) ⬇️
check_version_ur5e-5.12.8 11.31% <0.58%> (-0.29%) ⬇️
check_version_ur7e-10.11.0 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur7e-5.22.2 11.31% <0.58%> (-0.40%) ⬇️
check_version_ur8long-10.12.1 11.31% <0.58%> (-0.20%) ⬇️
check_version_ur8long-5.25.1 11.31% <0.58%> (-0.25%) ⬇️
python_scripts 75.90% <ø> (ø)
start_ursim 85.57% <ø> (-0.38%) ⬇️
ur20-latest 75.70% <42.94%> (+0.10%) ⬆️
ur5-3.14.3 75.58% <42.94%> (+0.19%) ⬆️
ur5e-10.11.0 69.80% <46.47%> (-0.46%) ⬇️
ur5e-10.12.0 ?
ur5e-10.7.0 69.59% <40.58%> (+0.02%) ⬆️
ur5e-5.9.4 76.03% <42.94%> (+0.18%) ⬆️
ur7e-10.13.0 71.95% <51.76%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@urfeex
urfeex marked this pull request as ready for review August 20, 2026 15:17
@urfeex
urfeex requested review from a team and a balanced review from Copilot August 20, 2026 15:17
Comment thread src/ur/dashboard_client_implementation_x.cpp
Comment thread src/ur/dashboard_client_implementation_x.cpp Outdated
Comment thread src/ur/dashboard_client_implementation_x.cpp
Comment thread src/ur/dashboard_client_implementation_x.cpp

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.

Pull request overview

Adds PolyScope X 10.14 Robot API support to the dashboard client.

Changes:

  • Implements popup, shutdown, logging, robot-information, flight-report, and support-file endpoints.
  • Centralizes Robot API version gating and accepts all successful 2xx responses.
  • Adds compatibility documentation, tests, a 10.14 CI target, and an updated program fixture.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
.github/workflows/ci.yml Adds the URSim 10.14 preview test target.
doc/architecture/dashboard_client.rst Documents X-only support-file downloads.
doc/polyscope_compatibility.rst Lists Robot API feature availability by version.
include/ur_client_library/ur/dashboard_client.h Exposes popup titles and support-file downloads.
include/ur_client_library/ur/dashboard_client_implementation.h Extends the implementation interface.
include/ur_client_library/ur/dashboard_client_implementation_g5.h Adds G5-compatible overrides.
include/ur_client_library/ur/dashboard_client_implementation_x.h Defines Robot API command metadata and X endpoints.
src/ur/dashboard_client.cpp Forwards the new public API calls.
src/ur/dashboard_client_implementation_g5.cpp Handles the updated popup signature and X-only command.
src/ur/dashboard_client_implementation_x.cpp Implements the new Robot API requests and downloads.
tests/resources/upload_prog.urpx Refreshes the PolyScope X program fixture.
tests/test_dashboard_client.cpp Updates facade mocks and forwarding tests.
tests/test_dashboard_client_g5.cpp Verifies the X-only command throws on G5.
tests/test_dashboard_client_x.cpp Adds PolyScope X 10.14 integration coverage.
Suppressed comments (5)

src/ur/dashboard_client_implementation_x.cpp:389

  • Directly embedding log_text makes the request body invalid JSON for normal log messages containing quotes, backslashes, newlines, or other control characters. Serialize this value with the JSON library before posting it.
  const std::string message = R"({"message": ")" + log_text + R"("})";

src/ur/dashboard_client_implementation_x.cpp:390

  • This unconditionally mirrors the caller's log entry to process stdout, bypassing the library's configured logger and potentially exposing sensitive text in service/CI logs. Sending the entry to the robot should not also print it locally.
  std::cout << message << std::endl;

src/ur/dashboard_client_implementation_x.cpp:563

  • The endpoint returns a ZIP archive, but the destination is opened in text mode. On Windows, newline translation can modify bytes and corrupt the archive; open it in binary mode.
    std::ofstream save_file(save_path, std::ios_base::out);

src/ur/dashboard_client_implementation_x.cpp:574

  • The write result is never checked, so a disk-full or short-write failure still returns the successful HTTP response and reports that the archive was downloaded. Check the stream after writing and closing, and return ok = false on failure.
    save_file << response.message;

    response.message = "Downloaded support files to " + save_path;

src/ur/dashboard_client_implementation_x.cpp:578

  • This is the support-file download path, so reporting a failed program download is misleading during diagnosis.
    URCL_LOG_ERROR("Failed to download program. Response message: %s", response.message.c_str());

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

Comment thread src/ur/dashboard_client_implementation_x.cpp Outdated
Comment thread src/ur/dashboard_client_implementation_x.cpp Outdated
Comment thread src/ur/dashboard_client_implementation_x.cpp Outdated
Comment thread src/ur/dashboard_client_implementation_x.cpp Outdated
Comment thread tests/test_dashboard_client_x.cpp Outdated
Comment thread tests/test_dashboard_client_x.cpp Outdated
}
else
{
auto response = dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip");
Comment thread include/ur_client_library/ur/dashboard_client_implementation.h
Comment thread doc/polyscope_compatibility.rst Outdated
Comment thread include/ur_client_library/ur/dashboard_client_implementation_x.h
@urfeex

urfeex commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

This should be ready to review now. The failing test shows a model clash between the RobotAPI and the primary interface. For a ur7e, the RobotAPI reports a UR7, the primary interface a UR5. I guess this is expected behavior, since for the controller a UR7 is a UR5. We would need to update this.

@urrsk what would be your preferred solution? In the helpers we have robotTypeFromString() which reflects what the primary interface reports and defines. We could have another conversion function, that resolves "ur7e" to "UR7" and compare the response to the converted model environment variable that we have available in CI (which is the mechanism we use to test the same functionality on the primary interface).

Edit: I just realized, there's a lot of AI review I want to look at first. Converting back to draft.

@urfeex
urfeex marked this pull request as draft August 21, 2026 06:46
@urfeex
urfeex requested a balanced review from Copilot August 21, 2026 08:48

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.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

#else // _WIN32

std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".tmp")).string();
int tmp_fd = _s_open_s(temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _S_IREAD | _S_IWRITE);

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.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.

Suppressed comments (3)

include/ur_client_library/ur/dashboard_client_implementation.h:327

  • Replacing this pure virtual signature breaks every downstream DashboardClientImpl subclass that currently overrides the one-argument method. Preserve the existing virtual and add a title-aware overload with a default implementation that delegates to the legacy method, so external implementations remain source-compatible.
  virtual DashboardResponse commandPopup(const std::string& popup_text, const std::string& popup_title = "") = 0;

src/ur/dashboard_client_implementation_x.cpp:450

  • A non-JSON HTTP error is parsed before response.ok is checked, causing this query to throw instead of returning its failed DashboardResponse. Restrict JSON parsing to successful responses.
  auto json_data = json::parse(response.message);
  if (response.ok)
  {
    response.data["serial_number"] = std::string(json_data["serialNumber"]);

src/ur/dashboard_client_implementation_x.cpp:437

  • This parses error responses before testing response.ok; a plain-text or empty 4xx/5xx body therefore raises a JSON exception rather than returning the API failure. Move parsing inside the success branch.
  auto json_data = json::parse(response.message);
  if (response.ok)
  {
    response.data["robot_model"] = std::string(json_data["robotType"]);

Comment thread include/ur_client_library/ur/dashboard_client.h
Comment thread src/ur/dashboard_client_implementation_x.cpp
Comment thread src/ur/dashboard_client_implementation_x.cpp Outdated

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.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

src/ur/dashboard_client_implementation_x.cpp:563

  • The third parameter of post is bool debug, not a content type, so this string literal is implicitly converted to true. The helper already sends application/json; remove the misleading argument.
    response = post(endpoint, "", "application/json");

src/ur/dashboard_client_implementation_x.cpp:698

  • std::filesystem::rename does not replace an existing destination on Windows, so repeat downloads fail there despite this method and its test claiming overwrite support. Use a Windows replacement primitive such as MoveFileExW(..., MOVEFILE_REPLACE_EXISTING) while retaining atomic rename on POSIX.
    // std::filesystem::rename replaces an existing destination atomically on POSIX and
    // uses MoveFileExW(MOVEFILE_REPLACE_EXISTING) on Windows, so repeat downloads to
    // the same path work correctly on both platforms.
    std::error_code ec;
    std::filesystem::rename(temp_save_path, save_path, ec);

include/ur_client_library/ur/dashboard_client.h:408

  • The default argument preserves source compatibility only; replacing the one-parameter overload removes its mangled symbol and breaks ABI for existing binaries. The corresponding virtual signature change also breaks external DashboardClientImpl subclasses. Keep the one-parameter overload delegating to a new two-parameter overload, and preserve the old implementation virtual contract.
  bool commandPopup(const std::string& popup_text, const std::string& popup_title = "");

tests/test_dashboard_client_x.cpp:727

  • response is never used because all assertions below are commented out. The CI build enables -Wall -Wextra with warnings as errors, so this produces an unused-variable build failure; either assert the response or avoid binding it.
    auto response = dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip");

Comment thread src/ur/dashboard_client_implementation_x.cpp
@urfeex
urfeex marked this pull request as ready for review August 21, 2026 15:28
Copilot AI review requested due to automatic review settings August 21, 2026 15:28

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.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (2) — in code that hasn't changed since the last review.

src/ur/dashboard_client_implementation_x.cpp:698

  • std::filesystem::rename does not replace an existing destination on Windows, so the second download to the same path fails despite the stated overwrite behavior. Use a Windows replacement primitive such as MoveFileExW(..., MOVEFILE_REPLACE_EXISTING) (while retaining POSIX rename) rather than removing the destination first, which would lose atomicity.
    // std::filesystem::rename replaces an existing destination atomically on POSIX and
    // uses MoveFileExW(MOVEFILE_REPLACE_EXISTING) on Windows, so repeat downloads to
    // the same path work correctly on both platforms.
    std::error_code ec;
    std::filesystem::rename(temp_save_path, save_path, ec);

src/ur/dashboard_client_implementation_x.cpp:639

  • The fixed <destination>.tmp name combined with _O_EXCL means a temp file left by a crash permanently blocks all later downloads to that destination; simultaneous downloads also collide. Generate a randomized unique name on Windows, as the POSIX branch does, and retry exclusive-create collisions.
  std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".tmp")).string();
  int tmp_fd = -1;
  _sopen_s(&tmp_fd, temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _SH_DENYRW,
           _S_IREAD | _S_IWRITE);

include/ur_client_library/ur/dashboard_client_implementation.h:327

  • Adding a default argument does not preserve the existing virtual signature: downstream DashboardClientImpl subclasses overriding commandPopup(const std::string&) will no longer override this pure virtual and will fail to compile. Preserve the one-argument virtual overload and add a two-argument overload with a default forwarding implementation (which can ignore the title), then override the latter for PolyScope X.
  virtual DashboardResponse commandPopup(const std::string& popup_text, const std::string& popup_title = "") = 0;

Comment thread src/ur/dashboard_client_implementation_x.cpp
Copilot AI review requested due to automatic review settings August 21, 2026 15:47

@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 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 9978847. Configure here.

Comment thread src/ur/dashboard_client_implementation_x.cpp Outdated

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.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

Previously missed (1) — in code that hasn't changed since the last review.

src/ur/dashboard_client_implementation_x.cpp:674

  • For a 204 response, the vendored httplib skips the response handler entirely (httplib.h:14137-14144). Consequently status_code is never populated here and http_ok merely retains its initial value, unlike every response handled by handleHttpResult. Read the final status from the valid Result before deciding success so callers can distinguish the documented no-content case.
  if (http_ok)

include/ur_client_library/ur/dashboard_client.h:408

  • Changing the one-argument public method to a two-argument method with a default does not preserve ABI: existing binaries still reference the old mangled symbol and will fail to link/load against the updated shared library. Keep the one-argument overload (and the response overload below) and add explicit two-argument overloads that the old methods forward to; mirror this through the implementation hierarchy.
  bool commandPopup(const std::string& popup_text, const std::string& popup_title = "");

include/ur_client_library/ur/dashboard_client_implementation.h:561

  • Removing const changes the pure virtual contract in an installed header. Any downstream DashboardClientImpl subclass implementing the previous const override will no longer override this method and becomes abstract. Preserve the existing virtual signature and move the reconnecting/mutating work needed by the X implementation into a separate helper or otherwise keep it out of this interface.
  virtual void assertHasCommand(const std::string& command) = 0;

tests/test_dashboard_client_x.cpp:663

  • This branch executes a real shutdown whenever remote-control tests are enabled, but nothing implements the preceding comment's requirement to skip this on physical robots. A developer running the integration suite against a real 10.14 robot can therefore power it down and disrupt the rest of the run. Gate the destructive call on the URSim environment (or a dedicated explicit opt-in).
      auto response = dashboard_client_->commandShutdown();

Comment thread src/ur/dashboard_client_implementation_x.cpp
Copilot AI review requested due to automatic review settings August 21, 2026 15:56

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.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/ur/dashboard_client_implementation_x.cpp:683

  • On Windows, std::filesystem::rename fails when save_path already exists; it does not use MoveFileExW(MOVEFILE_REPLACE_EXISTING). Consequently the documented repeat-download behavior and the overwrite test only work on POSIX. Use an explicit Windows replacement operation (while retaining atomic POSIX rename) so an existing archive can be overwritten cross-platform.
    std::filesystem::rename(temp_save_path, save_path, ec);

@urfeex

urfeex commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

There are currently two issues:

  • The tests seem to be flaky, since sometimes the robot probably isn't started up correctly. Loading the first program sometimes fails with "Program load failed, another program is currently being loaded. Please try again once the current program has finished loading." This could potentially be fixed by using a smarter wait mechanism.
  • The dashboard example failed executing saying it didn't find the program (which it was loading fine during integration tests).

As I'll be a week off, I'll unfortunately have to let it sit here until then.

@urfeex
urfeex marked this pull request as draft August 21, 2026 19:21
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.

4 participants