RTUnit.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ArmarX.
3  *
4  * Copyright (C) 2011-2017, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5  *
6  * ArmarX is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  * ArmarX is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * @package ArmarX
19  * @author Mirko Waechter( mirko.waechter at kit dot edu)
20  * @date 2018
21  * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22  * GNU General Public License
23  */
24 
25 
26 #include "RTUnit.h"
27 
28 #include <chrono>
29 #include <filesystem>
30 #include <sstream>
31 #include <thread>
32 
33 #include <sys/mman.h>
34 
35 #include <VirtualRobot/Nodes/RobotNode.h>
36 #include <VirtualRobot/RobotNodeSet.h>
37 #include <VirtualRobot/Tools/Gravity.h>
38 
46 
51 
58 
60 {
61  // The maximum stack size which is guaranteed safe to access without faulting
62  static constexpr unsigned int MAX_SAFE_STACK = (8 * 1024);
63 
65  {
66  ;
67  }
68 
69  void
71  {
73 
74  ARMARX_INFO << "Locking memory";
75 
76  if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1)
77  {
78  ARMARX_WARNING << "Could not lock memory (mlockall failed)";
79  }
80 
81  ARMARX_DEBUG << "Pre-fault our stack";
82  unsigned char dummy[MAX_SAFE_STACK];
83  memset(dummy, 0, MAX_SAFE_STACK);
84 
85  ARMARX_INFO << "RTUnit initializing now";
86  robot = rtGetRobot()->clone();
87  ARMARX_CHECK_EXPRESSION(rtGetRobot()->hasRobotNodeSet(properties.robotJointsNodeSet));
88  ARMARX_CHECK_EXPRESSION(rtGetRobot()->hasRobotNodeSet(properties.robotColNodeSet));
89  rtRobotJointSet = rtGetRobot()->getRobotNodeSet(properties.robotJointsNodeSet);
90  rtRobotBodySet = rtGetRobot()->getRobotNodeSet(properties.robotColNodeSet);
93  for (auto& node : *rtRobotJointSet)
94  {
95  node->setEnforceJointLimits(false);
96  }
97  std::stringstream massesInfo;
98  for (int i = 0; i < static_cast<int>(rtRobotBodySet->getSize()); ++i)
99  {
100  auto node = rtRobotBodySet->getNode(i);
101  massesInfo << "\t" << node->getName() << ": " << node->getMass() << " kg\n";
102  }
103  ARMARX_DEBUG << "Masses info: " << massesInfo.str();
104 
106  std::filesystem::path(properties.periodicLoggingDirectory).is_absolute());
107 
108  //setting the bus
109  Bus& bus = Bus::getBus();
110  bus.setRobot(robot);
112  bus.setTimeouts(ethercatTimeouts); // Set configured timeouts.
113  }
114 
115  void
117  {
118  ARMARX_INFO << "RTUnit connects now";
119 
120  startRTThread();
122  {
123  usleep(100'000);
124  }
125  }
126 
127  void
128  RTUnit::onDisconnectRobotUnit()
129  {
130  ARMARX_INFO << "RTUnit disconnects now";
131  }
132 
133  void
134  RTUnit::onExitRobotUnit()
135  {
136  ARMARX_INFO << "RTUnit is exiting now ";
137 
138  RTUtility::stopLowLatencyMode();
139 
140  ARMARX_INFO << "RTUnit exit complete";
141  }
142 
143  void
144  RTUnit::initializeKinematicUnit()
145  {
146  using IfaceT = KinematicUnitInterface;
147 
148  auto guard = getGuard();
149  throwIfStateIsNot(RobotUnitState::InitializingUnits, __FUNCTION__);
150  //check if unit is already added
151  if (getUnit(IfaceT::ice_staticId()))
152  {
153  return;
154  }
155 
156  auto unit = createKinematicSubUnit(getIceProperties()->clone(),
157  ControlModes::Position1DoF,
158  properties.useTorqueVelocityModeAsDefault
159  ? ControlModes::VelocityTorque
160  : ControlModes::Velocity1DoF);
161  //add
162  if (unit)
163  {
164  addUnit(std::move(unit));
165  }
166  }
167 
168  void
169  RTUnit::startRTThread()
170  {
171  ARMARX_INFO << "starting control task";
172 
173  if (rtTask.joinable())
174  {
175  rtTask.join();
176  }
177 
178  rtTask = std::thread{[this]
179  {
180  taskRunning = true;
181  try
182  {
183  this->rtRun();
184  }
185  catch (...)
186  {
187  ARMARX_FATAL << "RT Thread caused an uncaught exception:\n"
188  << GetHandledExceptionString();
189  }
190  }};
191  }
192 
193  void
194  RTUnit::joinControlThread()
195  {
196  ARMARX_INFO << "stopping control task";
197  taskRunning = false;
198  rtTask.join();
199  ARMARX_INFO << "rt task stopped";
200  }
201 
202  void
203  RTUnit::publish(StringVariantBaseMap debugObserverMap, StringVariantBaseMap timingMap)
204  {
205  RobotUnit::publish(std::move(debugObserverMap), std::move(timingMap));
206  }
207 
208  armarx::PropertyDefinitionsPtr
209  RTUnit::createPropertyDefinitions()
210  {
211  PropertyDefinitionsPtr def = RobotUnit::createPropertyDefinitions();
212 
213  def->optional(
214  properties.busConfigFilePath, "BusConfigFilePath", "Location of the BusConfigFile");
215  def->optional(properties.socketFileDescriptor,
216  "SocketFileDescriptor",
217  "Socketfiledescriptor on which the ethercat connection is running");
218  def->optional(
219  properties.ethercatInterfaceName,
220  "EthercatInterfaceName",
221  "Name of the ethercat socket. If set to non-empty string, this will be used over "
222  "SocketFileDescriptor");
223  def->optional(properties.useTorqueVelocityModeAsDefault,
224  "UseTorqueVelocityModeAsDefault",
225  "If true, the KinematicUnit will use TorqueVelocity mode for velocity mode");
226  def->optional(properties.rtLoopTimeInUS,
227  "RTLoopTimeInUS",
228  "Desired duration of one interation in the real-time control loop");
229  def->optional(properties.rTLoopTimingCheckToleranceFactor,
230  "RTLoopTimingCheckToleranceFactor",
231  "Factor by which the timing checks are multiplied. Higher value -> less "
232  "warning outputs");
233 
234 
235  // Periodic Logging
236  def->optional(properties.periodicLoggingDirectory,
237  "PeriodicLoggingDirectory",
238  "This absolute path to a directory is used for storing periodic log-files.");
239  def->optional(
240  properties.periodicLoggingHistorySize,
241  "PeriodicLoggingHistorySize",
242  "The amount of older iterations of the periodic logging files that are kept.");
243  def->optional(properties.periodicLoggingIntervalInSeconds,
244  "PeriodicLoggingIntervalInSeconds",
245  "The duration in seconds of one periodic logging interval. After that "
246  "duration, a new set of log-files will be created");
247  def->optional(properties.periodicLoggingMode,
248  "PeriodicLoggingMode",
249  "Select in which mode the periodic logging should be executed.")
250  .map("None", PeriodicLoggingMode::None)
251  .map("OnlyBusErrors", PeriodicLoggingMode::OnlyBusErrors)
252  .map("OnlyRtLogging", PeriodicLoggingMode::OnlyRtLogging)
253  .map("Both", PeriodicLoggingMode::Both);
254 
255 
256  // EtherCAT error counters
257  def->optional(properties.enableErrorCountersReading,
258  "ErrorCountersReading.Enable",
259  "Whether to periodically read the error counters from all slaves and "
260  "provide them as sensor values");
261  def->optional(properties.errorCountersReadingPeriodInMS,
262  "ErrorCountersReading.PeriodInMS",
263  "Wait time in milliseconds between two full reads of the error counters of "
264  "all slaves.");
265  def->optional(properties.resetErrorRegistersAtStartup,
266  "ErrorCountersReading.ResetAtStartup",
267  "Whether to reset all error counters on all slaves after starting the bus.");
268 
269  // node sets
270  def->optional(properties.robotJointsNodeSet,
271  "nodeSet.robotJoints",
272  "The node set which contains the actuated joints.");
273  def->optional(properties.robotColNodeSet,
274  "nodeSet.robotCol",
275  "The node set for e.g. gravity computation.");
276 
277  def->optional(ethercatTimeouts.registerRead_us,
278  "ethercat.timeouts.register_read",
279  "EtherCAT timeout for a register frame to read certain registers in µs.");
280  def->optional(ethercatTimeouts.stateCheck_us,
281  "ethercat.timeouts.state_check",
282  "EtherCAT timeout to check for the current EtherCAT state of slaves in µs.");
283  def->optional(ethercatTimeouts.sdoRead_us,
284  "ethercat.timeouts.sdo_read",
285  "EtherCAT timeout to read an SDO in µs.");
286  def->optional(ethercatTimeouts.sdoWrite_us,
287  "ethercat.timeouts.sdo_write",
288  "EtherCAT timeout to write an SDO in µs.");
289 
290  return def;
291  }
292 
293  RTUnit::~RTUnit()
294  {
295  ARMARX_INFO << "DTOR of RTUnit";
296 
297  backupLoggingRunning.store(false);
298  if (backupLoggingTask.joinable())
299  {
300  backupLoggingTask.join();
301  }
302  }
303 
304  void
305  RTUnit::rtRun()
306  {
307  ARMARX_INFO << "Control task running";
308  Bus& bus = Bus::getBus();
309 
310  // Start periodic logging of rt-values and bus errors
311  startBackupLogging();
312 
313  ARMARX_ON_SCOPE_EXIT
314  {
315  ARMARX_INFO << "Leaving rtRun scope";
316  if (bus.rtGetEtherCATState() != EtherCATState::init)
317  {
318  bus.shutdown();
319  }
320  backupLoggingRunning.store(false); // This will automatically dump the latest BusErrors
321  if (backupLoggingTask.joinable())
322  {
323  backupLoggingTask.join();
324  }
325  };
326 
327  bool busStartSucceeded = false;
328 
329 
330  try
331  {
332  ARMARX_DEBUG << "initBus";
333  busStartSucceeded = initBusRTThread();
334  }
335  catch (...)
336  {
337  ARMARX_ERROR << "Initializing EtherCAT bus RT thread failed with exception: "
338  << armarx::GetHandledExceptionString();
339  return;
340  }
341 
342  ARMARX_INFO << "initBus finished with: " << busStartSucceeded;
343  if (!busStartSucceeded)
344  {
345  return;
346  }
347 
348  bool initializationDone = false;
349  bool initializationFailed = false;
350  ARMARX_DEBUG << "Async init starting now";
351  std::thread unitInitTask = std::thread{
352  [&, this]
353  {
354  try
355  {
356  finishDeviceInitialization();
357  // rtReadSensorDeviceValues(TimeUtil::GetTime(), IceUtil::Time::microSeconds(1)); // initialize sensor values
358  initializeDefaultUnits();
359  ARMARX_IMPORTANT << "Setting up custom Units";
360  finishUnitInitialization();
361  ARMARX_IMPORTANT << "Finishing setting up custom Units...DONE";
362  ARMARX_INFO << "Sleeping a moment to let everything settle in";
363  usleep(500000);
364  initializationDone = true;
365  }
366  catch (...)
367  {
368  ARMARX_FATAL
369  << "Shutting down - RobotUnit Init Thread caused an uncaught exception:\n"
370  << GetHandledExceptionString();
371  initializationFailed = true;
372  }
373  }};
374  unitInitTask.detach();
375  if (initializationFailed)
376  {
377  return;
378  }
379  if (busStartSucceeded)
380  {
381  //setting the timestamps for the pdo update, at the moment we only have on
382  currentPDOUpdateTimestamp = armarx::rtNow();
383  CycleUtil cycleStats(IceUtil::Time::microSeconds(properties.rtLoopTimeInUS));
384  cycleStats.setBusyWaitShare(0.1f);
385  while (!initializationDone)
386  {
387  // auto realDeltaT = currentPDOUpdateTimestamp - lastPDOUpdateTimestamp;
388  // auto cappedDeltaT = IceUtil::Time::microSeconds(boost::algorithm::clamp(realDeltaT.toMicroSeconds(), 1, 2000)); // TODO: Make property
389  // bus.updateBus(false);
390  // rtReadSensorDeviceValues(currentPDOUpdateTimestamp, cappedDeltaT);
391  // lastPDOUpdateTimestamp = currentPDOUpdateTimestamp;
392  // currentPDOUpdateTimestamp = TimeUtil::GetTime();
393  cycleStats.waitForCycleDuration();
394  ARMARX_INFO << deactivateSpam(1) << "Waiting for unit initialization!";
395  }
396  ARMARX_IMPORTANT << "RobotUnit is now ready";
397  }
398  else
399  {
400  ARMARX_WARNING << "Bus was not started!";
401  return;
402  }
403 
404  ARMARX_DEBUG << "Setting up gravity calculation";
405  // init data structs for gravity calculation
406  for (int i = 0; i < static_cast<int>(rtRobotJointSet->getSize()); i++)
407  {
408  auto selectedJoint = getSensorDevice(rtRobotJointSet->getNode(i)->getName());
409  if (selectedJoint)
410  {
411  auto sensorValue = const_cast<SensorValue1DoFGravityTorque*>(
412  selectedJoint->getSensorValue()->asA<SensorValue1DoFGravityTorque>());
413  ARMARX_DEBUG << "will calculate gravity for robot node "
414  << rtRobotJointSet->getNode(i)->getName();
415  nodeJointDataVec.push_back(
416  std::make_pair(rtRobotJointSet->getNode(i), sensorValue));
417  }
418  else
419  {
420  ARMARX_INFO << "Joint " << rtRobotJointSet->getNode(i)->getName() << " not found";
421  nodeJointDataVec.push_back(std::make_pair(rtRobotJointSet->getNode(i), nullptr));
422  }
423  }
424 
425  controlLoopRTThread();
426 
427  while (getObjectScheduler() && !getObjectScheduler()->isTerminationRequested())
428  {
429  ARMARX_INFO << deactivateSpam() << "Waiting for shutdown";
430  usleep(10000);
431  }
432  }
433 
434  MultiNodeRapidXMLReader
435  RTUnit::ReadHardwareConfigFile(std::string busConfigFilePath, std::string robotName)
436  {
437  ARMARX_TRACE;
438  if (!ArmarXDataPath::getAbsolutePath(busConfigFilePath, busConfigFilePath))
439  {
440  throw LocalException("could not find BusConfigFilePath: ") << busConfigFilePath;
441  }
442  ARMARX_CHECK(std::filesystem::is_regular_file(busConfigFilePath))
443  << "The bus config file `" << busConfigFilePath << "` does not exist!";
444 
445  ARMARX_INFO_S << "Read the hw config from " << busConfigFilePath;
446 
447  //reading the config for the Bus and create all the robot objects in the robot container
448  auto busConfigFilePathDir = std::filesystem::path(busConfigFilePath).parent_path();
449 
450  ARMARX_TRACE;
451  ARMARX_INFO_S << "Reading file `" << busConfigFilePath << "`";
452  auto rapidXmlReaderPtr = RapidXmlReader::FromFile(busConfigFilePath);
453  ARMARX_CHECK_NOT_NULL(rapidXmlReaderPtr) << "Failed to read `" << busConfigFilePath << "`";
454 
455  ARMARX_TRACE;
456  auto rootNode = rapidXmlReaderPtr->getRoot("HardwareConfig");
457  ARMARX_CHECK(rootNode.is_valid());
458  ARMARX_CHECK(rootNode.attribute_value("robotName") == robotName);
459 
460  MultiNodeRapidXMLReader multiNode({rootNode});
461  for (RapidXmlReaderNode& includeNode : rootNode.nodes("include"))
462  {
463  ARMARX_CHECK(includeNode.has_attribute("file"))
464  << "File `" << busConfigFilePath
465  << "` contains invalid `include` tag. The `file` attribute is missing.";
466  const auto relPath = includeNode.attribute_value("file");
467 
468  std::filesystem::path filepath = (busConfigFilePathDir / relPath);
469  if (!std::filesystem::exists(filepath))
470  {
471  std::string absPath;
472  if (!ArmarXDataPath::getAbsolutePath(relPath, absPath))
473  {
474  throw LocalException("Could not find config file at path ") << relPath;
475  }
476  }
477 
478  ARMARX_TRACE;
479  auto includedNode = RapidXmlReader::FromFile(filepath.string());
480  ARMARX_CHECK(includedNode->getRoot("HardwareConfig").is_valid())
481  << "Invalid root node for XML file `" << filepath.string() << "`";
482  ARMARX_CHECK(includedNode->getRoot("HardwareConfig").attribute_value("robotName") ==
483  robotName);
484  multiNode.addNode(includedNode->getRoot("HardwareConfig"));
485  }
486  return multiNode;
487  }
488 
489  bool
490  RTUnit::initBusRTThread()
491  {
492  // init EtherCAT
493  Bus& bus = Bus::getBus();
494  bus.setSocketFileDescriptor(properties.socketFileDescriptor);
495  bus.setIfName(properties.ethercatInterfaceName);
496  if (!bus.switchBusToSafeOp())
497  {
498  ARMARX_ERROR << "Bringing bus to EtherCAT state 'safe-operational' failed.";
499  return false;
500  }
501 
502  auto addDevices = [&](auto device)
503  {
504  auto cd = std::dynamic_pointer_cast<ControlDevice>(device);
505  if (cd)
506  {
507  addControlDevice(cd);
508  }
509  auto sd = std::dynamic_pointer_cast<SensorDevice>(device);
510  if (sd)
511  {
512  addSensorDevice(sd);
513  }
514  };
515 
516  for (auto& device : bus.getDevices())
517  {
518  addDevices(device);
519  for (const auto& subDevice : device->getSubDevices())
520  {
521  addDevices(subDevice);
522  }
523  }
524 
525  if (properties.resetErrorRegistersAtStartup)
526  {
527  for (const auto& slave : bus.getSlaves())
528  {
529  bus.resetErrorRegisters(
530  static_cast<std::uint16_t>(slave->getSlaveIdentifier().slaveIndex));
531  }
532  }
533 
534  if (properties.enableErrorCountersReading)
535  {
536  bus.configureErrorCountersReading(properties.enableErrorCountersReading,
537  properties.errorCountersReadingPeriodInMS);
538  // Add special slave error counters sensor devices
539  for (const auto& slave : bus.getSlaves())
540  {
541  auto sd = std::dynamic_pointer_cast<SensorDevice>(slave->getErrorRegistersDevice());
542  if (sd)
543  {
544  addSensorDevice(sd);
545  }
546  }
547  }
548 
549  ARMARX_INFO << "EtherCAT bus is started";
550  return true;
551  }
552 
553  void
554  RTUnit::controlLoopRTThread()
555  {
556  Bus& bus = Bus::getBus();
557  try
558  {
559  finishControlThreadInitialization();
560 
561  RTUtility::pinThreadToCPU(0);
562  RTUtility::elevateThreadPriority(RTUtility::RT_THREAD_PRIORITY);
563  RTUtility::startLowLatencyMode();
564 
565  //to not get any strange start values
566  currentPDOUpdateTimestamp = armarx::rtNow();
567  CycleUtil cycleKeeper(IceUtil::Time::microSeconds(properties.rtLoopTimeInUS));
568  cycleKeeper.setBusyWaitShare(1.0f);
569  ARMARX_CHECK_EXPRESSION(rtGetRobot());
570  ARMARX_CHECK_EXPRESSION(rtRobotJointSet);
571  ARMARX_CHECK_EXPRESSION(rtRobotBodySet);
572  ARMARX_CHECK_EXPRESSION(rtRobotJointSet->getSize() > 0);
573  VirtualRobot::Gravity gravity(rtGetRobot(), rtRobotJointSet, rtRobotBodySet);
574 
575  const std::uint64_t loopDurationThreshold =
576  properties.rtLoopTimeInUS / 1000 * properties.rTLoopTimingCheckToleranceFactor;
577 
578  //#if 0
579  std::vector<float> gravityValues(rtRobotJointSet->getSize());
580  ARMARX_CHECK_EQUAL(rtRobotJointSet->getSize(), nodeJointDataVec.size());
581  //#endif
582 
583 
584  if (!bus.switchBusToOp())
585  {
586  BUS_FATAL_AND_THROW(bus.getIterationCount(),
587  "Switching bus to EtherCAT state 'operational' failed");
588  }
589 
590 
591  ARMARX_INFO << "RT control loop started";
592  EmergencyStopState lastSoftwareEmergencyStopState = rtGetEmergencyStopState();
593 
594  auto lastMonoticTimestamp = armarx::rtNow();
595  auto currentMonotonicTimestamp = lastMonoticTimestamp;
596  currentPDOUpdateTimestamp = armarx::rtNow();
597 
598  for (; taskRunning; ++_iterationCount)
599  {
600  const auto loopStartTime = armarx::rtNow();
601  rtGetRTThreadTimingsSensorDevice().rtMarkRtLoopStart();
602 
603  auto realDeltaT = currentMonotonicTimestamp - lastMonoticTimestamp;
604  auto cappedDeltaT = IceUtil::Time::microSeconds(
605  std::clamp<long>(realDeltaT.toMicroSeconds(), 1, 2000)); // TODO: Make property
606  if (rtIsCalibrating())
607  {
608  BUS_TIMING_START(calibrateActivateControlers)
609  rtCalibrateActivateControlers();
610  BUS_TIMING_CEND(calibrateActivateControlers,
611  bus.getIterationCount(),
612  0.3 * loopDurationThreshold)
613 
614  BUS_TIMING_START(switchControllerSetup)
615  rtSwitchControllerSetup(SwitchControllerMode::RTThread);
616  BUS_TIMING_CEND(
617  switchControllerSetup, bus.getIterationCount(), 0.3 * loopDurationThreshold)
618 
619  BUS_TIMING_START(resetAllTargets)
620  rtResetAllTargets();
621  BUS_TIMING_CEND(
622  resetAllTargets, bus.getIterationCount(), 0.3 * loopDurationThreshold)
623 
624  BUS_TIMING_START(calibrateInRt)
625  _calibrationStatus = rtCalibrate();
626  BUS_TIMING_CEND(
627  calibrateInRt, bus.getIterationCount(), 0.3 * loopDurationThreshold)
628 
629  BUS_TIMING_START(handleInvalidTargets)
630  rtHandleInvalidTargets();
631  BUS_TIMING_CEND(
632  handleInvalidTargets, bus.getIterationCount(), 0.3 * loopDurationThreshold)
633 
634  BUS_TIMING_START(runJointControllers)
635  rtRunJointControllers(currentPDOUpdateTimestamp, cappedDeltaT);
636  BUS_TIMING_CEND(
637  runJointControllers, bus.getIterationCount(), 0.3 * loopDurationThreshold)
638  }
639  else
640  {
641  BUS_TIMING_START(RunControllers)
642  BUS_TIMING_START(SwitchControllers)
643  rtSwitchControllerSetup();
644  BUS_TIMING_CEND(
645  SwitchControllers, bus.getIterationCount(), 0.3 * loopDurationThreshold)
646  BUS_TIMING_START(ResettingTargets)
647  rtResetAllTargets();
648  BUS_TIMING_CEND(
649  ResettingTargets, bus.getIterationCount(), 0.3 * loopDurationThreshold)
650  BUS_TIMING_START(RunNJointControllers)
651  rtRunNJointControllers(currentPDOUpdateTimestamp, cappedDeltaT);
652  BUS_TIMING_CEND(
653  RunNJointControllers, bus.getIterationCount(), 0.3 * loopDurationThreshold)
654  BUS_TIMING_START(CheckInvalidTargets)
655  rtHandleInvalidTargets();
656  BUS_TIMING_CEND(
657  CheckInvalidTargets, bus.getIterationCount(), 0.3 * loopDurationThreshold)
658 
659  BUS_TIMING_START(RunJointControllers)
660  rtRunJointControllers(currentPDOUpdateTimestamp, cappedDeltaT);
661  BUS_TIMING_CEND(
662  RunJointControllers, bus.getIterationCount(), 0.3 * loopDurationThreshold)
663  BUS_TIMING_CEND(
664  RunControllers, bus.getIterationCount(), 0.3 * loopDurationThreshold)
665  }
666 
667  //bus update
668  BUS_TIMING_START(rtBusSendReceive)
669  rtGetRTThreadTimingsSensorDevice().rtMarkRtBusSendReceiveStart();
670  if (bus.rtGetEtherCATState() == EtherCATState::op)
671  {
672  // error correction
673  BUS_TIMING_START(getEmergencyStopState)
674  auto currentSoftwareEmergencyStopState = rtGetEmergencyStopState();
675  BUS_TIMING_CEND(
676  getEmergencyStopState, bus.getIterationCount(), 0.5 * loopDurationThreshold)
677  if (lastSoftwareEmergencyStopState ==
678  EmergencyStopState::eEmergencyStopActive &&
679  currentSoftwareEmergencyStopState ==
680  EmergencyStopState::eEmergencyStopInactive)
681  {
682  // bus.rebootBus();
683  }
684  lastSoftwareEmergencyStopState = currentSoftwareEmergencyStopState;
685 
686  BUS_TIMING_START(rtUpdateBus)
687  bus.rtUpdateBus(true, _iterationCount);
688  BUS_TIMING_CEND(
689  rtUpdateBus, bus.getIterationCount(), 0.6 * loopDurationThreshold)
690  if (bus.rtIsEmergencyStopActive() || bus.rtHasError())
691  {
692  rtSetEmergencyStopState(EmergencyStopState::eEmergencyStopActive);
693  }
694  }
695  currentPDOUpdateTimestamp = armarx::rtNow();
696 
697  rtGetRTThreadTimingsSensorDevice().rtMarkRtBusSendReceiveEnd();
698  BUS_TIMING_CEND(rtBusSendReceive, bus.getIterationCount(), 0.55)
699  BUS_TIMING_START(ReadSensorValues)
700  rtReadSensorDeviceValues(currentPDOUpdateTimestamp, cappedDeltaT);
701  BUS_TIMING_CEND(
702  ReadSensorValues, bus.getIterationCount(), 0.7 * loopDurationThreshold)
703  lastMonoticTimestamp = currentMonotonicTimestamp;
704  currentMonotonicTimestamp = armarx::rtNow();
705 
706  BUS_TIMING_START(Publish)
707  rtUpdateSensorAndControlBuffer(currentPDOUpdateTimestamp, cappedDeltaT);
708  BUS_TIMING_CEND(Publish, bus.getIterationCount(), 0.15 * loopDurationThreshold)
709 
710  BUS_TIMING_START(ComputeGravityTorques)
711  gravity.computeGravityTorque(gravityValues);
712  size_t i = 0;
713  for (auto& node : nodeJointDataVec)
714  {
715  auto torque = gravityValues.at(i);
716  if (node.second)
717  {
718  node.second->gravityTorque = -torque;
719  }
720 
721  i++;
722  }
723  BUS_TIMING_CEND(
724  ComputeGravityTorques, bus.getIterationCount(), 0.2 * loopDurationThreshold)
725 
726 
727  rtGetRTThreadTimingsSensorDevice().rtMarkRtLoopPreSleep();
728 
729  const auto loopPreSleepTime = armarx::rtNow();
730  BUS_TIMING_START(RTLoopWaiting)
731  cycleKeeper.waitForCycleDuration();
732  BUS_TIMING_CEND(RTLoopWaiting, bus.getIterationCount(), loopDurationThreshold)
733  const auto loopPostSleepTime = armarx::rtNow();
734 
735  const auto loopDuration = armarx::rtNow() - loopStartTime;
736 
737  if (bus.rtGetFunctionalState() == BusFunctionalState::Running &&
738  loopDuration.toMicroSecondsDouble() >
739  (properties.rtLoopTimeInUS + 500) *
740  properties.rTLoopTimingCheckToleranceFactor)
741  {
742  const SensorValueRTThreadTimings* sval =
743  rtGetRTThreadTimingsSensorDevice()
744  .getSensorValue()
745  ->asA<SensorValueRTThreadTimings>();
746  ARMARX_CHECK_NOT_NULL(sval);
747  BUS_WARNING(
748  bus.getIterationCount(),
749  "RT loop is under 1kHz control frequency: %.1f \n"
750  "-- cycle time PDO timestamp %.1f µs\n"
751  "-- sleep %.1f µs\n"
752  "-- thread timing sensor value: \n"
753  "---- rtSwitchControllerSetup Duration %.1f µs\t"
754  " RoundTripTime %.1f µs\n"
755  "---- rtRunNJointControllers Duration %.1f µs\t"
756  " RoundTripTime %.1f µs\n"
757  "---- rtHandleInvalidTargets Duration %.1f µs\t"
758  " RoundTripTime %.1f µs\n"
759  "---- rtRunJointControllers Duration %.1f µs\t"
760  " RoundTripTime %.1f µs\n"
761  "---- rtBusSendReceive Duration %.1f µs\t"
762  " RoundTripTime %.1f µs\n"
763  "---- rtReadSensorDeviceValues Duration %.1f µs\t"
764  " RoundTripTime %.1f µs\n"
765  "---- rtUpdateSensorAndControlBuffer Duration %.1f µs\t"
766  " RoundTripTime %.1f µs\n"
767  "---- rtResetAllTargets Duration %.1f µs\t"
768  " RoundTripTime %.1f µs\n"
769  "---- rtLoop Duration %.1f µs\t"
770  " RoundTripTime %.1f µs\n",
771  realDeltaT.toMicroSecondsDouble(),
772  loopDuration.toMicroSecondsDouble(),
773  (loopPostSleepTime - loopPreSleepTime).toMicroSecondsDouble(),
774  sval->rtSwitchControllerSetupDuration.toMicroSecondsDouble(),
775  sval->rtSwitchControllerSetupRoundTripTime.toMicroSecondsDouble(),
776  sval->rtRunNJointControllersDuration.toMicroSecondsDouble(),
777  sval->rtRunNJointControllersRoundTripTime.toMicroSecondsDouble(),
778  sval->rtHandleInvalidTargetsDuration.toMicroSecondsDouble(),
779  sval->rtHandleInvalidTargetsRoundTripTime.toMicroSecondsDouble(),
780  sval->rtRunJointControllersDuration.toMicroSecondsDouble(),
781  sval->rtRunJointControllersRoundTripTime.toMicroSecondsDouble(),
782  sval->rtBusSendReceiveDuration.toMicroSecondsDouble(),
783  sval->rtBusSendReceiveRoundTripTime.toMicroSecondsDouble(),
784  sval->rtReadSensorDeviceValuesDuration.toMicroSecondsDouble(),
785  sval->rtReadSensorDeviceValuesRoundTripTime.toMicroSecondsDouble(),
786  sval->rtUpdateSensorAndControlBufferDuration.toMicroSecondsDouble(),
787  sval->rtUpdateSensorAndControlBufferRoundTripTime.toMicroSecondsDouble(),
788  sval->rtResetAllTargetsDuration.toMicroSecondsDouble(),
789  sval->rtResetAllTargetsRoundTripTime.toMicroSecondsDouble(),
790  sval->rtLoopDuration.toMicroSecondsDouble(),
791  sval->rtLoopRoundTripTime.toMicroSecondsDouble());
792  }
793  }
794  ARMARX_IMPORTANT << "RT loop stopped";
795  ARMARX_INFO << "Execution stats: Average: "
796  << cycleKeeper.getAverageDuration().toMilliSecondsDouble()
797  << " max: " << cycleKeeper.getMaximumDuration().toMilliSecondsDouble()
798  << " min: " << cycleKeeper.getMinimumDuration().toMilliSecondsDouble();
799  //switching off the bus and be sure that the bus will receive
800  bus.shutdown();
801  }
802  catch (UserException& e)
803  {
804  ARMARX_FATAL << "exception in control thread!"
805  << "\nwhat:\n"
806  << e.what() << "\nreason:\n"
807  << e.reason << "\n\tname: " << e.ice_id() << "\n\tfile: " << e.ice_file()
808  << "\n\tline: " << e.ice_line() << "\n\tstack: " << e.ice_stackTrace()
809  << std::flush;
810  bus.shutdown();
811  }
812  catch (Ice::Exception& e)
813  {
814  ARMARX_FATAL << "exception in control thread!\nwhat:\n"
815  << e.what() << "\n\tname: " << e.ice_id() << "\n\tfile: " << e.ice_file()
816  << "\n\tline: " << e.ice_line() << "\n\tstack: " << e.ice_stackTrace()
817  << std::flush;
818  bus.shutdown();
819  }
820  catch (std::exception& e)
821  {
822  ARMARX_FATAL << "exception in control thread!\nwhat:\n" << e.what() << std::flush;
823  bus.shutdown();
824  }
825  catch (...)
826  {
827  ARMARX_FATAL << "exception in control thread!" << std::flush;
828  bus.shutdown();
829  }
830  ARMARX_INFO << "Leaving control loop function";
831  }
832 
833  void
834  RTUnit::startBackupLogging()
835  {
836  ARMARX_IMPORTANT << "startBackupLogging()";
837 
838  if (properties.periodicLoggingMode == PeriodicLoggingMode::None)
839  {
840  ARMARX_IMPORTANT << "Periodic backup logging deactivated.";
841  return;
842  }
843 
844  backupLoggingTask = std::thread{[this]
845  {
846  try
847  {
848  backupLoggingRunning.store(true);
849  periodicBackupLoggingRun();
850  }
851  catch (...)
852  {
853  handleExceptions();
854  }
855  }};
856  }
857 
858  void
859  RTUnit::periodicBackupLoggingRun()
860  {
861  // This loop is responsible for keeping the last x minutes of Sensor- and ControlData and BusErrors in temporary files
862  // Each x minutes a new instance of rtLogging is started and therefore a new file generated.
863  // Similary each x minutes the BusErrors from that timeframe are dumped into a file.
864 
865  struct FilePaths
866  {
867  std::filesystem::path rtLogPath;
868  std::filesystem::path busErrorLogPath;
869  };
870 
871  std::queue<FilePaths> fileHistory;
872  IceUtil::Time lastIterationStart = armarx::rtNow();
873 
874  std::string robotUnitLogPathFormat =
875  std::filesystem::path(properties.periodicLoggingDirectory)
876  .append("rt_log_{DateTime}.csv")
877  .string();
878  std::filesystem::path busErrorLogPath =
879  std::filesystem::path(properties.periodicLoggingDirectory).append("bus_errors.txt");
880 
881 
882  SimpleRemoteReferenceCounterBasePtr token;
883  while (backupLoggingRunning.load())
884  {
885  ARMARX_TRACE;
886  if (fileHistory.size() > properties.periodicLoggingHistorySize)
887  {
888  FilePaths p = fileHistory.front();
889  std::filesystem::remove(p.rtLogPath);
890  std::filesystem::remove(p.busErrorLogPath);
891  fileHistory.pop();
892  }
893 
894  lastIterationStart = armarx::rtNow();
895  while (backupLoggingRunning.load() &&
896  armarx::rtNow() <
897  lastIterationStart +
898  IceUtil::Time::seconds(properties.periodicLoggingIntervalInSeconds))
899  {
900  if (!token && this->getRobotUnitState() == RobotUnitState::Running &&
901  (properties.periodicLoggingMode == PeriodicLoggingMode::OnlyRtLogging ||
902  properties.periodicLoggingMode == PeriodicLoggingMode::Both))
903  {
904  ARMARX_DEBUG << "Start new periodic log iteration";
905  token = this->startRtLogging(robotUnitLogPathFormat, {"*"});
906  }
907  std::this_thread::sleep_for(std::chrono::milliseconds(100));
908  }
909 
910  ARMARX_TRACE;
911 
912  std::string rtLogActualPath;
913  if (backupLoggingRunning.load() && token)
914  {
915  rtLogActualPath = token->getId();
916  this->stopRtLogging(token);
917  token = nullptr;
918  }
919 
920  ARMARX_TRACE;
921 
922  std::string busErrorLogActualPath;
923  busErrorLogActualPath = Bus::getBus().logErrorsToFile(
924  busErrorLogPath, properties.periodicLoggingIntervalInSeconds);
925 
926  if (properties.periodicLoggingMode == PeriodicLoggingMode::OnlyRtLogging ||
927  properties.periodicLoggingMode == PeriodicLoggingMode::None)
928  {
929  std::filesystem::remove(busErrorLogActualPath);
930  }
931 
932  if (busErrorLogActualPath.empty())
933  {
934  // No errors happend in the last interval therefore we can delete the
935  // rtLog as well to save disk space
936  std::filesystem::remove(rtLogActualPath);
937  }
938  else
939  {
940  fileHistory.push({rtLogActualPath, busErrorLogActualPath});
941  }
942  }
943 
944  // Special case for last rtLog in case of shutdown
945  // This rt log gets always saved no matter if the corresponding bus error log is empty or not
946  // We have to check it by hand and delete if no bus errors happend
947  if (token && (fileHistory.empty() || fileHistory.back().rtLogPath != token->getId()))
948  {
949  std::filesystem::remove(token->getId());
950  }
951  }
952 } // namespace armarx::control::ethercat
armarx::control::ethercat::RTUnit::rtRobotBodySet
VirtualRobot::RobotNodeSetPtr rtRobotBodySet
Definition: RTUnit.h:170
OnScopeExit.h
RtTiming.h
armarx::control::ethercat::RTUnit::startRTThread
void startRTThread()
Definition: RTUnit.cpp:169
armarx::control::ethercat::RTUnit::ethercatTimeouts
armarx::control::ethercat::Timeouts ethercatTimeouts
Definition: RTUnit.h:215
Pose.h
ARMARX_CHECK_NOT_NULL
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
Definition: ExpressionException.h:206
armarx::control::ethercat::RTUnit::properties
struct armarx::control::ethercat::RTUnit::@55 properties
armarx::control::ethercat::RTUnit::RTUnit
RTUnit()
Definition: RTUnit.cpp:64
armarx::RobotUnitModule::ModuleBase::getRobotUnitState
RobotUnitState getRobotUnitState() const
Returns the RobotUnit's State.
Definition: RobotUnitModuleBase.ipp:29
armarx::control::ethercat::BusIO::setTimeouts
void setTimeouts(Timeouts const &timeouts)
Definition: BusIO.cpp:470
Bus.h
ARMARX_TRACE
#define ARMARX_TRACE
Definition: trace.h:77
armarx::control::ethercat::Bus
Brief description of class Bus.
Definition: Bus.h:56
DeviceInterface.h
armarx::RobotUnitState::Running
@ Running
armarx::control::ethercat::Bus::setRobot
void setRobot(const VirtualRobot::RobotPtr &robot)
Definition: Bus.cpp:1453
armarx::RobotUnitModule::ControlThread::rtGetRobot
const VirtualRobot::RobotPtr & rtGetRobot() const
Returns the simox robot used in the control thread.
Definition: RobotUnitModuleControlThread.h:181
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:184
armarx::control::ethercat
Definition: Bus.cpp:24
armarx::Logging::setLocalMinimumLoggingLevel
void setLocalMinimumLoggingLevel(MessageTypeT level)
With setLocalMinimumLoggingLevel the minimum verbosity-level of log-messages can be set.
Definition: Logging.cpp:66
armarx::control::ethercat::Bus::getBus
static Bus & getBus()
This returns the one and only Bus object.
Definition: Bus.cpp:29
RobotUnitSubUnit.h
ArmarXObjectScheduler.h
SlaveInterface.h
SlaveErrorRegistersDevice.h
Timing.h
armarx::Logging::getEffectiveLoggingLevel
MessageTypeT getEffectiveLoggingLevel() const
Definition: Logging.cpp:130
CycleUtil.h
NJointController.h
ARMARX_CHECK_EXPRESSION
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
Definition: ExpressionException.h:73
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:181
armarx::control::ethercat::RTUnit::onInitRobotUnit
void onInitRobotUnit() override
called in onInitComponent
Definition: RTUnit.cpp:70
RapidXmlReader.h
MultiNodeRapidXMLReader.h
armarx::control::ethercat::RTUnit::robot
VirtualRobot::RobotPtr robot
Definition: RTUnit.h:168
armarx::control::ethercat::RTUnit::onConnectRobotUnit
void onConnectRobotUnit() override
called in onConnectComponent
Definition: RTUnit.cpp:116
Logging.h
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:193
ArmarXDataPath.h
armarx::control::ethercat::RTUnit::rtRobotJointSet
VirtualRobot::RobotNodeSetPtr rtRobotJointSet
Definition: RTUnit.h:170
RTUtility.h
RTUnit.h