halt_detector.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA)
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 #include <ros/ros.h>
19 #include <cob_base_controller_utils/WheelCommands.h>
20 #include <controller_manager_msgs/SwitchController.h>
21 #include <diagnostic_msgs/DiagnosticArray.h>
22 #include <geometry_msgs/Twist.h>
23 #include <std_srvs/Trigger.h>
24 
28 std::vector<std::string> g_controller_spawn;
32 std::vector<ros::Time> g_last_wheel_commands_ok;
42 std::vector<std::string> g_diagnostic_recovery_trigger;
43 
44 
45 
50  controller_manager_msgs::SwitchController switch_srv;
51  switch_srv.request.start_controllers = g_controller_spawn;
52  switch_srv.request.strictness = 1;
53 
54  if (g_switch_controller_client.call(switch_srv)) {
55  if (switch_srv.response.ok) {
56  ROS_INFO("Successfully switched controllers");
57  } else {
58  ROS_ERROR("Could not switch controllers");
59  }
60  } else {
61  ROS_ERROR("ServiceCall failed: switch_controller");
62  }
63 }
64 
65 
69 void recover() {
70  if (!g_recovering) {
71  g_recovering = true;
72  std_srvs::Trigger srv;
73  g_recover_client.call(srv);
75  g_is_stopped = false;
76  g_is_stuck = false;
77  g_recovering = false;
78  }
79 }
80 
81 
85 void halt() {
86  std_srvs::Trigger srv;
87  g_halt_client.call(srv);
88 }
89 
90 
95  g_is_stopped = true;
96  ROS_INFO_STREAM("Halt, no movement commands received for " << g_stop_timeout << " seconds");
97  halt();
98 }
99 
100 
105  ROS_INFO("Recovering after stuck");
106  recover();
107  g_stop_timer.start();
108 
109  // Wait until the next stuck is possible to prevent "stuck loop"
110  g_stuck_timeout.sleep();
111  g_stuck_possible = true;
112 }
113 
121 bool containsSubstring(std::string string, std::string substring) {
122  if (std::strstr(string.c_str(), substring.c_str())) {
123  return true;
124  } else {
125  return false;
126  }
127 }
128 
136 void diagnosticsAggCallback(const diagnostic_msgs::DiagnosticArray::ConstPtr& msg) {
137  for (size_t i = 0; i < msg->status.size(); i++) {
138 
139  // Get status with name containing 'base' and 'wheel' or 'base' and 'rotation'
140  if (msg->status[i].level > diagnostic_msgs::DiagnosticStatus::WARN &&
141  containsSubstring(msg->status[i].name, "base") &&
142  (containsSubstring(msg->status[i].name, "wheel") ||
143  containsSubstring(msg->status[i].name, "rotation"))) {
144 
145  for (size_t j = 0; j < msg->status[i].values.size(); j++) {
146 
147  // Check if error value is in trigger list and recover eventually
148  if (msg->status[i].values[j].key == "errors") {
149  bool error = false;
150  for (int k = 0; k < g_diagnostic_recovery_trigger.size(); k++) {
151  if (containsSubstring(msg->status[i].values[j].value, g_diagnostic_recovery_trigger[k])) {
152  error = true;
153  }
154  }
155  if (error && !g_is_stuck && ros::Time::now() - g_last_diagnostic_recover_timestamp >= ros::Duration(2.0)) {
156  ROS_INFO("Recovering from diagnostic error");
157  g_stop_timer.stop();
158  recover();
159  g_last_diagnostic_recover_timestamp = ros::Time::now();
160  g_stop_timer.start();
161  }
162  }
163  }
164  }
165  }
166 }
167 
176 void wheelCommandsCallback(const cob_base_controller_utils::WheelCommands::ConstPtr& msg) {
177 
178  // Initialize last timestamp
179  if (g_last_wheel_commands_ok.size() == 0) {
180  g_last_wheel_commands_ok.resize(msg->steer_target_error.size(), msg->header.stamp);
181  }
182 
183  // Check if a wheel exceeds threshold
184  for (size_t i = 0; i < msg->steer_target_error.size(); ++i) {
185  if (fabs(msg->steer_target_error[i]) < g_stuck_threshold) {
186  g_last_wheel_commands_ok[i] = msg->header.stamp;
187  }
188  else {
189  ROS_DEBUG_STREAM("Wheel " << i << " exceeded stuck threshold");
190  }
191  }
192 
193  // Check if a wheel exceeds timeout
194  bool exceeded = false;
195  for (size_t i = 0; i < g_last_wheel_commands_ok.size(); ++i) {
196  if ((msg->header.stamp - g_last_wheel_commands_ok[i]) >= g_stuck_timeout) {
197  exceeded = true;
198  ROS_ERROR_STREAM("Wheel " << i << " exceeded stuck threshold and timeout");
199  }
200  }
201 
202  // Halt if exceeded and allowed
203  if (exceeded && !g_is_stuck && !g_is_stopped && g_stuck_possible) {
204  g_is_stuck = true;
205  g_stuck_possible = false;
206  g_recover_after_stuck_timer.stop();
207  ROS_ERROR("Halt, robot is stuck");
208  halt();
209  g_recover_after_stuck_timer.start();
210  }
211 }
212 
213 
223 void baseCommandsCallback(const geometry_msgs::Twist::ConstPtr& msg) {
224  if (fabs(msg->linear.x) >= g_stop_threshold ||
225  fabs(msg->linear.y) >= g_stop_threshold ||
226  fabs(msg->linear.z) >= g_stop_threshold ||
227  fabs(msg->angular.x) >= g_stop_threshold ||
228  fabs(msg->angular.y) >= g_stop_threshold ||
229  fabs(msg->angular.z) >= g_stop_threshold) {
230  g_stop_timer.stop();
231  if (!g_is_stuck) {
232  if (g_is_stopped) {
233  ROS_INFO("Recovering after stopped");
234  recover();
235  }
236  ROS_DEBUG("Robot is moving, restarting halt timer");
237  g_stop_timer.start();
238  }
239  }
240 }
241 
242 
243 int main(int argc, char* argv[])
244 {
245  ros::init(argc, argv, "cob_halt_detector");
246 
247  ros::NodeHandle nh;
248  ros::NodeHandle nh_priv("~");
249 
250  // Get ROS parameter
251  if (!nh_priv.getParam("stop_threshold", g_stop_threshold)) {
252  ROS_ERROR("Please provide a stop threshold");
253  return 1;
254  }
255 
256  if (!nh_priv.getParam("stuck_threshold", g_stuck_threshold)) {
257  ROS_ERROR("Please provide a stuck threshold");
258  return 1;
259  }
260 
261  double timeout;
262  if (!nh_priv.getParam("stop_timeout", timeout) || timeout <= 0.0) {
263  ROS_ERROR("Please provide valid stop timeout");
264  return 1;
265  }
266  g_stop_timeout = ros::Duration(timeout);
267 
268  if (!nh_priv.getParam("stuck_timeout", timeout) || timeout <= 0.0) {
269  ROS_ERROR("Please provide valid stuck timeout");
270  return 1;
271  }
272  g_stuck_timeout = ros::Duration(timeout);
273 
274  if (!nh_priv.getParam("recover_after_stuck_timeout", timeout) || timeout <= 0.0) {
275  ROS_ERROR("Please provide valid timeout for recovering after stuck");
276  return 1;
277  }
278  g_recover_after_stuck_timeout = ros::Duration(timeout);
279 
280  ros::Subscriber diagnostics_agg_sub;
281  if (nh_priv.hasParam("diagnostic_recovery_trigger")) {
282  nh_priv.getParam("diagnostic_recovery_trigger", g_diagnostic_recovery_trigger);
283  if (g_diagnostic_recovery_trigger.size() > 0) {
284  diagnostics_agg_sub = nh.subscribe("/diagnostics_agg", 10, diagnosticsAggCallback);
285  }
286  }
287  g_last_diagnostic_recover_timestamp = ros::Time(0);
288 
289  g_controller_spawn = {"joint_state_controller", "twist_controller"};
290 
292  g_recovering = false;
293  g_is_stopped = false;
294  g_is_stuck = false;
295  g_stuck_possible = true;
296 
297  g_halt_client = nh.serviceClient<std_srvs::Trigger>("driver/halt");
298  g_recover_client = nh.serviceClient<std_srvs::Trigger>("driver/recover");
299  g_switch_controller_client = nh.serviceClient<controller_manager_msgs::SwitchController>("controller_manager/switch_controller");
300  g_halt_client.waitForExistence();
301  g_recover_client.waitForExistence();
302  g_switch_controller_client.waitForExistence();
303 
304  g_stop_timer = nh.createTimer(g_stop_timeout, haltAfterStopTimeoutCallback, true, false); // oneshot=true, auto_start=false
305  g_recover_after_stuck_timer = nh.createTimer(g_recover_after_stuck_timeout, recoverAfterStuckCallback, true, false); // oneshot=true, auto_start=false
306 
307  ros::Subscriber base_commands_sub = nh.subscribe("twist_controller/command", 10, baseCommandsCallback);
308  ros::Subscriber wheel_commands_sub = nh.subscribe("twist_controller/wheel_commands", 10, wheelCommandsCallback);
309 
310  ros::spin();
311  return 0;
312 }
bool g_is_stuck
ros::Duration g_stuck_timeout
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
void halt()
void haltAfterStopTimeoutCallback(const ros::TimerEvent &)
ros::Time g_last_diagnostic_recover_timestamp
double g_stop_threshold
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
ros::ServiceClient g_halt_client
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
std::vector< std::string > g_controller_spawn
void stop()
bool containsSubstring(std::string string, std::string substring)
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
ros::ServiceClient g_recover_client
bool call(MReq &req, MRes &res)
void start()
void baseCommandsCallback(const geometry_msgs::Twist::ConstPtr &msg)
double g_stuck_threshold
void recover()
ros::Timer g_stop_timer
bool g_is_stopped
void switch_controllers()
bool waitForExistence(ros::Duration timeout=ros::Duration(-1))
#define ROS_INFO(...)
bool getParam(const std::string &key, std::string &s) const
ROSCPP_DECL void spin()
std::vector< std::string > g_diagnostic_recovery_trigger
bool g_recovering
#define ROS_DEBUG_STREAM(args)
int main(int argc, char *argv[])
void recoverAfterStuckCallback(const ros::TimerEvent &)
bool hasParam(const std::string &key) const
ros::ServiceClient g_switch_controller_client
#define ROS_INFO_STREAM(args)
bool g_stuck_possible
ros::Timer g_recover_after_stuck_timer
ros::Duration g_recover_after_stuck_timeout
static Time now()
ros::Duration g_stop_timeout
std::vector< ros::Time > g_last_wheel_commands_ok
void diagnosticsAggCallback(const diagnostic_msgs::DiagnosticArray::ConstPtr &msg)
bool sleep() const
#define ROS_ERROR_STREAM(args)
void wheelCommandsCallback(const cob_base_controller_utils::WheelCommands::ConstPtr &msg)
#define ROS_ERROR(...)
#define ROS_DEBUG(...)


cob_base_controller_utils
Author(s): Felix Messmer , Mathias Lüdtke
autogenerated on Mon May 1 2023 02:50:06