ros-controls / ros_control

Generic and simple controls framework for ROS
http://wiki.ros.org/ros_control
BSD 3-Clause "New" or "Revised" License
485 stars 305 forks source link

Splitting a joint across two RobotHW plugins #370

Open mikepurvis opened 5 years ago

mikepurvis commented 5 years ago

We've been exploring a path of fine-grained but highly generic RobotHW plugins using CombinedRobotHW, where typically each plugin is managing just a single joint. One thing that's been a frustration, though, is that we've had cases where the sensing for a joint is logically separate from the control of it. For example, we might have velocity control over a joint, where the low level control loop uses an encoder, but the actual sensing we want for our control purposes comes from a multi-turn potentiometer, since that gives us absolute position. But the low lever motor controller knows nothing about the potentiometer; they're completely isolated.

In this scenario, it's hard to write a generic RobotHW that exposes this particular combination of sensing+control— the logical thing is rather to break it up and have the sensing in one plugin and the control in the other. This is almost possible already with ros_control today, but the issue comes in that JointCommandHandle class expects to initialize itself from a JointStateHandle instance. If the command plugin initializes separate from the sensing plugin, it has no way of getting a reference to the JointStateHandle in order to pass it to its own JointCommandHandle's constructor.

I'm doubtful that it's going to be possible to resolve this without some kind of ABI break, but if we're willing to consider a longer term solution, there are some possibilities:

Other options considered and rejected:

@efernandez @p6chen @bmagyar for thoughts/comment.

mikepurvis commented 5 years ago

For interested parties, here's a very simple example of the third proposed scheme for implementing this:

https://github.com/ros-controls/ros_control/compare/melodic-devel...mikepurvis:parent-interface

Most of the code is changing resource claiming to use std::enable_if so that in the case where you are not claiming the resource (eg a JointStateHandle), the reference you get back is const. This means that it's possible to expose a const JointStateInterface, which can return only non-claimable const handles.

The usage which addresses the original motivation looks like so:

  try
  {
    auto state_handle = getParentInterfaceHandle<hardware_interface::JointStateInterface>(joint_name);
    hardware_interface::JointHandle command_handle(state_handle, &cmd_);
    joint_command_interface_.registerHandle(command_handle);
    registerInterface(&joint_command_interface_);
    ROS_INFO_STREAM("Registered JointHandle for " << joint_name << ".");
  }
  catch (const hardware_interface::HardwareInterfaceException& ex)
  {
    ROS_ERROR_STREAM("Unable to register JointHandle for " << joint_name << ": " << ex.what());
    return false;
  }

As far as the other options, if you add a default value parameter to a virtual method, overrides still have to change to match the new signature, so that's a non API compatible path; the deferred JointHandle option is intriguing, but I believe would ultimately require some more invasive changes to CombinedRobotHW, which I wanted to stay away from.