qb_device_control.cpp
Go to the documentation of this file.
1 /***
2  * Software License Agreement: BSD 3-Clause License
3  *
4  * Copyright (c) 2016-2018, qbrobotics®
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
8  * following conditions are met:
9  *
10  * * Redistributions of source code must retain the above copyright notice, this list of conditions and the
11  * following disclaimer.
12  *
13  * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
14  * following disclaimer in the documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
17  * products derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
20  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
25  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
29 
30 using namespace qb_device_control;
31 
33  : spinner_(1, callback_queue_.get()), // the dedicated callback queue is needed to avoid deadlocks caused by action client calls (together with controller manager update loop)
34  callback_queue_(boost::make_shared<ros::CallbackQueue>()),
35  node_handle_(ros::NodeHandle()),
36  node_handle_control_(node_handle_, "control"),
37  control_duration_(node_handle_.param<double>("control_duration", 0.01)),
38  init_success_(devices_.init(node_handle_, node_handle_)),
39  controller_manager_(&devices_, node_handle_control_) {
41  spinner_.start();
42 
43  if (init_success_) {
44  // combined_robot_hw properly initialized (i.e. robot_hardware exists)
45  node_handle_.getParam("robot_hardware", device_names_);
46 
47  if (node_handle_.param("robot_activate_control", true)) {
49 
50  if (node_handle_.param("use_waypoints", false)) {
52  }
53 
54  get_measurements_client_ = node_handle_.serviceClient<qb_device_srvs::GetMeasurements>("/communication_handler/get_measurements", true);
55  set_commands_client_ = node_handle_.serviceClient<qb_device_srvs::SetCommands>("/communication_handler/set_commands", true);
56  set_pid_client_ = node_handle_.serviceClient<qb_device_srvs::SetPID>("/communication_handler/set_pid", true);
60  }
61 
62  frequency_publisher_ = node_handle_.advertise<std_msgs::Int32>("frequency", 1);
64  }
65 }
66 
70  spinner_.stop();
71 }
72 
73 void qbDeviceControl::actionActiveCallback(const std::string &controller) {
74  ROS_INFO_STREAM_NAMED("robot_control", "Controller [" << controller << "] action start.");
75 }
76 
77 void qbDeviceControl::actionDoneCallback(const actionlib::SimpleClientGoalState &state, const control_msgs::FollowJointTrajectoryResultConstPtr &result, const std::string &controller) {
78  if (result->error_code != result->SUCCESSFUL) {
79  ROS_WARN_STREAM_NAMED("robot_control", "Controller [" << controller << "] action ended in state [" << state.toString() <<"] with error code [" << result->error_code << "]");
80  }
81  else {
82  ROS_INFO_STREAM_NAMED("robot_control", "Controller [" << controller << "] action ended in state [" << state.toString() <<"].");
83  }
84 
85  // automatically restart waypoint trajectories if not empty and all Actions have ended
86  if (!joint_trajectories_.empty()) {
87  if (std::all_of(controllers_.begin(), controllers_.end(), [this](auto c){ return action_clients_.at(c)->getState().isDone(); })) {
88  move();
89  }
90  }
91 }
92 
93 void qbDeviceControl::actionFeedbackCallback(const control_msgs::FollowJointTrajectoryFeedbackConstPtr &feedback, const std::string &controller) {
94  for (int i=0; i<feedback->joint_names.size(); i++) {
95  ROS_DEBUG_STREAM_NAMED("robot_control", "Controller [" << controller << "] joint [" << feedback->joint_names.at(i) << "] state is [" << feedback->actual.positions.at(i) << "] (expecting [" << feedback->desired.positions.at(i) << "]).");
96  }
97 }
98 
100  std::lock_guard<std::mutex> motor_lock(sync_protector_); // automatically released at the end of the callback
101  update(timer_event.current_real, timer_event.current_real - timer_event.last_real);
102  counter_++;
103 
104  // can serve async pending request when the lock is released
105 }
106 
109  counter_ = 0;
110 
113 
114  // automatically restart waypoint trajectories if not empty
115  if (!joint_trajectories_.empty()) {
116  ros::Duration(0.5).sleep();
117  move();
118  }
119 }
120 
121 std::string qbDeviceControl::extractDeviceName(const std::string &controller) {
122  for (auto const &device_name : device_names_) {
123  std::smatch match;
124  if (std::regex_search(controller, match, std::regex("^" + device_name))) {
125  return match[0];
126  }
127  }
128  ROS_ERROR_STREAM("Controller [" << controller << "] has no associated device hardware interface.");
129  return "";
130 }
131 
133  frequency_publisher_.publish([this](){ std_msgs::Int32 hz_msg; hz_msg.data = counter_; counter_ = 0; return hz_msg; }()); // publish the control loop real Hz
134 }
135 
136 bool qbDeviceControl::getAsyncMeasurementsCallback(qb_device_srvs::GetMeasurementsRequest &request, qb_device_srvs::GetMeasurementsResponse &response) {ros::Time now(ros::Time::now());
137  std::lock_guard<std::mutex> motor_lock(sync_protector_); // automatically released at the end of the callback
139  get_measurements_client_.call(request, response);
140  return true;
141  }
142  ROS_ERROR_STREAM_NAMED("robot_control", "Required service seems no longer advertised.");
143  return false;
144 }
145 
146 std::vector<trajectory_msgs::JointTrajectoryPoint> qbDeviceControl::getSinusoidalPoints(const double &amplitude, const double &period, const int &samples_per_period, const int &periods) {
147  std::vector<trajectory_msgs::JointTrajectoryPoint> points;
148  double delta_period = period/samples_per_period;
149  double omega = 2*M_PI/period; // angular frequency of the wave
150 
151  for (int i=0; i<samples_per_period*periods; i++) {
152  trajectory_msgs::JointTrajectoryPoint point;
153  double point_time = (i+1)*delta_period;
154 
155  point.positions.push_back(amplitude * std::sin(omega*point_time)); // A sin(wt)
156  point.velocities.push_back(amplitude*omega * std::cos(omega*point_time)); // Aw cos(wt)
157  point.accelerations.push_back(-amplitude*std::pow(omega,2) * std::sin(omega*point_time)); // -Aw^2 sin(wt)
158  point.time_from_start = ros::Duration(point_time);
159 
160  points.push_back(point);
161  }
162 
163  return points;
164 }
165 
166 std::vector<trajectory_msgs::JointTrajectoryPoint> qbDeviceControl::getTrapezoidalPoints(const double &amplitude, const double &period, const double &ramp_duration, const int &periods) {
167  std::vector<trajectory_msgs::JointTrajectoryPoint> points;
168  std::vector<double> semi_amplitudes({amplitude, amplitude, -amplitude, -amplitude});
169  std::vector<double> semi_periods({ramp_duration, period/2, period/2+ramp_duration, period});
170 
171  for (int i=0; i<semi_periods.size()*periods; i++) {
172  trajectory_msgs::JointTrajectoryPoint point;
173  double point_time = i*period + semi_periods.at(i%semi_periods.size());
174 
175  point.positions.push_back(semi_amplitudes.at(i%semi_amplitudes.size()));
176  point.velocities.push_back(0);
177  point.accelerations.push_back(0);
178  point.time_from_start = ros::Duration(point_time);
179 
180  points.push_back(point);
181  }
182 
183  return points;
184 }
185 
186 trajectory_msgs::JointTrajectory qbDeviceControl::getCustomTrajectory(const std::vector<std::vector<trajectory_msgs::JointTrajectoryPoint>> &joint_points, const std::string &controller) {
187  if (joint_points.size() != controller_joints_.at(controller).size()) {
188  ROS_ERROR_STREAM_NAMED("robot_control", "Cannot build joint trajectory because joint sizes mismatch.");
189  return trajectory_msgs::JointTrajectory();
190  }
191  auto min_max_sizes(std::minmax_element(joint_points.begin(), joint_points.end(), [](auto pl, auto pr){ return pl.size() < pr.size(); }));
192  if (min_max_sizes.first != min_max_sizes.second) {
193  ROS_ERROR_STREAM_NAMED("robot_control", "Cannot build joint trajectory because point sizes mismatch.");
194  return trajectory_msgs::JointTrajectory();
195  }
196 
197  trajectory_msgs::JointTrajectory joint_trajectory;
198  for (int i=0; i<joint_points.front().size(); i++) {
199  trajectory_msgs::JointTrajectoryPoint point;
200  for (int j=0; j<joint_points.size(); j++) {
201  point.positions.push_back(joint_points.at(j).at(i).positions.front());
202  point.velocities.push_back(joint_points.at(j).at(i).velocities.front());
203  point.accelerations.push_back(joint_points.at(j).at(i).accelerations.front());
204  }
205  point.time_from_start = joint_points.front().at(i).time_from_start; // it is assumed that time_from_start are the same for each joint
206  joint_trajectory.points.push_back(point);
207  }
208 
209  joint_trajectory.joint_names = controller_joints_.at(controller);
210  joint_trajectory.header.stamp = ros::Time(0);
211  return joint_trajectory;
212 }
213 
214 trajectory_msgs::JointTrajectory qbDeviceControl::getWaypointTrajectory(ros::NodeHandle &node_handle, const std::string &controller) {
215  XmlRpc::XmlRpcValue waypoints;
216  if (!node_handle.getParam("waypoints", waypoints)) {
217  ROS_ERROR_STREAM_NAMED("robot_control", "No waypoints specified in the Parameter Server under [" << node_handle.getNamespace() << "/waypoints].");
218  return trajectory_msgs::JointTrajectory();
219  }
220 
221  trajectory_msgs::JointTrajectory joint_trajectory;
222  for (int i=0; i<waypoints.size(); i++) {
223  trajectory_msgs::JointTrajectoryPoint point;
224  std::string device_name(controller_device_name_.at(controller));
225 
226  if (!waypoints[i].hasMember("time")) {
227  continue;
228  }
229 
230  // retrieve joint positions
231  if (!waypoints[i].hasMember("joint_positions") || !waypoints[i]["joint_positions"].hasMember(device_name) || !parseVector(waypoints[i]["joint_positions"][device_name], controller, point.positions)) {
232  continue;
233  }
234 
235  // retrieve joint velocities and accelerations only if there are also positions associated
236  if (!waypoints[i].hasMember("joint_velocities") || !waypoints[i]["joint_velocities"].hasMember(device_name) || !parseVector(waypoints[i]["joint_velocities"][device_name], controller, point.velocities)) {
237  point.velocities = std::vector<double>(controller_joints_.at(controller).size(), 0.0);
238  }
239  if (!waypoints[i].hasMember("joint_accelerations") || !waypoints[i]["joint_accelerations"].hasMember(device_name) || !parseVector(waypoints[i]["joint_accelerations"][device_name], controller, point.accelerations)) {
240  point.accelerations = std::vector<double>(controller_joints_.at(controller).size(), 0.0);
241  }
242 
243  // set joint waypoint for the whole time interval (be aware of joint trajectory interpolation)
244  for (int j=0; j<waypoints[i]["time"].size(); j++) {
245  point.time_from_start = ros::Duration(xmlCast<double>(waypoints[i]["time"][j]));
246  joint_trajectory.points.push_back(point);
247  }
248  }
249  if (!joint_trajectory.points.empty()) {
250  joint_trajectory.header.frame_id = controller_device_name_.at(controller);
251  joint_trajectory.header.stamp = ros::Time(0);
252  joint_trajectory.joint_names = controller_joints_.at(controller);
253  }
254  return joint_trajectory;
255 }
256 
258  XmlRpc::XmlRpcValue parameters;
259  if (!node_handle_control_.getParam("", parameters)) {
260  ROS_ERROR_STREAM_NAMED("robot_control", "Fails while retrieving the parameters.");
261  return;
262  }
263  for (auto const &param : parameters) {
264  std::string controller(param.first);
265  std::vector<std::string> controller_joints;
266  if (std::regex_match(controller, std::regex(".*_controller$")) && node_handle_control_.hasParam(controller + "/type") && node_handle_control_.getParam(controller + "/joints", controller_joints)) {
267  std::string controller_action(controller + "/follow_joint_trajectory");
268  controllers_.push_back(controller);
269  controller_device_name_.insert(std::make_pair(controller, extractDeviceName(controller))); //TODO: fix for multi-device controller (use controller joints instead of controller name)
270  controller_joints_.insert(std::make_pair(controller, controller_joints));
271  action_clients_.insert(std::make_pair(controller, std::make_unique<actionlib::SimpleActionClient<control_msgs::FollowJointTrajectoryAction>>(node_handle_control_, controller_action, false)));
272  }
273  }
274 }
275 
277  for (auto const &controller : controllers_) {
278  move(joint_trajectories_.at(controller), controller);
279  }
280 }
281 
282 void qbDeviceControl::move(const trajectory_msgs::JointTrajectory &joint_trajectory, const std::string &controller) {
283  control_msgs::FollowJointTrajectoryAction control_action;
284  control_action.action_goal.goal.trajectory = joint_trajectory;
285  control_action.action_goal.header.stamp = ros::Time(0);
286  action_clients_.at(controller)->sendGoal(control_action.action_goal.goal,
287  std::bind(&qbDeviceControl::actionDoneCallback, this, std::placeholders::_1, std::placeholders::_2, controller),
288  std::bind(&qbDeviceControl::actionActiveCallback, this, controller),
289  std::bind(&qbDeviceControl::actionFeedbackCallback, this, std::placeholders::_1, controller));
290 }
291 
292 bool qbDeviceControl::parseVector(const XmlRpc::XmlRpcValue &xml_value, const std::string &controller, std::vector<double> &vector) {
293  if (xml_value.size() != controller_joints_.at(controller).size()) {
294  ROS_ERROR_STREAM_NAMED("robot_control", "Device [" << controller_device_name_.at(controller) << "] fails while setting the joint trajectory (joints size mismatch).");
295  return false;
296  }
297  for (int j=0; j<xml_value.size(); j++) {
298  vector.push_back(xmlCast<double>(xml_value[j]));
299  }
300  return true;
301 }
302 
304  joint_trajectories_.clear();
305  for (auto const &controller : controllers_) {
306  trajectory_msgs::JointTrajectory trajectory(getWaypointTrajectory(node_handle_, controller));
307  if (!trajectory.points.empty()) {
308  joint_trajectories_.insert(std::make_pair(controller, trajectory));
309  }
310  }
311 }
312 
313 bool qbDeviceControl::setAsyncCommandsCallback(qb_device_srvs::SetCommandsRequest &request, qb_device_srvs::SetCommandsResponse &response) {
314  std::lock_guard<std::mutex> motor_lock(sync_protector_); // automatically released at the end of the callback
315  if (set_commands_client_) {
316  set_commands_client_.call(request, response);
317  return true;
318  }
319  ROS_ERROR_STREAM_NAMED("robot_control", "Required service seems no longer advertised.");
320  return false;
321 }
322 
323 bool qbDeviceControl::setAsyncPIDCallback(qb_device_srvs::SetPIDRequest &request, qb_device_srvs::SetPIDResponse &response) {
324  std::lock_guard<std::mutex> motor_lock(sync_protector_); // automatically released at the end of the callback
325  if (set_pid_client_) {
326  set_pid_client_.call(request, response);
327  return true;
328  }
329  ROS_ERROR_STREAM_NAMED("robot_control", "Required service seems no longer advertised.");
330  return false;
331 }
332 
333 void qbDeviceControl::update(const ros::WallTime &time, const ros::WallDuration &period) {
334  // read the state from the hardware
335  devices_.read(ros::Time(time.toSec()), ros::Duration(period.toSec()));
336 
337  // update the controllers (set the new control actions)
339 
340  // write the commands to the hardware
341  devices_.write(ros::Time(time.toSec()), ros::Duration(period.toSec()));
342 }
343 
344 bool qbDeviceControl::waitForResult(const ros::Duration &timeout, const std::string &controller) {
345  return action_clients_.at(controller)->waitForResult(timeout);
346 }
347 
348 template<class T>
350  // XmlRpcValue does not handle conversion among types but throws an exception if an improper cast is invoked
351  if (xml_value.getType() == XmlRpc::XmlRpcValue::TypeBoolean) {
352  return static_cast<bool>(xml_value);
353  }
354  if (xml_value.getType() == XmlRpc::XmlRpcValue::TypeDouble) {
355  return static_cast<double>(xml_value);
356  }
357  if (xml_value.getType() == XmlRpc::XmlRpcValue::TypeInt) {
358  return static_cast<int>(xml_value);
359  }
360  ROS_ERROR_STREAM_NAMED("robot_control", "Fails while casting the XmlRpcValue [" << xml_value << "].");
361  return 0;
362 }
ros::ServiceServer set_async_commands_server_
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
bool param(const std::string &param_name, T &param_val, const T &default_val)
#define ROS_DEBUG_STREAM_NAMED(name, args)
void publish(const boost::shared_ptr< M > &message) const
#define ROS_ERROR_STREAM_NAMED(name, args)
bool getAsyncMeasurementsCallback(qb_device_srvs::GetMeasurementsRequest &request, qb_device_srvs::GetMeasurementsResponse &response)
Make a call to the same type of service provided by the Communication Handler.
trajectory_msgs::JointTrajectory getCustomTrajectory(const std::vector< std::vector< trajectory_msgs::JointTrajectoryPoint >> &joint_points, const std::string &controller)
Build a joint trajectory for the given controller using the given points, which must match the contro...
bool setAsyncCommandsCallback(qb_device_srvs::SetCommandsRequest &request, qb_device_srvs::SetCommandsResponse &response)
Make a call to the same type of service provided by the Communication Handler.
void init(const M_string &remappings)
int size() const
bool waitForResult(const ros::Duration &timeout, const std::string &controller)
Wait until the action is completed or the given timeout is reached.
bool sleep() const
bool call(MReq &req, MRes &res)
void initActionClients()
Retrieve all the controllers which have their parameters set in the Parameter Server and initialize t...
std::vector< trajectory_msgs::JointTrajectoryPoint > getTrapezoidalPoints(const double &amplitude, const double &period, const double &ramp_duration, const int &periods)
Build a single-joint trapezoidal wave point trajectory from the given parameters. ...
std::map< std::string, std::string > controller_device_name_
std::map< std::string, trajectory_msgs::JointTrajectory > joint_trajectories_
std::map< std::string, std::unique_ptr< actionlib::SimpleActionClient< control_msgs::FollowJointTrajectoryAction > > > action_clients_
ros::ServiceServer get_async_measurements_server_
#define ROS_INFO_STREAM_NAMED(name, args)
std::vector< std::string > controllers_
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
T xmlCast(XmlRpc::XmlRpcValue xml_value)
Cast an XmlRpcValue from TypeDouble, TypeInt or TypeBoolean to the specified template type...
Type const & getType() const
bool parseVector(const XmlRpc::XmlRpcValue &xml_value, const std::string &controller, std::vector< double > &vector)
Parse the given XmlRpcValue as a std::vector, since the XmlRpc::XmlRpcValue class does not handle thi...
void update(const ros::WallTime &time, const ros::WallDuration &period)
Read the current state from the HW, update all active controllers, and send the new references to the...
std::string extractDeviceName(const std::string &controller)
Extract the device names associated to the given controller (each controller name is assumed to start...
std::string toString() const
void controlSetupCallback(const ros::WallTimerEvent &timer_event)
Initialize the control timer and automatically start the waypoint trajectory if it is not empty...
void actionDoneCallback(const actionlib::SimpleClientGoalState &state, const control_msgs::FollowJointTrajectoryResultConstPtr &result, const std::string &controller)
Restart the waypoint trajectory automatically if waypoints have been retrieved during the initializat...
ROSCPP_DECL bool get(const std::string &key, std::string &s)
void controlCallback(const ros::WallTimerEvent &timer_event)
Call the update() each time the timer triggers.
bool param(const std::string &param_name, T &param_val, const T &default_val) const
void setCallbackQueue(CallbackQueueInterface *queue)
std::vector< trajectory_msgs::JointTrajectoryPoint > getSinusoidalPoints(const double &amplitude, const double &period, const int &samples_per_period, const int &periods)
Build a single-joint sine wave point trajectory from the given parameters.
virtual void read(const ros::Time &time, const ros::Duration &period)
std::map< std::string, std::vector< std::string > > controller_joints_
qbDeviceControl()
Initialize the control structures needed by ros_control: the combined_robot_hw::CombinedRobotHW to co...
const std::string & getNamespace() const
WallTimer createWallTimer(WallDuration period, void(T::*callback)(const WallTimerEvent &), T *obj, bool oneshot=false, bool autostart=true) const
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
bool setAsyncPIDCallback(qb_device_srvs::SetPIDRequest &request, qb_device_srvs::SetPIDResponse &response)
Make a call to the same type of service provided by the Communication Handler.
bool hasMember(const std::string &name) const
void move()
Make all the calls to the Action Servers relative to all the waypoint trajectories previously parsed...
bool getParam(const std::string &key, std::string &s) const
void frequencyCallback(const ros::WallTimerEvent &timer_event)
Publish the real control loop frequency in Hz in the relative topic.
void actionFeedbackCallback(const control_msgs::FollowJointTrajectoryFeedbackConstPtr &feedback, const std::string &controller)
Do nothing apart from debug info.
static Time now()
void parseWaypoints()
Parse all the waypoints set up in the Parameter Server at <robot_namespace>/waypoints.
virtual void write(const ros::Time &time, const ros::Duration &period)
bool hasParam(const std::string &key) const
#define ROS_ERROR_STREAM(args)
void update(const ros::Time &time, const ros::Duration &period, bool reset_controllers=false)
virtual ~qbDeviceControl()
Stop timer and spinner structures.
void actionActiveCallback(const std::string &controller)
Do nothing apart from debug info.
std::vector< std::string > device_names_
controller_manager::ControllerManager controller_manager_
#define ROS_WARN_STREAM_NAMED(name, args)
trajectory_msgs::JointTrajectory getWaypointTrajectory(ros::NodeHandle &node_handle, const std::string &controller)
Retrieve a set of reference waypoints from the Parameter Server.
combined_robot_hw::CombinedRobotHW devices_


qb_device_control
Author(s): qbrobotics®
autogenerated on Thu Jun 6 2019 19:46:33