Flexiv RDK APIs  1.6.0
test_endurance.cpp
1 
9 #include <flexiv/rdk/robot.hpp>
10 #include <flexiv/rdk/scheduler.hpp>
11 #include <flexiv/rdk/utility.hpp>
12 #include <spdlog/spdlog.h>
13 
14 #include <iostream>
15 #include <fstream>
16 #include <string>
17 #include <cmath>
18 #include <thread>
19 #include <atomic>
20 
21 namespace {
23 const double kLoopPeriod = 0.001;
24 
26 const double kSwingAmp = 0.1;
27 
29 const double kSwingFreq = 0.025; // = 10mm/s linear velocity
30 
32 std::array<double, flexiv::rdk::kPoseSize> g_curr_tcp_pose;
33 
35 uint64_t g_test_duration_loop_counts = 0;
36 
38 const unsigned int kLogDurationLoopCounts = 10 * 60 * 1000; // = 10 min/file
39 
41 struct LogData
42 {
43  std::array<double, flexiv::rdk::kPoseSize> tcp_pose;
44  std::array<double, flexiv::rdk::kCartDoF> tcp_force;
45 } g_log_data;
46 
48 std::atomic<bool> g_stop = {false};
49 }
50 
51 void highPriorityTask(
52  flexiv::rdk::Robot& robot, const std::array<double, flexiv::rdk::kPoseSize>& init_pose)
53 {
54  // Local periodic loop counter
55  static uint64_t loop_counter = 0;
56 
57  try {
58  // Monitor fault on the connected robot
59  if (robot.fault()) {
60  throw std::runtime_error(
61  "highPriorityTask: Fault occurred on the connected robot, exiting ...");
62  }
63 
64  // Swing along Z direction
65  g_curr_tcp_pose[2]
66  = init_pose[2] + kSwingAmp * sin(2 * M_PI * kSwingFreq * loop_counter * kLoopPeriod);
67  robot.StreamCartesianMotionForce(g_curr_tcp_pose);
68 
69  // Save data to global buffer, not using mutex to avoid interruption on RT loop from
70  // potential priority inversion
71  g_log_data.tcp_pose = robot.states().tcp_pose;
72  g_log_data.tcp_force = robot.states().ext_wrench_in_world;
73 
74  // Stop after test duration has elapsed
75  if (++loop_counter > g_test_duration_loop_counts) {
76  g_stop = true;
77  }
78 
79  } catch (const std::exception& e) {
80  spdlog::error(e.what());
81  g_stop = true;
82  }
83 }
84 
85 void lowPriorityTask()
86 {
87  // Low-priority task loop counter
88  uint64_t loop_counter = 0;
89 
90  // Data logging CSV file
91  std::ofstream csv_file;
92 
93  // CSV file name
94  std::string csv_file_name;
95 
96  // CSV file counter (data during the test is divided to multiple files)
97  unsigned int file_counter = 0;
98 
99  // Wait for a while for the robot states data to be available
100  std::this_thread::sleep_for(std::chrono::seconds(2));
101 
102  // Use while loop to prevent this thread from return
103  while (true) {
104  // Log data at 1kHz
105  std::this_thread::sleep_for(std::chrono::milliseconds(1));
106  loop_counter++;
107 
108  // Close existing log file and create a new one periodically
109  if (loop_counter % kLogDurationLoopCounts == 0) {
110  if (csv_file.is_open()) {
111  csv_file.close();
112  spdlog::info("Saved log file: {}", csv_file_name);
113  }
114 
115  // Increment log file counter
116  file_counter++;
117 
118  // Create new file name using the updated counter as suffix
119  csv_file_name = "endurance_test_data_" + std::to_string(file_counter) + ".csv";
120 
121  // Open new log file
122  csv_file.open(csv_file_name);
123  if (csv_file.is_open()) {
124  spdlog::info("Created new log file: {}", csv_file_name);
125  } else {
126  spdlog::error("Failed to create log file: {}", csv_file_name);
127  }
128  }
129 
130  // Log data to file in CSV format
131  if (csv_file.is_open()) {
132  // Loop counter x1, TCP pose x7, TCP external force x6
133  csv_file << loop_counter << ",";
134  for (const auto& i : g_log_data.tcp_pose) {
135  csv_file << i << ",";
136  }
137  for (const auto& i : g_log_data.tcp_force) {
138  csv_file << i << ",";
139  }
140  // End of line
141  csv_file << '\n';
142  }
143 
144  // Check if the test duration has elapsed
145  if (g_stop) {
146  spdlog::info("Test duration has elapsed, saving any open log file ...");
147  // Close log file
148  if (csv_file.is_open()) {
149  csv_file.close();
150  spdlog::info("Saved log file: {}", csv_file_name);
151  }
152  // Exit thread
153  return;
154  }
155  }
156 }
157 
158 void PrintHelp()
159 {
160  // clang-format off
161  std::cout << "Required arguments: [robot_sn] [test_hours]" << std::endl;
162  std::cout << " robot_sn: Serial number of the robot to connect. Remove any space, e.g. Rizon4s-123456" << std::endl;
163  std::cout << " test_hours: Duration of the test, can have decimals" << std::endl;
164  std::cout << "Optional arguments: None" << std::endl;
165  std::cout << std::endl;
166  // clang-format on
167 }
168 
169 int main(int argc, char* argv[])
170 {
171  // Parse Parameters
172  //==============================================================================================
173  if (argc < 3 || flexiv::rdk::utility::ProgramArgsExistAny(argc, argv, {"-h", "--help"})) {
174  PrintHelp();
175  return 1;
176  }
177 
178  // Serial number of the robot to connect to. Remove any space, for example: Rizon4s-123456
179  std::string robot_sn = argv[1];
180 
181  // test duration in hours
182  double test_hours = std::stof(argv[2]);
183  // convert duration in hours to loop counts
184  g_test_duration_loop_counts = (uint64_t)(test_hours * 3600.0 * 1000.0);
185  spdlog::info("Test duration: {} hours = {} cycles", test_hours, g_test_duration_loop_counts);
186 
187  try {
188  // RDK Initialization
189  //==========================================================================================
190  // Instantiate robot interface
191  flexiv::rdk::Robot robot(robot_sn);
192 
193  // Clear fault on the connected robot if any
194  if (robot.fault()) {
195  spdlog::warn("Fault occurred on the connected robot, trying to clear ...");
196  // Try to clear the fault
197  if (!robot.ClearFault()) {
198  spdlog::error("Fault cannot be cleared, exiting ...");
199  return 1;
200  }
201  spdlog::info("Fault on the connected robot is cleared");
202  }
203 
204  // enable the robot, make sure the E-stop is released before enabling
205  spdlog::info("Enabling robot ...");
206  robot.Enable();
207 
208  // Wait for the robot to become operational
209  while (!robot.operational()) {
210  std::this_thread::sleep_for(std::chrono::seconds(1));
211  }
212  spdlog::info("Robot is now operational");
213 
214  // Bring Robot To Home
215  //==========================================================================================
216  robot.SwitchMode(flexiv::rdk::Mode::NRT_PLAN_EXECUTION);
217  robot.ExecutePlan("PLAN-Home");
218 
219  // Wait fot the plan to finish
220  do {
221  std::this_thread::sleep_for(std::chrono::seconds(1));
222  } while (robot.busy());
223 
224  // Switch mode after robot is at home
225  robot.SwitchMode(flexiv::rdk::Mode::RT_CARTESIAN_MOTION_FORCE);
226 
227  // Set initial pose to current TCP pose
228  auto init_pose = robot.states().tcp_pose;
229  spdlog::info("Initial TCP pose set to [position 3x1, rotation (quaternion) 4x1]: "
230  + flexiv::rdk::utility::Arr2Str(init_pose));
231  g_curr_tcp_pose = init_pose;
232 
233  // Periodic Tasks
234  //==========================================================================================
235  flexiv::rdk::Scheduler scheduler;
236  // Add periodic task with 1ms interval and highest applicable priority
237  scheduler.AddTask(std::bind(highPriorityTask, std::ref(robot), std::ref(init_pose)),
238  "HP periodic", 1, scheduler.max_priority());
239  // Start all added tasks
240  scheduler.Start();
241 
242  // Use std::thread for logging task without strict chronology
243  std::thread low_priority_thread(lowPriorityTask);
244 
245  // low_priority_thread is responsible to release blocking
246  low_priority_thread.join();
247 
248  } catch (const std::exception& e) {
249  spdlog::error(e.what());
250  return 1;
251  }
252 
253  return 0;
254 }
Main interface with the robot, containing several function categories and background services.
Definition: robot.hpp:25
void ExecutePlan(unsigned int index, bool continue_exec=false, bool block_until_started=true)
[Blocking] Execute a plan by specifying its index.
const RobotStates states() const
[Non-blocking] Current states data of the robot.
bool operational(bool verbose=true) const
[Non-blocking] Whether the robot is ready to be operated, which requires the following conditions to ...
void StreamCartesianMotionForce(const std::array< double, kPoseSize > &pose, const std::array< double, kCartDoF > &wrench={}, const std::array< double, kCartDoF > &velocity={}, const std::array< double, kCartDoF > &acceleration={})
[Non-blocking] Continuously stream Cartesian motion and/or force command for the robot to track using...
void SwitchMode(Mode mode)
[Blocking] Switch to a new control mode and wait until mode transition is finished.
void Enable()
[Blocking] Enable the robot, if E-stop is released and there's no fault, the robot will release brake...
bool fault() const
[Non-blocking] Whether the robot is in fault state.
bool ClearFault(unsigned int timeout_sec=30)
[Blocking] Try to clear minor or critical fault of the robot without a power cycle.
bool busy() const
[Non-blocking] Whether the robot is currently executing a task. This includes any user commanded oper...
Real-time scheduler that can simultaneously run multiple periodic tasks. Parameters for each task are...
Definition: scheduler.hpp:22
int max_priority() const
[Non-blocking] Get maximum available priority for user tasks.
void AddTask(std::function< void(void)> &&callback, const std::string &task_name, int interval, int priority, int cpu_affinity=-1)
[Non-blocking] Add a new periodic task to the scheduler's task pool. Each task in the pool is assigne...
void Start()
[Blocking] Start all added tasks. A dedicated thread will be created for each added task and the peri...
std::array< double, kCartDoF > ext_wrench_in_world
Definition: data.hpp:247
std::array< double, kPoseSize > tcp_pose
Definition: data.hpp:208