LCOV - code coverage report
Current view: top level - SITL - SIM_Frame.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 176 199 88.4 %
Date: 2026-09-09 11:02:08 Functions: 9 10 90.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             :   multicopter frame simulator class
      17             : */
      18             : 
      19             : #include "SIM_Frame.h"
      20             : #include <AP_Motors/AP_Motors.h>
      21             : #include <AP_Baro/AP_Baro.h>
      22             : #include <AP_Filesystem/AP_Filesystem.h>
      23             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      24             : #include "SIM_Aircraft.h"
      25             : 
      26             : #include "SIM_config.h"
      27             : 
      28             : #include <stdio.h>
      29             : #include <sys/stat.h>
      30             : 
      31             : using namespace SITL;
      32             : 
      33             : #if AP_SIM_ENABLED
      34             : // default to no bluff body or momentum drag on planes, where the
      35             : // plane model handles drag
      36             : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
      37             : #define SIM_FRAME_BBDRAG_DEFAULT 0.0
      38             : #define SIM_FRAME_MDRAG_DEFAULT  0.0
      39             : #else
      40             : #define SIM_FRAME_BBDRAG_DEFAULT 1.0
      41             : #define SIM_FRAME_MDRAG_DEFAULT  0.2
      42             : #endif
      43             : 
      44             : // user settable multicopter model parameters. Defaults can be
      45             : // overridden by a json model file
      46             : const AP_Param::GroupInfo Frame::var_info[] = {
      47             :     // @Param: MASS
      48             :     // @DisplayName: model mass
      49             :     // @Description: mass of the multicopter model
      50             :     // @Units: kg
      51             :     AP_GROUPINFO("MASS", 1, Frame, model.mass, 3.0),
      52             : 
      53             :     // @Param: DIAG_SZ
      54             :     // @DisplayName: model diagonal size
      55             :     // @Description: diagonal motor to motor distance of the model
      56             :     // @Units: m
      57             :     AP_GROUPINFO("DIAG_SZ", 2, Frame, model.diagonal_size, 0.35),
      58             : 
      59             :     // @Param: REF_SPD
      60             :     // @DisplayName: reference speed
      61             :     // @Description: airspeed in the drag reference test, at a fixed lean angle
      62             :     // @Units: m/s
      63             :     AP_GROUPINFO("REF_SPD", 3, Frame, model.refSpd, 15.08),
      64             : 
      65             :     // @Param: REF_ANG
      66             :     // @DisplayName: reference angle
      67             :     // @Description: lean angle in the drag reference test
      68             :     // @Units: deg
      69             :     AP_GROUPINFO("REF_ANG", 4, Frame, model.refAngle, 45),
      70             : 
      71             :     // @Param: REF_VOLT
      72             :     // @DisplayName: reference voltage
      73             :     // @Description: battery voltage in the drag reference test
      74             :     // @Units: V
      75             :     AP_GROUPINFO("REF_VOLT", 5, Frame, model.refVoltage, 12.09),
      76             : 
      77             :     // @Param: REF_AMP
      78             :     // @DisplayName: reference current
      79             :     // @Description: battery current in the drag reference test
      80             :     // @Units: A
      81             :     AP_GROUPINFO("REF_AMP", 6, Frame, model.refCurrent, 29.3),
      82             : 
      83             :     // @Param: REF_ALT
      84             :     // @DisplayName: reference altitude
      85             :     // @Description: altitude AMSL of the drag reference test
      86             :     // @Units: m
      87             :     AP_GROUPINFO("REF_ALT", 7, Frame, model.refAlt, 593),
      88             : 
      89             :     // @Param: BAT_RES
      90             :     // @DisplayName: battery resistance
      91             :     // @Description: battery resistance
      92             :     // @Units: Ohm
      93             :     AP_GROUPINFO("BAT_RES", 8, Frame, model.refBatRes, 0.01),
      94             : 
      95             :     // @Param: BAT_VOLT
      96             :     // @DisplayName: battery full voltage
      97             :     // @Description: full pack battery voltage
      98             :     // @Units: V
      99             :     AP_GROUPINFO("BAT_VOLT", 9, Frame, model.maxVoltage, 12.6),
     100             : 
     101             :     // @Param: BAT_CAP
     102             :     // @DisplayName: battery capacity
     103             :     // @Description: battery capacity, zero for unlimited
     104             :     // @Units: Ah
     105             :     AP_GROUPINFO("BAT_CAP", 10, Frame, model.battCapacityAh, 0),
     106             : 
     107             :     // @Param: HOVR_THR
     108             :     // @DisplayName: hover throttle
     109             :     // @Description: throttle output (CTUN.ThO) at hover at the reference altitude
     110             :     // @Range: 0.05 1
     111             :     AP_GROUPINFO("HOVR_THR", 11, Frame, model.hoverThrOut, 0.39),
     112             : 
     113             :     // @Param: EXPO
     114             :     // @DisplayName: thrust expo
     115             :     // @Description: motor thrust expo, matching MOT_THST_EXPO
     116             :     AP_GROUPINFO("EXPO", 12, Frame, model.propExpo, 0.65),
     117             : 
     118             :     // @Param: ROT_RATE
     119             :     // @DisplayName: rotation rate
     120             :     // @Description: terminal yaw rotation rate
     121             :     // @Units: deg/s
     122             :     AP_GROUPINFO("ROT_RATE", 13, Frame, model.refRotRate, 120),
     123             : 
     124             :     // @Param: PWM_MIN
     125             :     // @DisplayName: minimum PWM
     126             :     // @Description: minimum motor PWM in the reference test, matching MOT_PWM_MIN
     127             :     // @Units: PWM
     128             :     AP_GROUPINFO("PWM_MIN", 14, Frame, model.pwmMin, 1000),
     129             : 
     130             :     // @Param: PWM_MAX
     131             :     // @DisplayName: maximum PWM
     132             :     // @Description: maximum motor PWM in the reference test, matching MOT_PWM_MAX
     133             :     // @Units: PWM
     134             :     AP_GROUPINFO("PWM_MAX", 15, Frame, model.pwmMax, 2000),
     135             : 
     136             :     // @Param: SPIN_MIN
     137             :     // @DisplayName: motor spin minimum
     138             :     // @Description: minimum motor spin fraction, matching MOT_SPIN_MIN
     139             :     // @Range: 0 1
     140             :     AP_GROUPINFO("SPIN_MIN", 16, Frame, model.spin_min, 0.15),
     141             : 
     142             :     // @Param: SPIN_MAX
     143             :     // @DisplayName: motor spin maximum
     144             :     // @Description: maximum motor spin fraction, matching MOT_SPIN_MAX
     145             :     // @Range: 0 1
     146             :     AP_GROUPINFO("SPIN_MAX", 17, Frame, model.spin_max, 0.95),
     147             : 
     148             :     // @Param: SLEW_MAX
     149             :     // @DisplayName: motor slew rate
     150             :     // @Description: maximum motor slew rate in throttle fraction per second
     151             :     // @Units: 1/s
     152             :     AP_GROUPINFO("SLEW_MAX", 18, Frame, model.slew_max, 150),
     153             : 
     154             :     // @Param: DISCAREA
     155             :     // @DisplayName: rotor disc area
     156             :     // @Description: total rotor disc area. Coaxial rotors count as one rotor only
     157             :     // @Units: m.m
     158             :     AP_GROUPINFO("DISCAREA", 19, Frame, model.disc_area, 0.385),
     159             : 
     160             :     // @Param: MDRAG
     161             :     // @DisplayName: momentum drag coefficient
     162             :     // @Description: momentum drag coefficient of the rotors. Defaults to zero on planes where the plane model handles drag
     163             :     AP_GROUPINFO("MDRAG", 20, Frame, model.mdrag_coef, SIM_FRAME_MDRAG_DEFAULT),
     164             : 
     165             :     // @Param: BBDRAG
     166             :     // @DisplayName: bluff body drag coefficient
     167             :     // @Description: scaling of the bluff body drag derived from the reference test, zero for no bluff body drag. Defaults to zero on planes where the plane model handles drag
     168             :     AP_GROUPINFO("BBDRAG", 21, Frame, model.bbdrag_coef, SIM_FRAME_BBDRAG_DEFAULT),
     169             : 
     170             :     AP_GROUPEND
     171             : };
     172             : #endif // AP_SIM_ENABLED
     173             : 
     174             : static Motor quad_plus_motors[] =
     175             : {
     176             :     Motor(AP_MOTORS_MOT_1,  90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2),
     177             :     Motor(AP_MOTORS_MOT_2, -90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 4),
     178             :     Motor(AP_MOTORS_MOT_3,   0, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  1),
     179             :     Motor(AP_MOTORS_MOT_4, 180, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3),
     180             : };
     181             : 
     182             : static Motor quad_x_motors[] =
     183             : {
     184             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     185             :     Motor(AP_MOTORS_MOT_2, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     186             :     Motor(AP_MOTORS_MOT_3,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     187             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     188             : };
     189             : 
     190             : // motor order to match betaflight conventions
     191             : // See: https://fpvfrenzy.com/betaflight-motor-order/
     192             : static Motor quad_bf_x_motors[] =
     193             : {
     194             :     Motor(AP_MOTORS_MOT_1,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW, 2),
     195             :     Motor(AP_MOTORS_MOT_2,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,1),
     196             :     Motor(AP_MOTORS_MOT_3, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,3),
     197             :     Motor(AP_MOTORS_MOT_4,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW, 4),
     198             : };
     199             : 
     200             : // motor order to match betaflight conventions, reversed direction
     201             : static Motor quad_bf_x_rev_motors[] =
     202             : {
     203             :     Motor(AP_MOTORS_MOT_1,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2),
     204             :     Motor(AP_MOTORS_MOT_2,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  1),
     205             :     Motor(AP_MOTORS_MOT_3, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3),
     206             :     Motor(AP_MOTORS_MOT_4,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 4),
     207             : };
     208             : 
     209             : // motor order to match DJI conventions
     210             : // See: https://forum44.djicdn.com/data/attachment/forum/201711/26/172348bppvtt1ot1nrtp5j.jpg
     211             : static Motor quad_dji_x_motors[] =
     212             : {
     213             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     214             :     Motor(AP_MOTORS_MOT_2,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     215             :     Motor(AP_MOTORS_MOT_3, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     216             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     217             : };
     218             : 
     219             : // motor order so that test order matches motor order ("clockwise X")
     220             : static Motor quad_cw_x_motors[] =
     221             : {
     222             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     223             :     Motor(AP_MOTORS_MOT_2,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     224             :     Motor(AP_MOTORS_MOT_3, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     225             :     Motor(AP_MOTORS_MOT_4,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     226             : };
     227             : 
     228             : #if AP_SIM_FRAME_COPTER_DOTRIACONTA_OCTAQUAD_X_ENABLED
     229             : static Motor dotriaconta_octaquad_x_motors[] =
     230             : {
     231             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,   1),
     232             :     Motor(AP_MOTORS_MOT_2, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  17),
     233             :     Motor(AP_MOTORS_MOT_3,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   25),
     234             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,    9),
     235             : 
     236             :     Motor(AP_MOTORS_MOT_5,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,    2),
     237             :     Motor(AP_MOTORS_MOT_6, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   18),
     238             :     Motor(AP_MOTORS_MOT_7,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  26),
     239             :     Motor(AP_MOTORS_MOT_8,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  10),
     240             : 
     241             :     Motor(AP_MOTORS_MOT_9,    45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  3),
     242             :     Motor(AP_MOTORS_MOT_10, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 19),
     243             :     Motor(AP_MOTORS_MOT_11,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  27),
     244             :     Motor(AP_MOTORS_MOT_12,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  11),
     245             : 
     246             :     Motor(AP_MOTORS_MOT_13,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   4),
     247             :     Motor(AP_MOTORS_MOT_14, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  20),
     248             :     Motor(AP_MOTORS_MOT_15,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 28),
     249             :     Motor(AP_MOTORS_MOT_16,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 12),
     250             : 
     251             :     Motor(AP_MOTORS_MOT_17,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  5),
     252             :     Motor(AP_MOTORS_MOT_18, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 21),
     253             :     Motor(AP_MOTORS_MOT_19,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  29),
     254             :     Motor(AP_MOTORS_MOT_20,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  13),
     255             : 
     256             :     Motor(AP_MOTORS_MOT_21,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   6),
     257             :     Motor(AP_MOTORS_MOT_22, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  22),
     258             :     Motor(AP_MOTORS_MOT_23,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 30),
     259             :     Motor(AP_MOTORS_MOT_24,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 14),
     260             : 
     261             :     Motor(AP_MOTORS_MOT_25,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  7),
     262             :     Motor(AP_MOTORS_MOT_26, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 23),
     263             :     Motor(AP_MOTORS_MOT_27,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  31),
     264             :     Motor(AP_MOTORS_MOT_28,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  15),
     265             : 
     266             :     Motor(AP_MOTORS_MOT_29,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   8),
     267             :     Motor(AP_MOTORS_MOT_30, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  24),
     268             :     Motor(AP_MOTORS_MOT_31,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 32),
     269             :     Motor(AP_MOTORS_MOT_32,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 16),
     270             : };
     271             : #endif  // AP_SIM_FRAME_COPTER_DOTRIACONTA_OCTAQUAD_X_ENABLED
     272             : 
     273             : 
     274             : static Motor tiltquad_h_vectored_motors[] =
     275             : {
     276             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  1, -1, 0, 0, 7, 10, -90),
     277             :     Motor(AP_MOTORS_MOT_2, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3, -1, 0, 0, 8, 10, -90),
     278             :     Motor(AP_MOTORS_MOT_3,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 4, -1, 0, 0, 8, 10, -90),
     279             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2, -1, 0, 0, 7, 10, -90),
     280             : };
     281             : 
     282             : static Motor tiltquad[] =
     283             : {
     284             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  1, -1, 0, 0, 7, 10, -90),
     285             :     Motor(AP_MOTORS_MOT_2, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  3),
     286             :     Motor(AP_MOTORS_MOT_3,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   4, -1, 0, 0, 8, 10, -90),
     287             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   2),
     288             : };
     289             : 
     290             : // collective pitch quad helicopter, matching AP_MotorsHeli_Quad's
     291             : // layout, with rotor speed control on channel 8 to match
     292             : // AP_MotorsHeli_RSC's default channel
     293             : // Run in a shell: ./Tools/autotest/fg_heliquad_view.sh
     294             : // then Tools/autotest/sim_vehicle.py -v ArduCopter -f heli-quad --enable-fgview
     295             : static Motor heliquad_motors[] =
     296             : {
     297             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1, 7),
     298             :     Motor(AP_MOTORS_MOT_2, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3, 7),
     299             :     Motor(AP_MOTORS_MOT_3,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4, 7),
     300             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2, 7),
     301             : };
     302             : 
     303             : static Motor hexa_motors[] =
     304             : {
     305             :     Motor(AP_MOTORS_MOT_1,   0, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  1),
     306             :     Motor(AP_MOTORS_MOT_2, 180, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 4),
     307             :     Motor(AP_MOTORS_MOT_3,-120, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  5),
     308             :     Motor(AP_MOTORS_MOT_4,  60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2),
     309             :     Motor(AP_MOTORS_MOT_5, -60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 6),
     310             :     Motor(AP_MOTORS_MOT_6, 120, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3)
     311             : };
     312             : 
     313             : static Motor hexax_motors[] =
     314             : {
     315             :     Motor(AP_MOTORS_MOT_1,  90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     316             :     Motor(AP_MOTORS_MOT_2, -90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     317             :     Motor(AP_MOTORS_MOT_3, -30, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6),
     318             :     Motor(AP_MOTORS_MOT_4, 150, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     319             :     Motor(AP_MOTORS_MOT_5,  30, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     320             :     Motor(AP_MOTORS_MOT_6,-150, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4)
     321             : };
     322             : 
     323             : static Motor hexa_dji_x_motors[] =
     324             : {
     325             :     Motor(AP_MOTORS_MOT_1,   30, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     326             :     Motor(AP_MOTORS_MOT_2,  -30, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6),
     327             :     Motor(AP_MOTORS_MOT_3,  -90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     328             :     Motor(AP_MOTORS_MOT_4, -150, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     329             :     Motor(AP_MOTORS_MOT_5,  150, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     330             :     Motor(AP_MOTORS_MOT_6,   90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2)
     331             : };
     332             : 
     333             : static Motor hexa_cw_x_motors[] = 
     334             : {
     335             :     Motor(AP_MOTORS_MOT_1,   30, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     336             :     Motor(AP_MOTORS_MOT_2,   90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     337             :     Motor(AP_MOTORS_MOT_3,  150, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     338             :     Motor(AP_MOTORS_MOT_4, -150, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     339             :     Motor(AP_MOTORS_MOT_5,  -90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     340             :     Motor(AP_MOTORS_MOT_6,  -30, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6)
     341             : };
     342             : 
     343             : static Motor octa_motors[] =
     344             : {
     345             :     Motor(AP_MOTORS_MOT_1,    0,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,  1),
     346             :     Motor(AP_MOTORS_MOT_2,  180,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,  5),
     347             :     Motor(AP_MOTORS_MOT_3,   45,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2),
     348             :     Motor(AP_MOTORS_MOT_4,  135,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 4),
     349             :     Motor(AP_MOTORS_MOT_5,  -45,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 8),
     350             :     Motor(AP_MOTORS_MOT_6, -135,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 6),
     351             :     Motor(AP_MOTORS_MOT_7,  -90,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,  7),
     352             :     Motor(AP_MOTORS_MOT_8,   90,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3)
     353             : };
     354             : 
     355             : static Motor octa_dji_x_motors[] =
     356             : {
     357             :     Motor(AP_MOTORS_MOT_1,   22.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     358             :     Motor(AP_MOTORS_MOT_2,  -22.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  8),
     359             :     Motor(AP_MOTORS_MOT_3,  -67.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 7),
     360             :     Motor(AP_MOTORS_MOT_4, -112.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6),
     361             :     Motor(AP_MOTORS_MOT_5, -157.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     362             :     Motor(AP_MOTORS_MOT_6,  157.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     363             :     Motor(AP_MOTORS_MOT_7,  112.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     364             :     Motor(AP_MOTORS_MOT_8,   67.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2)
     365             : };
     366             : 
     367             : static Motor octa_cw_x_motors[] = 
     368             : {
     369             :     Motor(AP_MOTORS_MOT_1,   22.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     370             :     Motor(AP_MOTORS_MOT_2,   67.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     371             :     Motor(AP_MOTORS_MOT_3,  112.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     372             :     Motor(AP_MOTORS_MOT_4,  157.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     373             :     Motor(AP_MOTORS_MOT_5, -157.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     374             :     Motor(AP_MOTORS_MOT_6, -112.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6),
     375             :     Motor(AP_MOTORS_MOT_7,  -67.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 7),
     376             :     Motor(AP_MOTORS_MOT_8,  -22.5f, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  8)
     377             : };
     378             : 
     379             : static Motor octa_quad_motors[] =
     380             : {
     381             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     382             :     Motor(AP_MOTORS_MOT_2,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  7),
     383             :     Motor(AP_MOTORS_MOT_3, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     384             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3),
     385             :     Motor(AP_MOTORS_MOT_5,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 8),
     386             :     Motor(AP_MOTORS_MOT_6,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     387             :     Motor(AP_MOTORS_MOT_7,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 4),
     388             :     Motor(AP_MOTORS_MOT_8, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6)
     389             : };
     390             : 
     391             : static Motor octa_quad_corotating_motors[] =
     392             : {
     393             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     394             :     Motor(AP_MOTORS_MOT_2,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  7),
     395             :     Motor(AP_MOTORS_MOT_3, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     396             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3),
     397             :     Motor(AP_MOTORS_MOT_5,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  8),
     398             :     Motor(AP_MOTORS_MOT_6,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2),
     399             :     Motor(AP_MOTORS_MOT_7,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     400             :     Motor(AP_MOTORS_MOT_8, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 6)
     401             : };
     402             : 
     403             : static Motor octa_quad_cw_corotating_motors[] =
     404             : {
     405             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     406             :     Motor(AP_MOTORS_MOT_2,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2),
     407             :     Motor(AP_MOTORS_MOT_3,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3),
     408             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     409             :     Motor(AP_MOTORS_MOT_5, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     410             :     Motor(AP_MOTORS_MOT_6, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 6),
     411             :     Motor(AP_MOTORS_MOT_7,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  7),
     412             :     Motor(AP_MOTORS_MOT_8,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  8),
     413             : };
     414             : 
     415             : static Motor octa_quad_cw_x_motors[] =
     416             : {
     417             :     Motor(AP_MOTORS_MOT_1,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     418             :     Motor(AP_MOTORS_MOT_2,   45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2),
     419             :     Motor(AP_MOTORS_MOT_3,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3),
     420             :     Motor(AP_MOTORS_MOT_4,  135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 4),
     421             :     Motor(AP_MOTORS_MOT_5, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5),
     422             :     Motor(AP_MOTORS_MOT_6, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6),
     423             :     Motor(AP_MOTORS_MOT_7,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  7),
     424             :     Motor(AP_MOTORS_MOT_8,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 8)
     425             : };
     426             : 
     427             : 
     428             : static Motor dodeca_hexa_motors[] =
     429             : {
     430             :     Motor(AP_MOTORS_MOT_1,   30, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  1),
     431             :     Motor(AP_MOTORS_MOT_2,   30, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   2),
     432             :     Motor(AP_MOTORS_MOT_3,   90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   3),
     433             :     Motor(AP_MOTORS_MOT_4,   90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  4),
     434             :     Motor(AP_MOTORS_MOT_5,  150, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  5),
     435             :     Motor(AP_MOTORS_MOT_6,  150, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   6),
     436             :     Motor(AP_MOTORS_MOT_7, -150, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   7),
     437             :     Motor(AP_MOTORS_MOT_8, -150, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  8),
     438             :     Motor(AP_MOTORS_MOT_9,  -90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  9),
     439             :     Motor(AP_MOTORS_MOT_10, -90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   10),
     440             :     Motor(AP_MOTORS_MOT_11, -30, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   11),
     441             :     Motor(AP_MOTORS_MOT_12, -30, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  12)
     442             : };
     443             : 
     444             : static Motor hexadeca_octa_motors[] =
     445             : {
     446             :     Motor(AP_MOTORS_MOT_1,     0, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   1),
     447             :     Motor(AP_MOTORS_MOT_2,     0, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  2),
     448             :     Motor(AP_MOTORS_MOT_3,    45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  3),
     449             :     Motor(AP_MOTORS_MOT_4,    45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   4),
     450             :     Motor(AP_MOTORS_MOT_5,    90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   5),
     451             :     Motor(AP_MOTORS_MOT_6,    90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  6),
     452             :     Motor(AP_MOTORS_MOT_7,   135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  7),
     453             :     Motor(AP_MOTORS_MOT_8,   135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   8),
     454             :     Motor(AP_MOTORS_MOT_9,   180, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   9),
     455             :     Motor(AP_MOTORS_MOT_10,  180, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 10),
     456             :     Motor(AP_MOTORS_MOT_11, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 11),
     457             :     Motor(AP_MOTORS_MOT_12, -135, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  12),
     458             :     Motor(AP_MOTORS_MOT_13,  -90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  13),
     459             :     Motor(AP_MOTORS_MOT_14,  -90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 14),
     460             :     Motor(AP_MOTORS_MOT_15,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 15),
     461             :     Motor(AP_MOTORS_MOT_16,  -45, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  16)
     462             : };
     463             : 
     464             : static Motor hexadeca_octa_cw_x_motors[] =
     465             : {
     466             :     Motor(AP_MOTORS_MOT_1,    22.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,   1),
     467             :     Motor(AP_MOTORS_MOT_2,    22.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  2),
     468             :     Motor(AP_MOTORS_MOT_3,    67.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  3),
     469             :     Motor(AP_MOTORS_MOT_4,    67.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,   4),
     470             :     Motor(AP_MOTORS_MOT_5,   112.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,   5),
     471             :     Motor(AP_MOTORS_MOT_6,   112.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  6),
     472             :     Motor(AP_MOTORS_MOT_7,   157.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  7),
     473             :     Motor(AP_MOTORS_MOT_8,   157.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,   8),
     474             :     Motor(AP_MOTORS_MOT_9,  -157.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,   9),
     475             :     Motor(AP_MOTORS_MOT_10, -157.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 10),
     476             :     Motor(AP_MOTORS_MOT_11, -112.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 11),
     477             :     Motor(AP_MOTORS_MOT_12, -112.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,  12),
     478             :     Motor(AP_MOTORS_MOT_13,  -67.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,  13),
     479             :     Motor(AP_MOTORS_MOT_14,  -67.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 14),
     480             :     Motor(AP_MOTORS_MOT_15,  -22.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 15),
     481             :     Motor(AP_MOTORS_MOT_16,  -22.5f,  AP_MOTORS_MATRIX_YAW_FACTOR_CW,  16)
     482             : };
     483             : 
     484             : static Motor deca_motors[] =
     485             : {
     486             :     Motor(AP_MOTORS_MOT_1,     0, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  1),
     487             :     Motor(AP_MOTORS_MOT_2,    36, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   2),
     488             :     Motor(AP_MOTORS_MOT_3,    72, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  3),
     489             :     Motor(AP_MOTORS_MOT_4,   108, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   4),
     490             :     Motor(AP_MOTORS_MOT_5,   144, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  5),
     491             :     Motor(AP_MOTORS_MOT_6,   180, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   6),
     492             :     Motor(AP_MOTORS_MOT_7,  -144, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  7),
     493             :     Motor(AP_MOTORS_MOT_8,  -108, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   8),
     494             :     Motor(AP_MOTORS_MOT_9,   -72, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  9),
     495             :     Motor(AP_MOTORS_MOT_10,  -36, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  10)
     496             : };
     497             : 
     498             : static Motor deca_cw_x_motors[] =
     499             : {
     500             :     Motor(AP_MOTORS_MOT_1,    18, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  1),
     501             :     Motor(AP_MOTORS_MOT_2,    54, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   2),
     502             :     Motor(AP_MOTORS_MOT_3,    90, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  3),
     503             :     Motor(AP_MOTORS_MOT_4,   126, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   4),
     504             :     Motor(AP_MOTORS_MOT_5,   162, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  5),
     505             :     Motor(AP_MOTORS_MOT_6,  -162, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   6),
     506             :     Motor(AP_MOTORS_MOT_7,  -126, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  7),
     507             :     Motor(AP_MOTORS_MOT_8,   -90, AP_MOTORS_MATRIX_YAW_FACTOR_CW,   8),
     508             :     Motor(AP_MOTORS_MOT_9,   -54, AP_MOTORS_MATRIX_YAW_FACTOR_CCW,  9),
     509             :     Motor(AP_MOTORS_MOT_10,  -18, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  10)
     510             : };
     511             : 
     512             : static Motor tri_motors[] =
     513             : {
     514             :     Motor(AP_MOTORS_MOT_1,   60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1),
     515             :     Motor(AP_MOTORS_MOT_2,  -60, AP_MOTORS_MATRIX_YAW_FACTOR_CW, 3),
     516             :     Motor(AP_MOTORS_MOT_4,  180, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2, AP_MOTORS_MOT_7, 60, -60, -1, 0, 0),
     517             : };
     518             : 
     519             : static Motor tilttri_motors[] =
     520             : {
     521             :     Motor(AP_MOTORS_MOT_1,   60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1, -1, 0, 0, AP_MOTORS_MOT_8, 0, -90),
     522             :     Motor(AP_MOTORS_MOT_2,  -60, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3, -1, 0, 0, AP_MOTORS_MOT_8, 0, -90),
     523             :     Motor(AP_MOTORS_MOT_4,  180, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2, AP_MOTORS_MOT_7, 60, -60, -1, 0, 0),
     524             : };
     525             : 
     526             : static Motor tilttri_vectored_motors[] =
     527             : {
     528             :     Motor(AP_MOTORS_MOT_1,   60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1, -1, 0, 0, 7, 10, -90),
     529             :     Motor(AP_MOTORS_MOT_2,  -60, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  3, -1, 0, 0, 8, 10, -90),
     530             :     Motor(AP_MOTORS_MOT_4,  180, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2)
     531             : };
     532             : 
     533             : static Motor y6_motors[] =
     534             : {
     535             :     Motor(AP_MOTORS_MOT_1,  60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 2),
     536             :     Motor(AP_MOTORS_MOT_2, -60, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  5),
     537             :     Motor(AP_MOTORS_MOT_3, -60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 6),
     538             :     Motor(AP_MOTORS_MOT_4, 180, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     539             :     Motor(AP_MOTORS_MOT_5,  60, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  1),
     540             :     Motor(AP_MOTORS_MOT_6, 180, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3)
     541             : };
     542             : 
     543             : /*
     544             :   FireflyY6 is a Y6 with front motors tiltable using servo on channel 9 (output 8)
     545             :  */
     546             : static Motor firefly_motors[] =
     547             : {
     548             :     Motor(AP_MOTORS_MOT_1, 180, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 3),
     549             :     Motor(AP_MOTORS_MOT_2,  60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 1, -1, 0, 0, 6, 0, -90),
     550             :     Motor(AP_MOTORS_MOT_3, -60, AP_MOTORS_MATRIX_YAW_FACTOR_CCW, 5, -1, 0, 0, 6, 0, -90),
     551             :     Motor(AP_MOTORS_MOT_4, 180, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  4),
     552             :     Motor(AP_MOTORS_MOT_5,  60, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  2, -1, 0, 0, 6, 0, -90),
     553             :     Motor(AP_MOTORS_MOT_6, -60, AP_MOTORS_MATRIX_YAW_FACTOR_CW,  6, -1, 0, 0, 6, 0, -90)
     554             : };
     555             : 
     556             : typedef struct {
     557             :     const char *name;
     558             :     uint8_t num_motors;
     559             :     Motor *motors;
     560             : } FrameTemplate;
     561             : 
     562             : /*
     563             :   table of supported frame types. String order is important for
     564             :   partial name matching
     565             :  */
     566             : static const FrameTemplate supported_frame_templates[] =
     567             : {
     568             :     {"+",         4, quad_plus_motors},
     569             :     {"quad",      4, quad_plus_motors},
     570             :     {"copter",    4, quad_plus_motors},
     571             :     {"x",         4, quad_x_motors},
     572             :     {"bfxrev",    4, quad_bf_x_rev_motors},
     573             :     {"bfx",       4, quad_bf_x_motors},
     574             : #if AP_SIM_FRAME_COPTER_DOTRIACONTA_OCTAQUAD_X_ENABLED
     575             :     {"dotriaconta", 32, dotriaconta_octaquad_x_motors},
     576             : #endif  // AP_SIM_FRAME_COPTER_DOTRIACONTA_OCTAQUAD_X_ENABLED
     577             :     {"djix",      4, quad_dji_x_motors},
     578             :     {"cwx",       4, quad_cw_x_motors},
     579             :     {"tilthvec",  4, tiltquad_h_vectored_motors},
     580             :     {"heli-quad", 4, heliquad_motors},
     581             :     {"hexadeca-octa", 16, hexadeca_octa_motors},
     582             :     {"hexadeca-octa-cwx", 16, hexadeca_octa_cw_x_motors},
     583             :     {"hexax",     6, hexax_motors},
     584             :     {"hexa-cwx",  6, hexa_cw_x_motors},
     585             :     {"hexa-dji",  6, hexa_dji_x_motors},
     586             :     {"hexa",      6, hexa_motors},
     587             :     {"octa-cwx",  8, octa_cw_x_motors},
     588             :     {"octa-dji",  8, octa_dji_x_motors},
     589             :     {"octa-quad-cwx",8, octa_quad_cw_x_motors},
     590             :     {"octa-quad-cor", 8, octa_quad_corotating_motors},
     591             :     {"octa-quad-cw-cor", 8, octa_quad_cw_corotating_motors},
     592             :     {"octa-quad", 8, octa_quad_motors},
     593             :     {"octa",      8, octa_motors},
     594             :     {"deca",     10, deca_motors},
     595             :     {"deca-cwx", 10, deca_cw_x_motors},
     596             :     {"dodeca-hexa", 12, dodeca_hexa_motors},
     597             :     {"tri",       3, tri_motors},
     598             :     {"tilttrivec",3, tilttri_vectored_motors},
     599             :     {"tilttri",   3, tilttri_motors},
     600             :     {"y6",        6, y6_motors},
     601             :     {"firefly",   6, firefly_motors},
     602             :     {"tilt",      4, tiltquad},
     603             : };
     604             : 
     605             : // get air density in kg/m^3
     606   215891572 : float Frame::get_air_density(float alt_amsl) const
     607             : {
     608   215891572 :     return AP_Baro::get_air_density_for_alt_amsl(alt_amsl);
     609             : }
     610             : 
     611             : /*
     612             :   load frame specific parameters from a json file if available
     613             :  */
     614          29 : void Frame::load_frame_params(const char *model_json)
     615             : {
     616          29 :     char *fname = nullptr;
     617             :     struct stat st;
     618          29 :     if (AP::FS().stat(model_json, &st) == 0) {
     619          29 :         fname = strdup(model_json);
     620             :     } else {
     621           0 :         IGNORE_RETURN(asprintf(&fname, "@ROMFS/models/%s", model_json));
     622           0 :         if (fname == nullptr || AP::FS().stat(fname, &st) != 0) {
     623           0 :             AP_HAL::panic("%s failed to load", model_json);
     624             :         }
     625             :     }
     626          29 :     if (fname == nullptr) {
     627           0 :         AP_HAL::panic("%s failed to load", model_json);
     628             :     }
     629          29 :     AP_JSON::value *obj = AP_JSON::load_json(fname);
     630          29 :     if (obj == nullptr) {
     631           0 :         AP_HAL::panic("%s failed to load", fname);
     632             :     }
     633             : 
     634             :     enum class VarType {
     635             :         FLOAT,
     636             :         AP_FLOAT,
     637             :         VECTOR3F,
     638             :     };
     639             : 
     640             :     struct json_search {
     641             :         const char *label;
     642             :         void *ptr;
     643             :         VarType t;
     644             :     };
     645             : 
     646          29 :     json_search vars[] = {
     647             : #define FRAME_VAR(s) { #s, &model.s, VarType::AP_FLOAT }
     648          29 :         FRAME_VAR(mass),
     649          29 :         FRAME_VAR(diagonal_size),
     650          29 :         FRAME_VAR(refSpd),
     651          29 :         FRAME_VAR(refAngle),
     652          29 :         FRAME_VAR(refVoltage),
     653          29 :         FRAME_VAR(refCurrent),
     654          29 :         FRAME_VAR(refAlt),
     655          29 :         FRAME_VAR(maxVoltage),
     656          29 :         FRAME_VAR(battCapacityAh),
     657          29 :         FRAME_VAR(refBatRes),
     658          29 :         FRAME_VAR(propExpo),
     659          29 :         FRAME_VAR(refRotRate),
     660          29 :         FRAME_VAR(hoverThrOut),
     661          29 :         FRAME_VAR(pwmMin),
     662          29 :         FRAME_VAR(pwmMax),
     663          29 :         FRAME_VAR(spin_min),
     664          29 :         FRAME_VAR(spin_max),
     665          29 :         FRAME_VAR(slew_max),
     666          29 :         FRAME_VAR(disc_area),
     667          29 :         FRAME_VAR(mdrag_coef),
     668          29 :         FRAME_VAR(bbdrag_coef),
     669          29 :         {"moment_inertia", &model.moment_of_inertia, VarType::VECTOR3F},
     670          29 :         {"refTempC", &model.refTempC, VarType::FLOAT},
     671          29 :         {"num_motors", &model.num_motors, VarType::FLOAT},
     672          29 :     };
     673             : 
     674         725 :     for (uint8_t i=0; i<ARRAY_SIZE(vars); i++) {
     675        1392 :         auto v = obj->get(vars[i].label);
     676         696 :         if (v.is<AP_JSON::null>()) {
     677             :             // use default value
     678          58 :             continue;
     679             :         }
     680         638 :         if (vars[i].t == VarType::FLOAT) {
     681          58 :             parse_float(v, vars[i].label, *((float *)vars[i].ptr));
     682             : 
     683         580 :         } else if (vars[i].t == VarType::AP_FLOAT) {
     684             :             // json model values become the defaults for the SIM_FRM_ parameters
     685             :             float value;
     686         580 :             parse_float(v, vars[i].label, value);
     687         580 :             ((AP_Float *)vars[i].ptr)->set_default(value);
     688             : 
     689           0 :         } else if (vars[i].t == VarType::VECTOR3F) {
     690           0 :             parse_vector3(v, vars[i].label, *(Vector3f *)vars[i].ptr);
     691             : 
     692             :         }
     693         696 :     }
     694             : 
     695          29 :     json_search per_motor_vars[] = {
     696          29 :         {"position", &model.motor_pos, VarType::VECTOR3F},
     697          29 :         {"vector", &model.motor_thrust_vec, VarType::VECTOR3F},
     698          29 :         {"yaw", &model.yaw_factor, VarType::FLOAT},
     699          29 :     };
     700             :     char label_name[20];
     701         116 :     for (uint8_t i=0; i<ARRAY_SIZE(per_motor_vars); i++) {
     702        2871 :         for (uint8_t j=0; j<SIM_FRAME_MAX_ACTUATORS; j++) {
     703        2784 :             snprintf(label_name, 20, "motor%i_%s", j+1, per_motor_vars[i].label);
     704        5568 :             auto v = obj->get(label_name);
     705        2784 :             if (v.is<AP_JSON::null>()) {
     706             :                 // use default value
     707        2784 :                 continue;
     708             :             }
     709           0 :             if (per_motor_vars[i].t == VarType::FLOAT) {
     710           0 :                 parse_float(v, label_name, *(((float *)per_motor_vars[i].ptr) + j));
     711             : 
     712           0 :             } else if (per_motor_vars[i].t == VarType::VECTOR3F) {
     713           0 :                 parse_vector3(v, label_name, *(((Vector3f *)per_motor_vars[i].ptr) + j));
     714             :             }
     715        2784 :         }
     716             :     }
     717             : 
     718          29 :     delete obj;
     719             : 
     720          29 :     ::printf("Loaded model params from %s\n", fname);
     721          29 :     free(fname);
     722          29 : }
     723             : 
     724         638 : void Frame::parse_float(AP_JSON::value val, const char* label, float &param) {
     725         638 :     if (!val.is<double>()) {
     726           0 :         AP_HAL::panic("Bad json type for %s: %s", label, val.to_str().c_str());
     727             :     }
     728         638 :     param = val.get<double>();
     729         638 : }
     730             : 
     731           0 : void Frame::parse_vector3(AP_JSON::value val, const char* label, Vector3f &param) {
     732           0 :     if (!val.is<AP_JSON::value::array>() || !val.contains(2) || val.contains(3)) {
     733           0 :         AP_HAL::panic("Bad json type for %s: %s", label, val.to_str().c_str());
     734             :     }
     735           0 :     for (uint8_t j=0; j<3; j++) {
     736           0 :         parse_float(val.get(j), label, param[j]);
     737             :     }
     738           0 : }
     739             : 
     740             : #if AP_SIM_ENABLED
     741             : 
     742             : /*
     743             :   initialise the frame
     744             :  */
     745        1110 : void Frame::init(const char *frame_str)
     746             : {
     747        1110 :     auto *sitl = AP::sitl();
     748        1110 :     if (sitl != nullptr) {
     749        1110 :         sitl->models.simframe_ptr = this;
     750             :     }
     751        1110 :     AP_Param::setup_object_defaults(this, var_info);
     752             : 
     753        1110 :     const char *colon = strchr(frame_str, ':');
     754        1110 :     size_t slen = strlen(frame_str);
     755        1110 :     if (colon != nullptr && slen > 5 && strcmp(&frame_str[slen-5], ".json") == 0) {
     756          29 :         load_frame_params(colon+1);
     757             :     }
     758             : 
     759        1110 :     if (uint8_t(model.num_motors) != num_motors) {
     760          40 :         ::printf("Warning model expected %u motors and got %u\n", uint8_t(model.num_motors), num_motors);
     761             :     }
     762             : 
     763        1110 :     update_parameters();
     764        1110 : }
     765             : 
     766             : /*
     767             :   apply settings from the model parameters. Called on each physics
     768             :   step so changes to SIM_FRM_ parameters take effect. Note that
     769             :   parameters loaded from eeprom are only applied after the frame is
     770             :   created, so this cannot be done just once in init()
     771             :  */
     772   107946341 : void Frame::update_parameters(void)
     773             : {
     774   107946341 :     mass = model.mass * mass_scale;
     775             : 
     776   107946341 :     const float drag_force = model.mass * GRAVITY_MSS * tanf(radians(model.refAngle));
     777             : 
     778   107946341 :     const float cos_tilt = cosf(radians(model.refAngle));
     779   107946341 :     const float airspeed_bf = model.refSpd * cos_tilt;
     780   107946341 :     const float ref_thrust = model.mass * GRAVITY_MSS / cos_tilt;
     781   107946341 :     const float ref_air_density = get_air_density(model.refAlt);
     782             : 
     783   107946341 :     mdrag_coef = model.mdrag_coef;
     784   107946341 :     const float momentum_drag = cos_tilt * mdrag_coef * airspeed_bf * sqrtf(ref_thrust * ref_air_density * model.disc_area);
     785             : 
     786   107946341 :     if (momentum_drag > drag_force) {
     787           0 :         mdrag_coef *= drag_force / momentum_drag;
     788           0 :         areaCd = 0.0;
     789             :     } else {
     790   107946341 :         areaCd = model.bbdrag_coef * (drag_force - momentum_drag) / (0.5f * ref_air_density * sq(model.refSpd));
     791             :     }
     792   107946341 :     const float drag_bcoef = is_positive(areaCd) ? model.mass / areaCd : 0.0;
     793   107946341 :     const float drag_mcoef = (momentum_drag / (model.mass * airspeed_bf)) * sqrtf(1.225f / ref_air_density);
     794   215892682 :     if ((!is_equal(drag_bcoef, last_drag_bcoef) || !is_equal(drag_mcoef, last_drag_mcoef)) &&
     795   215892682 :         isfinite(drag_bcoef) && isfinite(drag_mcoef)) {
     796         909 :         last_drag_bcoef = drag_bcoef;
     797         909 :         last_drag_mcoef = drag_mcoef;
     798         909 :         ::printf("Suggested EK3_DRAG_BCOEF_* = %.3f, EK3_DRAG_MCOEF = %.3f\n", drag_bcoef, drag_mcoef);
     799             :     }
     800             : 
     801   107946341 :     terminal_rotation_rate = model.refRotRate;
     802             : 
     803   107946341 :     const float hover_thrust = model.mass * GRAVITY_MSS;
     804   107946341 :     const float hover_power = model.refCurrent * model.refVoltage;
     805   107946341 :     const float hover_velocity_out = 2 * hover_power / hover_thrust;
     806   107946341 :     const float effective_disc_area = hover_thrust / (0.5 * ref_air_density * sq(hover_velocity_out));
     807   107946341 :     const float velocity_max = hover_velocity_out / sqrtf(MAX(model.hoverThrOut.get(), 0.01f));
     808   107946341 :     const float effective_prop_area = effective_disc_area / num_motors;
     809   107946341 :     const float true_prop_area = model.disc_area / num_motors;
     810             : 
     811             :     // power_factor is ratio of power consumed per newton of thrust
     812   107946341 :     const float power_factor = hover_power / hover_thrust;
     813             : 
     814   560909159 :     for (uint8_t i=0; i<num_motors; i++) {
     815   452962818 :         motors[i].setup_params(model.pwmMin.get(), model.pwmMax.get(), model.spin_min, model.spin_max, model.propExpo, model.slew_max,
     816             :                                model.diagonal_size, power_factor, model.maxVoltage, effective_prop_area, velocity_max,
     817   452962818 :                                model.motor_pos[i], model.motor_thrust_vec[i], model.yaw_factor[i], true_prop_area,
     818             :                                mdrag_coef);
     819             :     }
     820             : 
     821   107946341 :     if (is_zero(model.moment_of_inertia.x) || is_zero(model.moment_of_inertia.y) || is_zero(model.moment_of_inertia.z)) {
     822             :         // if no inertia provided, assume 50% of mass on ring around center
     823   107946341 :         moment_of_inertia.x = model.mass * 0.25 * sq(model.diagonal_size*0.5);
     824   107946341 :         moment_of_inertia.y = moment_of_inertia.x;
     825   107946341 :         moment_of_inertia.z = model.mass * 0.5 * sq(model.diagonal_size*0.5);
     826             :     } else {
     827           0 :         moment_of_inertia = model.moment_of_inertia;
     828             :     }
     829             : 
     830   107946341 :     if (!is_equal(last_batt_voltage, model.maxVoltage.get()) ||
     831   215891572 :         !is_equal(last_batt_cap, model.battCapacityAh.get()) ||
     832   107945231 :         !is_equal(last_batt_res, model.refBatRes.get())) {
     833        1110 :         const float prev_cap = last_batt_cap;
     834        1110 :         last_batt_voltage = model.maxVoltage;
     835        1110 :         last_batt_cap = model.battCapacityAh;
     836        1110 :         last_batt_res = model.refBatRes;
     837        1110 :         battery_dirty = true;
     838             : 
     839             :         // setup reasonable defaults for battery
     840        1110 :         AP_Param::set_default_by_name("SIM_BATT_VOLTAGE", model.maxVoltage);
     841        1110 :         AP_Param::set_default_by_name("SIM_BATT_CAP_AH", model.battCapacityAh);
     842        1110 :         if (model.battCapacityAh > 0) {
     843          25 :             AP_Param::set_default_by_name("BATT_CAPACITY", model.battCapacityAh*1000);
     844        1085 :         } else if (prev_cap > 0) {
     845             :             // returning to unlimited capacity, mark vehicle capacity unknown
     846           0 :             AP_Param::set_default_by_name("BATT_CAPACITY", 0);
     847             :         }
     848             :     }
     849   107946341 : }
     850             : 
     851             : // returns true once when the battery model values have changed, so
     852             : // the vehicle can re-setup the simulated battery
     853   107945231 : bool Frame::battery_changed(void)
     854             : {
     855   107945231 :     bool ret = battery_dirty;
     856   107945231 :     battery_dirty = false;
     857   107945231 :     return ret;
     858             : }
     859             : 
     860             : /*
     861             :   create a frame by name from its template
     862             :  */
     863        1110 : Frame *Frame::create_frame(const char *name)
     864             : {
     865        3096 :     for (uint8_t i=0; i < ARRAY_SIZE(supported_frame_templates); i++) {
     866        3096 :         auto &tplate = supported_frame_templates[i]; // `template` is a reserved word
     867             :         // do partial name matching to allow for frame variants
     868        3096 :         if (strncasecmp(name, tplate.name, strlen(tplate.name)) == 0) {
     869        1110 :             return NEW_NOTHROW Frame(tplate.name, tplate.num_motors, tplate.motors);
     870             :         }
     871             :     }
     872           0 :     return nullptr;
     873             : }
     874             : 
     875             : // calculate rotational and linear accelerations
     876   107945231 : void Frame::calculate_forces(const Aircraft &aircraft,
     877             :                              const struct sitl_input &input,
     878             :                              Vector3f &rot_accel,
     879             :                              Vector3f &body_accel,
     880             :                              float* rpm)
     881             : {
     882   107945231 :     Vector3f thrust; // newtons
     883   107945231 :     Vector3f torque;
     884             : 
     885   107945231 :     update_parameters();
     886             : 
     887   107945231 :     const float air_density = get_air_density(aircraft.get_location().alt*0.01);
     888   107945231 :     const Vector3f gyro = aircraft.get_gyro();
     889             : 
     890   107945231 :     Vector3f vel_air_bf = aircraft.get_dcm().transposed() * aircraft.get_velocity_air_ef();
     891             : 
     892   107945231 :     const auto *_sitl = AP::sitl();
     893   560903285 :     for (uint8_t i=0; i<num_motors; i++) {
     894   452958054 :         Vector3f mtorque, mthrust;
     895   452958054 :         motors[i].calculate_forces(input, motor_offset, mtorque, mthrust, vel_air_bf,
     896             :                                    gyro, air_density, aircraft.get_battery_voltage(), true);
     897   452958054 :         torque += mtorque;
     898   452958054 :         thrust += mthrust;
     899             :         // simulate motor rpm
     900   452958054 :         if (!is_zero(_sitl->vibe_motor)) {
     901    17508600 :             rpm[motor_offset+i] = motors[i].get_command() * AP::sitl()->vibe_motor * 60.0f;
     902             :         }
     903             :     }
     904             : 
     905             :     // calculate total rotational acceleration
     906   107945231 :     rot_accel.x = torque.x / moment_of_inertia.x;
     907   107945231 :     rot_accel.y = torque.y / moment_of_inertia.y;
     908   107945231 :     rot_accel.z = torque.z / moment_of_inertia.z;
     909             : 
     910   107945231 :     if (terminal_rotation_rate > 0) {
     911             :         // rotational air resistance
     912   107945231 :         rot_accel.x -= gyro.x * radians(400.0) / terminal_rotation_rate;
     913   107945231 :         rot_accel.y -= gyro.y * radians(400.0) / terminal_rotation_rate;
     914   107945231 :         rot_accel.z -= gyro.z * radians(400.0) / terminal_rotation_rate;
     915             :     }
     916             : 
     917   107945231 :     if (is_positive(areaCd)) {
     918             :         // use the model params to calculate drag
     919    81459271 :         Vector3f drag_bf;
     920    81459271 :         drag_bf.x = areaCd * 0.5f * air_density * sq(vel_air_bf.x);
     921    81459271 :         if (is_negative(vel_air_bf.x)) {
     922    22219331 :             drag_bf.x = -drag_bf.x;
     923             :         }
     924             : 
     925    81459271 :         drag_bf.y = areaCd * 0.5f * air_density * sq(vel_air_bf.y);
     926    81459271 :         if (is_negative(vel_air_bf.y)) {
     927    25285795 :             drag_bf.y = -drag_bf.y;
     928             :         }
     929             : 
     930    81459271 :         drag_bf.z = areaCd * 0.5f * air_density * sq(vel_air_bf.z);
     931    81459271 :         if (is_negative(vel_air_bf.z)) {
     932    30186263 :             drag_bf.z = -drag_bf.z;
     933             :         }
     934             : 
     935    81459271 :         thrust -= drag_bf;
     936             :     }
     937             : 
     938   107945231 :     body_accel = thrust/aircraft.gross_mass();
     939   107945231 : }
     940             : 
     941             : // computes (total) instantaneous current
     942   107945231 : float Frame::get_current_amp(void)
     943             : {
     944   107945231 :     float current = 0;
     945   560903285 :     for (uint8_t i=0; i<num_motors; i++) {
     946   452958054 :         current += motors[i].get_current();
     947             :     }
     948   107945231 :     return current;
     949             : }
     950             : #endif // AP_SIM_ENABLED

Generated by: LCOV version 1.14