controller_to_csv.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2015, University of Colorado, Boulder
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 Univ of CO, Boulder 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 
35 /* Author: Dave Coleman
36  Desc: Records a ros_control ControllerState data to CSV for Matlab/etc analysis
37 */
38 
40 
41 // Basic file operations
42 #include <iostream>
43 #include <fstream>
44 
45 // ROS parameter loading
47 
49 {
50 ControllerToCSV::ControllerToCSV(const std::string& topic) : nh_("~"), first_update_(true), recording_started_(true)
51 {
52  // Load rosparams
53  ros::NodeHandle rpsnh(nh_, name_);
54  int error = 0;
55  error += !rosparam_shortcuts::get(name_, rpsnh, "record_hz", record_hz_);
57 
58  ROS_INFO_STREAM_NAMED(name_, "Subscribing to " << topic);
59  // State subscriber
60  state_sub_ = nh_.subscribe<control_msgs::JointTrajectoryControllerState>(topic, 1, &ControllerToCSV::stateCB, this);
61 
62  // Wait for states to populate
64 
65  // Alert user to mode
66  if (recordAll())
67  {
68  ROS_INFO_STREAM_NAMED(name_, "Recording all incoming controller state messages");
69  }
70  else
71  {
72  ROS_INFO_STREAM_NAMED(name_, "Only recording every " << record_hz_ << " hz");
73  }
74 
75  ROS_INFO_STREAM_NAMED(name_, "ControllerToCSV Ready.");
76 }
77 
79 {
80  stopRecording();
81 }
82 
84 {
85  return record_hz_ == 0;
86 }
87 
88 void ControllerToCSV::stateCB(const control_msgs::JointTrajectoryControllerState::ConstPtr& state)
89 {
90  // Two modes - save all immediately, or only save at certain frequency
91  if (recordAll())
92  {
93  // Record state
94  states_.push_back(current_state_);
95 
96  // Record current time
97  timestamps_.push_back(ros::Time::now());
98  }
99  else // only record at freq
100  {
101  current_state_ = *state;
102  }
103 }
104 
105 // Start the data collection
106 void ControllerToCSV::startRecording(const std::string& file_name)
107 {
108  ROS_INFO_STREAM_NAMED(name_, "Saving to " << file_name);
109  file_name_ = file_name;
110 
111  // Reset data collections
112  states_.clear();
113  timestamps_.clear();
114 
115  recording_started_ = true;
116 
117  // Start sampling loop
118  if (!recordAll()) // only record at the specified frequency
119  {
120  ros::Duration update_freq = ros::Duration(1.0 / record_hz_);
122  }
123 }
124 
126 {
127  if (first_update_)
128  {
129  // Check if we've recieved any states yet
130  if (current_state_.joint_names.empty())
131  {
132  ROS_WARN_STREAM_THROTTLE_NAMED(2, name_, "No states recieved yet");
133  return;
134  }
135  first_update_ = false;
136  }
137  else // if (event.last_real > 0)
138  {
139  const double freq = 1.0 / (event.current_real - event.last_real).toSec();
141  "Updating at " << freq << " hz, total: "
142  << (ros::Time::now() - timestamps_.front()).toSec() << " seconds");
143  }
144  // Record state
145  states_.push_back(current_state_);
146 
147  // Record current time
148  timestamps_.push_back(ros::Time::now());
149 }
150 
152 {
154  writeToFile();
155 }
156 
158 {
159  std::cout << "Writing data to file " << std::endl;
160 
161  if (!states_.size())
162  {
163  std::cout << "No controller states populated" << std::endl;
164  return false;
165  }
166 
167  std::ofstream output_file;
168  output_file.open(file_name_.c_str());
169 
170  // Output header -------------------------------------------------------
171  output_file << "timestamp,";
172  for (std::size_t j = 0; j < states_[0].joint_names.size(); ++j)
173  {
174  output_file << states_[0].joint_names[j] << "_desired_pos," << states_[0].joint_names[j] << "_desired_vel,"
175  << states_[0].joint_names[j] << "_actual_pos," << states_[0].joint_names[j] << "_actual_vel,"
176  << states_[0].joint_names[j] << "_error_pos," << states_[0].joint_names[j] << "_error_vel,";
177  }
178  output_file << std::endl;
179 
180  // Output data ------------------------------------------------------
181 
182  // Subtract starting time
183  // double start_time = states_[0].header.stamp.toSec();
184  double start_time = timestamps_[0].toSec();
185 
186  for (std::size_t i = 0; i < states_.size(); ++i)
187  {
188  // Timestamp
189  output_file << timestamps_[i].toSec() - start_time << ",";
190 
191  // Output entire state of robot to single line
192  for (std::size_t j = 0; j < states_[i].joint_names.size(); ++j)
193  {
194  // Output State
195  output_file << states_[i].desired.positions[j] << "," << states_[i].desired.velocities[j] << ","
196  << states_[i].actual.positions[j] << "," << states_[i].actual.velocities[j] << ","
197  << states_[i].error.positions[j] << "," << states_[i].error.velocities[j] << ",";
198  }
199 
200  output_file << std::endl;
201  }
202  output_file.close();
203  std::cout << "Wrote to file: " << file_name_ << std::endl;
204  return true;
205 }
206 
207 bool ControllerToCSV::waitForSubscriber(const ros::Subscriber& sub, const double& wait_time)
208 {
209  // Benchmark runtime
210  ros::Time start_time;
211  start_time = ros::Time::now();
212 
213  // Will wait at most 1000 ms (1 sec)
214  ros::Time max_time(ros::Time::now() + ros::Duration(wait_time));
215 
216  // This is wrong. It returns only the number of subscribers that have already established their
217  // direct connections to this subscriber
218  int num_existing_subscribers = sub.getNumPublishers();
219 
220  // How often to check for subscribers
221  ros::Rate poll_rate(200);
222 
223  // Wait for subsriber
224  while (num_existing_subscribers == 0)
225  {
226  // Check if timed out
227  if (ros::Time::now() > max_time)
228  {
229  ROS_WARN_STREAM_NAMED(name_, "Topic '" << sub.getTopic() << "' unable to connect to any publishers within "
230  << wait_time << " seconds.");
231  return false;
232  }
233  ros::spinOnce();
234 
235  // Sleep
236  poll_rate.sleep();
237 
238  // Check again
239  num_existing_subscribers = sub.getNumPublishers();
240  }
241 
242  // Benchmark runtime
243  if (true)
244  {
245  double duration = (ros::Time::now() - start_time).toSec();
246  ROS_DEBUG_STREAM_NAMED(name_, "Topic '" << sub.getTopic() << "' took " << duration
247  << " seconds to connect to a subscriber. "
248  "Connected to "
249  << num_existing_subscribers << " total subsribers");
250  }
251  return true;
252 }
253 
254 } // namespace ros_control_boilerplate
std::string getTopic() const
#define ROS_DEBUG_STREAM_NAMED(name, args)
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
void stop()
bool writeToFile()
Send all resulting data to file.
uint32_t getNumPublishers() const
#define ROS_INFO_STREAM_NAMED(name, args)
void stateCB(const control_msgs::JointTrajectoryControllerState::ConstPtr &state)
Callback from ROS message.
void update(const ros::TimerEvent &e)
Recieve data from controller via ROS message.
bool recordAll()
Whether to record at a specific frequency, or record all incoming data.
control_msgs::JointTrajectoryControllerState current_state_
#define ROS_WARN_STREAM_THROTTLE_NAMED(period, name, args)
std::vector< control_msgs::JointTrajectoryControllerState > states_
bool waitForSubscriber(const ros::Subscriber &sub, const double &wait_time=10.0)
Check if topic has been connected to successfully.
bool sleep()
void shutdownIfError(const std::string &parent_name, std::size_t error_count)
static Time now()
#define ROS_INFO_STREAM_THROTTLE_NAMED(period, name, args)
void startRecording(const std::string &file_name)
Start the data collection.
ROSCPP_DECL void spinOnce()
bool get(const std::string &parent_name, const ros::NodeHandle &nh, const std::string &param_name, bool &value)
ControllerToCSV(const std::string &topic)
Constructor.
#define ROS_WARN_STREAM_NAMED(name, args)


ros_control_boilerplate
Author(s): Dave Coleman
autogenerated on Mon Feb 28 2022 23:27:26