kuka_eki_hw_interface.cpp
Go to the documentation of this file.
00001 /*
00002  * Software License Agreement (BSD License)
00003  *
00004  * Copyright (c) 2018, 3M
00005  * All rights reserved.
00006  *
00007  * Redistribution and use in source and binary forms, with or without
00008  * modification, are permitted provided that the following conditions are met:
00009  *
00010  *      * Redistributions of source code must retain the above copyright
00011  *        notice, this list of conditions and the following disclaimer.
00012  *      * Redistributions in binary form must reproduce the above copyright
00013  *        notice, this list of conditions and the following disclaimer in the
00014  *        documentation and/or other materials provided with the distribution.
00015  *      * Neither the name of the copyright holder, nor the names of its
00016  *        contributors may be used to endorse or promote products derived
00017  *        from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00020  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00021  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00022  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00023  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00024  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00025  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00026  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00027  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00028  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00029  * POSSIBILITY OF SUCH DAMAGE.
00030  */
00031 
00032 // Author: Brett Hemes (3M) <brhemes@mmm.com>
00033 
00034 
00035 #include <boost/array.hpp>
00036 #include <boost/bind.hpp>
00037 #include <boost/date_time/posix_time/posix_time_types.hpp>
00038 
00039 #include <angles/angles.h>
00040 
00041 #include <tinyxml.h>
00042 
00043 #include <kuka_eki_hw_interface/kuka_eki_hw_interface.h>
00044 
00045 
00046 namespace kuka_eki_hw_interface
00047 {
00048 
00049 KukaEkiHardwareInterface::KukaEkiHardwareInterface() : joint_position_(n_dof_, 0.0), joint_velocity_(n_dof_, 0.0),
00050     joint_effort_(n_dof_, 0.0), joint_position_command_(n_dof_, 0.0), joint_names_(n_dof_), deadline_(ios_),
00051     eki_server_socket_(ios_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0))
00052 {
00053 
00054 }
00055 
00056 
00057 KukaEkiHardwareInterface::~KukaEkiHardwareInterface() {}
00058 
00059 
00060 void KukaEkiHardwareInterface::eki_check_read_state_deadline()
00061 {
00062   // Check if deadline has already passed
00063   if (deadline_.expires_at() <= boost::asio::deadline_timer::traits_type::now())
00064   {
00065     eki_server_socket_.cancel();
00066     deadline_.expires_at(boost::posix_time::pos_infin);
00067   }
00068 
00069   // Sleep until deadline exceeded
00070   deadline_.async_wait(boost::bind(&KukaEkiHardwareInterface::eki_check_read_state_deadline, this));
00071 }
00072 
00073 
00074 void KukaEkiHardwareInterface::eki_handle_receive(const boost::system::error_code &ec, size_t length,
00075                                                   boost::system::error_code* out_ec, size_t* out_length)
00076 {
00077   *out_ec = ec;
00078   *out_length = length;
00079 }
00080 
00081 
00082 bool KukaEkiHardwareInterface::eki_read_state(std::vector<double> &joint_position,
00083                                               std::vector<double> &joint_velocity,
00084                                               std::vector<double> &joint_effort,
00085                                               int &cmd_buff_len)
00086 {
00087   static boost::array<char, 2048> in_buffer;
00088 
00089   // Read socket buffer (with timeout)
00090   // Based off of Boost documentation example: doc/html/boost_asio/example/timeouts/blocking_udp_client.cpp
00091   deadline_.expires_from_now(boost::posix_time::seconds(eki_read_state_timeout_));  // set deadline
00092   boost::system::error_code ec = boost::asio::error::would_block;
00093   size_t len = 0;
00094   eki_server_socket_.async_receive(boost::asio::buffer(in_buffer),
00095                                    boost::bind(&KukaEkiHardwareInterface::eki_handle_receive, _1, _2, &ec, &len));
00096   do
00097     ios_.run_one();
00098   while (ec == boost::asio::error::would_block);
00099   if (ec)
00100     return false;
00101 
00102   // Update joint positions from XML packet (if received)
00103   if (len == 0)
00104     return false;
00105 
00106   // Parse XML
00107   TiXmlDocument xml_in;
00108   in_buffer[len] = '\0';  // null-terminate data buffer for parsing (expects c-string)
00109   xml_in.Parse(in_buffer.data());
00110   TiXmlElement* robot_state = xml_in.FirstChildElement("RobotState");
00111   if (!robot_state)
00112     return false;
00113   TiXmlElement* pos = robot_state->FirstChildElement("Pos");
00114   TiXmlElement* vel = robot_state->FirstChildElement("Vel");
00115   TiXmlElement* eff = robot_state->FirstChildElement("Eff");
00116   TiXmlElement* robot_command = robot_state->FirstChildElement("RobotCommand");
00117   if (!pos || !vel || !eff || !robot_command)
00118     return false;
00119 
00120   // Extract axis positions
00121   double joint_pos;  // [deg]
00122   double joint_vel;  // [%max]
00123   double joint_eff;  // [Nm]
00124   char axis_name[] = "A1";
00125   for (int i = 0; i < n_dof_; ++i)
00126   {
00127     pos->Attribute(axis_name, &joint_pos);
00128     joint_position[i] = angles::from_degrees(joint_pos);  // convert deg to rad
00129     vel->Attribute(axis_name, &joint_vel);
00130     joint_velocity[i] = joint_vel;
00131     eff->Attribute(axis_name, &joint_eff);
00132     joint_effort[i] = joint_eff;
00133     axis_name[1]++;
00134   }
00135 
00136   // Extract number of command elements buffered on robot
00137   robot_command->Attribute("Size", &cmd_buff_len);
00138 
00139   return true;
00140 }
00141 
00142 
00143 bool KukaEkiHardwareInterface::eki_write_command(const std::vector<double> &joint_position_command)
00144 {
00145   TiXmlDocument xml_out;
00146   TiXmlElement* robot_command = new TiXmlElement("RobotCommand");
00147   TiXmlElement* pos = new TiXmlElement("Pos");
00148   TiXmlText* empty_text = new TiXmlText("");
00149   robot_command->LinkEndChild(pos);
00150   pos->LinkEndChild(empty_text);   // force <Pos></Pos> format (vs <Pos />)
00151   char axis_name[] = "A1";
00152   for (int i = 0; i < n_dof_; ++i)
00153   {
00154     pos->SetAttribute(axis_name, std::to_string(angles::to_degrees(joint_position_command[i])).c_str());
00155     axis_name[1]++;
00156   }
00157   xml_out.LinkEndChild(robot_command);
00158 
00159   TiXmlPrinter xml_printer;
00160   xml_printer.SetStreamPrinting();  // no linebreaks
00161   xml_out.Accept(&xml_printer);
00162 
00163   size_t len = eki_server_socket_.send_to(boost::asio::buffer(xml_printer.CStr(), xml_printer.Size()),
00164                                           eki_server_endpoint_);
00165 
00166   return true;
00167 }
00168 
00169 
00170 void KukaEkiHardwareInterface::init()
00171 {
00172   // Get controller joint names from parameter server
00173   if (!nh_.getParam("controller_joint_names", joint_names_))
00174   {
00175     ROS_ERROR("Cannot find required parameter 'controller_joint_names' on the parameter server.");
00176     throw std::runtime_error("Cannot find required parameter 'controller_joint_names' on the parameter server.");
00177   }
00178 
00179   // Get EKI parameters from parameter server
00180   const std::string param_addr = "eki/robot_address";
00181   const std::string param_port = "eki/robot_port";
00182   const std::string param_socket_timeout = "eki/socket_timeout";
00183   const std::string param_max_cmd_buf_len = "eki/max_cmd_buf_len";
00184 
00185   if (nh_.getParam(param_addr, eki_server_address_) &&
00186       nh_.getParam(param_port, eki_server_port_))
00187   {
00188     ROS_INFO_STREAM_NAMED("kuka_eki_hw_interface", "Configuring Kuka EKI hardware interface on: "
00189                           << eki_server_address_ << ", " << eki_server_port_);
00190   }
00191   else
00192   {
00193     std::string msg = "Failed to get EKI address/port from parameter server (looking for '" + param_addr +
00194                       "', '" + param_port + "')";
00195     ROS_ERROR_STREAM(msg);
00196     throw std::runtime_error(msg);
00197   }
00198 
00199   if (nh_.getParam(param_socket_timeout, eki_read_state_timeout_))
00200   {
00201     ROS_INFO_STREAM_NAMED("kuka_eki_hw_interface", "Configuring Kuka EKI hardware interface socket timeout to "
00202                           << eki_read_state_timeout_ << " seconds");
00203   }
00204   else
00205   {
00206     ROS_INFO_STREAM_NAMED("kuka_eki_hw_interface", "Failed to get EKI socket timeout from parameter server (looking "
00207                           "for '" + param_socket_timeout + "'), defaulting to " +
00208                           std::to_string(eki_read_state_timeout_)  + " seconds");
00209   }
00210 
00211   if (nh_.getParam(param_max_cmd_buf_len, eki_max_cmd_buff_len_))
00212   {
00213     ROS_INFO_STREAM_NAMED("kuka_eki_hw_interface", "Configuring Kuka EKI hardware interface maximum command buffer "
00214                           "length to " << eki_max_cmd_buff_len_);
00215   }
00216   else
00217   {
00218     ROS_INFO_STREAM_NAMED("kuka_eki_hw_interface", "Failed to get EKI hardware interface maximum command buffer length "
00219                           "from parameter server (looking for '" + param_max_cmd_buf_len + "'), defaulting to " +
00220                           std::to_string(eki_max_cmd_buff_len_));
00221   }
00222 
00223   // Create ros_control interfaces (joint state and position joint for all dof's)
00224   for (std::size_t i = 0; i < n_dof_; ++i)
00225   {
00226     // Joint state interface
00227     joint_state_interface_.registerHandle(
00228         hardware_interface::JointStateHandle(joint_names_[i], &joint_position_[i], &joint_velocity_[i],
00229                                              &joint_effort_[i]));
00230 
00231     // Joint position control interface
00232     position_joint_interface_.registerHandle(
00233         hardware_interface::JointHandle(joint_state_interface_.getHandle(joint_names_[i]),
00234                                         &joint_position_command_[i]));
00235   }
00236 
00237   // Register interfaces
00238   registerInterface(&joint_state_interface_);
00239   registerInterface(&position_joint_interface_);
00240 
00241   ROS_INFO_STREAM_NAMED("kukw_eki_hw_interface", "Loaded Kuka EKI hardware interface");
00242 }
00243 
00244 
00245 void KukaEkiHardwareInterface::start()
00246 {
00247   ROS_INFO_NAMED("kuka_eki_hw_interface", "Starting Kuka EKI hardware interface...");
00248 
00249   // Start client
00250   ROS_INFO_NAMED("kuka_eki_hw_interface", "... connecting to robot's EKI server...");
00251   boost::asio::ip::udp::resolver resolver(ios_);
00252   eki_server_endpoint_ = *resolver.resolve({boost::asio::ip::udp::v4(), eki_server_address_, eki_server_port_});
00253   boost::array<char, 1> ini_buf = { 0 };
00254   eki_server_socket_.send_to(boost::asio::buffer(ini_buf), eki_server_endpoint_);  // initiate contact to start server
00255 
00256   // Start persistent actor to check for eki_read_state timeouts
00257   deadline_.expires_at(boost::posix_time::pos_infin);  // do nothing unit a read is invoked (deadline_ = +inf)
00258   eki_check_read_state_deadline();
00259 
00260   // Initialize joint_position_command_ from initial robot state (avoid bad (null) commands before controllers come up)
00261   if (!eki_read_state(joint_position_, joint_velocity_, joint_effort_, eki_cmd_buff_len_))
00262   {
00263     std::string msg = "Failed to read from robot EKI server within alloted time of "
00264                       + std::to_string(eki_read_state_timeout_) + " seconds.  Make sure eki_hw_interface is running "
00265                       "on the robot controller and all configurations are correct.";
00266     ROS_ERROR_STREAM(msg);
00267     throw std::runtime_error(msg);
00268   }
00269   joint_position_command_ = joint_position_;
00270 
00271   ROS_INFO_NAMED("kuka_eki_hw_interface", "... done. EKI hardware interface started!");
00272 }
00273 
00274 
00275 void KukaEkiHardwareInterface::read(const ros::Time &time, const ros::Duration &period)
00276 {
00277   if (!eki_read_state(joint_position_, joint_velocity_, joint_effort_, eki_cmd_buff_len_))
00278   {
00279     std::string msg = "Failed to read from robot EKI server within alloted time of "
00280                       + std::to_string(eki_read_state_timeout_) + " seconds.  Make sure eki_hw_interface is running "
00281                       "on the robot controller and all configurations are correct.";
00282     ROS_ERROR_STREAM(msg);
00283     throw std::runtime_error(msg);
00284   }
00285 }
00286 
00287 
00288 void KukaEkiHardwareInterface::write(const ros::Time &time, const ros::Duration &period)
00289 {
00290   // only write if max will not be exceeded
00291   if (eki_cmd_buff_len_ < eki_max_cmd_buff_len_)
00292     eki_write_command(joint_position_command_);
00293 
00294   // underflow/overflow checking
00295   // NOTE: this is commented as it results in a lot of logging output and the use of ROS_*
00296   //       logging macros breaks incurs (quite) some overhead. Uncomment and rebuild this
00297   //       if you'd like to use this anyway.
00298   //if (eki_cmd_buff_len_ >= eki_max_cmd_buff_len_)
00299   //  ROS_WARN_STREAM("eki_hw_iface RobotCommand buffer overflow (curent size " << eki_cmd_buff_len_
00300   //                  << " greater than or equal max allowed " << eki_max_cmd_buff_len_ << ")");
00301   //else if (eki_cmd_buff_len_ == 0)
00302   //  ROS_WARN_STREAM("eki_hw_iface RobotCommand buffer empty");
00303 }
00304 
00305 } // namespace kuka_eki_hw_interface


kuka_eki_hw_interface
Author(s): Brett Hemes (3M)
autogenerated on Thu Jun 6 2019 17:56:08