LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/Rover - GCS_MAVLink_Rover.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 300 489 61.3 %
Date: 2026-09-09 11:02:08 Functions: 28 31 90.3 %

          Line data    Source code
       1             : #include "Rover.h"
       2             : 
       3             : #include "GCS_MAVLink_Rover.h"
       4             : 
       5             : #include <AP_RPM/AP_RPM_config.h>
       6             : #include <AP_RangeFinder/AP_RangeFinder_Backend.h>
       7             : #include <AP_EFI/AP_EFI_config.h>
       8             : #include <AC_Avoidance/AP_OADatabase.h>
       9             : 
      10       15726 : MAV_TYPE GCS_Rover::frame_type() const
      11             : {
      12       15726 :     if (rover.is_boat()) {
      13        1056 :         return MAV_TYPE_SURFACE_BOAT;
      14             :     }
      15       14670 :     return MAV_TYPE_GROUND_ROVER;
      16             : }
      17             : 
      18       15726 : uint8_t GCS_MAVLINK_Rover::base_mode() const
      19             : {
      20       15726 :     uint8_t _base_mode = MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
      21             : 
      22             :     // work out the base_mode. This value is not very useful
      23             :     // for APM, but we calculate it as best we can so a generic
      24             :     // MAVLink enabled ground station can work out something about
      25             :     // what the MAV is up to. The actual bit values are highly
      26             :     // ambiguous for most of the APM flight modes. In practice, you
      27             :     // only get useful information from the custom_mode, which maps to
      28             :     // the APM flight mode and has a well defined meaning in the
      29             :     // ArduPlane documentation
      30       15726 :     if (rover.control_mode->has_manual_input()) {
      31       10386 :         _base_mode |= MAV_MODE_FLAG_MANUAL_INPUT_ENABLED;
      32             :     }
      33             : 
      34       15726 :     if (rover.control_mode->is_autopilot_mode()) {
      35        5236 :         _base_mode |= MAV_MODE_FLAG_GUIDED_ENABLED;
      36             :     }
      37             : 
      38       15726 :     if (rover.g2.stick_mixing > 0 && rover.control_mode != &rover.mode_initializing) {
      39             :         // all modes except INITIALISING have some form of manual
      40             :         // override if stick mixing is enabled
      41          24 :         _base_mode |= MAV_MODE_FLAG_MANUAL_INPUT_ENABLED;
      42             :     }
      43             : 
      44             :     // we are armed if we are not initialising
      45       15726 :     if (rover.control_mode != &rover.mode_initializing && rover.arming.is_armed()) {
      46        5274 :         _base_mode |= MAV_MODE_FLAG_SAFETY_ARMED;
      47             :     }
      48             : 
      49             :     // indicate we have set a custom mode
      50       15726 :     _base_mode |= MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
      51             : 
      52       15726 :     return _base_mode;
      53             : }
      54             : 
      55       15726 : uint32_t GCS_Rover::custom_mode() const
      56             : {
      57       15726 :     return (uint32_t)rover.control_mode->mode_number();
      58             : }
      59             : 
      60       14999 : MAV_STATE GCS_MAVLINK_Rover::vehicle_system_status() const
      61             : {
      62       14999 :     if ((rover.failsafe.triggered != 0) || rover.failsafe.ekf) {
      63         153 :         return MAV_STATE_CRITICAL;
      64             :     }
      65       14846 :     if (rover.control_mode == &rover.mode_initializing) {
      66        1192 :         return MAV_STATE_CALIBRATING;
      67             :     }
      68       13654 :     if (rover.control_mode == &rover.mode_hold) {
      69         101 :         return MAV_STATE_STANDBY;
      70             :     }
      71             : 
      72       13553 :     return MAV_STATE_ACTIVE;
      73             : }
      74             : 
      75      124902 : bool GCS_MAVLINK_Rover::get_target_location(Location &target) const
      76             : {
      77      124902 :     return rover.control_mode->get_desired_location(target);
      78             : }
      79             : 
      80      124899 : void GCS_MAVLINK_Rover::send_nav_controller_output() const
      81             : {
      82      124899 :     if (!rover.control_mode->is_autopilot_mode()) {
      83       73936 :         return;
      84             :     }
      85             : 
      86       50963 :     const Mode *control_mode = rover.control_mode;
      87             : 
      88      101926 :     mavlink_msg_nav_controller_output_send(
      89       50963 :         chan,
      90             :         0,  // roll
      91             :         degrees(rover.g2.attitude_control.get_desired_pitch()),
      92       50963 :         control_mode->nav_bearing(),
      93       50963 :         control_mode->wp_bearing(),
      94      101926 :         MIN(control_mode->get_distance_to_destination(), UINT16_MAX),
      95             :         0,
      96             :         control_mode->speed_error(),
      97       50963 :         control_mode->crosstrack_error_m());
      98             : }
      99             : 
     100      125466 : int16_t GCS_MAVLINK_Rover::vfr_hud_throttle() const
     101             : {
     102      125466 :     return rover.g2.motors.get_throttle();
     103             : }
     104             : 
     105             : #if AP_MAVLINK_MSG_RANGEFINDER_SENDING_ENABLED
     106           2 : void GCS_MAVLINK_Rover::send_rangefinder() const
     107             : {
     108           2 :     float distance = 0;
     109           2 :     float voltage = 0;
     110           2 :     bool got_one = false;
     111             : 
     112             :     // report smaller distance of all rangefinders
     113           4 :     for (uint8_t i=0; i<rover.rangefinder.num_sensors(); i++) {
     114           2 :         AP_RangeFinder_Backend *s = rover.rangefinder.get_backend(i);
     115           2 :         if (s == nullptr) {
     116           0 :             continue;
     117             :         }
     118           2 :         if (!got_one ||
     119           0 :             s->distance() < distance) {
     120           2 :             distance = s->distance();
     121           2 :             voltage = s->voltage_mv();
     122           2 :             got_one = true;
     123             :         }
     124             :     }
     125           2 :     if (!got_one) {
     126             :         // no relevant data found
     127           0 :         return;
     128             :     }
     129             : 
     130           2 :     mavlink_msg_rangefinder_send(
     131           2 :         chan,
     132             :         distance,
     133             :         voltage);
     134             : }
     135             : #endif  // AP_MAVLINK_MSG_RANGEFINDER_SENDING_ENABLED
     136             : 
     137             : #if AP_RANGEFINDER_ENABLED
     138      124943 : void GCS_MAVLINK_Rover::send_water_depth()
     139             : {
     140      124943 :     if (!HAVE_PAYLOAD_SPACE(chan, WATER_DEPTH)) {
     141      123570 :         return;
     142             :     }
     143             : 
     144             :     // only send for boats:
     145      124943 :     if (!rover.is_boat()) {
     146      114901 :         return;
     147             :     }
     148             : 
     149       10042 :     RangeFinder *rangefinder = RangeFinder::get_singleton();
     150             : 
     151       10042 :     if (rangefinder == nullptr) {
     152           0 :         return;
     153             :     }
     154             : 
     155             :     // depth can only be measured by a downward-facing rangefinder:
     156       10042 :     if (!rangefinder->has_orientation(ROTATION_PITCH_270)) {
     157        8669 :         return;
     158             :     }
     159             : 
     160             :     // get position
     161        1373 :     const AP_AHRS &ahrs = AP::ahrs();
     162        1373 :     Location loc;
     163        1373 :     IGNORE_RETURN(ahrs.get_location(loc));
     164             : 
     165        1373 :     const auto num_sensors = rangefinder->num_sensors();
     166        4121 :     for (uint8_t i=0; i<num_sensors; i++) {
     167        4121 :         last_WATER_DEPTH_index += 1;
     168        4121 :         if (last_WATER_DEPTH_index >= num_sensors) {
     169         686 :             last_WATER_DEPTH_index = 0;
     170             :         }
     171             : 
     172        4121 :         const AP_RangeFinder_Backend *s = rangefinder->get_backend(last_WATER_DEPTH_index);
     173             : 
     174        4121 :         if (s == nullptr || s->orientation() != ROTATION_PITCH_270 || !s->has_data()) {
     175        2748 :             continue;
     176             :         }
     177             : 
     178             :         // get temperature
     179             :         float temp_C;
     180        1373 :         if (!s->get_temp(temp_C)) {
     181         687 :             temp_C = 0.0f;
     182             :         }
     183             : 
     184        1373 :         const bool sensor_healthy = (s->status() == RangeFinder::Status::Good);
     185             : 
     186        2746 :         mavlink_msg_water_depth_send(
     187             :             chan,
     188             :             AP_HAL::millis(),   // time since system boot TODO: take time of measurement
     189        1373 :             last_WATER_DEPTH_index, // rangefinder instance
     190             :             sensor_healthy,     // sensor healthy
     191             :             loc.lat,            // latitude of vehicle
     192             :             loc.lng,            // longitude of vehicle
     193        1373 :             loc.alt * 0.01f,    // altitude of vehicle (MSL)
     194             :             ahrs.get_roll_rad(),    // roll in radians
     195             :             ahrs.get_pitch_rad(),   // pitch in radians
     196             :             ahrs.get_yaw_rad(),     // yaw in radians
     197             :             s->distance(),    // distance in meters
     198             :             temp_C);            // temperature in degC
     199             : 
     200        1373 :         break;  // only send one WATER_DEPTH message per loop
     201             :     }
     202             : 
     203             : }
     204             : #endif  // AP_RANGEFINDER_ENABLED
     205             : 
     206             : /*
     207             :   send PID tuning message
     208             :  */
     209      124899 : void GCS_MAVLINK_Rover::send_pid_tuning()
     210             : {
     211      124899 :     Parameters &g = rover.g;
     212      124899 :     ParametersG2 &g2 = rover.g2;
     213             : 
     214             :     const AP_PIDInfo *pid_info;
     215             : 
     216             :     // steering PID
     217      124899 :     if (g.gcs_pid_mask & 1) {
     218          17 :         pid_info = &g2.attitude_control.get_steering_rate_pid().get_pid_info();
     219          17 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_STEER,
     220          17 :                                     degrees(pid_info->target),
     221          17 :                                     degrees(pid_info->actual),
     222          17 :                                     pid_info->FF,
     223          17 :                                     pid_info->P,
     224          17 :                                     pid_info->I,
     225          17 :                                     pid_info->D,
     226          17 :                                     pid_info->slew_rate,
     227          17 :                                     pid_info->Dmod);
     228          17 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     229           0 :             return;
     230             :         }
     231             :     }
     232             : 
     233             :     // speed to throttle PID
     234      124899 :     if (g.gcs_pid_mask & 2) {
     235           0 :         pid_info = &g2.attitude_control.get_throttle_speed_pid_info();
     236           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_ACCZ,
     237           0 :                                     pid_info->target,
     238           0 :                                     pid_info->actual,
     239           0 :                                     pid_info->FF,
     240           0 :                                     pid_info->P,
     241           0 :                                     pid_info->I,
     242           0 :                                     pid_info->D,
     243           0 :                                     pid_info->slew_rate,
     244           0 :                                     pid_info->Dmod);
     245           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     246           0 :             return;
     247             :         }
     248             :     }
     249             : 
     250             :     // pitch to throttle pid
     251      124899 :     if (g.gcs_pid_mask & 4) {
     252           0 :         pid_info = &g2.attitude_control.get_pitch_to_throttle_pid().get_pid_info();
     253           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_PITCH,
     254           0 :                                     degrees(pid_info->target),
     255           0 :                                     degrees(pid_info->actual),
     256           0 :                                     pid_info->FF,
     257           0 :                                     pid_info->P,
     258           0 :                                     pid_info->I,
     259           0 :                                     pid_info->D,
     260           0 :                                     pid_info->slew_rate,
     261           0 :                                     pid_info->Dmod);
     262           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     263           0 :             return;
     264             :         }
     265             :     }
     266             : 
     267             :     // left wheel rate control pid
     268      124899 :     if (g.gcs_pid_mask & 8) {
     269           0 :         pid_info = &g2.wheel_rate_control.get_pid(0).get_pid_info();
     270           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_WHEEL_LEFT,
     271           0 :                                     pid_info->target,
     272           0 :                                     pid_info->actual,
     273           0 :                                     pid_info->FF,
     274           0 :                                     pid_info->P,
     275           0 :                                     pid_info->I,
     276           0 :                                     pid_info->D,
     277           0 :                                     pid_info->slew_rate,
     278           0 :                                     pid_info->Dmod);
     279           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     280           0 :             return;
     281             :         }
     282             :     }
     283             : 
     284             :     // right wheel rate control pid
     285      124899 :     if (g.gcs_pid_mask & 16) {
     286           0 :         pid_info = &g2.wheel_rate_control.get_pid(1).get_pid_info();
     287           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_WHEEL_RIGHT,
     288           0 :                                     pid_info->target,
     289           0 :                                     pid_info->actual,
     290           0 :                                     pid_info->FF,
     291           0 :                                     pid_info->P,
     292           0 :                                     pid_info->I,
     293           0 :                                     pid_info->D,
     294           0 :                                     pid_info->slew_rate,
     295           0 :                                     pid_info->Dmod);
     296           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     297           0 :             return;
     298             :         }
     299             :     }
     300             : 
     301             :     // sailboat heel to mainsail pid
     302      124899 :     if (g.gcs_pid_mask & 32) {
     303           0 :         pid_info = &g2.attitude_control.get_sailboat_heel_pid().get_pid_info();
     304           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_SAIL_HEEL,
     305           0 :                                     pid_info->target,
     306           0 :                                     pid_info->actual,
     307           0 :                                     pid_info->FF,
     308           0 :                                     pid_info->P,
     309           0 :                                     pid_info->I,
     310           0 :                                     pid_info->D,
     311           0 :                                     pid_info->slew_rate,
     312           0 :                                     pid_info->Dmod);
     313           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     314           0 :             return;
     315             :         }
     316             :     }
     317             : 
     318             :     // Position Controller Velocity North PID
     319      124899 :     if (g.gcs_pid_mask & 64) {
     320           0 :         pid_info = &g2.pos_control.get_vel_pid().get_pid_info_x();
     321           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_VEL_NORTH,
     322           0 :                                     pid_info->target,
     323           0 :                                     pid_info->actual,
     324           0 :                                     pid_info->FF,
     325           0 :                                     pid_info->P,
     326           0 :                                     pid_info->I,
     327           0 :                                     pid_info->D,
     328           0 :                                     pid_info->slew_rate,
     329           0 :                                     pid_info->Dmod);
     330           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     331           0 :             return;
     332             :         }
     333             :     }
     334             : 
     335             :     // Position Controller Velocity East PID
     336      124899 :     if (g.gcs_pid_mask & 128) {
     337           0 :         pid_info = &g2.pos_control.get_vel_pid().get_pid_info_y();
     338           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_VEL_EAST,
     339           0 :                                     pid_info->target,
     340           0 :                                     pid_info->actual,
     341           0 :                                     pid_info->FF,
     342           0 :                                     pid_info->P,
     343           0 :                                     pid_info->I,
     344           0 :                                     pid_info->D,
     345           0 :                                     pid_info->slew_rate,
     346           0 :                                     pid_info->Dmod);
     347           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     348           0 :             return;
     349             :         }
     350             :     }
     351             : }
     352             : 
     353      124899 : void Rover::send_wheel_encoder_distance(const mavlink_channel_t chan)
     354             : {
     355             :     // send wheel encoder data using wheel_distance message
     356      124899 :     if (g2.wheel_encoder.num_sensors() > 0) {
     357         815 :         double distances[MAVLINK_MSG_WHEEL_DISTANCE_FIELD_DISTANCE_LEN] {};
     358        1886 :         for (uint8_t i = 0; i < g2.wheel_encoder.num_sensors(); i++) {
     359        1071 :             distances[i] = wheel_encoder_last_distance_m[i];
     360             :         }
     361         815 :         mavlink_msg_wheel_distance_send(chan, AP_HAL::micros64(), g2.wheel_encoder.num_sensors(), distances);
     362             :     }
     363      124899 : }
     364             : 
     365      258845 : bool GCS_Rover::vehicle_initialised() const
     366             : {
     367      258845 :     return rover.control_mode != &rover.mode_initializing;
     368             : }
     369             : 
     370             : // try to send a message, return false if it won't fit in the serial tx buffer
     371     7955264 : bool GCS_MAVLINK_Rover::try_send_message(enum ap_message id)
     372             : {
     373     7955264 :     switch (id) {
     374             : 
     375      124899 :     case MSG_WHEEL_DISTANCE:
     376      124899 :         CHECK_PAYLOAD_SIZE(WHEEL_DISTANCE);
     377      124899 :         rover.send_wheel_encoder_distance(chan);
     378      124899 :         break;
     379             : 
     380      124899 :     case MSG_WIND:
     381      124899 :         CHECK_PAYLOAD_SIZE(WIND);
     382      124899 :         rover.g2.windvane.send_wind(chan);
     383      124899 :         break;
     384             : 
     385             : #if AP_OADATABASE_ENABLED
     386      124162 :     case MSG_ADSB_VEHICLE: {
     387      124162 :         AP_OADatabase *oadb = AP::oadatabase();
     388      124162 :         if (oadb != nullptr) {
     389      124162 :             CHECK_PAYLOAD_SIZE(ADSB_VEHICLE);
     390      124162 :             uint16_t interval_ms = 0;
     391      124162 :             if (get_ap_message_interval(id, interval_ms)) {
     392      124162 :                 oadb->send_adsb_vehicle(chan, interval_ms);
     393             :             }
     394             :         }
     395      124162 :         break;
     396             :     }
     397             : #endif
     398             : 
     399             : #if AP_RANGEFINDER_ENABLED
     400      124943 :     case MSG_WATER_DEPTH:
     401      124943 :         CHECK_PAYLOAD_SIZE(WATER_DEPTH);
     402      124943 :         send_water_depth();
     403      124943 :         break;
     404             : #endif  // AP_RANGEFINDER_ENABLED
     405             : 
     406     7456361 :     default:
     407     7456361 :         return GCS_MAVLINK::try_send_message(id);
     408             :     }
     409      498903 :     return true;
     410             : }
     411           5 : bool GCS_MAVLINK_Rover::handle_guided_request(AP_Mission::Mission_Command &cmd)
     412             : {
     413           5 :     if (!rover.control_mode->in_guided_mode()) {
     414             :         // only accept position updates when in GUIDED mode
     415           0 :         return false;
     416             :     }
     417             : 
     418             :     // make any new wp uploaded instant (in case we are already in Guided mode)
     419           5 :     return rover.mode_guided.set_desired_location(cmd.content.location);
     420             : }
     421             : 
     422           1 : MAV_RESULT GCS_MAVLINK_Rover::_handle_command_preflight_calibration(const mavlink_command_int_t &packet, const mavlink_message_t &msg)
     423             : {
     424           1 :     switch (packet.y) { 
     425           0 :     case 1:
     426           0 :         if (rover.g2.windvane.start_direction_calibration()) {
     427           0 :             return MAV_RESULT_ACCEPTED;
     428             :         } else {
     429           0 :             return MAV_RESULT_FAILED;
     430             :         }
     431             : 
     432           0 :     case 2:
     433           0 :         if (rover.g2.windvane.start_speed_calibration()) {
     434           0 :             return MAV_RESULT_ACCEPTED;
     435             :         } else {
     436           0 :             return MAV_RESULT_FAILED;
     437             :         }
     438             : 
     439           1 :     default:
     440           1 :         break;
     441             : 
     442             :     }
     443             : 
     444           1 :     return GCS_MAVLINK::_handle_command_preflight_calibration(packet, msg);
     445             : }
     446             : 
     447       25177 : MAV_RESULT GCS_MAVLINK_Rover::handle_command_int_packet(const mavlink_command_int_t &packet, const mavlink_message_t &msg)
     448             : {
     449       25177 :     switch (packet.command) {
     450             : 
     451           8 :     case MAV_CMD_DO_CHANGE_SPEED:
     452             :         // param1 : type
     453             :         // param2 : new speed in m/s
     454           8 :         switch (SPEED_TYPE(packet.param1)) {
     455           0 :             case SPEED_TYPE_CLIMB_SPEED:
     456             :             case SPEED_TYPE_DESCENT_SPEED:
     457             :             case SPEED_TYPE_ENUM_END:
     458           0 :                 return MAV_RESULT_DENIED;
     459             : 
     460           8 :             case SPEED_TYPE_AIRSPEED: // Airspeed is treated as ground speed for GCS compatibility
     461             :             case SPEED_TYPE_GROUNDSPEED:
     462           8 :                 break;
     463             :         }
     464           8 :         if (!rover.control_mode->set_desired_speed(packet.param2)) {
     465           0 :             return MAV_RESULT_FAILED;
     466             :         }
     467           8 :         return MAV_RESULT_ACCEPTED;
     468             : 
     469           7 :     case MAV_CMD_DO_REPOSITION:
     470           7 :         return handle_command_int_do_reposition(packet);
     471             : 
     472           6 :     case MAV_CMD_DO_SET_REVERSE:
     473             :         // param1 : Direction (0=Forward, 1=Reverse)
     474           6 :         rover.control_mode->set_reversed(is_equal(packet.param1,1.0f));
     475           6 :         return MAV_RESULT_ACCEPTED;
     476             : 
     477           2 :     case MAV_CMD_NAV_RETURN_TO_LAUNCH:
     478           2 :         if (rover.set_mode(rover.mode_rtl, ModeReason::GCS_COMMAND)) {
     479           2 :             return MAV_RESULT_ACCEPTED;
     480             :         }
     481           0 :         return MAV_RESULT_FAILED;
     482             : 
     483           1 :     case MAV_CMD_DO_MOTOR_TEST:
     484             :         // param1 : motor sequence number (a number from 1 to max number of motors on the vehicle)
     485             :         // param2 : throttle type (0=throttle percentage, 1=PWM, 2=pilot throttle channel pass-through. See MOTOR_TEST_THROTTLE_TYPE enum)
     486             :         // param3 : throttle (range depends upon param2)
     487             :         // param4 : timeout (in seconds)
     488           1 :         return rover.mavlink_motor_test_start(*this,
     489           1 :                                               (AP_MotorsUGV::motor_test_order)packet.param1,
     490           1 :                                               static_cast<uint8_t>(packet.param2),
     491           1 :                                               static_cast<int16_t>(packet.param3),
     492           1 :                                               packet.param4);
     493             : 
     494           4 :     case MAV_CMD_MISSION_START:
     495           4 :         if (!is_zero(packet.param1) || !is_zero(packet.param2)) {
     496             :             // first-item/last item not supported
     497           0 :             return MAV_RESULT_DENIED;
     498             :         }
     499           4 :         if (rover.set_mode(rover.mode_auto, ModeReason::GCS_COMMAND)) {
     500           4 :             return MAV_RESULT_ACCEPTED;
     501             :         }
     502           0 :         return MAV_RESULT_FAILED;
     503             : 
     504       25149 :     default:
     505       25149 :         return GCS_MAVLINK::handle_command_int_packet(packet, msg);
     506             :     }
     507             : }
     508             : 
     509           7 : MAV_RESULT GCS_MAVLINK_Rover::handle_command_int_do_reposition(const mavlink_command_int_t &packet)
     510             : {
     511           7 :     const bool change_modes = ((int32_t)packet.param2 & MAV_DO_REPOSITION_FLAGS_CHANGE_MODE) == MAV_DO_REPOSITION_FLAGS_CHANGE_MODE;
     512           7 :     if (!rover.control_mode->in_guided_mode() && !change_modes) {
     513           0 :         return MAV_RESULT_DENIED;
     514             :     }
     515             : 
     516           7 :     if (packet.x == 0 && packet.y == 0) {
     517           0 :         return MAV_RESULT_DENIED;
     518             :     }
     519             : 
     520           7 :     Location requested_location {};
     521           7 :     if (!location_from_command_t(packet, requested_location)) {
     522           0 :         return MAV_RESULT_DENIED;
     523             :     }
     524             : 
     525           7 :     if (!rover.control_mode->in_guided_mode()) {
     526           0 :         if (!rover.set_mode(Mode::Number::GUIDED, ModeReason::GCS_COMMAND)) {
     527           0 :             return MAV_RESULT_FAILED;
     528             :         }
     529             :     }
     530             : 
     531           7 :     if (is_positive(packet.param1)) {
     532           0 :         if (!rover.control_mode->set_desired_speed(packet.param1)) {
     533           0 :             return MAV_RESULT_FAILED;
     534             :         }
     535             :     }
     536             : 
     537             :     // set the destination
     538           7 :     if (!rover.mode_guided.set_desired_location(requested_location)) {
     539           1 :         return MAV_RESULT_FAILED;
     540             :     }
     541             : 
     542           6 :     return MAV_RESULT_ACCEPTED;
     543             : }
     544             : 
     545      280649 : void GCS_MAVLINK_Rover::handle_message(const mavlink_message_t &msg)
     546             : {
     547      280649 :     switch (msg.msgid) {
     548             : 
     549         441 :     case MAVLINK_MSG_ID_SET_ATTITUDE_TARGET:
     550         441 :         handle_set_attitude_target(msg);
     551         441 :         break;
     552             : 
     553         309 :     case MAVLINK_MSG_ID_SET_POSITION_TARGET_LOCAL_NED:
     554         309 :         handle_set_position_target_local_ned(msg);
     555         309 :         break;
     556             : 
     557        1358 :     case MAVLINK_MSG_ID_SET_POSITION_TARGET_GLOBAL_INT:
     558        1358 :         handle_set_position_target_global_int(msg);
     559        1358 :         break;
     560             : 
     561      278541 :     default:
     562      278541 :         GCS_MAVLINK::handle_message(msg);
     563      278303 :         break;
     564             :     }
     565      280411 : }
     566             : 
     567          45 : void GCS_MAVLINK_Rover::handle_manual_control_axes(const mavlink_manual_control_t &packet, const uint32_t tnow)
     568             : {
     569          45 :     manual_override(rover.channel_steer, packet.y, 1000, 2000, tnow);
     570          45 :     manual_override(rover.channel_throttle, packet.z, 1000, 2000, tnow);
     571          45 : }
     572             : 
     573         441 : void GCS_MAVLINK_Rover::handle_set_attitude_target(const mavlink_message_t &msg)
     574             : {
     575             :     // decode packet
     576             :     mavlink_set_attitude_target_t packet;
     577         441 :     mavlink_msg_set_attitude_target_decode(&msg, &packet);
     578             : 
     579             :     // exit if vehicle is not in Guided mode
     580         441 :     if (!rover.control_mode->in_guided_mode()) {
     581           0 :         return;
     582             :     }
     583             : 
     584             :     // ensure type_mask specifies to use thrust
     585         441 :     if ((packet.type_mask & MAVLINK_SET_ATT_TYPE_MASK_THROTTLE_IGNORE) != 0) {
     586           0 :         return;
     587             :     }
     588             : 
     589             :     // convert thrust to ground speed
     590         441 :     packet.thrust = constrain_float(packet.thrust, -1.0f, 1.0f);
     591         441 :     const float target_speed = rover.control_mode->get_speed_default() * packet.thrust;
     592             : 
     593             :     // if the body_yaw_rate field is ignored, convert quaternion to heading
     594         441 :     if ((packet.type_mask & MAVLINK_SET_ATT_TYPE_MASK_YAW_RATE_IGNORE) != 0) {
     595             :         // convert quaternion to heading
     596         390 :         float target_heading_cd = degrees(Quaternion(packet.q[0], packet.q[1], packet.q[2], packet.q[3]).get_euler_yaw()) * 100.0f;
     597         390 :         rover.mode_guided.set_desired_heading_and_speed(target_heading_cd, target_speed);
     598             :     } else {
     599             :         // use body_yaw_rate field
     600          51 :         rover.mode_guided.set_desired_turn_rate_and_speed((RAD_TO_DEG * packet.body_yaw_rate) * 100.0f, target_speed);
     601             :     }
     602             : }
     603             : 
     604             : // if we receive a message where the user has not masked out
     605             : // acceleration from the input packet we send a curt message
     606             : // informing them:
     607           0 : void GCS_MAVLINK_Rover::send_acc_ignore_must_be_set_message(const char *msgname)
     608             : {
     609           0 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Ignoring %s; set ACC_IGNORE in mask", msgname);
     610           0 : }
     611             : 
     612         309 : void GCS_MAVLINK_Rover::handle_set_position_target_local_ned(const mavlink_message_t &msg)
     613             : {
     614             :     // decode packet
     615             :     mavlink_set_position_target_local_ned_t packet;
     616         309 :     mavlink_msg_set_position_target_local_ned_decode(&msg, &packet);
     617             : 
     618             :     // exit if vehicle is not in Guided mode
     619         309 :     if (!rover.control_mode->in_guided_mode()) {
     620         309 :         return;
     621             :     }
     622             : 
     623             :     // need ekf origin
     624         309 :     Location ekf_origin;
     625         309 :     if (!rover.ahrs.get_origin(ekf_origin)) {
     626           0 :         return;
     627             :     }
     628             : 
     629             :     // check for supported coordinate frames
     630         309 :     switch (packet.coordinate_frame) {
     631         309 :     case MAV_FRAME_LOCAL_NED:
     632             :     case MAV_FRAME_LOCAL_OFFSET_NED:
     633             :     case MAV_FRAME_BODY_NED:
     634             :     case MAV_FRAME_BODY_OFFSET_NED:
     635         309 :         break;
     636             : 
     637           0 :     default:
     638           0 :         return;
     639             :     }
     640             : 
     641         309 :     bool pos_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_POS_IGNORE;
     642         309 :     bool vel_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_VEL_IGNORE;
     643         309 :     bool acc_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_ACC_IGNORE;
     644         309 :     bool yaw_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_IGNORE;
     645         309 :     bool yaw_rate_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_RATE_IGNORE;
     646             : 
     647             :     // prepare target position
     648         309 :     Location target_loc = rover.current_loc;
     649         309 :     if (!pos_ignore) {
     650         309 :         switch (packet.coordinate_frame) {
     651           0 :         case MAV_FRAME_BODY_NED:
     652             :         case MAV_FRAME_BODY_OFFSET_NED: {
     653             :             // rotate from body-frame to NE frame
     654           0 :             const float ne_x = packet.x * rover.ahrs.cos_yaw() - packet.y * rover.ahrs.sin_yaw();
     655           0 :             const float ne_y = packet.x * rover.ahrs.sin_yaw() + packet.y * rover.ahrs.cos_yaw();
     656             :             // add offset to current location
     657           0 :             target_loc.offset(ne_x, ne_y);
     658             :         }
     659           0 :             break;
     660             : 
     661           0 :         case MAV_FRAME_LOCAL_OFFSET_NED:
     662             :             // add offset to current location
     663           0 :             target_loc.offset(packet.x, packet.y);
     664           0 :             break;
     665             : 
     666         309 :         case MAV_FRAME_LOCAL_NED:
     667             :         default:
     668             :             // MAV_FRAME_LOCAL_NED is interpreted as an offset from EKF origin
     669         309 :             target_loc = ekf_origin;
     670         309 :             target_loc.offset(packet.x, packet.y);
     671         309 :             break;
     672             :         }
     673             :     }
     674             : 
     675         309 :     float target_speed = 0.0f;
     676         309 :     float target_yaw_cd = 0.0f;
     677             : 
     678             :     // consume velocity and convert to target speed and heading
     679         309 :     if (!vel_ignore) {
     680           0 :         const float speed_max = rover.control_mode->get_speed_default();
     681             :         // convert vector length into a speed
     682           0 :         target_speed = constrain_float(safe_sqrt(sq(packet.vx) + sq(packet.vy)), -speed_max, speed_max);
     683             :         // convert vector direction to target yaw
     684           0 :         target_yaw_cd = degrees(atan2f(packet.vy, packet.vx)) * 100.0f;
     685             : 
     686             :         // rotate target yaw if provided in body-frame
     687           0 :         if (packet.coordinate_frame == MAV_FRAME_BODY_NED || packet.coordinate_frame == MAV_FRAME_BODY_OFFSET_NED) {
     688           0 :             target_yaw_cd = wrap_180_cd(target_yaw_cd + rover.ahrs.yaw_sensor);
     689             :         }
     690             :     }
     691             : 
     692             :     // consume yaw heading
     693         309 :     if (!yaw_ignore) {
     694           0 :         target_yaw_cd = degrees(packet.yaw) * 100.0f;
     695             :         // rotate target yaw if provided in body-frame
     696           0 :         if (packet.coordinate_frame == MAV_FRAME_BODY_NED || packet.coordinate_frame == MAV_FRAME_BODY_OFFSET_NED) {
     697           0 :             target_yaw_cd = wrap_180_cd(target_yaw_cd + rover.ahrs.yaw_sensor);
     698             :         }
     699             :     }
     700             :     // consume yaw rate
     701         309 :     float target_turn_rate_cds = 0.0f;
     702         309 :     if (!yaw_rate_ignore) {
     703           0 :         target_turn_rate_cds = degrees(packet.yaw_rate) * 100.0f;
     704             :     }
     705             : 
     706             :     // handling case when both velocity and either yaw or yaw-rate are provided
     707             :     // by default, we consider that the rover will drive forward
     708         309 :     float speed_dir = 1.0f;
     709         309 :     if (!vel_ignore && (!yaw_ignore || !yaw_rate_ignore)) {
     710             :         // Note: we are using the x-axis velocity to determine direction even though
     711             :         // the frame may have been provided in MAV_FRAME_LOCAL_OFFSET_NED or MAV_FRAME_LOCAL_NED
     712           0 :         if (is_negative(packet.vx)) {
     713           0 :             speed_dir = -1.0f;
     714             :         }
     715             :     }
     716             : 
     717         309 :     if (!acc_ignore) {
     718             :         // ignore any command where acceleration is not ignored
     719           0 :         send_acc_ignore_must_be_set_message("SET_POSITION_TARGET_LOCAL_NED");
     720           0 :         return;
     721             :     }
     722             : 
     723             :     // set guided mode targets
     724         309 :     if (!pos_ignore) {
     725             :         // consume position target
     726         309 :         if (!rover.mode_guided.set_desired_location(target_loc)) {
     727             :             // GCS will need to monitor desired location to
     728             :             // see if they are having an effect.
     729             :         }
     730         309 :         return;
     731             :     }
     732             : 
     733           0 :     if (!vel_ignore && yaw_ignore && yaw_rate_ignore) {
     734             :         // consume velocity
     735           0 :         rover.mode_guided.set_desired_heading_and_speed(target_yaw_cd, speed_dir * target_speed);
     736           0 :     } else if (!vel_ignore && yaw_ignore && !yaw_rate_ignore) {
     737             :         // consume velocity and turn rate
     738           0 :         rover.mode_guided.set_desired_turn_rate_and_speed(target_turn_rate_cds, speed_dir * target_speed);
     739           0 :     } else if (!vel_ignore && !yaw_ignore && yaw_rate_ignore) {
     740             :         // consume velocity and heading
     741           0 :         rover.mode_guided.set_desired_heading_and_speed(target_yaw_cd, speed_dir * target_speed);
     742           0 :     } else if (vel_ignore && !yaw_ignore && yaw_rate_ignore) {
     743             :         // consume just target heading (probably only skid steering vehicles can do this)
     744           0 :         rover.mode_guided.set_desired_heading_and_speed(target_yaw_cd, 0.0f);
     745           0 :     } else if (vel_ignore && yaw_ignore && !yaw_rate_ignore) {
     746             :         // consume just turn rate (probably only skid steering vehicles can do this)
     747           0 :         rover.mode_guided.set_desired_turn_rate_and_speed(target_turn_rate_cds, 0.0f);
     748             :     }
     749             : }
     750             : 
     751        1358 : void GCS_MAVLINK_Rover::handle_set_position_target_global_int(const mavlink_message_t &msg)
     752             : {
     753             :     // decode packet
     754             :     mavlink_set_position_target_global_int_t packet;
     755        1358 :     mavlink_msg_set_position_target_global_int_decode(&msg, &packet);
     756             : 
     757             :     // exit if vehicle is not in Guided mode
     758        1358 :     if (!rover.control_mode->in_guided_mode()) {
     759          46 :         return;
     760             :     }
     761             :     // check for supported coordinate frames
     762        1358 :     switch (packet.coordinate_frame) {
     763        1358 :     case MAV_FRAME_GLOBAL:
     764             :     case MAV_FRAME_GLOBAL_INT:
     765             :     case MAV_FRAME_GLOBAL_RELATIVE_ALT:
     766             :     case MAV_FRAME_GLOBAL_RELATIVE_ALT_INT:
     767             :     case MAV_FRAME_GLOBAL_TERRAIN_ALT:
     768             :     case MAV_FRAME_GLOBAL_TERRAIN_ALT_INT:
     769        1358 :         break;
     770             : 
     771           0 :     default:
     772           0 :         return;
     773             :     }
     774             :     
     775        1358 :     bool pos_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_POS_IGNORE;
     776        1358 :     bool vel_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_VEL_IGNORE;
     777        1358 :     bool acc_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_ACC_IGNORE;
     778        1358 :     bool yaw_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_IGNORE;
     779        1358 :     bool yaw_rate_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_RATE_IGNORE;
     780             : 
     781             :     // prepare target position
     782        1358 :     Location target_loc = rover.current_loc;
     783        1358 :     if (!pos_ignore) {
     784             :         // sanity check location
     785          46 :         if (!check_latlng(packet.lat_int, packet.lon_int)) {
     786             :             // result = MAV_RESULT_FAILED;
     787           0 :             return;
     788             :         }
     789          46 :         target_loc.lat = packet.lat_int;
     790          46 :         target_loc.lng = packet.lon_int;
     791             :     }
     792             : 
     793        1358 :     float target_speed = 0.0f;
     794        1358 :     float target_yaw_cd = 0.0f;
     795             : 
     796             :     // consume velocity and convert to target speed and heading
     797        1358 :     if (!vel_ignore) {
     798        1312 :         const float speed_max = rover.control_mode->get_speed_default();
     799             :         // convert vector length into a speed
     800        1312 :         target_speed = constrain_float(safe_sqrt(sq(packet.vx) + sq(packet.vy)), -speed_max, speed_max);
     801             :         // convert vector direction to target yaw
     802        1312 :         target_yaw_cd = degrees(atan2f(packet.vy, packet.vx)) * 100.0f;
     803             :     }
     804             : 
     805             :     // consume yaw heading
     806        1358 :     if (!yaw_ignore) {
     807           0 :         target_yaw_cd = degrees(packet.yaw) * 100.0f;
     808             :     }
     809             : 
     810             :     // consume yaw rate
     811        1358 :     float target_turn_rate_cds = 0.0f;
     812        1358 :     if (!yaw_rate_ignore) {
     813           0 :         target_turn_rate_cds = degrees(packet.yaw_rate) * 100.0f;
     814             :     }
     815             : 
     816             :     // handling case when both velocity and either yaw or yaw-rate are provided
     817             :     // by default, we consider that the rover will drive forward
     818        1358 :     float speed_dir = 1.0f;
     819        1358 :     if (!vel_ignore && (!yaw_ignore || !yaw_rate_ignore)) {
     820             :         // Note: we are using the x-axis velocity to determine direction even though
     821             :         // the frame is provided in MAV_FRAME_GLOBAL_xxx
     822           0 :         if (is_negative(packet.vx)) {
     823           0 :             speed_dir = -1.0f;
     824             :         }
     825             :     }
     826             : 
     827        1358 :     if (!acc_ignore) {
     828             :         // ignore any command where acceleration is not ignored
     829           0 :         send_acc_ignore_must_be_set_message("SET_POSITION_TARGET_GLOBAL_INT");
     830           0 :         return;
     831             :     }
     832             : 
     833             :     // set guided mode targets
     834        1358 :     if (!pos_ignore) {
     835             :         // consume position target
     836          46 :         if (!rover.mode_guided.set_desired_location(target_loc)) {
     837             :             // GCS will just need to look at desired location
     838             :             // outputs to see if it having an effect.
     839             :         }
     840          46 :         return;
     841             :     }
     842             : 
     843        1312 :     if (!vel_ignore && yaw_ignore && yaw_rate_ignore) {
     844             :         // consume velocity
     845        1312 :         rover.mode_guided.set_desired_heading_and_speed(target_yaw_cd, speed_dir * target_speed);
     846           0 :     } else if (!vel_ignore && yaw_ignore && !yaw_rate_ignore) {
     847             :         // consume velocity and turn rate
     848           0 :         rover.mode_guided.set_desired_turn_rate_and_speed(target_turn_rate_cds, speed_dir * target_speed);
     849           0 :     } else if (!vel_ignore && !yaw_ignore && yaw_rate_ignore) {
     850             :         // consume velocity
     851           0 :         rover.mode_guided.set_desired_heading_and_speed(target_yaw_cd, speed_dir * target_speed);
     852           0 :     } else if (vel_ignore && !yaw_ignore && yaw_rate_ignore) {
     853             :         // consume just target heading (probably only skid steering vehicles can do this)
     854           0 :         rover.mode_guided.set_desired_heading_and_speed(target_yaw_cd, 0.0f);
     855           0 :     } else if (vel_ignore && yaw_ignore && !yaw_rate_ignore) {
     856             :         // consume just turn rate(probably only skid steering vehicles can do this)
     857           0 :         rover.mode_guided.set_desired_turn_rate_and_speed(target_turn_rate_cds, 0.0f);
     858             :     }
     859             : }
     860             : 
     861             : /*
     862             :   handle a LANDING_TARGET command. The timestamp has been jitter corrected
     863             : */
     864           0 : void GCS_MAVLINK_Rover::handle_landing_target(const mavlink_landing_target_t &packet, uint32_t timestamp_ms)
     865             : {
     866             : #if AC_PRECLAND_ENABLED
     867           0 :     rover.precland.handle_msg(packet, timestamp_ms);
     868             : #endif
     869           0 : }
     870             : 
     871           9 : uint64_t GCS_MAVLINK_Rover::capabilities() const
     872             : {
     873             :     return (MAV_PROTOCOL_CAPABILITY_MISSION_FLOAT |
     874             :             MAV_PROTOCOL_CAPABILITY_MISSION_INT |
     875             :             MAV_PROTOCOL_CAPABILITY_COMMAND_INT |
     876             :             MAV_PROTOCOL_CAPABILITY_SET_POSITION_TARGET_LOCAL_NED |
     877             :             MAV_PROTOCOL_CAPABILITY_SET_POSITION_TARGET_GLOBAL_INT |
     878             :             MAV_PROTOCOL_CAPABILITY_SET_ATTITUDE_TARGET |
     879           9 :             GCS_MAVLINK::capabilities());
     880             : }
     881             : 
     882             : #if HAL_HIGH_LATENCY2_ENABLED
     883         727 : uint8_t GCS_MAVLINK_Rover::high_latency_tgt_heading() const
     884             : {
     885         727 :     const Mode *control_mode = rover.control_mode;
     886         727 :     if (rover.control_mode->is_autopilot_mode()) {
     887             :         // need to convert -180->180 to 0->360/2
     888           0 :         return wrap_360(control_mode->wp_bearing()) / 2;
     889             :     }
     890         727 :     return 0;
     891             : }
     892             :     
     893         727 : uint16_t GCS_MAVLINK_Rover::high_latency_tgt_dist_dam() const
     894             : {
     895         727 :     const Mode *control_mode = rover.control_mode;
     896         727 :     if (rover.control_mode->is_autopilot_mode()) {
     897           0 :         return MIN(static_cast<uint16_t>(control_mode->get_distance_to_destination() * 0.1), UINT16_MAX);
     898             :     }
     899         727 :     return 0;
     900             : }
     901             : 
     902         727 : uint8_t GCS_MAVLINK_Rover::high_latency_tgt_airspeed() const
     903             : {
     904         727 :     const Mode *control_mode = rover.control_mode;
     905         727 :     if (rover.control_mode->is_autopilot_mode()) {
     906             :         // return units are m/s*5
     907           0 :         return MIN((vfr_hud_airspeed() - control_mode->speed_error()) * 5, UINT8_MAX);
     908             :     }
     909         727 :     return 0;
     910             : }
     911             : 
     912         727 : uint8_t GCS_MAVLINK_Rover::high_latency_wind_speed() const
     913             : {
     914         727 :     if (rover.g2.windvane.enabled()) {
     915             :         // return units are m/s*5
     916           0 :         return MIN(rover.g2.windvane.get_true_wind_speed() * 5, UINT8_MAX);
     917             :     }
     918         727 :     return 0;
     919             : }
     920             : 
     921         727 : uint8_t GCS_MAVLINK_Rover::high_latency_wind_direction() const
     922             : {
     923         727 :     if (rover.g2.windvane.enabled()) {
     924             :         // return units are deg/2
     925           0 :         return wrap_360(degrees(rover.g2.windvane.get_true_wind_direction_rad())) / 2;
     926             :     }
     927         727 :     return 0;
     928             : }
     929             : #endif // HAL_HIGH_LATENCY2_ENABLED
     930             : 
     931             : // Send the mode with the given index (not mode number!) return the total number of modes
     932             : // Index starts at 1
     933           0 : uint8_t GCS_MAVLINK_Rover::send_available_mode(uint8_t index) const
     934             : {
     935           0 :     const Mode* modes[] {
     936             :         &rover.mode_manual,
     937             :         &rover.mode_acro,
     938             :         &rover.mode_steering,
     939             :         &rover.mode_hold,
     940             :         &rover.mode_loiter,
     941             : #if MODE_FOLLOW_ENABLED
     942             :         &rover.mode_follow,
     943             : #endif
     944             :         &rover.mode_simple,
     945             :         &rover.g2.mode_circle,
     946             :         &rover.mode_auto,
     947             :         &rover.mode_rtl,
     948             :         &rover.mode_smartrtl,
     949             :         &rover.mode_guided,
     950             :         &rover.mode_initializing,
     951             : #if MODE_DOCK_ENABLED
     952           0 :         (Mode *)rover.g2.mode_dock_ptr,
     953             : #endif
     954           0 :     };
     955             : 
     956           0 :     const uint8_t mode_count = ARRAY_SIZE(modes);
     957             : 
     958             :     // Convert to zero indexed
     959           0 :     const uint8_t index_zero = index - 1;
     960           0 :     if (index_zero >= mode_count) {
     961             :         // Mode does not exist!?
     962           0 :         return mode_count;
     963             :     }
     964             : 
     965             :     // Ask the mode for its name and number
     966           0 :     const char* name = modes[index_zero]->name();
     967           0 :     const Mode::Number mode_number = modes[index_zero]->mode_number();
     968             : 
     969             :     // the check here must be the same as the one in `get_available_mode_enabled_mask`
     970           0 :     const bool user_selectable = modes[index_zero]->enabled() && rover.gcs_mode_enabled(mode_number);
     971             : 
     972           0 :     mavlink_msg_available_modes_send(
     973           0 :         chan,
     974             :         mode_count,
     975             :         index,
     976             :         MAV_STANDARD_MODE::MAV_STANDARD_MODE_NON_STANDARD,
     977             :         (uint8_t)mode_number,
     978             :         // MAV_MODE_PROPERTY bitmask,
     979             :         user_selectable ? 0 : MAV_MODE_PROPERTY_NOT_USER_SELECTABLE,
     980             :         name
     981             :     );
     982             : 
     983           0 :     return mode_count;
     984             : }

Generated by: LCOV version 1.14