LCOV - code coverage report
Current view: top level - AP_ICEngine - AP_ICEngine.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 247 278 88.8 %
Date: 2026-09-09 11:02:08 Functions: 13 13 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : #include "AP_ICEngine.h"
      17             : 
      18             : #if AP_ICENGINE_ENABLED
      19             : 
      20             : #include <SRV_Channel/SRV_Channel.h>
      21             : #include <GCS_MAVLink/GCS.h>
      22             : #include <AP_AHRS/AP_AHRS.h>
      23             : #include <AP_Scheduler/AP_Scheduler.h>
      24             : #include <AP_Notify/AP_Notify.h>
      25             : #include <RC_Channel/RC_Channel.h>
      26             : #include <AP_RPM/AP_RPM.h>
      27             : #include <AP_Parachute/AP_Parachute.h>
      28             : #include <AP_Relay/AP_Relay.h>
      29             : #include "AP_ICEngine.h"
      30             : 
      31             : extern const AP_HAL::HAL& hal;
      32             : 
      33             : const AP_Param::GroupInfo AP_ICEngine::var_info[] = {
      34             : 
      35             :     // @Param: ENABLE
      36             :     // @DisplayName: Enable ICEngine control
      37             :     // @Description: This enables internal combustion engine control
      38             :     // @Values: 0:Disabled, 1:Enabled
      39             :     // @User: Advanced
      40             :     AP_GROUPINFO_FLAGS("ENABLE", 0, AP_ICEngine, enable, 0, AP_PARAM_FLAG_ENABLE),
      41             : 
      42             :     // @Param: START_CHAN
      43             :     // @DisplayName: Input channel for engine start
      44             :     // @Description: This is an RC input channel for requesting engine start. Engine will try to start when channel is at or above 1700. Engine will stop when channel is at or below 1300. Between 1301 and 1699 the engine will not change state unless a MAVLink command or mission item commands a state change, or the vehicle is disarmed. See ICE_STARTCHN_MIN parameter to change engine stop PWM value and/or to enable debouncing of the START_CH to avoid accidental engine kills due to noise on channel.
      45             :     // @Legacy: 4.5 param
      46             :     // @User: Standard
      47             :     // @Values: 0:None,1:Chan1,2:Chan2,3:Chan3,4:Chan4,5:Chan5,6:Chan6,7:Chan7,8:Chan8,9:Chan9,10:Chan10,11:Chan11,12:Chan12,13:Chan13,14:Chan14,15:Chan15,16:Chan16
      48             : 
      49             :     // 1 was START_CHAN
      50             : 
      51             :     // @Param: STARTER_TIME
      52             :     // @DisplayName: Time to run starter
      53             :     // @Description: This is the number of seconds to run the starter when trying to start the engine
      54             :     // @User: Standard
      55             :     // @Units: s
      56             :     // @Range: 0.1 5
      57             :     AP_GROUPINFO("STARTER_TIME", 2, AP_ICEngine, starter_time, 3),
      58             : 
      59             :     // @Param: START_DELAY
      60             :     // @DisplayName: Time to wait between starts
      61             :     // @Description: Delay between start attempts
      62             :     // @User: Standard
      63             :     // @Units: s
      64             :     // @Range: 1 10
      65             :     AP_GROUPINFO("START_DELAY", 3, AP_ICEngine, starter_delay, 2),
      66             : 
      67             : #if AP_RPM_ENABLED
      68             :     // @Param: RPM_THRESH
      69             :     // @DisplayName: RPM threshold
      70             :     // @Description: This is the measured RPM above which the engine is considered to be running
      71             :     // @User: Standard
      72             :     // @Range: 100 100000
      73             :     AP_GROUPINFO("RPM_THRESH", 4, AP_ICEngine, rpm_threshold, 100),
      74             : #endif
      75             : 
      76             :     // @Param: PWM_IGN_ON
      77             :     // @DisplayName: PWM value for ignition on
      78             :     // @Description: This is the value sent to the ignition channel when on
      79             :     // @Legacy: 4.5 param
      80             :     // @User: Standard
      81             :     // @Range: 1000 2000
      82             : 
      83             :     // 5 was PWM_IGN_ON
      84             : 
      85             :     // @Param: PWM_IGN_OFF
      86             :     // @DisplayName: PWM value for ignition off
      87             :     // @Description: This is the value sent to the ignition channel when off
      88             :     // @Legacy: 4.5 param
      89             :     // @User: Standard
      90             :     // @Range: 1000 2000
      91             : 
      92             :     // 6 was PWM_IGN_OFF
      93             : 
      94             :     // @Param: PWM_STRT_ON
      95             :     // @DisplayName: PWM value for starter on
      96             :     // @Description: This is the value sent to the starter channel when on
      97             :     // @Legacy: 4.5 param
      98             :     // @User: Standard
      99             :     // @Range: 1000 2000
     100             : 
     101             :     // 7 was PWM_STRT_ON
     102             : 
     103             :     // @Param: PWM_STRT_OFF
     104             :     // @DisplayName: PWM value for starter off
     105             :     // @Description: This is the value sent to the starter channel when off
     106             :     // @Legacy: 4.5 param
     107             :     // @User: Standard
     108             :     // @Range: 1000 2000
     109             : 
     110             :     // 8 was PWM_STRT_OFF
     111             : 
     112             : #if AP_RPM_ENABLED
     113             :     // @Param: RPM_CHAN
     114             :     // @DisplayName: RPM instance channel to use
     115             :     // @Description: This is which of the RPM instances to use for detecting the RPM of the engine
     116             :     // @User: Standard
     117             :     // @Values: 0:None,1:RPM1,2:RPM2
     118             :     AP_GROUPINFO("RPM_CHAN",  9, AP_ICEngine, rpm_instance, 0),
     119             : #endif
     120             : 
     121             :     // @Param: START_PCT
     122             :     // @DisplayName: Throttle percentage for engine start
     123             :     // @Description: This is the percentage throttle output for engine start
     124             :     // @User: Standard
     125             :     // @Range: 0 100
     126             :     AP_GROUPINFO("START_PCT", 10, AP_ICEngine, start_percent, 5),
     127             : 
     128             :     // @Param: IDLE_PCT
     129             :     // @DisplayName: Throttle percentage for engine idle
     130             :     // @Description: This is the minimum percentage throttle output while running, this includes being disarmed, but not safe
     131             :     // @User: Standard
     132             :     // @Range: 0 100
     133             :     AP_GROUPINFO("IDLE_PCT", 11, AP_ICEngine, idle_percent, 0),
     134             : 
     135             : #if AP_RPM_ENABLED
     136             :     // @Param: IDLE_RPM
     137             :     // @DisplayName: RPM Setpoint for Idle Governor
     138             :     // @Description: This configures the RPM that will be commanded by the idle governor. Set to -1 to disable
     139             :     // @User: Advanced
     140             :     AP_GROUPINFO("IDLE_RPM", 12, AP_ICEngine, idle_rpm, -1),
     141             : 
     142             :     // @Param: IDLE_DB
     143             :     // @DisplayName: Deadband for Idle Governor
     144             :     // @Description: This configures the deadband that is tolerated before adjusting the idle setpoint
     145             :     AP_GROUPINFO("IDLE_DB", 13, AP_ICEngine, idle_db, 50),
     146             : 
     147             :     // @Param: IDLE_SLEW
     148             :     // @DisplayName: Slew Rate for idle control
     149             :     // @Description: This configures the slewrate used to adjust the idle setpoint in percentage points per second
     150             :     AP_GROUPINFO("IDLE_SLEW", 14, AP_ICEngine, idle_slew, 1),
     151             : #endif
     152             : 
     153             :     // @Param: OPTIONS
     154             :     // @DisplayName: ICE options
     155             :     // @Description: Options for ICE control. The Disable ignition in RC failsafe option will cause the ignition to be set off on any R/C failsafe. If Throttle while disarmed is set then throttle control will be allowed while disarmed for planes when in MANUAL mode. If disable while disarmed is set the engine will not start while the vehicle is disarmed unless overriden by the MAVLink DO_ENGINE_CONTROL command.
     156             :     // @Bitmask: 0:Disable ignition in RC failsafe,1:Disable redline governor,2:Throttle control in MANUAL while disarmed with safety off,3:Disable while disarmed,4:Crank direction Reverse
     157             :     AP_GROUPINFO("OPTIONS", 15, AP_ICEngine, options, 0),
     158             : 
     159             :     // @Param: STARTCHN_MIN
     160             :     // @DisplayName: Input channel for engine start minimum PWM
     161             :     // @Description: This is a minimum PWM value for engine start channel for an engine stop to be commanded. Setting this value will avoid RC input glitches with low PWM values from causing an unwanted engine stop. A value of zero means any PWM above 800 and below 1300 triggers an engine stop. To stop the engine start channel must above the larger of this value and 800 and below 1300.
     162             :     // @User: Standard
     163             :     // @Range: 0 1300
     164             :     AP_GROUPINFO("STARTCHN_MIN", 16, AP_ICEngine, start_chan_min_pwm, 0),
     165             : 
     166             : #if AP_RPM_ENABLED
     167             :     // @Param: REDLINE_RPM
     168             :     // @DisplayName: RPM of the redline limit for the engine
     169             :     // @Description: Maximum RPM for the engine provided by the manufacturer. A value of 0 disables this feature. See ICE_OPTIONS to enable or disable the governor.
     170             :     // @User: Advanced
     171             :     // @Range: 0 2000000
     172             :     // @Units: RPM
     173             :     AP_GROUPINFO("REDLINE_RPM", 17, AP_ICEngine, redline_rpm, 0),
     174             : #endif
     175             : 
     176             :     // 18 was IGNITION_RLY
     177             : 
     178             :     // Hidden param used as a flag for param conversion
     179             :     // This allows one time conversion while allowing user to flash between versions with and without converted params
     180             :     AP_GROUPINFO_FLAGS("FMT_VER", 19, AP_ICEngine, param_format_version, 0, AP_PARAM_FLAG_HIDDEN),
     181             : 
     182             :     // @Param: STRT_MX_RTRY
     183             :     // @DisplayName: Maximum number of retries
     184             :     // @Description: If set 0 then there is no limit to retrials. If set to a value greater than 0 then the engine will retry starting the engine this many times before giving up.
     185             :     // @User: Standard
     186             :     // @Range: 0 127
     187             :     AP_GROUPINFO("STRT_MX_RTRY", 20, AP_ICEngine, max_crank_retry, 0),
     188             : 
     189             :     AP_GROUPEND
     190             : };
     191             : 
     192             : // constructor
     193         692 : AP_ICEngine::AP_ICEngine()
     194             : {
     195         692 :     AP_Param::setup_object_defaults(this, var_info);
     196             : 
     197         692 :     if (_singleton != nullptr) {
     198           0 :         AP_HAL::panic("AP_ICEngine must be singleton");
     199             :     }
     200         692 :     _singleton = this;
     201             : 
     202             : #if AP_RPM_ENABLED
     203             :     // ICEngine runs at 10Hz
     204         692 :     _rpm_filter.set_cutoff_frequency(10, 0.5f);
     205             : #endif
     206         692 : }
     207             : 
     208             : // One time init call
     209         691 : void AP_ICEngine::init()
     210             : {
     211             :     // Configure starter and ignition outputs as range type
     212         691 :     SRV_Channels::set_range(SRV_Channel::k_starter, 1);
     213         691 :     SRV_Channels::set_range(SRV_Channel::k_ignition, 1);
     214             : 
     215             :     // Set default PWM endpoints to 1000 to 2000
     216         691 :     SRV_Channels::set_output_min_max_defaults(SRV_Channel::k_starter, 1000, 2000);
     217         691 :     SRV_Channels::set_output_min_max_defaults(SRV_Channel::k_ignition, 1000, 2000);
     218             : 
     219             :     // Convert params
     220         691 :     param_conversion();
     221         691 : }
     222             : 
     223             : // PARAMETER_CONVERSION - Added: Aug-2024 for ArduPilot-4.6
     224         691 : void AP_ICEngine::param_conversion()
     225             : {
     226         691 :     if (!enable || (param_format_version == 1)) {
     227             :         // not enabled or conversion has already been done
     228         685 :         return;
     229             :     }
     230             : 
     231             :     // Set format version so the conversion is not done again
     232           6 :     param_format_version.set_and_save(1);
     233             : 
     234             :     AP_Param::ConversionInfo info;
     235           6 :     if (!AP_Param::find_key_by_pointer(this, info.old_key)) {
     236           0 :         return;
     237             :     }
     238             : 
     239             :     // Conversion table giving the old on and off pwm parameter indexes and the function for both starter and ignition
     240             :     const struct convert_table {
     241             :         uint32_t element[2];
     242             :         SRV_Channel::Function fuction;
     243           6 :     } conversion_table[] = {
     244             :         { {450, 514}, SRV_Channel::k_starter },  // PWM_STRT_ON, PWM_STRT_OFF
     245             :         { {322, 386}, SRV_Channel::k_ignition }, // PWM_IGN_ON, PWM_IGN_OFF
     246             :     };
     247             : 
     248             :     // All PWM values were int16
     249           6 :     info.type = AP_PARAM_INT16;
     250             : 
     251          18 :     for (const auto & elem : conversion_table) {
     252             :         // Use the original default values if params are not saved
     253          12 :         uint16_t pwm_on = 2000;
     254          12 :         uint16_t pwm_off = 1000;
     255             : 
     256             :         // Get param values if configured
     257          12 :         AP_Int16 param_value;
     258          12 :         info.old_group_element = elem.element[0];
     259          12 :         if (AP_Param::find_old_parameter(&info, &param_value)) {
     260           0 :             pwm_on = param_value;
     261             :         }
     262          12 :         info.old_group_element = elem.element[1];
     263          12 :         if (AP_Param::find_old_parameter(&info, &param_value)) {
     264           0 :             pwm_off = param_value;
     265             :         }
     266             : 
     267             :         // Save as servo endpoints, note that save_output_min_max will set reversed if needed
     268          12 :         SRV_Channels::save_output_min_max(elem.fuction, pwm_off, pwm_on);
     269             :     }
     270             : 
     271             :     // Convert to new RC option
     272           6 :     AP_Int8 start_chan;
     273           6 :     info.type = AP_PARAM_INT8;
     274           6 :     info.old_group_element = 66;
     275           6 :     if (AP_Param::find_old_parameter(&info, &start_chan) && (start_chan > 0)) {
     276           0 :         RC_Channel *chan = rc().channel(start_chan-1);
     277           0 :         if (chan != nullptr) {
     278           0 :             chan->option.set_and_save((int16_t)RC_Channel::AUX_FUNC::ICE_START_STOP);
     279             :         }
     280             :     }
     281             : }
     282             : 
     283             : // Handle incoming aux function
     284          28 : void AP_ICEngine::do_aux_function(const RC_Channel::AuxFuncTrigger &trigger)
     285             : {
     286             :     // If triggered from RC apply start chan min
     287          28 :     if (trigger.source == RC_Channel::AuxFuncTrigger::Source::RC) {
     288          20 :         RC_Channel *chan = rc().channel(trigger.source_index);
     289          20 :         if ((chan != nullptr) && (chan->get_radio_in() < start_chan_min_pwm)) {
     290           0 :             return;
     291             :         }
     292             :     }
     293             : 
     294          28 :     aux_pos = trigger.pos;
     295             : }
     296             : 
     297             : /*
     298             :   update engine state
     299             :  */
     300      547089 : void AP_ICEngine::update(void)
     301             : {
     302      547089 :     if (!enable) {
     303      535579 :         return;
     304             :     }
     305             : 
     306       11510 :     bool should_run = false;
     307       11510 :     uint32_t now = AP_HAL::millis();
     308             : 
     309             : 
     310       11510 :     if ((state == ICE_START_HEIGHT_DELAY) && (aux_pos == RC_Channel::AuxSwitchPos::HIGH)) {
     311             :         // user is overriding the height start delay and asking for
     312             :         // immediate start. Put into ICE_OFF so that the logic below
     313             :         // can start the engine now
     314           0 :         state = ICE_OFF;
     315             :     }
     316             : 
     317       11510 :     if ((state == ICE_OFF) && (aux_pos == RC_Channel::AuxSwitchPos::HIGH)) {
     318          34 :         should_run = true;
     319       11476 :     } else if (aux_pos == RC_Channel::AuxSwitchPos::LOW) {
     320         134 :         should_run = false;
     321             : 
     322             :         // clear the single start flag now that we will be stopping the engine
     323         134 :         if (state != ICE_OFF) {
     324           3 :             allow_single_start_while_disarmed = false;
     325             :         }
     326       11342 :     } else if (state != ICE_OFF) {
     327        9268 :         should_run = true;
     328             :     }
     329             : 
     330       11510 :     if (option_set(Options::DISABLE_IGNITION_RC_FAILSAFE) && AP_Notify::flags.failsafe_radio) {
     331             :         // user has requested ignition kill on RC failsafe
     332           0 :         should_run = false;
     333             :     }
     334             : 
     335       11510 :     if (option_set(Options::NO_RUNNING_WHILE_DISARMED)) {
     336           0 :         if (hal.util->get_soft_armed()) {
     337             :             // clear the disarmed start flag, as we are now armed, if we disarm again we expect the engine to stop
     338           0 :             allow_single_start_while_disarmed = false;
     339             :         } else {
     340             :             // check if we are blocking disarmed starts
     341           0 :             if (!allow_single_start_while_disarmed) {
     342           0 :                 should_run = false;
     343             :             }
     344             :         }
     345             :     }
     346             : 
     347             : #if HAL_PARACHUTE_ENABLED
     348             :     // Stop on parachute deployment
     349       11510 :     AP_Parachute *parachute = AP::parachute();
     350       11510 :     if ((parachute != nullptr) && parachute->release_initiated()) {
     351           0 :         should_run = false;
     352             :     }
     353             : #endif
     354             : 
     355             :     // Stop on emergency stop
     356       11510 :     if (SRV_Channels::get_emergency_stop()) {
     357             :         // Throttle is already forced to 0 in this case, ignition should also be stopped.
     358             :         // Starter should not run.
     359           0 :         should_run = false;
     360             :     }
     361             : 
     362             :     // switch on current state to work out new state
     363       11510 :     switch (state) {
     364           0 :     case ICE_DISABLED:
     365           0 :         return;
     366        2239 :     case ICE_OFF:
     367        2239 :         if (should_run) {
     368          34 :             state = ICE_START_DELAY;
     369             :         }
     370        2239 :         crank_retry_ct = 0;
     371             :         // clear the last uncommanded stop time, we only care about tracking
     372             :         // the last one since the engine was started
     373        2239 :         last_uncommanded_stop_ms = 0;
     374        2239 :         break;
     375             : 
     376         239 :     case ICE_START_HEIGHT_DELAY: {
     377         239 :         Vector3f pos;
     378         239 :         if (!should_run) {
     379           0 :             state = ICE_OFF;
     380         239 :         } else if (AP::ahrs().get_relative_position_NED_origin_float(pos)) {
     381         239 :             if (height_pending) {
     382           2 :                 height_pending = false;
     383           2 :                 initial_height = -pos.z;
     384         237 :             } else if ((-pos.z) >= initial_height + height_required) {
     385           2 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Starting height reached %.1f",
     386             :                                                  (double)(-pos.z - initial_height));
     387           2 :                 state = ICE_STARTING;
     388             :             }
     389             :         }
     390         239 :         break;
     391             :     }
     392             : 
     393         799 :     case ICE_START_DELAY:
     394         799 :         if (!should_run) {
     395           2 :             state = ICE_OFF;
     396         797 :         } else if (now - starter_last_run_ms >= starter_delay*1000) {
     397             :             // check if we should retry starting the engine
     398          49 :             if (max_crank_retry <= 0 || crank_retry_ct < max_crank_retry) {
     399          18 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Starting engine");
     400          18 :                 state = ICE_STARTING;
     401          18 :                 crank_retry_ct++;
     402             :             } else {
     403          31 :                 GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Engine max crank attempts reached");
     404             :                 // Mark the last run now so we don't send this message every loop
     405          31 :                 starter_last_run_ms = now;
     406             :             }
     407             :         }
     408         799 :         break;
     409             : 
     410         613 :     case ICE_STARTING:
     411         613 :         if (!should_run) {
     412           0 :             state = ICE_OFF;
     413         613 :         } else if (now - starter_start_time_ms >= starter_time*1000) {
     414          25 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Engine running");
     415          25 :             state = ICE_RUNNING;
     416             :         }
     417         613 :         break;
     418             : 
     419        7620 :     case ICE_RUNNING:
     420        7620 :         if (!should_run) {
     421           1 :             state = ICE_OFF;
     422           1 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Stopped engine");
     423             :         }
     424             : #if AP_RPM_ENABLED
     425        7619 :         else if (rpm_instance > 0) {
     426             :             // check RPM to see if still running
     427             :             float rpm_value;
     428       15228 :             if (!AP::rpm()->get_rpm(rpm_instance-1, rpm_value) ||
     429        7609 :                 rpm_value < rpm_threshold) {
     430             :                 // engine has stopped when it should be running
     431          15 :                 state = ICE_START_DELAY;
     432          15 :                 GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Uncommanded engine stop");
     433          23 :                 if (last_uncommanded_stop_ms != 0 &&
     434           8 :                         now - last_uncommanded_stop_ms > 3*(starter_time + starter_delay)*1000) {
     435             :                     // if it has been a long enough time since the last uncommanded stop
     436             :                     // (3 times the time between start attempts) then reset the retry count
     437           1 :                     crank_retry_ct = 0;
     438             :                 }
     439          15 :                 last_uncommanded_stop_ms = now;
     440             :             }
     441             :         }
     442             : #endif
     443        7620 :         break;
     444             :     }
     445             : 
     446       11510 :     if (!hal.util->get_soft_armed()) {
     447        3129 :         if (state == ICE_START_HEIGHT_DELAY) {
     448             :             // when disarmed we can be waiting for takeoff
     449           0 :             Vector3f pos;
     450           0 :             if (AP::ahrs().get_relative_position_NED_origin_float(pos)) {
     451             :                 // reset initial height while disarmed
     452           0 :                 initial_height = -pos.z;
     453             :             }
     454        3129 :         } else if (idle_percent <= 0 && !allow_throttle_while_disarmed()) {
     455             :             // force ignition off when disarmed
     456        1187 :             state = ICE_OFF;
     457             :         }
     458             :     }
     459             : 
     460             : #if AP_RPM_ENABLED
     461             :     // check against redline RPM
     462             :     float rpm_value;
     463       11510 :     if (rpm_instance > 0 && redline_rpm > 0 && AP::rpm()->get_rpm(rpm_instance-1, rpm_value)) {
     464             :         // update the filtered RPM value
     465         478 :         filtered_rpm_value =  _rpm_filter.apply(rpm_value);
     466         478 :         if (!redline.flag && filtered_rpm_value > redline_rpm) {
     467             :             // redline governor is off. rpm is too high. enable the governor
     468           1 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Engine: Above redline RPM");
     469           1 :             redline.flag = true;
     470         477 :         } else if (redline.flag && filtered_rpm_value < redline_rpm * 0.9f) {
     471             :             // redline governor is on. rpm is safely below. disable the governor
     472           1 :             redline.flag = false;
     473             :             // reset redline governor
     474           1 :             redline.throttle_percentage = 0.0f;
     475           1 :             redline.governor_integrator = 0.0f;
     476             :         }
     477             :     } else {
     478       11032 :         redline.flag = false;
     479             :     }
     480             : #endif // AP_RPM_ENABLED
     481             : 
     482             :     /* now set output channels */
     483       11510 :     switch (state) {
     484           0 :     case ICE_DISABLED:
     485           0 :         return;
     486        2240 :     case ICE_OFF:
     487        2240 :         set_ignition(false);
     488        2240 :         set_starter(false);
     489        2240 :         starter_start_time_ms = 0;
     490        2240 :         break;
     491             : 
     492        1036 :     case ICE_START_HEIGHT_DELAY:
     493             :     case ICE_START_DELAY:
     494        1036 :         set_ignition(true);
     495        1036 :         set_starter(false);
     496        1036 :         break;
     497             : 
     498         608 :     case ICE_STARTING:
     499         608 :         set_ignition(true);
     500         608 :         set_starter(true);
     501             : 
     502         608 :         if (starter_start_time_ms == 0) {
     503          20 :             starter_start_time_ms = now;
     504             :         }
     505         608 :         starter_last_run_ms = now;
     506         608 :         break;
     507             : 
     508        7626 :     case ICE_RUNNING:
     509        7626 :         set_ignition(true);
     510        7626 :         set_starter(false);
     511        7626 :         starter_start_time_ms = 0;
     512        7626 :         break;
     513             :     }
     514             : }
     515             : 
     516             : 
     517             : /*
     518             :   check for throttle override. This allows the ICE controller to force
     519             :   the correct starting throttle when starting the engine and maintain idle when disarmed
     520             : 
     521             :   base_throttle is the throttle before the disarmed override
     522             :   check. This allows for throttle control while disarmed
     523             :  */
     524     8377244 : bool AP_ICEngine::throttle_override(float &percentage, const float base_throttle)
     525             : {
     526     8377244 :     if (!enable) {
     527     8078727 :         return false;
     528             :     }
     529             : 
     530      298517 :     min_throttle_pct = idle_percent.get();
     531             :     #if AP_RPM_ENABLED
     532      298517 :         update_idle_governor(min_throttle_pct);
     533             :     #endif // AP_RPM_ENABLED
     534             : 
     535      298517 :     if (state == ICE_RUNNING &&
     536      188397 :         min_throttle_pct > 0 &&
     537       28690 :         min_throttle_pct < 100 &&
     538       28690 :         min_throttle_pct > percentage)
     539             :     {
     540       14830 :         percentage = min_throttle_pct;
     541       14830 :         if (allow_throttle_while_disarmed() && !hal.util->get_soft_armed()) {
     542       14829 :             percentage = MAX(percentage, base_throttle);
     543             :         }
     544       14830 :         return true;
     545             :     }
     546             : 
     547      283687 :     if (state == ICE_STARTING || state == ICE_START_DELAY) {
     548       41504 :         percentage = start_percent.get();
     549       41504 :         return true;
     550      242183 :     } else if (state != ICE_RUNNING && hal.util->get_soft_armed()) {
     551       37330 :         percentage = 0;
     552       37330 :         return true;
     553             :     }
     554             : 
     555             : #if AP_RPM_ENABLED
     556      204853 :     if (redline.flag && !option_set(Options::DISABLE_REDLINE_GOVERNOR)) {
     557             :         // limit the throttle from increasing above what the current output is
     558       13390 :         if (redline.throttle_percentage < 1.0f) {
     559           1 :             redline.throttle_percentage = percentage;
     560             :         }
     561       13390 :         if (percentage < redline.throttle_percentage - redline.governor_integrator) {
     562             :             // the throttle before the override is much lower than what the integrator is at
     563             :             // reset the integrator
     564           2 :             redline.governor_integrator = 0;
     565           2 :             redline.throttle_percentage = percentage;
     566       13388 :         } else if (percentage < redline.throttle_percentage) {
     567             :             // the throttle is below the integrator set point
     568             :             // remove the difference from the integrator
     569           0 :             redline.governor_integrator -= redline.throttle_percentage - percentage;
     570           0 :             redline.throttle_percentage = percentage;
     571       13388 :         } else if (filtered_rpm_value > redline_rpm) {
     572             :             // reduce the throttle if still over the redline RPM
     573        4432 :             const float redline_setpoint_step = idle_slew * AP::scheduler().get_loop_period_s();
     574        4432 :             redline.governor_integrator += redline_setpoint_step;
     575             :         }
     576       13390 :         percentage = redline.throttle_percentage - redline.governor_integrator;
     577       13390 :         return true;
     578             :     }
     579             : #endif // AP_RPM_ENABLED
     580             : 
     581             :     // if THROTTLE_WHILE_DISARMED is set then we use the base_throttle, allowing the pilot to control throttle while disarmed
     582      201086 :     if (allow_throttle_while_disarmed() && !hal.util->get_soft_armed() &&
     583        9623 :         base_throttle > percentage) {
     584         116 :         percentage = base_throttle;
     585         116 :         return true;
     586             :     }
     587             : 
     588      191347 :     return false;
     589             : }
     590             : 
     591             : /*
     592             :   handle DO_ENGINE_CONTROL messages via MAVLink or mission
     593             : */
     594         103 : bool AP_ICEngine::engine_control(float start_control, float cold_start, float height_delay, uint32_t flags)
     595             : {
     596         103 :     if (!enable) {
     597          82 :         return false;
     598             :     }
     599             : 
     600             :     // always update the start while disarmed flag
     601          21 :     allow_single_start_while_disarmed = (flags & ENGINE_CONTROL_OPTIONS_ALLOW_START_WHILE_DISARMED) != 0;
     602             : 
     603          21 :     if (start_control <= 0) {
     604          10 :         state = ICE_OFF;
     605          10 :         return true;
     606             :     }
     607          11 :     if (state == ICE_RUNNING || state == ICE_START_DELAY || state == ICE_STARTING) {
     608           2 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Engine: already running");
     609           2 :         return false;
     610             :     }
     611             : 
     612             :     // get starter control channel
     613           9 :     if (aux_pos == RC_Channel::AuxSwitchPos::LOW) {
     614           2 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Engine: start control disabled by aux function");
     615           2 :         return false;
     616             :     }
     617             : 
     618           7 :     if (height_delay > 0) {
     619           2 :         height_pending = true;
     620           2 :         initial_height = 0;
     621           2 :         height_required = height_delay;
     622           2 :         state = ICE_START_HEIGHT_DELAY;
     623           2 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Takeoff height set to %.1fm", (double)height_delay);
     624           2 :         return true;
     625             :     }
     626           5 :     state = ICE_STARTING;
     627           5 :     return true;
     628             : }
     629             : 
     630             : #if AP_RPM_ENABLED
     631             : /*
     632             :   Update low throttle limit to ensure steady idle for IC Engines
     633             :   return a new min_throttle value
     634             : */
     635      298517 : void AP_ICEngine::update_idle_governor(int8_t &min_throttle)
     636             : {
     637      298517 :     if (!enable) {
     638      293477 :         return;
     639             :     }
     640      298517 :     const int8_t min_throttle_base = min_throttle;
     641             : 
     642             :     // Initialize idle point to start_percent on the first run
     643             :     static bool idle_point_initialized = false;
     644      298517 :     if (!idle_point_initialized) {
     645      237116 :         idle_governor_integrator = start_percent.get();
     646      237116 :         idle_point_initialized = true;
     647             :     }
     648      298517 :     AP_RPM *ap_rpm = AP::rpm();
     649      298517 :     if (!ap_rpm || rpm_instance == 0 || !ap_rpm->healthy(rpm_instance-1)) {
     650       32711 :         return;
     651             :     }
     652             : 
     653             :     // Check to make sure we have an enabled IC Engine, EFI Instance and that the idle governor is enabled
     654      265806 :     if (get_state() != AP_ICEngine::ICE_RUNNING || idle_rpm < 0) {
     655      237116 :         idle_point_initialized = false;
     656      237116 :         return;
     657             :     }
     658             : 
     659             :     // get current RPM feedback
     660             :     float rpmv;
     661             : 
     662             :     // Double Check to make sure engine is really running
     663       28690 :     if (!ap_rpm->get_rpm(rpm_instance-1, rpmv) || rpmv < 1) {
     664             :         // Reset idle point to the default value when the engine is stopped
     665           0 :         idle_point_initialized = false;
     666           0 :         return;
     667             :     }
     668             : 
     669             :     // Override
     670       28690 :     min_throttle = roundf(idle_governor_integrator);
     671             : 
     672             :     // Calculate Error in system
     673       28690 :     int32_t error = idle_rpm - rpmv;
     674             : 
     675       28690 :     bool underspeed = error > 0;
     676             : 
     677             :     // Don't adjust idle point when we're within the deadband
     678       28690 :     if (abs(error) < idle_db) {
     679        9970 :         return;
     680             :     }
     681             : 
     682             :     // Don't adjust idle point if the commanded throttle is above the
     683             :     // current idle throttle setpoint and the RPM is above the idle
     684             :     // RPM setpoint (Normal flight)
     685       18720 :     if (SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) > min_throttle && !underspeed) {
     686       13680 :         return;
     687             :     }
     688             : 
     689             :     // Calculate the change per loop to achieve the desired slew rate of 1 percent per second
     690        5040 :     static const float idle_setpoint_step = idle_slew * AP::scheduler().get_loop_period_s();
     691             : 
     692             :     // Update Integrator
     693        5040 :     if (underspeed) {
     694        4848 :         idle_governor_integrator += idle_setpoint_step;
     695             :     } else {
     696         192 :         idle_governor_integrator -= idle_setpoint_step;
     697             :     }
     698             : 
     699        5040 :     idle_governor_integrator = constrain_float(idle_governor_integrator, min_throttle_base, 40.0f);
     700             : 
     701        5040 :     min_throttle = roundf(idle_governor_integrator);
     702             : }
     703             : #endif // AP_RPM_ENABLED
     704             : 
     705             : /*
     706             :   set ignition state
     707             :  */
     708       11510 : void AP_ICEngine::set_ignition(bool on)
     709             : {
     710       11510 :     SRV_Channels::set_output_scaled(SRV_Channel::k_ignition, on ? 1.0 : 0.0);
     711             : 
     712             : #if AP_RELAY_ENABLED
     713       11510 :     AP_Relay *relay = AP::relay();
     714       11510 :     if (relay != nullptr) {
     715       11510 :         relay->set(AP_Relay_Params::FUNCTION::IGNITION, on);
     716             :     }
     717             : #endif // AP_RELAY_ENABLED
     718             : 
     719       11510 : }
     720             : 
     721             : /*
     722             :   set starter state
     723             :  */
     724       11510 : void AP_ICEngine::set_starter(bool on)
     725             : {
     726       11510 :     SRV_Channels::set_output_scaled(SRV_Channel::k_starter, on ? 1.0 : 0.0);
     727             : 
     728             : #if AP_RELAY_ENABLED
     729       11510 :     AP_Relay *relay = AP::relay();
     730       11510 :     if (relay != nullptr) {
     731       11510 :         relay->set(AP_Relay_Params::FUNCTION::ICE_STARTER, on);
     732             :     }
     733             : #endif // AP_RELAY_ENABLED
     734       11510 : }
     735             : 
     736             : 
     737     8586710 : bool AP_ICEngine::allow_throttle_while_disarmed() const
     738             : {
     739     8685688 :     return option_set(Options::THROTTLE_WHILE_DISARMED) &&
     740     8685688 :         hal.util->safety_switch_state() != AP_HAL::Util::SAFETY_DISARMED;
     741             : }
     742             : 
     743             : #if AP_RELAY_ENABLED
     744         692 : bool AP_ICEngine::get_legacy_ignition_relay_index(int8_t &num) 
     745             : {
     746             :     // PARAMETER_CONVERSION - Added: Dec-2023
     747         692 :     if (!enable || !AP_Param::get_param_by_index(this, 18, AP_PARAM_INT8, &num)) {
     748         692 :         return false;
     749             :     }
     750             :     // convert to zero indexed
     751           0 :     num -= 1;
     752           0 :     return true;
     753             : }
     754             : #endif
     755             : 
     756             : // singleton instance. Should only ever be set in the constructor.
     757             : AP_ICEngine *AP_ICEngine::_singleton;
     758             : namespace AP {
     759        2103 : AP_ICEngine *ice() {
     760        2103 :         return AP_ICEngine::get_singleton();
     761             :     }
     762             : }
     763             : 
     764             : #endif  // AP_ICENGINE_ENABLED

Generated by: LCOV version 1.14