ethercat_device.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_device.h"
00036 
00037 #include <tinyxml.h>
00038 
00039 #include <iomanip>
00040 
00041 bool et1x00_error_counters::isGreaterThan(unsigned value) const
00042 {
00043   if ((pdi_error > value) || (epu_error > value))
00044   {
00045     return true;
00046   }
00047 
00048   for (unsigned i = 0; i < 4; ++i)
00049   {
00050     if ((port[i].rx_error > value) ||
00051         (forwarded_rx_error[i] > value) ||
00052         (lost_link[i] > value) ||
00053         (port[i].invalid_frame > value))
00054     {
00055       return true;
00056     }
00057   }
00058   return false;
00059 }
00060 
00061 bool et1x00_error_counters::isGreaterThan(const et1x00_error_counters &v) const
00062 {
00063   if ((pdi_error > v.pdi_error) || (epu_error > v.epu_error))
00064   {
00065     return true;
00066   }
00067 
00068   for (unsigned i = 0; i < 4; ++i)
00069   {
00070     if ((port[i].rx_error > v.port[i].rx_error) ||
00071         (forwarded_rx_error[i] > v.forwarded_rx_error[i]) ||
00072         (lost_link[i] > v.lost_link[i]) ||
00073         (port[i].invalid_frame > v.port[i].invalid_frame))
00074     {
00075       return true;
00076     }
00077   }
00078   return false;
00079 }
00080 
00081 bool et1x00_dl_status::hasLink(unsigned port)
00082 {
00083   assert(port < 4);
00084   return status & (1 << (4 + port));
00085 }
00086 
00087 bool et1x00_dl_status::hasCommunication(unsigned port)
00088 {
00089   assert(port < 4);
00090   return status & (1 << (9 + port * 2));
00091 }
00092 
00093 bool et1x00_dl_status::isClosed(unsigned port)
00094 {
00095   assert(port < 4);
00096   return status & (1 << (8 + port * 2));
00097 }
00098 
00099 void et1x00_error_counters::zero()
00100 {
00101   memset(this, 0, sizeof (et1x00_error_counters));
00102 }
00103 
00104 EthercatPortDiagnostics::EthercatPortDiagnostics() :
00105   hasLink(false),
00106   isClosed(false),
00107   hasCommunication(false)
00108 {
00109   zeroTotals();
00110 }
00111 
00112 void EthercatPortDiagnostics::zeroTotals()
00113 {
00114   rxErrorTotal = 0;
00115   invalidFrameTotal = 0;
00116   forwardedRxErrorTotal = 0;
00117   lostLinkTotal = 0;
00118 }
00119 
00120 EthercatDeviceDiagnostics::EthercatDeviceDiagnostics() :
00121   errorCountersMayBeCleared_(false),
00122   diagnosticsFirst_(true),
00123   diagnosticsValid_(false),
00124   resetDetected_(false),
00125   devicesRespondingToNodeAddress_(-1)
00126 {
00127   zeroTotals();
00128   errorCountersPrev_.zero();
00129 }
00130 
00131 void EthercatDeviceDiagnostics::zeroTotals()
00132 {
00133   pdiErrorTotal_ = 0;
00134   epuErrorTotal_ = 0;
00135   portDiagnostics_[0].zeroTotals();
00136   portDiagnostics_[1].zeroTotals();
00137   portDiagnostics_[2].zeroTotals();
00138   portDiagnostics_[3].zeroTotals();
00139 }
00140 
00141 void EthercatDeviceDiagnostics::accumulate(const et1x00_error_counters &n, const et1x00_error_counters &p)
00142 {
00143   pdiErrorTotal_ += n.pdi_error - p.pdi_error;
00144   epuErrorTotal_ += n.epu_error - p.epu_error;
00145   for (unsigned i = 0; i < 4; ++i)
00146   {
00147     EthercatPortDiagnostics & pt(portDiagnostics_[i]);
00148     pt.rxErrorTotal += n.port[i].rx_error - p.port[i].rx_error;
00149     pt.forwardedRxErrorTotal += n.forwarded_rx_error[i] - p.forwarded_rx_error[i];
00150     pt.lostLinkTotal += n.lost_link[i] - p.lost_link[i];
00151     pt.invalidFrameTotal += n.port[i].invalid_frame - p.port[i].invalid_frame;
00152   }
00153 }
00154 
00155 void EthercatDeviceDiagnostics::collect(EthercatCom *com, EtherCAT_SlaveHandler *sh)
00156 {
00157   diagnosticsValid_ = false;
00158   diagnosticsFirst_ = false;
00159 
00160   // Check if device has been reset/power cycled using its node address
00161   // Node address initialize to 0 after device reset.
00162   // EML library will configure each node address to non-zero when it first starts
00163   // Device should respond to its node address, if it does not either:
00164   //  1. communication to device is not possible (lost/broken link)
00165   //  2. device was reset, and its fixed address setting is now 0
00166   {
00167     // Send a packet with both a Fixed address read (NPRW) and a positional read (APRD)
00168     // If the NPRD has a working counter == 0, but the APRD sees the correct number of devices,
00169     // then the node has likely been reset.
00170     // Also, get DL status regiseter with nprd telegram
00171     EC_Logic *logic = EC_Logic::instance();
00172     et1x00_dl_status dl_status;
00173     NPRD_Telegram nprd_telegram(logic->get_idx(),
00174                                 sh->get_station_address(),
00175                                 dl_status.BASE_ADDR,
00176                                 logic->get_wkc(),
00177                                 sizeof (dl_status),
00178                                 (unsigned char*) &dl_status);
00179     // Use positional read to re-count number of devices on chain
00180     unsigned char buf[1];
00181     uint16_t address = 0x0000;
00182     APRD_Telegram aprd_telegram(logic->get_idx(), // Index
00183                                 0, // Slave position on ethercat chain (auto increment address) (
00184                                 address, // ESC physical memory address (start address)
00185                                 logic->get_wkc(), // Working counter
00186                                 sizeof (buf), // Data Length,
00187                                 buf); // Buffer to put read result into
00188 
00189 
00190 
00191     // Chain both telegrams together
00192     nprd_telegram.attach(&aprd_telegram);
00193 
00194     EC_Ethernet_Frame frame(&nprd_telegram);
00195 
00196     // Send/Recv data from slave
00197     if (!com->txandrx_once(&frame))
00198     {
00199       // no response - broken link to device
00200       goto end;
00201     }
00202 
00203     devicesRespondingToNodeAddress_ = nprd_telegram.get_wkc();
00204     if (devicesRespondingToNodeAddress_ == 0)
00205     {
00206       // Device has not responded to its node address.
00207       if (aprd_telegram.get_adp() >= EtherCAT_AL::instance()->get_num_slaves())
00208       {
00209         resetDetected_ = true;
00210         goto end;
00211       }
00212     }
00213     else if (devicesRespondingToNodeAddress_ > 1)
00214     {
00215       // Can't determine much if two (or more) devices are responding to same request.
00216       goto end;
00217     }
00218     else
00219     {
00220       resetDetected_ = false;
00221     }
00222 
00223     // fill in port status information
00224     for (unsigned i = 0; i < 4; ++i)
00225     {
00226       EthercatPortDiagnostics & pt(portDiagnostics_[i]);
00227       pt.hasLink = dl_status.hasLink(i);
00228       pt.isClosed = dl_status.isClosed(i);
00229       pt.hasCommunication = dl_status.hasCommunication(i);
00230     }
00231   }
00232 
00233   { // read and accumulate communication error counters
00234     et1x00_error_counters e;
00235     assert(sizeof (e) == (0x314 - 0x300));
00236     if (0 != EthercatDevice::readData(com, sh, e.BASE_ADDR, &e, sizeof (e), EthercatDevice::FIXED_ADDR))
00237     {
00238       goto end;
00239     }
00240 
00241     // If this previously tried to clear the error counters but d/n get a response
00242     // then use the newly read values to guess if they got cleared or not.
00243     if (errorCountersMayBeCleared_)
00244     {
00245       if (!e.isGreaterThan(errorCountersPrev_))
00246       {
00247         errorCountersPrev_.zero();
00248       }
00249       errorCountersMayBeCleared_ = false;
00250     }
00251     if (errorCountersPrev_.isGreaterThan(e))
00252     {
00253       ROS_ERROR("Device %d : previous port error counters less current values", sh->get_ring_position());
00254     }
00255 
00256     // Accumulate
00257     this->accumulate(e, errorCountersPrev_);
00258     errorCountersPrev_ = e;
00259 
00260     // re-read and clear communication error counters
00261     if (e.isGreaterThan(50))
00262     {
00263       if (0 != EthercatDevice::readWriteData(com, sh, e.BASE_ADDR, &e, sizeof (e), EthercatDevice::FIXED_ADDR))
00264       {
00265         // Packet got lost... Can't know for sure that error counters got cleared
00266         errorCountersMayBeCleared_ = true;
00267         goto end;
00268       }
00269       // We read and cleared error counters in same packet, accumulate any error counter differences
00270       this->accumulate(e, errorCountersPrev_);
00271       errorCountersPrev_.zero();
00272     }
00273   }
00274 
00275   // Everything was read successfully
00276   diagnosticsValid_ = true;
00277 
00278 end:
00279   return;
00280 }
00281 
00282 void EthercatDeviceDiagnostics::publish(diagnostic_updater::DiagnosticStatusWrapper &d, unsigned numPorts) const
00283 {
00284   if (numPorts > 4)
00285   {
00286     assert(numPorts < 4);
00287     numPorts = 4;
00288   }
00289   assert(numPorts > 0);
00290 
00291   d.addf("Reset detected", "%s", (resetDetected_ ? "Yes" : "No"));
00292   d.addf("Valid", "%s", (diagnosticsValid_ ? "Yes" : "No"));
00293 
00294   d.addf("EPU Errors", "%lld", epuErrorTotal_);
00295   d.addf("PDI Errors", "%lld", pdiErrorTotal_);
00296   ostringstream os, port;
00297   for (unsigned i = 0; i < numPorts; ++i)
00298   {
00299     const EthercatPortDiagnostics & pt(portDiagnostics_[i]);
00300     port.str("");
00301     port << " Port " << i;
00302     os.str("");
00303     os << "Status" << port.str();
00304     d.addf(os.str(), "%s Link, %s, %s Comm",
00305            pt.hasLink ? "Has" : "No",
00306            pt.isClosed ? "Closed" : "Open",
00307            pt.hasCommunication ? "Has" : "No");
00308     os.str("");
00309     os << "RX Error" << port.str();
00310     d.addf(os.str(), "%lld", pt.rxErrorTotal);
00311     os.str("");
00312     os << "Forwarded RX Error" << port.str();
00313     d.addf(os.str(), "%lld", pt.forwardedRxErrorTotal);
00314     os.str("");
00315     os << "Invalid Frame" << port.str();
00316     d.addf(os.str(), "%lld", pt.invalidFrameTotal);
00317     os.str("");
00318     os << "Lost Link" << port.str();
00319     d.addf(os.str(), "%lld", pt.lostLinkTotal);
00320   }
00321 
00322   if (resetDetected_)
00323   {
00324     d.mergeSummaryf(d.ERROR, "Device reset likely");
00325   }
00326   else if (devicesRespondingToNodeAddress_ > 1)
00327   {
00328     d.mergeSummaryf(d.ERROR, "More than one device (%d) responded to node address", devicesRespondingToNodeAddress_);
00329   }
00330   else
00331   {
00332     if (diagnosticsFirst_)
00333     {
00334       d.mergeSummaryf(d.WARN, "Have not yet collected diagnostics");
00335     }
00336     else if (!diagnosticsValid_)
00337     {
00338       d.mergeSummaryf(d.WARN, "Could not collect diagnostics");
00339     }
00340     else
00341     {
00342       if (!portDiagnostics_[0].hasLink)
00343       {
00344         d.mergeSummaryf(d.WARN, "No link on port 0");
00345       }
00346     }
00347   }
00348 }
00349 
00350 void EthercatDevice::construct(EtherCAT_SlaveHandler *sh, int &start_address)
00351 {
00352   sh_ = sh;
00353   sh->set_fmmu_config(new EtherCAT_FMMU_Config(0));
00354   sh->set_pd_config(new EtherCAT_PD_Config(0));
00355 }
00356 
00357 EthercatDevice::EthercatDevice() : use_ros_(true)
00358 {
00359   sh_ = NULL;
00360   command_size_ = 0;
00361   status_size_ = 0;
00362   newDiagnosticsIndex_ = 0;
00363 
00364   int error = pthread_mutex_init(&newDiagnosticsIndexLock_, NULL);
00365   if (error != 0)
00366   {
00367     ROS_FATAL("Initializing indexLock failed : %s", strerror(error));
00368     sleep(1); // wait for ros to flush rosconsole output
00369     exit(EXIT_FAILURE);
00370   }
00371 
00372   error = pthread_mutex_init(&diagnosticsLock_, NULL);
00373   if (error != 0)
00374   {
00375     ROS_FATAL("Initializing diagnositcsLock failed : %s", strerror(error));
00376     sleep(1); // wait for ros to flush rosconsole output
00377     exit(EXIT_FAILURE);
00378   }
00379 }
00380 
00381 void EthercatDevice::collectDiagnostics(EthercatCom *com)
00382 {
00383   // Really, should not need this lock, since there should only be one thread updating diagnostics.
00384   pthread_mutex_lock(&diagnosticsLock_);
00385 
00386   // Get references to diagnostics... code is easier to read
00387   unsigned oldDiagnosticsIndex = (newDiagnosticsIndex_ + 1)&1;
00388   const EthercatDeviceDiagnostics &newDiag = deviceDiagnostics[newDiagnosticsIndex_];
00389   EthercatDeviceDiagnostics &oldDiag = deviceDiagnostics[oldDiagnosticsIndex];
00390 
00391   // copy new diagnostics values in old diagnostics, because diagnostic data use accumumlators
00392   oldDiag = newDiag;
00393 
00394   // Collect diagnostics data into "old" buffer.
00395   // This way the "new" buffer is never changed while the publishing thread may be using it.
00396   oldDiag.collect(com, sh_);
00397 
00398   // Got new diagnostics... swap buffers.
00399   // Publisher thread uses "new" buffer.  New to lock while swapping buffers.
00400   // Note : this is just changing an integer value, so it only takes a couple of instructions.
00401   pthread_mutex_lock(&newDiagnosticsIndexLock_);
00402   newDiagnosticsIndex_ = oldDiagnosticsIndex;
00403   pthread_mutex_unlock(&newDiagnosticsIndexLock_);
00404 
00405   // Done, unlock
00406   pthread_mutex_unlock(&diagnosticsLock_);
00407 }
00408 
00409 int EthercatDevice::readWriteData(EthercatCom *com, EtherCAT_SlaveHandler *sh, uint16_t address, void* buffer, uint16_t length, AddrMode addrMode)
00410 {
00411   unsigned char *p = (unsigned char *) buffer;
00412   EC_Logic *logic = EC_Logic::instance();
00413 
00414   NPRW_Telegram nprw_telegram(logic->get_idx(),
00415                               sh->get_station_address(),
00416                               address,
00417                               logic->get_wkc(),
00418                               length,
00419                               p);
00420 
00421   APRW_Telegram aprw_telegram(logic->get_idx(), // Index
00422                               -sh->get_ring_position(), // Slave position on ethercat chain (auto increment address) (
00423                               address, // ESC physical memory address (start address)
00424                               logic->get_wkc(), // Working counter
00425                               length, // Data Length,
00426                               p); // Buffer to put read result into
00427 
00428   // Put read telegram in ros_ethercat_eml/ethernet frame
00429   EC_Telegram * telegram = NULL;
00430   if (addrMode == FIXED_ADDR)
00431   {
00432     telegram = &nprw_telegram;
00433   }
00434   else if (addrMode == POSITIONAL_ADDR)
00435   {
00436     telegram = &aprw_telegram;
00437   }
00438   else
00439   {
00440     assert(0);
00441     return -1;
00442   }
00443 
00444   // Put telegram in ros_ethercat_eml/ethernet frame
00445   EC_Ethernet_Frame frame(telegram);
00446 
00447   // Send/Recv data from slave
00448   if (!com->txandrx_once(&frame))
00449   {
00450     return -1;
00451   }
00452 
00453   // In some cases (clearing status mailbox) this is expected to occur
00454   if (telegram->get_wkc() != 3)
00455   {
00456     return -2;
00457   }
00458 
00459   return 0;
00460 }
00461 
00462 int EthercatDevice::readData(EthercatCom *com, EtherCAT_SlaveHandler *sh, uint16_t address, void* buffer, uint16_t length, AddrMode addrMode)
00463 {
00464   unsigned char *p = (unsigned char *) buffer;
00465   EC_Logic *logic = EC_Logic::instance();
00466 
00467   NPRD_Telegram nprd_telegram(logic->get_idx(),
00468                               sh->get_station_address(),
00469                               address,
00470                               logic->get_wkc(),
00471                               length,
00472                               p);
00473 
00474   APRD_Telegram aprd_telegram(logic->get_idx(), // Index
00475                               -sh->get_ring_position(), // Slave position on ethercat chain (auto increment address) (
00476                               address, // ESC physical memory address (start address)
00477                               logic->get_wkc(), // Working counter
00478                               length, // Data Length,
00479                               p); // Buffer to put read result into
00480 
00481   // Put read telegram in ros_ethercat_eml/ethernet frame
00482   EC_Telegram * telegram = NULL;
00483   if (addrMode == FIXED_ADDR)
00484   {
00485     telegram = &nprd_telegram;
00486   }
00487   else if (addrMode == POSITIONAL_ADDR)
00488   {
00489     telegram = &aprd_telegram;
00490   }
00491   else
00492   {
00493     assert(0);
00494     return -1;
00495   }
00496 
00497   // Put telegram in ros_ethercat_eml/ethernet frame
00498   EC_Ethernet_Frame frame(telegram);
00499 
00500   // Send/Recv data from slave
00501   if (!com->txandrx(&frame))
00502   {
00503     return -1;
00504   }
00505 
00506   // In some cases (clearing status mailbox) this is expected to occur
00507   if (telegram->get_wkc() != 1)
00508   {
00509     return -2;
00510   }
00511 
00512   return 0;
00513 }
00514 
00515 int EthercatDevice::writeData(EthercatCom *com, EtherCAT_SlaveHandler *sh, uint16_t address, void const* buffer, uint16_t length, AddrMode addrMode)
00516 {
00517   unsigned char const *p = (unsigned char const*) buffer;
00518   EC_Logic *logic = EC_Logic::instance();
00519 
00520   NPWR_Telegram npwr_telegram(logic->get_idx(),
00521                               sh->get_station_address(),
00522                               address,
00523                               logic->get_wkc(),
00524                               length,
00525                               p);
00526 
00527   APWR_Telegram apwr_telegram(logic->get_idx(), // Index
00528                               -sh->get_ring_position(), // Slave position on ethercat chain (auto increment address) (
00529                               address, // ESC physical memory address (start address)
00530                               logic->get_wkc(), // Working counter
00531                               length, // Data Length,
00532                               p); // Buffer to put read result into
00533 
00534   // Put read telegram in ros_ethercat_eml/ethernet frame
00535   EC_Telegram * telegram = NULL;
00536   if (addrMode == FIXED_ADDR)
00537   {
00538     telegram = &npwr_telegram;
00539   }
00540   else if (addrMode == POSITIONAL_ADDR)
00541   {
00542     telegram = &apwr_telegram;
00543   }
00544   else
00545   {
00546     assert(0);
00547     return -1;
00548   }
00549 
00550   // Put telegram in ros_ethercat_eml/ethernet frame
00551   EC_Ethernet_Frame frame(telegram);
00552 
00553   // Send/Recv data from slave
00554   if (!com->txandrx(&frame))
00555   {
00556     return -1;
00557   }
00558 
00559   if (telegram->get_wkc() != 1)
00560   {
00561     return -2;
00562   }
00563 
00564   return 0;
00565 }
00566 
00567 void EthercatDevice::ethercatDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &d, unsigned numPorts)
00568 {
00569   if (numPorts > 4)
00570   {
00571     assert(numPorts < 4);
00572     numPorts = 4;
00573   }
00574 
00575   // By locking index, diagnostics double-buffer cannot be swapped.
00576   // Update thread is only allowed to change 'old' diagnostics buffer,
00577   //  so new buffer data cannot be changed while index lock is held.
00578   pthread_mutex_lock(&newDiagnosticsIndexLock_);
00579 
00580   const EthercatDeviceDiagnostics &newDiag = deviceDiagnostics[newDiagnosticsIndex_];
00581   newDiag.publish(d, numPorts);
00582 
00583   pthread_mutex_unlock(&newDiagnosticsIndexLock_);
00584 }
00585 
00586 void EthercatDevice::diagnostics(diagnostic_updater::DiagnosticStatusWrapper &d, unsigned char *buffer)
00587 {
00588   stringstream str;
00589   str << "EtherCAT Device (" << std::setw(2) << std::setfill('0') << sh_->get_ring_position() << ")";
00590   d.name = str.str();
00591   str.str("");
00592   str << sh_->get_product_code() << '-' << sh_->get_serial();
00593   d.hardware_id = str.str();
00594 
00595   d.message = "";
00596   d.level = 0;
00597 
00598   d.clear();
00599   d.addf("Position", "%02d", sh_->get_ring_position());
00600   d.addf("Product code", "%08x", sh_->get_product_code());
00601   d.addf("Serial", "%08x", sh_->get_serial());
00602   d.addf("Revision", "%08x", sh_->get_revision());
00603 
00604   this->ethercatDiagnostics(d, 4); //assume unknown device has 4 ports
00605 }
00606 
00607 void EthercatDevice::multiDiagnostics(vector<diagnostic_msgs::DiagnosticStatus> &vec, unsigned char *buffer)
00608 {
00609   // Clean up recycled status object before reusing it.
00610   diagnostic_status_.clearSummary();
00611   diagnostic_status_.clear();
00612 
00613   // If child-class does not implement multiDiagnostics(), fall back to using slave's diagnostic() function
00614   diagnostics(diagnostic_status_, buffer);
00615   vec.push_back(diagnostic_status_);
00616 }


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