Note: This tutorial assumes that you have completed the previous tutorials: Getting Started.
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Running the flight control sample code

Description: This tutorial helps you to set up and run the flight control sample code, and explain the core functions of the sample code.

Tutorial Level: INTERMEDIATE

Preparation

Before running the example code, please follow the Getting Started tutorial and have the A3/N3 hardware, dji_sdk node and the simulation up and running.

The Flight Control Sample

This sample code controls the vehicle to take off, draw an L shape (half of a square) and lands. More specifically, the programs monitors the take off of the vehicle, starting from ( x = 0, y = 0 , z = 0 , yaw = 0 ), and control it to fly to ( x = 0, y = 6 , z = 3 , yaw = 60 ) and ( x = 6, y = 0 , z = 0 , yaw = 60 ) position w.r.t. the ground frame.

Running the Code

If do not have the sample code along with the dji_sdk ROS package, download the dji_sdk_demo and place the folder within your catkin_ws/src folder. Then, in your catkin workspace root directory, run catkin_make and compile.

Assume you have followed the Getting Started tutorial and have the dji_sdk node running in one of the terminals using the launch file, open another terminal, cd to your catkin workspace, then type and enter:

$ rosrun dji_sdk_demo demo_flight_control

Note: If terminal returns Error: package 'dji_sdk_demo' not found, 'catkin_make' the workspace and 'source devel/setup.bash' the terminal.

You can setup the DJI Simulator by right clicking the simulated drone and click setup. Enable trace will show the flight trajectory of the simulated drone. By running this example code, you will see the drone flying a L pattern.

Code Walk Through

In this section, we will explain the work flow of this sample code.

The main function

   1 //
   2 // Created by zyli on 5/11/17.
   3 //
   4 
   5 #include "dji_sdk_demo/demo_flight_control.h"
   6 #include "dji_sdk/dji_sdk.h"
   7 
   8 const float deg2rad = C_PI/180.0;
   9 const float rad2deg = 180.0/C_PI;
  10 
  11 ros::ServiceClient sdk_ctrl_authority_service;
  12 ros::ServiceClient drone_task_service;
  13 
  14 ros::Publisher ctrlPosYawPub;
  15 ros::Publisher ctrlVelYawRatePub;
  16 
  17 // global variables for subscribed topics
  18 uint8_t flight_status = 255;
  19 uint8_t display_mode  = 255;
  20 sensor_msgs::NavSatFix current_gps;
  21 geometry_msgs::Quaternion current_atti;
  22 
  23 Mission square_mission;
  24 
  25 int main(int argc, char** argv)
  26 {
  27   ros::init(argc, argv, "demo_flight_control_node");
  28   ros::NodeHandle nh;
  29 
  30   // Subscribe to messages from dji_sdk_node
  31   ros::Subscriber attitudeSub = nh.subscribe("dji_sdk/attitude", 10, &attitude_callback);
  32   ros::Subscriber gpsSub      = nh.subscribe("dji_sdk/gps_position", 10, &gps_callback);
  33   ros::Subscriber flightStatusSub = nh.subscribe("dji_sdk/flight_status", 10, &flight_status_callback);
  34   ros::Subscriber displayModeSub = nh.subscribe("dji_sdk/display_mode", 10, &display_mode_callback);
  35 
  36   // Publish the control signal
  37   ctrlPosYawPub = nh.advertise<sensor_msgs::Joy>("/dji_sdk/flight_control_setpoint_ENUposition_yaw", 10);
  38   ctrlVelYawRatePub = nh.advertise<sensor_msgs::Joy>("dji_sdk/flight_control_setpoint_generic", 10);
  39   // Basic services
  40   sdk_ctrl_authority_service = nh.serviceClient<dji_sdk::SDKControlAuthority> ("dji_sdk/sdk_control_authority");
  41   drone_task_service         = nh.serviceClient<dji_sdk::DroneTaskControl>("dji_sdk/drone_task_control");
  42 
  43   bool obtain_control_result = obtain_control();
  44   bool takeoff_result = monitoredTakeoff();
  45 
  46   if(takeoff_result)
  47   {
  48     square_mission.reset();
  49     square_mission.start_gps_location = current_gps;
  50     square_mission.setTarget(0, 6, 3, 60);
  51     square_mission.state = 1;
  52     ROS_INFO("##### Start route %d ....", square_mission.state);
  53   }
  54 
  55   ros::spin();
  56   return 0;
  57 }

Open 'dji_sdk_demo/src/demo_flight_control.cpp', the main function is as above. Let's break the code down.

   7 const float deg2rad = C_PI/180.0;
   8 const float rad2deg = 180.0/C_PI;
   9 
  10 ros::ServiceClient sdk_ctrl_authority_service;
  11 ros::ServiceClient drone_task_service;
  12 
  13 ros::Publisher ctrlPosYawPub;
  14 ros::Publisher ctrlVelYawRatePub;
  15 
  16 // global variables for subscribed topics
  17 uint8_t flight_status = 255;
  18 uint8_t display_mode  = 255;
  19 sensor_msgs::NavSatFix current_gps;
  20 geometry_msgs::Quaternion current_atti;

Above are the declaration of the global variables.

  27   ros::init(argc, argv, "demo_flight_control_node");
  28   ros::NodeHandle nh;

Initialize the node with the node name "demo_flight_control_node"; create a node handle.

  30   // Subscribe to messages from dji_sdk_node
  31   ros::Subscriber attitudeSub = nh.subscribe("dji_sdk/attitude", 10, &attitude_callback);
  32   ros::Subscriber gpsSub      = nh.subscribe("dji_sdk/gps_position", 10, &gps_callback);
  33   ros::Subscriber flightStatusSub = nh.subscribe("dji_sdk/flight_status", 10, &flight_status_callback);
  34   ros::Subscriber displayModeSub = nh.subscribe("dji_sdk/display_mode", 10, &display_mode_callback);

Subscribe to the topics with needed information. The received information will be saved inside the global variables for other functions to use. The subscribed topics gives us the information to close feedback loops. Assign a callback function to each subscribed topic. The buffer size of the topic is set to 10, but if you only need the latest info, it could be set to 1. For more information on flight status and display mode, see linked pages.

  36   // Publish the control signal
  37   ctrlPosYawPub = nh.advertise<sensor_msgs::Joy>("/dji_sdk/flight_control_setpoint_ENUposition_yaw", 10);
  38   ctrlVelYawRatePub = nh.advertise<sensor_msgs::Joy>("dji_sdk/flight_control_setpoint_generic", 10);

Initialize the publishers for the vehicle control. The ctrlPosYawPub publishes commands for position-controlling the x, y position and the yaw angle of the vehicle with respect to (w.r.t.) the ground truth coordinate. The 'ctrlVelYawRatePub publishes commands for velocity-controlling the xy` velocity and the rate of change of the yaw angle of the vehicle w.r.t. the ground truth coordinate. This is achieved using the generic control and it flag variable.

  39   // Basic services
  40   sdk_ctrl_authority_service = nh.serviceClient<dji_sdk::SDKControlAuthority> ("dji_sdk/sdk_control_authority");
  41   drone_task_service         = nh.serviceClient<dji_sdk::DroneTaskControl>("dji_sdk/drone_task_control");

Initialize the services we need. The sdk_ctrl_authority_service will be used to request control authority of the vehicle, so we can have obtain the vehicle's control. The drone_task_service is used later to request takeoff and landing of the vehicle.

  43   bool obtain_control_result = obtain_control();

Call obtain_control() to obtain control of the vehicle, utilizing the sdk_ctrl_authority_service service.

  44   bool takeoff_result = monitoredTakeoff();

Calls monitoredTakeoff() to request the vehicle to takeoff, this function also utilizes flight status and display mode to check if the vehicle is indeed hovering in air.

  46   if(takeoff_result)
  47   {
  48     square_mission.reset();
  49     square_mission.start_gps_location = current_gps;
  50     square_mission.setTarget(0, 6, 3, 60);
  51     square_mission.state = 1;
  52     ROS_INFO("##### Start route %d ....", square_mission.state);
  53   }

The Mission class of the 'square_mission' is a state machine defined in 'demo_flight_control.h' that can be found in the include folder. If we have successfully taken off, reset all the counters within the class variable with 'square_mission.reset()'. Then it takes the current_gps data as a reference start point using 'square_mission.start_gps_location = current_gps'. Set the next target global position with 'square_mission.setTarget(0, 6, 3, 60)'. The Mission class variable is initialized with state equal to 0 (state 0 does nothing), so we start the state machine by setting it to 1 with 'square_mission.state = 1'.

  55   ros::spin();

Start the ros spin();

The GPS callback

This is the function that is used to continuously run and update the state machine. We utilized the fact that the GPS callback function will be continuously called as long as the GPS signal is being published by the dji_sdk node.

 252 void gps_callback(const sensor_msgs::NavSatFix::ConstPtr& msg)
 253 {
 254   static ros::Time start_time = ros::Time::now();
 255   ros::Duration elapsed_time = ros::Time::now() - start_time;
 256   current_gps = *msg;
 257 
 258   // Down sampled to 50Hz loop
 259   if(elapsed_time > ros::Duration(0.02))
 260   {
 261     //ROS_INFO("Elapsed time: %f; GPS: lat=%.3f, long=%.3f", elapsed_time.toSec(), msg->latitude, msg->longitude);
 262     start_time = ros::Time::now();
 263     switch(square_mission.state)
 264     {
 265       case 0:
 266         break;
 267 
 268       case 1:
 269         if(!square_mission.finished)
 270         {
 271           square_mission.step();
 272         }
 273         else
 274         {
 275           square_mission.reset();
 276           square_mission.start_gps_location = current_gps;
 277           square_mission.setTarget(6, 0, 0, 0);
 278           square_mission.state = 2;
 279           ROS_INFO("##### Start route %d ....", square_mission.state);
 280         }
 281         break;
 282 
 283       case 2:
 284         if(!square_mission.finished)
 285         {
 286           square_mission.step();
 287         }
 288         else
 289         {
 290           ROS_INFO("##### Mission %d Finished ....", square_mission.state);
 291           square_mission.state = 0;
 292         }
 293         break;
 294     }
 295   }
 296 }

This callback function serves two purposes: 1. Update the 'current_gps' position. 2. update the state machine.

 256   current_gps = *msg;

Update the global 'current_gps' variable every time the callback function is being triggered.

 254   static ros::Time start_time = ros::Time::now();
 255   ros::Duration elapsed_time = ros::Time::now() - start_time;

Calculate the 'elapse_time' since the last update on the 'start_time'.

 258  // Down sampled to 50Hz loop
 259   if(elapsed_time > ros::Duration(0.02))
 260   {

Force the code inside the 'if' statement to run at most 50 Hz.

 262     start_time = ros::Time::now();

Once inside the if statement, update the 'start_time' to current time.

 263 switch(square_mission.state)
 264     {
 265       case 0:
 266         break;
 267 
 268       case 1:
 269         if(!square_mission.finished)
 270         {
 271           square_mission.step();
 272         }
 273         else
 274         {
 275           square_mission.reset();
 276           square_mission.start_gps_location = current_gps;
 277           square_mission.setTarget(6, 0, 0, 0);
 278           square_mission.state = 2;
 279           ROS_INFO("##### Start route %d ....", square_mission.state);
 280         }
 281         break;
 282 
 283       case 2:
 284         if(!square_mission.finished)
 285         {
 286           square_mission.step();
 287         }
 288         else
 289         {
 290           ROS_INFO("##### Mission %d Finished ....", square_mission.state);
 291           square_mission.state = 0;
 292         }
 293         break;
 294     }

This is the state machine that dictate the vehicle to go to each of the desinated target positions.

 265       case 0:
 266         break;

The state 0 is an idle state.

 268       case 1:
 269         if(!square_mission.finished)
 270         {
 271           square_mission.step();
 272         }
 273         else
 274         {
 275           square_mission.reset();
 276           square_mission.start_gps_location = current_gps;
 277           square_mission.setTarget(6, 0, 0, 0);
 278           square_mission.state = 2;
 279           ROS_INFO("##### Start route %d ....", square_mission.state);
 280         }
 281         break;

We assigned the 'square_mission.state' to '1', we also set our target to (0,6,3,60). Therefore, while the current target is not reached, which means 'square_mission.finished==0', then continue with stepping toward the target using square_mission.step(). If we reached the target position in 'state 1', the set the next target, update the state.

 283 case 2:
 284         if(!square_mission.finished)
 285         {
 286           square_mission.step();
 287         }
 288         else
 289         {
 290           ROS_INFO("##### Mission %d Finished ....", square_mission.state);
 291           square_mission.state = 0;
 292         }
 293         break;

check if the target has been reached, if not, step towart the target. Rinse and repeat.

Your Turn

Now try yourself and modify the code so the vihicle can draw a square!

Wiki: blah/tutorial/test (last edited 2017-06-06 17:44:40 by jifeixu)