Skip to content
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

eki_hw_interface: An EKI-based hardware interface. #110

Merged
merged 28 commits into from
Apr 20, 2018

Conversation

BrettHemes
Copy link
Member

@BrettHemes BrettHemes commented Feb 8, 2018

An EKI-based Kuka driver targeted at use with ros_control. Based off of existing kuka_rsi_hw_interface but with a "soft" real-time Ethernet UDP/IP interface. Streaming happens at ~50 Hz (vs the RSI interface's 250Hz) with all commanded setpoints executed as fast as possible on the KRC. In this configuration, any significant latency from the ROS side simply results in the robot safely decelerating while keeping the server running (i.e., no faults or program stops). 50 Hz streaming from the ROS PC (or really enough to prevent the advanced run from triggering a stop) results in smooth motion and no stutters.

Depending on the displacement of the commanded waypoints, I imagine that full joint speeds will not be reachable due to the 5-step look-ahead limit of the advance run on the controller (further testing and performance graphs planned to confirm this). Despite this, however, I believe that this is totally usable when less-than-maximum performance is required (use RSI in that case...) and will fill the use-case gap identified in #77. In fact the observed performance exceeded my original expectations of the approach. In my limited testing thus far the motion was significantly smooth and correct using MoveIt! RViz generated trajectories (using RRTkConfigDefault) on a real Kuka Agilus robot. Was using ros_control in Ubuntu 16.04 with the default (i.e., non-low-latency) kernel.

@BrettHemes BrettHemes mentioned this pull request Feb 8, 2018
@BrettHemes BrettHemes changed the title eki_hw_interface: Initial commit. An EKI-based hardware interface. eki_hw_interface: An EKI-based hardware interface. Feb 8, 2018
@gavanderhoorn
Copy link
Member

Unfortunately I have no way of testing this, as I don't have any hw that has this interface.

My first question would be: any guestimate as to what sort of performance we can expect from this? You mention that you believe it will/might not be full performance, but can you put any nrs on it?

I can imagine that it would be quite important to get a feeling for any performance differences when trying to use this for continuous processes that require specifiek (EEF) velocities fi.

@gavanderhoorn
Copy link
Member

@durovsky @simonschmeisser and @ivareri, you might also be interested in this.

@simonschmeisser
Copy link
Collaborator

thanks for the hint @gavanderhoorn Unfortunately we currently don't have access to any kuka and they were not cooperative wrt licencing a simulation

@BrettHemes
Copy link
Member Author

My first question would be: any guestimate as to what sort of performance we can expect from this? You mention that you believe it will/might not be full performance, but can you put any nrs on it?

I plan to measure this. The robot I want to use is getting moved, so probably next week or the week after. A single sent point via the interface (i.e., not using the ros_control-based node wrapper provided) will execute at full speed, no problem. My concern is that the higher density streaming (a la ros_control) might reduce the effective horizon of the lookahead via the advance run and limit max velocity but I am not sure. There is a chance I am over thinking it; we shall see :)

That said, the performance should be better than any of the KUKAVARPROXY implementations I am aware of. They use a single variable as the target position that gets updated in time via the proxy interface. This limits the advance run to a single point (i.e. $advance=1). The EKI approach in question leverages the socket receive buffer and can see up to five points ahead (i.e. $advance=5, the maximum) as they become available thus resulting in higher-speed, smoother motion overall.

I can imagine that it would be quite important to get a feeling for any performance differences when trying to use this for continuous processes that require specifiek (EEF) velocities fi.

Indeed. The data will help understand what people are getting. A trajectory downloading version/mode would also be a relatively easy extension. I am also entertaining the idea of a hybrid extension that can take streamed trajectories but also invoke internal KRL commands (similar to sending ur_script commands via socket) when necessary/desirable. This would get you nice reactionary MoveIt/ros_control style functionality but also rock-solid native velocity controlled motion when required (i.e., processing moves). I need to hash through the details but I envision these all being on different ports and all available at any given time to the user.

Copy link
Member

@gavanderhoorn gavanderhoorn left a comment

Choose a reason for hiding this comment

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

Thanks for the PR 👍. This is a welcome addition to this repository.

I added a lot of comments, but I was just commenting on everything I saw, so don't take it as there being many things that need changes.

The RSI hw iface could use some TLC, so I also looked for things that we could avoid with the EKI iface in this PR.

cmake_minimum_required(VERSION 2.8.3)
project(kuka_eki_hw_interface)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
Copy link
Member

Choose a reason for hiding this comment

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

We should probably use add_compile_options(-std=c++11) here instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed; copypasta from the rsi_hw_interface...

/*
* Software License Agreement (BSD License)
*
* Copyright (c) 2018, Brett Hemes (3M)
Copy link
Member

Choose a reason for hiding this comment

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

You probably already checked, but is the copyright yours, or 3M's?

You're always the author of course.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting 😄 3M didn't catch this but yes, this should be changed

{

private:

Copy link
Member

Choose a reason for hiding this comment

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

empty line seems redundant (same on line 56)

~KukaEkiHardwareInterface();

void start();
void configure();
Copy link
Member

Choose a reason for hiding this comment

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

There is a RobotHW::init(..) in kinetic-devel. Wondering if we can somehow already start using that, to facilitate porting later on (although introducing our own init() here will shadow the one in RobotHW under Kinetic).

See: https://github.com/ros-controls/ros_control/blob/81ed9edf2a399c1aae33fc5b603c2215dde5ffbe/hardware_interface/include/hardware_interface/robot_hw.h#L70-L80

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you elaborate here?

Copy link
Member

Choose a reason for hiding this comment

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

The kinetic-devel version of RobotHW in ros_control defines an init(..) method that should be used to perform any initialisation actions the hw iface may need. As there was nothing in Indigo, we have a custom configure().

I was wondering whether it would be an idea to instead of keeping our configure(), start using init(..) already, so when we migrate to kinetic-devel of ros_control with this repository we are already up-to-date.

void start();
void configure();
bool read(const ros::Time time, const ros::Duration period);
bool write(const ros::Time time, const ros::Duration period);
Copy link
Member

Choose a reason for hiding this comment

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

Can we make these references?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was going to originally but the rsi_hw_interface and Adolfo's examples are by value so I did the same.

Copy link
Member

Choose a reason for hiding this comment

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

Do you mean Dave's boilerplate? His code does use references.

Upstream kinetic-devel also has read(..) and write(..) and those also use references (here). I cannot really come up with a reason not to use them here tbh.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I was looking at some old wiki examples? I don't remember. Maybe here: https://github.com/ros-controls/ros_control/wiki/joint_limits_interface

Aside, but I also have a bad habit of not always realizing I am on an older distributions API documentation page 🙄

By reference is the right answer here. Thanks for the links.

TiXmlDocument xml_out;
TiXmlElement* robot_command = new TiXmlElement("RobotCommand");
TiXmlText* empty_text = new TiXmlText("");
robot_command->LinkEndChild(empty_text); // force <RobotCommand></RobotCommand> format (vs <RobotCommand />)
Copy link
Member

Choose a reason for hiding this comment

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

The construction of this XML document seems to be always the same. Would it make sense to move that to either the ctor or some other initialisation method and store xml_out (maybe member variable)?

Same for TiXmlPrinter below.

{
ROS_INFO_NAMED("kuka_eki_hw_interface", "Starting Kuka EKI hardware interface...");
// TODO(BrettHemes): Error handling?
// TODO(BrettHemes): Do these block? Look into implementing non-blocking receives
Copy link
Member

Choose a reason for hiding this comment

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

See earlier comment about TODOs.

command_server_port_});

// Initialize joint_position_command_ from initial robot state (avoid bad (null) commands before controllers come up)
while (!socket_read_state(joint_position_command_));
Copy link
Member

@gavanderhoorn gavanderhoorn Feb 13, 2018

Choose a reason for hiding this comment

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

I don't know whether it can happen, but perhaps we should add some timeout or maximum retry mechanism here to avoid infinite waits.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I will look into this.

else
{
ROS_ERROR("Failed to get EKI addresses/ports from parameter server!");
throw std::runtime_error("Failed to get EKI addresses/ports parameter server.");
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: mention which parameters we're looking for here (ref #106).

{
ros::init(argc, argv, "kuka_eki_hw_interface");

ros::AsyncSpinner spinner(1);
Copy link
Member

Choose a reason for hiding this comment

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

Should this be 2? I remember several implementations using 2 threads to avoid (ROS event) operations from blocking the control loop (Dave's ros_control_boilerplate fi (here).

@gavanderhoorn
Copy link
Member

🛎️ ?

@BrettHemes
Copy link
Member Author

Yes yes! I am still here. I got sick and then was on travel. I will followup this week 😃

@BrettHemes
Copy link
Member Author

@gavanderhoorn wonderful comments; thank you very much! There are some that I need a bit more help with please. I have begun addressing the rest and will update the PR as I knock them off.

@gavanderhoorn
Copy link
Member

I've replied to some of your questions @BrettHemes. Thanks for taking care of my comments.

@BrettHemes
Copy link
Member Author

Still working on addressing all the comments but had some time Friday to generate some quick plots with rqt_plot and MoveIt!/RViz using a physical Kuka KR6 R700 sixx robot. I have to say, the results were surprisingly good and help relieve my previous performance limit concerns 😃.

To generate the trajectories I used the RViz MotionPlanning plugin with RRTkConfigDefault and alternated between two poses:
traj_1_pose_1
traj_1_pose_2

With the defaults, the interface keeps up no problem with a constant lag of ~0.25 seconds:
default_v_1_00__a_1_00__traj_1

Slowing things down by setting the velocity and acceleration scaling factors to 0.01 was even better, producing the following:
default_v_0_01__a_0_01__traj_1

I believe the default behavior without acceleration limits specified is to use the (unrealistic) value of 1.0 rad/s/s. To test something a bit more representative I set the acceleration limits to 10x of the max KR6 r700 sixx velocities and then ran a set of trajectories tweaking the velocity and acceleration scaling limits.

Full tilt (acc_scale = 1.0; vel_scale = 1.0):
acc_10x_v_1_00__a_1_00__traj_1

acc_scale = 0.6; vel_scale = 1.0
acc_10x_v_1_00__a_0_60__traj_1

acc_scale = 0.6; vel_scale = 0.6
acc_10x_v_0_60__a_0_60__traj_1

The 10x, full speed run exhibits some error accumulation (in addition to the previously observed constant lag). This is probably due to BOTH hardware interface performance limits AND the fact that the 10x acceleration limits are a bit much. By scaling down the max velocity and acceleration the accumulating error goes away again leaving only the bounded error due to the time shift.

I ran these in some spare time and they are no means comprehensive but are encouraging nonetheless. Qualitatively, it seems that commanded velocities will be adhered to with realistic acceleration settings and velocities on the order of 60-80% of max. Not bad for a "soft" real-time streaming interface...

@@ -9,6 +9,7 @@ find_package(catkin REQUIRED COMPONENTS
hardware_interface
joint_limits_interface
roscpp
angles
Copy link
Member

Choose a reason for hiding this comment

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

Could you move angles to the top? Let's keep these COMPONENTS sorted alphabetically.

@@ -25,6 +26,7 @@ catkin_package(
hardware_interface
joint_limits_interface
roscpp
angles
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

@@ -20,4 +20,5 @@
<depend>hardware_interface</depend>
<depend>joint_limits_interface</depend>
<depend>roscpp</depend>
<depend>angles</depend>
Copy link
Member

Choose a reason for hiding this comment

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

Sorting here again.

And angles is a header only library, so no need to make it a depend.

@BrettHemes
Copy link
Member Author

BrettHemes commented Mar 8, 2018

OK I think I got most of @gavanderhoorn's suggestions addressed and the code is all the better for it.

Some things I did not fix / remaining nuances:

  1. Tiny XML document construction in send/recieve.
    I tried moving these objects to be member variables but had trouble getting them to update. The first construction worked but then subsequent read/writes were not modifying the xml values. I don't have enough experience with the library to know what the issue was and bailed in the interest of other fixes. How much is really saved by moving this?

  2. I added broadcasting of the full state (position, velocity, and effort) but then had issues with the controller struggling to populate the xml structure for transmission resulting in unacceptable stuttering motion (due most likely to the EKI buffer emptying mid-move and the subsequent advance run stop). Part of this is due to the fact that the joint velocities are reported as a % of max (which is stored in another system variable) and thus requires a multiplication and division for each joint in addition to the various variable reads. Even sending raw values introduced some stutter... Long story short, reading and loading ALL of the state info results in an interrupt service routine that is not sufficiently short. I left the code in to send the whole state but currently have the velocities and efforts commented out. I think there is room for some optimizations that will make this work (and have a couple of approaches in mind) but would like to save this for a later PR.

  3. I implemented a timeout on the socket read using Boost's somewhat involved tutorial on the matter (see here). It works well. I have set the timeout to a constant value of 5 seconds. Thoughts?

  4. Regarding the potential buffer overrun in the socket read. The Boost buffer abstraction prevents this and won't read more than the provided buffer's length. Also, there is no real way to query the size of received data via the udp interface so I don't really know how to do this otherwise.

@gavanderhoorn Would you mind giving the code another once over at this point?

@gavanderhoorn
Copy link
Member

gavanderhoorn commented Mar 13, 2018

@BrettHemes wrote:

1 . Tiny XML document construction in send/recieve.
I tried moving these objects to be member variables but had trouble getting them to update. The first construction worked but then subsequent read/writes were not modifying the xml values. I don't have enough experience with the library to know what the issue was and bailed in the interest of other fixes. How much is really saved by moving this?

Repeated construction of these TinyXML objects probably does not incur a very large overhead. Let's leave this as it is for now.

2 . I added broadcasting of the full state (position, velocity, and effort) but then had issues with the controller struggling to populate the xml structure for transmission resulting in unacceptable stuttering motion (due most likely to the EKI buffer emptying mid-move and the subsequent advance run stop). Part of this is due to the fact that the joint velocities are reported as a % of max (which is stored in another system variable) and thus requires a multiplication and division for each joint in addition to the various variable reads. Even sending raw values introduced some stutter... Long story short, reading and loading ALL of the state info results in an interrupt service routine that is not sufficiently short. I left the code in to send the whole state but currently have the velocities and efforts commented out. I think there is room for some optimizations that will make this work (and have a couple of approaches in mind) but would like to save this for a later PR.

hm, unfortunate. But as you say: let's save this for a later PR.

3 . I implemented a timeout on the socket read using Boost's somewhat involved tutorial on the matter (see here). It works well. I have set the timeout to a constant value of 5 seconds. Thoughts?

Other than "holy ****"? :)

Minor suggestion: we could make this time-out perhaps configurable using a ROS parameter.

4 . Regarding the potential buffer overrun in the socket read. The Boost buffer abstraction prevents this and won't read more than the provided buffer's length. Also, there is no real way to query the size of received data via the udp interface so I don't really know how to do this otherwise.

Ok. It sounds like this is sufficiently covered then.

@BrettHemes
Copy link
Member Author

@gavanderhoorn OK, what are your thoughts? I added the ability to set the socket read timeout via parameter and I think we are getting close to being mergable.

Also, extrapolating from previous discussions, I assume all of the tweaks since the original PR should get squashed into the "initial commit" commit when it comes time to merge? Am I correct here?

@gavanderhoorn
Copy link
Member

@BrettHemes wrote:

@gavanderhoorn OK, what are your thoughts? I added the ability to set the socket read timeout via parameter and I think we are getting close to being mergable.

yes, I think this is quite ok like this.

re: merge: I was hoping to get some time on an Agilus with EKI (which we apparently have) to test this out on real hw. Not that I don't trust you, but I just like to do that. That appears more difficult than I anticipated, so it doesn't make sense to hold off merging.

Also, extrapolating from previous discussions, I assume all of the tweaks since the original PR should get squashed into the "initial commit" commit when it comes time to merge? Am I correct here?

Yes, I typically like to do that, unless you absolutely want to keep the history of those changes. As they are all fixups though, I never really see the value. In a way, the end result is what the PR "should ideally have been".

@BrettHemes
Copy link
Member Author

BrettHemes commented Mar 29, 2018

I was hoping to get some time on an Agilus with EKI (which we apparently have) to test this out on real hw. ... That appears more difficult than I anticipated, so it doesn't make sense to hold off merging.

What time frame are you looking at? A second hw test would be nice...

In a way, the end result is what the PR "should ideally have been".

Agreed

@gavanderhoorn
Copy link
Member

@BrettHemes wrote:

What time frame are you looking at? A second hw test would be nice...

I've just sent the lab with the agilus with EKI an email.

If I don't get a response early next week, I'll merge without testing myself.

Copy link
Member

@gavanderhoorn gavanderhoorn left a comment

Choose a reason for hiding this comment

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

Robot is too busy, and I don't want to hold this up too long. It's been quite some time already.

Thanks for the contribution @BrettHemes, this is a very nice addition to this repository.

👍 🍔 🍻

@gavanderhoorn gavanderhoorn merged commit e8ddefd into ros-industrial:indigo-devel Apr 20, 2018
@BrettHemes BrettHemes deleted the eki_hw_interface branch July 12, 2018 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants