simple_offboard.cpp
Go to the documentation of this file.
1 /*
2  * Simplified copter control in OFFBOARD mode
3  * Copyright (C) 2019 Copter Express Technologies
4  *
5  * Author: Oleg Kalachev <okalachev@gmail.com>
6  *
7  * Distributed under MIT License (available at https://opensource.org/licenses/MIT).
8  * The above copyright notice and this permission notice shall be included in all
9  * copies or substantial portions of the Software.
10  */
11 
12 #include <algorithm>
13 #include <string>
14 #include <cmath>
15 #include <boost/format.hpp>
16 #include <stdexcept>
17 #include <GeographicLib/Geodesic.hpp>
18 #include <ros/ros.h>
19 #include <tf/transform_datatypes.h>
20 #include <tf2/utils.h>
25 #include <std_srvs/Trigger.h>
26 #include <geometry_msgs/PoseStamped.h>
27 #include <geometry_msgs/TwistStamped.h>
28 #include <geometry_msgs/Vector3Stamped.h>
29 #include <geometry_msgs/QuaternionStamped.h>
30 #include <sensor_msgs/NavSatFix.h>
31 #include <sensor_msgs/BatteryState.h>
32 #include <mavros_msgs/CommandBool.h>
33 #include <mavros_msgs/SetMode.h>
34 #include <mavros_msgs/PositionTarget.h>
35 #include <mavros_msgs/AttitudeTarget.h>
36 #include <mavros_msgs/Thrust.h>
37 #include <mavros_msgs/State.h>
38 #include <mavros_msgs/StatusText.h>
39 #include <mavros_msgs/ManualControl.h>
40 
41 #include <clover/GetTelemetry.h>
42 #include <clover/Navigate.h>
43 #include <clover/NavigateGlobal.h>
44 #include <clover/SetPosition.h>
45 #include <clover/SetVelocity.h>
46 #include <clover/SetAttitude.h>
47 #include <clover/SetRates.h>
48 
49 using std::string;
50 using std::isnan;
51 using namespace geometry_msgs;
52 using namespace sensor_msgs;
53 using namespace clover;
54 using mavros_msgs::PositionTarget;
55 using mavros_msgs::AttitudeTarget;
56 using mavros_msgs::Thrust;
57 
58 // tf2
60 std::shared_ptr<tf2_ros::TransformBroadcaster> transform_broadcaster;
61 std::shared_ptr<tf2_ros::StaticTransformBroadcaster> static_transform_broadcaster;
62 
63 // Parameters
64 string local_frame;
65 string fcu_frame;
80 std::map<string, string> reference_frames;
81 
82 // Publishers
84 
85 // Service clients
87 
88 // Containers
91 PoseStamped position_msg;
92 PositionTarget position_raw_msg;
93 AttitudeTarget att_raw_msg;
94 Thrust thrust_msg;
95 TwistStamped rates_msg;
96 TransformStamped target, setpoint;
97 geometry_msgs::TransformStamped body;
98 
99 // State
100 PoseStamped nav_start;
105 float nav_speed;
106 bool busy = false;
107 bool wait_armed = false;
108 bool nav_from_sp_flag = false;
109 
118 };
119 
121 
123 
124 // Last received telemetry messages
125 mavros_msgs::State state;
126 mavros_msgs::StatusText statustext;
127 mavros_msgs::ManualControl manual_control;
128 PoseStamped local_position;
129 TwistStamped velocity;
130 NavSatFix global_position;
131 BatteryState battery;
132 
133 // Common subscriber callback template that stores message to the variable
134 template<typename T, T& STORAGE>
135 void handleMessage(const T& msg)
136 {
137  STORAGE = msg;
138 }
139 
140 void handleState(const mavros_msgs::State& s)
141 {
142  state = s;
143  if (s.mode != "OFFBOARD") {
144  // flight intercepted
145  nav_from_sp_flag = false;
146  }
147 }
148 
149 inline void publishBodyFrame()
150 {
151  if (body.child_frame_id.empty()) return;
152 
153  tf::Quaternion q;
154  q.setRPY(0, 0, tf::getYaw(local_position.pose.orientation));
155  tf::quaternionTFToMsg(q, body.transform.rotation);
156 
157  body.transform.translation.x = local_position.pose.position.x;
158  body.transform.translation.y = local_position.pose.position.y;
159  body.transform.translation.z = local_position.pose.position.z;
160  body.header.frame_id = local_position.header.frame_id;
161  body.header.stamp = local_position.header.stamp;
162  transform_broadcaster->sendTransform(body);
163 }
164 
165 void handleLocalPosition(const PoseStamped& pose)
166 {
169  // TODO: terrain?, home?
170 }
171 
172 // wait for transform without interrupting publishing setpoints
173 inline bool waitTransform(const string& target, const string& source,
174  const ros::Time& stamp, const ros::Duration& timeout) // editorconfig-checker-disable-line
175 {
176  ros::Rate r(100);
177  auto start = ros::Time::now();
178  while (ros::ok()) {
179  if (ros::Time::now() - start > timeout) return false;
180  if (tf_buffer.canTransform(target, source, stamp)) return true;
181  ros::spinOnce();
182  r.sleep();
183  }
184 }
185 
186 #define TIMEOUT(msg, timeout) (ros::Time::now() - msg.header.stamp > timeout)
187 
188 bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
189 {
191 
192  if (req.frame_id.empty())
193  req.frame_id = local_frame;
194 
195  res.frame_id = req.frame_id;
196  res.x = NAN;
197  res.y = NAN;
198  res.z = NAN;
199  res.lat = NAN;
200  res.lon = NAN;
201  res.alt = NAN;
202  res.vx = NAN;
203  res.vy = NAN;
204  res.vz = NAN;
205  res.pitch = NAN;
206  res.roll = NAN;
207  res.yaw = NAN;
208  res.pitch_rate = NAN;
209  res.roll_rate = NAN;
210  res.yaw_rate = NAN;
211  res.voltage = NAN;
212  res.cell_voltage = NAN;
213 
214  if (!TIMEOUT(state, state_timeout)) {
215  res.connected = state.connected;
216  res.armed = state.armed;
217  res.mode = state.mode;
218  }
219 
220  try {
221  waitTransform(req.frame_id, fcu_frame, stamp, telemetry_transform_timeout);
222  auto transform = tf_buffer.lookupTransform(req.frame_id, fcu_frame, stamp);
223  res.x = transform.transform.translation.x;
224  res.y = transform.transform.translation.y;
225  res.z = transform.transform.translation.z;
226 
227  double yaw, pitch, roll;
228  tf2::getEulerYPR(transform.transform.rotation, yaw, pitch, roll);
229  res.yaw = yaw;
230  res.pitch = pitch;
231  res.roll = roll;
232  } catch (const tf2::TransformException& e) {
233  ROS_DEBUG("%s", e.what());
234  }
235 
236  if (!TIMEOUT(velocity, velocity_timeout)) {
237  try {
238  // transform velocity
239  waitTransform(req.frame_id, fcu_frame, velocity.header.stamp, telemetry_transform_timeout);
240  Vector3Stamped vec, vec_out;
241  vec.header.stamp = velocity.header.stamp;
242  vec.header.frame_id = velocity.header.frame_id;
243  vec.vector = velocity.twist.linear;
244  tf_buffer.transform(vec, vec_out, req.frame_id);
245 
246  res.vx = vec_out.vector.x;
247  res.vy = vec_out.vector.y;
248  res.vz = vec_out.vector.z;
249  } catch (const tf2::TransformException& e) {}
250 
251  // use angular velocities as they are
252  res.yaw_rate = velocity.twist.angular.z;
253  res.pitch_rate = velocity.twist.angular.y;
254  res.roll_rate = velocity.twist.angular.x;
255  }
256 
257  if (!TIMEOUT(global_position, global_position_timeout)) {
258  res.lat = global_position.latitude;
259  res.lon = global_position.longitude;
260  res.alt = global_position.altitude;
261  }
262 
263  if (!TIMEOUT(battery, battery_timeout)) {
264  res.voltage = battery.voltage;
265  if (!battery.cell_voltage.empty()) {
266  res.cell_voltage = battery.cell_voltage[0];
267  }
268  }
269 
270  return true;
271 }
272 
273 // throws std::runtime_error
275 {
276  ros::Rate r(10);
277 
278  if (state.mode != "OFFBOARD") {
279  auto start = ros::Time::now();
280  ROS_INFO("switch to OFFBOARD");
281  static mavros_msgs::SetMode sm;
282  sm.request.custom_mode = "OFFBOARD";
283 
284  if (!set_mode.call(sm))
285  throw std::runtime_error("Error calling set_mode service");
286 
287  // wait for OFFBOARD mode
288  while (ros::ok()) {
289  ros::spinOnce();
290  if (state.mode == "OFFBOARD") {
291  break;
292  } else if (ros::Time::now() - start > offboard_timeout) {
293  string report = "OFFBOARD timed out";
294  if (statustext.header.stamp > start)
295  report += ": " + statustext.text;
296  throw std::runtime_error(report);
297  }
298  ros::spinOnce();
299  r.sleep();
300  }
301  }
302 
303  if (!state.armed) {
305  ROS_INFO("arming");
306  mavros_msgs::CommandBool srv;
307  srv.request.value = true;
308  if (!arming.call(srv)) {
309  throw std::runtime_error("Error calling arming service");
310  }
311 
312  // wait until armed
313  while (ros::ok()) {
314  ros::spinOnce();
315  if (state.armed) {
316  break;
317  } else if (ros::Time::now() - start > arming_timeout) {
318  string report = "Arming timed out";
319  if (statustext.header.stamp > start)
320  report += ": " + statustext.text;
321  throw std::runtime_error(report);
322  }
323  ros::spinOnce();
324  r.sleep();
325  }
326  }
327 }
328 
329 inline double hypot(double x, double y, double z)
330 {
331  return std::sqrt(x * x + y * y + z * z);
332 }
333 
334 inline float getDistance(const Point& from, const Point& to)
335 {
336  return hypot(from.x - to.x, from.y - to.y, from.z - to.z);
337 }
338 
339 void getNavigateSetpoint(const ros::Time& stamp, float speed, Point& nav_setpoint)
340 {
341  if (wait_armed) {
342  // don't start navigating if we're waiting arming
343  nav_start.header.stamp = stamp;
344  }
345 
346  float distance = getDistance(nav_start.pose.position, setpoint_position_transformed.pose.position);
347  float time = distance / speed;
348  float passed = std::min((stamp - nav_start.header.stamp).toSec() / time, 1.0);
349 
350  nav_setpoint.x = nav_start.pose.position.x + (setpoint_position_transformed.pose.position.x - nav_start.pose.position.x) * passed;
351  nav_setpoint.y = nav_start.pose.position.y + (setpoint_position_transformed.pose.position.y - nav_start.pose.position.y) * passed;
352  nav_setpoint.z = nav_start.pose.position.z + (setpoint_position_transformed.pose.position.z - nav_start.pose.position.z) * passed;
353 }
354 
355 PoseStamped globalToLocal(double lat, double lon)
356 {
357  auto earth = GeographicLib::Geodesic::WGS84();
358 
359  // Determine azimuth and distance between current and destination point
360  double _, distance, azimuth;
361  earth.Inverse(global_position.latitude, global_position.longitude, lat, lon, distance, _, azimuth);
362 
363  double x_offset, y_offset;
364  double azimuth_radians = azimuth * M_PI / 180;
365  x_offset = distance * sin(azimuth_radians);
366  y_offset = distance * cos(azimuth_radians);
367 
368  if (!waitTransform(local_frame, fcu_frame, global_position.header.stamp, ros::Duration(0.2))) {
369  throw std::runtime_error("No local position");
370  }
371 
372  auto local = tf_buffer.lookupTransform(local_frame, fcu_frame, global_position.header.stamp);
373 
374  PoseStamped pose;
375  pose.header.stamp = global_position.header.stamp; // TODO: ?
376  pose.header.frame_id = local_frame;
377  pose.pose.position.x = local.transform.translation.x + x_offset;
378  pose.pose.position.y = local.transform.translation.y + y_offset;
379  pose.pose.orientation.w = 1;
380  return pose;
381 }
382 
383 void publish(const ros::Time stamp)
384 {
385  if (setpoint_type == NONE) return;
386 
387  position_raw_msg.header.stamp = stamp;
388  thrust_msg.header.stamp = stamp;
389  rates_msg.header.stamp = stamp;
390 
391  try {
392  // transform position and/or yaw
394  setpoint_position.header.stamp = stamp;
396  }
397 
398  // transform velocity
399  if (setpoint_type == VELOCITY) {
400  setpoint_velocity.header.stamp = stamp;
402  }
403 
404  } catch (const tf2::TransformException& e) {
405  ROS_WARN_THROTTLE(10, "can't transform");
406  }
407 
409  position_msg.pose.orientation = setpoint_position_transformed.pose.orientation; // copy yaw
410  getNavigateSetpoint(stamp, nav_speed, position_msg.pose.position);
411 
412  if (setpoint_yaw_type == TOWARDS) {
413  double yaw_towards = atan2(position_msg.pose.position.y - nav_start.pose.position.y,
414  position_msg.pose.position.x - nav_start.pose.position.x);
415  position_msg.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(0, 0, yaw_towards);
416  }
417  }
418 
419  if (setpoint_type == POSITION) {
421  }
422 
424  position_msg.header.stamp = stamp;
425 
427  position_pub.publish(position_msg);
428 
429  } else {
430  position_raw_msg.type_mask = PositionTarget::IGNORE_VX +
431  PositionTarget::IGNORE_VY +
432  PositionTarget::IGNORE_VZ +
433  PositionTarget::IGNORE_AFX +
434  PositionTarget::IGNORE_AFY +
435  PositionTarget::IGNORE_AFZ +
436  PositionTarget::IGNORE_YAW;
438  position_raw_msg.position = position_msg.pose.position;
439  position_raw_pub.publish(position_raw_msg);
440  }
441 
442  // publish setpoint frame
443  if (!setpoint.child_frame_id.empty()) {
444  setpoint.transform.translation.x = position_msg.pose.position.x;
445  setpoint.transform.translation.y = position_msg.pose.position.y;
446  setpoint.transform.translation.z = position_msg.pose.position.z;
447  setpoint.transform.rotation = position_msg.pose.orientation;
448  setpoint.header.frame_id = position_msg.header.frame_id;
449  setpoint.header.stamp = position_msg.header.stamp;
450  transform_broadcaster->sendTransform(setpoint);
451  }
452  }
453 
454  if (setpoint_type == VELOCITY) {
455  position_raw_msg.type_mask = PositionTarget::IGNORE_PX +
456  PositionTarget::IGNORE_PY +
457  PositionTarget::IGNORE_PZ +
458  PositionTarget::IGNORE_AFX +
459  PositionTarget::IGNORE_AFY +
460  PositionTarget::IGNORE_AFZ;
461  position_raw_msg.type_mask += setpoint_yaw_type == YAW ? PositionTarget::IGNORE_YAW_RATE : PositionTarget::IGNORE_YAW;
465  position_raw_pub.publish(position_raw_msg);
466  }
467 
468  if (setpoint_type == ATTITUDE) {
470  thrust_pub.publish(thrust_msg);
471  }
472 
473  if (setpoint_type == RATES) {
474  // rates_pub.publish(rates_msg);
475  // thrust_pub.publish(thrust_msg);
476  // mavros rates topics waits for rates in local frame
477  // use rates in body frame for simplicity
478  att_raw_msg.header.stamp = stamp;
479  att_raw_msg.header.frame_id = fcu_frame;
480  att_raw_msg.type_mask = AttitudeTarget::IGNORE_ATTITUDE;
481  att_raw_msg.body_rate = rates_msg.twist.angular;
482  att_raw_msg.thrust = thrust_msg.thrust;
483  attitude_raw_pub.publish(att_raw_msg);
484  }
485 }
486 
488 {
489  publish(event.current_real);
490 }
491 
492 inline void checkManualControl()
493 {
494  if (!manual_control_timeout.isZero() && TIMEOUT(manual_control, manual_control_timeout)) {
495  throw std::runtime_error("Manual control timeout, RC is switched off?");
496  }
497 
498  if (check_kill_switch) {
499  // switch values: https://github.com/PX4/PX4-Autopilot/blob/c302514a0809b1765fafd13c014d705446ae1113/msg/manual_control_setpoint.msg#L3
500  const uint8_t SWITCH_POS_NONE = 0; // switch is not mapped
501  const uint8_t SWITCH_POS_ON = 1; // switch activated
502  const uint8_t SWITCH_POS_MIDDLE = 2; // middle position
503  const uint8_t SWITCH_POS_OFF = 3; // switch not activated
504 
505  const int KILL_SWITCH_BIT = 12; // https://github.com/PX4/Firmware/blob/c302514a0809b1765fafd13c014d705446ae1113/src/modules/mavlink/mavlink_messages.cpp#L3975
506  uint8_t kill_switch = (manual_control.buttons & (0b11 << KILL_SWITCH_BIT)) >> KILL_SWITCH_BIT;
507 
508  if (kill_switch == SWITCH_POS_ON)
509  throw std::runtime_error("Kill switch is on");
510  }
511 }
512 
513 inline void checkState()
514 {
515  if (TIMEOUT(state, state_timeout))
516  throw std::runtime_error("State timeout, check mavros settings");
517 
518  if (!state.connected)
519  throw std::runtime_error("No connection to FCU, https://clover.coex.tech/connection");
520 }
521 
522 #define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); }
523 
524 bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz,
525  float pitch, float roll, float yaw, float pitch_rate, float roll_rate, float yaw_rate, // editorconfig-checker-disable-line
526  float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, // editorconfig-checker-disable-line
527  uint8_t& success, string& message) // editorconfig-checker-disable-line
528 {
529  auto stamp = ros::Time::now();
530 
531  try {
532  if (busy)
533  throw std::runtime_error("Busy");
534 
535  busy = true;
536 
537  // Checks
538  checkState();
539 
540  if (auto_arm) {
542  }
543 
544  // default frame is local frame
545  if (frame_id.empty())
546  frame_id = local_frame;
547 
548  // look up for reference frame
549  auto search = reference_frames.find(frame_id);
550  const string& reference_frame = search == reference_frames.end() ? frame_id : search->second;
551 
552  // Serve "partial" commands
553 
554  if (!auto_arm && std::isfinite(yaw) &&
555  isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
556  isnan(pitch) && isnan(roll) && isnan(thrust) &&
557  isnan(lat) && isnan(lon)) {
558  // change only the yaw
560  if (!waitTransform(setpoint_position.header.frame_id, frame_id, stamp, transform_timeout))
561  throw std::runtime_error("Can't transform from " + frame_id + " to " + setpoint_position.header.frame_id);
562 
563  message = "Changing yaw only";
564 
565  QuaternionStamped q;
566  q.header.frame_id = frame_id;
567  q.header.stamp = stamp;
568  q.quaternion = tf::createQuaternionMsgFromYaw(yaw); // TODO: pitch=0, roll=0 is not totally correct
569  setpoint_position.pose.orientation = tf_buffer.transform(q, setpoint_position.header.frame_id).quaternion;
571  goto publish_setpoint;
572  } else {
573  throw std::runtime_error("Setting yaw is possible only when position or velocity setpoints active");
574  }
575  }
576 
577  if (!auto_arm && std::isfinite(yaw_rate) &&
578  isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
579  isnan(pitch) && isnan(roll) && isnan(yaw) && isnan(thrust) &&
580  isnan(lat) && isnan(lon)) {
581  // change only the yaw rate
583  message = "Changing yaw rate only";
584 
586  setpoint_yaw_rate = yaw_rate;
587  goto publish_setpoint;
588  } else {
589  throw std::runtime_error("Setting yaw rate is possible only when position or velocity setpoints active");
590  }
591  }
592 
593  // Serve normal commands
594 
595  if (sp_type == NAVIGATE || sp_type == POSITION) {
596  ENSURE_FINITE(x);
597  ENSURE_FINITE(y);
598  ENSURE_FINITE(z);
599  } else if (sp_type == NAVIGATE_GLOBAL) {
600  ENSURE_FINITE(lat);
601  ENSURE_FINITE(lon);
602  ENSURE_FINITE(z);
603  } else if (sp_type == VELOCITY) {
604  ENSURE_FINITE(vx);
605  ENSURE_FINITE(vy);
606  ENSURE_FINITE(vz);
607  } else if (sp_type == ATTITUDE) {
608  ENSURE_FINITE(pitch);
609  ENSURE_FINITE(roll);
610  ENSURE_FINITE(thrust);
611  } else if (sp_type == RATES) {
612  ENSURE_FINITE(pitch_rate);
613  ENSURE_FINITE(roll_rate);
614  ENSURE_FINITE(thrust);
615  }
616 
617  if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
618  if (TIMEOUT(local_position, local_position_timeout))
619  throw std::runtime_error("No local position, check settings");
620 
621  if (speed < 0)
622  throw std::runtime_error("Navigate speed must be positive, " + std::to_string(speed) + " passed");
623 
624  if (speed == 0)
625  speed = default_speed;
626  }
627 
628  if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
629  if (yaw_rate != 0 && !std::isnan(yaw))
630  throw std::runtime_error("Yaw value should be NaN for setting yaw rate");
631 
632  if (std::isnan(yaw_rate) && std::isnan(yaw))
633  throw std::runtime_error("Both yaw and yaw_rate cannot be NaN");
634  }
635 
636  if (sp_type == NAVIGATE_GLOBAL) {
637  if (TIMEOUT(global_position, global_position_timeout))
638  throw std::runtime_error("No global position");
639  }
640 
641  if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY || sp_type == ATTITUDE) {
642  // make sure transform from frame_id to reference frame available
643  if (!waitTransform(reference_frame, frame_id, stamp, transform_timeout))
644  throw std::runtime_error("Can't transform from " + frame_id + " to " + reference_frame);
645 
646  // make sure transform from reference frame to local frame available
647  if (!waitTransform(local_frame, reference_frame, stamp, transform_timeout))
648  throw std::runtime_error("Can't transform from " + reference_frame + " to " + local_frame);
649  }
650 
651  if (sp_type == NAVIGATE_GLOBAL) {
652  // Calculate x and from lat and lot in request's frame
653  auto pose_local = globalToLocal(lat, lon);
654  pose_local.header.stamp = stamp; // TODO: fix
655  auto xy_in_req_frame = tf_buffer.transform(pose_local, frame_id);
656  x = xy_in_req_frame.pose.position.x;
657  y = xy_in_req_frame.pose.position.y;
658  }
659 
660  // Everything fine - switch setpoint type
661  setpoint_type = sp_type;
662 
663  if (sp_type != NAVIGATE && sp_type != NAVIGATE_GLOBAL) {
664  nav_from_sp_flag = false;
665  }
666 
667  if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
668  // starting point
669  if (nav_from_sp && nav_from_sp_flag) {
670  message = "Navigating from current setpoint";
672  } else {
674  }
675  nav_speed = speed;
676  nav_from_sp_flag = true;
677  }
678 
679  // if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
680  // if (std::isnan(yaw) && yaw_rate == 0) {
681  // // keep yaw unchanged
682  // // TODO: this is incorrect, because we need yaw in desired frame
683  // yaw = tf2::getYaw(local_position.pose.orientation);
684  // }
685  // }
686 
687  if (sp_type == POSITION || sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == VELOCITY || sp_type == ATTITUDE) {
688  // destination point and/or yaw
689  PoseStamped ps;
690  ps.header.frame_id = frame_id;
691  ps.header.stamp = stamp;
692  ps.pose.position.x = x;
693  ps.pose.position.y = y;
694  ps.pose.position.z = z;
695  ps.pose.orientation.w = 1.0; // Ensure quaternion is always valid
696 
697  if (std::isnan(yaw)) {
699  setpoint_yaw_rate = yaw_rate;
700  } else if (std::isinf(yaw) && yaw > 0) {
701  // yaw towards
703  yaw = 0;
704  setpoint_yaw_rate = 0;
705  } else {
707  setpoint_yaw_rate = 0;
708  ps.pose.orientation = tf::createQuaternionMsgFromYaw(yaw);
709  }
710 
711  tf_buffer.transform(ps, setpoint_position, reference_frame);
712  }
713 
714  if (sp_type == VELOCITY) {
715  static Vector3Stamped vel;
716  vel.header.frame_id = frame_id;
717  vel.header.stamp = stamp;
718  vel.vector.x = vx;
719  vel.vector.y = vy;
720  vel.vector.z = vz;
721  tf_buffer.transform(vel, setpoint_velocity, reference_frame);
722  }
723 
724  if (sp_type == ATTITUDE || sp_type == RATES) {
725  thrust_msg.thrust = thrust;
726  }
727 
728  if (sp_type == RATES) {
729  rates_msg.twist.angular.x = roll_rate;
730  rates_msg.twist.angular.y = pitch_rate;
731  rates_msg.twist.angular.z = yaw_rate;
732  }
733 
734  wait_armed = auto_arm;
735 
736 publish_setpoint:
737  publish(stamp); // calculate initial transformed messages first
738  setpoint_timer.start();
739 
740  // publish target frame
741  if (!target.child_frame_id.empty()) {
743  target.header.frame_id = setpoint_position.header.frame_id;
744  target.header.stamp = stamp;
745  target.transform.translation.x = setpoint_position.pose.position.x;
746  target.transform.translation.y = setpoint_position.pose.position.y;
747  target.transform.translation.z = setpoint_position.pose.position.z;
748  target.transform.rotation = setpoint_position.pose.orientation;
749  static_transform_broadcaster->sendTransform(target);
750  }
751  }
752 
753  if (auto_arm) {
754  offboardAndArm();
755  wait_armed = false;
756  } else if (state.mode != "OFFBOARD") {
757  setpoint_timer.stop();
758  throw std::runtime_error("Copter is not in OFFBOARD mode, use auto_arm?");
759  } else if (!state.armed) {
760  setpoint_timer.stop();
761  throw std::runtime_error("Copter is not armed, use auto_arm?");
762  }
763 
764  } catch (const std::exception& e) {
765  message = e.what();
766  ROS_INFO("%s", message.c_str());
767  busy = false;
768  return true;
769  }
770 
771  success = true;
772  busy = false;
773  return true;
774 }
775 
776 bool navigate(Navigate::Request& req, Navigate::Response& res) {
777  return serve(NAVIGATE, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
778 }
779 
780 bool navigateGlobal(NavigateGlobal::Request& req, NavigateGlobal::Response& res) {
781  return serve(NAVIGATE_GLOBAL, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, req.lat, req.lon, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
782 }
783 
784 bool setPosition(SetPosition::Request& req, SetPosition::Response& res) {
785  return serve(POSITION, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
786 }
787 
788 bool setVelocity(SetVelocity::Request& req, SetVelocity::Response& res) {
789  return serve(VELOCITY, NAN, NAN, NAN, req.vx, req.vy, req.vz, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
790 }
791 
792 bool setAttitude(SetAttitude::Request& req, SetAttitude::Response& res) {
793  return serve(ATTITUDE, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch, req.roll, req.yaw, NAN, NAN, NAN, NAN, NAN, req.thrust, NAN, req.frame_id, req.auto_arm, res.success, res.message);
794 }
795 
796 bool setRates(SetRates::Request& req, SetRates::Response& res) {
797  return serve(RATES, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch_rate, req.roll_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message);
798 }
799 
800 bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
801 {
802  try {
803  if (busy)
804  throw std::runtime_error("Busy");
805 
806  busy = true;
807 
808  checkState();
809 
810  if (land_only_in_offboard) {
811  if (state.mode != "OFFBOARD") {
812  throw std::runtime_error("Copter is not in OFFBOARD mode");
813  }
814  }
815 
816  static mavros_msgs::SetMode sm;
817  sm.request.custom_mode = "AUTO.LAND";
818 
819  if (!set_mode.call(sm))
820  throw std::runtime_error("Can't call set_mode service");
821 
822  if (!sm.response.mode_sent)
823  throw std::runtime_error("Can't send set_mode request");
824 
825  static ros::Rate r(10);
826  auto start = ros::Time::now();
827  while (ros::ok()) {
828  if (state.mode == "AUTO.LAND") {
829  res.success = true;
830  busy = false;
831  return true;
832  }
833  if (ros::Time::now() - start > land_timeout)
834  throw std::runtime_error("Land request timed out");
835 
836  ros::spinOnce();
837  r.sleep();
838  }
839 
840  } catch (const std::exception& e) {
841  res.message = e.what();
842  ROS_INFO("%s", e.what());
843  busy = false;
844  return true;
845  }
846 }
847 
848 int main(int argc, char **argv)
849 {
850  ros::init(argc, argv, "simple_offboard");
851  ros::NodeHandle nh, nh_priv("~");
852 
854  transform_broadcaster = std::make_shared<tf2_ros::TransformBroadcaster>();
855  static_transform_broadcaster = std::make_shared<tf2_ros::StaticTransformBroadcaster>();
856 
857  // Params
858  nh.param<string>("mavros/local_position/tf/frame_id", local_frame, "map");
859  nh.param<string>("mavros/local_position/tf/child_frame_id", fcu_frame, "base_link");
860  nh_priv.param("target_frame", target.child_frame_id, string("navigate_target"));
861  nh_priv.param("setpoint", setpoint.child_frame_id, string("setpoint"));
862  nh_priv.param("auto_release", auto_release, true);
863  nh_priv.param("land_only_in_offboard", land_only_in_offboard, true);
864  nh_priv.param("nav_from_sp", nav_from_sp, true);
865  nh_priv.param("check_kill_switch", check_kill_switch, true);
866  nh_priv.param("default_speed", default_speed, 0.5f);
867  nh_priv.param<string>("body_frame", body.child_frame_id, "body");
868  nh_priv.getParam("reference_frames", reference_frames);
869 
870  state_timeout = ros::Duration(nh_priv.param("state_timeout", 3.0));
871  local_position_timeout = ros::Duration(nh_priv.param("local_position_timeout", 2.0));
872  velocity_timeout = ros::Duration(nh_priv.param("velocity_timeout", 2.0));
873  global_position_timeout = ros::Duration(nh_priv.param("global_position_timeout", 10.0));
874  battery_timeout = ros::Duration(nh_priv.param("battery_timeout", 2.0));
875  manual_control_timeout = ros::Duration(nh_priv.param("manual_control_timeout", 0.0));
876 
877  transform_timeout = ros::Duration(nh_priv.param("transform_timeout", 0.5));
878  telemetry_transform_timeout = ros::Duration(nh_priv.param("telemetry_transform_timeout", 0.5));
879  offboard_timeout = ros::Duration(nh_priv.param("offboard_timeout", 3.0));
880  land_timeout = ros::Duration(nh_priv.param("land_timeout", 3.0));
881  arming_timeout = ros::Duration(nh_priv.param("arming_timeout", 4.0));
882 
883  // Service clients
884  arming = nh.serviceClient<mavros_msgs::CommandBool>("mavros/cmd/arming");
885  set_mode = nh.serviceClient<mavros_msgs::SetMode>("mavros/set_mode");
886 
887  // Telemetry subscribers
888  auto state_sub = nh.subscribe("mavros/state", 1, &handleState);
889  auto velocity_sub = nh.subscribe("mavros/local_position/velocity_body", 1, &handleMessage<TwistStamped, velocity>);
890  auto global_position_sub = nh.subscribe("mavros/global_position/global", 1, &handleMessage<NavSatFix, global_position>);
891  auto battery_sub = nh.subscribe("mavros/battery", 1, &handleMessage<BatteryState, battery>);
892  auto statustext_sub = nh.subscribe("mavros/statustext/recv", 1, &handleMessage<mavros_msgs::StatusText, statustext>);
893  auto manual_control_sub = nh.subscribe("mavros/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
894  auto local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &handleLocalPosition);
895 
896  // Setpoint publishers
897  position_pub = nh.advertise<PoseStamped>("mavros/setpoint_position/local", 1);
898  position_raw_pub = nh.advertise<PositionTarget>("mavros/setpoint_raw/local", 1);
899  attitude_pub = nh.advertise<PoseStamped>("mavros/setpoint_attitude/attitude", 1);
900  attitude_raw_pub = nh.advertise<AttitudeTarget>("mavros/setpoint_raw/attitude", 1);
901  rates_pub = nh.advertise<TwistStamped>("mavros/setpoint_attitude/cmd_vel", 1);
902  thrust_pub = nh.advertise<Thrust>("mavros/setpoint_attitude/thrust", 1);
903 
904  // Service servers
905  auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry);
906  auto na_serv = nh.advertiseService("navigate", &navigate);
907  auto ng_serv = nh.advertiseService("navigate_global", &navigateGlobal);
908  auto sp_serv = nh.advertiseService("set_position", &setPosition);
909  auto sv_serv = nh.advertiseService("set_velocity", &setVelocity);
910  auto sa_serv = nh.advertiseService("set_attitude", &setAttitude);
911  auto sr_serv = nh.advertiseService("set_rates", &setRates);
912  auto ld_serv = nh.advertiseService("land", &land);
913 
914  // Setpoint timer
915  setpoint_timer = nh.createTimer(ros::Duration(1 / nh_priv.param("setpoint_rate", 30.0)), &publishSetpoint, false, false);
916 
917  position_msg.header.frame_id = local_frame;
918  position_raw_msg.header.frame_id = local_frame;
919  position_raw_msg.coordinate_frame = PositionTarget::FRAME_LOCAL_NED;
920  rates_msg.header.frame_id = fcu_frame;
921 
922  ROS_INFO("ready");
923  ros::spin();
924 }
void publishSetpoint(const ros::TimerEvent &event)
bool nav_from_sp_flag
mavros_msgs::ManualControl manual_control
bool check_kill_switch
ros::Duration telemetry_transform_timeout
ros::Duration arming_timeout
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
Thrust thrust_msg
std::shared_ptr< tf2_ros::StaticTransformBroadcaster > static_transform_broadcaster
GeographicLib::Geocentric earth
enum setpoint_type_t setpoint_type
void getEulerYPR(const A &a, double &yaw, double &pitch, double &roll)
ros::Publisher position_pub
T & transform(const T &in, T &out, const std::string &target_frame, ros::Duration timeout=ros::Duration(0.0)) const
Vector3Stamped setpoint_velocity_transformed
ros::Publisher position_raw_pub
ros::NodeHandle nh
ROSCPP_DECL void start()
setpoint_type_t
stamp
ros::Publisher rates_pub
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
QuaternionStamped setpoint_attitude
void handleMessage(const T &msg)
PoseStamped local_position
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
bool busy
ros::Subscriber statustext_sub
double hypot(double x, double y, double z)
TransformStamped setpoint
tf::Quaternion tq
static double getYaw(const Quaternion &bt_q)
void stop()
ros::Duration timeout
Definition: shell.cpp:12
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
bool call(MReq &req, MRes &res)
bool navigateGlobal(NavigateGlobal::Request &req, NavigateGlobal::Response &res)
void start()
PoseStamped setpoint_position_transformed
bool getTelemetry(GetTelemetry::Request &req, GetTelemetry::Response &res)
ros::ServiceClient arming
PoseStamped setpoint_position
ros::Duration manual_control_timeout
void offboardAndArm()
geometry_msgs::TransformStamped body
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
tf::Vector3 Point
bool waitTransform(const string &target, const string &source, const ros::Time &stamp, const ros::Duration &timeout)
std::map< string, string > reference_frames
float setpoint_yaw_rate
#define ENSURE_FINITE(var)
std::string to_string(MAV_SENSOR_ORIENTATION orientation)
TransformStamped target
int16_t r
Definition: rc.cpp:247
bool wait_armed
void checkManualControl()
void checkState()
PositionTarget position_raw_msg
ros::Publisher attitude_raw_pub
bool land_only_in_offboard
ros::Duration offboard_timeout
string fcu_frame
ros::Duration transform_timeout
virtual bool canTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &target_time, const ros::Duration timeout, std::string *errstr=NULL) const
bool param(const std::string &param_name, T &param_val, const T &default_val) const
virtual geometry_msgs::TransformStamped lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration timeout) const
void publish(const boost::shared_ptr< M > &message) const
ros::Duration state_timeout
ros::Duration global_position_timeout
std::string frame_id
bool nav_from_sp
PoseStamped nav_start
std::shared_ptr< tf2_ros::TransformBroadcaster > transform_broadcaster
#define ROS_WARN_THROTTLE(period,...)
void setRPY(const tfScalar &roll, const tfScalar &pitch, const tfScalar &yaw)
PoseStamped position_msg
#define ROS_INFO(...)
bool getParam(const std::string &key, std::string &s) const
float nav_speed
ros::Duration land_timeout
static void quaternionTFToMsg(const Quaternion &bt, geometry_msgs::Quaternion &msg)
QuaternionStamped setpoint_attitude_transformed
ROSCPP_DECL bool ok()
bool setVelocity(SetVelocity::Request &req, SetVelocity::Response &res)
void getNavigateSetpoint(const ros::Time &stamp, float speed, Point &nav_setpoint)
ros::Subscriber local_position_sub
bool setPosition(SetPosition::Request &req, SetPosition::Response &res)
bool setRates(SetRates::Request &req, SetRates::Response &res)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
int16_t x
Definition: rc.cpp:247
ROSCPP_DECL void spin()
ros::Duration battery_timeout
ros::Duration local_position_timeout
ros::Duration velocity_timeout
bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz, float pitch, float roll, float yaw, float pitch_rate, float roll_rate, float yaw_rate, float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, uint8_t &success, string &message)
bool sleep()
double getYaw(const A &a)
mavros_msgs::State state
int16_t y
Definition: rc.cpp:247
bool land(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res)
static geometry_msgs::Quaternion createQuaternionMsgFromYaw(double yaw)
BatteryState battery
void publishBodyFrame()
ros::Publisher attitude_pub
INLINE Rall1d< T, V, S > atan2(const Rall1d< T, V, S > &y, const Rall1d< T, V, S > &x)
ftf::StaticTF transform
static geometry_msgs::Quaternion createQuaternionMsgFromRollPitchYaw(double roll, double pitch, double yaw)
#define TIMEOUT(msg, timeout)
static Time now()
ros::ServiceClient set_mode
ros::Timer setpoint_timer
TwistStamped rates_msg
int main(int argc, char **argv)
bool auto_release
PoseStamped pose
TwistStamped velocity
INLINE Rall1d< T, V, S > cos(const Rall1d< T, V, S > &arg)
int16_t z
Definition: rc.cpp:247
float getDistance(const Point &from, const Point &to)
ros::Publisher thrust_pub
ROSCPP_DECL void spinOnce()
mavros_msgs::StatusText statustext
ROSCPP_DECL bool search(const std::string &ns, const std::string &key, std::string &result)
bool setAttitude(SetAttitude::Request &req, SetAttitude::Response &res)
void handleState(const mavros_msgs::State &s)
INLINE Rall1d< T, V, S > sin(const Rall1d< T, V, S > &arg)
float default_speed
bool navigate(Navigate::Request &req, Navigate::Response &res)
string local_frame
PoseStamped globalToLocal(double lat, double lon)
NavSatFix global_position
tf2_ros::Buffer tf_buffer
void publish(const ros::Time stamp)
void handleLocalPosition(const PoseStamped &pose)
AttitudeTarget att_raw_msg
enum @0 setpoint_yaw_type
#define ROS_DEBUG(...)
Vector3Stamped setpoint_velocity


clover
Author(s): Oleg Kalachev , Artem Smirnov
autogenerated on Mon Feb 28 2022 22:08:29