Skip to content

Conversation

@Tacha-S
Copy link

@Tacha-S Tacha-S commented Dec 1, 2025

I updated the implementation so that when the URDF is parsed and a joint’s calibration rising value is present, it is applied to the Homing Offset parameter.

Signed-off-by: Tatsuro Sakaguchi <[email protected]>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Tacha-S, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses issue #96 by integrating URDF calibration data into the Dynamixel hardware interface. It specifically enables the system to read the 'rising' attribute from joint calibration tags within the URDF and translate it into a 'Homing Offset' parameter for the respective joints. This ensures that Dynamixel motors are initialized with their correct calibrated positions, improving the accuracy and reliability of robot control.

Highlights

  • URDF Parsing Enhancement: The pull request introduces new functionality to parse the URDF (Unified Robot Description Format) during the hardware interface initialization.
  • Homing Offset Application: It now extracts the 'rising' calibration value from joint definitions in the URDF and applies it as a 'Homing Offset' parameter to the corresponding Dynamixel joints, ensuring proper initial configuration.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds a useful feature to parse the rising calibration value from a URDF and apply it as a Homing Offset. The implementation is a good start, but I've found a critical bug and several areas for improvement.

My review includes a critical fix for an incorrect iterator comparison that could cause a crash. I've also suggested a more robust way to find the correct motor to apply the offset to, avoiding a fragile assumption about component ordering. Additionally, I've provided feedback on improving performance, readability by extracting the new logic into a helper function, and maintainability by using named constants.

Please note that while adding the #include <tinyxml2.h>, the corresponding dependency was not added to package.xml and CMakeLists.txt. This will likely cause build failures in a clean environment. You should add a dependency on tinyxml2_vendor.

Comment on lines 233 to 251
auto rising = std::atof(calibration_element->Attribute("rising"));
std::string name = joint_element->Attribute("name");
auto itr = std::find_if(
info_.joints.begin(), info_.joints.end(),
[&name](const hardware_interface::ComponentInfo & joint) {
return joint.name == name;
});
if (itr != info_.gpios.end()) {
auto params = info_.gpios[std::distance(info_.joints.begin(), itr)].parameters;
if (std::find_if(params.begin(), params.end(),
[](const std::pair<std::string, std::string> & p) {
return p.first == "Homing Offset";
}) == params.end())
{
info_.gpios[std::distance(info_.joints.begin(), itr)].parameters.emplace(
"Homing Offset",
std::to_string(std::round(rising * (180.0 / M_PI) / (360.0 / 4095))));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This block of code has several issues that should be addressed:

  • Critical Bug: The condition itr != info_.gpios.end() on line 240 is incorrect. The iterator itr is for info_.joints, so it must be compared with info_.joints.end(). This bug can lead to out-of-bounds memory access and undefined behavior.
  • Fragile Logic: The code uses std::distance to get an index from info_.joints and then uses this index to access info_.gpios. This assumes a direct 1-to-1 ordered mapping between joints and gpios, which is not guaranteed and makes the code brittle. A more robust approach is to find the GPIO component by its name.
  • Inefficiency:
    • On line 241, auto params = ... creates an unnecessary copy of the parameters map.
    • On line 242, std::find_if is used to search for a key in a std::unordered_map. Using the find member function is much more efficient.
  • Readability & Style:
    • Magic numbers like 180.0, 360.0, and 4095 should be replaced with named constants to improve clarity. The value 4095 is specific to 12-bit resolution Dynamixels and might need to be configurable for other models.
    • tinyxml2 provides helper functions like DoubleValue() which are safer and more expressive than using std::atof on an attribute string.
    • Variables that are not modified should be declared const.

Here is a suggested replacement that addresses all these points:

        const double rising = rising_attr->DoubleValue();
        const std::string name = name_attr->Value();
        // Find the corresponding GPIO component by name. This is more robust than assuming index correspondence.
        auto gpio_it = std::find_if(
          info_.gpios.begin(), info_.gpios.end(),
          [&name](const hardware_interface::ComponentInfo & gpio) {
            return gpio.name == name;
          });

        if (gpio_it != info_.gpios.end()) {
          // Use find() for efficient key lookup in std::unordered_map
          if (gpio_it->parameters.find("Homing Offset") == gpio_it->parameters.end()) {
            // Use named constants for magic numbers
            constexpr double RAD_TO_DEG = 180.0 / M_PI;
            constexpr double DXL_RESOLUTION_12BIT = 4095.0;
            constexpr double DXL_MAX_DEG = 360.0;
            const double homing_offset_steps =
              std::round(rising * RAD_TO_DEG / (DXL_MAX_DEG / DXL_RESOLUTION_12BIT));
            gpio_it->parameters.emplace("Homing Offset", std::to_string(homing_offset_steps));
          }
        }

Comment on lines 220 to 255
auto urdf = info_.original_xml;
tinyxml2::XMLDocument doc;
if (doc.Parse(urdf.c_str()) != tinyxml2::XML_SUCCESS) {
RCLCPP_ERROR(logger_, "Failed to parse URDF XML");
return hardware_interface::CallbackReturn::ERROR;
}
const auto * joint_element = doc.RootElement()->FirstChildElement("joint");
while (joint_element != nullptr) {
const auto * name_attr = joint_element->FindAttribute("name");
const auto * calibration_element = joint_element->FirstChildElement("calibration");
if (calibration_element != nullptr) {
const auto * rising_attr = calibration_element->FindAttribute("rising");
if ((rising_attr != nullptr) && (name_attr != nullptr)) {
auto rising = std::atof(calibration_element->Attribute("rising"));
std::string name = joint_element->Attribute("name");
auto itr = std::find_if(
info_.joints.begin(), info_.joints.end(),
[&name](const hardware_interface::ComponentInfo & joint) {
return joint.name == name;
});
if (itr != info_.gpios.end()) {
auto params = info_.gpios[std::distance(info_.joints.begin(), itr)].parameters;
if (std::find_if(params.begin(), params.end(),
[](const std::pair<std::string, std::string> & p) {
return p.first == "Homing Offset";
}) == params.end())
{
info_.gpios[std::distance(info_.joints.begin(), itr)].parameters.emplace(
"Homing Offset",
std::to_string(std::round(rising * (180.0 / M_PI) / (360.0 / 4095))));
}
}
}
}
joint_element = joint_element->NextSiblingElement("joint");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This new block of logic for parsing the URDF is quite large and is nested within the on_init function. To improve the readability and modularity of on_init, please consider extracting this entire block into a new private helper function, for example, void DynamixelHardware::updateHomingOffsetsFromURDF(). This will make on_init shorter and easier to understand at a glance.

Signed-off-by: Tatsuro Sakaguchi <[email protected]>
Signed-off-by: Tatsuro Sakaguchi <[email protected]>
Signed-off-by: Tatsuro Sakaguchi <[email protected]>
Signed-off-by: Tatsuro Sakaguchi <[email protected]>
Signed-off-by: Tatsuro Sakaguchi <[email protected]>
@Tacha-S
Copy link
Author

Tacha-S commented Dec 3, 2025

@Woojin-Crive Could you review it?

DentedTune

This comment was marked as off-topic.

@Woojin-Crive
Copy link
Member

Hello, i will check this!

@Woojin-Crive
Copy link
Member

Homing Offset has some complexity to implement such logic.
The sign of the Homing Offset does not change even if the user changes the Drive Mode's Reverse mode flag.
It's because Homing Offset function is for the human users, They calibrate with the bare servo and changes configuration.
It Means that the automatic Homing Offset apply logic should be aware of the Drive Mode too.
And it should not be hard-coded with 12Bit resolution range.
Parts of DXL-X series servo uses 12-Bit resolution encoder, others don't.
I think it should be taken as a long plan to implement this feature in the main branch.

Signed-off-by: Tatsuro Sakaguchi <[email protected]>
@Tacha-S
Copy link
Author

Tacha-S commented Dec 22, 2025

@Woojin-Crive
I agree that the Drive Mode needs to be taken into account.
I have also been struggling with how to properly handle the encoder resolution issue, so I understand your point well.
I agree that these problems are not something that can be solved in a simple or clean way.

As an alternative approach, I would like to propose applying a calibration offset only at the ROS interface level (i.e. on the joint angle), rather than using the Homing Offset in the servo itself.
With this approach, the current position inside the servo is not modified, so the impact should be limited and easier to manage.

I have created a sample implementation in commit 48d7c4a to demonstrate this idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants