LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduSub - GCS_MAVLink_Sub.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 264 461 57.3 %
Date: 2026-09-09 11:02:08 Functions: 26 38 68.4 %

          Line data    Source code
       1             : #include "Sub.h"
       2             : 
       3             : #include "GCS_MAVLink_Sub.h"
       4             : #include <AP_RPM/AP_RPM_config.h>
       5             : #include <AP_RangeFinder/AP_RangeFinder.h>
       6             : #include <AP_RangeFinder/AP_RangeFinder_Backend.h>
       7             : 
       8        6094 : MAV_TYPE GCS_Sub::frame_type() const
       9             : {
      10        6094 :     return MAV_TYPE_SUBMARINE;
      11             : }
      12             : 
      13        6094 : uint8_t GCS_MAVLINK_Sub::base_mode() const
      14             : {
      15        6094 :     uint8_t _base_mode = MAV_MODE_FLAG_STABILIZE_ENABLED;
      16             : 
      17             :     // work out the base_mode. This value is not very useful
      18             :     // for APM, but we calculate it as best we can so a generic
      19             :     // MAVLink enabled ground station can work out something about
      20             :     // what the MAV is up to. The actual bit values are highly
      21             :     // ambiguous for most of the APM flight modes. In practice, you
      22             :     // only get useful information from the custom_mode, which maps to
      23             :     // the APM flight mode and has a well defined meaning in the
      24             :     // ArduPlane documentation
      25        6094 :     switch (sub.control_mode) {
      26        2035 :     case Mode::Number::AUTO:
      27             :     case Mode::Number::GUIDED:
      28             :     case Mode::Number::CIRCLE:
      29             :     case Mode::Number::POSHOLD:
      30        2035 :         _base_mode |= MAV_MODE_FLAG_GUIDED_ENABLED;
      31             :         // note that MAV_MODE_FLAG_AUTO_ENABLED does not match what
      32             :         // APM does in any mode, as that is defined as "system finds its own goal
      33             :         // positions", which APM does not currently do
      34        2035 :         break;
      35        4059 :     default:
      36        4059 :         break;
      37             :     }
      38             : 
      39             :     // all modes except INITIALISING have some form of manual
      40             :     // override if stick mixing is enabled
      41        6094 :     _base_mode |= MAV_MODE_FLAG_MANUAL_INPUT_ENABLED;
      42             : 
      43        6094 :     if (sub.motors.armed()) {
      44        3137 :         _base_mode |= MAV_MODE_FLAG_SAFETY_ARMED;
      45             :     }
      46             : 
      47             :     // indicate we have set a custom mode
      48        6094 :     _base_mode |= MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
      49             : 
      50        6094 :     return _base_mode;
      51             : }
      52             : 
      53        6094 : uint32_t GCS_Sub::custom_mode() const
      54             : {
      55        6094 :     return (uint32_t)sub.control_mode;
      56             : }
      57             : 
      58        6094 : MAV_STATE GCS_MAVLINK_Sub::vehicle_system_status() const
      59             : {
      60             :     // set system as critical if any failsafe have triggered
      61        6094 :     if (sub.any_failsafe_triggered())  {
      62         468 :         return MAV_STATE_CRITICAL;
      63             :     }
      64             : 
      65        5626 :     if (sub.motors.armed()) {
      66        3063 :         return MAV_STATE_ACTIVE;
      67             :     }
      68        2563 :     if (!sub.ap.initialised) {
      69           0 :         return MAV_STATE_BOOT;
      70             :     }
      71             : 
      72        2563 :     return MAV_STATE_STANDBY;
      73             : }
      74             : 
      75          66 : void GCS_MAVLINK_Sub::send_banner()
      76             : {
      77          66 :     GCS_MAVLINK::send_banner();
      78          66 :     send_text(MAV_SEVERITY_INFO, "Frame: %s", sub.motors.get_frame_string());
      79          66 : }
      80             : 
      81       52070 : void GCS_MAVLINK_Sub::send_nav_controller_output() const
      82             : {
      83       52070 :     const Vector3f &targets = sub.attitude_control.get_att_target_euler_cd();
      84      156210 :     mavlink_msg_nav_controller_output_send(
      85       52070 :         chan,
      86       52070 :         targets.x * 1.0e-2f,
      87       52070 :         targets.y * 1.0e-2f,
      88       52070 :         targets.z * 1.0e-2f,
      89       52070 :         sub.wp_nav.get_wp_bearing_to_destination_cd() * 1.0e-2f,
      90       52070 :         MIN(sub.wp_nav.get_wp_distance_to_destination_cm() * 1.0e-2f, UINT16_MAX),
      91       52070 :         sub.pos_control.get_pos_error_U_cm() * 1.0e-2f,
      92             :         0,
      93             :         0);
      94       52070 : }
      95             : 
      96             : // returns a Location to which the vehicle is currently heading, or
      97             : // would head in an autonomous mode
      98       52071 : bool GCS_MAVLINK_Sub::get_target_location(Location &loc) const
      99             : {
     100             :     // in Guided mode, only Guided_WP uses wp_nav; the other submodes keep
     101             :     // their own targets. Guided_WP breaks out to the wp_nav return below.
     102       52071 :     if (sub.flightmode->in_guided_mode()) {
     103       17811 :         switch (sub.guided_mode) {
     104       15946 :         case Guided_WP:
     105       15946 :             break;
     106        1865 :         case Guided_PosVelAccel: {
     107        1865 :             Vector3f pos_neu_cm;
     108        1865 :             if (!sub.mode_guided.get_posvelaccel_target_NEU_cm(pos_neu_cm)) {
     109           0 :                 return false;
     110             :             }
     111        1865 :             loc = Location(pos_neu_cm, Location::AltFrame::ABOVE_ORIGIN);
     112        1865 :             return true;
     113             :         }
     114           0 :         case Guided_Velocity:
     115             :         case Guided_Angle:
     116             :             // no fixed position target to report
     117           0 :             return false;
     118             :         }
     119             :     }
     120             : 
     121             :     // for non-guided modes (Auto, etc.) fall back to wp_nav as before
     122       50206 :     return sub.wp_nav.is_active() && sub.wp_nav.get_wp_destination_loc(loc);
     123             : }
     124             : 
     125       55950 : int16_t GCS_MAVLINK_Sub::vfr_hud_throttle() const
     126             : {
     127       55950 :     return (int16_t)(sub.motors.get_throttle() * 100);
     128             : }
     129             : 
     130       55950 : float GCS_MAVLINK_Sub::vfr_hud_alt() const
     131             : {
     132       55950 :     return sub.get_alt_msl();
     133             : }
     134             : 
     135             : // Work around to get temperature sensor data out
     136       52070 : void GCS_MAVLINK_Sub::send_scaled_pressure3()
     137             : {
     138             : #if AP_TEMPERATURE_SENSOR_ENABLED
     139             :     float temperature;
     140       52070 :     if (!sub.temperature_sensor.get_temperature(temperature)) {
     141             :         // Fall back to original behaviour
     142          72 :         GCS_MAVLINK::send_scaled_pressure3();
     143          72 :         return;
     144             :     }
     145       51998 :     mavlink_msg_scaled_pressure3_send(
     146             :         chan,
     147             :         AP_HAL::millis(),
     148             :         0,
     149             :         0,
     150       51998 :         temperature * 100,
     151             :         0); // TODO: use differential pressure temperature
     152             : #else
     153             :     // Fall back to standard behaviour
     154             :     GCS_MAVLINK::send_scaled_pressure3();
     155             : #endif
     156             : }
     157             : 
     158       52069 : bool GCS_MAVLINK_Sub::send_info()
     159             : {
     160             :     // Just do this all at once, hopefully the hard-wire telemetry requirement means this is ok
     161             :     // Name is char[10]
     162       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     163      104138 :     send_named_float("CamTilt",
     164       52069 :                      1 - (SRV_Channels::get_output_norm(SRV_Channel::k_mount_tilt) / 2.0f + 0.5f));
     165             : 
     166       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     167      104138 :     send_named_float("CamPan",
     168       52069 :                      1 - (SRV_Channels::get_output_norm(SRV_Channel::k_mount_pan) / 2.0f + 0.5f));
     169             : 
     170       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     171       52069 :     send_named_float("TetherTrn",
     172       52069 :                      (float)sub.quarter_turn_count / 4);
     173             : 
     174       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     175      104138 :     send_named_float("Lights1",
     176       52069 :                      SRV_Channels::get_output_norm(SRV_Channel::k_lights1) / 2.0f + 0.5f);
     177             : 
     178       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     179      104138 :     send_named_float("Lights2",
     180       52069 :                      SRV_Channels::get_output_norm(SRV_Channel::k_lights2) / 2.0f + 0.5f);
     181             : 
     182       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     183       52069 :     send_named_float("PilotGain", sub.gain);
     184             : 
     185       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     186       52069 :     send_named_float("InputHold", sub.input_hold_engaged);
     187             : 
     188       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     189       52069 :     send_named_float("RollPitch", sub.roll_pitch_flag);
     190             : 
     191       52069 :     CHECK_PAYLOAD_SIZE(NAMED_VALUE_FLOAT);
     192       52069 :     send_named_float("RFTarget", sub.mode_surftrak.get_rangefinder_target_cm() * 0.01f);
     193             : 
     194       52069 :     return true;
     195             : }
     196             : 
     197             : #if AP_RANGEFINDER_ENABLED
     198          46 : void GCS_MAVLINK_Sub::send_water_depth()
     199             : {
     200          46 :     if (!HAVE_PAYLOAD_SPACE(chan, WATER_DEPTH)) {
     201           0 :         return;
     202             :     }
     203             : 
     204          46 :     RangeFinder *rangefinder = RangeFinder::get_singleton();
     205          46 :     if (rangefinder == nullptr) {
     206           0 :         return;
     207             :     }
     208             : 
     209             :     // depth can only be measured by a downward-facing rangefinder:
     210          46 :     if (!rangefinder->has_orientation(ROTATION_PITCH_270)) {
     211           0 :         return;
     212             :     }
     213             : 
     214             :     // get position
     215          46 :     const AP_AHRS &ahrs = AP::ahrs();
     216          46 :     Location loc;
     217          46 :     IGNORE_RETURN(ahrs.get_location(loc));
     218             : 
     219          46 :     const auto num_sensors = rangefinder->num_sensors();
     220          46 :     for (uint8_t i=0; i<num_sensors; i++) {
     221          46 :         last_WATER_DEPTH_index += 1;
     222          46 :         if (last_WATER_DEPTH_index >= num_sensors) {
     223          46 :             last_WATER_DEPTH_index = 0;
     224             :         }
     225             : 
     226          46 :         const AP_RangeFinder_Backend *s = rangefinder->get_backend(last_WATER_DEPTH_index);
     227          46 :         if (s == nullptr || s->orientation() != ROTATION_PITCH_270 || !s->has_data()) {
     228           0 :             continue;
     229             :         }
     230             : 
     231             :         // get temperature
     232             :         float temp_C;
     233          46 :         if (!s->get_temp(temp_C)) {
     234             :             // TODO: check known water temperature sources (temp sensor, external baro)
     235           0 :             temp_C = 0.0f;
     236             :         }
     237             : 
     238          46 :         const bool sensor_healthy = (s->status() == RangeFinder::Status::Good);
     239             : 
     240          92 :         mavlink_msg_water_depth_send(
     241             :             chan,
     242             :             AP_HAL::millis(),   // time since system boot TODO: take time of measurement
     243          46 :             last_WATER_DEPTH_index, // rangefinder instance
     244             :             sensor_healthy,     // sensor healthy
     245             :             loc.lat,            // latitude of vehicle
     246             :             loc.lng,            // longitude of vehicle
     247          46 :             loc.alt * 0.01f,    // altitude of vehicle (MSL)
     248             :             ahrs.get_roll_rad(),    // roll in radians
     249             :             ahrs.get_pitch_rad(),   // pitch in radians
     250             :             ahrs.get_yaw_rad(),     // yaw in radians
     251             :             s->distance(),    // distance in meters
     252             :             temp_C);            // temperature in degC
     253             : 
     254          46 :         break;  // only send one WATER_DEPTH message per loop
     255             :     }
     256             : }
     257             : #endif  // AP_RANGEFINDER_ENABLED
     258             : 
     259             : /*
     260             :   send PID tuning message
     261             :  */
     262       55949 : void GCS_MAVLINK_Sub::send_pid_tuning()
     263             : {
     264       55949 :     const Parameters &g = sub.g;
     265       55949 :     AP_AHRS &ahrs = AP::ahrs();
     266       55949 :     AC_AttitudeControl_Sub &attitude_control = sub.attitude_control;
     267             : 
     268       55949 :     const Vector3f &gyro = ahrs.get_gyro();
     269       55949 :     if (g.gcs_pid_mask & 1) {
     270           4 :         const AP_PIDInfo &pid_info = attitude_control.get_rate_roll_pid().get_pid_info();
     271           8 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_ROLL,
     272           4 :                                     pid_info.target*0.01f,
     273           4 :                                     degrees(gyro.x),
     274           4 :                                     pid_info.FF*0.01f,
     275           4 :                                     pid_info.P*0.01f,
     276           4 :                                     pid_info.I*0.01f,
     277           4 :                                     pid_info.D*0.01f,
     278           4 :                                     pid_info.slew_rate,
     279           4 :                                     pid_info.Dmod);
     280           4 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     281           0 :             return;
     282             :         }
     283             :     }
     284       55949 :     if (g.gcs_pid_mask & 2) {
     285           0 :         const AP_PIDInfo &pid_info = attitude_control.get_rate_pitch_pid().get_pid_info();
     286           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_PITCH,
     287           0 :                                     pid_info.target*0.01f,
     288           0 :                                     degrees(gyro.y),
     289           0 :                                     pid_info.FF*0.01f,
     290           0 :                                     pid_info.P*0.01f,
     291           0 :                                     pid_info.I*0.01f,
     292           0 :                                     pid_info.D*0.01f,
     293           0 :                                     pid_info.slew_rate,
     294           0 :                                     pid_info.Dmod);
     295           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     296           0 :             return;
     297             :         }
     298             :     }
     299       55949 :     if (g.gcs_pid_mask & 4) {
     300           0 :         const AP_PIDInfo &pid_info = attitude_control.get_rate_yaw_pid().get_pid_info();
     301           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_YAW,
     302           0 :                                     pid_info.target*0.01f,
     303           0 :                                     degrees(gyro.z),
     304           0 :                                     pid_info.FF*0.01f,
     305           0 :                                     pid_info.P*0.01f,
     306           0 :                                     pid_info.I*0.01f,
     307           0 :                                     pid_info.D*0.01f,
     308           0 :                                     pid_info.slew_rate,
     309           0 :                                     pid_info.Dmod);
     310           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     311           0 :             return;
     312             :         }
     313             :     }
     314       55949 :     if (g.gcs_pid_mask & 8) {
     315           0 :         const AP_PIDInfo &pid_info = sub.pos_control.D_get_accel_pid().get_pid_info();
     316           0 :         mavlink_msg_pid_tuning_send(chan, PID_TUNING_ACCZ,
     317           0 :                                     pid_info.target*0.01f,
     318           0 :                                     -(ahrs.get_accel_ef().z + GRAVITY_MSS),
     319           0 :                                     pid_info.FF*0.01f,
     320           0 :                                     pid_info.P*0.01f,
     321           0 :                                     pid_info.I*0.01f,
     322           0 :                                     pid_info.D*0.01f,
     323           0 :                                     pid_info.slew_rate,
     324           0 :                                     pid_info.Dmod);
     325           0 :         if (!HAVE_PAYLOAD_SPACE(chan, PID_TUNING)) {
     326           0 :             return;
     327             :         }
     328             :     }
     329             : }
     330             : 
     331      108218 : bool GCS_Sub::vehicle_initialised() const {
     332      108218 :     return sub.ap.initialised;
     333             : }
     334             : 
     335             : // try to send a message, return false if it won't fit in the serial tx buffer
     336     7219053 : bool GCS_MAVLINK_Sub::try_send_message(enum ap_message id)
     337             : {
     338     7219053 :     switch (id) {
     339             : 
     340       52069 :     case MSG_NAMED_FLOAT:
     341       52069 :         send_info();
     342       52069 :         break;
     343             : 
     344       52523 :     case MSG_WIND: // other vehicles do something custom with wind:
     345       52523 :         return true;
     346             : 
     347             : #if AP_RANGEFINDER_ENABLED
     348          46 :     case MSG_WATER_DEPTH:
     349          46 :         CHECK_PAYLOAD_SIZE(WATER_DEPTH);
     350          46 :         send_water_depth();
     351          46 :         break;
     352             : #endif  // AP_RANGEFINDER_ENABLED
     353             : 
     354     7114415 :     default:
     355     7114415 :         return GCS_MAVLINK::try_send_message(id);
     356             :     }
     357             : 
     358       52115 :     return true;
     359             : }
     360             : 
     361           0 : bool GCS_MAVLINK_Sub::handle_guided_request(AP_Mission::Mission_Command &cmd)
     362             : {
     363           0 :     return sub.do_guided(cmd);
     364             : }
     365             : 
     366           0 : MAV_RESULT GCS_MAVLINK_Sub::_handle_command_preflight_calibration_baro(const mavlink_message_t &msg)
     367             : {
     368           0 :     if (sub.motors.armed()) {
     369           0 :         gcs().send_text(MAV_SEVERITY_INFO, "Disarm before calibration.");
     370           0 :         return MAV_RESULT_FAILED;
     371             :     }
     372             : 
     373           0 :     if (!sub.control_check_barometer()) {
     374           0 :         return MAV_RESULT_FAILED;
     375             :     }
     376             : 
     377           0 :     AP::baro().calibrate(true);
     378           0 :     return MAV_RESULT_ACCEPTED;
     379             : }
     380             : 
     381           0 : MAV_RESULT GCS_MAVLINK_Sub::_handle_command_preflight_calibration(const mavlink_command_int_t &packet, const mavlink_message_t &msg)
     382             : {
     383           0 :     if (packet.y == 1) {
     384             :         // compassmot calibration
     385             :         //result = sub.mavlink_compassmot(chan);
     386           0 :         gcs().send_text(MAV_SEVERITY_INFO, "#CompassMot calibration not supported");
     387           0 :         return MAV_RESULT_UNSUPPORTED;
     388             :     }
     389             : 
     390           0 :     return GCS_MAVLINK::_handle_command_preflight_calibration(packet, msg);
     391             : }
     392             : 
     393           0 : MAV_RESULT GCS_MAVLINK_Sub::handle_command_do_set_roi(const Location &roi_loc)
     394             : {
     395           0 :     if (!roi_loc.check_latlng()) {
     396           0 :         return MAV_RESULT_FAILED;
     397             :     }
     398           0 :     sub.mode_auto.set_auto_yaw_roi(roi_loc);
     399           0 :     return MAV_RESULT_ACCEPTED;
     400             : }
     401             : 
     402           2 : MAV_RESULT GCS_MAVLINK_Sub::handle_command_int_do_reposition(const mavlink_command_int_t &packet)
     403             : {
     404           2 :     const bool change_modes = ((int32_t)packet.param2 & MAV_DO_REPOSITION_FLAGS_CHANGE_MODE) == MAV_DO_REPOSITION_FLAGS_CHANGE_MODE;
     405           2 :     if (!sub.flightmode->in_guided_mode() && !change_modes) {
     406           0 :         return MAV_RESULT_DENIED;
     407             :     }
     408             : 
     409           2 :     Location request_location;
     410           2 :     if (!location_from_command_t(packet, request_location)) {
     411           0 :         return MAV_RESULT_DENIED;
     412             :     }
     413             : 
     414           2 :     if (request_location.sanitize(sub.current_loc)) {
     415             :         // if the location wasn't already sane don't load it
     416           0 :         return MAV_RESULT_DENIED; // failed as the location is not valid
     417             :     }
     418             : 
     419             :     // we need to do this first, as we don't want to change the flight mode unless we can also set the target
     420           2 :     if (!sub.mode_guided.guided_set_destination(request_location)) {
     421           0 :         return MAV_RESULT_FAILED;
     422             :     }
     423             : 
     424           2 :     if (!sub.flightmode->in_guided_mode()) {
     425           0 :         if (!sub.set_mode(Mode::Number::GUIDED, ModeReason::GCS_COMMAND)) {
     426           0 :             return MAV_RESULT_FAILED;
     427             :         }
     428             :         // the position won't have been loaded if we had to change the flight mode, so load it again
     429           0 :         if (!sub.mode_guided.guided_set_destination(request_location)) {
     430           0 :             return MAV_RESULT_FAILED;
     431             :         }
     432             :     }
     433             : 
     434           2 :     return MAV_RESULT_ACCEPTED;
     435             : }
     436             : 
     437       34628 : MAV_RESULT GCS_MAVLINK_Sub::handle_command_int_packet(const mavlink_command_int_t &packet, const mavlink_message_t &msg)
     438             : {
     439       34628 :     switch(packet.command) {
     440             : 
     441          12 :     case MAV_CMD_CONDITION_YAW:
     442          12 :         return handle_MAV_CMD_CONDITION_YAW(packet);
     443             : 
     444           6 :     case MAV_CMD_DO_CHANGE_SPEED:
     445           6 :         return handle_MAV_CMD_DO_CHANGE_SPEED(packet);
     446             : 
     447           0 :     case MAV_CMD_DO_MOTOR_TEST:
     448           0 :         return handle_MAV_CMD_DO_MOTOR_TEST(packet);
     449             : 
     450           2 :     case MAV_CMD_DO_REPOSITION:
     451           2 :         return handle_command_int_do_reposition(packet);
     452             : 
     453           4 :     case MAV_CMD_MISSION_START:
     454           4 :         if (!is_zero(packet.param1) || !is_zero(packet.param2)) {
     455             :             // first-item/last item not supported
     456           0 :             return MAV_RESULT_DENIED;
     457             :         }
     458           4 :         return handle_MAV_CMD_MISSION_START(packet);
     459             : 
     460           2 :     case MAV_CMD_NAV_LOITER_UNLIM:
     461           2 :         return handle_MAV_CMD_NAV_LOITER_UNLIM(packet);
     462             : 
     463           2 :     case MAV_CMD_NAV_LAND:
     464           2 :         return handle_MAV_CMD_NAV_LAND(packet);
     465             : 
     466             :     }
     467             : 
     468       34600 :     return GCS_MAVLINK::handle_command_int_packet(packet, msg);
     469             : }
     470             : 
     471           2 : MAV_RESULT GCS_MAVLINK_Sub::handle_MAV_CMD_NAV_LOITER_UNLIM(const mavlink_command_int_t &packet)
     472             : {
     473           2 :         if (!sub.set_mode(Mode::Number::POSHOLD, ModeReason::GCS_COMMAND)) {
     474           0 :             return MAV_RESULT_FAILED;
     475             :         }
     476           2 :         return MAV_RESULT_ACCEPTED;
     477             : }
     478             : 
     479           2 : MAV_RESULT GCS_MAVLINK_Sub::handle_MAV_CMD_NAV_LAND(const mavlink_command_int_t &packet)
     480             : {
     481           2 :         if (!sub.set_mode(Mode::Number::SURFACE, ModeReason::GCS_COMMAND)) {
     482           0 :             return MAV_RESULT_FAILED;
     483             :         }
     484           2 :         return MAV_RESULT_ACCEPTED;
     485             : }
     486             : 
     487          12 : MAV_RESULT GCS_MAVLINK_Sub::handle_MAV_CMD_CONDITION_YAW(const mavlink_command_int_t &packet)
     488             : {
     489             :         // param1 : target angle [0-360]
     490             :         // param2 : speed during change [deg per second]
     491             :         // param3 : direction (-1:ccw, +1:cw)
     492             :         // param4 : relative offset (1) or absolute angle (0)
     493          36 :         if ((packet.param1 >= 0.0f)   &&
     494          26 :             (packet.param1 <= 360.0f) &&
     495          14 :             (is_zero(packet.param4) || is_equal(packet.param4,1.0f))) {
     496          12 :             sub.mode_auto.set_auto_yaw_look_at_heading(packet.param1, packet.param2, (int8_t)packet.param3, (uint8_t)packet.param4);
     497          12 :             return MAV_RESULT_ACCEPTED;
     498             :         }
     499           0 :         return MAV_RESULT_DENIED;
     500             : }
     501             : 
     502           6 : MAV_RESULT GCS_MAVLINK_Sub::handle_MAV_CMD_DO_CHANGE_SPEED(const mavlink_command_int_t &packet)
     503             : {
     504           6 :     if (!is_positive(packet.param2)) {
     505             :         // Target speed must be larger than zero
     506           0 :         return MAV_RESULT_DENIED;
     507             :     }
     508             : 
     509           6 :     switch (SPEED_TYPE(packet.param1)) {
     510           0 :         case SPEED_TYPE_CLIMB_SPEED:
     511             :         case SPEED_TYPE_DESCENT_SPEED:
     512             :         case SPEED_TYPE_ENUM_END:
     513           0 :             break;
     514             : 
     515           6 :         case SPEED_TYPE_AIRSPEED: // Airspeed is treated as ground speed for GCS compatibility
     516             :         case SPEED_TYPE_GROUNDSPEED:
     517           6 :             sub.wp_nav.set_speed_NE_cms(packet.param2 * 100.0);
     518           6 :             return MAV_RESULT_ACCEPTED;
     519             :     }
     520             : 
     521           0 :     return MAV_RESULT_DENIED;
     522             : }
     523             : 
     524           4 : MAV_RESULT GCS_MAVLINK_Sub::handle_MAV_CMD_MISSION_START(const mavlink_command_int_t &packet)
     525             : {
     526           4 :         if (sub.motors.armed() && sub.set_mode(Mode::Number::AUTO, ModeReason::GCS_COMMAND)) {
     527           4 :             return MAV_RESULT_ACCEPTED;
     528             :         }
     529           0 :         return MAV_RESULT_FAILED;
     530             : }
     531             : 
     532           0 : MAV_RESULT GCS_MAVLINK_Sub::handle_MAV_CMD_DO_MOTOR_TEST(const mavlink_command_int_t &packet)
     533             : {
     534             :         // param1 : motor sequence number (a number from 1 to max number of motors on the vehicle)
     535             :         // param2 : throttle type (0=throttle percentage, 1=PWM, 2=pilot throttle channel pass-through. See MOTOR_TEST_THROTTLE_TYPE enum)
     536             :         // param3 : throttle (range depends upon param2)
     537             :         // param4 : timeout (in seconds)
     538           0 :         if (!sub.handle_do_motor_test(packet)) {
     539           0 :             return MAV_RESULT_FAILED;
     540             :         }
     541           0 :         return MAV_RESULT_ACCEPTED;
     542             : }
     543             : 
     544             : // this is called on receipt of a MANUAL_CONTROL packet and is
     545             : // expected to call manual_override to override RC input on desired
     546             : // axes.
     547           0 : void GCS_MAVLINK_Sub::handle_manual_control_axes(const mavlink_manual_control_t &packet, const uint32_t tnow)
     548             : {
     549           0 :         sub.transform_manual_control_to_rc_override(
     550           0 :             packet.x,
     551           0 :             packet.y,
     552           0 :             packet.z,
     553           0 :             packet.r,
     554           0 :             packet.buttons,
     555           0 :             packet.buttons2,
     556           0 :             packet.enabled_extensions,
     557           0 :             packet.s,
     558           0 :             packet.t,
     559           0 :             packet.aux1,
     560           0 :             packet.aux2,
     561           0 :             packet.aux3,
     562           0 :             packet.aux4,
     563           0 :             packet.aux5,
     564           0 :             packet.aux6
     565             :         );
     566             : 
     567           0 :         sub.failsafe.last_pilot_input_ms = AP_HAL::millis();
     568           0 : }
     569             : 
     570      138056 : void GCS_MAVLINK_Sub::handle_message(const mavlink_message_t &msg)
     571             : {
     572      138056 :     switch (msg.msgid) {
     573             : 
     574           0 :     case MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE: {     // MAV ID: 70
     575           0 :         if (!gcs().sysid_is_gcs(msg.sysid)) {
     576           0 :             break;    // Only accept control from our gcs
     577             :         }
     578             : 
     579           0 :         sub.failsafe.last_pilot_input_ms = AP_HAL::millis();
     580             :         // a RC override message is considered to be a 'heartbeat'
     581             :         // from the ground station for failsafe purposes
     582             :         
     583           0 :         handle_rc_channels_override(msg);
     584           0 :         break;
     585             :     }
     586             : 
     587             :     
     588         282 :     case MAVLINK_MSG_ID_SET_ATTITUDE_TARGET: { // MAV ID: 82
     589             :         // decode packet
     590             :         mavlink_set_attitude_target_t packet;
     591         282 :         mavlink_msg_set_attitude_target_decode(&msg, &packet);
     592             : 
     593             :         // ensure type_mask specifies to use attitude
     594             :         // the thrust can be used from the altitude hold
     595         282 :         if (packet.type_mask & (1<<6)) {
     596         282 :             sub.set_attitude_target_no_gps = {AP_HAL::millis(), packet};
     597             :         }
     598             : 
     599             :         // ensure type_mask specifies to use attitude and thrust
     600         282 :         if ((packet.type_mask & ((1<<7)|(1<<6))) != 0) {
     601         282 :             break;
     602             :         }
     603             : 
     604             :         // convert thrust to climb rate
     605           0 :         packet.thrust = constrain_float(packet.thrust, 0.0f, 1.0f);
     606           0 :         float climb_rate_cms = 0.0f;
     607           0 :         if (is_equal(packet.thrust, 0.5f)) {
     608           0 :             climb_rate_cms = 0.0f;
     609           0 :         } else if (packet.thrust > 0.5f) {
     610             :             // climb at up to WP_SPD_UP
     611           0 :             climb_rate_cms = (packet.thrust - 0.5f) * 2.0f * sub.wp_nav.get_default_speed_up_cms();
     612             :         } else {
     613             :             // descend at up to WP_SPD_DN
     614           0 :             climb_rate_cms = (packet.thrust - 0.5f) * 2.0f * sub.wp_nav.get_default_speed_down_cms();
     615             :         }
     616           0 :         sub.mode_guided.guided_set_angle(Quaternion(packet.q[0],packet.q[1],packet.q[2],packet.q[3]), climb_rate_cms);
     617           0 :         break;
     618             :     }
     619             : 
     620           2 :     case MAVLINK_MSG_ID_SET_POSITION_TARGET_LOCAL_NED: {   // MAV ID: 84
     621             :         // decode packet
     622             :         mavlink_set_position_target_local_ned_t packet;
     623           2 :         mavlink_msg_set_position_target_local_ned_decode(&msg, &packet);
     624             : 
     625             :         // exit if vehicle is not in Guided mode or Auto-Guided mode
     626           2 :         if ((sub.control_mode != Mode::Number::GUIDED) && !(sub.control_mode == Mode::Number::AUTO && sub.auto_mode == Auto_NavGuided)) {
     627             :             break;
     628             :         }
     629             : 
     630             :         // check for supported coordinate frames
     631           2 :         if (packet.coordinate_frame != MAV_FRAME_LOCAL_NED &&
     632           0 :                 packet.coordinate_frame != MAV_FRAME_LOCAL_OFFSET_NED &&
     633           0 :                 packet.coordinate_frame != MAV_FRAME_BODY_NED &&
     634           0 :                 packet.coordinate_frame != MAV_FRAME_BODY_OFFSET_NED &&
     635           0 :                 packet.coordinate_frame != MAV_FRAME_BODY_FRD) {
     636           0 :             break;
     637             :         }
     638             : 
     639           2 :         bool pos_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_POS_IGNORE;
     640           2 :         bool vel_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_VEL_IGNORE;
     641           2 :         bool acc_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_ACC_IGNORE;
     642           2 :         bool yaw_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_IGNORE;
     643           2 :         bool yaw_rate_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_RATE_IGNORE;
     644             : 
     645             :         // prepare position
     646           2 :         Vector3f pos_vector_neu_cm;
     647           2 :         if (!pos_ignore) {
     648             :             // convert to cm
     649           2 :             pos_vector_neu_cm = Vector3f(packet.x * 100.0f, packet.y * 100.0f, -packet.z * 100.0f);
     650             :             // rotate from body-frame if necessary
     651           2 :             if (packet.coordinate_frame == MAV_FRAME_BODY_NED ||
     652           2 :                     packet.coordinate_frame == MAV_FRAME_BODY_FRD ||
     653           2 :                     packet.coordinate_frame == MAV_FRAME_BODY_OFFSET_NED) {
     654           0 :                 sub.rotate_body_frame_to_NE(pos_vector_neu_cm.x, pos_vector_neu_cm.y);
     655             :             }
     656             :             // add body offset if necessary
     657           2 :             if (packet.coordinate_frame == MAV_FRAME_LOCAL_OFFSET_NED ||
     658           2 :                     packet.coordinate_frame == MAV_FRAME_BODY_NED ||
     659           2 :                     packet.coordinate_frame == MAV_FRAME_BODY_FRD ||
     660           2 :                     packet.coordinate_frame == MAV_FRAME_BODY_OFFSET_NED) {
     661           0 :                 Vector3f pos_neu_cm = (sub.pos_control.get_pos_estimate_NED_m() * 100.0f).tofloat();
     662           0 :                 pos_neu_cm.z = -pos_neu_cm.z;
     663           0 :                 pos_vector_neu_cm += pos_neu_cm;
     664             :             }
     665             :         }
     666             : 
     667             :         // prepare velocity
     668           2 :         Vector3f vel_vector_neu_cms;
     669           2 :         if (!vel_ignore) {
     670             :             // convert to cm
     671           2 :             vel_vector_neu_cms = Vector3f(packet.vx * 100.0f, packet.vy * 100.0f, -packet.vz * 100.0f);
     672             :             // rotate from body-frame if necessary
     673           2 :             if (packet.coordinate_frame == MAV_FRAME_BODY_NED || packet.coordinate_frame == MAV_FRAME_BODY_FRD || packet.coordinate_frame == MAV_FRAME_BODY_OFFSET_NED) {
     674           0 :                 sub.rotate_body_frame_to_NE(vel_vector_neu_cms.x, vel_vector_neu_cms.y);
     675             :             }
     676             :         }
     677             : 
     678             :         // prepare acceleration
     679           2 :         Vector3f acc_vector_neu_cmss;
     680           2 :         if (!acc_ignore) {
     681             :             // convert to cm/s/s
     682           2 :             acc_vector_neu_cmss = Vector3f(packet.afx * 100.0f, packet.afy * 100.0f, -packet.afz * 100.0f);
     683             :             // rotate from body-frame if necessary
     684           2 :             if (packet.coordinate_frame == MAV_FRAME_BODY_NED || packet.coordinate_frame == MAV_FRAME_BODY_FRD || packet.coordinate_frame == MAV_FRAME_BODY_OFFSET_NED) {
     685           0 :                 sub.rotate_body_frame_to_NE(acc_vector_neu_cmss.x, acc_vector_neu_cmss.y);
     686             :             }
     687             :         }
     688             : 
     689             :         // prepare yaw
     690           2 :         float yaw_cd =  0.0f;
     691           2 :         bool yaw_relative = false;
     692           2 :         float yaw_rate_cds = 0.0f;
     693           2 :         if (!yaw_ignore) {
     694           0 :             yaw_cd = degrees(packet.yaw) * 100.0f;
     695           0 :             yaw_relative = packet.coordinate_frame == MAV_FRAME_BODY_OFFSET_NED;
     696             :         }
     697           2 :         if (!yaw_rate_ignore) {
     698           0 :             yaw_rate_cds = degrees(packet.yaw_rate) * 100.0f;
     699             :         }
     700             : 
     701             :         // send request
     702           2 :         if (!pos_ignore && !vel_ignore) {
     703           2 :             sub.mode_guided.guided_set_posvelaccel(pos_vector_neu_cm, vel_vector_neu_cms, acc_vector_neu_cmss, !yaw_ignore, yaw_cd, !yaw_rate_ignore, yaw_rate_cds, yaw_relative);
     704           0 :         } else if (pos_ignore && !vel_ignore && acc_ignore) {
     705           0 :             sub.mode_guided.guided_set_velocity(vel_vector_neu_cms, !yaw_ignore, yaw_cd, !yaw_rate_ignore, yaw_rate_cds, yaw_relative);
     706           0 :         } else if (!pos_ignore && vel_ignore && acc_ignore) {
     707           0 :             sub.mode_guided.guided_set_destination(pos_vector_neu_cm, !yaw_ignore, yaw_cd, !yaw_rate_ignore, yaw_rate_cds, yaw_relative);
     708             :         }
     709             : 
     710           2 :         break;
     711             :     }
     712             : 
     713           3 :     case MAVLINK_MSG_ID_SET_POSITION_TARGET_GLOBAL_INT: {  // MAV ID: 86
     714             :         // decode packet
     715             :         mavlink_set_position_target_global_int_t packet;
     716           3 :         mavlink_msg_set_position_target_global_int_decode(&msg, &packet);
     717             : 
     718             :         // exit if vehicle is not in Guided, Auto-Guided, or Depth Hold modes
     719           3 :         if ((sub.control_mode != Mode::Number::GUIDED)
     720           0 :             && !(sub.control_mode == Mode::Number::AUTO && sub.auto_mode == Auto_NavGuided)
     721           0 :             && !(sub.control_mode == Mode::Number::ALT_HOLD)) {
     722           0 :             break;
     723             :         }
     724             : 
     725           3 :         bool z_ignore        = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_Z_IGNORE;
     726           3 :         bool pos_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_POS_IGNORE;
     727           3 :         bool vel_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_VEL_IGNORE;
     728           3 :         bool acc_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_ACC_IGNORE;
     729             : 
     730             :         /*
     731             :          * for future use:
     732             :          * bool force           = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_FORCE;
     733             :          * bool yaw_ignore      = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_IGNORE;
     734             :          * bool yaw_rate_ignore = packet.type_mask & MAVLINK_SET_POS_TYPE_MASK_YAW_RATE_IGNORE;
     735             :          */
     736             : 
     737           3 :         if (!z_ignore && sub.control_mode == Mode::Number::ALT_HOLD) { // Control only target depth when in ALT_HOLD
     738           0 :             sub.pos_control.set_pos_desired_U_cm(packet.alt*100);
     739           0 :             break;
     740             :         }
     741             : 
     742           3 :         Location loc;
     743           3 :         if (!pos_ignore) {
     744             :             // sanity check location
     745           3 :             if (!check_latlng(packet.lat_int, packet.lon_int)) {
     746           0 :                 break;
     747             :             }
     748             :             Location::AltFrame frame;
     749           3 :             if (!mavlink_coordinate_frame_to_location_alt_frame((MAV_FRAME)packet.coordinate_frame, frame)) {
     750             :                 // unknown coordinate frame
     751           0 :                 break;
     752             :             }
     753           3 :             loc = {
     754             :                 packet.lat_int,
     755             :                 packet.lon_int,
     756           3 :                 int32_t(packet.alt*100),
     757             :                 frame,
     758             :             };
     759             :         }
     760             : 
     761           3 :         Vector3f acc_vector_neu_cmss;
     762           3 :         if (!acc_ignore) {
     763           0 :             acc_vector_neu_cmss = Vector3f(packet.afx * 100.0f, packet.afy * 100.0f, -packet.afz * 100.0f);
     764             :         }
     765             : 
     766           3 :         if (!pos_ignore && !vel_ignore) {
     767           0 :             Vector3f pos_neu_cm;
     768           0 :             if (!loc.get_vector_from_origin_NEU_cm(pos_neu_cm)) {
     769           0 :                 break;
     770             :             }
     771           0 :             sub.mode_guided.guided_set_posvelaccel(pos_neu_cm, Vector3f(packet.vx * 100.0f, packet.vy * 100.0f, -packet.vz * 100.0f), acc_vector_neu_cmss);
     772           3 :         } else if (pos_ignore && !vel_ignore && acc_ignore) {
     773           0 :             sub.mode_guided.guided_set_velocity(Vector3f(packet.vx * 100.0f, packet.vy * 100.0f, -packet.vz * 100.0f));
     774           3 :         } else if (!pos_ignore && vel_ignore && acc_ignore) {
     775           3 :             sub.mode_guided.guided_set_destination(loc);
     776             :         }
     777             : 
     778           3 :         break;
     779             :     }
     780             : 
     781             :     // Remote leak sensor support (e.g. in a separate enclosure), via MAVLink status messages.
     782           0 :     case MAVLINK_MSG_ID_SYS_STATUS: {
     783             :         mavlink_sys_status_t packet;
     784           0 :         mavlink_msg_sys_status_decode(&msg, &packet);
     785           0 :         if ((msg.sysid == gcs().sysid_this_mav()) &&
     786           0 :             (packet.onboard_control_sensors_present & MAV_SYS_STATUS_EXTENSION_USED) &&
     787           0 :             (packet.onboard_control_sensors_enabled_extended & MAV_SYS_STATUS_SENSOR_LEAK) &&
     788           0 :             !(packet.onboard_control_sensors_health_extended & MAV_SYS_STATUS_SENSOR_LEAK)) {
     789           0 :             sub.leak_detector.set_detect();
     790             :         }
     791           0 :         break;
     792             :     }
     793             : 
     794      137769 :     default:
     795      137769 :         GCS_MAVLINK::handle_message(msg);
     796      137700 :         break;
     797             :     }     // end switch
     798      137987 : } // end handle mavlink
     799             : 
     800           3 : uint64_t GCS_MAVLINK_Sub::capabilities() const
     801             : {
     802             :     return (MAV_PROTOCOL_CAPABILITY_MISSION_FLOAT |
     803             :             MAV_PROTOCOL_CAPABILITY_MISSION_INT |
     804             :             MAV_PROTOCOL_CAPABILITY_SET_POSITION_TARGET_LOCAL_NED |
     805             :             MAV_PROTOCOL_CAPABILITY_SET_POSITION_TARGET_GLOBAL_INT |
     806             :             MAV_PROTOCOL_CAPABILITY_FLIGHT_TERMINATION |
     807             : #if AP_TERRAIN_AVAILABLE
     808           3 :             (sub.terrain.enabled() ? MAV_PROTOCOL_CAPABILITY_TERRAIN : 0) |
     809             : #endif
     810             :             MAV_PROTOCOL_CAPABILITY_SET_ATTITUDE_TARGET |
     811           3 :             GCS_MAVLINK::capabilities()
     812           3 :         );
     813             : }
     814             : 
     815           0 : MAV_RESULT GCS_MAVLINK_Sub::handle_flight_termination(const mavlink_command_int_t &packet)
     816             : {
     817           0 :     if (packet.param1 > 0.5f) {
     818           0 :         sub.arming.disarm(AP_Arming::Method::TERMINATION);
     819           0 :         return MAV_RESULT_ACCEPTED;
     820             :     }
     821           0 :     return MAV_RESULT_FAILED;
     822             : }
     823             : 
     824       81589 : int32_t GCS_MAVLINK_Sub::global_position_int_alt() const
     825             : {
     826       81589 :     return static_cast<int32_t>(sub.get_alt_msl() * 1000.0f);
     827             : }
     828             : 
     829       81587 : int32_t GCS_MAVLINK_Sub::global_position_int_relative_alt() const
     830             : {
     831       81587 :     return static_cast<int32_t>(sub.get_alt_rel() * 1000.0f);
     832             : }
     833             : 
     834             : #if HAL_HIGH_LATENCY2_ENABLED
     835           0 : int16_t GCS_MAVLINK_Sub::high_latency_target_altitude() const
     836             : {
     837           0 :     AP_AHRS &ahrs = AP::ahrs();
     838           0 :     Location global_position_current;
     839           0 :     UNUSED_RESULT(ahrs.get_location(global_position_current));
     840             : 
     841             :     //return units are m
     842           0 :     if (sub.control_mode == Mode::Number::AUTO || sub.control_mode == Mode::Number::GUIDED) {
     843           0 :         return 0.01 * (global_position_current.alt + sub.pos_control.get_pos_error_U_cm());
     844             :     }
     845           0 :     return 0;
     846             :     
     847             : }
     848             : 
     849           0 : uint8_t GCS_MAVLINK_Sub::high_latency_tgt_heading() const
     850             : {
     851             :     // return units are deg/2
     852           0 :     if (sub.control_mode == Mode::Number::AUTO || sub.control_mode == Mode::Number::GUIDED) {
     853             :         // need to convert -18000->18000 to 0->360/2
     854           0 :         return wrap_360_cd(sub.wp_nav.get_wp_bearing_to_destination_cd()) / 200;
     855             :     }
     856           0 :     return 0;      
     857             : }
     858             :     
     859           0 : uint16_t GCS_MAVLINK_Sub::high_latency_tgt_dist_dam() const
     860             : {
     861           0 :     if (sub.control_mode == Mode::Number::AUTO || sub.control_mode == Mode::Number::GUIDED) {
     862           0 :         return MIN(static_cast<uint16_t>(sub.wp_nav.get_wp_distance_to_destination_cm() * 1e-3), UINT16_MAX);
     863             :     }
     864           0 :     return 0;
     865             : }
     866             : 
     867           0 : uint8_t GCS_MAVLINK_Sub::high_latency_tgt_airspeed() const
     868             : {
     869             :     // return units are m/s*5
     870           0 :     if (sub.control_mode == Mode::Number::AUTO || sub.control_mode == Mode::Number::GUIDED) {
     871           0 :         return MIN((sub.pos_control.get_vel_desired_NEU_cms().length()/100) * 5, UINT8_MAX);
     872             :     }
     873           0 :     return 0;
     874             : }
     875             : #endif // HAL_HIGH_LATENCY2_ENABLED
     876             : 
     877             : // Send the mode with the given index (not mode number!) return the total number of modes
     878             : // Index starts at 1
     879           0 : uint8_t GCS_MAVLINK_Sub::send_available_mode(uint8_t index) const
     880             : {
     881           0 :     const Mode* modes[] {
     882             :         &sub.mode_manual,
     883             :         &sub.mode_stabilize,
     884             :         &sub.mode_acro,
     885             :         &sub.mode_althold,
     886             :         &sub.mode_surftrak,
     887             :         &sub.mode_poshold,
     888             :         &sub.mode_auto,
     889             :         &sub.mode_guided,
     890             :         &sub.mode_circle,
     891             :         &sub.mode_surface,
     892             :         &sub.mode_motordetect,
     893             :     };
     894             : 
     895           0 :     const uint8_t mode_count = ARRAY_SIZE(modes);
     896             : 
     897             :     // Convert to zero indexed
     898           0 :     const uint8_t index_zero = index - 1;
     899           0 :     if (index_zero >= mode_count) {
     900             :         // Mode does not exist!?
     901           0 :         return mode_count;
     902             :     }
     903             : 
     904             :     // Ask the mode for its name and number
     905           0 :     const char* name = modes[index_zero]->name();
     906           0 :     const uint8_t mode_number = (uint8_t)modes[index_zero]->number();
     907             : 
     908           0 :     mavlink_msg_available_modes_send(
     909           0 :         chan,
     910             :         mode_count,
     911             :         index,
     912             :         MAV_STANDARD_MODE::MAV_STANDARD_MODE_NON_STANDARD,
     913             :         mode_number,
     914             :         0, // MAV_MODE_PROPERTY bitmask
     915             :         name
     916             :     );
     917             : 
     918           0 :     return mode_count;
     919             : }

Generated by: LCOV version 1.14