LCOV - code coverage report
Current view: top level - SRV_Channel - SRV_Channel.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 77 103 74.8 %
Date: 2026-09-09 11:02:08 Functions: 12 15 80.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             :   SRV_Channel.cpp - object to separate input and output channel
      17             :   ranges, trim and reversal
      18             :  */
      19             : 
      20             : #include <AP_HAL/AP_HAL.h>
      21             : #include <AP_Math/AP_Math.h>
      22             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      23             : #include "SRV_Channel.h"
      24             : 
      25             : extern const AP_HAL::HAL& hal;
      26             : 
      27             : SRV_Channel::servo_mask_t SRV_Channel::have_pwm_mask;
      28             : 
      29             : const AP_Param::GroupInfo SRV_Channel::var_info[] = {
      30             :     // @Param: MIN
      31             :     // @DisplayName: Minimum PWM
      32             :     // @Description: minimum PWM pulse width in microseconds. Typically 1000 is lower limit, 1500 is neutral and 2000 is upper limit.
      33             :     // @Units: PWM
      34             :     // @Range: 800 2200
      35             :     // @Increment: 1
      36             :     // @User: Standard
      37             :     AP_GROUPINFO("MIN",  1, SRV_Channel, servo_min, 1100),
      38             : 
      39             :     // @Param: MAX
      40             :     // @DisplayName: Maximum PWM
      41             :     // @Description: maximum PWM pulse width in microseconds. Typically 1000 is lower limit, 1500 is neutral and 2000 is upper limit.
      42             :     // @Units: PWM
      43             :     // @Range: 800 2200
      44             :     // @Increment: 1
      45             :     // @User: Standard
      46             :     AP_GROUPINFO("MAX",  2, SRV_Channel, servo_max, 1900),
      47             : 
      48             :     // @Param: TRIM
      49             :     // @DisplayName: Trim PWM
      50             :     // @Description: Trim PWM pulse width in microseconds. Typically 1000 is lower limit, 1500 is neutral and 2000 is upper limit.
      51             :     // @Units: PWM
      52             :     // @Range: 800 2200
      53             :     // @Increment: 1
      54             :     // @User: Standard
      55             :     AP_GROUPINFO("TRIM",  3, SRV_Channel, servo_trim, 1500),
      56             : 
      57             :     // @Param: REVERSED
      58             :     // @DisplayName: Servo reverse
      59             :     // @Description: Reverse servo operation. Set to 0 for normal operation. Set to 1 to reverse this output channel.
      60             :     // @Values: 0:Normal,1:Reversed
      61             :     // @User: Standard
      62             :     AP_GROUPINFO("REVERSED",  4, SRV_Channel, reversed, 0),
      63             : 
      64             :     // @Param: FUNCTION
      65             :     // @DisplayName: Servo output function
      66             :     // @Description: Function assigned to this servo. Setting this to Disabled(0) will setup this output for control by auto missions or MAVLink servo set commands. any other value will enable the corresponding function
      67             :     // @SortValues: AlphabeticalZeroAtTop
      68             :     // @Values: -1:GPIO
      69             :     // @Values{Plane, Copter, Rover, Sub}: -1:GPIO
      70             :     // @Values: 0:Disabled
      71             :     // @Values{Plane, Copter, Rover, Sub}: 0:Disabled
      72             :     // @Values: 1:RCPassThru
      73             :     // @Values{Plane, Copter, Rover, Sub}: 1:RCPassThru
      74             :     // @Values: 2:Flap, 3:FlapAuto
      75             :     // @Values{Plane}: 2:Flap,3:FlapAuto
      76             :     // @Values: 4:Aileron
      77             :     // @Values{Plane}: 4:Aileron
      78             :     // @Values: 6:Mount1Yaw,7:Mount1Pitch,8:Mount1Roll,9:Mount1Retract
      79             :     // @Values{Plane, Copter, Rover, Sub}: 6:Mount1Yaw,7:Mount1Pitch,8:Mount1Roll,9:Mount1Retract
      80             :     // @Values: 10:CameraTrigger
      81             :     // @Values{Plane, Copter, Rover}: 10:CameraTrigger
      82             :     // @Values: 12:Mount2Yaw,13:Mount2Pitch,14:Mount2Roll,15:Mount2Retract
      83             :     // @Values{Plane, Copter, Rover}: 12:Mount2Yaw,13:Mount2Pitch,14:Mount2Roll,15:Mount2Retract
      84             :     // @Values: 16:DifferentialSpoilerLeft1,17:DifferentialSpoilerRight1
      85             :     // @Values{Plane}: 16:DifferentialSpoilerLeft1,17:DifferentialSpoilerRight1
      86             :     // @Values: 19:Elevator
      87             :     // @Values{Plane}: 19:Elevator
      88             :     // @Values: 21:Rudder
      89             :     // @Values{Plane}: 21:Rudder
      90             :     // @Values: 22:SprayerPump,23:SprayerSpinner
      91             :     // @Values{Plane, Copter, Rover}: 22:SprayerPump,23:SprayerSpinner
      92             :     // @Values: 24:FlaperonLeft,25:FlaperonRight
      93             :     // @Values{Plane}: 24:FlaperonLeft,25:FlaperonRight
      94             :     // @Values: 26:GroundSteering
      95             :     // @Values{Plane, Rover}: 26:GroundSteering
      96             :     // @Values: 27:Parachute
      97             :     // @Values{Plane, Copter}: 27:Parachute
      98             :     // @Values: 28:Gripper
      99             :     // @Values{Plane, Copter, Rover, Sub}: 28:Gripper
     100             :     // @Values: 29:LandingGear
     101             :     // @Values{Plane, Copter}: 29:LandingGear
     102             :     // @Values: 30:EngineRunEnable
     103             :     // @Values{Plane, Copter}: 30:EngineRunEnable
     104             :     // @Values: 31:HeliRSC,32:HeliTailRSC
     105             :     // @Values{Copter}: 31:HeliRSC,32:HeliTailRSC
     106             :     // @Values: 33:Motor1,34:Motor2,35:Motor3,36:Motor4,37:Motor5,38:Motor6,39:Motor7,40:Motor8
     107             :     // @Values{Plane}: 33:Motor1,34:Motor2,35:Motor3,36:Motor4,37:Motor5,38:Motor6,39:Motor7/TailTiltServo,40:Motor8
     108             :     // @Values{Copter, Sub}: 33:Motor1,34:Motor2,35:Motor3,36:Motor4,37:Motor5,38:Motor6,39:Motor7,40:Motor8
     109             :     // @Values{Rover}: 33:Motor1,34:Motor2,35:Motor3,36:Motor4
     110             :     // @Values: 41:TiltMotorsFront,45:TiltMotorsRear,46:TiltMotorRearLeft,47:TiltMotorRearRight
     111             :     // @Values{Plane}: 41:TiltMotorsFront,45:TiltMotorsRear,46:TiltMotorRearLeft,47:TiltMotorRearRight
     112             :     // @Values: 51:RCIN1,52:RCIN2,53:RCIN3,54:RCIN4,55:RCIN5,56:RCIN6,57:RCIN7,58:RCIN8,59:RCIN9,60:RCIN10,61:RCIN11,62:RCIN12,63:RCIN13,64:RCIN14,65:RCIN15,66:RCIN16
     113             :     // @Values{Plane, Copter, Rover}: 51:RCIN1,52:RCIN2,53:RCIN3,54:RCIN4,55:RCIN5,56:RCIN6,57:RCIN7,58:RCIN8,59:RCIN9,60:RCIN10,61:RCIN11,62:RCIN12,63:RCIN13,64:RCIN14,65:RCIN15,66:RCIN16
     114             :     // @Values{Sub}: 51:RCIN1/Pitch,52:RCIN2/Roll,53:RCIN3/HeaveVertical,54:RCIN4/YawTurn,55:RCIN5/SurgeForward,56:RCIN6/SwayLateral,57:RCIN7/CameraPan,58:RCIN8/CameraTilt,59:RCIN9,60:RCIN10,61:RCIN11,62:RCIN12,63:RCIN13,64:RCIN14,65:RCIN15,66:RCIN16
     115             :     // @Values: 67:Ignition,69:Starter
     116             :     // @Values{Plane}: 67:Ignition,69:Starter
     117             :     // @Values: 70:Throttle
     118             :     // @Values{Plane, Rover}: 70:Throttle
     119             :     // @Values: 71:TrackerYaw,72:TrackerPitch
     120             :     // @Values: 73:ThrottleLeft,74:ThrottleRight
     121             :     // @Values{Plane, Copter, Rover}: 73:ThrottleLeft,74:ThrottleRight
     122             :     // @Values: 75:TiltMotorFrontLeft,76:TiltMotorFrontRight
     123             :     // @Values{Plane, Copter}: 75:TiltMotorFrontLeft,76:TiltMotorFrontRight
     124             :     // @Values: 77:ElevonLeft,78:ElevonRight
     125             :     // @Values{Plane}: 77:ElevonLeft,78:ElevonRight
     126             :     // @Values: 79:VTailLeft,80:VTailRight
     127             :     // @Values{Plane}: 79:VTailLeft,80:VTailRight
     128             :     // @Values: 81:BoostThrottle
     129             :     // @Values{Copter}: 81:BoostThrottle
     130             :     // @Values: 82:Motor9,83:Motor10,84:Motor11,85:Motor12
     131             :     // @Values{Plane, Copter, Sub}: 82:Motor9,83:Motor10,84:Motor11,85:Motor12
     132             :     // @Values: 86:DifferentialSpoilerLeft2,87:DifferentialSpoilerRight2
     133             :     // @Values{Plane}: 86:DifferentialSpoilerLeft2,87:DifferentialSpoilerRight2
     134             :     // @Values: 88:Winch
     135             :     // @Values{Copter, Rover, Sub}: 88:Winch
     136             :     // @Values: 89:Main Sail
     137             :     // @Values{Rover}: 89:Main Sail
     138             :     // @Values: 90:CameraISO,91:CameraAperture,92:CameraFocus,93:CameraShutterSpeed
     139             :     // @Values{Plane, Copter, Rover, Sub}: 90:CameraISO,91:CameraAperture,92:CameraFocus,93:CameraShutterSpeed
     140             :     // @Values: 94:Script1,95:Script2,96:Script3,97:Script4,98:Script5,99:Script6,100:Script7,101:Script8,102:Script9,103:Script10,104:Script11,105:Script12,106:Script13,107:Script14,108:Script15,109:Script16
     141             :     // @Values{Plane, Copter, Rover, Sub}: 94:Script1,95:Script2,96:Script3,97:Script4,98:Script5,99:Script6,100:Script7,101:Script8,102:Script9,103:Script10,104:Script11,105:Script12,106:Script13,107:Script14,108:Script15,109:Script16
     142             :     // @Values{Plane}: 110:Airbrakes
     143             :     // @Values: 120:NeoPixel1,121:NeoPixel2,122:NeoPixel3,123:NeoPixel4
     144             :     // @Values{Plane, Copter, Rover, Sub}: 120:NeoPixel1,121:NeoPixel2,122:NeoPixel3,123:NeoPixel4
     145             :     // @Values: 124:RateRoll,125:RatePitch,126:RateThrust,127:RateYaw
     146             :     // @Values{Plane, Copter}: 124:RateRoll,125:RatePitch,126:RateThrust,127:RateYaw
     147             :     // @Values: 128:WingSailElevator
     148             :     // @Values{Rover}: 128:WingSailElevator
     149             :     // @Values: 129:ProfiLED1,130:ProfiLED2,131:ProfiLED3,132:ProfiLEDClock
     150             :     // @Values{Plane, Copter, Rover}: 129:ProfiLED1,130:ProfiLED2,131:ProfiLED3,132:ProfiLEDClock
     151             :     // @Values: 133:Winch Clutch
     152             :     // @Values{Copter, Rover}: 133:Winch Clutch
     153             :     // @Values: 134:SERVOn_MIN,135:SERVOn_TRIM,136:SERVOn_MAX
     154             :     // @Values{Plane, Copter, Rover, Sub}: 134:SERVOn_MIN,135:SERVOn_TRIM,136:SERVOn_MAX
     155             :     // @Values: 137:SailMastRotation
     156             :     // @Values{Rover}: 137:SailMastRotation
     157             :     // @Values: 138:Alarm,139:Alarm Inverted
     158             :     // @Values{Plane, Copter, Rover}: 138:Alarm,139:Alarm Inverted
     159             :     // @Values: 140:RCIN1Scaled,141:RCIN2Scaled,142:RCIN3Scaled,143:RCIN4Scaled,144:RCIN5Scaled,145:RCIN6Scaled,146:RCIN7Scaled,147:RCIN8Scaled,148:RCIN9Scaled,149:RCIN10Scaled,150:RCIN11Scaled,151:RCIN12Scaled,152:RCIN13Scaled,153:RCIN14Scaled,154:RCIN15Scaled,155:RCIN16Scaled
     160             :     // @Values{Plane, Copter, Rover}: 140:RCIN1Scaled,141:RCIN2Scaled,142:RCIN3Scaled,143:RCIN4Scaled,144:RCIN5Scaled,145:RCIN6Scaled,146:RCIN7Scaled,147:RCIN8Scaled,148:RCIN9Scaled,149:RCIN10Scaled,150:RCIN11Scaled,151:RCIN12Scaled,152:RCIN13Scaled,153:RCIN14Scaled,154:RCIN15Scaled,155:RCIN16Scaled
     161             :     // @Values{Plane, Copter}: 160:Motor13,161:Motor14,162:Motor15,163:Motor16
     162             :     // @Values{Plane, Copter}: 164:Motor17,165:Motor18,166:Motor19,167:Motor20
     163             :     // @Values{Plane, Copter}: 168:Motor21,169:Motor22,170:Motor23,171:Motor24
     164             :     // @Values{Plane, Copter}: 172:Motor25,173:Motor26,174:Motor27,175:Motor28
     165             :     // @Values{Plane, Copter}: 176:Motor29,177:Motor30,178:Motor31,179:Motor32
     166             :     // @Values: 180:CameraZoom
     167             :     // @Values{Sub}: 181:Lights1,182:Lights2
     168             :     // @Values{Sub}: 183:VideoSwitch
     169             :     // @Values{Sub}: 184:Actuator1,185:Actuator2,186:Actuator3,187:Actuator4,188:Actuator5,189:Actuator6
     170             :     // @User: Standard
     171             :     // @RebootRequired: True
     172             :     AP_GROUPINFO("FUNCTION",  5, SRV_Channel, function, 0),
     173             : 
     174             :     AP_GROUPEND
     175             : };
     176             : 
     177             : 
     178       67008 : SRV_Channel::SRV_Channel(void)
     179             : {
     180       67008 :     AP_Param::setup_object_defaults(this, var_info);
     181             :     // start with all pwm at zero
     182       67008 :     have_pwm_mask = ~uint32_t(0);
     183       67008 : }
     184             : 
     185             : // convert a 0..range_max to a pwm
     186    15332303 : uint16_t SRV_Channel::pwm_from_range(float scaled_value) const
     187             : {
     188    15332303 :     if (servo_max <= servo_min || high_out == 0) {
     189           0 :         return servo_min;
     190             :     }
     191    15332303 :     scaled_value = constrain_float(scaled_value, 0, high_out);
     192    15332303 :     if (reversed) {
     193      299118 :         scaled_value = high_out - scaled_value;
     194             :     }
     195    15332303 :     return servo_min + uint16_t( (scaled_value * (float)(servo_max - servo_min)) / (float)high_out );
     196             : }
     197             : 
     198             : // convert a -angle_max..angle_max to a pwm
     199    31064465 : uint16_t SRV_Channel::pwm_from_angle(float scaled_value) const
     200             : {
     201    31064465 :     if (high_out == 0) {
     202         426 :         return servo_trim;
     203             :     }
     204    31064039 :     if (reversed) {
     205     3537952 :         scaled_value = -scaled_value;
     206             :     }
     207    31064039 :     scaled_value = constrain_float(scaled_value, -high_out, high_out);
     208    31064039 :     if (scaled_value > 0) {
     209    13293276 :         return servo_trim + uint16_t( (scaled_value * (float)(servo_max - servo_trim)) / (float)high_out);
     210             :     } else {
     211    17770763 :         return servo_trim - uint16_t( (-scaled_value * (float)(servo_trim - servo_min)) / (float)high_out);
     212             :     }
     213             : }
     214             : 
     215    46386012 : uint16_t SRV_Channel::pwm_from_scaled_value(float scaled_value) const
     216             : {
     217    46386012 :     if (type_angle) {
     218    31053709 :         return pwm_from_angle(scaled_value);
     219             :     } else {
     220    15332303 :         return pwm_from_range(scaled_value);
     221             :     }
     222             : }
     223             : 
     224   671375967 : void SRV_Channel::calc_pwm(float output_scaled)
     225             : {
     226   671375967 :     if (have_pwm_mask & (1U<<ch_num)) {
     227             :         // Note that this allows a set_output_pwm call to override E-Stop!!
     228             :         // tricky to fix because we would endup E-stoping to individual SEROVx_MIN not MOT_PWM_MIN on copter
     229   624989955 :         return;
     230             :     }
     231             : 
     232             :     // check for E - stop
     233    46386012 :     bool force = false;
     234    46386012 :     if (SRV_Channel::should_e_stop(get_function()) && SRV_Channels::emergency_stop) {
     235        1160 :         output_scaled = 0.0;
     236        1160 :         force = true;
     237             :     }
     238             : 
     239    46386012 :     if (!force && override_active) {
     240             :         // don't overwrite a override
     241           0 :         return;
     242             :     }
     243             : 
     244    46386012 :     output_pwm = pwm_from_scaled_value(output_scaled);
     245             : }
     246             : 
     247   166042225 : void SRV_Channel::set_output_pwm(uint16_t pwm, bool force)
     248             : {
     249   166042225 :     if (!override_active || force) {
     250   166042225 :         output_pwm = pwm;
     251   166042225 :         have_pwm_mask |= (1U<<ch_num);
     252             :     }
     253   166042225 : }
     254             : 
     255             : // set normalised output from -1 to 1, assuming 0 at mid point of servo_min/servo_max
     256           0 : void SRV_Channel::set_output_norm(float value)
     257             : {
     258             :     // convert normalised value to pwm
     259           0 :     set_output_pwm(pwm_from_scaled_value(value * high_out));
     260           0 : }
     261             : 
     262             : // set angular range of scaled output
     263      435285 : void SRV_Channel::set_angle(int16_t angle)
     264             : {
     265      435285 :     type_angle = true;
     266      435285 :     high_out = angle;    
     267      435285 :     type_setup = true;
     268      435285 : }
     269             : 
     270             : // set range of scaled output
     271     2580576 : void SRV_Channel::set_range(uint16_t high)
     272             : {
     273     2580576 :     type_angle = false;
     274     2580576 :     high_out = high;
     275     2580576 :     type_setup = true;
     276     2580576 : }
     277             : 
     278             : /*
     279             :   get normalised output from -1 to 1, assuming 0 at mid point of servo_min/servo_max
     280             :  */
     281      104138 : float SRV_Channel::get_output_norm(void)
     282             : {
     283      104138 :     uint16_t mid = (servo_max + servo_min) / 2;
     284             :     float ret;
     285      104138 :     if (mid <= servo_min) {
     286           0 :         return 0;
     287             :     }
     288      104138 :     if (output_pwm < mid) {
     289       52141 :         ret = (float)(output_pwm - mid) / (float)(mid - servo_min);
     290       51997 :     } else if (output_pwm > mid) {
     291         723 :         ret = (float)(output_pwm - mid) / (float)(servo_max  - mid);
     292             :     } else {
     293       51274 :         ret = 0;
     294             :     }
     295      104138 :     if (get_reversed()) {
     296           0 :         ret = -ret;
     297             :     }
     298      104138 :     return ret;
     299             : }
     300             : 
     301      436907 : uint16_t SRV_Channel::get_limit_pwm(Limit limit) const
     302             : {
     303      436907 :     switch (limit) {
     304      427347 :     case Limit::TRIM:
     305      427347 :         return servo_trim;
     306         665 :     case Limit::MIN:
     307         665 :         return reversed?servo_max:servo_min;
     308          18 :     case Limit::MAX:
     309          18 :         return reversed?servo_min:servo_max;
     310        8877 :     case Limit::ZERO_PWM:
     311        8877 :         return 0;
     312             :     }
     313           0 :     return 0;
     314             : }
     315             : 
     316             : // return true if function is for a multicopter motor
     317       22112 : bool SRV_Channel::is_motor(SRV_Channel::Function function)
     318             : {
     319       22112 :     return ((function >= SRV_Channel::k_motor1 && function <= SRV_Channel::k_motor8) ||
     320       44224 :             (function >= SRV_Channel::k_motor9 && function <= SRV_Channel::k_motor12) ||
     321       22112 :             (function >= SRV_Channel::k_motor13 && function <= SRV_Channel::k_motor32));
     322             : }
     323             : 
     324             : // return true if function is for anything that should be stopped in a e-stop situation, ie is dangerous
     325    46386012 : bool SRV_Channel::should_e_stop(SRV_Channel::Function function)
     326             : {
     327    46386012 :     switch (function) {
     328    16459368 :     case Function::k_heli_rsc:  
     329             :     case Function::k_heli_tail_rsc:
     330             :     case Function::k_motor1:
     331             :     case Function::k_motor2:
     332             :     case Function::k_motor3:
     333             :     case Function::k_motor4:
     334             :     case Function::k_motor5:
     335             :     case Function::k_motor6:
     336             :     case Function::k_motor7:
     337             :     case Function::k_motor8:
     338             :     case Function::k_starter:
     339             :     case Function::k_throttle:
     340             :     case Function::k_throttleLeft:
     341             :     case Function::k_throttleRight:
     342             :     case Function::k_boost_throttle:
     343             :     case Function::k_motor9:
     344             :     case Function::k_motor10:
     345             :     case Function::k_motor11:
     346             :     case Function::k_motor12:
     347             :     case Function::k_motor13 ... Function::k_motor32:
     348             :     case Function::k_engine_run_enable:
     349    16459368 :         return true;
     350    29926644 :     default:
     351    29926644 :         return false;
     352             :     }
     353             :     return false;
     354             : }
     355             : 
     356             : // return true if function is for a control surface
     357           0 : bool SRV_Channel::is_control_surface(SRV_Channel::Function function)
     358             : {
     359           0 :     switch (function) {
     360           0 :     case Function::k_flap:
     361             :     case Function::k_flap_auto:
     362             :     case Function::k_aileron:
     363             :     case Function::k_dspoilerLeft1:
     364             :     case Function::k_dspoilerLeft2:
     365             :     case Function::k_dspoilerRight1:
     366             :     case Function::k_dspoilerRight2:
     367             :     case Function::k_elevator:
     368             :     case Function::k_rudder:
     369             :     case Function::k_flaperon_left:
     370             :     case Function::k_flaperon_right:
     371             :     case Function::k_elevon_left:
     372             :     case Function::k_elevon_right:
     373             :     case Function::k_vtail_left:
     374             :     case Function::k_vtail_right:
     375             :     case Function::k_airbrake:
     376           0 :         return true;
     377             : 
     378           0 :     default:
     379           0 :         return false;
     380             :     }
     381             : 
     382             :     return false;
     383             : }
     384             : 
     385             : // return the motor number of a channel, or -1 if not a motor
     386             : // return 0 for first motor
     387           0 : int8_t SRV_Channel::get_motor_num(void) const
     388             : {
     389           0 :     const auto k_function = get_function();
     390           0 :     switch (k_function) {
     391           0 :     case k_motor1 ... k_motor8:
     392           0 :         return int8_t(uint16_t(k_function) - k_motor1);
     393           0 :     case k_motor9 ... k_motor12:
     394           0 :         return 8 + int8_t(uint16_t(k_function) - k_motor9);
     395           0 :     case k_motor13 ... k_motor32:
     396           0 :         return 12 + int8_t(uint16_t(k_function) - k_motor13);
     397           0 :     default:
     398           0 :         break;
     399             :     }
     400           0 :     return -1;
     401             : }

Generated by: LCOV version 1.14