From b79b8c9f4f135ab3c91875a1d2f78030ae91d435 Mon Sep 17 00:00:00 2001 From: Dhruv Patel Date: Mon, 25 Aug 2025 14:36:01 +0200 Subject: [PATCH 01/38] net: add macOS compatibility for TCP_KEEPIDLE --- src/network.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/network.h b/src/network.h index 967f6ac7..9b62a43f 100644 --- a/src/network.h +++ b/src/network.h @@ -16,6 +16,13 @@ #include #include +#include +#ifdef __APPLE__ + #ifndef TCP_KEEPIDLE + #define TCP_KEEPIDLE TCP_KEEPALIVE + #endif +#endif + #include #include From dea5a38610c24da153118e2de68ec7b7aa4ac731 Mon Sep 17 00:00:00 2001 From: Dhruv Patel Date: Mon, 25 Aug 2025 18:01:06 +0200 Subject: [PATCH 02/38] #include header fix --- src/network.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network.h b/src/network.h index 9b62a43f..1d2e187c 100644 --- a/src/network.h +++ b/src/network.h @@ -16,8 +16,8 @@ #include #include -#include #ifdef __APPLE__ +#include #ifndef TCP_KEEPIDLE #define TCP_KEEPIDLE TCP_KEEPALIVE #endif From e3b60122b15d6365b5af26308986492e6431b879 Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Fri, 28 Nov 2025 13:54:03 +0100 Subject: [PATCH 03/38] refactor: Adapting the async position control to support ROS 2 --- CHANGELOG.md | 6 +++- include/franka/active_motion_generator.h | 2 ++ include/franka/active_torque_control.h | 2 ++ .../async_position_control_handler.hpp | 4 ++- .../async_position_control_handler.cpp | 31 ++++++++++--------- src/robot.cpp | 3 -- src/robot_impl.cpp | 20 +++++++++--- src/robot_impl.h | 2 ++ test/robot_tests.cpp | 4 ++- 9 files changed, 49 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 636d7188..d26ce04d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to libfranka and pylibfranka will be documented in this file. +## UNRELEASED +### libfranka - C++ +#### Changed +- To support franka_ros2, we added an option for the async position control to base the `getFeedback` function on a robot state received via `franka_hardware` instead of querying the robot directly. + ## [0.18.2] Requires Franka Research 3 System Version >= 5.9.0 @@ -15,7 +20,6 @@ Requires Franka Research 3 System Version >= 5.9.0 #### Fixed - Fixed a compile issue with TinyXML2 dependency (see [github](https://github.com/frankarobotics/libfranka/issues/215)) - ### pylibfranka - Python #### Added - Added Joint and Cartesian Velocity controller examples. diff --git a/include/franka/active_motion_generator.h b/include/franka/active_motion_generator.h index 6f91d9a1..99022772 100644 --- a/include/franka/active_motion_generator.h +++ b/include/franka/active_motion_generator.h @@ -21,6 +21,8 @@ namespace franka { template class ActiveMotionGenerator : public ActiveControl { public: + ~ActiveMotionGenerator() override = default; + /** * Updates the motion generator commands of an active control * diff --git a/include/franka/active_torque_control.h b/include/franka/active_torque_control.h index 75b7a617..cca01209 100644 --- a/include/franka/active_torque_control.h +++ b/include/franka/active_torque_control.h @@ -20,6 +20,8 @@ namespace franka { */ class ActiveTorqueControl : public ActiveControl { public: + ~ActiveTorqueControl() override = default; + /** * Updates the joint-level based torque commands of an active joint effort control * diff --git a/include/franka/async_control/async_position_control_handler.hpp b/include/franka/async_control/async_position_control_handler.hpp index dc903c9a..1528cd4f 100644 --- a/include/franka/async_control/async_position_control_handler.hpp +++ b/include/franka/async_control/async_position_control_handler.hpp @@ -81,9 +81,10 @@ class AsyncPositionControlHandler { /** * Retrieves feedback about the current control state. * + * @param robot_state Optional robot state to use for feedback calculation. * @return TargetFeedback containing the current status and joint positions. */ - auto getTargetFeedback() -> TargetFeedback; + auto getTargetFeedback(const std::optional& robot_state = {}) -> TargetFeedback; /** * Stops the asynchronous position control. @@ -103,6 +104,7 @@ class AsyncPositionControlHandler { std::shared_ptr robot_; std::unique_ptr active_robot_control_; TargetStatus control_status_{TargetStatus::kIdle}; + franka::RobotState current_robot_state_{}; std::array target_position_{}; diff --git a/src/async_control/async_position_control_handler.cpp b/src/async_control/async_position_control_handler.cpp index 3b54d538..40b20fb2 100644 --- a/src/async_control/async_position_control_handler.cpp +++ b/src/async_control/async_position_control_handler.cpp @@ -37,18 +37,13 @@ auto AsyncPositionControlHandler::configure(const std::shared_ptr& robot, result.handler = std::shared_ptr(new AsyncPositionControlHandler( std::move(active_robot_control), configuration.goal_tolerance)); - - return result; + logging::logInfo("Async position control initialized successfully."); } catch (const std::exception& e) { result.error_message = e.what(); logging::logError("Error while constructing AsyncPositionControlHandler: {}", result.error_message.value()); - - return result; } - logging::logInfo("Async position control initialized successfully."); - return result; } @@ -85,7 +80,8 @@ auto AsyncPositionControlHandler::setJointPositionTarget(const JointPositionTarg return result; } -auto AsyncPositionControlHandler::getTargetFeedback() -> TargetFeedback { +auto AsyncPositionControlHandler::getTargetFeedback(const std::optional& robot_state) + -> TargetFeedback { TargetFeedback feedback{}; if (control_status_ == TargetStatus::kAborted || active_robot_control_ == nullptr) { @@ -99,13 +95,19 @@ auto AsyncPositionControlHandler::getTargetFeedback() -> TargetFeedback { } try { - auto [robot_state, duration] = active_robot_control_->readOnce(); + if (robot_state.has_value()) { + current_robot_state_ = robot_state.value(); + } else { + std::tie(current_robot_state_, std::ignore) = active_robot_control_->readOnce(); + } - switch (robot_state.robot_mode) { + switch (current_robot_state_.robot_mode) { case RobotMode::kMove: { - Eigen::Map current_position(robot_state.q_d.data(), Robot::kNumJoints); + Eigen::Map current_position(current_robot_state_.q_d.data(), + Robot::kNumJoints); Eigen::Map target_position(target_position_.data(), Robot::kNumJoints); - Eigen::Map current_velocity(robot_state.dq_d.data(), Robot::kNumJoints); + Eigen::Map current_velocity(current_robot_state_.dq_d.data(), + Robot::kNumJoints); auto position_error = (current_position - target_position).norm(); auto current_speed = current_velocity.norm(); @@ -143,10 +145,9 @@ auto AsyncPositionControlHandler::getTargetFeedback() -> TargetFeedback { auto AsyncPositionControlHandler::stopControl() -> void { try { - // ToDo(kuhn_an): Stop motion gracefully - // auto motion_finished = JointPositions{target_position_}; - // motion_finished.motion_finished = true; - // active_robot_control_->writeOnce(motion_finished); + auto motion_finished = JointPositions{target_position_}; + motion_finished.motion_finished = true; + active_robot_control_->writeOnce(motion_finished); active_robot_control_.reset(); logging::logInfo("Async position control stopped successfully."); diff --git a/src/robot.cpp b/src/robot.cpp index e3fd1fb2..56c0e1ac 100644 --- a/src/robot.cpp +++ b/src/robot.cpp @@ -182,9 +182,6 @@ void Robot::read(std::function read_callback) { } RobotState Robot::readOnce() { - std::unique_lock control_lock(control_mutex_, std::try_to_lock); - assertOwningLock(control_lock); - return impl_->readOnce(); } diff --git a/src/robot_impl.cpp b/src/robot_impl.cpp index 82b2fd5c..0a15638f 100644 --- a/src/robot_impl.cpp +++ b/src/robot_impl.cpp @@ -144,7 +144,10 @@ research_interface::robot::RobotCommand Robot::Impl::sendRobotCommand( return robot_command; } - robot_command.message_id = message_id_; + { + std::lock_guard lock(message_id_mutex_); + robot_command.message_id = message_id_; + } if (motion_command.has_value()) { if (current_move_motion_generator_mode_ == research_interface::robot::MotionGeneratorMode::kIdle || @@ -182,7 +185,12 @@ research_interface::robot::RobotCommand Robot::Impl::sendRobotCommand( research_interface::robot::RobotState Robot::Impl::receiveRobotState() { research_interface::robot::RobotState latest_accepted_state; - latest_accepted_state.message_id = message_id_; + auto last_message_id = 0U; + { + std::lock_guard lock(message_id_mutex_); + latest_accepted_state.message_id = message_id_; + last_message_id = message_id_; + } // If states are already available on the socket, use the one with the most recent message ID. research_interface::robot::RobotState received_state{}; @@ -193,7 +201,7 @@ research_interface::robot::RobotState Robot::Impl::receiveRobotState() { } // If there was no valid state on the socket, we need to wait. - while (latest_accepted_state.message_id == message_id_) { + while (latest_accepted_state.message_id == last_message_id) { received_state = network_->udpBlockingReceive(); if (received_state.message_id > latest_accepted_state.message_id) { latest_accepted_state = received_state; @@ -208,7 +216,11 @@ void Robot::Impl::updateState(const research_interface::robot::RobotState& robot robot_mode_ = robot_state.robot_mode; motion_generator_mode_ = robot_state.motion_generator_mode; controller_mode_ = robot_state.controller_mode; - message_id_ = robot_state.message_id; + + { + std::lock_guard lock(message_id_mutex_); + message_id_ = robot_state.message_id; + } } Robot::ServerVersion Robot::Impl::serverVersion() const noexcept { diff --git a/src/robot_impl.h b/src/robot_impl.h index 2ec31a32..9ddb3593 100644 --- a/src/robot_impl.h +++ b/src/robot_impl.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include @@ -238,6 +239,7 @@ class Robot::Impl : public RobotControl { bool controllerRunning() const noexcept; private: + mutable std::mutex message_id_mutex_; RobotState current_state_; template diff --git a/test/robot_tests.cpp b/test/robot_tests.cpp index 52b8b228..dac6495a 100644 --- a/test/robot_tests.cpp +++ b/test/robot_tests.cpp @@ -437,9 +437,11 @@ TEST_F(RobotTests, ThrowsIfConflictingOperationIsRunning) { InvalidOperationException); EXPECT_THROW(default_robot.read(std::function()), InvalidOperationException); - EXPECT_THROW(default_robot.readOnce(), InvalidOperationException); EXPECT_THROW(default_robot.startTorqueControl(), InvalidOperationException); + // Technically, readOnce is possible but due to the other call blocking, it will time out. + EXPECT_THROW(default_robot.readOnce(), NetworkException); + default_server.ignoreUdpBuffer(); run = false; From de4260f6d0b5942960e4edcf16990ff327abcd10 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Wed, 10 Dec 2025 21:19:45 +0100 Subject: [PATCH 04/38] fix: fix debian package format to to inlclude ubuntu and arch: libfranka_VERSION_CODENAME_ARCH.deb --- CHANGELOG.md | 1 + CMakeLists.txt | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d26ce04d..cc890c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to libfranka and pylibfranka will be documented in this file ### libfranka - C++ #### Changed - To support franka_ros2, we added an option for the async position control to base the `getFeedback` function on a robot state received via `franka_hardware` instead of querying the robot directly. +- Format libfranka debian package to inlclude ubuntu code name and arch: libfranka_VERSION_CODENAME_ARCH.deb ## [0.18.2] Requires Franka Research 3 System Version >= 5.9.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c4905f2..1a8d2a27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,12 +219,19 @@ endif() ## Packaging include(SetVersionFromGit) set_version_from_git(PACKAGE_VERSION PACKAGE_TAG) + +# Get architecture execute_process(COMMAND dpkg --print-architecture OUTPUT_VARIABLE PACKAGE_ARCH OUTPUT_STRIP_TRAILING_WHITESPACE) +# Get Ubuntu codename (focal, jammy, noble) +execute_process(COMMAND lsb_release -cs OUTPUT_VARIABLE UBUNTU_CODENAME OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CPACK_PACKAGE_VENDOR "Franka Robotics GmbH") set(CPACK_GENERATOR "DEB;TGZ") set(CPACK_PACKAGE_VERSION ${libfranka_VERSION}) -set(CPACK_PACKAGE_FILE_NAME ${PROJECT_NAME}_${PACKAGE_TAG}_${PACKAGE_ARCH}) + +# Updated filename format: libfranka_VERSION_CODENAME_ARCH.deb +set(CPACK_PACKAGE_FILE_NAME ${PROJECT_NAME}_${PACKAGE_TAG}_${UBUNTU_CODENAME}_${PACKAGE_ARCH}) set(CPACK_PACKAGE_VERSION ${PACKAGE_TAG}) set(CPACK_PACKAGE_ARCHITECTURE ${PACKAGE_ARCH}) set(CPACK_SYSTEM_NAME ${CMAKE_HOST_SYSTEM_PROCESSOR}) From e9bc21b95c798c1cc71ba898620e15158f1e4ba3 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 11 Dec 2025 09:27:45 +0100 Subject: [PATCH 05/38] feat: support lts ubuntu versions --- .ci/Dockerfile.jammy | 147 ++++++++++++++++++ .ci/Dockerfile.noble | 143 +++++++++++++++++ ...minimum-version-set-CMP0048-CMP0054-.patch | 29 ++++ ...minimum-version-set-CMP0048-CMP0054-.patch | 28 ++++ .devcontainer/devcontainer.json | 8 +- .devcontainer/docker-compose.yml | 10 +- examples/CMakeLists.txt | 2 +- 7 files changed, 357 insertions(+), 10 deletions(-) create mode 100644 .ci/Dockerfile.jammy create mode 100644 .ci/Dockerfile.noble create mode 100644 .ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch create mode 100644 .ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch diff --git a/.ci/Dockerfile.jammy b/.ci/Dockerfile.jammy new file mode 100644 index 00000000..3c9bd1fc --- /dev/null +++ b/.ci/Dockerfile.jammy @@ -0,0 +1,147 @@ +# Start with a base image +FROM ubuntu:22.04 + +# Set non-interactive mode +ENV DEBIAN_FRONTEND=noninteractive + +ARG USER_UID=1001 +ARG USER_GID=1001 +ARG USERNAME=user + +WORKDIR /workspaces + +# Create a non-root user +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ + && chown -R $USER_UID:$USER_GID /workspaces \ + && apt-get update \ + && apt-get install -y sudo \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME + +# Add LLVM repository for newer clang versions +RUN apt-get update && \ + apt-get install -y wget gnupg lsb-release software-properties-common && \ + wget https://apt.llvm.org/llvm.sh && \ + chmod +x llvm.sh && \ + ./llvm.sh 14 + +# Install necessary packages +RUN apt-get update \ + && apt-get install -y \ + bash-completion \ + build-essential \ + clang-14 \ + clang-format-14 \ + clang-tidy-14 \ + curl \ + doxygen \ + dpkg \ + git \ + graphviz \ + lcov \ + libeigen3-dev \ + libpoco-dev \ + lsb-release \ + libfmt-dev \ + python3-dev \ + python3-pip \ + python3-venv \ + python3-distutils \ + python3-numpy \ + pybind11-dev \ + rename \ + valgrind \ + wget \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && ln -s $(which clang-tidy-14) /usr/bin/clang-tidy \ + && ln -s $(which clang-format-14) /usr/bin/clang-format + +RUN apt-get update && \ + apt-get install -y wget gnupg && \ + wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ + apt-add-repository 'deb https://apt.kitware.com/ubuntu/ jammy main' && \ + apt-get update && \ + apt-get install -y cmake cmake-data + +# Add the necessary 3rd party dependencies for the robot-service +# Note: the order is important, change at your own risk. +RUN git clone --recursive --branch boost-1.77.0 https://github.com/boostorg/boost.git \ + && cd boost \ + && ./bootstrap.sh --prefix=/usr \ + && ./b2 install \ + && cd ../.. \ + && rm -rf boost + +RUN git clone --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ + && cd tinyxml2 \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf tinyxml2 + +COPY ./console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch /tmp/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch +RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ + && cd console_bridge \ + && git apply /tmp/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 \ + && make install \ + && cd ../.. \ + && rm -rf console_bridge + +COPY ./urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch /tmp/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch +RUN git clone --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ + && cd urdfdom_headers \ + && git apply /tmp/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf urdfdom_headers + +COPY ./urdfdom.patch /tmp/urdfdom.patch +RUN git clone --branch 4.0.0 https://github.com/ros/urdfdom.git \ + && cd urdfdom \ + && git apply /tmp/urdfdom.patch \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf urdfdom + +RUN git clone --recursive --branch v5.4.3 https://github.com/assimp/assimp.git \ + && cd assimp \ + && mkdir build && cd build \ + && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DASSIMP_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf assimp + +COPY ./pinocchio.patch /tmp/pinocchio.patch +RUN git clone --recursive --branch v3.4.0 https://github.com/stack-of-tasks/pinocchio.git \ + && cd pinocchio \ + && git apply /tmp/pinocchio.patch \ + && mkdir build && cd build \ + && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf pinocchio + +# Install Python wheel building tools +RUN python3 -m pip install --upgrade pip setuptools wheel && \ + python3 -m pip install \ + auditwheel \ + build \ + cibuildwheel \ + twine \ + patchelf \ + flake8 \ + pybind11 + +USER $USERNAME diff --git a/.ci/Dockerfile.noble b/.ci/Dockerfile.noble new file mode 100644 index 00000000..b98ca4df --- /dev/null +++ b/.ci/Dockerfile.noble @@ -0,0 +1,143 @@ +# Start with a base image +FROM ubuntu:24.04 + +# Set non-interactive mode +ENV DEBIAN_FRONTEND=noninteractive + +ARG USER_UID=1001 +ARG USER_GID=1001 +ARG USERNAME=user + +WORKDIR /workspaces + +# Create a non-root user +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ + && chown -R $USER_UID:$USER_GID /workspaces \ + && apt-get update \ + && apt-get install -y sudo \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME + +# Add LLVM repository for newer clang versions +RUN apt-get update && \ + apt-get install -y wget gnupg lsb-release software-properties-common && \ + wget https://apt.llvm.org/llvm.sh && \ + chmod +x llvm.sh && \ + ./llvm.sh 14 + +# Install necessary packages +RUN apt-get update \ + && apt-get install -y \ + bash-completion \ + build-essential \ + clang-14 \ + clang-format-14 \ + clang-tidy-14 \ + curl \ + doxygen \ + dpkg \ + git \ + graphviz \ + lcov \ + libeigen3-dev \ + libpoco-dev \ + lsb-release \ + libfmt-dev \ + python3-dev \ + python3-pip \ + python3-venv \ + python3-distutils \ + python3-numpy \ + pybind11-dev \ + rename \ + valgrind \ + wget \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && ln -s $(which clang-tidy-14) /usr/bin/clang-tidy \ + && ln -s $(which clang-format-14) /usr/bin/clang-format + +RUN apt-get update && \ + apt-get install -y wget gnupg && \ + wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ + apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' && \ + apt-get update && \ + apt-get install -y cmake=3.22.2-0kitware1ubuntu20.04.1 cmake-data=3.22.2-0kitware1ubuntu20.04.1 + +# Add the necessary 3rd party dependencies for the robot-service +# Note: the order is important, change at your own risk. +RUN git clone --recursive --branch boost-1.77.0 https://github.com/boostorg/boost.git \ + && cd boost \ + && ./bootstrap.sh --prefix=/usr \ + && ./b2 install \ + && cd ../.. \ + && rm -rf boost + +RUN git clone --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ + && cd tinyxml2 \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf tinyxml2 + +RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ + && cd console_bridge \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 \ + && make install \ + && cd ../.. \ + && rm -rf console_bridge + +RUN git clone --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ + && cd urdfdom_headers \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf urdfdom_headers + +COPY ./urdfdom.patch /tmp/urdfdom.patch +RUN git clone --branch 4.0.0 https://github.com/ros/urdfdom.git \ + && cd urdfdom \ + && git apply /tmp/urdfdom.patch \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf urdfdom + +RUN git clone --recursive --branch v5.4.3 https://github.com/assimp/assimp.git \ + && cd assimp \ + && mkdir build && cd build \ + && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DASSIMP_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf assimp + +COPY ./pinocchio.patch /tmp/pinocchio.patch +RUN git clone --recursive --branch v3.4.0 https://github.com/stack-of-tasks/pinocchio.git \ + && cd pinocchio \ + && git apply /tmp/pinocchio.patch \ + && mkdir build && cd build \ + && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ + && make -j4 && make install \ + && cd ../.. \ + && rm -rf pinocchio + +# Install Python wheel building tools +RUN python3 -m pip install --upgrade pip setuptools wheel && \ + python3 -m pip install \ + auditwheel \ + build \ + cibuildwheel \ + twine \ + patchelf \ + flake8 \ + pybind11 + +USER $USERNAME diff --git a/.ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch b/.ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch new file mode 100644 index 00000000..fbd54c23 --- /dev/null +++ b/.ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch @@ -0,0 +1,29 @@ +From 3aa14ae820ae9d8da361ca56bbf7fb62da272382 Mon Sep 17 00:00:00 2001 +From: mohy_ka +Date: Wed, 10 Dec 2025 23:00:19 +0100 +Subject: [PATCH] Modernize CMake minimum version + set CMP0048/CMP0054 + policies for modern CMake + +--- + CMakeLists.txt | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 3f55509..aa873eb 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1,4 +1,11 @@ +-cmake_minimum_required(VERSION 3.0.2) ++cmake_minimum_required(VERSION 3.5) ++if(POLICY CMP0048) ++ cmake_policy(SET CMP0048 NEW) ++endif() ++if(POLICY CMP0054) ++ cmake_policy(SET CMP0054 NEW) ++endif() ++ + project(console_bridge) + + set (CONSOLE_BRIDGE_MAJOR_VERSION 1) +-- +2.34.1 diff --git a/.ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch b/.ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch new file mode 100644 index 00000000..b0398993 --- /dev/null +++ b/.ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch @@ -0,0 +1,28 @@ +From bc23b709d6d9eeb208f436d08d455ff867b87646 Mon Sep 17 00:00:00 2001 +From: mohy_ka +Date: Wed, 10 Dec 2025 23:07:08 +0100 +Subject: [PATCH] Modernize CMake minimum version + set CMP0048/CMP0054 + policies for modern CMake + +--- + CMakeLists.txt | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 800aeed..5d11fd1 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1,4 +1,10 @@ +-cmake_minimum_required( VERSION 2.8 FATAL_ERROR ) ++cmake_minimum_required(VERSION 3.5) ++if(POLICY CMP0048) ++ cmake_policy(SET CMP0048 NEW) ++endif() ++if(POLICY CMP0054) ++ cmake_policy(SET CMP0054 NEW) ++endif() + project (urdfdom_headers) + + include(GNUInstallDirs) +-- +2.34.1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index be50bfc9..d85040c8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,10 @@ { - "name": "libfranka_container", - "dockerComposeFile": "./docker-compose.yml", - "service": "libfranka_project", + "name": "libfranka - Ubuntu ${localEnv:DISTRO_NAME:focal}", + "dockerComposeFile": "docker-compose.yml", + "service": "libfranka", "workspaceFolder": "/workspaces", "remoteUser": "user", - "initializeCommand": "echo \"USER_UID=$(id -u $USER)\nUSER_GID=$(id -g $USER)\" > .devcontainer/.env", + "initializeCommand": "echo \"USER_UID=$(id -u)\nUSER_GID=$(id -g)\n${localEnv:DISTRO:DISTRO=Dockerfile.focal}\n${localEnv:DISTRO_NAME:DISTRO_NAME=focal}\" > .devcontainer/.env", "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index bb17d660..2e205a7d 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,12 +1,12 @@ services: - libfranka_project: + libfranka: build: context: ../.ci/ - dockerfile: ../.ci/Dockerfile.focal + dockerfile: ../.ci/${DISTRO:-Dockerfile.focal} args: - USER_UID: ${USER_UID} - USER_GID: ${USER_GID} - container_name: libfranka + USER_UID: ${USER_UID:-1000} + USER_GID: ${USER_GID:-1000} + container_name: libfranka-${DISTRO_NAME:-focal} network_mode: "host" shm_size: 512m privileged: true diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5cf55e3f..270a6b00 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) project(libfranka-examples CXX) From 6099f14aac7825dfc59123fd79e299e42d61de0b Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Thu, 11 Dec 2025 10:03:37 +0100 Subject: [PATCH 06/38] refactor: Cleaned up the jammy and noble Dockerfiles --- .ci/Dockerfile.jammy | 16 +--------- .ci/Dockerfile.noble | 12 +------- ...minimum-version-set-CMP0048-CMP0054-.patch | 29 ------------------- ...minimum-version-set-CMP0048-CMP0054-.patch | 28 ------------------ 4 files changed, 2 insertions(+), 83 deletions(-) delete mode 100644 .ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch delete mode 100644 .ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch diff --git a/.ci/Dockerfile.jammy b/.ci/Dockerfile.jammy index 3c9bd1fc..68a55ad4 100644 --- a/.ci/Dockerfile.jammy +++ b/.ci/Dockerfile.jammy @@ -33,10 +33,8 @@ RUN apt-get update \ && apt-get install -y \ bash-completion \ build-essential \ - clang-14 \ - clang-format-14 \ - clang-tidy-14 \ curl \ + cmake \ doxygen \ dpkg \ git \ @@ -44,7 +42,6 @@ RUN apt-get update \ lcov \ libeigen3-dev \ libpoco-dev \ - lsb-release \ libfmt-dev \ python3-dev \ python3-pip \ @@ -60,13 +57,6 @@ RUN apt-get update \ && ln -s $(which clang-tidy-14) /usr/bin/clang-tidy \ && ln -s $(which clang-format-14) /usr/bin/clang-format -RUN apt-get update && \ - apt-get install -y wget gnupg && \ - wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ - apt-add-repository 'deb https://apt.kitware.com/ubuntu/ jammy main' && \ - apt-get update && \ - apt-get install -y cmake cmake-data - # Add the necessary 3rd party dependencies for the robot-service # Note: the order is important, change at your own risk. RUN git clone --recursive --branch boost-1.77.0 https://github.com/boostorg/boost.git \ @@ -84,10 +74,8 @@ RUN git clone --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ && cd ../.. \ && rm -rf tinyxml2 -COPY ./console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch /tmp/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ && cd console_bridge \ - && git apply /tmp/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch \ && mkdir build && cd build \ && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ && make -j4 \ @@ -95,10 +83,8 @@ RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ && cd ../.. \ && rm -rf console_bridge -COPY ./urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch /tmp/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch RUN git clone --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ && cd urdfdom_headers \ - && git apply /tmp/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch \ && mkdir build && cd build \ && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ && make -j4 && make install \ diff --git a/.ci/Dockerfile.noble b/.ci/Dockerfile.noble index b98ca4df..1eec8f1c 100644 --- a/.ci/Dockerfile.noble +++ b/.ci/Dockerfile.noble @@ -33,10 +33,8 @@ RUN apt-get update \ && apt-get install -y \ bash-completion \ build-essential \ - clang-14 \ - clang-format-14 \ - clang-tidy-14 \ curl \ + cmake \ doxygen \ dpkg \ git \ @@ -44,7 +42,6 @@ RUN apt-get update \ lcov \ libeigen3-dev \ libpoco-dev \ - lsb-release \ libfmt-dev \ python3-dev \ python3-pip \ @@ -60,13 +57,6 @@ RUN apt-get update \ && ln -s $(which clang-tidy-14) /usr/bin/clang-tidy \ && ln -s $(which clang-format-14) /usr/bin/clang-format -RUN apt-get update && \ - apt-get install -y wget gnupg && \ - wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ - apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' && \ - apt-get update && \ - apt-get install -y cmake=3.22.2-0kitware1ubuntu20.04.1 cmake-data=3.22.2-0kitware1ubuntu20.04.1 - # Add the necessary 3rd party dependencies for the robot-service # Note: the order is important, change at your own risk. RUN git clone --recursive --branch boost-1.77.0 https://github.com/boostorg/boost.git \ diff --git a/.ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch b/.ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch deleted file mode 100644 index fbd54c23..00000000 --- a/.ci/console-bridge-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 3aa14ae820ae9d8da361ca56bbf7fb62da272382 Mon Sep 17 00:00:00 2001 -From: mohy_ka -Date: Wed, 10 Dec 2025 23:00:19 +0100 -Subject: [PATCH] Modernize CMake minimum version + set CMP0048/CMP0054 - policies for modern CMake - ---- - CMakeLists.txt | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 3f55509..aa873eb 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -1,4 +1,11 @@ --cmake_minimum_required(VERSION 3.0.2) -+cmake_minimum_required(VERSION 3.5) -+if(POLICY CMP0048) -+ cmake_policy(SET CMP0048 NEW) -+endif() -+if(POLICY CMP0054) -+ cmake_policy(SET CMP0054 NEW) -+endif() -+ - project(console_bridge) - - set (CONSOLE_BRIDGE_MAJOR_VERSION 1) --- -2.34.1 diff --git a/.ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch b/.ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch deleted file mode 100644 index b0398993..00000000 --- a/.ci/urdfdom-Modernize-CMake-minimum-version-set-CMP0048-CMP0054-.patch +++ /dev/null @@ -1,28 +0,0 @@ -From bc23b709d6d9eeb208f436d08d455ff867b87646 Mon Sep 17 00:00:00 2001 -From: mohy_ka -Date: Wed, 10 Dec 2025 23:07:08 +0100 -Subject: [PATCH] Modernize CMake minimum version + set CMP0048/CMP0054 - policies for modern CMake - ---- - CMakeLists.txt | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 800aeed..5d11fd1 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -1,4 +1,10 @@ --cmake_minimum_required( VERSION 2.8 FATAL_ERROR ) -+cmake_minimum_required(VERSION 3.5) -+if(POLICY CMP0048) -+ cmake_policy(SET CMP0048 NEW) -+endif() -+if(POLICY CMP0054) -+ cmake_policy(SET CMP0054 NEW) -+endif() - project (urdfdom_headers) - - include(GNUInstallDirs) --- -2.34.1 From f186ab07d223b8df72015b16a1332adb65e44da7 Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Thu, 11 Dec 2025 10:39:11 +0100 Subject: [PATCH 07/38] refactor: Merged different Dockerfiles and added matrix build to Jenkinsfile --- .ci/{Dockerfile.focal => Dockerfile} | 87 +++++++----- .ci/Dockerfile.jammy | 133 ------------------ .ci/Dockerfile.noble | 133 ------------------ .devcontainer/devcontainer.json | 8 +- ...r-compose.yml => docker-compose-focal.yml} | 11 +- .github/workflows/libfranka-build.yml | 14 +- CHANGELOG.md | 1 + CMakeLists.txt | 3 + Jenkinsfile | 20 ++- examples/CMakeLists.txt | 2 +- 10 files changed, 94 insertions(+), 318 deletions(-) rename .ci/{Dockerfile.focal => Dockerfile} (56%) delete mode 100644 .ci/Dockerfile.jammy delete mode 100644 .ci/Dockerfile.noble rename .devcontainer/{docker-compose.yml => docker-compose-focal.yml} (62%) diff --git a/.ci/Dockerfile.focal b/.ci/Dockerfile similarity index 56% rename from .ci/Dockerfile.focal rename to .ci/Dockerfile index 65a75503..2801d2ec 100644 --- a/.ci/Dockerfile.focal +++ b/.ci/Dockerfile @@ -1,5 +1,8 @@ +ARG UBUNTU_VERSION="20.04" + # Start with a base image -FROM ubuntu:20.04 +FROM ubuntu:${UBUNTU_VERSION} +ARG UBUNTU_VERSION # Set non-interactive mode ENV DEBIAN_FRONTEND=noninteractive @@ -26,16 +29,17 @@ RUN apt-get update && \ apt-get install -y wget gnupg lsb-release software-properties-common && \ wget https://apt.llvm.org/llvm.sh && \ chmod +x llvm.sh && \ - ./llvm.sh 14 + ./llvm.sh 18 && \ + apt-get update && \ + apt-get install -y clang-format-18 clang-tidy-18 \ + && ln -s $(which clang-tidy-18) /usr/bin/clang-tidy \ + && ln -s $(which clang-format-18) /usr/bin/clang-format # Install necessary packages RUN apt-get update \ && apt-get install -y \ bash-completion \ build-essential \ - clang-14 \ - clang-format-14 \ - clang-tidy-14 \ curl \ doxygen \ dpkg \ @@ -49,34 +53,37 @@ RUN apt-get update \ python3-dev \ python3-pip \ python3-venv \ - python3-distutils \ - python3-numpy \ pybind11-dev \ rename \ valgrind \ wget \ && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* \ - && ln -s $(which clang-tidy-14) /usr/bin/clang-tidy \ - && ln -s $(which clang-format-14) /usr/bin/clang-format - -RUN apt-get update && \ - apt-get install -y wget gnupg && \ - wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ - apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' && \ - apt-get update && \ - apt-get install -y cmake=3.22.2-0kitware1ubuntu20.04.1 cmake-data=3.22.2-0kitware1ubuntu20.04.1 + && rm -rf /var/lib/apt/lists/* + +# Install CMake; use Kitware repo on Ubuntu 20.04 for >=3.22 +RUN if [ "${UBUNTU_VERSION}" = "20.04" ]; then \ + apt-get update && \ + apt-get install -y wget gnupg ca-certificates software-properties-common && \ + wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor | tee /usr/share/keyrings/kitware-archive-keyring.gpg > /dev/null && \ + echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main" > /etc/apt/sources.list.d/kitware.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends cmake=3.22.2-0kitware1ubuntu20.04.1 cmake-data=3.22.2-0kitware1ubuntu20.04.1; \ + else \ + apt-get update && apt-get install -y --no-install-recommends cmake; \ + fi && \ + cmake --version && \ + dpkg --compare-versions "$(cmake --version | head -n1 | awk '{print $3}')" ge 3.22 || (echo "Error: CMake >= 3.22 is required. Got ubuntu version ${UBUNTU_VERSION}" >&2; exit 1) # Add the necessary 3rd party dependencies for the robot-service # Note: the order is important, change at your own risk. -RUN git clone --recursive --branch boost-1.77.0 https://github.com/boostorg/boost.git \ +RUN git clone --depth 1 --recurse-submodules --shallow-submodules --branch boost-1.77.0 https://github.com/boostorg/boost.git \ && cd boost \ && ./bootstrap.sh --prefix=/usr \ && ./b2 install \ && cd ../.. \ && rm -rf boost -RUN git clone --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ +RUN git clone --depth 1 --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ && cd tinyxml2 \ && mkdir build && cd build \ && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ @@ -84,7 +91,7 @@ RUN git clone --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ && cd ../.. \ && rm -rf tinyxml2 -RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ +RUN git clone --depth 1 --branch 1.0.2 https://github.com/ros/console_bridge.git \ && cd console_bridge \ && mkdir build && cd build \ && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ @@ -93,7 +100,7 @@ RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ && cd ../.. \ && rm -rf console_bridge -RUN git clone --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ +RUN git clone --depth 1 --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ && cd urdfdom_headers \ && mkdir build && cd build \ && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ @@ -102,7 +109,7 @@ RUN git clone --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ && rm -rf urdfdom_headers COPY ./urdfdom.patch /tmp/urdfdom.patch -RUN git clone --branch 4.0.0 https://github.com/ros/urdfdom.git \ +RUN git clone --depth 1 --branch 4.0.0 https://github.com/ros/urdfdom.git \ && cd urdfdom \ && git apply /tmp/urdfdom.patch \ && mkdir build && cd build \ @@ -111,7 +118,7 @@ RUN git clone --branch 4.0.0 https://github.com/ros/urdfdom.git \ && cd ../.. \ && rm -rf urdfdom -RUN git clone --recursive --branch v5.4.3 https://github.com/assimp/assimp.git \ +RUN git clone --depth 1 --recurse-submodules --shallow-submodules --branch v5.4.3 https://github.com/assimp/assimp.git \ && cd assimp \ && mkdir build && cd build \ && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DASSIMP_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ @@ -120,7 +127,7 @@ RUN git clone --recursive --branch v5.4.3 https://github.com/assimp/assimp.git \ && rm -rf assimp COPY ./pinocchio.patch /tmp/pinocchio.patch -RUN git clone --recursive --branch v3.4.0 https://github.com/stack-of-tasks/pinocchio.git \ +RUN git clone --depth 1 --recurse-submodules --shallow-submodules --branch v3.4.0 https://github.com/stack-of-tasks/pinocchio.git \ && cd pinocchio \ && git apply /tmp/pinocchio.patch \ && mkdir build && cd build \ @@ -129,15 +136,27 @@ RUN git clone --recursive --branch v3.4.0 https://github.com/stack-of-tasks/pino && cd ../.. \ && rm -rf pinocchio -# Install Python wheel building tools -RUN python3 -m pip install --upgrade pip setuptools wheel && \ - python3 -m pip install \ - auditwheel \ - build \ - cibuildwheel \ - twine \ - patchelf \ - flake8 \ - pybind11 +# Install Python wheel building tools inside a virtualenv to satisfy PEP 668 +RUN python3 -m venv /opt/venv && \ + /opt/venv/bin/pip install --upgrade pip setuptools wheel && \ + /opt/venv/bin/pip install \ + auditwheel \ + build \ + cibuildwheel \ + twine \ + patchelf \ + flake8 \ + numpy \ + pybind11 && \ + /opt/venv/bin/python --version && /opt/venv/bin/pip --version + +ENV PATH="/opt/venv/bin:${PATH}" +RUN apt-get update && \ + apt-get install -y wget gnupg lsb-release software-properties-common && \ + wget https://apt.llvm.org/llvm.sh && \ + chmod +x llvm.sh && \ + ./llvm.sh 18 && \ + apt-get update && \ + apt-get install -y clang-format-18 clang-tidy-18 USER $USERNAME diff --git a/.ci/Dockerfile.jammy b/.ci/Dockerfile.jammy deleted file mode 100644 index 68a55ad4..00000000 --- a/.ci/Dockerfile.jammy +++ /dev/null @@ -1,133 +0,0 @@ -# Start with a base image -FROM ubuntu:22.04 - -# Set non-interactive mode -ENV DEBIAN_FRONTEND=noninteractive - -ARG USER_UID=1001 -ARG USER_GID=1001 -ARG USERNAME=user - -WORKDIR /workspaces - -# Create a non-root user -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && chown -R $USER_UID:$USER_GID /workspaces \ - && apt-get update \ - && apt-get install -y sudo \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -# Add LLVM repository for newer clang versions -RUN apt-get update && \ - apt-get install -y wget gnupg lsb-release software-properties-common && \ - wget https://apt.llvm.org/llvm.sh && \ - chmod +x llvm.sh && \ - ./llvm.sh 14 - -# Install necessary packages -RUN apt-get update \ - && apt-get install -y \ - bash-completion \ - build-essential \ - curl \ - cmake \ - doxygen \ - dpkg \ - git \ - graphviz \ - lcov \ - libeigen3-dev \ - libpoco-dev \ - libfmt-dev \ - python3-dev \ - python3-pip \ - python3-venv \ - python3-distutils \ - python3-numpy \ - pybind11-dev \ - rename \ - valgrind \ - wget \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* \ - && ln -s $(which clang-tidy-14) /usr/bin/clang-tidy \ - && ln -s $(which clang-format-14) /usr/bin/clang-format - -# Add the necessary 3rd party dependencies for the robot-service -# Note: the order is important, change at your own risk. -RUN git clone --recursive --branch boost-1.77.0 https://github.com/boostorg/boost.git \ - && cd boost \ - && ./bootstrap.sh --prefix=/usr \ - && ./b2 install \ - && cd ../.. \ - && rm -rf boost - -RUN git clone --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ - && cd tinyxml2 \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf tinyxml2 - -RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ - && cd console_bridge \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 \ - && make install \ - && cd ../.. \ - && rm -rf console_bridge - -RUN git clone --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ - && cd urdfdom_headers \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf urdfdom_headers - -COPY ./urdfdom.patch /tmp/urdfdom.patch -RUN git clone --branch 4.0.0 https://github.com/ros/urdfdom.git \ - && cd urdfdom \ - && git apply /tmp/urdfdom.patch \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf urdfdom - -RUN git clone --recursive --branch v5.4.3 https://github.com/assimp/assimp.git \ - && cd assimp \ - && mkdir build && cd build \ - && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DASSIMP_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf assimp - -COPY ./pinocchio.patch /tmp/pinocchio.patch -RUN git clone --recursive --branch v3.4.0 https://github.com/stack-of-tasks/pinocchio.git \ - && cd pinocchio \ - && git apply /tmp/pinocchio.patch \ - && mkdir build && cd build \ - && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf pinocchio - -# Install Python wheel building tools -RUN python3 -m pip install --upgrade pip setuptools wheel && \ - python3 -m pip install \ - auditwheel \ - build \ - cibuildwheel \ - twine \ - patchelf \ - flake8 \ - pybind11 - -USER $USERNAME diff --git a/.ci/Dockerfile.noble b/.ci/Dockerfile.noble deleted file mode 100644 index 1eec8f1c..00000000 --- a/.ci/Dockerfile.noble +++ /dev/null @@ -1,133 +0,0 @@ -# Start with a base image -FROM ubuntu:24.04 - -# Set non-interactive mode -ENV DEBIAN_FRONTEND=noninteractive - -ARG USER_UID=1001 -ARG USER_GID=1001 -ARG USERNAME=user - -WORKDIR /workspaces - -# Create a non-root user -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && chown -R $USER_UID:$USER_GID /workspaces \ - && apt-get update \ - && apt-get install -y sudo \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -# Add LLVM repository for newer clang versions -RUN apt-get update && \ - apt-get install -y wget gnupg lsb-release software-properties-common && \ - wget https://apt.llvm.org/llvm.sh && \ - chmod +x llvm.sh && \ - ./llvm.sh 14 - -# Install necessary packages -RUN apt-get update \ - && apt-get install -y \ - bash-completion \ - build-essential \ - curl \ - cmake \ - doxygen \ - dpkg \ - git \ - graphviz \ - lcov \ - libeigen3-dev \ - libpoco-dev \ - libfmt-dev \ - python3-dev \ - python3-pip \ - python3-venv \ - python3-distutils \ - python3-numpy \ - pybind11-dev \ - rename \ - valgrind \ - wget \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* \ - && ln -s $(which clang-tidy-14) /usr/bin/clang-tidy \ - && ln -s $(which clang-format-14) /usr/bin/clang-format - -# Add the necessary 3rd party dependencies for the robot-service -# Note: the order is important, change at your own risk. -RUN git clone --recursive --branch boost-1.77.0 https://github.com/boostorg/boost.git \ - && cd boost \ - && ./bootstrap.sh --prefix=/usr \ - && ./b2 install \ - && cd ../.. \ - && rm -rf boost - -RUN git clone --branch 10.0.0 https://github.com/leethomason/tinyxml2.git \ - && cd tinyxml2 \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf tinyxml2 - -RUN git clone --branch 1.0.2 https://github.com/ros/console_bridge.git \ - && cd console_bridge \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 \ - && make install \ - && cd ../.. \ - && rm -rf console_bridge - -RUN git clone --branch 1.0.5 https://github.com/ros/urdfdom_headers.git \ - && cd urdfdom_headers \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf urdfdom_headers - -COPY ./urdfdom.patch /tmp/urdfdom.patch -RUN git clone --branch 4.0.0 https://github.com/ros/urdfdom.git \ - && cd urdfdom \ - && git apply /tmp/urdfdom.patch \ - && mkdir build && cd build \ - && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf urdfdom - -RUN git clone --recursive --branch v5.4.3 https://github.com/assimp/assimp.git \ - && cd assimp \ - && mkdir build && cd build \ - && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DASSIMP_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf assimp - -COPY ./pinocchio.patch /tmp/pinocchio.patch -RUN git clone --recursive --branch v3.4.0 https://github.com/stack-of-tasks/pinocchio.git \ - && cd pinocchio \ - && git apply /tmp/pinocchio.patch \ - && mkdir build && cd build \ - && cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib \ - && make -j4 && make install \ - && cd ../.. \ - && rm -rf pinocchio - -# Install Python wheel building tools -RUN python3 -m pip install --upgrade pip setuptools wheel && \ - python3 -m pip install \ - auditwheel \ - build \ - cibuildwheel \ - twine \ - patchelf \ - flake8 \ - pybind11 - -USER $USERNAME diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d85040c8..aeb22d26 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,10 @@ { - "name": "libfranka - Ubuntu ${localEnv:DISTRO_NAME:focal}", - "dockerComposeFile": "docker-compose.yml", - "service": "libfranka", + "name": "libfranka - Ubuntu 20.04", + "dockerComposeFile": "docker-compose-focal.yml", + "service": "libfranka_project", "workspaceFolder": "/workspaces", "remoteUser": "user", - "initializeCommand": "echo \"USER_UID=$(id -u)\nUSER_GID=$(id -g)\n${localEnv:DISTRO:DISTRO=Dockerfile.focal}\n${localEnv:DISTRO_NAME:DISTRO_NAME=focal}\" > .devcontainer/.env", + "initializeCommand": "echo \"USER_UID=$(id -u $USER)\nUSER_GID=$(id -g $USER)\" > .devcontainer/.env", "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose-focal.yml similarity index 62% rename from .devcontainer/docker-compose.yml rename to .devcontainer/docker-compose-focal.yml index 2e205a7d..d0cd179a 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose-focal.yml @@ -1,12 +1,13 @@ services: - libfranka: + libfranka_project: build: context: ../.ci/ - dockerfile: ../.ci/${DISTRO:-Dockerfile.focal} + dockerfile: Dockerfile args: - USER_UID: ${USER_UID:-1000} - USER_GID: ${USER_GID:-1000} - container_name: libfranka-${DISTRO_NAME:-focal} + USER_UID: ${USER_UID} + USER_GID: ${USER_GID} + UBUNTU_VERSION: "20.04" + container_name: libfranka-focal network_mode: "host" shm_size: 512m privileged: true diff --git a/.github/workflows/libfranka-build.yml b/.github/workflows/libfranka-build.yml index da923cee..ea5fb596 100644 --- a/.github/workflows/libfranka-build.yml +++ b/.github/workflows/libfranka-build.yml @@ -9,6 +9,10 @@ on: jobs: build-deb: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ubuntu_version: ["20.04", "22.04", "24.04"] steps: - name: Checkout repository @@ -21,15 +25,17 @@ jobs: uses: docker/build-push-action@v4 with: context: .ci/ - file: .ci/Dockerfile.focal - tags: libfranka-build:latest + file: .ci/Dockerfile + build-args: | + UBUNTU_VERSION=${{ matrix.ubuntu_version }} + tags: libfranka-build:${{ matrix.ubuntu_version }} push: false load: true - name: Build and package in container uses: addnab/docker-run-action@v3 with: - image: libfranka-build:latest + image: libfranka-build:${{ matrix.ubuntu_version }} options: -v ${{ github.workspace }}:/workspaces run: | cd /workspaces @@ -43,7 +49,7 @@ jobs: if: github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: - name: libfranka-deb + name: libfranka-deb-${{ matrix.ubuntu_version }} path: build/*.deb # Create release for tag pushes diff --git a/CHANGELOG.md b/CHANGELOG.md index cc890c64..fc6dde9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to libfranka and pylibfranka will be documented in this file #### Changed - To support franka_ros2, we added an option for the async position control to base the `getFeedback` function on a robot state received via `franka_hardware` instead of querying the robot directly. - Format libfranka debian package to inlclude ubuntu code name and arch: libfranka_VERSION_CODENAME_ARCH.deb +- Added build containers to support Ubuntu 22.04 and 24.04 ## [0.18.2] Requires Franka Research 3 System Version >= 5.9.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a8d2a27..a3d7b42f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ if(BUILD_COVERAGE) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") endif() +# Suppress false positive warnings about string operations (aka GetRobotModel) +add_compile_options(-Wno-stringop-overflow) + ## Load dependencies include(FetchFMT) diff --git a/Jenkinsfile b/Jenkinsfile index 0b813048..8457987a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,8 +24,9 @@ pipeline { agent { dockerfile { dir ".ci" - filename "Dockerfile.${env.DISTRO}" + filename "Dockerfile" reuseNode true + additionalBuildArgs "--build-arg UBUNTU_VERSION=${env.UBUNTU_VERSION}" args '--privileged ' + '--cap-add=SYS_PTRACE ' + '--security-opt seccomp=unconfined ' + @@ -34,11 +35,22 @@ pipeline { } axes { axis { - name 'DISTRO' - values 'focal' + name 'UBUNTU_VERSION' + values '20.04', '22.04', '24.04' } } stages { + stage('Init Distro') { + steps { + script { + def map = ['20.04': 'focal', '22.04': 'jammy', '24.04': 'noble'] + env.DISTRO = map[env.UBUNTU_VERSION] + if (!env.DISTRO) { + error "Unknown UBUNTU_VERSION=${env.UBUNTU_VERSION}" + } + } + } + } stage('Setup') { stages { stage('Notify Stash') { @@ -50,7 +62,7 @@ pipeline { } stage('Clean Workspace') { steps { - sh "rm -rf build-*${DISTRO}" + sh "rm -rf build-*${env.DISTRO}" } } } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 270a6b00..5cf55e3f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.4) project(libfranka-examples CXX) From 23ccee89e6a37ce81cb659fe388d0298ff884ce8 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Fri, 12 Dec 2025 10:01:39 +0100 Subject: [PATCH 08/38] ci: parametrize container from .env --- .devcontainer/devcontainer.json | 6 +++--- .../{docker-compose-focal.yml => docker-compose.yml} | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) rename .devcontainer/{docker-compose-focal.yml => docker-compose.yml} (67%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index aeb22d26..c3967bba 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,10 @@ { - "name": "libfranka - Ubuntu 20.04", - "dockerComposeFile": "docker-compose-focal.yml", + "name": "libfranka", + "dockerComposeFile": "docker-compose.yml", "service": "libfranka_project", "workspaceFolder": "/workspaces", "remoteUser": "user", - "initializeCommand": "echo \"USER_UID=$(id -u $USER)\nUSER_GID=$(id -g $USER)\" > .devcontainer/.env", + "initializeCommand": "bash -c 'ENV_FILE=\".devcontainer/.env\"; if [ ! -f \"$ENV_FILE\" ]; then echo -e \"UBUNTU_VERSION=20.04\\nUBUNTU_CODENAME=focal\\nUSER_UID=$(id -u)\\nUSER_GID=$(id -g)\" > \"$ENV_FILE\"; else sed -i.bak \"s/^USER_UID=.*/USER_UID=$(id -u)/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_GID=.*/USER_GID=$(id -g)/\" \"$ENV_FILE\"; rm -f \"$ENV_FILE.bak\"; fi'", "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/docker-compose-focal.yml b/.devcontainer/docker-compose.yml similarity index 67% rename from .devcontainer/docker-compose-focal.yml rename to .devcontainer/docker-compose.yml index d0cd179a..333e1025 100644 --- a/.devcontainer/docker-compose-focal.yml +++ b/.devcontainer/docker-compose.yml @@ -4,10 +4,10 @@ services: context: ../.ci/ dockerfile: Dockerfile args: - USER_UID: ${USER_UID} - USER_GID: ${USER_GID} - UBUNTU_VERSION: "20.04" - container_name: libfranka-focal + USER_UID: ${USER_UID:-1000} + USER_GID: ${USER_GID:-1000} + UBUNTU_VERSION: ${UBUNTU_VERSION:-20.04} + container_name: libfranka-${UBUNTU_CODENAME:-focal} network_mode: "host" shm_size: 512m privileged: true From c593c15cc9dd0351fc2c8802b13779d25cba98c6 Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Fri, 12 Dec 2025 10:05:10 +0100 Subject: [PATCH 09/38] fix: Removed some further 'error as warning' flags and fixed some 'maybe uninitialized' warnings. --- .ci/Dockerfile | 4 +- .pre-commit-config.yaml | 2 +- CMakeLists.txt | 22 ++++++++-- Jenkinsfile | 54 ++++++++++++++++++------ include/franka/active_motion_generator.h | 2 +- include/franka/active_torque_control.h | 2 +- include/franka/robot_model.h | 13 ++---- src/robot.cpp | 2 +- src/robot_model.cpp | 18 ++++++++ test/active_motion_generator_tests.cpp | 2 +- test/active_torque_control_tests.cpp | 2 +- test/control_loop_tests.cpp | 24 ++++------- test/mock_robot.h | 2 +- test/mock_robot_impl.h | 2 +- test/robot_command_tests.cpp | 14 +++--- 15 files changed, 108 insertions(+), 57 deletions(-) diff --git a/.ci/Dockerfile b/.ci/Dockerfile index 2801d2ec..fc4998a0 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -47,13 +47,13 @@ RUN apt-get update \ graphviz \ lcov \ libeigen3-dev \ + libfmt-dev \ libpoco-dev \ lsb-release \ - libfmt-dev \ + pybind11-dev \ python3-dev \ python3-pip \ python3-venv \ - pybind11-dev \ rename \ valgrind \ wget \ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4fe0a2dd..ab912562 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: # C++ formatting with clang-format - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v16.0.6 + rev: v18.1.8 hooks: - id: clang-format types_or: [c++, c, cuda] diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d7b42f..98091327 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ if(BUILD_COVERAGE) endif() # Suppress false positive warnings about string operations (aka GetRobotModel) -add_compile_options(-Wno-stringop-overflow) +add_compile_options(-Wno-stringop-overflow -Wno-array-bounds) ## Load dependencies include(FetchFMT) @@ -287,16 +287,32 @@ if(CLANG_TIDY_PROG) string(REPLACE ";" "\"},{\"name\":\"" TIDY_LINE_FILTER "${TIDY_FILES}") set(TIDY_LINE_FILTER "[{\"name\":\"${TIDY_LINE_FILTER}\"}]") + # Disable noisy checks reported in tidy summary + # - misc-include-cleaner: requires direct include of providing headers + # - performance-avoid-endl: prefer \n over std::endl (OK in examples) + # - misc-const-correctness: suggests const qualifiers for locals + set(CLANG_TIDY_DISABLED_CHECKS "-misc-include-cleaner,-performance-avoid-endl,-misc-const-correctness") + add_custom_target(tidy COMMAND ${CLANG_TIDY_PROG} -p=${CMAKE_BINARY_DIR} - -line-filter=${TIDY_LINE_FILTER} ${SOURCES} + -quiet + -line-filter=${TIDY_LINE_FILTER} + -checks=${CLANG_TIDY_DISABLED_CHECKS} + --extra-arg=-Wno-unknown-warning-option + --extra-arg=-Wno-unused-command-line-argument + ${SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Running clang-tidy" VERBATIM ) add_custom_target(check-tidy COMMAND scripts/fail-on-output.sh ${CLANG_TIDY_PROG} -p=${CMAKE_BINARY_DIR} - -line-filter=${TIDY_LINE_FILTER} ${SOURCES} + -quiet + -line-filter=${TIDY_LINE_FILTER} + -checks=${CLANG_TIDY_DISABLED_CHECKS} + --extra-arg=-Wno-unknown-warning-option + --extra-arg=-Wno-unused-command-line-argument + ${SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Running clang-tidy" VERBATIM diff --git a/Jenkinsfile b/Jenkinsfile index 8457987a..e5090901 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,7 +12,7 @@ pipeline { } options { parallelsAlwaysFailFast() - timeout(time: 1, unit: 'HOURS') + timeout(time: 2, unit: 'HOURS') } environment { VERSION = feDetermineVersionFromGit() @@ -25,8 +25,8 @@ pipeline { dockerfile { dir ".ci" filename "Dockerfile" - reuseNode true - additionalBuildArgs "--build-arg UBUNTU_VERSION=${env.UBUNTU_VERSION}" + reuseNode false + additionalBuildArgs "--pull --build-arg UBUNTU_VERSION=${env.UBUNTU_VERSION} --tag libfranka:${env.UBUNTU_VERSION}" args '--privileged ' + '--cap-add=SYS_PTRACE ' + '--security-opt seccomp=unconfined ' + @@ -62,7 +62,15 @@ pipeline { } stage('Clean Workspace') { steps { - sh "rm -rf build-*${env.DISTRO}" + sh ''' + # Clean build dirs for this distro axis + rm -rf build-*${DISTRO} + rm -rf install-*${DISTRO} + # Remove corrupted googletest fetch content if present + rm -rf build-release.${DISTRO}/_deps/gtest-src build-release.${DISTRO}/_deps/gtest-build || true + rm -rf build-debug.${DISTRO}/_deps/gtest-src build-debug.${DISTRO}/_deps/gtest-build || true + rm -rf build-coverage.${DISTRO}/_deps/gtest-src build-coverage.${DISTRO}/_deps/gtest-build || true + ''' } } } @@ -73,10 +81,12 @@ pipeline { steps { dir("build-debug.${env.DISTRO}") { sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true cmake -DCMAKE_BUILD_TYPE=Debug -DSTRICT=ON -DBUILD_COVERAGE=OFF \ -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ -DGENERATE_PYLIBFRANKA=ON .. make -j$(nproc) + cmake --install . --prefix ../install-debug.${DISTRO} ''' } } @@ -85,10 +95,12 @@ pipeline { steps { dir("build-release.${env.DISTRO}") { sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true cmake -DCMAKE_BUILD_TYPE=Release -DSTRICT=ON -DBUILD_COVERAGE=OFF \ -DBUILD_DOCUMENTATION=ON -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ -DGENERATE_PYLIBFRANKA=ON .. make -j$(nproc) + cmake --install . --prefix ../install-release.${DISTRO} ''' } } @@ -96,7 +108,7 @@ pipeline { stage('Build examples (debug)') { steps { dir("build-debug-examples.${env.DISTRO}") { - sh "cmake -DFranka_DIR:PATH=../build-debug.${env.DISTRO} ../examples" + sh "cmake -DCMAKE_PREFIX_PATH=../install-debug.${env.DISTRO} ../examples" sh 'make -j$(nproc)' } } @@ -104,15 +116,21 @@ pipeline { stage('Build examples (release)') { steps { dir("build-release-examples.${env.DISTRO}") { - sh "cmake -DFranka_DIR:PATH=../build-release.${env.DISTRO} ../examples" + sh "cmake -DCMAKE_PREFIX_PATH=../install-release.${env.DISTRO} ../examples" sh 'make -j$(nproc)' } } } stage('Build coverage') { + when { + not { + environment name: 'UBUNTU_VERSION', value: '24.04' + } + } steps { dir("build-coverage.${env.DISTRO}") { sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_COVERAGE=ON \ -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. make -j$(nproc) @@ -147,6 +165,11 @@ pipeline { } } stage('Coverage') { + when { + not { + environment name: 'UBUNTU_VERSION', value: '24.04' + } + } steps { dir("build-coverage.${env.DISTRO}") { catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { @@ -240,16 +263,23 @@ pipeline { sh ''' # Install pylibfranka from root (builds against libfranka in build-release.focal) export LD_LIBRARY_PATH="${WORKSPACE}/build-release.${DISTRO}:${LD_LIBRARY_PATH:-}" - pip3 install . --user + if [ -n "$VIRTUAL_ENV" ]; then + python3 -m pip install . + else + python3 -m pip install . --user + fi ''' dir('pylibfranka/docs') { sh ''' - # Add sphinx to PATH - export PATH="$HOME/.local/bin:$PATH" - - # Install Sphinx and dependencies - pip3 install -r requirements.txt --user + # Install Sphinx and dependencies (respect virtualenv if present) + if [ -n "$VIRTUAL_ENV" ]; then + python3 -m pip install -r requirements.txt + else + # Add sphinx to PATH for --user installs + export PATH="$HOME/.local/bin:$PATH" + python3 -m pip install -r requirements.txt --user + fi # Set locale export LC_ALL=C.UTF-8 diff --git a/include/franka/active_motion_generator.h b/include/franka/active_motion_generator.h index 99022772..c5014920 100644 --- a/include/franka/active_motion_generator.h +++ b/include/franka/active_motion_generator.h @@ -59,7 +59,7 @@ class ActiveMotionGenerator : public ActiveControl { std::unique_lock control_lock, research_interface::robot::Move::ControllerMode controller_type) : ActiveControl(robot_impl, motion_id, std::move(control_lock)), - controller_type_(controller_type){}; + controller_type_(controller_type) {}; bool isTorqueControlFinished(const std::optional& control_input); diff --git a/include/franka/active_torque_control.h b/include/franka/active_torque_control.h index cca01209..0bcb75ac 100644 --- a/include/franka/active_torque_control.h +++ b/include/franka/active_torque_control.h @@ -52,7 +52,7 @@ class ActiveTorqueControl : public ActiveControl { ActiveTorqueControl(std::shared_ptr robot_impl, uint32_t motion_id, std::unique_lock control_lock) - : ActiveControl(std::move(robot_impl), motion_id, std::move(control_lock)){}; + : ActiveControl(std::move(robot_impl), motion_id, std::move(control_lock)) {}; }; } // namespace franka diff --git a/include/franka/robot_model.h b/include/franka/robot_model.h index 513510d8..1129deac 100644 --- a/include/franka/robot_model.h +++ b/include/franka/robot_model.h @@ -3,18 +3,11 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include +#include +#include + #include "franka/robot_model_base.h" /** diff --git a/src/robot.cpp b/src/robot.cpp index 56c0e1ac..3a31c4e2 100644 --- a/src/robot.cpp +++ b/src/robot.cpp @@ -346,7 +346,7 @@ Model Robot::loadModel(std::unique_ptr robot_model) { return impl_->loadModel(std::move(robot_model)); } -Robot::Robot(std::shared_ptr robot_impl) : impl_(std::move(robot_impl)){}; +Robot::Robot(std::shared_ptr robot_impl) : impl_(std::move(robot_impl)) {}; template std::unique_ptr Robot::startControl( const research_interface::robot::Move::ControllerMode& controller_type); diff --git a/src/robot_model.cpp b/src/robot_model.cpp index c59eb44d..a3780a9b 100644 --- a/src/robot_model.cpp +++ b/src/robot_model.cpp @@ -1,8 +1,26 @@ // Copyright (c) 2025 Franka Robotics GmbH // Use of this source code is governed by the Apache-2.0 license, see LICENSE #include "franka/robot_model.h" + #include +// Ignore warnings from Pinocchio includes +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + namespace franka { RobotModel::RobotModel(const std::string& urdf) { diff --git a/test/active_motion_generator_tests.cpp b/test/active_motion_generator_tests.cpp index b536cac4..ee7ccb6f 100644 --- a/test/active_motion_generator_tests.cpp +++ b/test/active_motion_generator_tests.cpp @@ -27,7 +27,7 @@ class ActiveMotionGeneratorTest : public ::testing::Test { std::move(std::make_unique("127.0.0.1", robot::kCommandPort)), 0, RealtimeConfig::kIgnore)), - robot_(RobotMock(robot_impl_mock_)){}; + robot_(RobotMock(robot_impl_mock_)) {}; std::unique_ptr startControl( research_interface::robot::Move::ControllerMode controller_mode); diff --git a/test/active_torque_control_tests.cpp b/test/active_torque_control_tests.cpp index b0f8b426..f0e11c8a 100644 --- a/test/active_torque_control_tests.cpp +++ b/test/active_torque_control_tests.cpp @@ -26,7 +26,7 @@ class ActiveTorqueControlTest : public ::testing::Test { std::move(std::make_unique("127.0.0.1", robot::kCommandPort)), 0, RealtimeConfig::kIgnore)), - robot(RobotMock(robot_impl_mock)){}; + robot(RobotMock(robot_impl_mock)) {}; std::unique_ptr startTorqueControl() { EXPECT_CALL(*robot_impl_mock, diff --git a/test/control_loop_tests.cpp b/test/control_loop_tests.cpp index 073bb6e1..5af99076 100644 --- a/test/control_loop_tests.cpp +++ b/test/control_loop_tests.cpp @@ -756,14 +756,12 @@ TYPED_TEST(ControlLoops, CanNotConstructWithoutMotionCallback) { StrictMock robot; setupJointVelocityLimitsMock(robot); - EXPECT_THROW(typename TestFixture::Loop loop( - robot, - [](const RobotState&, Duration) { - return Torques({0, 1, 2, 3, 4, 5, 6}); - }, - typename TestFixture::MotionGeneratorCallback(), TestFixture::kLimitRate, - getCutoffFreq(TestFixture::kFilter)), - std::invalid_argument); + EXPECT_THROW( + typename TestFixture::Loop loop( + robot, [](const RobotState&, Duration) { return Torques({0, 1, 2, 3, 4, 5, 6}); }, + typename TestFixture::MotionGeneratorCallback(), TestFixture::kLimitRate, + getCutoffFreq(TestFixture::kFilter)), + std::invalid_argument); EXPECT_THROW( typename TestFixture::Loop loop(robot, ControllerMode::kCartesianImpedance, @@ -792,10 +790,7 @@ TYPED_TEST(ControlLoops, CanConstructWithMotionAndControllerCallback) { .WillOnce(Return(100)); EXPECT_NO_THROW(typename TestFixture::Loop( - robot, - [](const RobotState&, Duration) { - return Torques({0, 1, 2, 3, 4, 5, 6}); - }, + robot, [](const RobotState&, Duration) { return Torques({0, 1, 2, 3, 4, 5, 6}); }, std::bind(&TestFixture::createMotion, this), TestFixture::kLimitRate, getCutoffFreq(TestFixture::kFilter))); } @@ -823,10 +818,7 @@ TYPED_TEST(ControlLoops, CanConstructWithControlCallback) { .WillOnce(Return(200)); EXPECT_NO_THROW(typename TestFixture::Loop( - robot, - [](const RobotState&, Duration) { - return Torques({0, 1, 2, 3, 4, 5, 6}); - }, + robot, [](const RobotState&, Duration) { return Torques({0, 1, 2, 3, 4, 5, 6}); }, TestFixture::kLimitRate, getCutoffFreq(TestFixture::kFilter))); } diff --git a/test/mock_robot.h b/test/mock_robot.h index c36f4773..a1921bea 100644 --- a/test/mock_robot.h +++ b/test/mock_robot.h @@ -9,5 +9,5 @@ using namespace franka; class RobotMock : public Robot { public: ~RobotMock() = default; - RobotMock(std::shared_ptr robot_impl) : Robot(robot_impl){}; + RobotMock(std::shared_ptr robot_impl) : Robot(robot_impl) {}; }; diff --git a/test/mock_robot_impl.h b/test/mock_robot_impl.h index 40fa13b7..8e9c8f51 100644 --- a/test/mock_robot_impl.h +++ b/test/mock_robot_impl.h @@ -13,7 +13,7 @@ class RobotImplMock : public Robot::Impl { public: virtual ~RobotImplMock() = default; RobotImplMock(std::unique_ptr network, size_t log_size, RealtimeConfig realtime_config) - : Robot::Impl(std::move(network), log_size, realtime_config){}; + : Robot::Impl(std::move(network), log_size, realtime_config) {}; MOCK_METHOD(uint32_t, startMotion, (research_interface::robot::Move::ControllerMode, diff --git a/test/robot_command_tests.cpp b/test/robot_command_tests.cpp index e36cade1..5ffadef2 100644 --- a/test/robot_command_tests.cpp +++ b/test/robot_command_tests.cpp @@ -30,6 +30,8 @@ using research_interface::robot::SetNEToEE; using research_interface::robot::StopMove; const std::string kExpectedModelString = "test_string"; +constexpr research_interface::robot::Move::Deviation kDefaultDeviation1(1, 2, 3); +constexpr research_interface::robot::Move::Deviation kDefaultDeviation2(4, 5, 6); template class Command : public ::testing::Test { @@ -152,8 +154,8 @@ bool Command::compare(const StopMove::Request&, const StopMove::Reques template <> Move::Request Command::getExpected() { return Move::Request(Move::ControllerMode::kJointImpedance, - Move::MotionGeneratorMode::kJointVelocity, Move::Deviation(1, 2, 3), - Move::Deviation(4, 5, 6)); + Move::MotionGeneratorMode::kJointVelocity, kDefaultDeviation1, + kDefaultDeviation2); } template <> @@ -390,8 +392,8 @@ TEST_F(MoveCommand, CanReceiveMotionStarted) { franka::RealtimeConfig::kIgnore); Move::Request request(Move::ControllerMode::kJointImpedance, - Move::MotionGeneratorMode::kJointVelocity, Move::Deviation(1, 2, 3), - Move::Deviation(4, 5, 6)); + Move::MotionGeneratorMode::kJointVelocity, kDefaultDeviation1, + kDefaultDeviation2); server .waitForCommand( @@ -412,8 +414,8 @@ TEST_P(MoveCommand, CanReceiveErrorResponses) { franka::RealtimeConfig::kIgnore); Move::Request request(Move::ControllerMode::kJointImpedance, - Move::MotionGeneratorMode::kJointVelocity, Move::Deviation(1, 2, 3), - Move::Deviation(4, 5, 6)); + Move::MotionGeneratorMode::kJointVelocity, kDefaultDeviation1, + kDefaultDeviation2); server .waitForCommand<::Move>([this](const Move::Request& request) -> Move::Response { From 754403c158d5e525134f228e66dbede028a87cdf Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Tue, 16 Dec 2025 17:37:40 +0100 Subject: [PATCH 10/38] refactor: The user can now choose the ubuntu version in the dev container --- .devcontainer/devcontainer.json | 2 +- .devcontainer/docker-compose.yml | 2 +- devcontainer_distro | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 devcontainer_distro diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c3967bba..7535b16c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "service": "libfranka_project", "workspaceFolder": "/workspaces", "remoteUser": "user", - "initializeCommand": "bash -c 'ENV_FILE=\".devcontainer/.env\"; if [ ! -f \"$ENV_FILE\" ]; then echo -e \"UBUNTU_VERSION=20.04\\nUBUNTU_CODENAME=focal\\nUSER_UID=$(id -u)\\nUSER_GID=$(id -g)\" > \"$ENV_FILE\"; else sed -i.bak \"s/^USER_UID=.*/USER_UID=$(id -u)/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_GID=.*/USER_GID=$(id -g)/\" \"$ENV_FILE\"; rm -f \"$ENV_FILE.bak\"; fi'", + "initializeCommand": "bash -c 'ENV_FILE=\".devcontainer/.env\"; UBUNTU_VERSION=$(cat devcontainer_distro 2>/dev/null || echo 24.04); if [ ! -f \"$ENV_FILE\" ]; then echo -e \"UBUNTU_VERSION=${UBUNTU_VERSION}\\nUSER_UID=$(id -u)\\nUSER_GID=$(id -g)\" > \"$ENV_FILE\"; else sed -i.bak \"s/^UBUNTU_VERSION=.*/UBUNTU_VERSION=${UBUNTU_VERSION}/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_UID=.*/USER_UID=$(id -u)/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_GID=.*/USER_GID=$(id -g)/\" \"$ENV_FILE\"; rm -f \"$ENV_FILE.bak\"; fi'", "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 333e1025..8bfa12a9 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -7,7 +7,7 @@ services: USER_UID: ${USER_UID:-1000} USER_GID: ${USER_GID:-1000} UBUNTU_VERSION: ${UBUNTU_VERSION:-20.04} - container_name: libfranka-${UBUNTU_CODENAME:-focal} + container_name: libfranka-${UBUNTU_VERSION:-20.04} network_mode: "host" shm_size: 512m privileged: true diff --git a/devcontainer_distro b/devcontainer_distro new file mode 100644 index 00000000..e191604a --- /dev/null +++ b/devcontainer_distro @@ -0,0 +1 @@ +24.04 From f7c548552fa809c1f8fbf8f54032cfb2f07635c3 Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Tue, 16 Dec 2025 17:43:04 +0100 Subject: [PATCH 11/38] fix: Muted and fixed serveral clang-tidy warnings --- .ci/Dockerfile | 7 +- CMakeLists.txt | 2 +- Jenkinsfile | 10 +- examples/motion_with_control.cpp | 6 +- ...ion_with_control_external_control_loop.cpp | 6 +- include/franka/control_types.h | 7 +- include/franka/errors.h | 118 ++++++++++++------ include/franka/exception.h | 7 +- include/franka/model.h | 2 +- include/franka/robot_state.h | 2 +- include/franka/vacuum_gripper.h | 2 +- src/control_loop.h | 16 ++- src/network.h | 4 +- src/robot_impl.cpp | 2 +- 14 files changed, 122 insertions(+), 69 deletions(-) diff --git a/.ci/Dockerfile b/.ci/Dockerfile index fc4998a0..02f1ee7c 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -148,9 +148,12 @@ RUN python3 -m venv /opt/venv && \ flake8 \ numpy \ pybind11 && \ - /opt/venv/bin/python --version && /opt/venv/bin/pip --version + /opt/venv/bin/python --version && /opt/venv/bin/pip --version && \ + chown -R $USER_UID:$USER_GID /opt/venv -ENV PATH="/opt/venv/bin:${PATH}" +# Expose the virtualenv via env var for downstream scripts (e.g., Jenkinsfile) +ENV VIRTUAL_ENV="/opt/venv" +ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" RUN apt-get update && \ apt-get install -y wget gnupg lsb-release software-properties-common && \ wget https://apt.llvm.org/llvm.sh && \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 98091327..9c19bd79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,7 +291,7 @@ if(CLANG_TIDY_PROG) # - misc-include-cleaner: requires direct include of providing headers # - performance-avoid-endl: prefer \n over std::endl (OK in examples) # - misc-const-correctness: suggests const qualifiers for locals - set(CLANG_TIDY_DISABLED_CHECKS "-misc-include-cleaner,-performance-avoid-endl,-misc-const-correctness") + set(CLANG_TIDY_DISABLED_CHECKS "-misc-include-cleaner,-performance-avoid-endl,-misc-const-correctness,-readability-redundant-member-init") add_custom_target(tidy COMMAND ${CLANG_TIDY_PROG} -p=${CMAKE_BINARY_DIR} diff --git a/Jenkinsfile b/Jenkinsfile index e5090901..a639be89 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -261,7 +261,7 @@ pipeline { // Build and publish pylibfranka documentation catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { sh ''' - # Install pylibfranka from root (builds against libfranka in build-release.focal) + # Install pylibfranka from root (builds against libfranka in build-release.${DISTRO}) export LD_LIBRARY_PATH="${WORKSPACE}/build-release.${DISTRO}:${LD_LIBRARY_PATH:-}" if [ -n "$VIRTUAL_ENV" ]; then python3 -m pip install . @@ -288,8 +288,12 @@ pipeline { # Add libfranka to library path export LD_LIBRARY_PATH="${WORKSPACE}/build-release.${DISTRO}:${LD_LIBRARY_PATH:-}" - # Build the documentation - make html + # Build the documentation only on Ubuntu 20.04 + if [ "${UBUNTU_VERSION}" = "20.04" ]; then + make html + else + echo "Skipping docs build on ${UBUNTU_VERSION}" + fi ''' publishHTML([allowMissing: false, diff --git a/examples/motion_with_control.cpp b/examples/motion_with_control.cpp index bc23ce22..2aaf78a1 100644 --- a/examples/motion_with_control.cpp +++ b/examples/motion_with_control.cpp @@ -37,7 +37,7 @@ class Controller { dq_buffer_ = std::vector(dq_filter_size_ * 7, 0.0); } - inline franka::Torques step(const franka::RobotState& state) { + franka::Torques step(const franka::RobotState& state) { updateDQFilter(state); std::array tau_J_d; // NOLINT(readability-identifier-naming) @@ -66,8 +66,8 @@ class Controller { size_t dq_current_filter_position_{0}; size_t dq_filter_size_; - const std::array K_P_; // NOLINT(readability-identifier-naming) - const std::array K_D_; // NOLINT(readability-identifier-naming) + std::array K_P_; // NOLINT(readability-identifier-naming) + std::array K_D_; // NOLINT(readability-identifier-naming) std::array dq_d_; std::vector dq_buffer_; diff --git a/examples/motion_with_control_external_control_loop.cpp b/examples/motion_with_control_external_control_loop.cpp index b746b7e3..849ecada 100644 --- a/examples/motion_with_control_external_control_loop.cpp +++ b/examples/motion_with_control_external_control_loop.cpp @@ -40,7 +40,7 @@ class Controller { dq_buffer_ = std::vector(dq_filter_size_ * 7, 0.0); } - inline franka::Torques step(const franka::RobotState& state) { + franka::Torques step(const franka::RobotState& state) { updateDQFilter(state); std::array tau_J_d; // NOLINT(readability-identifier-naming) @@ -69,8 +69,8 @@ class Controller { size_t dq_current_filter_position_{0}; size_t dq_filter_size_; - const std::array K_P_; // NOLINT(readability-identifier-naming) - const std::array K_D_; // NOLINT(readability-identifier-naming) + std::array K_P_; // NOLINT(readability-identifier-naming) + std::array K_D_; // NOLINT(readability-identifier-naming) std::array dq_d_; std::vector dq_buffer_; diff --git a/include/franka/control_types.h b/include/franka/control_types.h index d5019f7a..965b4959 100644 --- a/include/franka/control_types.h +++ b/include/franka/control_types.h @@ -16,14 +16,17 @@ namespace franka { /** * Available controller modes for a franka::Robot. */ -enum class ControllerMode { kJointImpedance, kCartesianImpedance }; +enum class ControllerMode { // NOLINT(performance-enum-size) + kJointImpedance, + kCartesianImpedance +}; /** * Used to decide whether to enforce realtime mode for a control loop thread. * * @see Robot::Robot */ -enum class RealtimeConfig { kEnforce, kIgnore }; +enum class RealtimeConfig { kEnforce, kIgnore }; // NOLINT(performance-enum-size) /** * Helper type for control and motion generation loops. diff --git a/include/franka/errors.h b/include/franka/errors.h index a3b6f768..7957af99 100644 --- a/include/franka/errors.h +++ b/include/franka/errors.h @@ -67,182 +67,218 @@ struct Errors { /** * True if the robot moved past the joint limits. */ - const bool& joint_position_limits_violation; + const bool& + joint_position_limits_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the robot moved past any of the virtual walls. */ - const bool& cartesian_position_limits_violation; + const bool& + cartesian_position_limits_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the robot would have collided with itself. */ - const bool& self_collision_avoidance_violation; + const bool& + self_collision_avoidance_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the robot exceeded joint velocity limits. */ - const bool& joint_velocity_violation; + const bool& + joint_velocity_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the robot exceeded Cartesian velocity limits. */ - const bool& cartesian_velocity_violation; + const bool& + cartesian_velocity_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the robot exceeded safety threshold during force control. */ - const bool& force_control_safety_violation; + const bool& + force_control_safety_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if a collision was detected, i.e.\ the robot exceeded a torque threshold in a joint * motion. */ - const bool& joint_reflex; + const bool& joint_reflex; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if a collision was detected, i.e.\ the robot exceeded a torque threshold in a Cartesian * motion. */ - const bool& cartesian_reflex; + const bool& cartesian_reflex; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if internal motion generator did not reach the goal pose. */ - const bool& max_goal_pose_deviation_violation; + const bool& + max_goal_pose_deviation_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if internal motion generator deviated from the path. */ - const bool& max_path_pose_deviation_violation; + const bool& + max_path_pose_deviation_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if Cartesian velocity profile for internal motions was exceeded. */ - const bool& cartesian_velocity_profile_safety_violation; + const bool& + cartesian_velocity_profile_safety_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if an external joint position motion generator was started with a pose too far from the * current pose. */ - const bool& joint_position_motion_generator_start_pose_invalid; + const bool& + joint_position_motion_generator_start_pose_invalid; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if an external joint motion generator would move into a joint limit. */ - const bool& joint_motion_generator_position_limits_violation; + const bool& + joint_motion_generator_position_limits_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if an external joint motion generator exceeded velocity limits. */ - const bool& joint_motion_generator_velocity_limits_violation; + const bool& + joint_motion_generator_velocity_limits_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if commanded velocity in joint motion generators is discontinuous (target values are too * far apart). */ - const bool& joint_motion_generator_velocity_discontinuity; + const bool& + joint_motion_generator_velocity_discontinuity; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if commanded acceleration in joint motion generators is discontinuous (target values are * too far apart). */ - const bool& joint_motion_generator_acceleration_discontinuity; + const bool& + joint_motion_generator_acceleration_discontinuity; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if an external Cartesian position motion generator was started with a pose too far from * the current pose. */ - const bool& cartesian_position_motion_generator_start_pose_invalid; + const bool& + cartesian_position_motion_generator_start_pose_invalid; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if an external Cartesian motion generator would move into an elbow limit. */ - const bool& cartesian_motion_generator_elbow_limit_violation; + const bool& + cartesian_motion_generator_elbow_limit_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if an external Cartesian motion generator would move with too high velocity. */ - const bool& cartesian_motion_generator_velocity_limits_violation; + const bool& + cartesian_motion_generator_velocity_limits_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if commanded velocity in Cartesian motion generators is discontinuous (target values are * too far apart). */ - const bool& cartesian_motion_generator_velocity_discontinuity; + const bool& + cartesian_motion_generator_velocity_discontinuity; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if commanded acceleration in Cartesian motion generators is discontinuous (target values * are too far apart). */ - const bool& cartesian_motion_generator_acceleration_discontinuity; + const bool& + cartesian_motion_generator_acceleration_discontinuity; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if commanded elbow values in Cartesian motion generators are inconsistent. */ - const bool& cartesian_motion_generator_elbow_sign_inconsistent; + const bool& + cartesian_motion_generator_elbow_sign_inconsistent; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the first elbow value in Cartesian motion generators is too far from initial one. */ - const bool& cartesian_motion_generator_start_elbow_invalid; + const bool& + cartesian_motion_generator_start_elbow_invalid; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the joint position limits would be exceeded after IK calculation. */ - const bool& cartesian_motion_generator_joint_position_limits_violation; + const bool& + cartesian_motion_generator_joint_position_limits_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the joint velocity limits would be exceeded after IK calculation. */ - const bool& cartesian_motion_generator_joint_velocity_limits_violation; + const bool& + cartesian_motion_generator_joint_velocity_limits_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the joint velocity in Cartesian motion generators is discontinuous after IK * calculation. */ - const bool& cartesian_motion_generator_joint_velocity_discontinuity; + const bool& + cartesian_motion_generator_joint_velocity_discontinuity; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the joint acceleration in Cartesian motion generators is discontinuous after IK * calculation. */ - const bool& cartesian_motion_generator_joint_acceleration_discontinuity; + const bool& + cartesian_motion_generator_joint_acceleration_discontinuity; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the Cartesian pose is not a valid transformation matrix. */ - const bool& cartesian_position_motion_generator_invalid_frame; + const bool& + cartesian_position_motion_generator_invalid_frame; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if desired force exceeds the safety thresholds. */ - const bool& force_controller_desired_force_tolerance_violation; + const bool& + force_controller_desired_force_tolerance_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the torque set by the external controller is discontinuous. */ - const bool& controller_torque_discontinuity; + const bool& + controller_torque_discontinuity; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the start elbow sign was inconsistent. * * Applies only to motions started from Desk. */ - const bool& start_elbow_sign_inconsistent; + const bool& + start_elbow_sign_inconsistent; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if minimum network communication quality could not be held during a motion. */ - const bool& communication_constraints_violation; + const bool& + communication_constraints_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if commanded values would result in exceeding the power limit. */ - const bool& power_limit_violation; + const bool& power_limit_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the robot is overloaded for the required motion. * * Applies only to motions started from Desk. */ - const bool& joint_p2p_insufficient_torque_for_planning; + const bool& + joint_p2p_insufficient_torque_for_planning; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the measured torque signal is out of the safe range. */ - const bool& tau_j_range_violation; + const bool& tau_j_range_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if an instability is detected. */ - const bool& instability_detected; + const bool& instability_detected; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the robot is in joint position limits violation error and the user guides the robot * further towards the limit. */ - const bool& joint_move_in_wrong_direction; + const bool& + joint_move_in_wrong_direction; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the generated motion violates a joint limit. */ - const bool& cartesian_spline_motion_generator_violation; + const bool& + cartesian_spline_motion_generator_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the generated motion violates a joint limit. */ - const bool& joint_via_motion_generator_planning_joint_limit_violation; + const bool& + joint_via_motion_generator_planning_joint_limit_violation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the gravity vector could not be initialized by measureing the base acceleration. */ - const bool& base_acceleration_initialization_timeout; + const bool& + base_acceleration_initialization_timeout; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * True if the base acceleration O_ddP_O cannot be determined. */ - const bool& base_acceleration_invalid_reading; + const bool& + base_acceleration_invalid_reading; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) }; /** diff --git a/include/franka/exception.h b/include/franka/exception.h index 92425347..2a8cf392 100644 --- a/include/franka/exception.h +++ b/include/franka/exception.h @@ -57,11 +57,11 @@ struct IncompatibleVersionException : public Exception { /** * Control's protocol version. */ - const uint16_t server_version; + const uint16_t server_version; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) /** * libfranka protocol version. */ - const uint16_t library_version; + const uint16_t library_version; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) }; /** @@ -82,7 +82,8 @@ struct ControlException : public Exception { /** * Vector of states and commands logged just before the exception occurred. */ - const std::vector log; + const std::vector + log; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) }; /** diff --git a/include/franka/model.h b/include/franka/model.h index b6ba1e57..66209a7f 100644 --- a/include/franka/model.h +++ b/include/franka/model.h @@ -19,7 +19,7 @@ namespace franka { /** * Enumerates the seven joints, the flange, and the end effector of a robot. */ -enum class Frame { +enum class Frame { // NOLINT(performance-enum-size) kJoint1, kJoint2, kJoint3, diff --git a/include/franka/robot_state.h b/include/franka/robot_state.h index 2bc9a6e7..aae3f02f 100644 --- a/include/franka/robot_state.h +++ b/include/franka/robot_state.h @@ -18,7 +18,7 @@ namespace franka { /** * Describes the robot's current mode. */ -enum class RobotMode { +enum class RobotMode { // NOLINT(performance-enum-size) kOther, kIdle, kMove, diff --git a/include/franka/vacuum_gripper.h b/include/franka/vacuum_gripper.h index 1064b95c..0303c075 100644 --- a/include/franka/vacuum_gripper.h +++ b/include/franka/vacuum_gripper.h @@ -35,7 +35,7 @@ class VacuumGripper { /** * Vacuum production setup profile. */ - enum class ProductionSetupProfile { kP0, kP1, kP2, kP3 }; + enum class ProductionSetupProfile { kP0, kP1, kP2, kP3 }; // NOLINT(performance-enum-size) /** * Establishes a connection with a vacuum gripper connected to a robot. diff --git a/src/control_loop.h b/src/control_loop.h index 83c8e1b8..109c4bae 100644 --- a/src/control_loop.h +++ b/src/control_loop.h @@ -105,11 +105,17 @@ class ControlLoop { research_interface::robot::MotionGeneratorCommand& motion_generation_command) -> bool; private: - RobotControl& robot_; - const MotionGeneratorCallback motion_callback_; // NOLINT(readability-identifier-naming) - const ControlCallback control_callback_; // NOLINT(readability-identifier-naming) - const bool limit_rate_; // NOLINT(readability-identifier-naming) - const double cutoff_frequency_; // NOLINT(readability-identifier-naming) + RobotControl& robot_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const MotionGeneratorCallback + motion_callback_; /* NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members, + readability-identifier-naming) */ + const ControlCallback + control_callback_; /* NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members, + readability-identifier-naming) */ + const bool limit_rate_; /* NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members, + readability-identifier-naming) */ + const double cutoff_frequency_; /* NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members, + readability-identifier-naming) */ uint32_t motion_id_ = 0; bool initialized_filter_{false}; diff --git a/src/network.h b/src/network.h index 1d2e187c..be18e955 100644 --- a/src/network.h +++ b/src/network.h @@ -261,7 +261,7 @@ typename T::Response Network::tcpBlockingReceiveResponse(uint32_t command_id, using namespace std::literals::chrono_literals; // NOLINT(google-build-using-namespace) std::unique_lock lock(tcp_mutex_, std::defer_lock); decltype(received_responses_)::const_iterator received_response; - do { + do { // NOLINT(cppcoreguidelines-avoid-do-while) lock.lock(); tcpReadFromBuffer(kTimeout); received_response = received_responses_.find(command_id); @@ -294,7 +294,7 @@ Network::tcpBlockingReceiveResponse( using namespace std::literals::chrono_literals; // NOLINT(google-build-using-namespace) std::unique_lock lock(tcp_mutex_, std::defer_lock); decltype(received_responses_)::const_iterator received_response; - do { + do { // NOLINT(cppcoreguidelines-avoid-do-while) lock.lock(); tcpReadFromBuffer(kTimeout); received_response = received_responses_.find(command_id); diff --git a/src/robot_impl.cpp b/src/robot_impl.cpp index 0a15638f..646f8506 100644 --- a/src/robot_impl.cpp +++ b/src/robot_impl.cpp @@ -466,7 +466,7 @@ void Robot::Impl::cancelMotion(uint32_t motion_id) { } research_interface::robot::RobotState robot_state; - do { + do { // NOLINT(cppcoreguidelines-avoid-do-while) robot_state = receiveRobotState(); } while (motionGeneratorRunning() || controllerRunning()); From 0d14978d41b80e4d26d480057312d19295c5d632 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Wed, 17 Dec 2025 21:27:13 +0100 Subject: [PATCH 12/38] docs: add supported ubuntu versions --- docs/installation.rst | 39 ++++++++++++++++++++++++++++----------- docs/real_time_kernel.rst | 27 +++++++-------------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 7a243a46..dd88567a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -5,35 +5,52 @@ Build / Installation .. note:: - The installation currently only support Ubuntu 20.04. Please ensure you are using this version to avoid compatibility issues. + **libfranka** supports Ubuntu 20.04 (focal), 22.04 (jammy), and 24.04 (noble) LTS versions. Pre-built **amd64** Debian packages are available for all supported versions. .. _libfranka_installation_debian_package: + Debian Package ~~~~~~~~~~~~~~ -**libfranka** releases are provided as pre-built Debian packages. +**libfranka** releases are provided as pre-built Debian packages for multiple Ubuntu LTS versions. You can find the packaged artifacts on the `libfranka releases `_ page on GitHub. -Download the Debian package: + +Installation Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Step 1: Check your Ubuntu version** .. code-block:: bash - wget https://github.com/frankarobotics/libfranka/releases/download/0.18.2/libfranka_0.18.2_focal_amd64.deb + lsb_release -a + +**Step 2: Download and install the appropriate package** + -Install the package on your system: +For the supported ubuntu versions, use the following pattern: .. code-block:: bash - sudo dpkg -i libfranka_0.18.2_focal_amd64.deb + # Replace with your desired version and Ubuntu codename + VERSION=0.18.2 + CODENAME=focal # or jammy, noble -For other versions or architectures, use the following pattern: + wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb + sudo dpkg -i libfranka_${VERSION}_${CODENAME}_amd64.deb -.. code-block:: bash +.. tip:: + + This is the recommended installation method for **libfranka** if you do not need to modify the source code. + +Verify Installation +^^^^^^^^^^^^^^^^^^^ - wget https://github.com/frankarobotics/libfranka/releases/download//libfranka___.deb - sudo dpkg -i libfranka___.deb +After installation, verify that libfranka is correctly installed: + +.. code-block:: bash -This is the recommended installation method for **libfranka** if you do not need to modify the source code. + dpkg -l | grep libfranka .. _libfranka_installation_docker: Inside docker container diff --git a/docs/real_time_kernel.rst b/docs/real_time_kernel.rst index 4825b620..b455e10e 100644 --- a/docs/real_time_kernel.rst +++ b/docs/real_time_kernel.rst @@ -11,8 +11,14 @@ Different kernel versions are compatible with specific Ubuntu releases. The tabl +----------------+-------------------------+ | Kernel Version | Ubuntu Version | +================+=========================+ +| Pro Kernel | 24.04 (Noble Numbat) | ++----------------+-------------------------+ +| 6.8.0 | 24.04 (Noble Numbat) | ++----------------+-------------------------+ | Pro Kernel | 22.04 (Jammy Jellyfish) | +----------------+-------------------------+ +| 6.8.0 | 22.04 (Jammy Jellyfish) | ++----------------+-------------------------+ | 5.9.1 | 20.04 (Focal Fossa) | +----------------+-------------------------+ | 5.4.19 | 18.04 (Bionic Beaver) | @@ -37,25 +43,6 @@ Then decide which kernel version to use. Check your current version with ``uname Real-time patches are only available for select versions: `RT Kernel Patches `_. Choose the version closest to your current kernel. Use ``curl`` to download the sources. -.. note:: - Ubuntu 16.04 (tested with kernel 4.14.12): - - .. code-block:: bash - - curl -LO https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.14.12.tar.xz - curl -LO https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.14.12.tar.sign - curl -LO https://www.kernel.org/pub/linux/kernel/projects/rt/4.14/older/patch-4.14.12-rt10.patch.xz - curl -LO https://www.kernel.org/pub/linux/kernel/projects/rt/4.14/older/patch-4.14.12-rt10.patch.sign - -.. note:: - Ubuntu 18.04 (tested with kernel 5.4.19): - - .. code-block:: bash - - curl -LO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.4.19.tar.xz - curl -LO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.4.19.tar.sign - curl -LO https://www.kernel.org/pub/linux/kernel/projects/rt/5.4/older/patch-5.4.19-rt10.patch.xz - curl -LO https://www.kernel.org/pub/linux/kernel/projects/rt/5.4/older/patch-5.4.19-rt10.patch.sign .. note:: Ubuntu 20.04 (tested with kernel 5.9.1): @@ -68,7 +55,7 @@ Choose the version closest to your current kernel. Use ``curl`` to download the curl -LO https://www.kernel.org/pub/linux/kernel/projects/rt/5.9/patch-5.9.1-rt20.patch.sign .. note:: - Ubuntu 22.04: We recommend using the `Ubuntu Pro real-time kernel `_. + Ubuntu 22.04 and 24.04: We recommend using the `Ubuntu Pro real-time kernel `_. After enabling it, you can skip directly to :ref:`installation-real-time`. If you prefer not to use Ubuntu Pro, you can patch manually (tested with kernel 6.8.0): From 9595ba84f132be7557a0471045fc85568820ee1b Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Wed, 17 Dec 2025 21:44:00 +0100 Subject: [PATCH 13/38] ci: add checksum for releases --- .github/workflows/libfranka-build.yml | 41 +++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/libfranka-build.yml b/.github/workflows/libfranka-build.yml index ea5fb596..40eefd49 100644 --- a/.github/workflows/libfranka-build.yml +++ b/.github/workflows/libfranka-build.yml @@ -1,4 +1,4 @@ -name: Build the libfranka Debian Package +name: Build libfranka Debian Packages on: push: @@ -21,6 +21,9 @@ jobs: submodules: 'recursive' fetch-depth: 0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build Docker image uses: docker/build-push-action@v4 with: @@ -31,6 +34,8 @@ jobs: tags: libfranka-build:${{ matrix.ubuntu_version }} push: false load: true + cache-from: type=gha,scope=${{ matrix.ubuntu_version }} + cache-to: type=gha,mode=max,scope=${{ matrix.ubuntu_version }} - name: Build and package in container uses: addnab/docker-run-action@v3 @@ -44,22 +49,42 @@ jobs: cd build cpack -G DEB - # Upload to artifacts for manual workflow runs - - name: Upload Debian package (manual runs) + - name: Generate SHA256 checksums + run: | + cd build + for deb in *.deb; do + if [ -f "$deb" ]; then + sha256sum "$deb" > "${deb}.sha256" + echo "Generated checksum for $deb:" + cat "${deb}.sha256" + fi + done + + - name: List generated files + run: | + echo "Generated packages and checksums:" + ls -lh build/*.deb build/*.sha256 + + - name: Upload Debian package and checksum (manual runs) if: github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: - name: libfranka-deb-${{ matrix.ubuntu_version }} - path: build/*.deb + name: libfranka-${{ matrix.ubuntu_version }} + path: | + build/*.deb + build/*.sha256 + retention-days: 30 - # Create release for tag pushes - - name: Create GitHub Release and Upload Assets + - name: Upload to GitHub Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: - files: build/*.deb + files: | + build/*.deb + build/*.sha256 generate_release_notes: true draft: false prerelease: false + fail_on_unmatched_files: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8318355df440654b3c8b5f63a495f1db21db55d1 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 18 Dec 2025 10:37:51 +0100 Subject: [PATCH 14/38] docs: update README.md --- README.rst | 609 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 444 insertions(+), 165 deletions(-) diff --git a/README.rst b/README.rst index fa21fdad..fbba89bb 100644 --- a/README.rst +++ b/README.rst @@ -16,352 +16,631 @@ Key Features - **Low-level control**: Access precise motion control for research robots. - **Real-time communication**: Interact with the robot in real-time. +- **Multi-platform support**: Ubuntu 20.04, 22.04, and 24.04 LTS -Getting Started ---------------- - -.. _system-requirements: 1. System Requirements ~~~~~~~~~~~~~~~~~~~~~~ Before using **libfranka**, ensure your system meets the following requirements: -- **Operating System**: `Linux with PREEMPT_RT patched kernel `_ (Ubuntu 16.04 or later, Ubuntu 22.04 recommended) -- **Compiler**: GCC 7 or later -- **CMake**: Version 3.10 or later -- **Robot**: Franka Robotics robot with FCI feature installed +**Operating System**: + - Ubuntu 20.04 LTS (Focal Fossa) + - Ubuntu 22.04 LTS (Jammy Jellyfish) + - Ubuntu 24.04 LTS (Noble Numbat) + - `Linux with PREEMPT_RT patched kernel `_ recommended for real-time control + +**Build Tools** (for building from source): + - GCC 9 or later + - CMake 3.22 or later + - Git + +**Hardware**: + - Franka Robotics robot with FCI feature installed + - Network connection to robot (1000BASE-T Ethernet recommended) + +.. _installation-debian-package: + +2. Installation from Debian Package (Recommended) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The easiest way to install **libfranka** is using pre-built Debian packages. This method is recommended for most users. + +Supported Platforms +^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 25 25 25 25 + + * - Ubuntu Version + - Codename + - Architecture + - Status + * - 20.04 LTS + - focal + - amd64 + - ✅ Supported + * - 22.04 LTS + - jammy + - amd64 + - ✅ Supported + * - 24.04 LTS + - noble + - amd64 + - ✅ Supported + +Installation Steps +^^^^^^^^^^^^^^^^^^ + +**Step 1: Check your Ubuntu version** -.. _installing-dependencies: +.. code-block:: bash -2. Installing dependencies -~~~~~~~~~~~~~~~~~~~~~~~~~~ + lsb_release -a + +**Step 2: Download and install the appropriate package** + +**For Ubuntu 20.04 (Focal):** .. code-block:: bash - sudo apt-get update - sudo apt-get install -y build-essential cmake git libpoco-dev libeigen3-dev libfmt-dev + wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_focal_amd64.deb + sudo dpkg -i libfranka_0.18.3_focal_amd64.deb -3. Install from Debian Package - Generic Pattern -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**For Ubuntu 22.04 (Jammy):** -.. note:: +.. code-block:: bash - The installation packages currently only support Ubuntu 20 (focal). Please ensure you are using this version to avoid compatibility issues. + wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_jammy_amd64.deb + sudo dpkg -i libfranka_0.18.3_jammy_amd64.deb -**Check your architecture:** +**For Ubuntu 24.04 (Noble):** .. code-block:: bash - dpkg --print-architecture + wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_noble_amd64.deb + sudo dpkg -i libfranka_0.18.3_noble_amd64.deb -**Download and install:** +**Step 3 (Optional): Verify checksum** .. code-block:: bash - wget https://github.com/frankarobotics/libfranka/releases/download//libfranka___.deb - sudo dpkg -i libfranka___.deb + # Download checksum file + wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_focal_amd64.deb.sha256 -Replace ```` with the desired release version (e.g., ``0.18.2``, with ``focal`` and ```` with your system architecture (e.g., ``amd64`` or ``arm64``). + # Verify integrity + sha256sum -c libfranka_0.18.3_focal_amd64.deb.sha256 -**Example for version 0.18.2 on amd64:** +Generic Installation Pattern +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For other versions, use this pattern: .. code-block:: bash - wget https://github.com/frankarobotics/libfranka/releases/download/0.18.2/libfranka_0.18.2_focal_amd64.deb - sudo dpkg -i libfranka_0.18.2_focal_amd64.deb + # Set your version and Ubuntu codename + VERSION=0.18.3 + CODENAME=focal # or jammy, noble -.. _building-in-docker: -4. Building libfranka Inside Docker -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb + sudo dpkg -i libfranka_${VERSION}_${CODENAME}_amd64.deb -If you prefer to build **libfranka** inside a Docker container, you can use the provided Docker setup. This ensures a consistent build environment and avoids dependency conflicts on your host system. +.. tip:: -Docker creates a self-contained environment, which is helpful if: + This is the **recommended installation method** if you don't need to modify the source code. -- Your system doesn't meet the requirements -- You want to avoid installing dependencies on your main system -- You prefer a clean, reproducible setup +Find all available releases on the `GitHub Releases page `_. -If you haven't already, clone the **libfranka** repository: +.. _building-with-docker: - .. code-block:: bash +3. Building with Docker (Development Environment) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - git clone --recurse-submodules https://github.com/frankarobotics/libfranka.git - cd libfranka - git checkout +Docker provides a consistent, isolated build environment. This method is ideal for: -Using Docker command line -^^^^^^^^^^^^^^^^^^^^^^^^^ +- Development and testing +- Building for multiple Ubuntu versions +- Avoiding dependency conflicts on your host system -1. **Build the Docker image**: +Prerequisites +^^^^^^^^^^^^^ - .. code-block:: bash +- Docker installed on your system +- Visual Studio Code with Dev Containers extension (optional, for VS Code users) - cd .ci - docker build -t libfranka:latest -f Dockerfile.focal . - cd .. +Clone the Repository +^^^^^^^^^^^^^^^^^^^^ -2. **Run the Docker container**: +.. code-block:: bash - .. code-block:: bash + git clone --recurse-submodules https://github.com/frankarobotics/libfranka.git + cd libfranka + git checkout 0.18.3 # or your desired version - docker run --rm -it -v $(pwd):/workspace libfranka:latest +Method A: Using Visual Studio Code (Recommended) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - If you already have a build folder, you must remove it first to avoid issues: +1. **Install Visual Studio Code** - .. code-block:: bash + Download from https://code.visualstudio.com/ - rm -rf /workspace/build - mkdir -p /workspace/build - cd /workspace/build - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF /workspace - make +2. **Install the Dev Containers extension** - To generate a Debian package: + - Open VS Code + - Go to Extensions (Ctrl+Shift+X) + - Search for "Dev Containers" and install it - .. code-block:: bash +3. **Configure Ubuntu version** (optional, defaults to 20.04) - sudo cpack -G DEB + Edit ``devcontainer_distro`` file in the project root and specify the desired Ubuntu version: - Exit the Docker container by typing ``exit`` in the terminal. + .. code-block:: bash -3. **Install libfranka on your host system**: + 22.04 # Ubuntu 22.04 (default) - Inside the libfranka build folder ``cd build`` on your host system, run: - .. code-block:: bash +4. **Open in container** - sudo dpkg -i libfranka*.deb + - Open the project in VS Code: ``code .`` + - Press ``Ctrl+Shift+P`` + - Select "Dev Containers: Reopen in Container" + - Wait for the container to build (first time takes ~15-20 minutes) +5. **Build libfranka** -Using Visual Studio Code -^^^^^^^^^^^^^^^^^^^^^^^^ + Open a terminal in VS Code and run: -You can also build **libfranka** inside Docker using **VS Code** with the **Dev Containers** extension. This provides an integrated development environment inside the container. + .. code-block:: bash -1. **Install Visual Studio Code**: + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF .. + cmake --build . -- -j$(nproc) - - Download and install **Visual Studio Code** from the official website: https://code.visualstudio.com/. - - Follow the installation instructions for your operating system. +6. **Create Debian package** (optional) + + .. code-block:: bash -2. **Open the Project in VS Code**: + cd build + cpack -G DEB - Inside the libfranka folder, open a new terminal and run: + The package will be in the ``build/`` directory. To install on your host system: .. code-block:: bash - code . + # On host system (outside container) + cd libfranka/build + sudo dpkg -i libfranka*.deb - This will open the project in VS Code. +Method B: Using Docker Command Line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -3. **Install the Dev Containers Extension**: +1. **Build the Docker image** - Install the "Dev Containers" extension in VS Code from the Extensions Marketplace. + .. code-block:: bash -4. **Open the Project in a Dev Container**: + # For Ubuntu 20.04 + docker build --build-arg UBUNTU_VERSION=20.04 -t libfranka-build:20.04 .ci/ - - Open the **Command Palette** (``Ctrl+Shift+P``). - - Select **Dev Containers: Reopen in Container**. - - VS Code will build the Docker image and start a container based on the provided ``.ci/Dockerfile``. + # For Ubuntu 22.04 + docker build --build-arg UBUNTU_VERSION=22.04 -t libfranka-build:22.04 .ci/ -5. **Build libfranka**: + # For Ubuntu 24.04 + docker build --build-arg UBUNTU_VERSION=24.04 -t libfranka-build:24.04 .ci/ - - Open a terminal in VS Code. - - Run the following commands: +2. **Run the container and build** .. code-block:: bash + docker run --rm -it -v $(pwd):/workspaces -w /workspaces libfranka-build:20.04 + + # Inside container: mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF .. - make - -6. **Install libfranka**: + cmake --build . -- -j$(nproc) + cpack -G DEB + exit - If you want to install **libfranka** inside the container, you can run: +3. **Install on host system** .. code-block:: bash - sudo make install + cd build + sudo dpkg -i libfranka*.deb - If you want to install **libfranka** on your host system, you can run: +Quick Docker Build Script +^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. code-block:: bash +For automated builds: - sudo cpack -G DEB +.. code-block:: bash - Then in a new terminal on your host system, navigate to the libfranka ``build`` folder and run: + # Build for specific Ubuntu version + docker build --build-arg UBUNTU_VERSION=22.04 -t libfranka-build .ci/ && \ + docker run --rm -v $(pwd):/workspaces -w /workspaces libfranka-build bash -c "\ + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -B build -S . && \ + cmake --build build -- -j\$(nproc) && \ + cd build && cpack -G DEB" - .. code-block:: bash +.. _building-from-source: - sudo dpkg -i libfranka*.deb +4. Building from Source (Advanced) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. _verifying-installation: -Verify the installation on your local system -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. warning:: + + Building from source is complex and requires building multiple dependencies. **We strongly recommend using the Debian package or Docker method** unless you need to modify the source code. -To verify its installation, you can run: +Prerequisites +^^^^^^^^^^^^^ + +**System packages:** .. code-block:: bash - ls /usr/lib/libfranka.so + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + cmake \ + git \ + wget \ + libeigen3-dev \ + libpoco-dev \ + libfmt-dev \ + pybind11-dev + +**For Ubuntu 20.04, install CMake >= 3.22:** -Expected output: +.. code-block:: bash -.. code-block:: text + wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | sudo gpg --dearmor -o /usr/share/keyrings/kitware-archive-keyring.gpg + echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main" | sudo tee /etc/apt/sources.list.d/kitware.list + sudo apt-get update + sudo apt-get install cmake + +Remove Existing Installations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + sudo apt-get remove "*libfranka*" + +Build Dependencies from Source +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - /usr/lib/libfranka.so +libfranka requires several dependencies built with static linking. Follow these steps **in order**: -Check the installed headers: +**1. Boost 1.77.0** .. code-block:: bash - ls /usr/include/franka/ + git clone --depth 1 --recurse-submodules --shallow-submodules \ + --branch boost-1.77.0 https://github.com/boostorg/boost.git + cd boost + ./bootstrap.sh --prefix=/usr/local + sudo ./b2 install -j$(nproc) + cd .. && rm -rf boost -Expected output: +**2. TinyXML2** -.. code-block:: text +.. code-block:: bash - active_control_base.h active_torque_control.h control_tools.h errors.h - gripper_state.h lowpass_filter.h robot.h robot_state.h - active_control.h async_control control_types.h exception.h - joint_velocity_limits.h model.h robot_model_base.h vacuum_gripper.h - active_motion_generator.h commands duration.h gripper.h - logging rate_limiting.h robot_model.h vacuum_gripper_state.h + git clone --depth 1 --branch 10.0.0 https://github.com/leethomason/tinyxml2.git + cd tinyxml2 && mkdir build && cd build + cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local + make -j$(nproc) && sudo make install + cd ../.. && rm -rf tinyxml2 -You can check the version of the installed library: +**3. console_bridge** .. code-block:: bash - dpkg -l | grep libfranka + git clone --depth 1 --branch 1.0.2 https://github.com/ros/console_bridge.git + cd console_bridge && mkdir build && cd build + cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local + make -j$(nproc) && sudo make install + cd ../.. && rm -rf console_bridge -Expected output: +**4. urdfdom_headers** -.. code-block:: text +.. code-block:: bash - ii libfranka 0.18.2-9-g722bf63 amd64 libfranka built using CMake + git clone --depth 1 --branch 1.0.5 https://github.com/ros/urdfdom_headers.git + cd urdfdom_headers && mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local + make -j$(nproc) && sudo make install + cd ../.. && rm -rf urdfdom_headers +**5. urdfdom (with patch)** -.. _building-from-source: +.. code-block:: bash -5. Building and Installation from Source -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + wget https://raw.githubusercontent.com/frankarobotics/libfranka/main/.ci/urdfdom.patch + git clone --depth 1 --branch 4.0.0 https://github.com/ros/urdfdom.git + cd urdfdom && git apply ../urdfdom.patch + mkdir build && cd build + cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local + make -j$(nproc) && sudo make install + cd ../.. && rm -rf urdfdom -Before building and installing from source, please uninstall existing installations of libfranka to avoid conflicts: +**6. Assimp** .. code-block:: bash - sudo apt-get remove "*libfranka*" + git clone --depth 1 --recurse-submodules --shallow-submodules \ + --branch v5.4.3 https://github.com/assimp/assimp.git + cd assimp && mkdir build && cd build + cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DBUILD_SHARED_LIBS=OFF -DASSIMP_BUILD_TESTS=OFF \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local + make -j$(nproc) && sudo make install + cd ../.. && rm -rf assimp -Clone the Repository -^^^^^^^^^^^^^^^^^^^^ +**7. Pinocchio (with patch)** -You can clone the repository and choose the version you need by selecting a specific tag: +.. code-block:: bash + + wget https://raw.githubusercontent.com/frankarobotics/libfranka/main/.ci/pinocchio.patch + git clone --depth 1 --recurse-submodules --shallow-submodules \ + --branch v3.4.0 https://github.com/stack-of-tasks/pinocchio.git + cd pinocchio && git apply ../pinocchio.patch + mkdir build && cd build + cmake .. -DBoost_USE_STATIC_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DBUILD_SHARED_LIBS=OFF -DBUILD_PYTHON_INTERFACE=OFF \ + -DBUILD_DOCUMENTATION=OFF -DBUILD_TESTING=OFF \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local + make -j$(nproc) && sudo make install + cd ../.. && rm -rf pinocchio + +Build libfranka +^^^^^^^^^^^^^^^ .. code-block:: bash git clone --recurse-submodules https://github.com/frankarobotics/libfranka.git cd libfranka + git checkout 0.18.3 + git submodule update --init --recursive -List available tags + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTS=OFF \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + .. + cmake --build . -- -j$(nproc) + sudo cmake --install . + +Create Debian Package (Optional) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash - git tag -l + cd build + cpack -G DEB + sudo dpkg -i libfranka*.deb -Checkout a specific tag (e.g., 0.15.0) +.. _verifying-installation: -.. code-block:: bash +5. Verifying Installation +~~~~~~~~~~~~~~~~~~~~~~~~~~ - git checkout 0.15.0 +After installation (any method), verify that libfranka is properly installed: -Update submodules +**Check library file:** .. code-block:: bash - git submodule update + ls -l /usr/lib/libfranka.so -Create a build directory and navigate to it +Expected output: -.. code-block:: bash +.. code-block:: text - mkdir build - cd build + /usr/lib/libfranka.so -> libfranka.so.0.18.3 -Configure the project and build +**Check header files:** .. code-block:: bash - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/opt/openrobots/lib/cmake -DBUILD_TESTS=OFF .. - make + ls /usr/include/franka/ -.. _installing-debian-package: +Expected output: -Installing libfranka as a Debian Package (Optional but recommended) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: text -Building a Debian package is optional but recommended for easier installation and management. In the build folder, execute: + active_control_base.h active_torque_control.h control_tools.h + gripper_state.h lowpass_filter.h robot.h + ... + +**Check installed package version:** .. code-block:: bash - cpack -G DEB + dpkg -l | grep libfranka -This command creates a Debian package named libfranka--.deb. You can then install it with: +Expected output: -.. code-block:: bash +.. code-block:: text - sudo dpkg -i libfranka*.deb + ii libfranka 0.18.3 amd64 libfranka - Franka Robotics C++ library + +**Test with pkg-config:** -Installing via a Debian package simplifies the process compared to building from source every time. Additionally the package integrates better with -system tools and package managers, which can help manage updates and dependencies more effectively. +.. code-block:: bash + + pkg-config --modversion libfranka .. _usage: 6. Usage ~~~~~~~~ -After installation, check the `Minimum System and Network Requirements `_ for network settings, -the `Setting up the Real-Time Kernel `_ for system setup, -and the `Getting Started Manual `_ for initial steps. Once configured, -you can control the robot using the example applications provided in the examples folder (`Usage Examples `_). +After installation, configure your system for real-time control and run example programs: + +System Setup +^^^^^^^^^^^^ -To run a sample program, navigate to the build folder and execute the following command: +1. **Network configuration**: Follow `Minimum System and Network Requirements `_ +2. **Real-time kernel**: See `Setting up the Real-Time Kernel `_ +3. **Getting started**: Read the `Getting Started Manual `_ + +Running Examples +^^^^^^^^^^^^^^^^ + +If you built from source, example programs are in the ``build/examples/`` directory: .. code-block:: bash - ./examples/communication_test + cd build/examples + ./communication_test + +Replace ```` with your robot's IP address (e.g., ``192.168.1.1``). + +For more examples, see the `Usage Examples documentation `_. .. _pylibfranka: -7. Pylibfranka -~~~~~~~~~~~~~~ +7. Pylibfranka (Python Bindings) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pylibfranka is a Python binding for libfranka, allowing you to control Franka robots using Python. It is included in the libfranka repository and -can be built alongside libfranka. For more details, see ``pylibfranka`` and its `README `_. -The `generated API documentation `_ offers an overview of its capabilities. +**Pylibfranka** provides Python bindings for libfranka, allowing robot control with Python. + +Installation +^^^^^^^^^^^^ + +Pylibfranka is included in the libfranka Debian packages. For manual installation: + +.. code-block:: bash + + # Build with Python bindings enabled + cd libfranka/build + cmake -DGENERATE_PYLIBFRANKA=ON .. + cmake --build . -- -j$(nproc) + sudo cmake --install . + +Documentation +^^^^^^^^^^^^^ + +- `Pylibfranka README `_ +- `API Documentation `_ .. _development-information: 8. Development Information ~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you actively contribute to this repository, you should install and set up pre-commit hooks: +Contributing to libfranka +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you're contributing to libfranka development: + +**Install pre-commit hooks:** .. code-block:: bash pip install pre-commit pre-commit install -This will install pre-commit and set up the git hooks to automatically run checks before each commit. -The hooks will help maintain code quality by running various checks like code formatting, linting, and other validations. +This automatically runs code formatting and linting checks before each commit. -To manually run the pre-commit checks on all files: +**Run checks manually:** .. code-block:: bash pre-commit run --all-files -This will build the C++ extension and install the Python package. +Build Options +^^^^^^^^^^^^^ + +Customize your build with CMake options: + +.. list-table:: + :header-rows: 1 + :widths: 35 50 15 + + * - Option + - Description + - Default + * - ``CMAKE_BUILD_TYPE`` + - Build type (Release/Debug) + - Release + * - ``BUILD_TESTS`` + - Build unit tests + - ON + * - ``BUILD_EXAMPLES`` + - Build example programs + - ON + * - ``GENERATE_PYLIBFRANKA`` + - Build Python bindings + - OFF + * - ``CMAKE_INSTALL_PREFIX`` + - Installation directory + - /usr/local + +Example: + +.. code-block:: bash + + cmake -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_TESTS=ON \ + -DGENERATE_PYLIBFRANKA=ON \ + .. + +Troubleshooting +~~~~~~~~~~~~~~~ + +**Cannot find libfranka.so** + +Update library cache: + +.. code-block:: bash + + sudo ldconfig + +**CMake cannot find dependencies** + +Set the prefix path: + +.. code-block:: bash + + cmake -DCMAKE_PREFIX_PATH=/usr/local .. + +Or set library path: + +.. code-block:: bash + + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + +**Network connection issues** + +See the `Troubleshooting Network `_ guide. + +Uninstalling +~~~~~~~~~~~~ + +**Debian package:** + +.. code-block:: bash + + sudo apt-get remove libfranka + +**Built from source:** + +.. code-block:: bash + + cd libfranka/build + sudo cmake --build . --target uninstall + +Or manually: + +.. code-block:: bash + + sudo rm -rf /usr/local/include/franka + sudo rm -f /usr/local/lib/libfranka.* + sudo rm -f /usr/local/lib/cmake/Franka License ------- From 84683d1d5231dfca50fd5646236e83a354793a5c Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Thu, 18 Dec 2025 11:04:28 +0100 Subject: [PATCH 15/38] refactor: Default devcontainer_distro is set to ubuntu-22.04 --- .devcontainer/devcontainer.json | 2 +- .devcontainer/docker-compose.yml | 4 ++-- devcontainer_distro | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7535b16c..f4a4b36d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "service": "libfranka_project", "workspaceFolder": "/workspaces", "remoteUser": "user", - "initializeCommand": "bash -c 'ENV_FILE=\".devcontainer/.env\"; UBUNTU_VERSION=$(cat devcontainer_distro 2>/dev/null || echo 24.04); if [ ! -f \"$ENV_FILE\" ]; then echo -e \"UBUNTU_VERSION=${UBUNTU_VERSION}\\nUSER_UID=$(id -u)\\nUSER_GID=$(id -g)\" > \"$ENV_FILE\"; else sed -i.bak \"s/^UBUNTU_VERSION=.*/UBUNTU_VERSION=${UBUNTU_VERSION}/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_UID=.*/USER_UID=$(id -u)/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_GID=.*/USER_GID=$(id -g)/\" \"$ENV_FILE\"; rm -f \"$ENV_FILE.bak\"; fi'", + "initializeCommand": "bash -c 'ENV_FILE=\".devcontainer/.env\"; UBUNTU_VERSION=$(cat devcontainer_distro 2>/dev/null || echo 22.04); if [ ! -f \"$ENV_FILE\" ]; then echo -e \"UBUNTU_VERSION=${UBUNTU_VERSION}\\nUSER_UID=$(id -u)\\nUSER_GID=$(id -g)\" > \"$ENV_FILE\"; else sed -i.bak \"s/^UBUNTU_VERSION=.*/UBUNTU_VERSION=${UBUNTU_VERSION}/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_UID=.*/USER_UID=$(id -u)/\" \"$ENV_FILE\"; sed -i.bak \"s/^USER_GID=.*/USER_GID=$(id -g)/\" \"$ENV_FILE\"; rm -f \"$ENV_FILE.bak\"; fi'", "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 8bfa12a9..435be07e 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -6,8 +6,8 @@ services: args: USER_UID: ${USER_UID:-1000} USER_GID: ${USER_GID:-1000} - UBUNTU_VERSION: ${UBUNTU_VERSION:-20.04} - container_name: libfranka-${UBUNTU_VERSION:-20.04} + UBUNTU_VERSION: ${UBUNTU_VERSION:-22.04} + container_name: libfranka-${UBUNTU_VERSION:-22.04} network_mode: "host" shm_size: 512m privileged: true diff --git a/devcontainer_distro b/devcontainer_distro index e191604a..dcdf6284 100644 --- a/devcontainer_distro +++ b/devcontainer_distro @@ -1 +1 @@ -24.04 +22.04 From f24b955917632e487ff690e1abf35033747807ab Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 18 Dec 2025 15:28:01 +0100 Subject: [PATCH 16/38] docs: update Installation from Debian Package --- README.rst | 89 ++++++++++++++++++++++-------------------------------- 1 file changed, 36 insertions(+), 53 deletions(-) diff --git a/README.rst b/README.rst index fbba89bb..50488464 100644 --- a/README.rst +++ b/README.rst @@ -42,16 +42,17 @@ Before using **libfranka**, ensure your system meets the following requirements: .. _installation-debian-package: 2. Installation from Debian Package (Recommended) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The easiest way to install **libfranka** is using pre-built Debian packages. This method is recommended for most users. +The easiest way to install **libfranka** is by using the pre-built Debian packages +published on GitHub. Supported Platforms ^^^^^^^^^^^^^^^^^^^ .. list-table:: :header-rows: 1 - :widths: 25 25 25 25 + :widths: 28 18 18 26 * - Ubuntu Version - Codename @@ -60,77 +61,59 @@ Supported Platforms * - 20.04 LTS - focal - amd64 - - ✅ Supported + - Supported * - 22.04 LTS - jammy - amd64 - - ✅ Supported + - Supported * - 24.04 LTS - noble - amd64 - - ✅ Supported - -Installation Steps -^^^^^^^^^^^^^^^^^^ - -**Step 1: Check your Ubuntu version** - -.. code-block:: bash - - lsb_release -a - -**Step 2: Download and install the appropriate package** - -**For Ubuntu 20.04 (Focal):** - -.. code-block:: bash + - Supported - wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_focal_amd64.deb - sudo dpkg -i libfranka_0.18.3_focal_amd64.deb - -**For Ubuntu 22.04 (Jammy):** - -.. code-block:: bash - - wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_jammy_amd64.deb - sudo dpkg -i libfranka_0.18.3_jammy_amd64.deb +Quick Install +^^^^^^^^^^^^^ -**For Ubuntu 24.04 (Noble):** +Use the follwing scripts to set the desired **libfranka** version. The script automatically detects your +Ubuntu codename, downloads the matching Debian package, verifies its checksum, +and installs it. .. code-block:: bash - wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_noble_amd64.deb - sudo dpkg -i libfranka_0.18.3_noble_amd64.deb + VERSION=0.19.0 + CODENAME=$(lsb_release -cs) -**Step 3 (Optional): Verify checksum** + # Download package + wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb -.. code-block:: bash + # Download checksum + wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb.sha256 - # Download checksum file - wget https://github.com/frankarobotics/libfranka/releases/download/0.18.3/libfranka_0.18.3_focal_amd64.deb.sha256 + # Verify package integrity + sha256sum -c libfranka_${VERSION}_${CODENAME}_amd64.deb.sha256 - # Verify integrity - sha256sum -c libfranka_0.18.3_focal_amd64.deb.sha256 + # Install package + sudo dpkg -i libfranka_${VERSION}_${CODENAME}_amd64.deb -Generic Installation Pattern -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Example +^^^^^^^ -For other versions, use this pattern: +The following example installs **libfranka 0.19.0** on **Ubuntu 22.04 (Jammy)**: .. code-block:: bash - # Set your version and Ubuntu codename - VERSION=0.18.3 - CODENAME=focal # or jammy, noble + VERSION=0.19.0 - wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb - sudo dpkg -i libfranka_${VERSION}_${CODENAME}_amd64.deb + wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_jammy_amd64.deb + wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_jammy_amd64.deb.sha256 + sha256sum -c libfranka_${VERSION}_jammy_amd64.deb.sha256 + sudo dpkg -i libfranka_${VERSION}_jammy_amd64.deb .. tip:: - This is the **recommended installation method** if you don't need to modify the source code. + All released versions, packages, and checksums are available on the + `GitHub Releases page `_. -Find all available releases on the `GitHub Releases page `_. .. _building-with-docker: @@ -156,7 +139,7 @@ Clone the Repository git clone --recurse-submodules https://github.com/frankarobotics/libfranka.git cd libfranka - git checkout 0.18.3 # or your desired version + git checkout 0.19.0 # or your desired version Method A: Using Visual Studio Code (Recommended) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -402,7 +385,7 @@ Build libfranka git clone --recurse-submodules https://github.com/frankarobotics/libfranka.git cd libfranka - git checkout 0.18.3 + git checkout 0.19.0 git submodule update --init --recursive mkdir build && cd build @@ -439,7 +422,7 @@ Expected output: .. code-block:: text - /usr/lib/libfranka.so -> libfranka.so.0.18.3 + /usr/lib/libfranka.so -> libfranka.so.0.19.0 **Check header files:** @@ -465,7 +448,7 @@ Expected output: .. code-block:: text - ii libfranka 0.18.3 amd64 libfranka - Franka Robotics C++ library + ii libfranka 0.19.0 amd64 libfranka - Franka Robotics C++ library **Test with pkg-config:** From 7391d916945c16894ef07270169272149873e355 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 18 Dec 2025 18:13:49 +0100 Subject: [PATCH 17/38] docs: update build from source --- README.rst | 120 +++++++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/README.rst b/README.rst index 50488464..a24c2a87 100644 --- a/README.rst +++ b/README.rst @@ -168,7 +168,7 @@ Method A: Using Visual Studio Code (Recommended) - Open the project in VS Code: ``code .`` - Press ``Ctrl+Shift+P`` - Select "Dev Containers: Reopen in Container" - - Wait for the container to build (first time takes ~15-20 minutes) + - Wait for the container to build 5. **Build libfranka** @@ -231,29 +231,11 @@ Method B: Using Docker Command Line cd build sudo dpkg -i libfranka*.deb -Quick Docker Build Script -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -For automated builds: - -.. code-block:: bash - - # Build for specific Ubuntu version - docker build --build-arg UBUNTU_VERSION=22.04 -t libfranka-build .ci/ && \ - docker run --rm -v $(pwd):/workspaces -w /workspaces libfranka-build bash -c "\ - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -B build -S . && \ - cmake --build build -- -j\$(nproc) && \ - cd build && cpack -G DEB" - .. _building-from-source: 4. Building from Source (Advanced) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. warning:: - - Building from source is complex and requires building multiple dependencies. **We strongly recommend using the Debian package or Docker method** unless you need to modify the source code. - Prerequisites ^^^^^^^^^^^^^ @@ -272,26 +254,26 @@ Prerequisites libfmt-dev \ pybind11-dev -**For Ubuntu 20.04, install CMake >= 3.22:** +**Ubuntu 20.04:** ensure CMake >= 3.22: .. code-block:: bash - wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | sudo gpg --dearmor -o /usr/share/keyrings/kitware-archive-keyring.gpg + wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor -o /usr/share/keyrings/kitware-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main" | sudo tee /etc/apt/sources.list.d/kitware.list sudo apt-get update - sudo apt-get install cmake + sudo apt-get install -y cmake Remove Existing Installations -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash - sudo apt-get remove "*libfranka*" + sudo apt-get remove -y "*libfranka*" Build Dependencies from Source -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -libfranka requires several dependencies built with static linking. Follow these steps **in order**: +Follow these steps **in order**. All dependencies are built with static linking. **1. Boost 1.77.0** @@ -309,10 +291,18 @@ libfranka requires several dependencies built with static linking. Follow these .. code-block:: bash git clone --depth 1 --branch 10.0.0 https://github.com/leethomason/tinyxml2.git - cd tinyxml2 && mkdir build && cd build - cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local - make -j$(nproc) && sudo make install + cd tinyxml2 + mkdir build && cd build + + cmake .. \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local + + make -j$(nproc) + sudo make install + cd ../.. && rm -rf tinyxml2 **3. console_bridge** @@ -320,34 +310,64 @@ libfranka requires several dependencies built with static linking. Follow these .. code-block:: bash git clone --depth 1 --branch 1.0.2 https://github.com/ros/console_bridge.git - cd console_bridge && mkdir build && cd build - cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local - make -j$(nproc) && sudo make install - cd ../.. && rm -rf console_bridge + cd console_bridge + mkdir build && cd build + + cmake .. \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local + + make -j$(nproc) + sudo make install + + cd ../.. + rm -rf console_bridge + **4. urdfdom_headers** .. code-block:: bash git clone --depth 1 --branch 1.0.5 https://github.com/ros/urdfdom_headers.git - cd urdfdom_headers && mkdir build && cd build - cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local - make -j$(nproc) && sudo make install - cd ../.. && rm -rf urdfdom_headers + cd urdfdom_headers + mkdir build && cd build + + cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local + + make -j$(nproc) + sudo make install + + cd ../.. + rm -rf urdfdom_headers + **5. urdfdom (with patch)** .. code-block:: bash wget https://raw.githubusercontent.com/frankarobotics/libfranka/main/.ci/urdfdom.patch + git clone --depth 1 --branch 4.0.0 https://github.com/ros/urdfdom.git - cd urdfdom && git apply ../urdfdom.patch + cd urdfdom + + git apply ../urdfdom.patch + mkdir build && cd build - cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local - make -j$(nproc) && sudo make install - cd ../.. && rm -rf urdfdom + cmake .. \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local + + make -j$(nproc) + sudo make install + cd ../.. + rm -rf urdfdom + **6. Assimp** @@ -391,17 +411,15 @@ Build libfranka mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTS=OFF \ - -DCMAKE_INSTALL_PREFIX=/usr/local \ - .. + -DCMAKE_INSTALL_PREFIX=/usr/local .. cmake --build . -- -j$(nproc) - sudo cmake --install . Create Debian Package (Optional) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Inside the build folder, run: .. code-block:: bash - cd build cpack -G DEB sudo dpkg -i libfranka*.deb @@ -450,12 +468,6 @@ Expected output: ii libfranka 0.19.0 amd64 libfranka - Franka Robotics C++ library -**Test with pkg-config:** - -.. code-block:: bash - - pkg-config --modversion libfranka - .. _usage: 6. Usage From 12c8b38c4ff5c49ef63b92219d7059088af06e35 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 18 Dec 2025 18:25:14 +0100 Subject: [PATCH 18/38] bump: libfranka 0.19.0 release --- CHANGELOG.md | 4 ++-- CMakeLists.txt | 2 +- docs/installation.rst | 2 +- package.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6dde9c..4be0882a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # CHANGELOG -All notable changes to libfranka and pylibfranka will be documented in this file. +All notable changes to libfranka in this file. -## UNRELEASED +## [0.19.0] ### libfranka - C++ #### Changed - To support franka_ros2, we added an option for the async position control to base the `getFeedback` function on a robot state received via `franka_hardware` instead of querying the robot directly. diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c19bd79..5e15f8a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(libfranka_VERSION 0.18.2) +set(libfranka_VERSION 0.19.0) project(libfranka VERSION ${libfranka_VERSION} diff --git a/docs/installation.rst b/docs/installation.rst index dd88567a..6d0d85f2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -33,7 +33,7 @@ For the supported ubuntu versions, use the following pattern: .. code-block:: bash # Replace with your desired version and Ubuntu codename - VERSION=0.18.2 + VERSION=0.19.0 CODENAME=focal # or jammy, noble wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb diff --git a/package.xml b/package.xml index 3d96b007..ea1e71ff 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> libfranka - 0.18.2 + 0.19.0 libfranka is a C++ library for Franka Robotics research robots Franka Robotics GmbH Apache 2.0 From f7c29c9154fd21f8e47dcb2eae3c14e4f6935025 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 18 Dec 2025 18:32:26 +0100 Subject: [PATCH 19/38] ci: fix pylibfranka docu build --- .github/workflows/pylibfranka-docs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pylibfranka-docs.yml b/.github/workflows/pylibfranka-docs.yml index bb07c9ba..b90636ca 100644 --- a/.github/workflows/pylibfranka-docs.yml +++ b/.github/workflows/pylibfranka-docs.yml @@ -20,8 +20,10 @@ jobs: uses: docker/build-push-action@v4 with: context: .ci/ - file: .ci/Dockerfile.focal + file: .ci/Dockerfile tags: pylibfranka:latest + build-args: | + UBUNTU_VERSION=20.04 push: false load: true From 2b954ec7e5384d918c1fcf2cb6fd233eceeb8b50 Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 18 Dec 2025 18:32:26 +0100 Subject: [PATCH 20/38] ci: fix pylibfranka docu build --- README.rst | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/README.rst b/README.rst index a24c2a87..0eab8e30 100644 --- a/README.rst +++ b/README.rst @@ -587,56 +587,10 @@ Example: Troubleshooting ~~~~~~~~~~~~~~~ -**Cannot find libfranka.so** - -Update library cache: - -.. code-block:: bash - - sudo ldconfig - -**CMake cannot find dependencies** - -Set the prefix path: - -.. code-block:: bash - - cmake -DCMAKE_PREFIX_PATH=/usr/local .. - -Or set library path: - -.. code-block:: bash - - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - **Network connection issues** See the `Troubleshooting Network `_ guide. -Uninstalling -~~~~~~~~~~~~ - -**Debian package:** - -.. code-block:: bash - - sudo apt-get remove libfranka - -**Built from source:** - -.. code-block:: bash - - cd libfranka/build - sudo cmake --build . --target uninstall - -Or manually: - -.. code-block:: bash - - sudo rm -rf /usr/local/include/franka - sudo rm -f /usr/local/lib/libfranka.* - sudo rm -f /usr/local/lib/cmake/Franka - License ------- From 4780b643ad99456aca8e32d2b3ef36bbb01836ab Mon Sep 17 00:00:00 2001 From: mohy_ka Date: Thu, 18 Dec 2025 18:54:42 +0100 Subject: [PATCH 21/38] ci: upload changelog only once in the workflow --- .github/workflows/libfranka-build.yml | 2 +- README.rst | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/libfranka-build.yml b/.github/workflows/libfranka-build.yml index 40eefd49..612da144 100644 --- a/.github/workflows/libfranka-build.yml +++ b/.github/workflows/libfranka-build.yml @@ -76,7 +76,7 @@ jobs: retention-days: 30 - name: Upload to GitHub Release - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/') && matrix.ubuntu_version == '20.04' uses: softprops/action-gh-release@v2 with: files: | diff --git a/README.rst b/README.rst index 0eab8e30..fede6fe0 100644 --- a/README.rst +++ b/README.rst @@ -74,26 +74,23 @@ Supported Platforms Quick Install ^^^^^^^^^^^^^ -Use the follwing scripts to set the desired **libfranka** version. The script automatically detects your -Ubuntu codename, downloads the matching Debian package, verifies its checksum, -and installs it. +substitute `` with the desired version number (e.g., `0.19.0`). And +substitute `` with your Ubuntu codename (e.g., `focal`, `jammy`, `noble`). you can find it by running `lsb_release -cs`. -.. code-block:: bash - VERSION=0.19.0 - CODENAME=$(lsb_release -cs) +.. code-block:: bash # Download package - wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb + wget https://github.com/frankarobotics/libfranka/releases/download//libfranka___amd64.deb # Download checksum - wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_${CODENAME}_amd64.deb.sha256 + wget https://github.com/frankarobotics/libfranka/releases/download//libfranka___amd64.deb.sha256 # Verify package integrity - sha256sum -c libfranka_${VERSION}_${CODENAME}_amd64.deb.sha256 + sha256sum -c libfranka___amd64.deb.sha256 # Install package - sudo dpkg -i libfranka_${VERSION}_${CODENAME}_amd64.deb + sudo dpkg -i libfranka___amd64.deb Example ^^^^^^^ @@ -102,12 +99,11 @@ The following example installs **libfranka 0.19.0** on **Ubuntu 22.04 (Jammy)**: .. code-block:: bash - VERSION=0.19.0 - wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_jammy_amd64.deb - wget https://github.com/frankarobotics/libfranka/releases/download/${VERSION}/libfranka_${VERSION}_jammy_amd64.deb.sha256 - sha256sum -c libfranka_${VERSION}_jammy_amd64.deb.sha256 - sudo dpkg -i libfranka_${VERSION}_jammy_amd64.deb + wget https://github.com/frankarobotics/libfranka/releases/download/0.19.0/libfranka_0.19.0_jammy_amd64.deb + wget https://github.com/frankarobotics/libfranka/releases/download/0.19.0/libfranka_0.19.0_jammy_amd64.deb.sha256 + sha256sum -c libfranka_0.19.0_jammy_amd64.deb.sha256 + sudo dpkg -i libfranka_0.19.0_jammy_amd64.deb .. tip:: From 76d5cad75cc5bad6dbf2fd60cd544f95807a51c3 Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Wed, 14 Jan 2026 09:59:17 +0000 Subject: [PATCH 22/38] fix: hotfix to avoid torque discontinuities with float based robot-state --- CHANGELOG.md | 5 +++++ include/franka/rate_limiting.h | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4be0882a..b2559446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to libfranka in this file. +## [Unreleased] +### libfranka - C++ +#### Changed +- Hotfix to avoid torques discontinuity false positives due to robot state float precision change. + ## [0.19.0] ### libfranka - C++ #### Changed diff --git a/include/franka/rate_limiting.h b/include/franka/rate_limiting.h index 4185462a..b91a7ce1 100644 --- a/include/franka/rate_limiting.h +++ b/include/franka/rate_limiting.h @@ -37,12 +37,15 @@ constexpr double kTolNumberPacketsLost = 0.0; * Factor for the definition of rotational limits using the Cartesian Pose interface */ constexpr double kFactorCartesianRotationPoseInterface = 0.99; + +constexpr double kTorqueLimitEps = 3e-3; + /** * Maximum torque rate */ constexpr std::array kMaxTorqueRate{ - {1000 - kLimitEps, 1000 - kLimitEps, 1000 - kLimitEps, 1000 - kLimitEps, 1000 - kLimitEps, - 1000 - kLimitEps, 1000 - kLimitEps}}; + {1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, + 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps}}; /** * Maximum joint jerk */ From 0b98e0917bc2c64425a8acbd575d1522f58ebc3e Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Wed, 14 Jan 2026 16:27:32 +0100 Subject: [PATCH 23/38] build: Fixed a jenkins build problem by mixing up environment variables --- Jenkinsfile | 310 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 178 insertions(+), 132 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a639be89..555143f0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,3 +1,5 @@ +DISTRO_VERSIONS = ['20.04': 'focal', '22.04': 'jammy', '24.04': 'noble'] + pipeline { libraries { lib('fe-pipeline-steps@1.0.0') @@ -43,11 +45,11 @@ pipeline { stage('Init Distro') { steps { script { - def map = ['20.04': 'focal', '22.04': 'jammy', '24.04': 'noble'] - env.DISTRO = map[env.UBUNTU_VERSION] - if (!env.DISTRO) { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + if (!distro) { error "Unknown UBUNTU_VERSION=${env.UBUNTU_VERSION}" } + echo "Distro for ${env.UBUNTU_VERSION}: ${distro}" } } } @@ -62,15 +64,20 @@ pipeline { } stage('Clean Workspace') { steps { - sh ''' - # Clean build dirs for this distro axis - rm -rf build-*${DISTRO} - rm -rf install-*${DISTRO} - # Remove corrupted googletest fetch content if present - rm -rf build-release.${DISTRO}/_deps/gtest-src build-release.${DISTRO}/_deps/gtest-build || true - rm -rf build-debug.${DISTRO}/_deps/gtest-src build-debug.${DISTRO}/_deps/gtest-build || true - rm -rf build-coverage.${DISTRO}/_deps/gtest-src build-coverage.${DISTRO}/_deps/gtest-build || true - ''' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + withEnv(["DISTRO=${distro}"]) { + sh ''' + # Clean build dirs for this distro axis + rm -rf build-*${DISTRO} + rm -rf install-*${DISTRO} + # Remove corrupted googletest fetch content if present + rm -rf build-release.${DISTRO}/_deps/gtest-src build-release.${DISTRO}/_deps/gtest-build || true + rm -rf build-debug.${DISTRO}/_deps/gtest-src build-debug.${DISTRO}/_deps/gtest-build || true + rm -rf build-coverage.${DISTRO}/_deps/gtest-src build-coverage.${DISTRO}/_deps/gtest-build || true + ''' + } + } } } } @@ -79,45 +86,61 @@ pipeline { stages { stage('Build debug') { steps { - dir("build-debug.${env.DISTRO}") { - sh ''' - rm -rf CMakeCache.txt CMakeFiles _deps || true - cmake -DCMAKE_BUILD_TYPE=Debug -DSTRICT=ON -DBUILD_COVERAGE=OFF \ - -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ - -DGENERATE_PYLIBFRANKA=ON .. - make -j$(nproc) - cmake --install . --prefix ../install-debug.${DISTRO} - ''' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-debug.${distro}") { + withEnv(["DISTRO=${distro}"]) { + sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true + cmake -DCMAKE_BUILD_TYPE=Debug -DSTRICT=ON -DBUILD_COVERAGE=OFF \ + -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ + -DGENERATE_PYLIBFRANKA=ON .. + make -j$(nproc) + cmake --install . --prefix ../install-debug.${DISTRO} + ''' + } + } } } } stage('Build release') { steps { - dir("build-release.${env.DISTRO}") { - sh ''' - rm -rf CMakeCache.txt CMakeFiles _deps || true - cmake -DCMAKE_BUILD_TYPE=Release -DSTRICT=ON -DBUILD_COVERAGE=OFF \ - -DBUILD_DOCUMENTATION=ON -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ - -DGENERATE_PYLIBFRANKA=ON .. - make -j$(nproc) - cmake --install . --prefix ../install-release.${DISTRO} - ''' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-release.${distro}") { + withEnv(["DISTRO=${distro}"]) { + sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true + cmake -DCMAKE_BUILD_TYPE=Release -DSTRICT=ON -DBUILD_COVERAGE=OFF \ + -DBUILD_DOCUMENTATION=ON -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ + -DGENERATE_PYLIBFRANKA=ON .. + make -j$(nproc) + cmake --install . --prefix ../install-release.${DISTRO} + ''' + } + } } } } stage('Build examples (debug)') { steps { - dir("build-debug-examples.${env.DISTRO}") { - sh "cmake -DCMAKE_PREFIX_PATH=../install-debug.${env.DISTRO} ../examples" - sh 'make -j$(nproc)' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-debug-examples.${distro}") { + sh "cmake -DCMAKE_PREFIX_PATH=../install-debug.${distro} ../examples" + sh 'make -j$(nproc)' + } } } } stage('Build examples (release)') { steps { - dir("build-release-examples.${env.DISTRO}") { - sh "cmake -DCMAKE_PREFIX_PATH=../install-release.${env.DISTRO} ../examples" - sh 'make -j$(nproc)' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-release-examples.${distro}") { + sh "cmake -DCMAKE_PREFIX_PATH=../install-release.${distro} ../examples" + sh 'make -j$(nproc)' + } } } } @@ -128,13 +151,16 @@ pipeline { } } steps { - dir("build-coverage.${env.DISTRO}") { - sh ''' - rm -rf CMakeCache.txt CMakeFiles _deps || true - cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_COVERAGE=ON \ - -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. - make -j$(nproc) - ''' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-coverage.${distro}") { + sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true + cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_COVERAGE=ON \ + -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. + make -j$(nproc) + ''' + } } } } @@ -142,24 +168,30 @@ pipeline { } stage('Lint') { steps { - dir("build-lint.${env.DISTRO}") { - catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh ''' - cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON .. - make check-tidy -j$(nproc) - ''' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-lint.${distro}") { + catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { + sh ''' + cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON .. + make check-tidy -j$(nproc) + ''' + } } } } } stage('Format') { steps { - dir("build-format.${env.DISTRO}") { - catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh ''' - cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON .. - make check-format -j$(nproc) - ''' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-format.${distro}") { + catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { + sh ''' + cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON .. + make check-format -j$(nproc) + ''' + } } } } @@ -171,18 +203,21 @@ pipeline { } } steps { - dir("build-coverage.${env.DISTRO}") { - catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh ''' - cmake -DBUILD_COVERAGE=ON -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. - make coverage -j$(nproc) - ''' - publishHTML([allowMissing: false, - alwaysLinkToLastBuild: false, - keepAll: true, - reportDir: 'coverage', - reportFiles: 'index.html', - reportName: "Code Coverage (${env.DISTRO})"]) + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-coverage.${distro}") { + catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { + sh ''' + cmake -DBUILD_COVERAGE=ON -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. + make coverage -j$(nproc) + ''' + publishHTML([allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: 'coverage', + reportFiles: 'index.html', + reportName: "Code Coverage (${distro})"]) + } } } } @@ -198,18 +233,21 @@ pipeline { echo "[Debug Tests] ASLR status: $(cat /proc/sys/kernel/randomize_va_space)" ''' - dir("build-debug.${env.DISTRO}") { - sh ''' - echo "[Debug Tests] Running tests..." - ctest -V - ''' - } + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-debug.${distro}") { + sh ''' + echo "[Debug Tests] Running tests..." + ctest -V + ''' + } - dir("build-release.${env.DISTRO}") { - sh ''' - echo "[Release Tests] Running tests..." - ctest -V - ''' + dir("build-release.${distro}") { + sh ''' + echo "[Release Tests] Running tests..." + ctest -V + ''' + } } sh ''' @@ -224,8 +262,8 @@ pipeline { post { always { catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - junit "build-release.${env.DISTRO}/test_results/*.xml" - junit "build-debug.${env.DISTRO}/test_results/*.xml" + junit "build-release.${DISTRO_VERSIONS[env.UBUNTU_VERSION]}/test_results/*.xml" + junit "build-debug.${DISTRO_VERSIONS[env.UBUNTU_VERSION]}/test_results/*.xml" } } } @@ -237,71 +275,79 @@ pipeline { } stage('Publish') { steps { - dir("build-release.${env.DISTRO}") { - catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh 'cpack' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + dir("build-release.${distro}") { + catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { + sh 'cpack' - // Publish Debian packages with Git commit hash in the name - fePublishDebian('*.deb', 'fci', "deb.distribution=${env.DISTRO};deb.component=main;deb.architecture=amd64") + // Publish Debian packages with Git commit hash in the name + fePublishDebian('*.deb', 'fci', "deb.distribution=${distro};deb.component=main;deb.architecture=amd64") - dir('doc') { - sh 'mv docs/*/html/ html/' - sh 'tar cfz ../libfranka-docs.tar.gz html' + dir('doc') { + sh 'mv docs/*/html/ html/' + sh 'tar cfz ../libfranka-docs.tar.gz html' + } + sh "rename -e 's/(.tar.gz)\$/-${distro}\$1/' *.tar.gz" + publishHTML([allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: 'doc/html', + reportFiles: 'index.html', + reportName: "API Documentation (${distro})"]) } - sh "rename -e 's/(.tar.gz)\$/-${env.DISTRO}\$1/' *.tar.gz" - publishHTML([allowMissing: false, - alwaysLinkToLastBuild: false, - keepAll: true, - reportDir: 'doc/html', - reportFiles: 'index.html', - reportName: "API Documentation (${env.DISTRO})"]) } } // Build and publish pylibfranka documentation catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh ''' - # Install pylibfranka from root (builds against libfranka in build-release.${DISTRO}) - export LD_LIBRARY_PATH="${WORKSPACE}/build-release.${DISTRO}:${LD_LIBRARY_PATH:-}" - if [ -n "$VIRTUAL_ENV" ]; then - python3 -m pip install . - else - python3 -m pip install . --user - fi - ''' + script { + def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] + withEnv(["DISTRO=${distro}"]) { + sh ''' + # Install pylibfranka from root (builds against libfranka in build-release.${DISTRO}) + export LD_LIBRARY_PATH="${WORKSPACE}/build-release.${DISTRO}:${LD_LIBRARY_PATH:-}" + if [ -n "$VIRTUAL_ENV" ]; then + python3 -m pip install . + else + python3 -m pip install . --user + fi + ''' - dir('pylibfranka/docs') { - sh ''' - # Install Sphinx and dependencies (respect virtualenv if present) - if [ -n "$VIRTUAL_ENV" ]; then - python3 -m pip install -r requirements.txt - else - # Add sphinx to PATH for --user installs - export PATH="$HOME/.local/bin:$PATH" - python3 -m pip install -r requirements.txt --user - fi + dir('pylibfranka/docs') { + sh ''' + # Install Sphinx and dependencies (respect virtualenv if present) + if [ -n "$VIRTUAL_ENV" ]; then + python3 -m pip install -r requirements.txt + else + # Add sphinx to PATH for --user installs + export PATH="$HOME/.local/bin:$PATH" + python3 -m pip install -r requirements.txt --user + fi - # Set locale - export LC_ALL=C.UTF-8 - export LANG=C.UTF-8 + # Set locale + export LC_ALL=C.UTF-8 + export LANG=C.UTF-8 - # Add libfranka to library path - export LD_LIBRARY_PATH="${WORKSPACE}/build-release.${DISTRO}:${LD_LIBRARY_PATH:-}" + # Add libfranka to library path + export LD_LIBRARY_PATH="${WORKSPACE}/build-release.${DISTRO}:${LD_LIBRARY_PATH:-}" - # Build the documentation only on Ubuntu 20.04 - if [ "${UBUNTU_VERSION}" = "20.04" ]; then - make html - else - echo "Skipping docs build on ${UBUNTU_VERSION}" - fi - ''' + # Build the documentation only on Ubuntu 20.04 + if [ "${UBUNTU_VERSION}" = "20.04" ]; then + make html + else + echo "Skipping docs build on ${UBUNTU_VERSION}" + fi + ''' - publishHTML([allowMissing: false, - alwaysLinkToLastBuild: false, - keepAll: true, - reportDir: '_build/html', - reportFiles: 'index.html', - reportName: "pylibfranka Documentation (${env.DISTRO})"]) + publishHTML([allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: '_build/html', + reportFiles: 'index.html', + reportName: "pylibfranka Documentation (${distro})"]) + } + } } } } From d285aeabd9e95f792bd64dc1f3651471f4a94975 Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Fri, 9 Jan 2026 16:10:09 +0000 Subject: [PATCH 24/38] feat: removed useless trampoline classes, added bindings for async position control ported async example --- pylibfranka/__init__.py | 2 + .../examples/async_position_control.py | 117 +++++++++ pylibfranka/include/pygripper.h | 46 ---- pylibfranka/include/pylibfranka.h | 104 -------- pylibfranka/src/pylibfranka.cpp | 234 +++++++----------- 5 files changed, 212 insertions(+), 291 deletions(-) create mode 100644 pylibfranka/examples/async_position_control.py delete mode 100644 pylibfranka/include/pygripper.h delete mode 100644 pylibfranka/include/pylibfranka.h diff --git a/pylibfranka/__init__.py b/pylibfranka/__init__.py index 4a31b474..ee4114d2 100644 --- a/pylibfranka/__init__.py +++ b/pylibfranka/__init__.py @@ -28,6 +28,7 @@ RobotMode, RobotState, Torques, + AsyncPositionControlHandler ) from ._version import __version__ @@ -55,4 +56,5 @@ "RobotMode", "RobotState", "Torques", + "AsyncPositionControlHandler" ] diff --git a/pylibfranka/examples/async_position_control.py b/pylibfranka/examples/async_position_control.py new file mode 100644 index 00000000..0e124cd9 --- /dev/null +++ b/pylibfranka/examples/async_position_control.py @@ -0,0 +1,117 @@ +# Copyright (c) 2025 Franka Robotics GmbH +# Apache-2.0 + +import signal +import sys +import time +import math +import threading +from datetime import timedelta + +import pylibfranka as franka +from example_common import setDefaultBehaviour + +kDefaultMaximumVelocities = [0.655, 0.655, 0.655, 0.655, 1.315, 1.315, 1.315] +kDefaultGoalTolerance = 10.0 + +motion_finished = False + + +def signal_handler(sig, frame): + global motion_finished + if sig == signal.SIGINT: + motion_finished = True + + +def main(): + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") + sys.exit(-1) + + signal.signal(signal.SIGINT, signal_handler) + + try: + robot = franka.Robot(sys.argv[1], franka.RealtimeConfig.kIgnore) + except Exception as e: + print(f"Could not connect to robot: {e}") + sys.exit(-1) + + setDefaultBehaviour(robot) + + initial_position = [0, + -math.pi / 4, + 0, + -3 * math.pi / 4, + 0, + math.pi / 2, + math.pi / 4] + + time_elapsed = 0.0 + direction = 1.0 + time_since_last_log = 0.0 + + def calculate_joint_position_target(period_sec): + nonlocal time_elapsed, direction, time_since_last_log + + time_elapsed += period_sec + + target_positions = [ + initial_position[i] + direction * 0.25 + for i in range(7) + ] + + time_since_last_log += period_sec + if time_since_last_log >= 1.0: + direction *= -1.0 + time_since_last_log = 0.0 + + return franka.AsyncPositionControlHandler.JointPositionTarget( + joint_positions=target_positions + ) + + joint_position_control_configuration = \ + franka.AsyncPositionControlHandler.Configuration( + maximum_joint_velocities=kDefaultMaximumVelocities, + goal_tolerance=kDefaultGoalTolerance + ) + + result = franka.AsyncPositionControlHandler.configure(robot, + joint_position_control_configuration) + + if result.error_message is not None: + print(result.error_message) + sys.exit(-1) + + position_control_handler = result.handler + target_feedback = position_control_handler.getTargetFeedback() + + time_step = 0.020 # 20 ms, 50 Hz + + global motion_finished + while not motion_finished: + loop_start = time.monotonic() + + target_feedback = position_control_handler.getTargetFeedback() + if target_feedback.error_message is not None: + print(target_feedback.error_message) + sys.exit(-1) + + next_target = calculate_joint_position_target(time_step) + command_result = position_control_handler.setJointPositionTarget(next_target) + + if command_result.error_message is not None: + print(command_result.error_message) + sys.exit(-1) + + if time_elapsed > 10.0: + position_control_handler.stopControl() + motion_finished = True + break + + sleep_time = time_step - (time.monotonic() - loop_start) + if sleep_time > 0: + time.sleep(sleep_time) + + +if __name__ == "__main__": + main() diff --git a/pylibfranka/include/pygripper.h b/pylibfranka/include/pygripper.h deleted file mode 100644 index 8199f348..00000000 --- a/pylibfranka/include/pygripper.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2025 Franka Robotics GmbH -// Use of this source code is governed by the Apache-2.0 license, see LICENSE - -/** - * @file pygripper.h - * @brief Python bindings for the Franka Robotics Gripper Control - * - * This header file provides C++ class that wraps the Franka Robotics Gripper Control - * for use in Python through pybind11. It offers: - * - Gripper homing and movement control - * - Grasping functionality with configurable parameters - * - State reading and control methods - */ - -#pragma once - -#include -#include -#include -#include - -namespace pylibfranka { - -class PyGripper { - public: - explicit PyGripper(const std::string& franka_address); - ~PyGripper() = default; - - bool homing(); - bool grasp(double width, - double speed, - double force, - double epsilon_inner = 0.005, - double epsilon_outer = 0.005); - - franka::GripperState readOnce(); - - bool stop(); - bool move(double width, double speed); - franka::Gripper::ServerVersion serverVersion(); - - private: - std::unique_ptr gripper_; -}; - -} // namespace pylibfranka diff --git a/pylibfranka/include/pylibfranka.h b/pylibfranka/include/pylibfranka.h deleted file mode 100644 index ebe54d04..00000000 --- a/pylibfranka/include/pylibfranka.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2025 Franka Robotics GmbH -// Use of this source code is governed by the Apache-2.0 license, see LICENSE - -/** - * @file pylibfranka.h - * @brief Python bindings for the Franka Robotics Robot Control Library - * - * This header file provides C++ classes that wrap the Franka Robotics Robot Control Library - * for use in Python through pybind11. It offers: - * PyRobot: A wrapper for the Franka robot control functionality, providing: - * - Active control methods (torque, joint position, joint velocity control) - * - Configuration methods (collision behavior, impedance settings, etc.) - * - State reading and control methods - */ - -#pragma once - -// C++ standard library headers -#include -#include -#include -#include - -// Third-party library headers -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace pylibfranka { - -class PyRobot { - public: - explicit PyRobot(const std::string& robot_ip_address, - franka::RealtimeConfig realtime_config = franka::RealtimeConfig::kEnforce); - ~PyRobot() = default; - - // Active control methods - /** - * Starts torque control mode. - */ - auto startTorqueControl() -> std::unique_ptr; - - /** - * Starts the joint position control mode. - * @param control_type The type of controller to use (JointImpedance or CartesianImpedance). - */ - auto startJointPositionControl(franka::ControllerMode controller_mode) - -> std::unique_ptr; - - /** - * Starts the joint velocity control mode. - * @param control_type The type of controller to use (JointImpedance or CartesianImpedance). - */ - auto startJointVelocityControl(franka::ControllerMode controller_mode) - -> std::unique_ptr; - - /** - * Starts the Cartesian pose control mode. - * @param control_type The type of controller to use (JointImpedance or CartesianImpedance). - */ - auto startCartesianPoseControl(franka::ControllerMode controller_mode) - -> std::unique_ptr; - - /** - * Starts the Cartesian velocity control mode. - * @param control_type The type of controller to use (JointImpedance or CartesianImpedance). - */ - auto startCartesianVelocityControl(franka::ControllerMode controller_mode) - -> std::unique_ptr; - - // Configuration methods - void setCollisionBehavior(const std::array& lower_torque_thresholds, - const std::array& upper_torque_thresholds, - const std::array& lower_force_thresholds, - const std::array& upper_force_thresholds); - - void setJointImpedance(const std::array& K_theta); - void setCartesianImpedance(const std::array& K_x); - void setK(const std::array& EE_T_K); - void setEE(const std::array& NE_T_EE); - void setLoad(double load_mass, - const std::array& F_x_Cload, - const std::array& load_inertia); - void automaticErrorRecovery(); - - // State methods - franka::RobotState readOnce(); - void stop(); - - std::unique_ptr loadModel(); - - private: - std::unique_ptr robot_; -}; - -} // namespace pylibfranka diff --git a/pylibfranka/src/pylibfranka.cpp b/pylibfranka/src/pylibfranka.cpp index 7641c8fc..cfdaacba 100644 --- a/pylibfranka/src/pylibfranka.cpp +++ b/pylibfranka/src/pylibfranka.cpp @@ -1,17 +1,30 @@ // Copyright (c) 2025 Franka Robotics GmbH // Use of this source code is governed by the Apache-2.0 license, see LICENSE -#include "pylibfranka.h" -#include "pygripper.h" - // C++ standard library headers +#include +#include +#include +#include // Third-party library headers -#include -#include #include #include #include + +// Libfranka +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include namespace py = pybind11; @@ -38,110 +51,6 @@ auto convertControllerMode(franka::ControllerMode mode) namespace pylibfranka { -PyGripper::PyGripper(const std::string& franka_address) - : gripper_(std::make_unique(franka_address)) {} - -bool PyGripper::homing() { - return gripper_->homing(); -} - -bool PyGripper::grasp(double width, - double speed, - double force, - double epsilon_inner, - double epsilon_outer) { - return gripper_->grasp(width, speed, force, epsilon_inner, epsilon_outer); -} - -franka::GripperState PyGripper::readOnce() { - return gripper_->readOnce(); -} - -bool PyGripper::stop() { - return gripper_->stop(); -} -bool PyGripper::move(double width, double speed) { - return gripper_->move(width, speed); -} - -franka::Gripper::ServerVersion PyGripper::serverVersion() { - return gripper_->serverVersion(); -} - -PyRobot::PyRobot(const std::string& franka_address, franka::RealtimeConfig realtime_config) - : robot_(std::make_unique(franka_address, realtime_config)) {} - -auto PyRobot::startTorqueControl() -> std::unique_ptr { - return robot_->startTorqueControl(); -} - -auto PyRobot::startJointPositionControl(franka::ControllerMode controller_mode) - -> std::unique_ptr { - return robot_->startJointPositionControl(convertControllerMode(controller_mode)); -} - -auto PyRobot::startJointVelocityControl(franka::ControllerMode controller_mode) - -> std::unique_ptr { - return robot_->startJointVelocityControl(convertControllerMode(controller_mode)); -} - -auto PyRobot::startCartesianPoseControl(franka::ControllerMode controller_mode) - -> std::unique_ptr { - return robot_->startCartesianPoseControl(convertControllerMode(controller_mode)); -} - -auto PyRobot::startCartesianVelocityControl(franka::ControllerMode controller_mode) - -> std::unique_ptr { - return robot_->startCartesianVelocityControl(convertControllerMode(controller_mode)); -} - -void PyRobot::setCollisionBehavior(const std::array& lower_torque_thresholds, - const std::array& upper_torque_thresholds, - const std::array& lower_force_thresholds, - const std::array& upper_force_thresholds) { - robot_->setCollisionBehavior(lower_torque_thresholds, upper_torque_thresholds, - lower_torque_thresholds, upper_torque_thresholds, - lower_force_thresholds, upper_force_thresholds, - lower_force_thresholds, upper_force_thresholds); -} - -void PyRobot::setJointImpedance(const std::array& K_theta) { - robot_->setJointImpedance(K_theta); -} - -void PyRobot::setCartesianImpedance(const std::array& K_x) { - robot_->setCartesianImpedance(K_x); -} - -void PyRobot::setK(const std::array& EE_T_K) { - robot_->setK(EE_T_K); -} - -void PyRobot::setEE(const std::array& NE_T_EE) { - robot_->setEE(NE_T_EE); -} - -void PyRobot::setLoad(double load_mass, - const std::array& F_x_Cload, - const std::array& load_inertia) { - robot_->setLoad(load_mass, F_x_Cload, load_inertia); -} - -void PyRobot::automaticErrorRecovery() { - robot_->automaticErrorRecovery(); -} - -franka::RobotState PyRobot::readOnce() { - return robot_->readOnce(); -} - -std::unique_ptr PyRobot::loadModel() { - return std::make_unique(robot_->loadModel()); -} - -void PyRobot::stop() { - robot_->stop(); -} PYBIND11_MODULE(_pylibfranka, m) { m.doc() = "Python bindings for Franka Robotics Robot Control Library"; @@ -587,7 +496,7 @@ PYBIND11_MODULE(_pylibfranka, m) { .def_readwrite("motion_finished", &franka::CartesianVelocities::motion_finished, "Set to True to finish motion"); // Bind PyRobot - py::class_(m, "Robot", R"pbdoc( + py::class_>(m, "Robot", R"pbdoc( Main interface for controlling a Franka robot. Provides real-time control capabilities including torque, position, @@ -600,84 +509,98 @@ PYBIND11_MODULE(_pylibfranka, m) { @param robot_ip_address IP address or hostname of the robot @param realtime_config Real-time scheduling requirements (default: kEnforce) )pbdoc") - .def("start_torque_control", &PyRobot::startTorqueControl, R"pbdoc( + .def("start_torque_control", &franka::Robot::startTorqueControl, R"pbdoc( Start torque control mode. @return ActiveControlBase interface for sending torque commands )pbdoc") - .def("start_joint_position_control", &PyRobot::startJointPositionControl, R"pbdoc( + .def("start_joint_position_control", [](franka::Robot& self, franka::ControllerMode mode) + {return self.startJointPositionControl(convertControllerMode(mode));}, R"pbdoc( Start joint position control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending position commands )pbdoc") - .def("start_joint_velocity_control", &PyRobot::startJointVelocityControl, R"pbdoc( + .def("start_joint_velocity_control", [](franka::Robot& self, franka::ControllerMode mode) + {return self.startJointVelocityControl(convertControllerMode(mode));}, R"pbdoc( Start joint velocity control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending velocity commands )pbdoc") - .def("start_cartesian_pose_control", &PyRobot::startCartesianPoseControl, R"pbdoc( + .def("start_cartesian_pose_control", [](franka::Robot& self, franka::ControllerMode mode) + {return self.startCartesianPoseControl(convertControllerMode(mode));}, R"pbdoc( Start Cartesian pose control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending Cartesian pose commands )pbdoc") - .def("start_cartesian_velocity_control", &PyRobot::startCartesianVelocityControl, R"pbdoc( + .def("start_cartesian_velocity_control", [](franka::Robot& self, franka::ControllerMode mode) + {return self.startCartesianVelocityControl(convertControllerMode(mode));}, R"pbdoc( Start Cartesian velocity control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending Cartesian velocity commands )pbdoc") - .def("set_collision_behavior", &PyRobot::setCollisionBehavior, R"pbdoc( - Configure collision detection thresholds. - - @param lower_torque_thresholds Lower torque thresholds [Nm] (7,) - @param upper_torque_thresholds Upper torque thresholds [Nm] (7,) - @param lower_force_thresholds Lower Cartesian force thresholds [N, Nm] (6,) - @param upper_force_thresholds Upper Cartesian force thresholds [N, Nm] (6,) - )pbdoc") - .def("set_joint_impedance", &PyRobot::setJointImpedance, R"pbdoc( + .def("set_collision_behavior", py::overload_cast< + const std::array&, + const std::array&, + const std::array&, + const std::array& + >(&franka::Robot::setCollisionBehavior), + py::arg("lower_torque_thresholds"), + py::arg("upper_torque_thresholds"), + py::arg("lower_force_thresholds"), + py::arg("upper_force_thresholds"), + R"pbdoc( + Configure collision detection thresholds. + + @param lower_torque_thresholds Lower torque thresholds [Nm] (7,) + @param upper_torque_thresholds Upper torque thresholds [Nm] (7,) + @param lower_force_thresholds Lower Cartesian force thresholds [N, Nm] (6,) + @param upper_force_thresholds Upper Cartesian force thresholds [N, Nm] (6,) + )pbdoc") + .def("set_joint_impedance", &franka::Robot::setJointImpedance, R"pbdoc( Set joint impedance for internal controller. @param K_theta Joint stiffness values [Nm/rad] (7,) )pbdoc") - .def("set_cartesian_impedance", &PyRobot::setCartesianImpedance, R"pbdoc( + .def("set_cartesian_impedance", &franka::Robot::setCartesianImpedance, R"pbdoc( Set Cartesian impedance for internal controller. @param K_x Cartesian stiffness values [N/m, Nm/rad] (6,) )pbdoc") - .def("set_K", &PyRobot::setK, R"pbdoc( + .def("set_K", &franka::Robot::setK, R"pbdoc( Set stiffness frame K in end effector frame. @param EE_T_K Homogeneous transformation matrix (16,) in column-major order )pbdoc") - .def("set_EE", &PyRobot::setEE, R"pbdoc( + .def("set_EE", &franka::Robot::setEE, R"pbdoc( Set end effector frame relative to nominal end effector frame. @param NE_T_EE Homogeneous transformation matrix (16,) in column-major order )pbdoc") - .def("set_load", &PyRobot::setLoad, R"pbdoc( + .def("set_load", &franka::Robot::setLoad, R"pbdoc( Set external load parameters. @param load_mass Mass of the external load [kg] @param F_x_Cload Center of mass of load in flange frame [m] (3,) @param load_inertia Inertia tensor of load [kg*m²] (9,) in column-major order )pbdoc") - .def("automatic_error_recovery", &PyRobot::automaticErrorRecovery, R"pbdoc( + .def("automatic_error_recovery", &franka::Robot::automaticErrorRecovery, R"pbdoc( Attempt automatic error recovery. )pbdoc") - .def("read_once", &PyRobot::readOnce, R"pbdoc( + .def("read_once", &franka::Robot::readOnce, R"pbdoc( Read current robot state once. @return Current robot state )pbdoc") - .def("load_model", &PyRobot::loadModel, R"pbdoc( + .def("load_model", py::overload_cast<>(&franka::Robot::loadModel), R"pbdoc( Load robot dynamics model. @return Model object for computing dynamics quantities )pbdoc") - .def("stop", &PyRobot::stop, R"pbdoc( + .def("stop", &franka::Robot::stop, R"pbdoc( Stop currently running motion. )pbdoc"); @@ -691,8 +614,8 @@ PYBIND11_MODULE(_pylibfranka, m) { .def_readwrite("temperature", &franka::GripperState::temperature, "Gripper temperature [°C]") .def_readwrite("time", &franka::GripperState::time, "Timestamp"); - // Bind PyGripper - py::class_(m, "Gripper", R"pbdoc( + // Bind franka::Gripper + py::class_(m, "Gripper", R"pbdoc( Interface for controlling a Franka Hand gripper. )pbdoc") .def(py::init(), R"pbdoc( @@ -703,7 +626,7 @@ PYBIND11_MODULE(_pylibfranka, m) { @param franka_address IP address or hostname of the robot :raises NetworkException: if the connection cannot be established )pbdoc") - .def("homing", &PyGripper::homing, R"pbdoc( + .def("homing", &franka::Gripper::homing, R"pbdoc( Perform homing to find maximum width. Homing moves the gripper fingers to the maximum width to calibrate the position. @@ -713,12 +636,12 @@ PYBIND11_MODULE(_pylibfranka, m) { :raises CommandException: if the command fails :raises NetworkException: if the connection is lost )pbdoc") - .def("grasp", &PyGripper::grasp, py::arg("width"), py::arg("speed"), py::arg("force"), + .def("grasp", &franka::Gripper::grasp, py::arg("width"), py::arg("speed"), py::arg("force"), py::arg("epsilon_inner") = 0.005, py::arg("epsilon_outer") = 0.005, R"pbdoc( Grasp an object. The gripper closes at the specified speed and force until an object is detected - or the target width is reached. The grasp is considered successful if the final + or the target width is reached. The grasp is considered successfulsetCurrentThreadToHighestSchedulerPriority if the final width is within the specified tolerances. @param width Target grasp width [m] @@ -730,7 +653,7 @@ PYBIND11_MODULE(_pylibfranka, m) { :raises CommandException: if the command fails :raises NetworkException: if the connection is lost )pbdoc") - .def("read_once", &PyGripper::readOnce, R"pbdoc( + .def("read_once", &franka::Gripper::readOnce, R"pbdoc( Read current gripper state once. Queries the gripper for its current state including width, temperature, @@ -739,7 +662,7 @@ PYBIND11_MODULE(_pylibfranka, m) { @return Current gripper state :raises NetworkException: if the connection is lost )pbdoc") - .def("stop", &PyGripper::stop, R"pbdoc( + .def("stop", &franka::Gripper::stop, R"pbdoc( Stop current gripper motion. Stops any ongoing gripper movement immediately. @@ -748,7 +671,7 @@ PYBIND11_MODULE(_pylibfranka, m) { :raises CommandException: if the command fails :raises NetworkException: if the connection is lost )pbdoc") - .def("move", &PyGripper::move, R"pbdoc( + .def("move", &franka::Gripper::move, R"pbdoc( Move gripper fingers to a specific width. Moves the gripper to the specified width at the given speed. Use this for @@ -760,11 +683,40 @@ PYBIND11_MODULE(_pylibfranka, m) { :raises CommandException: if the command fails :raises NetworkException: if the connection is lost )pbdoc") - .def("server_version", &PyGripper::serverVersion, R"pbdoc( + .def("server_version", &franka::Gripper::serverVersion, R"pbdoc( Get gripper server version. @return Server version information )pbdoc"); + + auto async_position_control_handler = py::class_(m, "AsyncPositionControlHandler", R"pbdoc( + Handler for asynchronous joint position control + )pbdoc") + .def_static("configure", &franka::AsyncPositionControlHandler::configure) + .def("set_joint_position_target", &franka::AsyncPositionControlHandler::setJointPositionTarget) + .def("get_target_feedback", &franka::AsyncPositionControlHandler::getTargetFeedback) + .def("stop_control", &franka::AsyncPositionControlHandler::stopControl); + + py::class_(async_position_control_handler, "Configuration") + .def(py::init&, double>(), py::arg("maximum_joint_velocities"), py::arg("goal_tolerance")) + .def_readwrite("maximum_joint_velocities", &franka::AsyncPositionControlHandler::Configuration::maximum_joint_velocities) + .def_readwrite("goal_tolerance", &franka::AsyncPositionControlHandler::Configuration::goal_tolerance); + + py::class_(async_position_control_handler, "ConfigurationResult") + .def_readwrite("handler", &franka::AsyncPositionControlHandler::ConfigurationResult::handler) + .def_readwrite("error_message", &franka::AsyncPositionControlHandler::ConfigurationResult::error_message); + + py::class_(async_position_control_handler, "JointPositionTarget") + .def_readwrite("joint_positions", &franka::AsyncPositionControlHandler::JointPositionTarget::joint_positions); + + py::class_(async_position_control_handler, "CommandResult") + .def_readwrite("motion_uuid", &franka::AsyncPositionControlHandler::CommandResult::motion_uuid) + .def_readwrite("was_successful", &franka::AsyncPositionControlHandler::CommandResult::was_successful) + .def_readwrite("error_message", &franka::AsyncPositionControlHandler::CommandResult::error_message); + + py::class_(async_position_control_handler, "TargetFeedback") + .def_readwrite("status", &franka::AsyncPositionControlHandler::TargetFeedback::status) + .def_readwrite("error_message", &franka::AsyncPositionControlHandler::TargetFeedback::error_message); } } // namespace pylibfranka From eac623350b7edac8239fb5fa0e53959d1a0e58b8 Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Fri, 9 Jan 2026 17:29:43 +0000 Subject: [PATCH 25/38] fix: async example is working, api cleaned --- common | 2 +- examples/async_position_control.cpp | 2 +- .../async_position_control_handler.hpp | 2 +- include/franka/robot.h | 4 +-- .../examples/async_position_control.py | 9 +++--- pylibfranka/src/pylibfranka.cpp | 29 +++++++++++++++++-- src/robot.cpp | 4 +-- src/robot_control.h | 2 +- src/robot_impl.cpp | 2 +- src/robot_impl.h | 2 +- 10 files changed, 41 insertions(+), 17 deletions(-) diff --git a/common b/common index d96c221a..8b34eecb 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit d96c221a32c343384cbe2f77c61116ed58b84231 +Subproject commit 8b34eecbb8636e36a4d74b257cb6d3da4fd23821 diff --git a/examples/async_position_control.cpp b/examples/async_position_control.cpp index ca4bae3e..8445d8c6 100644 --- a/examples/async_position_control.cpp +++ b/examples/async_position_control.cpp @@ -17,7 +17,7 @@ using namespace std::chrono_literals; -const std::vector kDefaultMaximumVelocities{0.655, 0.655, 0.655, 0.655, +const std::array kDefaultMaximumVelocities{0.655, 0.655, 0.655, 0.655, 1.315, 1.315, 1.315}; constexpr double kDefaultGoalTolerance = 10.0; diff --git a/include/franka/async_control/async_position_control_handler.hpp b/include/franka/async_control/async_position_control_handler.hpp index 1528cd4f..eba4d5b1 100644 --- a/include/franka/async_control/async_position_control_handler.hpp +++ b/include/franka/async_control/async_position_control_handler.hpp @@ -26,7 +26,7 @@ class AsyncPositionControlHandler { struct Configuration { // Maximum joint velocities for the position control. If no values are provided, the maximum // velocities of the robot will be used. - std::optional> maximum_joint_velocities{}; + std::optional> maximum_joint_velocities{}; std::optional goal_tolerance{}; }; diff --git a/include/franka/robot.h b/include/franka/robot.h index c6e96fba..9f490785 100644 --- a/include/franka/robot.h +++ b/include/franka/robot.h @@ -716,7 +716,7 @@ class Robot { */ virtual std::unique_ptr startAsyncJointPositionControl( const research_interface::robot::Move::ControllerMode& control_type, - const std::optional>& maximum_velocities); + const std::optional>& maximum_velocities); /** * Starts a new joint velocity motion generator @@ -857,7 +857,7 @@ class Robot { */ template auto startAsyncControl(const research_interface::robot::Move::ControllerMode& controller_type, - const std::optional>& maximum_velocities) + const std::optional>& maximum_velocities) -> std::unique_ptr; }; diff --git a/pylibfranka/examples/async_position_control.py b/pylibfranka/examples/async_position_control.py index 0e124cd9..936dd6d2 100644 --- a/pylibfranka/examples/async_position_control.py +++ b/pylibfranka/examples/async_position_control.py @@ -83,7 +83,7 @@ def calculate_joint_position_target(period_sec): sys.exit(-1) position_control_handler = result.handler - target_feedback = position_control_handler.getTargetFeedback() + target_feedback = position_control_handler.get_target_feedback() time_step = 0.020 # 20 ms, 50 Hz @@ -91,21 +91,22 @@ def calculate_joint_position_target(period_sec): while not motion_finished: loop_start = time.monotonic() - target_feedback = position_control_handler.getTargetFeedback() + target_feedback = position_control_handler.get_target_feedback() if target_feedback.error_message is not None: print(target_feedback.error_message) sys.exit(-1) next_target = calculate_joint_position_target(time_step) - command_result = position_control_handler.setJointPositionTarget(next_target) + command_result = position_control_handler.set_joint_position_target(next_target) if command_result.error_message is not None: print(command_result.error_message) sys.exit(-1) if time_elapsed > 10.0: - position_control_handler.stopControl() + position_control_handler.stop_control() motion_finished = True + print("Control finished") break sleep_time = time_step - (time.monotonic() - loop_start) diff --git a/pylibfranka/src/pylibfranka.cpp b/pylibfranka/src/pylibfranka.cpp index cfdaacba..1ad1af8c 100644 --- a/pylibfranka/src/pylibfranka.cpp +++ b/pylibfranka/src/pylibfranka.cpp @@ -689,16 +689,31 @@ PYBIND11_MODULE(_pylibfranka, m) { @return Server version information )pbdoc"); - auto async_position_control_handler = py::class_(m, "AsyncPositionControlHandler", R"pbdoc( + auto async_position_control_handler = py::class_>(m, "AsyncPositionControlHandler", R"pbdoc( Handler for asynchronous joint position control )pbdoc") .def_static("configure", &franka::AsyncPositionControlHandler::configure) .def("set_joint_position_target", &franka::AsyncPositionControlHandler::setJointPositionTarget) - .def("get_target_feedback", &franka::AsyncPositionControlHandler::getTargetFeedback) + .def("get_target_feedback", &franka::AsyncPositionControlHandler::getTargetFeedback, py::arg("robot_state") = std::nullopt) .def("stop_control", &franka::AsyncPositionControlHandler::stopControl); py::class_(async_position_control_handler, "Configuration") - .def(py::init&, double>(), py::arg("maximum_joint_velocities"), py::arg("goal_tolerance")) + .def(py::init([](const std::vector& maximum_joint_velocities, double goal_tolerance) + { + if (maximum_joint_velocities.size() != franka::Robot::kNumJoints) { + throw std::invalid_argument("joint_velocities must have exactly 7 values"); + } + franka::AsyncPositionControlHandler::Configuration config; + + std::array arr{}; + std::copy(maximum_joint_velocities.begin(), + maximum_joint_velocities.end(), + arr.begin()); + + config.maximum_joint_velocities = arr; + config.goal_tolerance = goal_tolerance; + return config; + }), py::arg("maximum_joint_velocities"), py::arg("goal_tolerance")) .def_readwrite("maximum_joint_velocities", &franka::AsyncPositionControlHandler::Configuration::maximum_joint_velocities) .def_readwrite("goal_tolerance", &franka::AsyncPositionControlHandler::Configuration::goal_tolerance); @@ -707,6 +722,14 @@ PYBIND11_MODULE(_pylibfranka, m) { .def_readwrite("error_message", &franka::AsyncPositionControlHandler::ConfigurationResult::error_message); py::class_(async_position_control_handler, "JointPositionTarget") + .def(py::init([](const std::vector& joints) { + if (joints.size() != franka::Robot::kNumJoints) { + throw std::invalid_argument("joint_positions must have exactly 7 values"); + } + franka::AsyncPositionControlHandler::JointPositionTarget tgt; + std::copy(joints.begin(), joints.end(), tgt.joint_positions.begin()); + return tgt; + }), py::arg("joint_positions")) .def_readwrite("joint_positions", &franka::AsyncPositionControlHandler::JointPositionTarget::joint_positions); py::class_(async_position_control_handler, "CommandResult") diff --git a/src/robot.cpp b/src/robot.cpp index 3a31c4e2..ba735097 100644 --- a/src/robot.cpp +++ b/src/robot.cpp @@ -278,7 +278,7 @@ std::unique_ptr Robot::startControl( template auto Robot::startAsyncControl( const research_interface::robot::Move::ControllerMode& controller_type, - const std::optional>& maximum_velocities) + const std::optional>& maximum_velocities) -> std::unique_ptr { std::unique_lock control_lock(control_mutex_, std::try_to_lock); assertOwningLock(control_lock); @@ -315,7 +315,7 @@ std::unique_ptr Robot::startJointPositionControl( std::unique_ptr Robot::startAsyncJointPositionControl( const research_interface::robot::Move::ControllerMode& control_type, - const std::optional>& maximum_velocities) { + const std::optional>& maximum_velocities) { return startAsyncControl(control_type, maximum_velocities); } diff --git a/src/robot_control.h b/src/robot_control.h index 1c728ddf..ce3ae420 100644 --- a/src/robot_control.h +++ b/src/robot_control.h @@ -39,7 +39,7 @@ class RobotControl { const research_interface::robot::Move::Deviation& maximum_path_deviation, const research_interface::robot::Move::Deviation& maximum_goal_pose_deviation, bool use_async_motion_generator, - const std::optional>& maximum_velocities) = 0; + const std::optional>& maximum_velocities) = 0; /** * Receives the new motion/control command to send to the robot while updating the robot state. diff --git a/src/robot_impl.cpp b/src/robot_impl.cpp index 646f8506..7c8a9a67 100644 --- a/src/robot_impl.cpp +++ b/src/robot_impl.cpp @@ -256,7 +256,7 @@ uint32_t Robot::Impl::startMotion( const research_interface::robot::Move::Deviation& maximum_path_deviation, const research_interface::robot::Move::Deviation& maximum_goal_pose_deviation, bool use_async_motion_generator, - const std::optional>& maximum_velocities) { + const std::optional>& maximum_velocities) { if (motionGeneratorRunning() || controllerRunning()) { throw ControlException("libfranka robot: Attempted to start multiple motions!"); } diff --git a/src/robot_impl.h b/src/robot_impl.h index 9ddb3593..cbdfd072 100644 --- a/src/robot_impl.h +++ b/src/robot_impl.h @@ -54,7 +54,7 @@ class Robot::Impl : public RobotControl { const research_interface::robot::Move::Deviation& maximum_path_deviation, const research_interface::robot::Move::Deviation& maximum_goal_pose_deviation, bool use_async_motion_generator, - const std::optional>& maximum_velocities) + const std::optional>& maximum_velocities) -> uint32_t override; auto cancelMotion(uint32_t motion_id) -> void override; auto finishMotion( From 50f446dfd1a8938e3ac4993bb7b0f911fbe830f4 Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Mon, 12 Jan 2026 16:42:20 +0000 Subject: [PATCH 26/38] chore: update docs --- CHANGELOG.md | 6 + common | 2 +- examples/async_position_control.cpp | 2 +- .../async_position_control_handler.hpp | 2 +- include/franka/robot.h | 4 +- pylibfranka/CMakeLists.txt | 3 + pylibfranka/README.md | 11 + .../examples/async_position_control.py | 16 +- .../include/pylibfranka/async_control.hpp | 15 ++ pylibfranka/include/pylibfranka/gripper.hpp | 15 ++ pylibfranka/src/async_control.cpp | 114 ++++++++++ pylibfranka/src/gripper.cpp | 96 +++++++++ pylibfranka/src/pylibfranka.cpp | 204 ++++-------------- src/robot.cpp | 4 +- src/robot_control.h | 2 +- src/robot_impl.cpp | 2 +- src/robot_impl.h | 2 +- 17 files changed, 325 insertions(+), 175 deletions(-) create mode 100644 pylibfranka/include/pylibfranka/async_control.hpp create mode 100644 pylibfranka/include/pylibfranka/gripper.hpp create mode 100644 pylibfranka/src/async_control.cpp create mode 100644 pylibfranka/src/gripper.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index b2559446..238953d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ All notable changes to libfranka in this file. #### Changed - Hotfix to avoid torques discontinuity false positives due to robot state float precision change. +### pylibfranka - Python +#### Added +- Async control python bindings +- Async joint positions control example from C++. + ## [0.19.0] ### libfranka - C++ #### Changed @@ -14,6 +19,7 @@ All notable changes to libfranka in this file. - Format libfranka debian package to inlclude ubuntu code name and arch: libfranka_VERSION_CODENAME_ARCH.deb - Added build containers to support Ubuntu 22.04 and 24.04 + ## [0.18.2] Requires Franka Research 3 System Version >= 5.9.0 diff --git a/common b/common index 8b34eecb..d96c221a 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 8b34eecbb8636e36a4d74b257cb6d3da4fd23821 +Subproject commit d96c221a32c343384cbe2f77c61116ed58b84231 diff --git a/examples/async_position_control.cpp b/examples/async_position_control.cpp index 8445d8c6..ca4bae3e 100644 --- a/examples/async_position_control.cpp +++ b/examples/async_position_control.cpp @@ -17,7 +17,7 @@ using namespace std::chrono_literals; -const std::array kDefaultMaximumVelocities{0.655, 0.655, 0.655, 0.655, +const std::vector kDefaultMaximumVelocities{0.655, 0.655, 0.655, 0.655, 1.315, 1.315, 1.315}; constexpr double kDefaultGoalTolerance = 10.0; diff --git a/include/franka/async_control/async_position_control_handler.hpp b/include/franka/async_control/async_position_control_handler.hpp index eba4d5b1..1528cd4f 100644 --- a/include/franka/async_control/async_position_control_handler.hpp +++ b/include/franka/async_control/async_position_control_handler.hpp @@ -26,7 +26,7 @@ class AsyncPositionControlHandler { struct Configuration { // Maximum joint velocities for the position control. If no values are provided, the maximum // velocities of the robot will be used. - std::optional> maximum_joint_velocities{}; + std::optional> maximum_joint_velocities{}; std::optional goal_tolerance{}; }; diff --git a/include/franka/robot.h b/include/franka/robot.h index 9f490785..c6e96fba 100644 --- a/include/franka/robot.h +++ b/include/franka/robot.h @@ -716,7 +716,7 @@ class Robot { */ virtual std::unique_ptr startAsyncJointPositionControl( const research_interface::robot::Move::ControllerMode& control_type, - const std::optional>& maximum_velocities); + const std::optional>& maximum_velocities); /** * Starts a new joint velocity motion generator @@ -857,7 +857,7 @@ class Robot { */ template auto startAsyncControl(const research_interface::robot::Move::ControllerMode& controller_type, - const std::optional>& maximum_velocities) + const std::optional>& maximum_velocities) -> std::unique_ptr; }; diff --git a/pylibfranka/CMakeLists.txt b/pylibfranka/CMakeLists.txt index 784270f7..efcb92f7 100644 --- a/pylibfranka/CMakeLists.txt +++ b/pylibfranka/CMakeLists.txt @@ -20,6 +20,9 @@ find_package(pinocchio REQUIRED) # Create Python module pybind11_add_module(_pylibfranka src/pylibfranka.cpp + src/async_control.cpp + src/gripper.cpp + # add more source files as needed for next splits ) # Include directories diff --git a/pylibfranka/README.md b/pylibfranka/README.md index 8aea8329..a4d7c5f3 100644 --- a/pylibfranka/README.md +++ b/pylibfranka/README.md @@ -161,3 +161,14 @@ The gripper example: - Attempts to grasp an object with specified parameters - Verifies successful grasping - Releases the object + +### Async Joint Position Control Example + +You can also use the async API to control the robot in a low-rate fashion, e.g. 50Hz. +This allows you to specify joint position setpoints without the need for a blocking loop. + +```bash +cd examples +python3 async_position_control.py --ip +``` + diff --git a/pylibfranka/examples/async_position_control.py b/pylibfranka/examples/async_position_control.py index 936dd6d2..89acca60 100644 --- a/pylibfranka/examples/async_position_control.py +++ b/pylibfranka/examples/async_position_control.py @@ -1,10 +1,16 @@ -# Copyright (c) 2025 Franka Robotics GmbH +# Copyright (c) 2026 Franka Robotics GmbH # Apache-2.0 +# This example demonstrates asynchronous position control of a Franka robot using the +# pylibfranka library. It connects to the robot, sets up an asynchronous position control +# handler, and continuously updates joint position targets in a loop until interrupted, leveraging +# the latest low-rate control API. + import signal import sys import time import math +import argparse import threading from datetime import timedelta @@ -24,14 +30,14 @@ def signal_handler(sig, frame): def main(): - if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} ") - sys.exit(-1) + parser = argparse.ArgumentParser() + parser.add_argument("--ip", type=str, default="localhost", help="Robot IP address") + args = parser.parse_args() signal.signal(signal.SIGINT, signal_handler) try: - robot = franka.Robot(sys.argv[1], franka.RealtimeConfig.kIgnore) + robot = franka.Robot(args.ip, franka.RealtimeConfig.kIgnore) except Exception as e: print(f"Could not connect to robot: {e}") sys.exit(-1) diff --git a/pylibfranka/include/pylibfranka/async_control.hpp b/pylibfranka/include/pylibfranka/async_control.hpp new file mode 100644 index 00000000..6c04fb96 --- /dev/null +++ b/pylibfranka/include/pylibfranka/async_control.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2026 Franka Robotics GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE + +// Expose async position control handler python bindings + +#pragma once +#include + +namespace py = pybind11; + +namespace pylibfranka { + +void bind_async_control(py::module& m); + +} diff --git a/pylibfranka/include/pylibfranka/gripper.hpp b/pylibfranka/include/pylibfranka/gripper.hpp new file mode 100644 index 00000000..4cff936c --- /dev/null +++ b/pylibfranka/include/pylibfranka/gripper.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2026 Franka Robotics GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE + +// Expose gripper-related python bindings + +#pragma once +#include + +namespace py = pybind11; + +namespace pylibfranka { + +void bind_gripper(py::module& m); + +} \ No newline at end of file diff --git a/pylibfranka/src/async_control.cpp b/pylibfranka/src/async_control.cpp new file mode 100644 index 00000000..718166aa --- /dev/null +++ b/pylibfranka/src/async_control.cpp @@ -0,0 +1,114 @@ +// Copyright (c) 2026 Franka Robotics GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE + +#include +#include + +namespace pylibfranka { + +void bind_async_control(py::module& m) { + auto async_position_control_handler = + py::class_>( + m, "AsyncPositionControlHandler", R"pbdoc( + Handler for asynchronous joint position control + )pbdoc") + .def_static("configure", &franka::AsyncPositionControlHandler::configure, R"pbdoc( + Configure an AsyncPositionControlHandler with the given configuration. + + @param configuration Configuration parameters + @return ConfigurationResult containing the handler or an error message + )pbdoc") + .def("set_joint_position_target", + &franka::AsyncPositionControlHandler::setJointPositionTarget, R"pbdoc( + Set a new joint position target. + + @param target Joint position target + @return CommandResult containing the motion UUID, success flag, and error message + )pbdoc") + .def("get_target_feedback", &franka::AsyncPositionControlHandler::getTargetFeedback, + py::arg("robot_state") = std::nullopt, R"pbdoc( + Get feedback on the current target. + + @param robot_state Optional current robot state for more detailed feedback + @return TargetFeedback containing status and error message + )pbdoc") + .def("stop_control", &franka::AsyncPositionControlHandler::stopControl, R"pbdoc( + Stop the position control handler. + )pbdoc"); + + py::class_(async_position_control_handler, + "Configuration", R"pbdoc( + Configuration parameters for AsyncPositionControlHandler. + )pbdoc") + .def(py::init([](const std::vector& maximum_joint_velocities, double goal_tolerance) { + if (maximum_joint_velocities.size() != franka::Robot::kNumJoints) { + throw std::invalid_argument("joint_velocities must have exactly" + + std::to_string(franka::Robot::kNumJoints) + " values"); + } + franka::AsyncPositionControlHandler::Configuration config{ + .maximum_joint_velocities = maximum_joint_velocities, + .goal_tolerance = goal_tolerance}; + return config; + }), + py::arg("maximum_joint_velocities"), py::arg("goal_tolerance"), R"pbdoc( + Create Configuration. + + @param maximum_joint_velocities Maximum joint velocities [rad/s] (7,) + @param goal_tolerance Goal tolerance [rad] + )pbdoc") + .def_readwrite("maximum_joint_velocities", + &franka::AsyncPositionControlHandler::Configuration::maximum_joint_velocities) + .def_readwrite("goal_tolerance", + &franka::AsyncPositionControlHandler::Configuration::goal_tolerance); + + py::class_( + async_position_control_handler, "ConfigurationResult", R"pbdoc( + Result of configuring an AsyncPositionControlHandler. + )pbdoc") + .def_readwrite("handler", &franka::AsyncPositionControlHandler::ConfigurationResult::handler) + .def_readwrite("error_message", + &franka::AsyncPositionControlHandler::ConfigurationResult::error_message); + + py::class_( + async_position_control_handler, "JointPositionTarget", R"pbdoc( + Joint position target for AsyncPositionControlHandler. + )pbdoc") + .def(py::init([](const std::vector& joints) { + if (joints.size() != franka::Robot::kNumJoints) { + throw std::invalid_argument("joint_positions must have exactly" + + std::to_string(franka::Robot::kNumJoints) + " values"); + } + franka::AsyncPositionControlHandler::JointPositionTarget tgt; + std::copy(joints.begin(), joints.end(), tgt.joint_positions.begin()); + return tgt; + }), + py::arg("joint_positions"), R"pbdoc( + Create JointPositionTarget. + + @param joint_positions Target joint positions [rad] (7,) + )pbdoc") + .def_readwrite("joint_positions", + &franka::AsyncPositionControlHandler::JointPositionTarget::joint_positions); + + py::class_(async_position_control_handler, + "CommandResult", R"pbdoc( + Result of setting a joint position target. + )pbdoc") + .def_readwrite("motion_uuid", + &franka::AsyncPositionControlHandler::CommandResult::motion_uuid) + .def_readwrite("was_successful", + &franka::AsyncPositionControlHandler::CommandResult::was_successful) + .def_readwrite("error_message", + &franka::AsyncPositionControlHandler::CommandResult::error_message); + + py::class_(async_position_control_handler, + "TargetFeedback", R"pbdoc( + Feedback from a target position command. + )pbdoc") + .def_readwrite("status", &franka::AsyncPositionControlHandler::TargetFeedback::status) + .def_readwrite("error_message", + &franka::AsyncPositionControlHandler::TargetFeedback::error_message); +} + +} // namespace pylibfranka \ No newline at end of file diff --git a/pylibfranka/src/gripper.cpp b/pylibfranka/src/gripper.cpp new file mode 100644 index 00000000..e58604cf --- /dev/null +++ b/pylibfranka/src/gripper.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2026 Franka Robotics GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE + +#include +#include + +namespace pylibfranka { + +void bind_gripper(py::module& m) { + // Bind franka::GripperState + py::class_(m, "GripperState", R"pbdoc( + Current state of the Franka Hand gripper. + )pbdoc") + .def_readwrite("width", &franka::GripperState::width, "Current gripper width [m]") + .def_readwrite("max_width", &franka::GripperState::max_width, "Maximum gripper width [m]") + .def_readwrite("is_grasped", &franka::GripperState::is_grasped, "True if object is grasped") + .def_readwrite("temperature", &franka::GripperState::temperature, "Gripper temperature [°C]") + .def_readwrite("time", &franka::GripperState::time, "Timestamp"); + + // Bind franka::Gripper + py::class_(m, "Gripper", R"pbdoc( + Interface for controlling a Franka Hand gripper. + )pbdoc") + .def(py::init(), R"pbdoc( + Connect to gripper. + + Establishes connection to the Franka Hand gripper at the specified address. + + @param franka_address IP address or hostname of the robot + :raises NetworkException: if the connection cannot be established + )pbdoc") + .def("homing", &franka::Gripper::homing, R"pbdoc( + Perform homing to find maximum width. + + Homing moves the gripper fingers to the maximum width to calibrate the position. + This should be performed after powering on the gripper or when the gripper state is uncertain. + + @return True if successful + :raises CommandException: if the command fails + :raises NetworkException: if the connection is lost + )pbdoc") + .def("grasp", &franka::Gripper::grasp, py::arg("width"), py::arg("speed"), py::arg("force"), + py::arg("epsilon_inner") = 0.005, py::arg("epsilon_outer") = 0.005, R"pbdoc( + Grasp an object. + + The gripper closes at the specified speed and force until an object is detected + or the target width is reached. The grasp is considered successfulsetCurrentThreadToHighestSchedulerPriority if the final + width is within the specified tolerances. + + @param width Target grasp width [m] + @param speed Closing speed [m/s] + @param force Grasping force [N] + @param epsilon_inner Inner tolerance for grasp check [m] (default: 0.005) + @param epsilon_outer Outer tolerance for grasp check [m] (default: 0.005) + @return True if grasp successful + :raises CommandException: if the command fails + :raises NetworkException: if the connection is lost + )pbdoc") + .def("read_once", &franka::Gripper::readOnce, R"pbdoc( + Read current gripper state once. + + Queries the gripper for its current state including width, temperature, + and grasp status. + + @return Current gripper state + :raises NetworkException: if the connection is lost + )pbdoc") + .def("stop", &franka::Gripper::stop, R"pbdoc( + Stop current gripper motion. + + Stops any ongoing gripper movement immediately. + + @return True if successful + :raises CommandException: if the command fails + :raises NetworkException: if the connection is lost + )pbdoc") + .def("move", &franka::Gripper::move, R"pbdoc( + Move gripper fingers to a specific width. + + Moves the gripper to the specified width at the given speed. Use this for + opening and closing the gripper without force control. + + @param width Target width [m] + @param speed Movement speed [m/s] + @return True if successful + :raises CommandException: if the command fails + :raises NetworkException: if the connection is lost + )pbdoc") + .def("server_version", &franka::Gripper::serverVersion, R"pbdoc( + Get gripper server version. + + @return Server version information + )pbdoc"); +} + +} // namespace pylibfranka \ No newline at end of file diff --git a/pylibfranka/src/pylibfranka.cpp b/pylibfranka/src/pylibfranka.cpp index 1ad1af8c..610056de 100644 --- a/pylibfranka/src/pylibfranka.cpp +++ b/pylibfranka/src/pylibfranka.cpp @@ -18,15 +18,18 @@ #include #include #include -#include -#include -#include #include #include -#include +#include +#include +// common #include +// Pylibfranka +#include +#include + namespace py = pybind11; namespace { @@ -51,7 +54,6 @@ auto convertControllerMode(franka::ControllerMode mode) namespace pylibfranka { - PYBIND11_MODULE(_pylibfranka, m) { m.doc() = "Python bindings for Franka Robotics Robot Control Library"; @@ -495,7 +497,8 @@ PYBIND11_MODULE(_pylibfranka, m) { "End effector twist [m/s, rad/s] (6,)") .def_readwrite("motion_finished", &franka::CartesianVelocities::motion_finished, "Set to True to finish motion"); - // Bind PyRobot + + // Bind Robot py::class_>(m, "Robot", R"pbdoc( Main interface for controlling a Franka robot. @@ -514,45 +517,57 @@ PYBIND11_MODULE(_pylibfranka, m) { @return ActiveControlBase interface for sending torque commands )pbdoc") - .def("start_joint_position_control", [](franka::Robot& self, franka::ControllerMode mode) - {return self.startJointPositionControl(convertControllerMode(mode));}, R"pbdoc( + .def( + "start_joint_position_control", + [](franka::Robot& self, franka::ControllerMode mode) { + return self.startJointPositionControl(convertControllerMode(mode)); + }, + R"pbdoc( Start joint position control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending position commands )pbdoc") - .def("start_joint_velocity_control", [](franka::Robot& self, franka::ControllerMode mode) - {return self.startJointVelocityControl(convertControllerMode(mode));}, R"pbdoc( + .def( + "start_joint_velocity_control", + [](franka::Robot& self, franka::ControllerMode mode) { + return self.startJointVelocityControl(convertControllerMode(mode)); + }, + R"pbdoc( Start joint velocity control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending velocity commands )pbdoc") - .def("start_cartesian_pose_control", [](franka::Robot& self, franka::ControllerMode mode) - {return self.startCartesianPoseControl(convertControllerMode(mode));}, R"pbdoc( + .def( + "start_cartesian_pose_control", + [](franka::Robot& self, franka::ControllerMode mode) { + return self.startCartesianPoseControl(convertControllerMode(mode)); + }, + R"pbdoc( Start Cartesian pose control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending Cartesian pose commands )pbdoc") - .def("start_cartesian_velocity_control", [](franka::Robot& self, franka::ControllerMode mode) - {return self.startCartesianVelocityControl(convertControllerMode(mode));}, R"pbdoc( + .def( + "start_cartesian_velocity_control", + [](franka::Robot& self, franka::ControllerMode mode) { + return self.startCartesianVelocityControl(convertControllerMode(mode)); + }, + R"pbdoc( Start Cartesian velocity control mode. @param control_type Controller mode (JointImpedance or CartesianImpedance) @return ActiveControlBase interface for sending Cartesian velocity commands )pbdoc") - .def("set_collision_behavior", py::overload_cast< - const std::array&, - const std::array&, - const std::array&, - const std::array& - >(&franka::Robot::setCollisionBehavior), - py::arg("lower_torque_thresholds"), - py::arg("upper_torque_thresholds"), - py::arg("lower_force_thresholds"), - py::arg("upper_force_thresholds"), - R"pbdoc( + .def("set_collision_behavior", + py::overload_cast&, const std::array&, + const std::array&, const std::array&>( + &franka::Robot::setCollisionBehavior), + py::arg("lower_torque_thresholds"), py::arg("upper_torque_thresholds"), + py::arg("lower_force_thresholds"), py::arg("upper_force_thresholds"), + R"pbdoc( Configure collision detection thresholds. @param lower_torque_thresholds Lower torque thresholds [Nm] (7,) @@ -604,142 +619,11 @@ PYBIND11_MODULE(_pylibfranka, m) { Stop currently running motion. )pbdoc"); - // Bind franka::GripperState - py::class_(m, "GripperState", R"pbdoc( - Current state of the Franka Hand gripper. - )pbdoc") - .def_readwrite("width", &franka::GripperState::width, "Current gripper width [m]") - .def_readwrite("max_width", &franka::GripperState::max_width, "Maximum gripper width [m]") - .def_readwrite("is_grasped", &franka::GripperState::is_grasped, "True if object is grasped") - .def_readwrite("temperature", &franka::GripperState::temperature, "Gripper temperature [°C]") - .def_readwrite("time", &franka::GripperState::time, "Timestamp"); - - // Bind franka::Gripper - py::class_(m, "Gripper", R"pbdoc( - Interface for controlling a Franka Hand gripper. - )pbdoc") - .def(py::init(), R"pbdoc( - Connect to gripper. - - Establishes connection to the Franka Hand gripper at the specified address. - - @param franka_address IP address or hostname of the robot - :raises NetworkException: if the connection cannot be established - )pbdoc") - .def("homing", &franka::Gripper::homing, R"pbdoc( - Perform homing to find maximum width. - - Homing moves the gripper fingers to the maximum width to calibrate the position. - This should be performed after powering on the gripper or when the gripper state is uncertain. - - @return True if successful - :raises CommandException: if the command fails - :raises NetworkException: if the connection is lost - )pbdoc") - .def("grasp", &franka::Gripper::grasp, py::arg("width"), py::arg("speed"), py::arg("force"), - py::arg("epsilon_inner") = 0.005, py::arg("epsilon_outer") = 0.005, R"pbdoc( - Grasp an object. - - The gripper closes at the specified speed and force until an object is detected - or the target width is reached. The grasp is considered successfulsetCurrentThreadToHighestSchedulerPriority if the final - width is within the specified tolerances. - - @param width Target grasp width [m] - @param speed Closing speed [m/s] - @param force Grasping force [N] - @param epsilon_inner Inner tolerance for grasp check [m] (default: 0.005) - @param epsilon_outer Outer tolerance for grasp check [m] (default: 0.005) - @return True if grasp successful - :raises CommandException: if the command fails - :raises NetworkException: if the connection is lost - )pbdoc") - .def("read_once", &franka::Gripper::readOnce, R"pbdoc( - Read current gripper state once. - - Queries the gripper for its current state including width, temperature, - and grasp status. - - @return Current gripper state - :raises NetworkException: if the connection is lost - )pbdoc") - .def("stop", &franka::Gripper::stop, R"pbdoc( - Stop current gripper motion. - - Stops any ongoing gripper movement immediately. - - @return True if successful - :raises CommandException: if the command fails - :raises NetworkException: if the connection is lost - )pbdoc") - .def("move", &franka::Gripper::move, R"pbdoc( - Move gripper fingers to a specific width. - - Moves the gripper to the specified width at the given speed. Use this for - opening and closing the gripper without force control. - - @param width Target width [m] - @param speed Movement speed [m/s] - @return True if successful - :raises CommandException: if the command fails - :raises NetworkException: if the connection is lost - )pbdoc") - .def("server_version", &franka::Gripper::serverVersion, R"pbdoc( - Get gripper server version. - - @return Server version information - )pbdoc"); + // Gripper bindings + bind_gripper(m); - auto async_position_control_handler = py::class_>(m, "AsyncPositionControlHandler", R"pbdoc( - Handler for asynchronous joint position control - )pbdoc") - .def_static("configure", &franka::AsyncPositionControlHandler::configure) - .def("set_joint_position_target", &franka::AsyncPositionControlHandler::setJointPositionTarget) - .def("get_target_feedback", &franka::AsyncPositionControlHandler::getTargetFeedback, py::arg("robot_state") = std::nullopt) - .def("stop_control", &franka::AsyncPositionControlHandler::stopControl); - - py::class_(async_position_control_handler, "Configuration") - .def(py::init([](const std::vector& maximum_joint_velocities, double goal_tolerance) - { - if (maximum_joint_velocities.size() != franka::Robot::kNumJoints) { - throw std::invalid_argument("joint_velocities must have exactly 7 values"); - } - franka::AsyncPositionControlHandler::Configuration config; - - std::array arr{}; - std::copy(maximum_joint_velocities.begin(), - maximum_joint_velocities.end(), - arr.begin()); - - config.maximum_joint_velocities = arr; - config.goal_tolerance = goal_tolerance; - return config; - }), py::arg("maximum_joint_velocities"), py::arg("goal_tolerance")) - .def_readwrite("maximum_joint_velocities", &franka::AsyncPositionControlHandler::Configuration::maximum_joint_velocities) - .def_readwrite("goal_tolerance", &franka::AsyncPositionControlHandler::Configuration::goal_tolerance); - - py::class_(async_position_control_handler, "ConfigurationResult") - .def_readwrite("handler", &franka::AsyncPositionControlHandler::ConfigurationResult::handler) - .def_readwrite("error_message", &franka::AsyncPositionControlHandler::ConfigurationResult::error_message); - - py::class_(async_position_control_handler, "JointPositionTarget") - .def(py::init([](const std::vector& joints) { - if (joints.size() != franka::Robot::kNumJoints) { - throw std::invalid_argument("joint_positions must have exactly 7 values"); - } - franka::AsyncPositionControlHandler::JointPositionTarget tgt; - std::copy(joints.begin(), joints.end(), tgt.joint_positions.begin()); - return tgt; - }), py::arg("joint_positions")) - .def_readwrite("joint_positions", &franka::AsyncPositionControlHandler::JointPositionTarget::joint_positions); - - py::class_(async_position_control_handler, "CommandResult") - .def_readwrite("motion_uuid", &franka::AsyncPositionControlHandler::CommandResult::motion_uuid) - .def_readwrite("was_successful", &franka::AsyncPositionControlHandler::CommandResult::was_successful) - .def_readwrite("error_message", &franka::AsyncPositionControlHandler::CommandResult::error_message); - - py::class_(async_position_control_handler, "TargetFeedback") - .def_readwrite("status", &franka::AsyncPositionControlHandler::TargetFeedback::status) - .def_readwrite("error_message", &franka::AsyncPositionControlHandler::TargetFeedback::error_message); + // Async Control bindings + bind_async_control(m); } } // namespace pylibfranka diff --git a/src/robot.cpp b/src/robot.cpp index ba735097..3a31c4e2 100644 --- a/src/robot.cpp +++ b/src/robot.cpp @@ -278,7 +278,7 @@ std::unique_ptr Robot::startControl( template auto Robot::startAsyncControl( const research_interface::robot::Move::ControllerMode& controller_type, - const std::optional>& maximum_velocities) + const std::optional>& maximum_velocities) -> std::unique_ptr { std::unique_lock control_lock(control_mutex_, std::try_to_lock); assertOwningLock(control_lock); @@ -315,7 +315,7 @@ std::unique_ptr Robot::startJointPositionControl( std::unique_ptr Robot::startAsyncJointPositionControl( const research_interface::robot::Move::ControllerMode& control_type, - const std::optional>& maximum_velocities) { + const std::optional>& maximum_velocities) { return startAsyncControl(control_type, maximum_velocities); } diff --git a/src/robot_control.h b/src/robot_control.h index ce3ae420..1c728ddf 100644 --- a/src/robot_control.h +++ b/src/robot_control.h @@ -39,7 +39,7 @@ class RobotControl { const research_interface::robot::Move::Deviation& maximum_path_deviation, const research_interface::robot::Move::Deviation& maximum_goal_pose_deviation, bool use_async_motion_generator, - const std::optional>& maximum_velocities) = 0; + const std::optional>& maximum_velocities) = 0; /** * Receives the new motion/control command to send to the robot while updating the robot state. diff --git a/src/robot_impl.cpp b/src/robot_impl.cpp index 7c8a9a67..646f8506 100644 --- a/src/robot_impl.cpp +++ b/src/robot_impl.cpp @@ -256,7 +256,7 @@ uint32_t Robot::Impl::startMotion( const research_interface::robot::Move::Deviation& maximum_path_deviation, const research_interface::robot::Move::Deviation& maximum_goal_pose_deviation, bool use_async_motion_generator, - const std::optional>& maximum_velocities) { + const std::optional>& maximum_velocities) { if (motionGeneratorRunning() || controllerRunning()) { throw ControlException("libfranka robot: Attempted to start multiple motions!"); } diff --git a/src/robot_impl.h b/src/robot_impl.h index cbdfd072..9ddb3593 100644 --- a/src/robot_impl.h +++ b/src/robot_impl.h @@ -54,7 +54,7 @@ class Robot::Impl : public RobotControl { const research_interface::robot::Move::Deviation& maximum_path_deviation, const research_interface::robot::Move::Deviation& maximum_goal_pose_deviation, bool use_async_motion_generator, - const std::optional>& maximum_velocities) + const std::optional>& maximum_velocities) -> uint32_t override; auto cancelMotion(uint32_t motion_id) -> void override; auto finishMotion( From fc6c6512a7fdc6cebb409d852da3ebd05d18844b Mon Sep 17 00:00:00 2001 From: Andreas Kuhner Date: Fri, 16 Jan 2026 08:46:54 +0100 Subject: [PATCH 27/38] bump: Bump version to 0.20.0 --- CHANGELOG.md | 2 +- CMakeLists.txt | 2 +- package.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 238953d3..d70e8942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to libfranka in this file. -## [Unreleased] +## [0.20.0] ### libfranka - C++ #### Changed - Hotfix to avoid torques discontinuity false positives due to robot state float precision change. diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e15f8a1..510accf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(libfranka_VERSION 0.19.0) +set(libfranka_VERSION 0.20.0) project(libfranka VERSION ${libfranka_VERSION} diff --git a/package.xml b/package.xml index ea1e71ff..f71ca8e9 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> libfranka - 0.19.0 + 0.20.0 libfranka is a C++ library for Franka Robotics research robots Franka Robotics GmbH Apache 2.0 From 123846defda759413fd09fead9b39dd6be1244d1 Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Fri, 16 Jan 2026 10:16:03 +0000 Subject: [PATCH 28/38] adding tinyxml2 as build_depend --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index f71ca8e9..fd3ca364 100644 --- a/package.xml +++ b/package.xml @@ -20,6 +20,7 @@ fmt libpoco-dev pinocchio + tinyxml2 fmt libpoco-dev From 270d7cf033125134734d4a3e2d5e9986584bba2f Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Fri, 16 Jan 2026 13:48:33 +0000 Subject: [PATCH 29/38] also exec_depend to be sure --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index fd3ca364..35365407 100644 --- a/package.xml +++ b/package.xml @@ -25,6 +25,7 @@ fmt libpoco-dev pinocchio + tinyxml2 doxygen graphviz From 11ea86d7fb8570bba427f77eae5b48f41e6d7e9a Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Fri, 16 Jan 2026 15:10:09 +0000 Subject: [PATCH 30/38] bump version to 0.20.1 --- CHANGELOG.md | 6 ++++++ CMakeLists.txt | 2 +- package.xml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d70e8942..3b6cd268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to libfranka in this file. +## [0.20.1] + +### libfranka - C++ + +- Fixed tinyxml2 dependency for ros users using `rosdep install` + ## [0.20.0] ### libfranka - C++ #### Changed diff --git a/CMakeLists.txt b/CMakeLists.txt index 510accf4..66171e40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(libfranka_VERSION 0.20.0) +set(libfranka_VERSION 0.20.1) project(libfranka VERSION ${libfranka_VERSION} diff --git a/package.xml b/package.xml index 35365407..f0737b2b 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> libfranka - 0.20.0 + 0.20.1 libfranka is a C++ library for Franka Robotics research robots Franka Robotics GmbH Apache 2.0 From 90df8907e944c8f4f8b69d218b5ba031f95b4ea1 Mon Sep 17 00:00:00 2001 From: Baris Yazici Date: Fri, 16 Jan 2026 16:27:25 +0100 Subject: [PATCH 31/38] feat: create vendored wheel for pylibfranka --- .ci/Dockerfile | 54 +++- .github/workflows/docker-image.yml | 110 ++++++++ .github/workflows/libfranka-build.yml | 42 +-- .github/workflows/pylibfranka-docs.yml | 29 +- .github/workflows/pylibfranka-wheels.yml | 319 ++++++++++++++++++++++ .gitignore | 1 + CHANGELOG.md | 8 +- CMakeLists.txt | 1 + Jenkinsfile | 148 +++++----- README.rst | 217 ++++++++++++++- examples/CMakeLists.txt | 2 +- include/franka/rate_limiting.h | 6 +- pylibfranka/.github/workflows/wheels.yml | 119 -------- pylibfranka/Jenkinsfile | 150 ---------- pylibfranka/README.md | 44 ++- pylibfranka/scripts/build_package.sh | 15 +- pylibfranka/scripts/get_version.sh | 18 -- pylibfranka/scripts/lint_code.sh | 19 -- pylibfranka/scripts/repair_wheels.sh | 80 +++++- pylibfranka/scripts/setup_dependencies.sh | 92 ++++++- pylibfranka/scripts/test_installation.sh | 163 +++++++++-- pyproject.toml | 37 ++- setup.py | 86 +++--- 23 files changed, 1199 insertions(+), 561 deletions(-) create mode 100644 .github/workflows/docker-image.yml create mode 100644 .github/workflows/pylibfranka-wheels.yml delete mode 100644 pylibfranka/.github/workflows/wheels.yml delete mode 100644 pylibfranka/Jenkinsfile delete mode 100755 pylibfranka/scripts/get_version.sh delete mode 100755 pylibfranka/scripts/lint_code.sh diff --git a/.ci/Dockerfile b/.ci/Dockerfile index 02f1ee7c..04abb19b 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -1,8 +1,10 @@ ARG UBUNTU_VERSION="20.04" +ARG PYTHON_VERSION="3.10" # Start with a base image FROM ubuntu:${UBUNTU_VERSION} ARG UBUNTU_VERSION +ARG PYTHON_VERSION # Set non-interactive mode ENV DEBIAN_FRONTEND=noninteractive @@ -35,7 +37,9 @@ RUN apt-get update && \ && ln -s $(which clang-tidy-18) /usr/bin/clang-tidy \ && ln -s $(which clang-format-18) /usr/bin/clang-format -# Install necessary packages +# Install necessary packages (without Python - we'll install specific version later) +# Note: pybind11-dev kept for devcontainer users (works with Ubuntu's default Python) +# For CI wheel builds, pip-installed pybind11 in venv is used for each Python version RUN apt-get update \ && apt-get install -y \ bash-completion \ @@ -51,15 +55,42 @@ RUN apt-get update \ libpoco-dev \ lsb-release \ pybind11-dev \ - python3-dev \ - python3-pip \ - python3-venv \ rename \ valgrind \ wget \ + software-properties-common \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* +# Install specific Python version +# On Ubuntu 22.04, Python 3.10 is the default system Python (packages are python3-dev, python3-venv) +# For other versions, use deadsnakes PPA (packages are python3.X-dev, python3.X-venv) +RUN set -ex && \ + # Check if Python version is already available as system default + SYSTEM_PYTHON_VERSION=$(python3 --version 2>/dev/null | grep -oP '\d+\.\d+' || echo "none") && \ + echo "System Python: $SYSTEM_PYTHON_VERSION, Requested: ${PYTHON_VERSION}" && \ + if [ "$SYSTEM_PYTHON_VERSION" = "${PYTHON_VERSION}" ]; then \ + echo "Python ${PYTHON_VERSION} is already the system default, installing dev packages..." && \ + apt-get update && \ + apt-get install -y python3-dev python3-venv && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/*; \ + else \ + echo "Installing Python ${PYTHON_VERSION} from deadsnakes PPA..." && \ + add-apt-repository ppa:deadsnakes/ppa -y && \ + apt-get update && \ + apt-get install -y \ + python${PYTHON_VERSION} \ + python${PYTHON_VERSION}-dev \ + python${PYTHON_VERSION}-venv && \ + (apt-get install -y python${PYTHON_VERSION}-distutils 2>/dev/null || true) && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* && \ + update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 && \ + update-alternatives --set python3 /usr/bin/python${PYTHON_VERSION}; \ + fi && \ + echo "Installed Python version:" && python3 --version + # Install CMake; use Kitware repo on Ubuntu 20.04 for >=3.22 RUN if [ "${UBUNTU_VERSION}" = "20.04" ]; then \ apt-get update && \ @@ -137,6 +168,7 @@ RUN git clone --depth 1 --recurse-submodules --shallow-submodules --branch v3.4. && rm -rf pinocchio # Install Python wheel building tools inside a virtualenv to satisfy PEP 668 +# Note: python3 -m venv uses ensurepip to bootstrap pip automatically RUN python3 -m venv /opt/venv && \ /opt/venv/bin/pip install --upgrade pip setuptools wheel && \ /opt/venv/bin/pip install \ @@ -147,19 +179,15 @@ RUN python3 -m venv /opt/venv && \ patchelf \ flake8 \ numpy \ - pybind11 && \ - /opt/venv/bin/python --version && /opt/venv/bin/pip --version && \ + pybind11 \ + cmake && \ + echo "Python version in venv:" && \ + /opt/venv/bin/python --version && \ + /opt/venv/bin/pip --version && \ chown -R $USER_UID:$USER_GID /opt/venv # Expose the virtualenv via env var for downstream scripts (e.g., Jenkinsfile) ENV VIRTUAL_ENV="/opt/venv" ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" -RUN apt-get update && \ - apt-get install -y wget gnupg lsb-release software-properties-common && \ - wget https://apt.llvm.org/llvm.sh && \ - chmod +x llvm.sh && \ - ./llvm.sh 18 && \ - apt-get update && \ - apt-get install -y clang-format-18 clang-tidy-18 USER $USERNAME diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 00000000..1c333d46 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,110 @@ +name: Build and Push Docker Images + +on: + push: + branches: [main, master] + paths: + - '.ci/Dockerfile' + - '.ci/*.patch' + - '.github/workflows/docker-image.yml' + workflow_dispatch: + inputs: + force_rebuild: + description: 'Force rebuild all images' + required: false + default: 'false' + type: boolean + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/pylibfranka-build + +jobs: + build-and-push: + name: Build ${{ matrix.name }} image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + strategy: + fail-fast: false + matrix: + include: + # Python wheel build images + - python-version: '3.9' + ubuntu-version: '20.04' # Ubuntu 20.04 for glibc 2.31 compatibility + name: 'Python 3.9' + tag-prefix: 'py' + - python-version: '3.10' + ubuntu-version: '22.04' + name: 'Python 3.10' + tag-prefix: 'py' + - python-version: '3.11' + ubuntu-version: '22.04' + name: 'Python 3.11' + tag-prefix: 'py' + - python-version: '3.12' + ubuntu-version: '22.04' + name: 'Python 3.12' + tag-prefix: 'py' + # Debian package build images (Ubuntu version specific) + - python-version: '3.8' + ubuntu-version: '20.04' + name: 'Ubuntu 20.04' + tag-prefix: 'ubuntu' + - python-version: '3.10' + ubuntu-version: '22.04' + name: 'Ubuntu 22.04' + tag-prefix: 'ubuntu' + - python-version: '3.12' + ubuntu-version: '24.04' + name: 'Ubuntu 24.04' + tag-prefix: 'ubuntu' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ matrix.tag-prefix }}${{ matrix.tag-prefix == 'py' && matrix.python-version || matrix.ubuntu-version }} + type=sha,prefix=${{ matrix.tag-prefix }}${{ matrix.tag-prefix == 'py' && matrix.python-version || matrix.ubuntu-version }}- + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: .ci/ + file: .ci/Dockerfile + build-args: | + UBUNTU_VERSION=${{ matrix.ubuntu-version }} + PYTHON_VERSION=${{ matrix.python-version }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=docker-${{ matrix.tag-prefix }}${{ matrix.tag-prefix == 'py' && matrix.python-version || matrix.ubuntu-version }} + cache-to: type=gha,mode=max,scope=docker-${{ matrix.tag-prefix }}${{ matrix.tag-prefix == 'py' && matrix.python-version || matrix.ubuntu-version }} + + - name: Verify pushed image + env: + IMAGE_TAG: ${{ matrix.tag-prefix }}${{ matrix.tag-prefix == 'py' && matrix.python-version || matrix.ubuntu-version }} + run: | + echo "Verifying image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG}" + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG} + docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG} python3 --version diff --git a/.github/workflows/libfranka-build.yml b/.github/workflows/libfranka-build.yml index 612da144..f54ca6ec 100644 --- a/.github/workflows/libfranka-build.yml +++ b/.github/workflows/libfranka-build.yml @@ -6,13 +6,26 @@ on: - '*' workflow_dispatch: +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/pylibfranka-build + jobs: build-deb: runs-on: ubuntu-latest + permissions: + contents: write + packages: read strategy: fail-fast: false matrix: - ubuntu_version: ["20.04", "22.04", "24.04"] + include: + - ubuntu_version: "20.04" + python_version: "3.8" + - ubuntu_version: "22.04" + python_version: "3.10" + - ubuntu_version: "24.04" + python_version: "3.12" steps: - name: Checkout repository @@ -21,21 +34,18 @@ jobs: submodules: 'recursive' fetch-depth: 0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build Docker image - uses: docker/build-push-action@v4 + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 with: - context: .ci/ - file: .ci/Dockerfile - build-args: | - UBUNTU_VERSION=${{ matrix.ubuntu_version }} - tags: libfranka-build:${{ matrix.ubuntu_version }} - push: false - load: true - cache-from: type=gha,scope=${{ matrix.ubuntu_version }} - cache-to: type=gha,mode=max,scope=${{ matrix.ubuntu_version }} + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pull pre-built Docker image + run: | + echo "Pulling pre-built image for Ubuntu ${{ matrix.ubuntu_version }}..." + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:ubuntu${{ matrix.ubuntu_version }} + docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:ubuntu${{ matrix.ubuntu_version }} libfranka-build:${{ matrix.ubuntu_version }} - name: Build and package in container uses: addnab/docker-run-action@v3 @@ -44,7 +54,7 @@ jobs: options: -v ${{ github.workspace }}:/workspaces run: | cd /workspaces - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -B build -S . + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -B build -S . cmake --build build -- -j$(nproc) cd build cpack -G DEB diff --git a/.github/workflows/pylibfranka-docs.yml b/.github/workflows/pylibfranka-docs.yml index b90636ca..1b40cc0a 100644 --- a/.github/workflows/pylibfranka-docs.yml +++ b/.github/workflows/pylibfranka-docs.yml @@ -5,9 +5,16 @@ on: tags: - '*' +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/pylibfranka-build + jobs: sphinx-docs: runs-on: ubuntu-latest + permissions: + contents: write + packages: read steps: - name: Checkout repository @@ -16,16 +23,18 @@ jobs: submodules: 'recursive' fetch-depth: 0 # Fetch all history including tags for version detection - - name: Build Docker Image - uses: docker/build-push-action@v4 + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 with: - context: .ci/ - file: .ci/Dockerfile - tags: pylibfranka:latest - build-args: | - UBUNTU_VERSION=20.04 - push: false - load: true + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pull pre-built Docker image + run: | + echo "Pulling pre-built image..." + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:py3.10 + docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:py3.10 pylibfranka:latest - name: Setup Dependencies uses: addnab/docker-run-action@v3 @@ -54,7 +63,7 @@ jobs: cd pylibfranka/docs # Install Sphinx requirements - pip3 install -r requirements.txt --user + pip3 install -r requirements.txt # Get version from installed package (synced from CMakeLists.txt) VERSION=$(python3 -c "import pylibfranka; print(pylibfranka.__version__)") diff --git a/.github/workflows/pylibfranka-wheels.yml b/.github/workflows/pylibfranka-wheels.yml new file mode 100644 index 00000000..1bf0cb17 --- /dev/null +++ b/.github/workflows/pylibfranka-wheels.yml @@ -0,0 +1,319 @@ +name: Build and Publish pylibfranka Wheels + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' # Also trigger on tags like 0.19.0 + pull_request: + branches: ['main', 'master'] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/pylibfranka-build + +jobs: + build_wheels: + name: Build wheels for Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + permissions: + contents: write + packages: read + strategy: + fail-fast: false + matrix: + include: + - python-version: '3.9' + python-tag: 'cp39' + ubuntu-version: '20.04' + manylinux-tag: 'manylinux_2_31_x86_64' + - python-version: '3.10' + python-tag: 'cp310' + ubuntu-version: '22.04' + manylinux-tag: 'manylinux_2_34_x86_64' + - python-version: '3.11' + python-tag: 'cp311' + ubuntu-version: '22.04' + manylinux-tag: 'manylinux_2_34_x86_64' + - python-version: '3.12' + python-tag: 'cp312' + ubuntu-version: '22.04' + manylinux-tag: 'manylinux_2_34_x86_64' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 0 + + - name: Get Package Version + id: get_version + run: | + VERSION=$(grep -oP 'set\(libfranka_VERSION\s+\K[\d.]+' CMakeLists.txt) + if [ -z "$VERSION" ]; then + echo "Error: Could not extract version from CMakeLists.txt" + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Package version: $VERSION" + shell: bash + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pull pre-built Docker image + run: | + echo "Pulling pre-built image for Python ${{ matrix.python-version }}..." + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:py${{ matrix.python-version }} + docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:py${{ matrix.python-version }} pylibfranka-build:py${{ matrix.python-version }} + + - name: Build Wheel for Python ${{ matrix.python-version }} + uses: addnab/docker-run-action@v3 + with: + image: pylibfranka-build:py${{ matrix.python-version }} + options: -v ${{ github.workspace }}:/workspace + run: | + cd /workspace + + # Verify Python version + echo "Python version:" + python3 --version + + # Clean previous builds + rm -rf build dist *.egg-info + + # Build the wheel (version is auto-extracted from CMakeLists.txt by setup.py) + echo "Building wheel for Python ${{ matrix.python-version }}..." + python3 -m build --wheel + + # Create wheelhouse directory + mkdir -p wheelhouse + + echo "Built wheel:" + ls -la dist/*.whl + + - name: Repair Wheels with Auditwheel + uses: addnab/docker-run-action@v3 + with: + image: pylibfranka-build:py${{ matrix.python-version }} + options: -v ${{ github.workspace }}:/workspace + run: | + cd /workspace + mkdir -p wheelhouse + + echo "Repairing wheels with auditwheel..." + for whl in dist/*.whl; do + if [ -f "$whl" ]; then + echo "Repairing: $whl" + echo "Target platform: ${{ matrix.manylinux-tag }}" + # Use the manylinux tag from matrix for this Python version + auditwheel repair "$whl" -w wheelhouse/ --plat ${{ matrix.manylinux-tag }} || \ + auditwheel repair "$whl" -w wheelhouse/ || \ + cp "$whl" wheelhouse/ + fi + done + + echo "Final wheels in wheelhouse:" + ls -la wheelhouse/ + + - name: Test Installation + uses: addnab/docker-run-action@v3 + with: + image: python:${{ matrix.python-version }}-slim + options: -v ${{ github.workspace }}:/workspace + run: | + # Install the wheel from wheelhouse + pip install /workspace/wheelhouse/*.whl + + # IMPORTANT: Change to /tmp to avoid importing from local pylibfranka folder + cd /tmp + + # Test import and version + python -c " + import pylibfranka + print('pylibfranka imported successfully') + print(f'Version: {pylibfranka.__version__}') + + # Verify key classes are available + from pylibfranka import Robot, Gripper, Model, RobotState + print('All core classes imported successfully') + + # Test creating objects + from pylibfranka import JointPositions, JointVelocities, Torques + jp = JointPositions([0.0] * 7) + print(f'JointPositions: {jp.q}') + print('All tests passed!') + " + + - name: Upload Wheel Artifact + uses: actions/upload-artifact@v4 + with: + name: wheel-python${{ matrix.python-version }} + path: wheelhouse/*.whl + retention-days: 30 + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build setuptools wheel + + - name: Build source distribution + # Version is auto-extracted from CMakeLists.txt by setup.py + run: python -m build --sdist + + - name: Upload sdist artifact + uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + retention-days: 30 + + release: + name: Create GitHub Release + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get release info + id: get_release_info + run: | + TAG=${GITHUB_REF#refs/tags/} + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "version=$TAG" >> $GITHUB_OUTPUT + shell: bash + + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + path: dist/ + pattern: wheel-* + merge-multiple: true + + - name: Download sdist artifact + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist/ + + - name: List distributions + run: | + echo "Distributions to upload:" + ls -la dist/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.get_release_info.outputs.tag }} + name: pylibfranka ${{ steps.get_release_info.outputs.version }} + draft: false + prerelease: false + files: dist/* + body: | + # pylibfranka ${{ steps.get_release_info.outputs.version }} + + Python bindings for libfranka, providing easy-to-use Python interfaces for controlling Franka robots. + + ## Installation + + ### From PyPI (Recommended) + ```bash + pip install pylibfranka==${{ steps.get_release_info.outputs.version }} + ``` + + ### From GitHub Release + Download the appropriate wheel for your Python version and install: + ```bash + pip install pylibfranka-${{ steps.get_release_info.outputs.version }}-cp310-cp310-manylinux_2_34_x86_64.whl + ``` + + ## Platform Compatibility + + | Ubuntu Version | Supported Python Versions | + |----------------|---------------------------| + | 20.04 (Focal) | Python 3.9 only | + | 22.04 (Jammy) | Python 3.9, 3.10, 3.11, 3.12 | + | 24.04 (Noble) | Python 3.9, 3.10, 3.11, 3.12 | + + **Note:** Ubuntu 20.04 users must use Python 3.9 due to glibc compatibility requirements. + + ## Available Wheels + - Python 3.9: `cp39-cp39-manylinux_2_31_x86_64.whl` (Ubuntu 20.04+) + - Python 3.10: `cp310-cp310-manylinux_2_34_x86_64.whl` (Ubuntu 22.04+) + - Python 3.11: `cp311-cp311-manylinux_2_34_x86_64.whl` (Ubuntu 22.04+) + - Python 3.12: `cp312-cp312-manylinux_2_34_x86_64.whl` (Ubuntu 22.04+) + - Source: `pylibfranka-${{ steps.get_release_info.outputs.version }}.tar.gz` + + ## Quick Start + ```python + import pylibfranka + print(f"pylibfranka version: {pylibfranka.__version__}") + + robot = pylibfranka.Robot("172.16.0.2") + state = robot.read_once() + ``` + + ## Documentation + - [Examples](https://github.com/frankarobotics/libfranka/tree/main/pylibfranka/examples) + - [API Documentation](https://frankarobotics.github.io/libfranka/pylibfranka/latest) + - [README](https://github.com/frankarobotics/libfranka/blob/main/pylibfranka/README.md) + + publish_pypi: + name: Publish to PyPI + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + # Publish automatically on tags + if: startsWith(github.ref, 'refs/tags/') + permissions: + id-token: write # Required for Trusted Publisher OIDC + + steps: + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + path: dist/ + pattern: wheel-* + merge-multiple: true + + - name: Download sdist artifact + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist/ + + - name: List distributions + run: | + echo "Distributions to upload:" + ls -la dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true + verbose: true diff --git a/.gitignore b/.gitignore index a943e170..46ea99e3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ __pycache__/ version_info.properties pylibfranka/docs/_build/ pylibfranka/_version.py +pylibfranka/VERSION diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b6cd268..de5bace6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to libfranka in this file. +## [Unreleased] +### pylibfranka - Python +#### Added +- Automated publishing to PyPI via GitHub Actions workflow. When a version tag is pushed, the workflow automatically builds wheels for Python 3.9, 3.10, 3.11, and 3.12, and publishes them to PyPI. Users can now install pylibfranka directly from PyPI using `pip install pylibfranka`. + ## [0.20.1] ### libfranka - C++ @@ -11,7 +16,8 @@ All notable changes to libfranka in this file. ## [0.20.0] ### libfranka - C++ #### Changed -- Hotfix to avoid torques discontinuity false positives due to robot state float precision change. +- Hotfix to avoid torques discontinuity false positives due to robot state float precision change (was again reverted). +- Breaking change: Fixed a wrong torque discontinuity trigger by reverting the float change within the robot state back to doubles. ### pylibfranka - Python #### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index 66171e40..bcaf0b44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,7 @@ target_link_libraries(franka PUBLIC fmt::fmt ) + ## Compile pylibfranka if (GENERATE_PYLIBFRANKA) add_subdirectory(pylibfranka) diff --git a/Jenkinsfile b/Jenkinsfile index 555143f0..ddd5a935 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,4 @@ -DISTRO_VERSIONS = ['20.04': 'focal', '22.04': 'jammy', '24.04': 'noble'] +def PYTHON_VERSION_BY_UBUNTU = ['20.04': '3.9', '22.04': '3.10', '24.04': '3.12'] pipeline { libraries { @@ -23,33 +23,38 @@ pipeline { stages { stage('Matrix') { matrix { + axes { + axis { + name 'UBUNTU_VERSION' + values '20.04', '22.04', '24.04' + } + } + environment { + DISTRO = "${['20.04': 'focal', '22.04': 'jammy', '24.04': 'noble'][UBUNTU_VERSION]}" + PYTHON_VERSION = "${PYTHON_VERSION_BY_UBUNTU[UBUNTU_VERSION]}" + } agent { dockerfile { dir ".ci" filename "Dockerfile" reuseNode false - additionalBuildArgs "--pull --build-arg UBUNTU_VERSION=${env.UBUNTU_VERSION} --tag libfranka:${env.UBUNTU_VERSION}" + additionalBuildArgs "--pull --build-arg UBUNTU_VERSION=${env.UBUNTU_VERSION} --build-arg PYTHON_VERSION=${PYTHON_VERSION_BY_UBUNTU[env.UBUNTU_VERSION]} --tag libfranka:${env.UBUNTU_VERSION}" args '--privileged ' + '--cap-add=SYS_PTRACE ' + '--security-opt seccomp=unconfined ' + '--shm-size=2g ' } } - axes { - axis { - name 'UBUNTU_VERSION' - values '20.04', '22.04', '24.04' - } - } stages { stage('Init Distro') { steps { script { - def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] - if (!distro) { + if (!env.DISTRO) { error "Unknown UBUNTU_VERSION=${env.UBUNTU_VERSION}" } - echo "Distro for ${env.UBUNTU_VERSION}: ${distro}" + if (!env.PYTHON_VERSION) { + error "Unknown PYTHON_VERSION for UBUNTU_VERSION=${env.UBUNTU_VERSION}" + } } } } @@ -86,39 +91,29 @@ pipeline { stages { stage('Build debug') { steps { - script { - def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] - dir("build-debug.${distro}") { - withEnv(["DISTRO=${distro}"]) { - sh ''' - rm -rf CMakeCache.txt CMakeFiles _deps || true - cmake -DCMAKE_BUILD_TYPE=Debug -DSTRICT=ON -DBUILD_COVERAGE=OFF \ - -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ - -DGENERATE_PYLIBFRANKA=ON .. - make -j$(nproc) - cmake --install . --prefix ../install-debug.${DISTRO} - ''' - } - } + dir("build-debug.${env.DISTRO}") { + sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true + cmake -DCMAKE_BUILD_TYPE=Debug -DSTRICT=ON -DBUILD_COVERAGE=OFF \ + -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DGENERATE_PYLIBFRANKA=ON .. + make -j$(nproc) + cmake --install . --prefix ../install-debug.${DISTRO} + ''' } } } stage('Build release') { steps { - script { - def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] - dir("build-release.${distro}") { - withEnv(["DISTRO=${distro}"]) { - sh ''' - rm -rf CMakeCache.txt CMakeFiles _deps || true - cmake -DCMAKE_BUILD_TYPE=Release -DSTRICT=ON -DBUILD_COVERAGE=OFF \ - -DBUILD_DOCUMENTATION=ON -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ - -DGENERATE_PYLIBFRANKA=ON .. - make -j$(nproc) - cmake --install . --prefix ../install-release.${DISTRO} - ''' - } - } + dir("build-release.${env.DISTRO}") { + sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true + cmake -DCMAKE_BUILD_TYPE=Release -DSTRICT=ON -DBUILD_COVERAGE=OFF \ + -DBUILD_DOCUMENTATION=ON -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DGENERATE_PYLIBFRANKA=ON .. + make -j$(nproc) + cmake --install . --prefix ../install-release.${DISTRO} + ''' } } } @@ -151,16 +146,14 @@ pipeline { } } steps { - script { - def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] - dir("build-coverage.${distro}") { - sh ''' - rm -rf CMakeCache.txt CMakeFiles _deps || true - cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_COVERAGE=ON \ - -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. - make -j$(nproc) - ''' - } + dir("build-coverage.${env.DISTRO}") { + sh ''' + rm -rf CMakeCache.txt CMakeFiles _deps || true + cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_COVERAGE=ON \ + -DBUILD_DOCUMENTATION=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. + make -j$(nproc) + ''' } } } @@ -168,30 +161,24 @@ pipeline { } stage('Lint') { steps { - script { - def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] - dir("build-lint.${distro}") { - catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh ''' - cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON .. - make check-tidy -j$(nproc) - ''' - } + dir("build-lint.${env.DISTRO}") { + catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { + sh ''' + cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DBUILD_TESTS=ON .. + make check-tidy -j$(nproc) + ''' } } } } stage('Format') { steps { - script { - def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] - dir("build-format.${distro}") { - catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh ''' - cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON .. - make check-format -j$(nproc) - ''' - } + dir("build-format.${env.DISTRO}") { + catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { + sh ''' + cmake -DBUILD_COVERAGE=OFF -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DBUILD_TESTS=ON .. + make check-format -j$(nproc) + ''' } } } @@ -203,21 +190,18 @@ pipeline { } } steps { - script { - def distro = DISTRO_VERSIONS[env.UBUNTU_VERSION] - dir("build-coverage.${distro}") { - catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { - sh ''' - cmake -DBUILD_COVERAGE=ON -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. - make coverage -j$(nproc) - ''' - publishHTML([allowMissing: false, - alwaysLinkToLastBuild: false, - keepAll: true, - reportDir: 'coverage', - reportFiles: 'index.html', - reportName: "Code Coverage (${distro})"]) - } + dir("build-coverage.${env.DISTRO}") { + catchError(buildResult: env.UNSTABLE, stageResult: env.UNSTABLE) { + sh ''' + cmake -DBUILD_COVERAGE=ON -DBUILD_DOCUMENTATION=OFF -DBUILD_EXAMPLES=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DBUILD_TESTS=ON .. + make coverage -j$(nproc) + ''' + publishHTML([allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: 'coverage', + reportFiles: 'index.html', + reportName: "Code Coverage (${env.DISTRO})"]) } } } diff --git a/README.rst b/README.rst index fede6fe0..d7cca5e5 100644 --- a/README.rst +++ b/README.rst @@ -42,7 +42,7 @@ Before using **libfranka**, ensure your system meets the following requirements: .. _installation-debian-package: 2. Installation from Debian Package (Recommended) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The easiest way to install **libfranka** is by using the pre-built Debian packages published on GitHub. @@ -260,7 +260,7 @@ Prerequisites sudo apt-get install -y cmake Remove Existing Installations -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash @@ -515,7 +515,6 @@ Pylibfranka is included in the libfranka Debian packages. For manual installatio Documentation ^^^^^^^^^^^^^ -- `Pylibfranka README `_ - `API Documentation `_ .. _development-information: @@ -580,6 +579,8 @@ Example: -DGENERATE_PYLIBFRANKA=ON \ .. +.. _troubleshooting: + Troubleshooting ~~~~~~~~~~~~~~~ @@ -591,3 +592,213 @@ License ------- ``libfranka`` is licensed under the `Apache 2.0 license `_. + +pylibfranka Installation and Usage Guide +----------------------------------------- + +This document provides comprehensive instructions for installing and using pylibfranka, a Python binding for libfranka that enables control of Franka Robotics robots. + +Table of Contents +~~~~~~~~~~~~~~~~~ + +- `Installation`_ +- `Examples`_ +- `Troubleshooting`_ + +.. _installation: + +Installation +~~~~~~~~~~~~ + +From PyPI (Recommended) +~~~~~~~~~~~~~~~~~~~~~~~ + +The easiest way to install pylibfranka is via pip. Pre-built wheels include all necessary dependencies. + +.. code-block:: bash + + pip install pylibfranka + +Platform Compatibility +~~~~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Ubuntu Version + - Supported Python Versions + * - 20.04 (Focal) + - Python 3.9 only + * - 22.04 (Jammy) + - Python 3.9, 3.10, 3.11, 3.12 + * - 24.04 (Noble) + - Python 3.9, 3.10, 3.11, 3.12 + +**Note:** Ubuntu 20.04 users must use Python 3.9 due to glibc compatibility requirements. + +From Source +~~~~~~~~~~~ + +If you need to build from source (e.g., for development or unsupported platforms): + +Prerequisites +~~~~~~~~~~~~~ + +- Python 3.9 or newer +- CMake 3.16 or newer +- C++ compiler with C++17 support +- Eigen3 development headers +- Poco development headers + +**Disclaimer: If you are using the provided devcontainer, you can skip the prerequisites installation as they are already included in the container.** + +Installing Prerequisites on Ubuntu/Debian +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + sudo apt-get update + sudo apt-get install -y build-essential cmake libeigen3-dev libpoco-dev python3-dev + +Build and Install +~~~~~~~~~~~~~~~~~ + +From the root folder, you can install `pylibfranka` using pip: + +.. code-block:: bash + + pip install . + + +This will install pylibfranka in your current Python environment. + +.. _examples: + +Examples +^^^^^^^^ + +pylibfranka comes with three example scripts that demonstrate how to use the library to control a Franka robot. + +Joint Position Example +~~~~~~~~~~~~~~~~~~~~~~ + +This example demonstrates how to use an external control loop with pylibfranka to move the robot joints. + +To run the example: + +.. code-block:: bash + + cd examples + python3 joint_position_example.py --ip + +Where `` is the IP address of your Franka robot. If not specified, it defaults to "localhost". + +The active control example: +- Sets collision behavior parameters +- Starts joint position control with CartesianImpedance controller mode +- Moves the robot using an external control loop +- Performs a simple motion of selected joints + +Print Robot State Example +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows how to read and display the complete state of the robot. + +To run the example: + +.. code-block:: bash + + cd examples + python3 print_robot_state.py --ip [--rate ] [--count ] + +Where: +- `--ip` is the robot's IP address (defaults to "localhost") +- `--rate` is the frequency at which to print the state in Hz (defaults to 0.5) +- `--count` is the number of state readings to print (defaults to 1, use 0 for continuous) + +The print robot state example: + +- Connects to the robot +- Reads the complete robot state +- Prints detailed information about: + + - Joint positions, velocities, and torques + - End effector pose and velocities + - External forces and torques + - Robot mode and error states + - Mass and inertia properties + +Joint Impedance Control Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example demonstrates how to implement a joint impedance controller that renders a spring-damper system to move the robot through a sequence of target joint configurations. + +To run the example: + +.. code-block:: bash + + cd examples + python3 joint_impedance_example.py --ip + +Where `--ip` is the robot's IP address (defaults to "localhost"). + +The joint impedance example: + +- Implements a minimum jerk trajectory generator for smooth joint motion +- Uses a spring-damper system for compliant control +- Moves through a sequence of predefined joint configurations: + + - Home position (slightly bent arm) + - Extended arm pointing forward + - Arm pointing to the right + - Arm pointing to the left + - Return to home position + +- Includes position holding with dwell time between movements +- Compensates for Coriolis effects + +And more control examples +~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are more control examples to discover. All of them can be executed in a similar way: + +.. code-block:: bash + + cd examples + python3 other_example.py --ip + +Gripper Control Example +~~~~~~~~~~~~~~~~~~~~~~~ + +This example demonstrates how to control the Franka gripper, including homing, grasping, and reading gripper state. + +To run the example: + +.. code-block:: bash + + cd examples + python3 move_gripper.py --robot_ip [--width ] [--homing <0|1>] [--speed ] [--force ] + +Where: +- `--robot_ip` is the robot's IP address (required) +- `--width` is the object width to grasp in meters (defaults to 0.005) +- `--homing` enables/disables gripper homing (0 or 1, defaults to 1) +- `--speed` is the gripper speed (defaults to 0.1) +- `--force` is the gripper force in N (defaults to 60) + +The gripper example: + +- Connects to the gripper +- Performs homing to estimate maximum grasping width +- Reads and displays gripper state including: + + - Current width + - Maximum width + - Grasp status + - Temperature + - Timestamp + +- Attempts to grasp an object with specified parameters +- Verifies successful grasping +- Releases the object diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5cf55e3f..270a6b00 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) project(libfranka-examples CXX) diff --git a/include/franka/rate_limiting.h b/include/franka/rate_limiting.h index b91a7ce1..189b7a7e 100644 --- a/include/franka/rate_limiting.h +++ b/include/franka/rate_limiting.h @@ -38,14 +38,12 @@ constexpr double kTolNumberPacketsLost = 0.0; */ constexpr double kFactorCartesianRotationPoseInterface = 0.99; -constexpr double kTorqueLimitEps = 3e-3; - /** * Maximum torque rate */ constexpr std::array kMaxTorqueRate{ - {1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, - 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps, 1000 - kTorqueLimitEps}}; + {1000 - kLimitEps, 1000 - kLimitEps, 1000 - kLimitEps, 1000 - kLimitEps, 1000 - kLimitEps, + 1000 - kLimitEps, 1000 - kLimitEps}}; /** * Maximum joint jerk */ diff --git a/pylibfranka/.github/workflows/wheels.yml b/pylibfranka/.github/workflows/wheels.yml deleted file mode 100644 index 91244e70..00000000 --- a/pylibfranka/.github/workflows/wheels.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: Build Wheels - -on: - push: - branches: [ '*' ] - tags: [ 'v*' ] # Trigger on version tags - pull_request: - branches: [ '*' ] - -jobs: - build_and_publish: - name: Build and Publish - runs-on: ubuntu-latest - permissions: - contents: write # Needed to create releases - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Get Package Version - id: get_package_version - run: ./scripts/get_version.sh - shell: bash - - - name: Build Docker Image - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - tags: pylibfranka:latest - push: false - load: true - - - name: Setup Dependencies - uses: addnab/docker-run-action@v3 - with: - image: pylibfranka:latest - options: -v ${{ github.workspace }}:/workspace - run: | - cd /workspace - ./scripts/setup_dependencies.sh - - - name: Build Package - uses: addnab/docker-run-action@v3 - with: - image: pylibfranka:latest - options: -v ${{ github.workspace }}:/workspace - run: | - cd /workspace - ./scripts/build_package.sh - - - name: Repair Wheels with Auditwheel - uses: addnab/docker-run-action@v3 - with: - image: pylibfranka:latest - options: -v ${{ github.workspace }}:/workspace - run: | - cd /workspace - ./scripts/repair_wheels.sh - - - name: Test Installation - uses: addnab/docker-run-action@v3 - with: - image: python:3.10-slim - options: -v ${{ github.workspace }}:/workspace - run: | - cd /workspace - /workspace/scripts/test_installation.sh "-manylinux_2_34_x86_64.whl" - - - name: Lint Code - uses: addnab/docker-run-action@v3 - with: - image: pylibfranka:latest - options: -v ${{ github.workspace }}:/workspace - run: | - cd /workspace - ./scripts/lint_code.sh - - - name: Upload wheels as artifacts - uses: actions/upload-artifact@v4 - with: - name: wheels - path: ./wheelhouse/*.whl - - - name: Get release info - if: startsWith(github.ref, 'refs/tags/') - id: get_release_info - run: | - # Extract tag name from GITHUB_REF (e.g., refs/tags/v1.0.0 -> v1.0.0) - TAG=${GITHUB_REF#refs/tags/} - echo "tag=$TAG" >> $GITHUB_OUTPUT - # Extract version without 'v' prefix (e.g., v1.0.0 -> 1.0.0) - VERSION=${TAG#v} - echo "version=$VERSION" >> $GITHUB_OUTPUT - shell: bash - - - name: Create GitHub Release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.get_release_info.outputs.tag }} - name: Release ${{ steps.get_release_info.outputs.tag }} - draft: false - prerelease: false - files: ./wheelhouse/*.whl - body: | - # pylibfranka ${{ steps.get_release_info.outputs.version }} - Python bindings for libfranka, providing easy-to-use Python interfaces for controlling Franka robots. - - ## Installation - ```bash - pip install pylibfranka==${{ steps.get_release_info.outputs.version }} - ``` - - ## Documentation - See the [README](https://github.com/frankaemika/pylibfranka#readme) for more information. diff --git a/pylibfranka/Jenkinsfile b/pylibfranka/Jenkinsfile deleted file mode 100644 index 12248d62..00000000 --- a/pylibfranka/Jenkinsfile +++ /dev/null @@ -1,150 +0,0 @@ -pipeline { - libraries { - lib('fe-pipeline-steps@1.5.0') - } - agent none - - triggers { - pollSCM('H/5 * * * *') - } - options { - buildDiscarder(logRotator(numToKeepStr: '30')) - parallelsAlwaysFailFast() - timeout(time: 1, unit: 'HOURS') - } - - stages { - stage('Prepare') { - agent { - dockerfile { - filename './Dockerfile' - label 'docker1' - reuseNode true - } - } - steps { - script { - notifyBitbucket() - env.VERSION = feDetermineVersionFromGit() - } - feSetupPip() - sh './scripts/setup_dependencies.sh' - } - } - - stage('Get Package Version') { - agent { - dockerfile { - filename './Dockerfile' - label 'docker1' - reuseNode true - } - } - steps { - sh './scripts/get_version.sh' - script { - def props = readProperties file: 'version_info.properties' - env.PACKAGE_VERSION = props.PACKAGE_VERSION - } - } - } - - stage('Build') { - agent { - dockerfile { - filename './Dockerfile' - label 'docker1' - reuseNode true - } - } - steps { - sh './scripts/build_package.sh' - stash includes: 'dist/*.whl', name: 'built-wheels' - } - } - - stage('Vendoring') { - agent { - dockerfile { - filename './Dockerfile' - label 'docker1' - reuseNode true - } - } - steps { - unstash 'built-wheels' - sh './scripts/repair_wheels.sh' - stash includes: 'wheelhouse/*.whl', name: 'wheels' - } - } - - stage('Test') { - agent { - docker { - image 'python:3.10-slim' - label 'docker1' - reuseNode true - args '-v $WORKSPACE:/workspace -e HOME=/workspace/.test_home --user root' - } - } - steps { - // Unstash the wheels from the build stage - unstash 'wheels' - catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') { - sh ''' - cd /workspace - # Ensure the HOME directory exists and is writable - mkdir -p $HOME - mkdir -p $HOME/.cache - - # Run the test script with manylinux wheel pattern - /workspace/scripts/test_installation.sh "-manylinux_2_34_x86_64.whl" - ''' - } - } - } - - stage('Analyze') { - parallel { - stage('Lint') { - agent { - dockerfile { - filename './Dockerfile' - label 'docker1' - reuseNode true - } - } - steps { - catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') { - sh './scripts/lint_code.sh' - } - } - } - } - } - - stage('Publish') { - agent { - dockerfile { - filename './Dockerfile' - label 'docker1' - reuseNode true - } - } - steps { - fePublishPip("wheelhouse/*.whl", "tools") - } - post { - success { - fePublishBuildInfo() - } - always { - cleanWs() - script { - notifyBitbucket() - } - } - } - } - } -} diff --git a/pylibfranka/README.md b/pylibfranka/README.md index a4d7c5f3..44bedfa8 100644 --- a/pylibfranka/README.md +++ b/pylibfranka/README.md @@ -3,9 +3,9 @@ This document provides comprehensive instructions for installing and using pylibfranka, a Python binding for libfranka that enables control of Franka Robotics robots. ## Table of Contents -- [Prerequisites](#prerequisites) - [Installation](#installation) - - [Installation from Source](#installation-from-source) + - [From PyPI (Recommended)](#from-pypi-recommended) + - [From Source](#from-source) - [Examples](#examples) - [Joint Position Example](#joint-position-example) - [Print Robot State Example](#print-robot-state-example) @@ -13,11 +13,33 @@ This document provides comprehensive instructions for installing and using pylib - [Gripper Control Example](#gripper-control-example) - [Troubleshooting](#troubleshooting) -## Prerequisites +## Installation -Before installing pylibfranka, ensure you have the following prerequisites: +### From PyPI (Recommended) -- Python 3.8 or newer +The easiest way to install pylibfranka is via pip. Pre-built wheels include all necessary dependencies. + +```bash +pip install pylibfranka +``` + +#### Platform Compatibility + +| Ubuntu Version | Supported Python Versions | +|----------------|---------------------------| +| 20.04 (Focal) | Python 3.9 only | +| 22.04 (Jammy) | Python 3.9, 3.10, 3.11, 3.12 | +| 24.04 (Noble) | Python 3.9, 3.10, 3.11, 3.12 | + +**Note:** Ubuntu 20.04 users must use Python 3.9 due to glibc compatibility requirements. + +### From Source + +If you need to build from source (e.g., for development or unsupported platforms): + +#### Prerequisites + +- Python 3.9 or newer - CMake 3.16 or newer - C++ compiler with C++17 support - Eigen3 development headers @@ -25,27 +47,21 @@ Before installing pylibfranka, ensure you have the following prerequisites: **Disclaimer: If you are using the provided devcontainer, you can skip the prerequisites installation as they are already included in the container.** -### Installing Prerequisites on Ubuntu/Debian +#### Installing Prerequisites on Ubuntu/Debian ```bash sudo apt-get update sudo apt-get install -y build-essential cmake libeigen3-dev libpoco-dev python3-dev ``` -#### Installing pylibfranka via PIP +#### Build and Install -From the root folder, you can install `pylibfranka` (therefore, NOT in the build folder) using pip: +From the root folder, you can install `pylibfranka` using pip: ```bash pip install . ``` -or - -```bash -pip3 install . -``` - This will install pylibfranka in your current Python environment. ## Examples diff --git a/pylibfranka/scripts/build_package.sh b/pylibfranka/scripts/build_package.sh index c1413046..515da60f 100755 --- a/pylibfranka/scripts/build_package.sh +++ b/pylibfranka/scripts/build_package.sh @@ -1,23 +1,22 @@ #!/bin/bash set -e -echo "Building package..." +echo "Building pylibfranka package..." -# Ensure user-level Python packages are in PATH -export PATH=$HOME/.local/bin:$PATH +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +cd "$PROJECT_ROOT" -# Set up CMake for pybind11 +export PATH=$HOME/.local/bin:$PATH export pybind11_DIR=/usr/lib/cmake/pybind11 -# Install the package in development mode echo "Installing package..." -pip install . +pip install . --no-build-isolation || pip install . -# Build the wheel echo "Building wheel..." python3 -m build --wheel -# Create wheelhouse directory mkdir -p wheelhouse echo "Package build complete!" diff --git a/pylibfranka/scripts/get_version.sh b/pylibfranka/scripts/get_version.sh deleted file mode 100755 index 43cbb25c..00000000 --- a/pylibfranka/scripts/get_version.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -e - -# Get Package Version from CMakeLists.txt (single source of truth) -echo "Getting package version from CMakeLists.txt..." -PACKAGE_VERSION=$(grep -oP 'set\(libfranka_VERSION\s+\K[\d.]+' CMakeLists.txt) - -if [ -z "$PACKAGE_VERSION" ]; then - echo "Error: Could not extract version from CMakeLists.txt" - exit 1 -fi - -echo "Package version: $PACKAGE_VERSION" - -# Export for use in pipelines -echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> version_info.properties -export PACKAGE_VERSION -echo "PACKAGE_VERSION=$PACKAGE_VERSION" diff --git a/pylibfranka/scripts/lint_code.sh b/pylibfranka/scripts/lint_code.sh deleted file mode 100755 index 20173249..00000000 --- a/pylibfranka/scripts/lint_code.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -e - -echo "Running code linting..." - -# Ensure user-level Python packages are in PATH -export PATH=$HOME/.local/bin:$PATH - -# Install flake8 if not already installed -if ! command -v flake8 &> /dev/null; then - echo "Installing flake8..." - python3 -m pip install --user flake8 -fi - -# Run linting (allow it to fail without stopping the build) -echo "Running flake8 on pylibfranka and examples..." -flake8 pylibfranka examples || echo "Linting issues found but continuing" - -echo "Linting complete!" diff --git a/pylibfranka/scripts/repair_wheels.sh b/pylibfranka/scripts/repair_wheels.sh index 92d08c16..5577483e 100755 --- a/pylibfranka/scripts/repair_wheels.sh +++ b/pylibfranka/scripts/repair_wheels.sh @@ -3,31 +3,85 @@ set -e echo "Repairing wheels with auditwheel..." +# Get script and project directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +cd "$PROJECT_ROOT" + # Ensure user-level Python packages are in PATH export PATH=$HOME/.local/bin:$PATH # Create wheelhouse directory if it doesn't exist mkdir -p wheelhouse -# Try different approaches to run auditwheel +# Target platform for manylinux wheels +PLATFORM="${1:-manylinux_2_17_x86_64}" + +# Function to run auditwheel +run_auditwheel() { + local wheel="$1" + local platform="$2" + + echo "Repairing: $wheel for $platform" + + # Try with specified platform first + if auditwheel repair "$wheel" -w wheelhouse/ --plat "$platform" 2>/dev/null; then + return 0 + fi + + # Fallback to auto-detection + echo "Trying auto-detection..." + if auditwheel repair "$wheel" -w wheelhouse/ 2>/dev/null; then + return 0 + fi + + # Final fallback: copy as-is + echo "Warning: auditwheel failed, copying wheel as-is" + cp "$wheel" wheelhouse/ + return 0 +} + +# Find auditwheel if command -v auditwheel &> /dev/null; then - echo "Using system auditwheel" - auditwheel repair dist/*.whl -w wheelhouse/ -elif [ -f $HOME/.local/bin/auditwheel ]; then - echo "Using user-local auditwheel" - $HOME/.local/bin/auditwheel repair dist/*.whl -w wheelhouse/ + AUDITWHEEL="auditwheel" +elif [ -f "$HOME/.local/bin/auditwheel" ]; then + AUDITWHEEL="$HOME/.local/bin/auditwheel" else - echo "Installing auditwheel and running repair" - python3 -m pip install --user auditwheel - $HOME/.local/bin/auditwheel repair dist/*.whl -w wheelhouse/ + echo "Installing auditwheel..." + PIP_USER_FLAG="" + if [ -z "$VIRTUAL_ENV" ]; then + PIP_USER_FLAG="--user" + fi + python3 -m pip install $PIP_USER_FLAG auditwheel patchelf + AUDITWHEEL="$HOME/.local/bin/auditwheel" fi -# If auditwheel fails, copy the wheel directly as fallback -if [ ! -f wheelhouse/*.whl ]; then - echo "auditwheel failed, copying wheel directly" - cp dist/*.whl wheelhouse/ +# Check for wheels in dist directory +if [ ! -d dist ] || [ -z "$(ls -A dist/*.whl 2>/dev/null)" ]; then + echo "Error: No wheels found in dist/ directory" + echo "Run ./scripts/build_package.sh first" + exit 1 fi +# Repair each wheel +for whl in dist/*.whl; do + if [ -f "$whl" ]; then + run_auditwheel "$whl" "$PLATFORM" + fi +done + # List the final wheels +echo "" echo "Final wheels in wheelhouse:" ls -la wheelhouse/ + +# Show wheel contents for verification +echo "" +echo "Wheel contents (showing bundled libraries):" +for whl in wheelhouse/*.whl; do + if [ -f "$whl" ]; then + echo "--- $whl ---" + unzip -l "$whl" | grep -E "\.so|\.libs" | head -20 || true + fi +done diff --git a/pylibfranka/scripts/setup_dependencies.sh b/pylibfranka/scripts/setup_dependencies.sh index d5319dcd..d6975577 100755 --- a/pylibfranka/scripts/setup_dependencies.sh +++ b/pylibfranka/scripts/setup_dependencies.sh @@ -1,29 +1,95 @@ #!/bin/bash set -e -echo "Setting up dependencies..." +echo "Setting up dependencies for pylibfranka..." -# Update package lists (allow failure) -sudo apt-get update || true +# Get script and project directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Update package lists (allow failure for non-root users) +echo "Updating package lists..." +sudo apt-get update 2>/dev/null || apt-get update 2>/dev/null || true # Install system dependencies -sudo apt-get install -y libeigen3-dev libpoco-dev pybind11-dev || true +echo "Installing system dependencies..." +sudo apt-get install -y \ + build-essential \ + cmake \ + libeigen3-dev \ + libpoco-dev \ + libfmt-dev \ + pybind11-dev \ + python3-dev \ + python3-pip \ + patchelf \ + 2>/dev/null || \ +apt-get install -y \ + build-essential \ + cmake \ + libeigen3-dev \ + libpoco-dev \ + libfmt-dev \ + pybind11-dev \ + python3-dev \ + python3-pip \ + patchelf \ + 2>/dev/null || true # Install Python dependencies echo "Installing Python dependencies..." -python3 -m pip install --user auditwheel setuptools build wheel patchelf pybind11 numpy cmake +# Use --user only if not in a virtualenv (virtualenv doesn't support --user) +PIP_USER_FLAG="" +if [ -z "$VIRTUAL_ENV" ]; then + PIP_USER_FLAG="--user" +fi +python3 -m pip install $PIP_USER_FLAG --upgrade \ + pip \ + setuptools \ + wheel \ + build \ + auditwheel \ + patchelf \ + pybind11 \ + numpy \ + cmake \ + twine -# Add user-level Python bin to PATH -export PATH=$HOME/.local/bin:$PATH +# Add user-level Python bin to PATH (only needed for --user installs) +export PATH="$HOME/.local/bin:$PATH" -# Verify auditwheel is available -if command -v auditwheel &> /dev/null; then - echo "auditwheel found at: $(which auditwheel)" -else - echo "WARNING: auditwheel not found in PATH" -fi +# Verify tools are available +echo "" +echo "Verifying installed tools..." + +check_tool() { + local tool="$1" + if command -v "$tool" &> /dev/null; then + echo "✓ $tool found at: $(which $tool)" + return 0 + elif [ -f "$HOME/.local/bin/$tool" ]; then + echo "✓ $tool found at: $HOME/.local/bin/$tool" + return 0 + else + echo "✗ $tool not found" + return 1 + fi +} + +check_tool cmake +check_tool python3 +check_tool pip +check_tool auditwheel +check_tool patchelf # Set up CMake for pybind11 export pybind11_DIR=/usr/lib/cmake/pybind11 +echo "" echo "Dependencies setup complete!" +echo "" +echo "To build the wheel, run:" +echo " cd $PROJECT_ROOT" +echo " ./pylibfranka/scripts/build_package.sh" +echo " ./pylibfranka/scripts/repair_wheels.sh" +echo " ./pylibfranka/scripts/test_installation.sh" \ No newline at end of file diff --git a/pylibfranka/scripts/test_installation.sh b/pylibfranka/scripts/test_installation.sh index f89dafbf..9e892c31 100755 --- a/pylibfranka/scripts/test_installation.sh +++ b/pylibfranka/scripts/test_installation.sh @@ -1,39 +1,164 @@ #!/bin/bash set -e +# Test pylibfranka wheel installation +# Usage: ./test_installation.sh [wheel_pattern] +# +# Examples: +# ./test_installation.sh # Install any wheel from wheelhouse/ +# ./test_installation.sh manylinux_2_17_x86_64.whl # Install specific platform wheel +# ./test_installation.sh cp310 # Install Python 3.10 wheel + # Accept wheel pattern as first argument, default to all wheels WHEEL_PATTERN=${1:-"*.whl"} +echo "===========================================" +echo "Testing pylibfranka installation" +echo "===========================================" +echo "" + +# Get script and project directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +WHEELHOUSE="$PROJECT_ROOT/wheelhouse" + +if [ ! -d "$WHEELHOUSE" ]; then + echo "Error: wheelhouse directory not found at $WHEELHOUSE" + exit 1 +fi + echo "Installing wheel with pattern: $WHEEL_PATTERN" +echo "" # Find wheels matching the pattern if [[ "$WHEEL_PATTERN" == "*.whl" ]]; then - # Install all wheels (default behavior) - pip install wheelhouse/*.whl + # Install first available wheel + MATCHING_WHEEL=$(ls -1 "$WHEELHOUSE"/*.whl 2>/dev/null | head -1) else # Find specific wheel matching pattern echo "Available wheels:" - ls -la wheelhouse/ + ls -la "$WHEELHOUSE"/*.whl + echo "" echo "Filtering for wheels matching: $WHEEL_PATTERN" - MATCHING_WHEEL=$(find wheelhouse/ -name "*${WHEEL_PATTERN}" | head -1) - if [ -z "$MATCHING_WHEEL" ]; then - echo "No wheel found matching pattern: $WHEEL_PATTERN" - exit 1 - fi - echo "Found wheel: $MATCHING_WHEEL" - pip install "$MATCHING_WHEEL" + MATCHING_WHEEL=$(find "$WHEELHOUSE" -name "*${WHEEL_PATTERN}*" -name "*.whl" | head -1) fi -# Test the installation +if [ -z "$MATCHING_WHEEL" ] || [ ! -f "$MATCHING_WHEEL" ]; then + echo "Error: No wheel found matching pattern: $WHEEL_PATTERN" + echo "Available wheels in $WHEELHOUSE:" + ls -la "$WHEELHOUSE"/*.whl 2>/dev/null || echo " (none)" + exit 1 +fi + +echo "Installing: $MATCHING_WHEEL" +pip install --force-reinstall "$MATCHING_WHEEL" + +echo "" +echo "===========================================" echo "Testing pylibfranka import..." -cd / -python -c " +echo "===========================================" + +# IMPORTANT: Change to a neutral directory to avoid importing from local pylibfranka folder +# The project has a pylibfranka/ folder which would shadow the installed package +cd /tmp +echo "Testing from directory: $(pwd)" +echo "" + +python3 << 'EOF' +import sys +import os + +print(f"Python version: {sys.version}") +print(f"Python executable: {sys.executable}") +print(f"Current directory: {os.getcwd()}") +print() + +# Test basic import +print("Testing basic import...") import pylibfranka -print('pylibfranka imported successfully') -try: - print(f'Version: {pylibfranka.__version__}') -except AttributeError: - print('No version attribute found') -" +print(f"✓ pylibfranka imported successfully") +print(f" Version: {pylibfranka.__version__}") +print(f" Location: {pylibfranka.__file__}") + +# Verify we're NOT importing from the local project folder +if "/workspace" in pylibfranka.__file__ or "/workspaces" in pylibfranka.__file__: + print(f"✗ ERROR: Importing from project folder instead of installed package!") + print(f" This means the test is not testing the wheel correctly.") + sys.exit(1) +print(f"✓ Confirmed: importing from installed package (not local folder)") +print() + +# Test core classes +print("Testing core classes...") +from pylibfranka import ( + Robot, + Gripper, + Model, + RobotState, + GripperState, + Duration, + Errors, + RobotMode, + ControllerMode, + RealtimeConfig, +) +print("✓ All core classes imported successfully") +print() + +# Test control types +print("Testing control types...") +from pylibfranka import ( + JointPositions, + JointVelocities, + CartesianPose, + CartesianVelocities, + Torques, +) +print("✓ All control types imported successfully") +print() + +# Test exceptions +print("Testing exceptions...") +from pylibfranka import ( + FrankaException, + CommandException, + ControlException, + NetworkException, + RealtimeException, + InvalidOperationException, +) +print("✓ All exception types imported successfully") +print() + +# Test creating objects (without connecting to robot) +print("Testing object creation...") +jp = JointPositions([0.0] * 7) +print(f"✓ JointPositions created: {jp.q}") + +jv = JointVelocities([0.0] * 7) +print(f"✓ JointVelocities created: {jv.dq}") + +t = Torques([0.0] * 7) +print(f"✓ Torques created: {t.tau_J}") + +# Duration class exists (constructor may vary by version) +print(f"✓ Duration class available: {Duration}") +print() + +# Verify version matches expected format +import re +if re.match(r'^\d+\.\d+\.\d+', pylibfranka.__version__): + print(f"✓ Version format valid: {pylibfranka.__version__}") +else: + print(f"✗ Warning: Unexpected version format: {pylibfranka.__version__}") + sys.exit(1) + +print() +print("=========================================") +print("All tests passed!") +print("=========================================") +EOF +echo "" echo "Installation test complete!" diff --git a/pyproject.toml b/pyproject.toml index 06ad92e1..80af29bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,28 +14,35 @@ build-backend = "setuptools.build_meta" [project] name = "pylibfranka" dynamic = ["version"] -description = "Python bindings for libfranka with automatic dependency bundling" +description = "Python bindings for libfranka - Control Franka robots with Python" +readme = "pylibfranka/README.md" authors = [{name = "Franka Robotics GmbH", email = "info@franka.de"}] license = {text = "Apache-2.0"} -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "numpy>=1.19.0", ] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", + "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.8", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Topic :: Scientific/Engineering :: Robotics", + "Programming Language :: Python :: 3.12", + "Programming Language :: C++", + "Topic :: Software Development :: Libraries :: Python Modules", ] [project.urls] Homepage = "https://github.com/frankarobotics/libfranka" +Documentation = "https://frankarobotics.github.io/libfranka/pylibfranka/latest" Repository = "https://github.com/frankarobotics/libfranka" Issues = "https://github.com/frankarobotics/libfranka/issues" +Changelog = "https://github.com/frankarobotics/libfranka/blob/main/CHANGELOG.md" [project.optional-dependencies] dev = [ @@ -53,37 +60,37 @@ packages = ["pylibfranka"] zip-safe = false [tool.setuptools.dynamic] -version = {attr = "pylibfranka._version.__version__"} +version = {file = "pylibfranka/VERSION"} [tool.setuptools.package-data] pylibfranka = ["*.so", "*.pyd", "_pylibfranka*"] [tool.cibuildwheel] -# Build only on modern Python versions -build = "cp38-* cp39-* cp310-* cp311-*" -skip = "pp* *-manylinux_i686" +# Build for Python 3.9 through 3.12 (3.8 EOL) +build = "cp39-* cp310-* cp311-* cp312-*" +skip = "pp* *-manylinux_i686 *-musllinux*" # Install system dependencies before building before-all = [ "yum install -y eigen3-devel poco-devel || true", "apt-get update && apt-get install -y libeigen3-dev libpoco-dev || true", - "pip install pybind11 cmake" + "pip install pybind11 cmake numpy" ] # Build requirements build-verbosity = 1 [tool.cibuildwheel.linux] -# Use manylinux2014 for better compatibility -manylinux-x86_64-image = "manylinux2014" -manylinux-aarch64-image = "manylinux2014" +# Use manylinux_2_17 for broader compatibility +manylinux-x86_64-image = "manylinux_2_17" +manylinux-aarch64-image = "manylinux_2_17" -# Let auditwheel bundle dependencies automatically -repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel} --lib-sdir ." +# Repair wheel to bundle shared libraries with unique hashes +repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel} --plat manylinux_2_17_x86_64" [tool.black] line-length = 100 -target-version = ["py38", "py39", "py310", "py311"] +target-version = ["py39", "py310", "py311", "py312"] [tool.isort] profile = "black" diff --git a/setup.py b/setup.py index 574232c3..71e6603d 100644 --- a/setup.py +++ b/setup.py @@ -7,56 +7,43 @@ from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +# Root directory of the project +ROOT_DIR = Path(__file__).parent + def get_version(): """Extract version from CMakeLists.txt.""" - cmake_file = Path(__file__).parent / "CMakeLists.txt" + cmake_file = ROOT_DIR / "CMakeLists.txt" if cmake_file.exists(): - with open(cmake_file, 'r') as f: + with open(cmake_file, "r", encoding="utf-8") as f: content = f.read() - match = re.search(r'set\(libfranka_VERSION\s+(\d+\.\d+\.\d+)\)', content) + match = re.search(r"set\(libfranka_VERSION\s+(\d+\.\d+\.\d+)\)", content) if match: return match.group(1) return "0.0.0" -def update_version_file(): - """Update _version.py with current version from CMakeLists.txt.""" - version = get_version() - version_file = Path(__file__).parent / "pylibfranka" / "_version.py" - - if version_file.exists(): - # Read current content - with open(version_file, 'r') as f: - content = f.read() - - # Update the version line (matches the pattern with AUTO-GENERATED comment) - new_content = re.sub( - r'__version__ = "[^"]*" # AUTO-GENERATED', - f'__version__ = "{version}" # AUTO-GENERATED', - content - ) - - # Write the updated content - with open(version_file, 'w') as f: - f.write(new_content) - else: - # Create the file if it doesn't exist - with open(version_file, 'w') as f: - f.write(f'''"""Version information for pylibfranka.""" +def write_version_files(version): + """Write version to VERSION file and _version.py for runtime access.""" + pylibfranka_dir = ROOT_DIR / "pylibfranka" -__all__ = ['__version__'] + # Write VERSION file (used by pyproject.toml for build metadata) + version_file = pylibfranka_dir / "VERSION" + version_file.write_text(f"{version}\n") + # Write _version.py (used at runtime for pylibfranka.__version__) + version_py = pylibfranka_dir / "_version.py" + version_py.write_text( + '"""Version information for pylibfranka."""\n\n' + "__all__ = ['__version__']\n\n" + "# Version is auto-generated from CMakeLists.txt during build\n" + f'__version__ = "{version}"\n' + ) -# Version will be written here by pip install . command -# or setup.py during build/install -# DO NOT EDIT THIS LINE - it is automatically replaced -__version__ = "{version}" # AUTO-GENERATED -''') - -# Update version file immediately when setup.py is loaded -update_version_file() +# Extract and write version files before setuptools processes pyproject.toml +_version = get_version() +write_version_files(_version) class CMakeExtension(Extension): @@ -65,14 +52,22 @@ def __init__(self, name, sourcedir=""): self.sourcedir = os.path.abspath(sourcedir) +def get_pybind11_cmake_dir(): + """Get pybind11 CMake directory from pip-installed pybind11.""" + try: + import pybind11 + + return pybind11.get_cmake_dir() + except (ImportError, AttributeError): + # Fallback to system pybind11 or let CMake find it + return None + + class CMakeBuild(build_ext): def run(self): - # Ensure version file is updated (redundant but safe) - update_version_file() - # Call parent run super().run() - + def build_extension(self, ext): extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) @@ -92,6 +87,11 @@ def build_extension(self, ext): "-DCMAKE_BUILD_TYPE=Release", ] + # Use pip-installed pybind11 if available (ensures Python version compatibility) + pybind11_dir = get_pybind11_cmake_dir() + if pybind11_dir: + cmake_args.append(f"-Dpybind11_DIR={pybind11_dir}") + subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=build_temp) subprocess.check_call( ["cmake", "--build", ".", "--config", "Release", "--parallel"], @@ -113,9 +113,9 @@ def build_extension(self, ext): setup( name="pylibfranka", - version=get_version(), + version=_version, packages=["pylibfranka"], - python_requires=">=3.8", + python_requires=">=3.9", install_requires=["numpy>=1.19.0"], ext_modules=[CMakeExtension("pylibfranka._pylibfranka")], cmdclass={ @@ -123,6 +123,6 @@ def build_extension(self, ext): }, zip_safe=False, package_data={ - "pylibfranka": ["*.so", "*.pyd"], + "pylibfranka": ["*.so", "*.pyd", "VERSION"], }, ) From d8996c3ec4000665d43901229430662feabb0efa Mon Sep 17 00:00:00 2001 From: Baris Yazici Date: Fri, 16 Jan 2026 16:52:56 +0100 Subject: [PATCH 32/38] fix: push all debian packages --- .github/workflows/libfranka-build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/libfranka-build.yml b/.github/workflows/libfranka-build.yml index f54ca6ec..adf65d91 100644 --- a/.github/workflows/libfranka-build.yml +++ b/.github/workflows/libfranka-build.yml @@ -5,6 +5,11 @@ on: tags: - '*' workflow_dispatch: + inputs: + release_tag: + description: 'Tag name for release (leave empty for artifacts only)' + required: false + type: string env: REGISTRY: ghcr.io @@ -86,7 +91,7 @@ jobs: retention-days: 30 - name: Upload to GitHub Release - if: startsWith(github.ref, 'refs/tags/') && matrix.ubuntu_version == '20.04' + if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: | From 457285a29950b9c0178ca6cd4a68ddaac668431a Mon Sep 17 00:00:00 2001 From: Baris Yazici Date: Fri, 16 Jan 2026 16:55:00 +0100 Subject: [PATCH 33/38] feat: release with the manual runs --- .github/workflows/libfranka-build.yml | 53 +++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/.github/workflows/libfranka-build.yml b/.github/workflows/libfranka-build.yml index adf65d91..5288f386 100644 --- a/.github/workflows/libfranka-build.yml +++ b/.github/workflows/libfranka-build.yml @@ -80,8 +80,7 @@ jobs: echo "Generated packages and checksums:" ls -lh build/*.deb build/*.sha256 - - name: Upload Debian package and checksum (manual runs) - if: github.event_name == 'workflow_dispatch' + - name: Upload Debian package and checksum uses: actions/upload-artifact@v4 with: name: libfranka-${{ matrix.ubuntu_version }} @@ -90,16 +89,54 @@ jobs: build/*.sha256 retention-days: 30 - - name: Upload to GitHub Release - if: startsWith(github.ref, 'refs/tags/') + release: + name: Create GitHub Release + needs: [build-deb] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '') + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get release info + id: get_release_info + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + TAG="${{ github.event.inputs.release_tag }}" + else + TAG=${GITHUB_REF#refs/tags/} + fi + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Release tag: $TAG" + shell: bash + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: packages/ + pattern: libfranka-* + merge-multiple: true + + - name: Generate checksums summary + run: | + echo "Packages to upload:" + ls -la packages/ + echo "" + echo "Checksums:" + cat packages/*.sha256 + + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: | - build/*.deb - build/*.sha256 - generate_release_notes: true + tag_name: ${{ steps.get_release_info.outputs.tag }} + name: libfranka ${{ steps.get_release_info.outputs.tag }} draft: false prerelease: false + files: packages/* + generate_release_notes: true fail_on_unmatched_files: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 9bb430cb1a7e453df75d379be44e92422bb5bfd8 Mon Sep 17 00:00:00 2001 From: Baris Yazici Date: Fri, 16 Jan 2026 18:40:42 +0100 Subject: [PATCH 34/38] fix: broken pylibfranka binding --- pylibfranka/src/async_control.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylibfranka/src/async_control.cpp b/pylibfranka/src/async_control.cpp index 718166aa..7bde8889 100644 --- a/pylibfranka/src/async_control.cpp +++ b/pylibfranka/src/async_control.cpp @@ -27,7 +27,7 @@ void bind_async_control(py::module& m) { @return CommandResult containing the motion UUID, success flag, and error message )pbdoc") .def("get_target_feedback", &franka::AsyncPositionControlHandler::getTargetFeedback, - py::arg("robot_state") = std::nullopt, R"pbdoc( + py::arg("robot_state") = py::none(), R"pbdoc( Get feedback on the current target. @param robot_state Optional current robot state for more detailed feedback From 79d3b6eafe1c820c0a7ecd5e27b19a62403a9050 Mon Sep 17 00:00:00 2001 From: Baris Yazici Date: Fri, 16 Jan 2026 18:53:56 +0100 Subject: [PATCH 35/38] chore: include the libfranka deb package installation to the github release --- .github/workflows/pylibfranka-wheels.yml | 63 +++++++++++++++++++----- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pylibfranka-wheels.yml b/.github/workflows/pylibfranka-wheels.yml index 1bf0cb17..f5393839 100644 --- a/.github/workflows/pylibfranka-wheels.yml +++ b/.github/workflows/pylibfranka-wheels.yml @@ -231,29 +231,67 @@ jobs: uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.get_release_info.outputs.tag }} - name: pylibfranka ${{ steps.get_release_info.outputs.version }} + name: libfranka ${{ steps.get_release_info.outputs.version }} draft: false prerelease: false files: dist/* body: | - # pylibfranka ${{ steps.get_release_info.outputs.version }} + # libfranka ${{ steps.get_release_info.outputs.version }} + + C++ library and Python bindings for controlling Franka robots. + + --- + + ## libfranka (C++ Library) + + ### Installation from Debian Package + Download the appropriate `.deb` file for your Ubuntu version from the assets below and install: + + **Ubuntu 20.04 (Focal):** + ```bash + sudo dpkg -i libfranka_${{ steps.get_release_info.outputs.version }}_focal_amd64.deb + sudo apt-get install -f # Install dependencies if needed + ``` + + **Ubuntu 22.04 (Jammy):** + ```bash + sudo dpkg -i libfranka_${{ steps.get_release_info.outputs.version }}_jammy_amd64.deb + sudo apt-get install -f # Install dependencies if needed + ``` + + **Ubuntu 24.04 (Noble):** + ```bash + sudo dpkg -i libfranka_${{ steps.get_release_info.outputs.version }}_noble_amd64.deb + sudo apt-get install -f # Install dependencies if needed + ``` + + ### Available Debian Packages + | Ubuntu Version | Package | + |----------------|---------| + | 20.04 (Focal) | `libfranka_${{ steps.get_release_info.outputs.version }}_focal_amd64.deb` | + | 22.04 (Jammy) | `libfranka_${{ steps.get_release_info.outputs.version }}_jammy_amd64.deb` | + | 24.04 (Noble) | `libfranka_${{ steps.get_release_info.outputs.version }}_noble_amd64.deb` | + + --- + + ## pylibfranka (Python Bindings) Python bindings for libfranka, providing easy-to-use Python interfaces for controlling Franka robots. - ## Installation + ### Installation - ### From PyPI (Recommended) + #### From PyPI (Recommended) ```bash pip install pylibfranka==${{ steps.get_release_info.outputs.version }} ``` - ### From GitHub Release + #### From GitHub Release Download the appropriate wheel for your Python version and install: ```bash pip install pylibfranka-${{ steps.get_release_info.outputs.version }}-cp310-cp310-manylinux_2_34_x86_64.whl ``` - ## Platform Compatibility + ### Platform Compatibility | Ubuntu Version | Supported Python Versions | |----------------|---------------------------| @@ -263,14 +301,14 @@ jobs: **Note:** Ubuntu 20.04 users must use Python 3.9 due to glibc compatibility requirements. - ## Available Wheels + ### Available Wheels - Python 3.9: `cp39-cp39-manylinux_2_31_x86_64.whl` (Ubuntu 20.04+) - Python 3.10: `cp310-cp310-manylinux_2_34_x86_64.whl` (Ubuntu 22.04+) - Python 3.11: `cp311-cp311-manylinux_2_34_x86_64.whl` (Ubuntu 22.04+) - Python 3.12: `cp312-cp312-manylinux_2_34_x86_64.whl` (Ubuntu 22.04+) - Source: `pylibfranka-${{ steps.get_release_info.outputs.version }}.tar.gz` - ## Quick Start + ### Quick Start ```python import pylibfranka print(f"pylibfranka version: {pylibfranka.__version__}") @@ -279,10 +317,13 @@ jobs: state = robot.read_once() ``` + --- + ## Documentation - - [Examples](https://github.com/frankarobotics/libfranka/tree/main/pylibfranka/examples) - - [API Documentation](https://frankarobotics.github.io/libfranka/pylibfranka/latest) - - [README](https://github.com/frankarobotics/libfranka/blob/main/pylibfranka/README.md) + - [libfranka Documentation](https://frankarobotics.github.io/libfranka/) + - [pylibfranka Examples](https://github.com/frankarobotics/libfranka/tree/main/pylibfranka/examples) + - [pylibfranka API Documentation](https://frankarobotics.github.io/libfranka/pylibfranka/latest) + - [pylibfranka README](https://github.com/frankarobotics/libfranka/blob/main/pylibfranka/README.md) publish_pypi: name: Publish to PyPI From ba5ff874279b7db4af319dbb3badaec0dc946eda Mon Sep 17 00:00:00 2001 From: Baris Yazici Date: Fri, 16 Jan 2026 19:34:50 +0100 Subject: [PATCH 36/38] bump version 0.20.2 --- CHANGELOG.md | 5 ++++- CMakeLists.txt | 2 +- package.xml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de5bace6..5d47e070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ All notable changes to libfranka in this file. -## [Unreleased] +## [0.20.2] +### libfranka - C++ +- Fix the github workflow to push all the debian packages from 20.4, 22.04 and 24.04 ### pylibfranka - Python #### Added - Automated publishing to PyPI via GitHub Actions workflow. When a version tag is pushed, the workflow automatically builds wheels for Python 3.9, 3.10, 3.11, and 3.12, and publishes them to PyPI. Users can now install pylibfranka directly from PyPI using `pip install pylibfranka`. +- Fix the pylibfranka pybind error with std::nullopt ## [0.20.1] diff --git a/CMakeLists.txt b/CMakeLists.txt index bcaf0b44..deed49f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(libfranka_VERSION 0.20.1) +set(libfranka_VERSION 0.20.2) project(libfranka VERSION ${libfranka_VERSION} diff --git a/package.xml b/package.xml index f0737b2b..83011ba1 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> libfranka - 0.20.1 + 0.20.2 libfranka is a C++ library for Franka Robotics research robots Franka Robotics GmbH Apache 2.0 From 419639dc296e2468d05269ff37cbaec4474d7169 Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Mon, 19 Jan 2026 12:26:15 +0000 Subject: [PATCH 37/38] disabled tsan tests to release libfranka as ros2 package --- test/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6d010234..1a688ecc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -100,7 +100,11 @@ get_target_property(TESTS run_all_tests SOURCES) set(ASan_FLAGS -g -O1 -fsanitize=address) set(UBSan_FLAGS -g -O1 -fsanitize=undefined -fno-sanitize=alignment -fno-sanitize-recover=undefined) set(TSan_FLAGS -g -O1 -fsanitize=thread) -set(SANITIZERS ASan UBSan TSan) +set(SANITIZERS + ASan + UBSan + # TSan # ThreadSanitizer tests currently not supported due g++ ASLR issues on latest Linux kernel (6.5+). This is fixed with clang, consider enabling if we switch to clang +) foreach(sanitizer ${SANITIZERS}) set(sanitizer_flags "${${sanitizer}_FLAGS}") From 90bc9af0b97a357e60c2046057055199a18d60c0 Mon Sep 17 00:00:00 2001 From: Andrea Franceschetti Date: Mon, 19 Jan 2026 13:35:35 +0000 Subject: [PATCH 38/38] bump version to 0.20.3 --- CMakeLists.txt | 2 +- package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index deed49f4..078ce6db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(libfranka_VERSION 0.20.2) +set(libfranka_VERSION 0.20.3) project(libfranka VERSION ${libfranka_VERSION} diff --git a/package.xml b/package.xml index 83011ba1..b40c5f25 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> libfranka - 0.20.2 + 0.20.3 libfranka is a C++ library for Franka Robotics research robots Franka Robotics GmbH Apache 2.0