ethercat_hardware.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  * Software License Agreement (BSD License)
00003  *
00004  *  Copyright (c) 2008, Willow Garage, Inc.
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
00009  *  are met:
00010  *
00011  *   * Redistributions of source code must retain the above copyright
00012  *     notice, this list of conditions and the following disclaimer.
00013  *   * Redistributions in binary form must reproduce the above
00014  *     copyright notice, this list of conditions and the following
00015  *     disclaimer in the documentation and/or other materials provided
00016  *     with the distribution.
00017  *   * Neither the name of the Willow Garage nor the names of its
00018  *     contributors may be used to endorse or promote products derived
00019  *     from this software without specific prior written permission.
00020  *
00021  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032  *  POSSIBILITY OF SUCH DAMAGE.
00033  *********************************************************************/
00034 
00035 #include "ros_ethercat_hardware/ethercat_hardware.h"
00036 
00037 #include <ros_ethercat_eml/ethercat_xenomai_drv.h>
00038 
00039 #include <sstream>
00040 
00041 #include <net/if.h>
00042 #include <sys/ioctl.h>
00043 #include <boost/foreach.hpp>
00044 #include <boost/regex.hpp>
00045 
00046 EthercatHardwareDiagnostics::EthercatHardwareDiagnostics() :
00047 
00048   txandrx_errors_(0),
00049   device_count_(0),
00050   pd_error_(false),
00051   halt_after_reset_(false),
00052   reset_motors_service_count_(0),
00053   halt_motors_service_count_(0),
00054   halt_motors_error_count_(0),
00055   motors_halted_(false),
00056   motors_halted_reason_("")
00057 {
00058   resetMaxTiming();
00059 }
00060 
00061 void EthercatHardwareDiagnostics::resetMaxTiming()
00062 {
00063   max_pack_command_ = 0.0;
00064   max_txandrx_ = 0.0;
00065   max_unpack_state_ = 0.0;
00066   max_publish_ = 0.0;
00067 }
00068 
00069 EthercatHardware::EthercatHardware(const std::string& name,
00070                                    hardware_interface::HardwareInterface *hw,
00071                                    const std::string& eth, bool allow_unprogrammed) :
00072   hw_(hw),
00073   node_(ros::NodeHandle(name)),
00074   ni_(0),
00075   interface_(eth),
00076   this_buffer_(0),
00077   prev_buffer_(0),
00078   buffer_size_(0),
00079   halt_motors_(true),
00080   reset_state_(0),
00081   max_pd_retries_(10),
00082   diagnostics_publisher_(node_),
00083   motor_publisher_(node_, "motors_halted", 1, true),
00084   device_loader_("ros_ethercat_hardware", "EthercatDevice"),
00085   allow_unprogrammed_(allow_unprogrammed)
00086 {
00087   if (!interface_.empty())
00088     init();
00089   else
00090     ROS_DEBUG("No ethercat interface given. EthercatHardware will not be initialized");
00091 }
00092 
00093 EthercatHardware::~EthercatHardware()
00094 {
00095   diagnostics_publisher_.stop();
00096   for (uint32_t i = 0; i < slaves_.size(); ++i)
00097   {
00098     EC_FixedStationAddress fsa(i + 1);
00099     EtherCAT_SlaveHandler *sh = em_->get_slave_handler(fsa);
00100     if (sh)
00101       sh->to_state(EC_PREOP_STATE);
00102   }
00103   if (ni_)
00104   {
00105     close_socket(ni_);
00106   }
00107   delete[] buffers_;
00108   delete oob_com_;
00109   motor_publisher_.stop();
00110 }
00111 
00112 void EthercatHardware::changeState(EtherCAT_SlaveHandler *sh, EC_State new_state)
00113 {
00114   unsigned product_code = sh->get_product_code();
00115   unsigned serial = sh->get_serial();
00116   uint32_t revision = sh->get_revision();
00117   unsigned slave = sh->get_station_address() - 1;
00118 
00119   if (!sh->to_state(new_state))
00120   {
00121     ROS_FATAL("Cannot goto state %d for slave #%d, product code: %u (0x%X), serial: %u (0x%X), revision: %d (0x%X)",
00122               new_state,
00123               slave, product_code, product_code, serial, serial, revision, revision);
00124     if ((product_code == 0xbaddbadd) || (serial == 0xbaddbadd) || (revision == 0xbaddbadd))
00125       ROS_FATAL("Note: 0xBADDBADD indicates that the value was not read correctly from device.");
00126     exit(EXIT_FAILURE);
00127   }
00128 }
00129 
00130 void EthercatHardware::init()
00131 {
00132   // open temporary socket to use with ioctl
00133   int sock = socket(PF_INET, SOCK_DGRAM, 0);
00134   if (sock < 0)
00135   {
00136     int error = errno;
00137     ROS_FATAL("Couldn't open temp socket : %s", strerror(error));
00138     sleep(1);
00139     exit(EXIT_FAILURE);
00140   }
00141 
00142   struct ifreq ifr;
00143   strncpy(ifr.ifr_name, interface_.c_str(), IFNAMSIZ);
00144   if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0)
00145   {
00146     int error = errno;
00147     ROS_FATAL("Cannot get interface_.c_str() flags for %s: %s", interface_.c_str(),
00148               strerror(error));
00149     sleep(1);
00150     exit(EXIT_FAILURE);
00151   }
00152 
00153   close(sock);
00154   sock = -1;
00155 
00156   if (!(ifr.ifr_flags & IFF_UP))
00157   {
00158     ROS_FATAL("Interface %s is not UP. Try : ifup %s", interface_.c_str(), interface_.c_str());
00159     sleep(1);
00160     exit(EXIT_FAILURE);
00161   }
00162   if (!(ifr.ifr_flags & IFF_RUNNING))
00163   {
00164     ROS_FATAL("Interface %s is not RUNNING. Is cable plugged in and device powered?",
00165               interface_.c_str());
00166     sleep(1);
00167     exit(EXIT_FAILURE);
00168   }
00169 
00170   // Initialize network interface
00171   if ((ni_ = init_ec(interface_.c_str())) == NULL)
00172   {
00173     ROS_FATAL("Unable to initialize interface_.c_str(): %s", interface_.c_str());
00174     sleep(1);
00175     exit(EXIT_FAILURE);
00176   }
00177 
00178   oob_com_ = new EthercatOobCom(ni_);
00179 
00180   // Initialize Application Layer (AL)
00181   EtherCAT_DataLinkLayer::instance()->attach(ni_);
00182   if ((al_ = EtherCAT_AL::instance()) == NULL)
00183   {
00184     ROS_FATAL("Unable to initialize Application Layer (AL): %p", al_);
00185     sleep(1);
00186     exit(EXIT_FAILURE);
00187   }
00188 
00189   int num_ethercat_devices_ = al_->get_num_slaves();
00190   if (num_ethercat_devices_ == 0)
00191   {
00192     ROS_FATAL("Unable to locate any slaves");
00193     sleep(1);
00194     exit(EXIT_FAILURE);
00195   }
00196 
00197   // Initialize Master
00198   if ((em_ = EtherCAT_Master::instance()) == NULL)
00199   {
00200     ROS_FATAL("Unable to initialize EtherCAT_Master: %p", em_);
00201     sleep(1);
00202     exit(EXIT_FAILURE);
00203   }
00204 
00205   // Size slaves vector to hold appropriate number of device pointers
00206   slaves_.resize(num_ethercat_devices_);
00207 
00208   // Make temporary list of slave handles
00209   std::vector<EtherCAT_SlaveHandler*> slave_handles;
00210   for (unsigned int slave = 0; slave < slaves_.size(); ++slave)
00211   {
00212     EC_FixedStationAddress fsa(slave + 1);
00213     EtherCAT_SlaveHandler *sh = em_->get_slave_handler(fsa);
00214     if (sh == NULL)
00215     {
00216       ROS_FATAL("Unable to get slave handler #%d", slave);
00217       sleep(1);
00218       exit(EXIT_FAILURE);
00219     }
00220     slave_handles.push_back(sh);
00221   }
00222 
00223   // Configure EtherCAT slaves
00224 
00225   BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
00226   {
00227     unsigned slave = sh->get_station_address() - 1;
00228     if ((slaves_[slave] = configSlave(sh)) == NULL)
00229     {
00230       ROS_FATAL("Unable to configure slave #%d", slave);
00231       sleep(1);
00232       exit(EXIT_FAILURE);
00233     }
00234     buffer_size_ += slaves_[slave]->command_size_ + slaves_[slave]->status_size_;
00235   }
00236 
00237   // Configure any non-ethercat slaves (appends devices to slaves_ vector)
00238   loadNonEthercatDevices();
00239 
00240   // Move slave from INIT to PREOP
00241 
00242   BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
00243   {
00244     changeState(sh, EC_PREOP_STATE);
00245   }
00246 
00247   // Move slave from PREOP to SAFEOP
00248 
00249   BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
00250   {
00251     changeState(sh, EC_SAFEOP_STATE);
00252   }
00253 
00254   // Move slave from SAFEOP to OP
00255   // TODO : move to OP after initializing slave process data
00256 
00257   BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
00258   {
00259     changeState(sh, EC_OP_STATE);
00260   }
00261 
00262   // Allocate buffers to send and receive commands
00263   buffers_ = new unsigned char[2 * buffer_size_];
00264   this_buffer_ = buffers_;
00265   prev_buffer_ = buffers_ + buffer_size_;
00266 
00267   // Make sure motors are disabled, also collect status data
00268   memset(this_buffer_, 0, 2 * buffer_size_);
00269   if (!txandrx_PD(buffer_size_, this_buffer_, 20))
00270   {
00271     ROS_FATAL("No communication with devices");
00272     sleep(1);
00273     exit(EXIT_FAILURE);
00274   }
00275 
00276   // prev_buffer should contain valid status data when update function is first used
00277   memcpy(prev_buffer_, this_buffer_, buffer_size_);
00278 
00279   last_published_ = ros::Time::now();
00280 
00281   // Initialize slaves
00282   //set<string> actuator_names;
00283   for (unsigned int slave = 0; slave < slaves_.size(); ++slave)
00284   {
00285     if (slaves_[slave]->initialize(hw_, allow_unprogrammed_) < 0)
00286     {
00287       EtherCAT_SlaveHandler *sh = slaves_[slave]->sh_;
00288       if (sh != NULL)
00289       {
00290         ROS_FATAL("Unable to initialize slave #%d, product code: %d, revision: %d, serial: %d",
00291                   slave,
00292                   sh->get_product_code(), sh->get_revision(), sh->get_serial());
00293         sleep(1);
00294       }
00295       else
00296       {
00297         ROS_FATAL("Unable to initialize slave #%d", slave);
00298       }
00299       exit(EXIT_FAILURE);
00300     }
00301   }
00302 
00303   { // Initialization is now complete. Reduce timeout of EtherCAT txandrx for better realtime performance
00304     // Allow timeout to be configured at program load time with rosparam.
00305     // This will allow tweaks for systems with different realtime performance
00306     static const int MAX_TIMEOUT = 100000; // 100ms = 100,000us
00307     static const int DEFAULT_TIMEOUT = 20000; // default to timeout to 20000us = 20ms
00308     int timeout;
00309     if (!node_.getParam("realtime_socket_timeout", timeout))
00310     {
00311       timeout = DEFAULT_TIMEOUT;
00312     }
00313     if ((timeout <= 1) || (timeout > MAX_TIMEOUT))
00314     {
00315       int old_timeout = timeout;
00316       timeout = std::max(1, std::min(MAX_TIMEOUT, timeout));
00317       ROS_WARN("Invalid timeout (%d) for socket, using %d", old_timeout, timeout);
00318     }
00319     if (set_socket_timeout(ni_, timeout))
00320     {
00321       ROS_FATAL("Error setting socket timeout to %d", timeout);
00322       sleep(1);
00323       exit(EXIT_FAILURE);
00324     }
00325     timeout_ = timeout;
00326 
00327     // When packet containing process data is does not return after a given timeout, it is
00328     // assumed to be dropped and the process data will automatically get re-sent.
00329     // After a number of retries, the driver will halt motors as a safety precaution.
00330     //
00331     // The following code allows the number of process data retries to be changed with a rosparam.
00332     // This is needed because lowering the txandrx timeout makes it more likely that a
00333     // performance glitch in network or OS causes will cause the motors to halt.
00334     //
00335     // If number of retries is not specified, use a formula that allows 100ms of dropped packets
00336     int max_pd_retries = MAX_TIMEOUT / timeout; // timeout is in nanoseconds : 20msec = 20000usec
00337     static const int MAX_RETRIES = 50, MIN_RETRIES = 1;
00338     node_.getParam("max_pd_retries", max_pd_retries);
00339     // Make sure motor halt due to dropped packet takes less than 1/10 of a second
00340     if ((max_pd_retries * timeout) > (MAX_TIMEOUT))
00341     {
00342       max_pd_retries = MAX_TIMEOUT / timeout;
00343       ROS_WARN("Max PD retries is too large for given timeout.  Limiting value to %d",
00344                max_pd_retries);
00345     }
00346     if ((max_pd_retries < MIN_RETRIES) || (max_pd_retries > MAX_RETRIES))
00347     {
00348       max_pd_retries = std::max(MIN_RETRIES, std::min(MAX_RETRIES, max_pd_retries));
00349       ROS_WARN("Limiting max PD retries to %d", max_pd_retries);
00350     }
00351     max_pd_retries = std::max(MIN_RETRIES, std::min(MAX_RETRIES, max_pd_retries));
00352     max_pd_retries_ = max_pd_retries;
00353   }
00354 
00355   diagnostics_publisher_.initialize(interface_, buffer_size_, slaves_, num_ethercat_devices_,
00356                                     timeout_,
00357                                     max_pd_retries_);
00358 }
00359 
00360 EthercatHardwareDiagnosticsPublisher::EthercatHardwareDiagnosticsPublisher(ros::NodeHandle &node) :
00361   node_(node),
00362   diagnostics_ready_(false),
00363   publisher_(node_.advertise<diagnostic_msgs::DiagnosticArray>("/diagnostics", 1)),
00364   diagnostics_buffer_(NULL),
00365   last_dropped_packet_count_(0),
00366   last_dropped_packet_time_(0)
00367 {
00368 }
00369 
00370 EthercatHardwareDiagnosticsPublisher::~EthercatHardwareDiagnosticsPublisher()
00371 {
00372   delete[] diagnostics_buffer_;
00373 }
00374 
00375 void EthercatHardwareDiagnosticsPublisher::initialize(const string &interface, unsigned int buffer_size,
00376                                                       const std::vector<boost::shared_ptr<EthercatDevice> > &slaves,
00377                                                       unsigned int num_ethercat_devices,
00378                                                       unsigned timeout,
00379                                                       unsigned max_pd_retries)
00380 {
00381   interface_ = interface;
00382   buffer_size_ = buffer_size;
00383   slaves_ = slaves;
00384   num_ethercat_devices_ = num_ethercat_devices;
00385   timeout_ = timeout;
00386   max_pd_retries_ = max_pd_retries;
00387 
00388   diagnostics_buffer_ = new unsigned char[buffer_size_];
00389 
00390   // Initialize diagnostic data structures
00391   diagnostic_array_.status.reserve(slaves_.size() + 1);
00392   values_.reserve(10);
00393 
00394   ethernet_interface_info_.initialize(interface);
00395 
00396   diagnostics_thread_ = boost::thread(boost::bind(&EthercatHardwareDiagnosticsPublisher::diagnosticsThreadFunc, this));
00397 }
00398 
00399 void EthercatHardwareDiagnosticsPublisher::publish(const unsigned char *buffer,
00400                                                    const EthercatHardwareDiagnostics &diagnostics)
00401 {
00402   boost::try_to_lock_t try_lock;
00403   boost::unique_lock<boost::mutex> lock(diagnostics_mutex_, try_lock);
00404   if (lock.owns_lock())
00405   {
00406     // Make copies of diagnostic data for diagnostic thread
00407     memcpy(diagnostics_buffer_, buffer, buffer_size_);
00408     diagnostics_ = diagnostics;
00409     // Trigger diagnostics publish thread
00410     diagnostics_ready_ = true;
00411     diagnostics_cond_.notify_one();
00412   }
00413 }
00414 
00415 void EthercatHardwareDiagnosticsPublisher::stop()
00416 {
00417   diagnostics_thread_.interrupt();
00418   diagnostics_thread_.join();
00419   publisher_.shutdown();
00420 }
00421 
00422 void EthercatHardwareDiagnosticsPublisher::diagnosticsThreadFunc()
00423 {
00424   try
00425   {
00426     while (1)
00427     {
00428       boost::unique_lock<boost::mutex> lock(diagnostics_mutex_);
00429       while (!diagnostics_ready_)
00430       {
00431         diagnostics_cond_.wait(lock);
00432       }
00433       diagnostics_ready_ = false;
00434       publishDiagnostics();
00435     }
00436   }
00437   catch (boost::thread_interrupted const&)
00438   {
00439     return;
00440   }
00441 }
00442 
00443 void EthercatHardwareDiagnosticsPublisher::timingInformation(diagnostic_updater::DiagnosticStatusWrapper &status,
00444                                                              const string &key,
00445                                                              const accumulator_set<double, stats<tag::max, tag::mean> > &acc,
00446                                                              double max)
00447 {
00448   status.addf(key + " Avg (us)", "%5.4f", extract_result<tag::mean>(acc) * 1e6); // Average over last 1 second
00449   status.addf(key + " 1 Sec Max (us)", "%5.4f", extract_result<tag::max>(acc) * 1e6); // Max over last 1 second
00450   status.addf(key + " Max (us)", "%5.4f", max * 1e6); // Max since start
00451 }
00452 
00453 void EthercatHardwareDiagnosticsPublisher::publishDiagnostics()
00454 {
00455   ros::Time now(ros::Time::now());
00456 
00457   // Publish status of EtherCAT master
00458   status_.clearSummary();
00459   status_.clear();
00460 
00461   status_.name = "EtherCAT Master";
00462   if (diagnostics_.motors_halted_)
00463   {
00464     std::ostringstream desc;
00465     desc << "Motors halted";
00466     if (diagnostics_.halt_after_reset_)
00467     {
00468       desc << " soon after reset";
00469     }
00470     desc << " (" << diagnostics_.motors_halted_reason_ << ")";
00471     status_.summary(status_.ERROR, desc.str());
00472   }
00473   else
00474   {
00475     status_.summary(status_.OK, "OK");
00476   }
00477 
00478   if (diagnostics_.pd_error_)
00479   {
00480     status_.mergeSummary(status_.ERROR, "Error sending proccess data");
00481   }
00482 
00483   status_.add("Motors halted", diagnostics_.motors_halted_ ? "true" : "false");
00484   status_.addf("EtherCAT devices (expected)", "%d", num_ethercat_devices_);
00485   status_.addf("EtherCAT devices (current)", "%d", diagnostics_.device_count_);
00486   ethernet_interface_info_.publishDiagnostics(status_);
00487   //status_.addf("Reset state", "%d", reset_state_);
00488 
00489   status_.addf("Timeout (us)", "%d", timeout_);
00490   status_.addf("Max PD Retries", "%d", max_pd_retries_);
00491 
00492   // Produce warning if number of devices changed after device initialization
00493   if (num_ethercat_devices_ != diagnostics_.device_count_)
00494   {
00495     status_.mergeSummary(status_.WARN, "Number of EtherCAT devices changed");
00496   }
00497 
00498   timingInformation(status_, "Roundtrip time", diagnostics_.txandrx_acc_,
00499                     diagnostics_.max_txandrx_);
00500   if (diagnostics_.collect_extra_timing_)
00501   {
00502     timingInformation(status_, "Pack command time", diagnostics_.pack_command_acc_,
00503                       diagnostics_.max_pack_command_);
00504     timingInformation(status_, "Unpack state time", diagnostics_.unpack_state_acc_,
00505                       diagnostics_.max_unpack_state_);
00506     timingInformation(status_, "Publish time", diagnostics_.publish_acc_,
00507                       diagnostics_.max_publish_);
00508   }
00509 
00510   status_.addf("EtherCAT Process Data txandrx errors", "%d", diagnostics_.txandrx_errors_);
00511 
00512   status_.addf("Reset motors service count", "%d", diagnostics_.reset_motors_service_count_);
00513   status_.addf("Halt motors service count", "%d", diagnostics_.halt_motors_service_count_);
00514   status_.addf("Halt motors error count", "%d", diagnostics_.halt_motors_error_count_);
00515 
00516   { // Publish ethercat network interface counters
00517     const struct netif_counters *c = &diagnostics_.counters_;
00518     status_.add("Input Thread", (diagnostics_.input_thread_is_stopped_ ? "Stopped" : "Running"));
00519     status_.addf("Sent Packets", "%llu", (unsigned long long) c->sent);
00520     status_.addf("Received Packets", "%llu", (unsigned long long) c->received);
00521     status_.addf("Collected Packets", "%llu", (unsigned long long) c->collected);
00522     status_.addf("Dropped Packets", "%llu", (unsigned long long) c->dropped);
00523     status_.addf("TX Errors", "%llu", (unsigned long long) c->tx_error);
00524     status_.addf("TX Network Down", "%llu", (unsigned long long) c->tx_net_down);
00525     status_.addf("TX Would Block", "%llu", (unsigned long long) c->tx_would_block);
00526     status_.addf("TX No Buffers", "%llu", (unsigned long long) c->tx_no_bufs);
00527     status_.addf("TX Queue Full", "%llu", (unsigned long long) c->tx_full);
00528     status_.addf("RX Runt Packet", "%llu", (unsigned long long) c->rx_runt_pkt);
00529     status_.addf("RX Not EtherCAT", "%llu", (unsigned long long) c->rx_not_ecat);
00530     status_.addf("RX Other EML", "%llu", (unsigned long long) c->rx_other_eml);
00531     status_.addf("RX Bad Index", "%llu", (unsigned long long) c->rx_bad_index);
00532     status_.addf("RX Bad Sequence", "%llu", (unsigned long long) c->rx_bad_seqnum);
00533     status_.addf("RX Duplicate Sequence", "%llu", (unsigned long long) c->rx_dup_seqnum);
00534     status_.addf("RX Duplicate Packet", "%llu", (unsigned long long) c->rx_dup_pkt);
00535     status_.addf("RX Bad Order", "%llu", (unsigned long long) c->rx_bad_order);
00536     status_.addf("RX Late Packet", "%llu", (unsigned long long) c->rx_late_pkt);
00537     status_.addf("RX Late Packet RTT", "%llu", (unsigned long long) c->rx_late_pkt_rtt_us);
00538 
00539     double rx_late_pkt_rtt_us_avg = 0.0;
00540     if (c->rx_late_pkt > 0)
00541     {
00542       rx_late_pkt_rtt_us_avg = ((double) c->rx_late_pkt_rtt_us_sum) / ((double) c->rx_late_pkt);
00543     }
00544     status_.addf("RX Late Packet Avg RTT", "%f", rx_late_pkt_rtt_us_avg);
00545 
00546     // Check for newly dropped packets
00547     if (c->dropped > last_dropped_packet_count_)
00548     {
00549       last_dropped_packet_time_ = now;
00550       last_dropped_packet_count_ = c->dropped;
00551     }
00552   }
00553 
00554   // Create error message if packet has been dropped recently
00555   if ((last_dropped_packet_count_ > 0)
00556       && ((now - last_dropped_packet_time_).toSec() < dropped_packet_warning_hold_time_))
00557   {
00558     status_.mergeSummaryf(status_.WARN, "Dropped packets in last %d seconds",
00559                           dropped_packet_warning_hold_time_);
00560   }
00561 
00562   diagnostic_array_.status.clear();
00563   diagnostic_array_.status.push_back(status_);
00564 
00565   // Also, collect diagnostic statuses of all EtherCAT device
00566   unsigned char *current = diagnostics_buffer_;
00567   for (unsigned int s = 0; s < slaves_.size(); ++s)
00568   {
00569     slaves_[s]->multiDiagnostics(diagnostic_array_.status, current);
00570     current += slaves_[s]->command_size_ + slaves_[s]->status_size_;
00571   }
00572 
00573   // Publish status of each EtherCAT device
00574   diagnostic_array_.header.stamp = ros::Time::now();
00575   publisher_.publish(diagnostic_array_);
00576 }
00577 
00578 void EthercatHardware::update(bool reset, bool halt)
00579 {
00580   // Update current time
00581   ros::Time update_start_time(ros::Time::now());
00582 
00583   unsigned char *this_buffer, *prev_buffer;
00584 
00585   // Convert HW Interface commands to MCB-specific buffers
00586   this_buffer = this_buffer_;
00587 
00588   if (halt)
00589   {
00590     ++diagnostics_.halt_motors_service_count_;
00591     haltMotors(false /*no error*/, "service request");
00592   }
00593 
00594   // Resetting devices should clear device errors and release devices from halt.
00595   // To reduce load on power system, release devices from halt, one at a time
00596   const unsigned CYCLES_PER_HALT_RELEASE = 2; // Wait two cycles between releasing each device
00597   if (reset)
00598   {
00599     ++diagnostics_.reset_motors_service_count_;
00600     reset_state_ = CYCLES_PER_HALT_RELEASE * slaves_.size() + 5;
00601     last_reset_ = update_start_time;
00602     diagnostics_.halt_after_reset_ = false;
00603   }
00604   bool reset_devices = reset_state_ == CYCLES_PER_HALT_RELEASE * slaves_.size() + 3;
00605   if (reset_devices)
00606   {
00607     halt_motors_ = false;
00608     diagnostics_.motors_halted_ = false;
00609     diagnostics_.motors_halted_reason_ = "";
00610     diagnostics_.resetMaxTiming();
00611     diagnostics_.pd_error_ = false;
00612   }
00613 
00614   for (unsigned int s = 0; s < slaves_.size(); ++s)
00615   {
00616     // Pack the command structures into the EtherCAT buffer
00617     // Disable the motor if they are halted or coming out of reset
00618     bool halt_device = halt_motors_ || ((s * CYCLES_PER_HALT_RELEASE + 1) < reset_state_);
00619     slaves_[s]->packCommand(this_buffer, halt_device, reset_devices);
00620     this_buffer += slaves_[s]->command_size_ + slaves_[s]->status_size_;
00621   }
00622 
00623   // Transmit process data
00624   ros::Time txandrx_start_time(ros::Time::now()); // Also end time for pack_command_stage
00625   diagnostics_.pack_command_acc_((txandrx_start_time - update_start_time).toSec());
00626 
00627   // Send/receive device process data
00628   bool success = txandrx_PD(buffer_size_, this_buffer_, max_pd_retries_);
00629 
00630   ros::Time txandrx_end_time(ros::Time::now()); // Also start unpack_state
00631   diagnostics_.txandrx_acc_((txandrx_end_time - txandrx_start_time).toSec());
00632 
00633   if (!success)
00634   {
00635     // If process data didn't get sent after multiple retries, stop motors
00636     haltMotors(true /*error*/, "communication error");
00637     diagnostics_.pd_error_ = true;
00638   }
00639   else
00640   {
00641     // Convert status back to HW Interface
00642     this_buffer = this_buffer_;
00643     prev_buffer = prev_buffer_;
00644     for (unsigned int s = 0; s < slaves_.size(); ++s)
00645     {
00646       if (!slaves_[s]->unpackState(this_buffer, prev_buffer) && !reset_devices)
00647       {
00648         haltMotors(true /*error*/, "device error");
00649       }
00650       this_buffer += slaves_[s]->command_size_ + slaves_[s]->status_size_;
00651       prev_buffer += slaves_[s]->command_size_ + slaves_[s]->status_size_;
00652     }
00653 
00654     if (reset_state_)
00655       --reset_state_;
00656 
00657     unsigned char *tmp = this_buffer_;
00658     this_buffer_ = prev_buffer_;
00659     prev_buffer_ = tmp;
00660   }
00661 
00662   ros::Time unpack_end_time;
00663   if (diagnostics_.collect_extra_timing_)
00664   {
00665     unpack_end_time = ros::Time::now(); // also start of publish time
00666     diagnostics_.unpack_state_acc_((unpack_end_time - txandrx_end_time).toSec());
00667   }
00668 
00669   if ((update_start_time - last_published_) > ros::Duration(1.0))
00670   {
00671     last_published_ = update_start_time;
00672     publishDiagnostics();
00673     motor_publisher_.lock();
00674     motor_publisher_.msg_.data = halt_motors_;
00675     motor_publisher_.unlockAndPublish();
00676   }
00677 
00678   if (diagnostics_.collect_extra_timing_)
00679   {
00680     ros::Time publish_end_time(ros::Time::now());
00681     diagnostics_.publish_acc_((publish_end_time - unpack_end_time).toSec());
00682   }
00683 }
00684 
00685 void EthercatHardware::haltMotors(bool error, const char* reason)
00686 {
00687   if (!halt_motors_)
00688   {
00689     // wasn't already halted
00690     motor_publisher_.lock();
00691     motor_publisher_.msg_.data = halt_motors_;
00692     motor_publisher_.unlockAndPublish();
00693 
00694     diagnostics_.motors_halted_reason_ = reason;
00695     if (error)
00696     {
00697       ++diagnostics_.halt_motors_error_count_;
00698       if ((ros::Time::now() - last_reset_) < ros::Duration(0.5))
00699       {
00700         // halted soon after reset
00701         diagnostics_.halt_after_reset_ = true;
00702       }
00703     }
00704   }
00705 
00706   diagnostics_.motors_halted_ = true;
00707   halt_motors_ = true;
00708 }
00709 
00710 void EthercatHardware::updateAccMax(double &max,
00711                                     const accumulator_set<double, stats<tag::max, tag::mean> > &acc)
00712 {
00713   max = std::max(max, extract_result<tag::max>(acc));
00714 }
00715 
00716 void EthercatHardware::publishDiagnostics()
00717 {
00718   // Update max timing values
00719   updateAccMax(diagnostics_.max_pack_command_, diagnostics_.pack_command_acc_);
00720   updateAccMax(diagnostics_.max_txandrx_, diagnostics_.txandrx_acc_);
00721   updateAccMax(diagnostics_.max_unpack_state_, diagnostics_.unpack_state_acc_);
00722   updateAccMax(diagnostics_.max_publish_, diagnostics_.publish_acc_);
00723 
00724   // Grab stats and counters from input thread
00725   diagnostics_.counters_ = ni_->counters;
00726   diagnostics_.input_thread_is_stopped_ = bool(ni_->is_stopped);
00727 
00728   diagnostics_.motors_halted_ = halt_motors_;
00729 
00730   // Pass diagnostic data to publisher thread
00731   diagnostics_publisher_.publish(this_buffer_, diagnostics_);
00732 
00733   // Clear statistics accumulators
00734   static accumulator_set<double, stats<tag::max, tag::mean> > blank;
00735   diagnostics_.pack_command_acc_ = blank;
00736   diagnostics_.txandrx_acc_ = blank;
00737   diagnostics_.unpack_state_acc_ = blank;
00738   diagnostics_.publish_acc_ = blank;
00739 }
00740 
00741 boost::shared_ptr<EthercatDevice>
00742 EthercatHardware::configSlave(EtherCAT_SlaveHandler *sh)
00743 {
00744   static int start_address = 0x00010000;
00745   boost::shared_ptr<EthercatDevice> p;
00746   unsigned product_code = sh->get_product_code();
00747   unsigned serial = sh->get_serial();
00748   uint32_t revision = sh->get_revision();
00749   unsigned slave = sh->get_station_address() - 1;
00750 
00751   // The point of this code to find a class whose name matches the EtherCAT
00752   // product ID for a given device.
00753   // Thus device plugins would register themselves with PLUGINLIB_EXPORT_CLASS
00754   //
00755   //    PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
00756   //
00757   // and in the plugin.xml, specify name="" inside the <class> tag:
00758   //
00759   //    <class name="package/serial"
00760   //           base_class_type="ethercat_hardware::EthercatDevice" />
00761   //
00762   //
00763   // For the WG05 driver (productID = 6805005), this statement would look
00764   // something like:
00765   //
00766   //    PLUGINLIB_EXPORT_CLASS(WG05, EthercatDevice)
00767   //
00768   // and in the plugin.xml:
00769   //
00770   //    <class name="ethercat_hardware/6805005" type="WG05"
00771   //           base_class_type="EthercatDevice">
00772   //      <description>
00773   //        WG05 - Generic Motor Control Board
00774   //      </description>
00775   //    </class>
00776   //
00777   //
00778   // Unfortunately, we don't know which ROS package that a particular driver is defined in.
00779   // To account for this, we search through the list of class names, one-by-one and find string where
00780   // last part of string matches product ID of device.
00781   stringstream class_name_regex_str;
00782   class_name_regex_str << "(.*/)?" << product_code;
00783   boost::regex class_name_regex(class_name_regex_str.str(), boost::regex::extended);
00784 
00785   std::vector<std::string> classes = device_loader_.getDeclaredClasses();
00786   std::string matching_class_name;
00787 
00788   BOOST_FOREACH(const std::string &class_name, classes)
00789   {
00790     if (regex_match(class_name, class_name_regex))
00791     {
00792       if (matching_class_name.size() != 0)
00793       {
00794         ROS_ERROR("Found more than 1 EtherCAT driver for device with product code : %d", product_code);
00795         ROS_ERROR("First class name = '%s'.  Second class name = '%s'",
00796                   class_name.c_str(), matching_class_name.c_str());
00797       }
00798       matching_class_name = class_name;
00799     }
00800   }
00801 
00802   if (matching_class_name.size() != 0)
00803   {
00804     ROS_WARN("Using device '%s' with product code %d",
00805              device_loader_.getClassDescription(matching_class_name).c_str(),
00806              product_code);
00807     try
00808     {
00809       p = device_loader_.createInstance(matching_class_name);
00810     }
00811     catch (pluginlib::LibraryLoadException &e)
00812     {
00813       p.reset();
00814       ROS_FATAL("Unable to load plugin for slave #%d, product code: %u (0x%X), serial: %u (0x%X), revision: %d (0x%X)",
00815                 slave,
00816                 product_code, product_code, serial, serial, revision, revision);
00817       ROS_FATAL("%s", e.what());
00818     }
00819   }
00820   else
00821   {
00822     if ((product_code == 0xbaddbadd) || (serial == 0xbaddbadd) || (revision == 0xbaddbadd))
00823     {
00824       ROS_FATAL("Note: 0xBADDBADD indicates that the value was not read correctly from device.");
00825       ROS_FATAL("Perhaps you should power-cycle the MCBs");
00826     }
00827     else
00828     {
00829       ROS_ERROR("Unable to load plugin for slave #%d, product code: %u (0x%X), serial: %u (0x%X), revision: %d (0x%X)",
00830                 slave,
00831                 product_code, product_code, serial, serial, revision, revision);
00832       ROS_ERROR("Possible classes:");
00833 
00834       BOOST_FOREACH(const std::string &class_name, classes)
00835       {
00836         ROS_ERROR("  %s", class_name.c_str());
00837       }
00838 
00839       // TODO, use default plugin for ethercat devices that have no driver.
00840       // This way, the EtherCAT chain still works.
00841     }
00842   }
00843 
00844   if (p != NULL)
00845   {
00846     p->construct(sh, start_address);
00847   }
00848 
00849   return p;
00850 }
00851 
00852 boost::shared_ptr<EthercatDevice>
00853 EthercatHardware::configNonEthercatDevice(const std::string &name, const std::string &type)
00854 {
00855   boost::shared_ptr<EthercatDevice> p;
00856   try
00857   {
00858     p = device_loader_.createInstance(type);
00859   }
00860   catch (pluginlib::LibraryLoadException &e)
00861   {
00862     p.reset();
00863     ROS_FATAL("Unable to load plugin for non-EtherCAT device '%s' with type: %s : %s",
00864               name.c_str(), type.c_str(), e.what());
00865   }
00866   if (p)
00867   {
00868     ROS_INFO("Creating non-EtherCAT device '%s' of type '%s'", name.c_str(), type.c_str());
00869     ros::NodeHandle nh(node_, "non_ethercat_devices/" + name);
00870     p->construct(nh);
00871   }
00872   return p;
00873 }
00874 
00875 // Do this to get access to Struct (std::map) element of XmlRpcValue
00876 
00877 class MyXmlRpcValue : public XmlRpc::XmlRpcValue
00878 {
00879 public:
00880 
00881   MyXmlRpcValue(XmlRpc::XmlRpcValue &value) :
00882     XmlRpc::XmlRpcValue(value)
00883   {
00884   }
00885 
00886   XmlRpcValue::ValueStruct &getMap()
00887   {
00888     return *_value.asStruct;
00889   }
00890 };
00891 
00892 void EthercatHardware::loadNonEthercatDevices()
00893 {
00894   // non-EtherCAT device drivers are descibed via struct named "non_ethercat_devices"
00895   // Each element of "non_ethercat_devices" must be a struct that contains a "type" field.
00896   // The element struct can also contain other elements that are used as configuration parameters
00897   // for the device plugin.
00898   // Example:
00899   //   non_ethercat_devices:
00900   //     netft1:
00901   //       type: NetFT
00902   //       address: 192.168.1.1
00903   //       publish_period: 0.01
00904   //     netft2:
00905   //       type: NetFT
00906   //       address: 192.168.1.2
00907   //       publish_period: 0.01
00908   //     dummy_device:
00909   //       type: Dummy
00910   //       msg: "This a dummy device"
00911   //
00912   //   The name of the device is used to create a ros::NodeHandle that is
00913   //    that is passed to construct function of device class.
00914   //   NOTE: construct function is not the same as C++ construtor
00915   if (!node_.hasParam("non_ethercat_devices"))
00916   {
00917     // It is perfectly fine if there is no list of non-ethercat devices
00918     return;
00919   }
00920 
00921   XmlRpc::XmlRpcValue devices;
00922   node_.getParam("non_ethercat_devices", devices);
00923   if (devices.getType() != XmlRpc::XmlRpcValue::TypeStruct)
00924   {
00925     ROS_ERROR("Rosparam 'non_ethercat_devices' is not a struct type");
00926     return;
00927   }
00928 
00929   MyXmlRpcValue my_devices(devices);
00930   typedef XmlRpc::XmlRpcValue::ValueStruct::value_type map_item_t;
00931 
00932   BOOST_FOREACH(map_item_t &item, my_devices.getMap())
00933   {
00934     const std::string & name(item.first);
00935     XmlRpc::XmlRpcValue & device_info(item.second);
00936 
00937     if (device_info.getType() != XmlRpc::XmlRpcValue::TypeStruct)
00938     {
00939       ROS_ERROR("non_ethercat_devices/%s is not a struct type", name.c_str());
00940       continue;
00941     }
00942 
00943     if (!device_info.hasMember("type"))
00944     {
00945       ROS_ERROR("non_ethercat_devices/%s 'type' element", name.c_str());
00946       continue;
00947     }
00948 
00949     std::string type(static_cast<std::string> (device_info["type"]));
00950 
00951     boost::shared_ptr<EthercatDevice> new_device =
00952       configNonEthercatDevice(name, type);
00953     if (new_device != NULL)
00954     {
00955       slaves_.push_back(new_device);
00956     }
00957   }
00958 }
00959 
00960 void EthercatHardware::collectDiagnostics()
00961 {
00962   if (NULL == oob_com_)
00963     return;
00964 
00965   { // Count number of devices
00966     EC_Logic *logic = EC_Logic::instance();
00967     unsigned char p[1];
00968     uint16_t length = sizeof (p);
00969 
00970     // Build read telegram, use slave position
00971     APRD_Telegram status(logic->get_idx(), // Index
00972                          0, // Slave position on ethercat chain (auto increment address)
00973                          0, // ESC physical memory address (start address)
00974                          logic->get_wkc(), // Working counter
00975                          length, // Data Length,
00976                          p); // Buffer to put read result into
00977 
00978     // Put read telegram in ros_ethercat_eml/ethernet frame
00979     EC_Ethernet_Frame frame(&status);
00980     oob_com_->txandrx(&frame);
00981 
00982     // Worry about locking for single value?
00983     diagnostics_.device_count_ = status.get_adp();
00984   }
00985 
00986   for (unsigned i = 0; i < slaves_.size(); ++i)
00987   {
00988     boost::shared_ptr<EthercatDevice> d(slaves_[i]);
00989     d->collectDiagnostics(oob_com_);
00990   }
00991 }
00992 
00993 // Prints (error) counter information of network interface driver
00994 
00995 void EthercatHardware::printCounters(std::ostream &os)
00996 {
00997   const struct netif_counters & c(ni_->counters);
00998   os << "netif counters :" << endl
00999     << " sent          = " << c.sent << endl
01000     << " received      = " << c.received << endl
01001     << " collected     = " << c.collected << endl
01002     << " dropped       = " << c.dropped << endl
01003     << " tx_error      = " << c.tx_error << endl
01004     << " tx_net_down   = " << c.tx_net_down << endl
01005     << " tx_would_block= " << c.tx_would_block << endl
01006     << " tx_no_bufs    = " << c.tx_no_bufs << endl
01007     << " tx_full       = " << c.tx_full << endl
01008     << " rx_runt_pkt   = " << c.rx_runt_pkt << endl
01009     << " rx_not_ecat   = " << c.rx_not_ecat << endl
01010     << " rx_other_eml  = " << c.rx_other_eml << endl
01011     << " rx_bad_index  = " << c.rx_bad_index << endl
01012     << " rx_bad_seqnum = " << c.rx_bad_seqnum << endl
01013     << " rx_dup_seqnum = " << c.rx_dup_seqnum << endl
01014     << " rx_dup_pkt    = " << c.rx_dup_pkt << endl
01015     << " rx_bad_order  = " << c.rx_bad_order << endl
01016     << " rx_late_pkt   = " << c.rx_late_pkt << endl;
01017 }
01018 
01019 bool EthercatHardware::txandrx_PD(unsigned buffer_size, unsigned char* buffer, unsigned tries)
01020 {
01021   // Try multiple times to get proccess data to device
01022   bool success = false;
01023   for (unsigned i = 0; i < tries && !success; ++i)
01024   {
01025     // Try transmitting process data
01026     success = em_->txandrx_PD(buffer_size_, this_buffer_);
01027     if (!success)
01028     {
01029       ++diagnostics_.txandrx_errors_;
01030     }
01031     // Transmit new OOB data
01032     oob_com_->tx();
01033   }
01034   return success;
01035 }
01036 
01037 bool EthercatHardware::publishTrace(int position, const string &reason, unsigned level,
01038                                     unsigned delay)
01039 {
01040   if (position >= (int) slaves_.size())
01041   {
01042     ROS_WARN("Invalid device position %d.  Use 0-%d, or -1.", position, int(slaves_.size()) - 1);
01043     return false;
01044   }
01045 
01046   if (level > 2)
01047   {
01048     ROS_WARN("Invalid level : %d.  Using level=2 (ERROR).", level);
01049     level = 2;
01050   }
01051 
01052   string new_reason("Manually triggered : " + reason);
01053 
01054   bool retval = false;
01055   if (position < 0)
01056   {
01057     for (unsigned i = 0; i < slaves_.size(); ++i)
01058     {
01059       if (slaves_[i]->publishTrace(new_reason, level, delay))
01060       {
01061         retval = true;
01062       }
01063     }
01064   }
01065   else
01066   {
01067     retval = slaves_[position]->publishTrace(new_reason, level, delay);
01068     if (!retval)
01069     {
01070       ROS_WARN("Device %d does not support publishing trace", position);
01071     }
01072   }
01073   return retval;
01074 }


ros_ethercat_hardware
Author(s): Rob Wheeler , Derek King , Manos Nikolaidis
autogenerated on Thu Aug 27 2015 14:47:15