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