-
Notifications
You must be signed in to change notification settings - Fork 308
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[proposal] Support multiple hardware interfaces in controllers #75
Comments
This is a big limitation, yes. You have summarized very well the usecases that are currently not supported. I haven't thought enough about the problem to fully understand the repercussions of your proposals, but I'll describe how I managed to hack a controller with multiple hardware interfaces. The usecase is that of a biped walking controller that has one virtual std::string getHardwareInterfaceType() const = 0; which I set to Preliminary thoughts
|
@jbohren Appreciate the motivation and usecases in your proposal! I'm on board with the idea that this should get fixed. I need to think through the consequences of each of the proposals, but at first glance the idea to pass multiple RobotHW instances to a controllers sounds cleanest. |
I'd like to go ahead and resurface this issue since I'm now running into a similar problem. My usecase: I have a UR arm and have recently written a RobotHW interface for moving the joints. I have also attached a Robotiq gripper as an end effector and will soon be mounting the arm to a linear actuator. I would like to write and release independent RobotHWs for both the gripper and the actuator such that I can write controllers which act independently, but also others which utilize multiple interfaces jointly. Since there hasn't been much development here, I was wondering how best to proceed. I'm not sure I want to rewrite the several packages to maintain a list of RobotHWs, though this might be the only way to accomplish this. Anyone working on a solution here or found a good workaround? |
Looking at it a little more, it seems like there is very difficult to "add" RobotHW/InterfaceManager instances into a new composite, without again respecifying every single interface added. Since the only way to obtain each interface is to call the templated method get(), you can't really "add" ResourceManager instances across different RobotHW. I thought you could achieve this by making a new ResourceManager when the "MultiRobotHW->get" gets called, but since it looks only for a pointer, you would create a memory leak. It might make more sense to create a new MultiControllerManager class (or rewrite the existing) to take a list of RobotHW. These separate lines:
Are the primary places where robot_hw_ is used. If controller_manager.cpp were re-written to attempt initializing controllers through a list, and looking through the entire list for conflicts, then it could be updated relatively cleanly with a list. This would solve the multi-RobotHW problem, at least. The cross-interface/cross-RobotHW controller wouldn't work though. Thoughts? |
I'd have to dig a bit deeper into the design again to make sure, but I think we can support controllers that use 1, 2, 3, or more interfaces from the same robot, by adding new templated controller classes that derive from the controller base class. Currently the only class that derives from the controller base is Controller, but we can add Controller<T1, T2> and ControllerT1, T2, T3> to support 2 or 3 interfaces. These new controller classes implement the same initRequest method as Controller, but they will have an init method with the multiple interfaces that were pulled out of the robot. We'd definitely have to make other smaller changes to make this work, but I think there might be a path forward here. |
Suppose we created two new classes: RobotHWGroup and InterfaceGroup. The RobotHWGroup will contain and maintain a list of RobotHW and implement checkForConflict such that it checks for conflicts across the full list of RobotHW. The new group class would be the parameter passed to the ControllerManager method ControllerBase::initRequest. Thus, every "main" loop for the robot controller managers will need slight modification, to create a dummy group and add the only current RobotHW. InterfaceGroup will be templated on a particular interface (with subclass HardwareResourceManager), and contain/maintain a list of pointers to interfaces. The interface group will also implement getHandle and getNames, which manages these across the multiple interfaces. The Controller::init method's first parameter would be changed from T* to InterfaceGroup* though every controller's init definition would need to be modified, the functionality would be identical to the original. By using a list of interfaces instead of combining them, each interface for each RobotHW is left to manage its own resources, even if the interfaces are the same. Finally, the ControllerT1/T2/T3 problem can be resolved just as Wim mentioned. Now that the cross-RobotHW problem is handled by InterfaceGroups/RobotHWGroup, each controller has access to all RobotHW instances. Let me know if this seems like a palatable solution. If I have time, I might try coding something up and see what I come up with. |
The idea of a RobotHWGroup seems pretty cool. If I understand it correctly, it would simply be an object that derives from RobotHW, and internally manages the RobotHW of multiple robots. So this would not require any changes to the ros_control framework, right? The only change would be in the main, where a user creates its own RobotHW. As for the InterfaceGroup, I think we can replace that with The Controller<T1, T2> I talked about earlier. The init call of a Controller looks like |
The problem which you need InterfaceGroups to overcome is multiple RobotHW instances with identical interfaces (like 2 identical arms). Since One potential workaround is to make the Honestly, HardwareResourceManager seems like a perfect candidate for a singleton design pattern. Hands down, there should just only be one hardware interface instance for each type. If the changes were made to make this happen, then |
@kphawkins I see your point and how this makes it harder to implement the RobotHWGroup object to support multiple robots that have the same type of interface (by combining multiple interfaces of the same type into one interface inside RobotHWGroup). On the other side, breaking existing interfaces is a pretty big deal, so if we can get away with spending more time implementing the RobotHWGroup and not changing any existing interface, I'd definitely favor that solution. The implementation of RobotHWGroup is a one time effort that we can use over and over again. I do want to keep around all ideas we come up with to improve the overall design of ros_control, and when the time comes to create ros_control 2.0 and improve existing APIs, we can learn from our current experiences. I started a new wiki page where we can collect these ideas https://github.com/ros-controls/ros_control/wiki/Ros-control-2.0. |
Can we make get and registerInterface virtual so that they could be properly overridden? ControllerBase::initRequest takes a RobotHW parameter. |
That sounds like a good plan, and should not affect our current APIs. |
Catching up here. I haven't thought much about the full implications of the proposed solutions, but some comments do come to mind: Controllers with multiple hardware interfacesAllowing controllers with multiple hardware interfaces would require changing the Multiple RobotHWs, alternative solutionNot that I dislike having multiple Example: class FooArm
{
public:
FooArm(const std::string& prefix = ""); // Probably also needs hw-specific params to connect to it
bool addInterfaces(RobotHW* hw); // Register hardware interfaces
bool read(); // Read hardware state
bool write(); // Send commands to hardware
private:
// Raw data here
}; Then, when setting up your hardware abstraction, you would create as many instances of FooArm as you want, giving each an appropriate prefix. The main loop would call Notice that |
@adolfo-rt, some comments: Controllers with multiple hardware interfacesYou're right, there will be a number of smaller changes needed to support controllers with multiple hardware interfaces. I think we can make those changes while maintaining backwards compatibility for most users. Multiple RobotHWsI think we can do this without changing any existing code. I had a bit more of a generic helper class in mind (if I understood your example correctly), called RobotHWGroup. The RobotHWGroup derives from RobotHW, so it would offer the same get method as all RobotHWs. You can register multiple RobotHW with the RobotHWGroup; the group will provide access too all the registered RobotHWs through its own get method. So, the RobotHWGroup pretty much pretends that multiple robots are just one single robot. At runtime, the get method of the RobotHWGroup can call through to all the get methods of the registered RobotHW, and see if it can find the correct interface, and check if there are no duplicates. Alternatively, we can register RobotHWs with a prefix, and then the prefix would tell the RobotHWGroup wich RobotHW to call get on. In any case, the RobotHWGroup can just have a templated implementation of get, and does not need to be specific to any interface type. |
I don't know if we can maintain 100% API compatibility, as methods and messages previously containing a single string for the hardware interface identifier now might contain a vector of strings instead. The migration path is not painful, but still exists.
You're right. This would be such a convenience class, with the added benefit that it exposes an existing ros_control interface.
Are you referring to no duplicate interfaces or handles?. Duplicate interfaces should definitely be allowed: if two arms have a JointStateInterface, I'd expect
Implementation-wise, we could internally keep a separate set of interfaces containing the aggregate of all registered I'm really liking this idea now. |
I have opened a pull request here: #137 It doesn't alter any existing interfaces, and seems to work very much like we've discussed. Of course, I still haven't figured out same-interface-different-RobotHW problem, but that might require more detailed changes. I added one major test case which seems to show it working properly, see |
I just went through #137. Great initiative. I think that not supporting multiple identical interfaces is a big issue, and I don't know how to address it at this moment. Futile comment: If C++ allowed reflection (get a type from a string, the opposite of the typeid operator), we could get this done inside the |
Implementation details apart, what's your opinion on having a separate RobotHWGroup class vs. supporting the behavior directly by RobotHW?. The RobotHWGroup seems more like an easy way to batch-add contents to a RobotHW. The idea can also be extented to batch-adding handles to interfaces:
|
I have submitted another pull request to fix the interface aliasing issue here: #138 See Though this is an extensively involved and potentially confusing implementation, it passes all unit tests, including a new added test which merges identical interfaces from separate First, understand that after all RobotHW are registered in the RobotHWGroup, a new interface almost certainly must be created for Since it would make sense that only The added helper methods to I was trying to avoid was requiring the user to call several templated "re-register interface" methods after all |
This is a great step forward. You definitely tackled some of the big issues. I was expecting the implementation to get more involved to support this usecase. Here's some feedback from reading (but not running) the code:
|
I'm about to add a few commits based on these comments:
|
#138 Has been updated. I have totally removed One potential use-case which was potentially problematic is the fact that I have extended my original implementation to also create new instances if it seems like new interfaces have been registered in the meantime. I know this feels memory leaky, but tolerating |
#138 Now includes multi_controller.h, which has an implementation of I have also added the functionality to do simple tests in |
Checking in again. First, from my inline code comment, I think that the Secondly, I don't feel great about leaking a few interface instances, but I agree that under the current design, there's no way that the Lastly, It's great that you've been adding so many tests to the PR. I can't tell how much this is appreciated. I've been slow on this review, and would like to excercise the code myself, but my response times will likely continue to be slow for the time being (especially because this issue is non-trivial and requires focus and concentration). |
Hi. What do you think about pull #151 |
And what do you think about pull #153 |
We´re running into this issue right now (looking into implementing an admittance controller that needs both force/torque sensor interface and a joint interface). The quickest way to resolve this that comes to my mind without requiring changes to ros_control is creating a new custom interface that provides both data, but that of course would not be required if there´s a workable composite solution by now. Seeing how last post here is from April, I just wanted to ping and ask what the current state of affairs is. |
@skohlbr. Your usecase is similar to a previous thread comment #75 (comment), which means that you can work around the current state of affairs from within your controller implementation. If you inherit from Since you only have one interface that actually sends commands, I would make the controller expose that as its main HW interface in I hope this can get you going. We use this trick and it works fine. |
@adolfo-rt Thanks, that indeed looks like a workable solution. Have to admit I read all comments some time ago and missed the details while skimming over things this time around. We´ll use the proposed scheme and report back in case of issues. |
Allowing multiple hardware interfaces in a single controller have been addressed in #204. Composing multiple |
Composing multiple |
Thanks for the note. |
👍 |
+1 |
Introduction
As of this writing, the biggest limitation that I see in ros_control is its inability to connect a single controller to two different hardware interfaces / robot hardware abstractions. As far as I can tell, removing this limitation would fundamentally change the design of ros_control.
RobotHW
components at the level of ros_control (consider connecting a PHANToM Omni to a KUKA LWR via ros_control),RobotHW
components which use the same low-level interface (consider a robot that can have different numbers of arms or arm components like grippers which all talk over CANBus like the WAM),The pipeline currently connects like so:
ControllerManager
callsControllerBase::initRequest()
ControllerBase::initRequest()
callsControllerInterfaceType<HardwareInterfaceType>::init()
HardwareInterfaceType::init()
is passed the singleHardwareInterfaceType
All of this connection via template inference means:
ControllerManager
cannot connect a single controller to multiple hardware interfaces of different types from a single robot hardware abstraction and the only way to connect two different interfaces is to connect them with theRobotHW
which means control computation would be happening in the hardware abstraction layer instead of the controllers.ControllerManager
cannot connect a single controller to multiple robot hardware abstractions which means you need to create aRobotHW
subclass for every robot configuration (including master/slave devices which might be fundamentally different hardware).Multiple RobotHW in a Single ControllerManager
To support multiple robot hardware abstractions in a controller manager, we could take a hierarchical approach, and compose
RobotHW
subclasses in a singleCompositeRobotHW
class which combines the resources of each component's hardware interfaces. This would require some template magic and/or a redesign of the resource manager to enable the extraction and aggregating of handles of the same type.As a slight alternative, we could modify the
ControllerManager
to keep its own aggregated hardware interfaces internally, and instead registerRobotHW
instances with it, and have the controller manager extract and aggregate the handles, itself.Multiple Hardware Interfaces in a Single Controller
Controllers are currently strongly typed at compile time based on a single hardware interface. via template parameter. In order to allow a single controller to talk to two different hardware interfaces, we could change the lookup pattern from using templates to just using string identifiers. It's worth noting that the interface types are already being extracted from the
RobotHW
class in this way by callinginternal::demangledTypeName<T>()
and looking up the interface object in astd::map<std::string, HardwareInterface*>
.Feedback
Please comment on what you think about the described limitation and my sketches of workarounds, and feel free to suggest other solutions!
@wmeeusse @adolfo-rt @mikeferguson @davetcoleman
The text was updated successfully, but these errors were encountered: