ethercat_hardware.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2008, Willow Garage, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of the Willow Garage nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
36 
37 #include <ethercat/ethercat_xenomai_drv.h>
38 #include <dll/ethercat_dll.h>
39 #include <dll/ethercat_device_addressed_telegram.h>
40 
41 #include <sstream>
42 
43 #include <net/if.h>
44 #include <sys/ioctl.h>
45 #include <boost/foreach.hpp>
46 #include <boost/regex.hpp>
47 
49 
50  txandrx_errors_(0),
51  device_count_(0),
52  pd_error_(false),
53  halt_after_reset_(false),
54  reset_motors_service_count_(0),
55  halt_motors_service_count_(0),
56  halt_motors_error_count_(0),
57  motors_halted_(false),
58  motors_halted_reason_("")
59 {
61 }
62 
64 {
65  max_pack_command_ = 0.0;
66  max_txandrx_ = 0.0;
67  max_unpack_state_ = 0.0;
68  max_publish_ = 0.0;
69 }
70 
71 EthercatHardware::EthercatHardware(const std::string& name) :
72  hw_(0), node_(ros::NodeHandle(name)),
73  ni_(0), this_buffer_(0), prev_buffer_(0), buffer_size_(0), halt_motors_(true), reset_state_(0),
74  max_pd_retries_(10),
75  diagnostics_publisher_(node_),
76  motor_publisher_(node_, "motors_halted", 1, true),
77  device_loader_("ethercat_hardware", "EthercatDevice")
78 {
79 
80 }
81 
83 {
85  for (uint32_t i = 0; i < slaves_.size(); ++i)
86  {
87  EC_FixedStationAddress fsa(i + 1);
88  EtherCAT_SlaveHandler *sh = em_->get_slave_handler(fsa);
89  if (sh) sh->to_state(EC_PREOP_STATE);
90  }
91  if (ni_)
92  {
94  }
95  delete[] buffers_;
96  delete hw_;
97  delete oob_com_;
99 }
100 
101 
102 void EthercatHardware::changeState(EtherCAT_SlaveHandler *sh, EC_State new_state)
103 {
104  unsigned product_code = sh->get_product_code();
105  unsigned serial = sh->get_serial();
106  uint32_t revision = sh->get_revision();
107  unsigned slave = sh->get_station_address()-1;
108 
109  if (!sh->to_state(new_state))
110  {
111  ROS_FATAL("Cannot goto state %d for slave #%d, product code: %u (0x%X), serial: %u (0x%X), revision: %d (0x%X)",
112  new_state, slave, product_code, product_code, serial, serial, revision, revision);
113  if ((product_code==0xbaddbadd) || (serial==0xbaddbadd) || (revision==0xbaddbadd))
114  ROS_FATAL("Note: 0xBADDBADD indicates that the value was not read correctly from device.");
115  exit(EXIT_FAILURE);
116  }
117 }
118 
119 void EthercatHardware::init(char *interface, bool allow_unprogrammed)
120 {
121  // open temporary socket to use with ioctl
122  int sock = socket(PF_INET, SOCK_DGRAM, 0);
123  if (sock < 0) {
124  int error = errno;
125  ROS_FATAL("Couldn't open temp socket : %s", strerror(error));
126  sleep(1);
127  exit(EXIT_FAILURE);
128  }
129 
130  struct ifreq ifr;
131  strncpy(ifr.ifr_name, interface, IFNAMSIZ);
132  if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
133  int error = errno;
134  ROS_FATAL("Cannot get interface flags for %s: %s", interface, strerror(error));
135  sleep(1);
136  exit(EXIT_FAILURE);
137  }
138 
139  close(sock);
140  sock = -1;
141 
142  if (!(ifr.ifr_flags & IFF_UP)) {
143  ROS_FATAL("Interface %s is not UP. Try : ifup %s", interface, interface);
144  sleep(1);
145  exit(EXIT_FAILURE);
146  }
147  if (!(ifr.ifr_flags & IFF_RUNNING)) {
148  ROS_FATAL("Interface %s is not RUNNING. Is cable plugged in and device powered?", interface);
149  sleep(1);
150  exit(EXIT_FAILURE);
151  }
152 
153 
154  // Initialize network interface
155  interface_ = interface;
156  if ((ni_ = init_ec(interface)) == NULL)
157  {
158  ROS_FATAL("Unable to initialize interface: %s", interface);
159  sleep(1);
160  exit(EXIT_FAILURE);
161  }
162 
163  oob_com_ = new EthercatOobCom(ni_);
164 
165  // Initialize Application Layer (AL)
166  EtherCAT_DataLinkLayer::instance()->attach(ni_);
167  if ((al_ = EtherCAT_AL::instance()) == NULL)
168  {
169  ROS_FATAL("Unable to initialize Application Layer (AL): %p", al_);
170  sleep(1);
171  exit(EXIT_FAILURE);
172  }
173 
174  int num_ethercat_devices_ = al_->get_num_slaves();
175  if (num_ethercat_devices_ == 0)
176  {
177  ROS_FATAL("Unable to locate any slaves");
178  sleep(1);
179  exit(EXIT_FAILURE);
180  }
181 
182  // Initialize Master
183  if ((em_ = EtherCAT_Master::instance()) == NULL)
184  {
185  ROS_FATAL("Unable to initialize EtherCAT_Master: %p", em_);
186  sleep(1);
187  exit(EXIT_FAILURE);
188  }
189 
190  // Size slaves vector to hold appropriate number of device pointers
191  slaves_.resize(num_ethercat_devices_);
192 
193  // Make temporary list of slave handles
194  std::vector<EtherCAT_SlaveHandler*> slave_handles;
195  for (unsigned int slave = 0; slave < slaves_.size(); ++slave)
196  {
197  EC_FixedStationAddress fsa(slave + 1);
198  EtherCAT_SlaveHandler *sh = em_->get_slave_handler(fsa);
199  if (sh == NULL)
200  {
201  ROS_FATAL("Unable to get slave handler #%d", slave);
202  sleep(1);
203  exit(EXIT_FAILURE);
204  }
205  slave_handles.push_back(sh);
206  }
207 
208  // Configure EtherCAT slaves
209  BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
210  {
211  unsigned slave = sh->get_station_address()-1;
212  if ((slaves_[slave] = configSlave(sh)) == NULL)
213  {
214  ROS_FATAL("Unable to configure slave #%d", slave);
215  sleep(1);
216  exit(EXIT_FAILURE);
217  }
218  buffer_size_ += slaves_[slave]->command_size_ + slaves_[slave]->status_size_;
219  }
220 
221  // Configure any non-ethercat slaves (appends devices to slaves_ vector)
223 
224  // Move slave from INIT to PREOP
225  BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
226  {
227  changeState(sh,EC_PREOP_STATE);
228  }
229 
230  // Move slave from PREOP to SAFEOP
231  BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
232  {
233  changeState(sh,EC_SAFEOP_STATE);
234  }
235 
236  // Move slave from SAFEOP to OP
237  // TODO : move to OP after initializing slave process data
238  BOOST_FOREACH(EtherCAT_SlaveHandler *sh, slave_handles)
239  {
240  changeState(sh,EC_OP_STATE);
241  }
242 
243  // Allocate buffers to send and receive commands
244  buffers_ = new unsigned char[2 * buffer_size_];
247 
248  // Make sure motors are disabled, also collect status data
249  memset(this_buffer_, 0, 2 * buffer_size_);
250  if (!txandrx_PD(buffer_size_, this_buffer_, 20))
251  {
252  ROS_FATAL("No communication with devices");
253  sleep(1);
254  exit(EXIT_FAILURE);
255  }
256 
257  // prev_buffer should contain valid status data when update function is first used
258  memcpy(prev_buffer_, this_buffer_, buffer_size_);
259 
260  // Create pr2_hardware_interface::HardwareInterface
264 
265  // Initialize slaves
266  //set<string> actuator_names;
267  for (unsigned int slave = 0; slave < slaves_.size(); ++slave)
268  {
269  if (slaves_[slave]->initialize(hw_, allow_unprogrammed) < 0)
270  {
271  EtherCAT_SlaveHandler *sh = slaves_[slave]->sh_;
272  if (sh != NULL)
273  {
274  ROS_FATAL("Unable to initialize slave #%d, product code: %d, revision: %d, serial: %d",
275  slave, sh->get_product_code(), sh->get_revision(), sh->get_serial());
276  sleep(1);
277  }
278  else
279  {
280  ROS_FATAL("Unable to initialize slave #%d", slave);
281  }
282  exit(EXIT_FAILURE);
283  }
284  }
285 
286 
287  { // Initialization is now complete. Reduce timeout of EtherCAT txandrx for better realtime performance
288  // Allow timeout to be configured at program load time with rosparam.
289  // This will allow tweaks for systems with different realtime performace
290  static const int MAX_TIMEOUT = 100000; // 100ms = 100,000us
291  static const int DEFAULT_TIMEOUT = 20000; // default to timeout to 20000us = 20ms
292  int timeout;
293  if (!node_.getParam("realtime_socket_timeout", timeout))
294  {
295  timeout = DEFAULT_TIMEOUT;
296  }
297  if ((timeout <= 1) || (timeout > MAX_TIMEOUT))
298  {
299  int old_timeout = timeout;
300  timeout = std::max(1, std::min(MAX_TIMEOUT, timeout));
301  ROS_WARN("Invalid timeout (%d) for socket, using %d", old_timeout, timeout);
302  }
303  if (set_socket_timeout(ni_, timeout))
304  {
305  ROS_FATAL("Error setting socket timeout to %d", timeout);
306  sleep(1);
307  exit(EXIT_FAILURE);
308  }
309  timeout_ = timeout;
310 
311  // When packet constaining process data is does not return after a given timeout, it is
312  // assumed to be dropped and the process data will automatically get re-sent.
313  // After a number of retries, the driver will halt motors as a safety precaution.
314  //
315  // The following code allows the number of process data retries to be changed with a rosparam.
316  // This is needed because lowering the txandrx timeout makes it more likely that a
317  // performance hickup in network or OS causes will cause the motors to halt.
318  //
319  // If number of retries is not specified, use a formula that allows 100ms of dropped packets
320  int max_pd_retries = MAX_TIMEOUT / timeout; // timeout is in nanoseconds : 20msec = 20000usec
321  static const int MAX_RETRIES=50, MIN_RETRIES=1;
322  node_.getParam("max_pd_retries", max_pd_retries);
323  // Make sure motor halt due to dropped packet takes less than 1/10 of a second
324  if ((max_pd_retries * timeout) > (MAX_TIMEOUT))
325  {
326  max_pd_retries = MAX_TIMEOUT / timeout;
327  ROS_WARN("Max PD retries is too large for given timeout. Limiting value to %d", max_pd_retries);
328  }
329  if ((max_pd_retries < MIN_RETRIES) || (max_pd_retries > MAX_RETRIES))
330  {
331  max_pd_retries = std::max(MIN_RETRIES,std::min(MAX_RETRIES,max_pd_retries));
332  ROS_WARN("Limiting max PD retries to %d", max_pd_retries);
333  }
334  max_pd_retries = std::max(MIN_RETRIES,std::min(MAX_RETRIES,max_pd_retries));
335  max_pd_retries_ = max_pd_retries;
336  }
337 
338  diagnostics_publisher_.initialize(interface_, buffer_size_, slaves_, num_ethercat_devices_, timeout_, max_pd_retries_);
339 }
340 
341 
343  node_(node),
344  diagnostics_ready_(false),
345  publisher_(node_.advertise<diagnostic_msgs::DiagnosticArray>("/diagnostics", 1)),
346  diagnostics_buffer_(NULL),
347  last_dropped_packet_count_(0),
348  last_dropped_packet_time_(0)
349 {
350 }
351 
353 {
354  delete[] diagnostics_buffer_;
355 }
356 
357 void EthercatHardwareDiagnosticsPublisher::initialize(const string &interface, unsigned int buffer_size,
358  const std::vector<boost::shared_ptr<EthercatDevice> > &slaves,
359  unsigned int num_ethercat_devices,
360  unsigned timeout, unsigned max_pd_retries)
361 {
362  interface_ = interface;
363  buffer_size_ = buffer_size;
364  slaves_ = slaves;
365  num_ethercat_devices_ = num_ethercat_devices;
366  timeout_ = timeout;
367  max_pd_retries_ = max_pd_retries;
368 
369  diagnostics_buffer_ = new unsigned char[buffer_size_];
370 
371  // Initialize diagnostic data structures
372  diagnostic_array_.status.reserve(slaves_.size() + 1);
373  values_.reserve(10);
374 
376 
378 }
379 
381  const unsigned char *buffer,
382  const EthercatHardwareDiagnostics &diagnostics)
383 {
384  boost::try_to_lock_t try_lock;
385  boost::unique_lock<boost::mutex> lock(diagnostics_mutex_, try_lock);
386  if (lock.owns_lock())
387  {
388  // Make copies of diagnostic data for dianostic thread
389  memcpy(diagnostics_buffer_, buffer, buffer_size_);
390  diagnostics_ = diagnostics;
391  // Trigger diagnostics publish thread
392  diagnostics_ready_ = true;
394  }
395 }
396 
398 {
399  diagnostics_thread_.interrupt();
400  diagnostics_thread_.join();
402 }
403 
405 {
406  try {
407  while (1) {
408  boost::unique_lock<boost::mutex> lock(diagnostics_mutex_);
409  while (!diagnostics_ready_) {
410  diagnostics_cond_.wait(lock);
411  }
412  diagnostics_ready_ = false;
414  }
415  } catch (boost::thread_interrupted const&) {
416  return;
417  }
418 }
419 
422  const string &key,
423  const accumulator_set<double, stats<tag::max, tag::mean> > &acc,
424  double max)
425 {
426  status.addf(key + " Avg (us)", "%5.4f", extract_result<tag::mean>(acc) * 1e6); // Average over last 1 second
427  status.addf(key + " 1 Sec Max (us)", "%5.4f", extract_result<tag::max>(acc) * 1e6); // Max over last 1 second
428  status.addf(key + " Max (us)", "%5.4f", max * 1e6); // Max since start
429 }
430 
432 {
433  ros::Time now(ros::Time::now());
434 
435  // Publish status of EtherCAT master
437  status_.clear();
438 
439  status_.name = "EtherCAT Master";
441  {
442  std::ostringstream desc;
443  desc << "Motors halted";
445  {
446  desc << " soon after reset";
447  }
448  desc << " (" << diagnostics_.motors_halted_reason_ << ")";
449  status_.summary(status_.ERROR, desc.str());
450  } else {
451  status_.summary(status_.OK, "OK");
452  }
453 
455  {
456  status_.mergeSummary(status_.ERROR, "Error sending proccess data");
457  }
458 
459  status_.add("Motors halted", diagnostics_.motors_halted_ ? "true" : "false");
460  status_.addf("EtherCAT devices (expected)", "%d", num_ethercat_devices_);
461  status_.addf("EtherCAT devices (current)", "%d", diagnostics_.device_count_);
463  //status_.addf("Reset state", "%d", reset_state_);
464 
465  status_.addf("Timeout (us)", "%d", timeout_);
466  status_.addf("Max PD Retries", "%d", max_pd_retries_);
467 
468  // Produce warning if number of devices changed after device initalization
470  status_.mergeSummary(status_.WARN, "Number of EtherCAT devices changed");
471  }
472 
475  {
479  }
480 
481  status_.addf("EtherCAT Process Data txandrx errors", "%d", diagnostics_.txandrx_errors_);
482 
483  status_.addf("Reset motors service count", "%d", diagnostics_.reset_motors_service_count_);
484  status_.addf("Halt motors service count", "%d", diagnostics_.halt_motors_service_count_);
485  status_.addf("Halt motors error count", "%d", diagnostics_.halt_motors_error_count_);
486 
487  { // Publish ethercat network interface counters
488  const struct netif_counters *c = &diagnostics_.counters_;
489  status_.add("Input Thread", (diagnostics_.input_thread_is_stopped_ ? "Stopped" : "Running"));
490  status_.addf("Sent Packets", "%llu", (unsigned long long)c->sent);
491  status_.addf("Received Packets", "%llu", (unsigned long long)c->received);
492  status_.addf("Collected Packets", "%llu", (unsigned long long)c->collected);
493  status_.addf("Dropped Packets", "%llu", (unsigned long long)c->dropped);
494  status_.addf("TX Errors", "%llu", (unsigned long long)c->tx_error);
495  status_.addf("TX Network Down", "%llu", (unsigned long long)c->tx_net_down);
496  status_.addf("TX Would Block", "%llu", (unsigned long long)c->tx_would_block);
497  status_.addf("TX No Buffers", "%llu", (unsigned long long)c->tx_no_bufs);
498  status_.addf("TX Queue Full", "%llu", (unsigned long long)c->tx_full);
499  status_.addf("RX Runt Packet", "%llu", (unsigned long long)c->rx_runt_pkt);
500  status_.addf("RX Not EtherCAT", "%llu", (unsigned long long)c->rx_not_ecat);
501  status_.addf("RX Other EML", "%llu", (unsigned long long)c->rx_other_eml);
502  status_.addf("RX Bad Index", "%llu", (unsigned long long)c->rx_bad_index);
503  status_.addf("RX Bad Sequence", "%llu", (unsigned long long)c->rx_bad_seqnum);
504  status_.addf("RX Duplicate Sequence", "%llu", (unsigned long long)c->rx_dup_seqnum);
505  status_.addf("RX Duplicate Packet", "%llu", (unsigned long long)c->rx_dup_pkt);
506  status_.addf("RX Bad Order", "%llu", (unsigned long long)c->rx_bad_order);
507  status_.addf("RX Late Packet", "%llu", (unsigned long long)c->rx_late_pkt);
508  status_.addf("RX Late Packet RTT", "%llu", (unsigned long long)c->rx_late_pkt_rtt_us);
509 
510  double rx_late_pkt_rtt_us_avg = 0.0;
511  if (c->rx_late_pkt > 0) {
512  rx_late_pkt_rtt_us_avg = ((double)c->rx_late_pkt_rtt_us_sum)/((double)c->rx_late_pkt);
513  }
514  status_.addf("RX Late Packet Avg RTT", "%f", rx_late_pkt_rtt_us_avg);
515 
516  // Check for newly dropped packets
517  if (c->dropped > last_dropped_packet_count_)
518  {
520  last_dropped_packet_count_ = c->dropped;
521  }
522  }
523 
524  // Create error message if packet has been dropped recently
526  {
527  status_.mergeSummaryf(status_.WARN, "Dropped packets in last %d seconds", dropped_packet_warning_hold_time_);
528  }
529 
530  diagnostic_array_.status.clear();
531  diagnostic_array_.status.push_back(status_);
532 
533  // Also, collect diagnostic statuses of all EtherCAT device
534  unsigned char *current = diagnostics_buffer_;
535  for (unsigned int s = 0; s < slaves_.size(); ++s)
536  {
537  slaves_[s]->multiDiagnostics(diagnostic_array_.status, current);
538  current += slaves_[s]->command_size_ + slaves_[s]->status_size_;
539  }
540 
541  // Publish status of each EtherCAT device
542  diagnostic_array_.header.stamp = ros::Time::now();
544 }
545 
546 void EthercatHardware::update(bool reset, bool halt)
547 {
548  // Update current time
549  ros::Time update_start_time(ros::Time::now());
550 
551  unsigned char *this_buffer, *prev_buffer;
552 
553  // Convert HW Interface commands to MCB-specific buffers
554  this_buffer = this_buffer_;
555 
556  if (halt)
557  {
559  haltMotors(false /*no error*/, "service request");
560  }
561 
562  // Resetting devices should clear device errors and release devices from halt.
563  // To reduce load on power system, release devices from halt, one at a time
564  const unsigned CYCLES_PER_HALT_RELEASE = 2; // Wait two cycles between releasing each device
565  if (reset)
566  {
568  reset_state_ = CYCLES_PER_HALT_RELEASE * slaves_.size() + 5;
569  last_reset_ = update_start_time;
571  }
572  bool reset_devices = reset_state_ == CYCLES_PER_HALT_RELEASE * slaves_.size() + 3;
573  if (reset_devices)
574  {
575  halt_motors_ = false;
579  diagnostics_.pd_error_ = false;
580  }
581 
582  for (unsigned int s = 0; s < slaves_.size(); ++s)
583  {
584  // Pack the command structures into the EtherCAT buffer
585  // Disable the motor if they are halted or coming out of reset
586  bool halt_device = halt_motors_ || ((s*CYCLES_PER_HALT_RELEASE+1) < reset_state_);
587  slaves_[s]->packCommand(this_buffer, halt_device, reset_devices);
588  this_buffer += slaves_[s]->command_size_ + slaves_[s]->status_size_;
589  }
590 
591  // Transmit process data
592  ros::Time txandrx_start_time(ros::Time::now()); // Also end time for pack_command_stage
593  diagnostics_.pack_command_acc_((txandrx_start_time-update_start_time).toSec());
594 
595  // Send/receive device proccess data
596  bool success = txandrx_PD(buffer_size_, this_buffer_, max_pd_retries_);
597 
598  ros::Time txandrx_end_time(ros::Time::now()); // Also begining of unpack_state
599  diagnostics_.txandrx_acc_((txandrx_end_time - txandrx_start_time).toSec());
600 
601  hw_->current_time_ = txandrx_end_time;
602 
603  if (!success)
604  {
605  // If process data didn't get sent after multiple retries, stop motors
606  haltMotors(true /*error*/, "communication error");
607  diagnostics_.pd_error_ = true;
608  }
609  else
610  {
611  // Convert status back to HW Interface
612  this_buffer = this_buffer_;
613  prev_buffer = prev_buffer_;
614  for (unsigned int s = 0; s < slaves_.size(); ++s)
615  {
616  if (!slaves_[s]->unpackState(this_buffer, prev_buffer) && !reset_devices)
617  {
618  haltMotors(true /*error*/, "device error");
619  }
620  this_buffer += slaves_[s]->command_size_ + slaves_[s]->status_size_;
621  prev_buffer += slaves_[s]->command_size_ + slaves_[s]->status_size_;
622  }
623 
624  if (reset_state_)
625  --reset_state_;
626 
627  unsigned char *tmp = this_buffer_;
628  this_buffer_ = prev_buffer_;
629  prev_buffer_ = tmp;
630  }
631 
632  ros::Time unpack_end_time;
634  {
635  unpack_end_time = ros::Time::now(); // also start of publish time
636  diagnostics_.unpack_state_acc_((unpack_end_time - txandrx_end_time).toSec());
637  }
638 
639  if ((update_start_time - last_published_) > ros::Duration(1.0))
640  {
641  last_published_ = update_start_time;
643  motor_publisher_.lock();
644  motor_publisher_.msg_.data = halt_motors_;
645  motor_publisher_.unlockAndPublish();
646  }
647 
649  {
650  ros::Time publish_end_time(ros::Time::now());
651  diagnostics_.publish_acc_((publish_end_time - unpack_end_time).toSec());
652  }
653 }
654 
655 
656 void EthercatHardware::haltMotors(bool error, const char* reason)
657 {
658  if (!halt_motors_)
659  {
660  // wasn't already halted
661  motor_publisher_.lock();
662  motor_publisher_.msg_.data = halt_motors_;
663  motor_publisher_.unlockAndPublish();
664 
666  if (error)
667  {
669  if ((ros::Time::now() - last_reset_) < ros::Duration(0.5))
670  {
671  // halted soon after reset
673  }
674  }
675  }
676 
678  halt_motors_ = true;
679 }
680 
681 
682 void EthercatHardware::updateAccMax(double &max, const accumulator_set<double, stats<tag::max, tag::mean> > &acc)
683 {
684  max = std::max(max, extract_result<tag::max>(acc));
685 }
686 
688 {
689  // Update max timing values
694 
695  // Grab stats and counters from input thread
696  diagnostics_.counters_ = ni_->counters;
697  diagnostics_.input_thread_is_stopped_ = bool(ni_->is_stopped);
698 
699  diagnostics_.motors_halted_ = halt_motors_;
700 
701  // Pass diagnostic data to publisher thread
702  diagnostics_publisher_.publish(this_buffer_, diagnostics_);
703 
704  // Clear statistics accumulators
705  static accumulator_set<double, stats<tag::max, tag::mean> > blank;
707  diagnostics_.txandrx_acc_ = blank;
709  diagnostics_.publish_acc_ = blank;
710 }
711 
712 
714 EthercatHardware::configSlave(EtherCAT_SlaveHandler *sh)
715 {
716  static int start_address = 0x00010000;
718  unsigned product_code = sh->get_product_code();
719  unsigned serial = sh->get_serial();
720  uint32_t revision = sh->get_revision();
721  unsigned slave = sh->get_station_address()-1;
722 
723  // The point of this code to find a class whose name matches the EtherCAT
724  // product ID for a given device.
725  // Thus device plugins would register themselves with PLUGIN_REGISTER_CLASS
726  //
727  // PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
728  //
729  // and in the plugin.xml, specify name="" inside the <class> tag:
730  //
731  // <class name="package/serial"
732  // base_class_type="ethercat_hardware::EthercatDevice" />
733  //
734  //
735  // For the WG05 driver (productID = 6805005), this statement would look
736  // something like:
737  //
738  // PLUGINLIB_EXPORT_CLASS(WG05, EthercatDevice)
739  //
740  // and in the plugin.xml:
741  //
742  // <class name="ethercat_hardware/6805005" type="WG05"
743  // base_class_type="EthercatDevice">
744  // <description>
745  // WG05 - Generic Motor Control Board
746  // </description>
747  // </class>
748  //
749  //
750  // Unfortunately, we don't know which ROS package that a particular driver is defined in.
751  // To account for this, we search through the list of class names, one-by-one and find string where
752  // last part of string matches product ID of device.
753  stringstream class_name_regex_str;
754  class_name_regex_str << "(.*/)?" << product_code;
755  boost::regex class_name_regex(class_name_regex_str.str(), boost::regex::extended);
756 
757  std::vector<std::string> classes = device_loader_.getDeclaredClasses();
758  std::string matching_class_name;
759 
760  BOOST_FOREACH(const std::string &class_name, classes)
761  {
762  if (regex_match(class_name, class_name_regex))
763  {
764  if (matching_class_name.size() != 0)
765  {
766  ROS_ERROR("Found more than 1 EtherCAT driver for device with product code : %d", product_code);
767  ROS_ERROR("First class name = '%s'. Second class name = '%s'",
768  class_name.c_str(), matching_class_name.c_str());
769  }
770  matching_class_name = class_name;
771  }
772  }
773 
774  if (matching_class_name.size() != 0)
775  {
776  //ROS_WARN("Using driver class '%s' for device with product code %d",
777  // matching_class_name.c_str(), product_code);
778  try {
779  p = device_loader_.createInstance(matching_class_name);
780  }
782  {
783  p.reset();
784  ROS_FATAL("Unable to load plugin for slave #%d, product code: %u (0x%X), serial: %u (0x%X), revision: %d (0x%X)",
785  slave, product_code, product_code, serial, serial, revision, revision);
786  ROS_FATAL("%s", e.what());
787  }
788  }
789  else
790  {
791  if ((product_code==0xbaddbadd) || (serial==0xbaddbadd) || (revision==0xbaddbadd))
792  {
793  ROS_FATAL("Note: 0xBADDBADD indicates that the value was not read correctly from device.");
794  ROS_FATAL("Perhaps you should power-cycle the MCBs");
795  }
796  else
797  {
798  ROS_ERROR("Unable to load plugin for slave #%d, product code: %u (0x%X), serial: %u (0x%X), revision: %d (0x%X)",
799  slave, product_code, product_code, serial, serial, revision, revision);
800  ROS_ERROR("Possible classes:");
801  BOOST_FOREACH(const std::string &class_name, classes)
802  {
803  ROS_ERROR(" %s", class_name.c_str());
804  }
805 
806  // TODO, use default plugin for ethercat devices that have no driver.
807  // This way, the EtherCAT chain still works.
808  }
809  }
810 
811  if (p != NULL)
812  {
813  p->construct(sh, start_address);
814  }
815 
816  return p;
817 }
818 
819 
821 EthercatHardware::configNonEthercatDevice(const std::string &name, const std::string &type)
822 {
824  try {
825  p = device_loader_.createInstance(type);
826  }
828  {
829  p.reset();
830  ROS_FATAL("Unable to load plugin for non-EtherCAT device '%s' with type: %s : %s"
831  , name.c_str(), type.c_str(), e.what());
832  }
833  if (p) {
834  ROS_INFO("Creating non-EtherCAT device '%s' of type '%s'", name.c_str(), type.c_str());
835  ros::NodeHandle nh(node_, "non_ethercat_devices/"+name);
836  p->construct(nh);
837  }
838  return p;
839 }
840 
841 
842 // Do this to get access to Struct (std::map) element of XmlRpcValue
844 {
845 public:
846  MyXmlRpcValue(XmlRpc::XmlRpcValue &value) : XmlRpc::XmlRpcValue(value) { }
847  XmlRpcValue::ValueStruct &getMap() {return *_value.asStruct;}
848 };
849 
851 {
852  // non-EtherCAT device drivers are descibed via struct named "non_ethercat_devices"
853  // Each element of "non_ethercat_devices" must be a struct that contains a "type" field.
854  // The element struct can also contain other elements that are used as configuration parameters
855  // for the device plugin.
856  // Example:
857  // non_ethercat_devices:
858  // netft1:
859  // type: NetFT
860  // address: 192.168.1.1
861  // publish_period: 0.01
862  // netft2:
863  // type: NetFT
864  // address: 192.168.1.2
865  // publish_period: 0.01
866  // dummy_device:
867  // type: Dummy
868  // msg: "This a dummy device"
869  //
870  // The name of the device is used to create a ros::NodeHandle that is
871  // that is passed to construct function of device class.
872  // NOTE: construct function is not the same as C++ construtor
873  if (!node_.hasParam("non_ethercat_devices"))
874  {
875  // It is perfectly fine if there is no list of non-ethercat devices
876  return;
877  }
878 
880  node_.getParam("non_ethercat_devices", devices);
881  if (devices.getType() != XmlRpc::XmlRpcValue::TypeStruct)
882  {
883  ROS_ERROR("Rosparam 'non_ethercat_devices' is not a struct type");
884  return;
885  }
886 
887  MyXmlRpcValue my_devices(devices);
888  typedef XmlRpc::XmlRpcValue::ValueStruct::value_type map_item_t ;
889  BOOST_FOREACH(map_item_t &item, my_devices.getMap())
890  {
891  const std::string &name(item.first);
892  XmlRpc::XmlRpcValue &device_info(item.second);
893 
894  if (device_info.getType() != XmlRpc::XmlRpcValue::TypeStruct)
895  {
896  ROS_ERROR("non_ethercat_devices/%s is not a struct type", name.c_str());
897  continue;
898  }
899 
900  if (!device_info.hasMember("type"))
901  {
902  ROS_ERROR("non_ethercat_devices/%s 'type' element", name.c_str());
903  continue;
904  }
905 
906  std::string type(static_cast<std::string>(device_info["type"]));
907 
909  configNonEthercatDevice(name,type);
910  if (new_device != NULL)
911  {
912  slaves_.push_back(new_device);
913  }
914  }
915 }
916 
917 
919 {
920  if (NULL == oob_com_)
921  return;
922 
923  { // Count number of devices
924  EC_Logic *logic = EC_Logic::instance();
925  unsigned char p[1];
926  EC_UINT length = sizeof(p);
927 
928  // Build read telegram, use slave position
929  APRD_Telegram status(logic->get_idx(), // Index
930  0, // Slave position on ethercat chain (auto increment address)
931  0, // ESC physical memory address (start address)
932  logic->get_wkc(), // Working counter
933  length, // Data Length,
934  p); // Buffer to put read result into
935 
936  // Put read telegram in ethercat/ethernet frame
937  EC_Ethernet_Frame frame(&status);
938  oob_com_->txandrx(&frame);
939 
940  // Worry about locking for single value?
941  diagnostics_.device_count_ = status.get_adp();
942  }
943 
944  for (unsigned i = 0; i < slaves_.size(); ++i)
945  {
947  d->collectDiagnostics(oob_com_);
948  }
949 }
950 
951 
952 // Prints (error) counter infomation of network interface driver
953 void EthercatHardware::printCounters(std::ostream &os)
954 {
955  const struct netif_counters &c(ni_->counters);
956  os << "netif counters :" << endl
957  << " sent = " << c.sent << endl
958  << " received = " << c.received << endl
959  << " collected = " << c.collected << endl
960  << " dropped = " << c.dropped << endl
961  << " tx_error = " << c.tx_error << endl
962  << " tx_net_down = " << c.tx_net_down << endl
963  << " tx_would_block= " << c.tx_would_block << endl
964  << " tx_no_bufs = " << c.tx_no_bufs << endl
965  << " tx_full = " << c.tx_full << endl
966  << " rx_runt_pkt = " << c.rx_runt_pkt << endl
967  << " rx_not_ecat = " << c.rx_not_ecat << endl
968  << " rx_other_eml = " << c.rx_other_eml << endl
969  << " rx_bad_index = " << c.rx_bad_index << endl
970  << " rx_bad_seqnum = " << c.rx_bad_seqnum << endl
971  << " rx_dup_seqnum = " << c.rx_dup_seqnum << endl
972  << " rx_dup_pkt = " << c.rx_dup_pkt << endl
973  << " rx_bad_order = " << c.rx_bad_order << endl
974  << " rx_late_pkt = " << c.rx_late_pkt << endl;
975 }
976 
977 
978 bool EthercatHardware::txandrx_PD(unsigned buffer_size, unsigned char* buffer, unsigned tries)
979 {
980  // Try multiple times to get proccess data to device
981  bool success = false;
982  for (unsigned i=0; i<tries && !success; ++i) {
983  // Try transmitting process data
984  success = em_->txandrx_PD(buffer_size_, this_buffer_);
985  if (!success) {
987  }
988  // Transmit new OOB data
989  oob_com_->tx();
990  }
991  return success;
992 }
993 
994 
995 bool EthercatHardware::publishTrace(int position, const string &reason, unsigned level, unsigned delay)
996 {
997  if (position >= (int)slaves_.size())
998  {
999  ROS_WARN("Invalid device position %d. Use 0-%d, or -1.", position, int(slaves_.size())-1);
1000  return false;
1001  }
1002 
1003  if (level > 2)
1004  {
1005  ROS_WARN("Invalid level : %d. Using level=2 (ERROR).", level);
1006  level = 2;
1007  }
1008 
1009  string new_reason("Manually triggered : " + reason);
1010 
1011  bool retval = false;
1012  if (position < 0)
1013  {
1014  for (unsigned i=0; i<slaves_.size(); ++i)
1015  {
1016  if (slaves_[i]->publishTrace(new_reason,level,delay))
1017  {
1018  retval = true;
1019  }
1020  }
1021  }
1022  else
1023  {
1024  retval = slaves_[position]->publishTrace(new_reason,level,delay);
1025  if (!retval)
1026  {
1027  ROS_WARN("Device %d does not support publishing trace", position);
1028  }
1029  }
1030  return retval;
1031 }
vector< diagnostic_msgs::KeyValue > values_
unsigned reset_motors_service_count_
Number of times reset_motor service has been used.
static void timingInformation(diagnostic_updater::DiagnosticStatusWrapper &status, const string &key, const accumulator_set< double, stats< tag::max, tag::mean > > &acc, double max)
Helper function for converting timing for diagnostics.
d
static const unsigned dropped_packet_warning_hold_time_
Number of seconds since late dropped packet to keep warning.
#define ROS_FATAL(...)
EthercatHardwareDiagnostics diagnostics_
Diagnostics information use by publish function.
uint16_t length
Definition: wg_util.h:119
void publish(const boost::shared_ptr< M > &message) const
EtherCAT_Master * em_
void diagnosticsThreadFunc()
Publishing thread main loop.
void haltMotors(bool error, const char *reason)
void wait(unique_lock< mutex > &m)
void summary(unsigned char lvl, const std::string s)
ROSCONSOLE_DECL void initialize()
pr2_hardware_interface::HardwareInterface * hw_
void initialize(const std::string &interface)
void init(char *interface, bool allow_unprogrammed)
Initialize the EtherCAT Master Library.
MyXmlRpcValue(XmlRpc::XmlRpcValue &value)
XmlRpcServer s
std::vector< boost::shared_ptr< EthercatDevice > > slaves_
struct netif * ni_
accumulator_set< double, stats< tag::max, tag::mean > > pack_command_acc_
time taken by all devices packCommand functions
unsigned halt_motors_service_count_
Number of time halt_motor service call is used.
unsigned halt_motors_error_count_
Number of transitions into halt state due to device error.
void stop()
Stops publishing thread. May block.
ros::NodeHandle node_
bool motors_halted_
True if motors are halted.
bool publishTrace(int position, const string &reason, unsigned level, unsigned delay)
Ask one or all EtherCAT devices to publish (motor) traces.
unsigned char * prev_buffer_
Type const & getType() const
#define ROS_WARN(...)
EthernetInterfaceInfo ethernet_interface_info_
Information about Ethernet interface used for EtherCAT communication.
void addf(const std::string &key, const char *format,...)
accumulator_set< double, stats< tag::max, tag::mean > > txandrx_acc_
time taken by to transmit and recieve process data
const char * motors_halted_reason_
reason that motors first halted
void publishDiagnostics()
Collects raw diagnostics data and passes it to diagnostics_publisher.
void publishDiagnostics()
Publishes diagnostics.
unsigned timeout_
Timeout (in microseconds) to used for sending/recieving packets once in realtime mode.
vector< EthercatDevice * > devices
Definition: motorconf.cpp:66
void collectDiagnostics()
Collects diagnotics from all devices.
unsigned int buffer_size_
bool txandrx_PD(unsigned buffer_size, unsigned char *buffer, unsigned tries)
Send process data.
uint16_t status
void update(bool reset, bool halt)
Send most recent motor commands and retrieve updates. This command must be run at a sufficient rate o...
boost::shared_ptr< EthercatDevice > configSlave(EtherCAT_SlaveHandler *sh)
#define ROS_INFO(...)
std::vector< boost::shared_ptr< EthercatDevice > > slaves_
uint64_t last_dropped_packet_count_
Count of dropped packets last diagnostics cycle.
unsigned char * buffers_
diagnostic_updater::DiagnosticStatusWrapper status_
unsigned int num_ethercat_devices_
EthercatHardwareDiagnosticsPublisher(ros::NodeHandle &node)
boost::shared_ptr< EthercatDevice > configNonEthercatDevice(const std::string &product_id, const std::string &data)
realtime_tools::RealtimePublisher< std_msgs::Bool > motor_publisher_
boost::condition_variable diagnostics_cond_
void initialize(const string &interface, unsigned int buffer_size, const std::vector< boost::shared_ptr< EthercatDevice > > &slaves, unsigned int num_ethercat_devices_, unsigned timeout, unsigned max_pd_retries)
Initializes hardware publish.
static void changeState(EtherCAT_SlaveHandler *sh, EC_State new_state)
EthercatOobCom * oob_com_
void mergeSummary(unsigned char lvl, const std::string s)
unsigned char * this_buffer_
bool hasMember(const std::string &name) const
struct netif_counters counters_
diagnostic_msgs::DiagnosticArray diagnostic_array_
static void updateAccMax(double &max, const accumulator_set< double, stats< tag::max, tag::mean > > &acc)
EthercatHardwareDiagnosticsPublisher diagnostics_publisher_
void publish(const unsigned char *buffer, const EthercatHardwareDiagnostics &diagnostics)
Triggers publishing of new diagnostics data.
~EthercatHardware()
Destructor.
unsigned timeout_
Timeout controls how long EtherCAT driver waits for packet before declaring it as dropped...
EthercatHardware(const std::string &name)
Constructor.
bool getParam(const std::string &key, std::string &s) const
unsigned max_pd_retries_
Number of times (in a row) to retry sending process data (realtime data) before halting motors...
static Time now()
void mergeSummaryf(unsigned char lvl, const char *format,...)
void add(const std::string &key, const T &val)
boost::mutex diagnostics_mutex_
mutex protects all class data and cond variable
bool hasParam(const std::string &key) const
void publishDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &d)
Collect and append ethernet interface diagnostics.
accumulator_set< double, stats< tag::max, tag::mean > > unpack_state_acc_
time taken by all devices updateState functions
ROSCPP_DECL int close_socket(socket_fd_t &socket)
void notify_one() BOOST_NOEXCEPT
void printCounters(std::ostream &os=std::cout)
static double min(double a, double b)
Definition: motor_model.cpp:4
unsigned max_pd_retries_
Max number of times to retry sending process data before halting motors.
#define ROS_ERROR(...)
bool halt_after_reset_
True if motor halt soon after motor reset.
ros::Time last_dropped_packet_time_
Time last packet was dropped 0 otherwise. Used for warning about dropped packets. ...
static const bool collect_extra_timing_
XmlRpcValue::ValueStruct & getMap()
accumulator_set< double, stats< tag::max, tag::mean > > publish_acc_
time taken by any publishing step in main loop


ethercat_hardware
Author(s): Rob Wheeler , Derek King
autogenerated on Fri Mar 15 2019 02:53:29