2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
27 #include "common/bitarray.h"
28 #include "common/streambuf.h"
29 #include "common/utils.h"
31 #include "config/feature.h"
33 #include "config/config.h"
34 #include "fc/runtime_config.h"
36 #include "flight/mixer.h"
37 #include "flight/pid.h"
39 #include "sensors/sensors.h"
41 #include "telemetry/telemetry.h"
43 #include "pg/piniobox.h"
47 // permanent IDs must uniquely identify BOX meaning, DO NOT REUSE THEM!
48 static const box_t boxes
[CHECKBOX_ITEM_COUNT
] = {
49 { .boxId
= BOXARM
, .boxName
= "ARM", .permanentId
= 0 },
50 { .boxId
= BOXANGLE
, .boxName
= "ANGLE", .permanentId
= 1 },
51 { .boxId
= BOXHORIZON
, .boxName
= "HORIZON", .permanentId
= 2 },
52 { .boxId
= BOXALTHOLD
, .boxName
= "ALTHOLD", .permanentId
= 3 },
53 { .boxId
= BOXANTIGRAVITY
, .boxName
= "ANTI GRAVITY", .permanentId
= 4 },
54 { .boxId
= BOXMAG
, .boxName
= "MAG", .permanentId
= 5 },
55 { .boxId
= BOXHEADFREE
, .boxName
= "HEADFREE", .permanentId
= 6 },
56 { .boxId
= BOXHEADADJ
, .boxName
= "HEADADJ", .permanentId
= 7 },
57 { .boxId
= BOXCAMSTAB
, .boxName
= "CAMSTAB", .permanentId
= 8 },
58 // { .boxId = BOXCAMTRIG, .boxName = "CAMTRIG", .permanentId = 9 },
59 // { .boxId = BOXGPSHOME, .boxName = "GPS HOME", .permanentId = 10 },
60 { .boxId
= BOXPOSHOLD
, .boxName
= "POS HOLD", .permanentId
= 11 },
61 { .boxId
= BOXPASSTHRU
, .boxName
= "PASSTHRU", .permanentId
= 12 },
62 { .boxId
= BOXBEEPERON
, .boxName
= "BEEPER", .permanentId
= 13 },
63 // { .boxId = BOXLEDMAX, .boxName = "LEDMAX", .permanentId = 14 }, (removed)
64 { .boxId
= BOXLEDLOW
, .boxName
= "LEDLOW", .permanentId
= 15 },
65 // { .boxId = BOXLLIGHTS, .boxName = "LLIGHTS", .permanentId = 16 }, (removed)
66 { .boxId
= BOXCALIB
, .boxName
= "CALIB", .permanentId
= 17 },
67 // { .boxId = BOXGOV, .boxName = "GOVERNOR", .permanentId = 18 }, (removed)
68 { .boxId
= BOXOSD
, .boxName
= "OSD DISABLE", .permanentId
= 19 },
69 { .boxId
= BOXTELEMETRY
, .boxName
= "TELEMETRY", .permanentId
= 20 },
70 // { .boxId = BOXGTUNE, .boxName = "GTUNE", .permanentId = 21 }, (removed)
71 // { .boxId = BOXRANGEFINDER, .boxName = "RANGEFINDER", .permanentId = 22 }, (removed)
72 { .boxId
= BOXSERVO1
, .boxName
= "SERVO1", .permanentId
= 23 },
73 { .boxId
= BOXSERVO2
, .boxName
= "SERVO2", .permanentId
= 24 },
74 { .boxId
= BOXSERVO3
, .boxName
= "SERVO3", .permanentId
= 25 },
75 { .boxId
= BOXBLACKBOX
, .boxName
= "BLACKBOX", .permanentId
= 26 },
76 { .boxId
= BOXFAILSAFE
, .boxName
= "FAILSAFE", .permanentId
= 27 },
77 { .boxId
= BOXAIRMODE
, .boxName
= "AIR MODE", .permanentId
= 28 },
78 { .boxId
= BOX3D
, .boxName
= "3D DISABLE", .permanentId
= 29},
79 { .boxId
= BOXFPVANGLEMIX
, .boxName
= "FPV ANGLE MIX", .permanentId
= 30},
80 { .boxId
= BOXBLACKBOXERASE
, .boxName
= "BLACKBOX ERASE", .permanentId
= 31 },
81 { .boxId
= BOXCAMERA1
, .boxName
= "CAMERA CONTROL 1", .permanentId
= 32},
82 { .boxId
= BOXCAMERA2
, .boxName
= "CAMERA CONTROL 2", .permanentId
= 33},
83 { .boxId
= BOXCAMERA3
, .boxName
= "CAMERA CONTROL 3", .permanentId
= 34 },
84 { .boxId
= BOXCRASHFLIP
, .boxName
= "FLIP OVER AFTER CRASH", .permanentId
= 35 },
85 { .boxId
= BOXPREARM
, .boxName
= "PREARM", .permanentId
= 36 },
86 { .boxId
= BOXBEEPGPSCOUNT
, .boxName
= "GPS BEEP SATELLITE COUNT", .permanentId
= 37 },
87 // { .boxId = BOX3DONASWITCH, .boxName = "3D ON A SWITCH", .permanentId = 38 }, (removed)
88 { .boxId
= BOXVTXPITMODE
, .boxName
= "VTX PIT MODE", .permanentId
= 39 },
89 { .boxId
= BOXUSER1
, .boxName
= BOX_USER1_NAME
, .permanentId
= 40 }, // may be overridden by modeActivationConfig
90 { .boxId
= BOXUSER2
, .boxName
= BOX_USER2_NAME
, .permanentId
= 41 },
91 { .boxId
= BOXUSER3
, .boxName
= BOX_USER3_NAME
, .permanentId
= 42 },
92 { .boxId
= BOXUSER4
, .boxName
= BOX_USER4_NAME
, .permanentId
= 43 },
93 { .boxId
= BOXPIDAUDIO
, .boxName
= "PID AUDIO", .permanentId
= 44 },
94 { .boxId
= BOXPARALYZE
, .boxName
= "PARALYZE", .permanentId
= 45 },
95 { .boxId
= BOXGPSRESCUE
, .boxName
= "GPS RESCUE", .permanentId
= 46 },
96 { .boxId
= BOXACROTRAINER
, .boxName
= "ACRO TRAINER", .permanentId
= 47 },
97 { .boxId
= BOXVTXCONTROLDISABLE
, .boxName
= "VTX CONTROL DISABLE", .permanentId
= 48},
98 { .boxId
= BOXLAUNCHCONTROL
, .boxName
= "LAUNCH CONTROL", .permanentId
= 49 },
99 { .boxId
= BOXMSPOVERRIDE
, .boxName
= "MSP OVERRIDE", .permanentId
= 50},
100 { .boxId
= BOXSTICKCOMMANDDISABLE
, .boxName
= "STICK COMMANDS DISABLE", .permanentId
= 51},
101 { .boxId
= BOXBEEPERMUTE
, .boxName
= "BEEPER MUTE", .permanentId
= 52},
102 { .boxId
= BOXREADY
, .boxName
= "READY", .permanentId
= 53},
103 { .boxId
= BOXLAPTIMERRESET
, .boxName
= "LAP TIMER RESET", .permanentId
= 54},
106 // mask of enabled IDs, calculated on startup based on enabled features. boxId_e is used as bit index
108 static boxBitmask_t activeBoxIds
;
110 const box_t
*findBoxByBoxId(boxId_e boxId
)
112 for (unsigned i
= 0; i
< ARRAYLEN(boxes
); i
++) {
113 const box_t
*candidate
= &boxes
[i
];
114 if (candidate
->boxId
== boxId
)
120 const box_t
*findBoxByPermanentId(uint8_t permanentId
)
122 for (unsigned i
= 0; i
< ARRAYLEN(boxes
); i
++) {
123 const box_t
*candidate
= &boxes
[i
];
124 if (candidate
->permanentId
== permanentId
)
130 static bool activeBoxIdGet(boxId_e boxId
)
132 if (boxId
> sizeof(activeBoxIds
) * 8) {
136 return bitArrayGet(&activeBoxIds
, boxId
);
139 int serializeBoxNameFn(sbuf_t
*dst
, const box_t
*box
)
141 const char* name
= NULL
;
143 #if defined(USE_CUSTOM_BOX_NAMES)
144 if (box
->boxId
>= BOXUSER1
&& box
->boxId
<= BOXUSER4
) {
145 const int n
= box
->boxId
- BOXUSER1
;
146 name
= modeActivationConfig()->box_user_names
[n
];
147 // possibly there is no '\0' in boxname
149 len
= strnlen(name
, sizeof(modeActivationConfig()->box_user_names
[n
]));
159 if (sbufBytesRemaining(dst
) < len
+ 1) {
160 // boxname or separator won't fit
163 sbufWriteData(dst
, name
, len
);
164 sbufWriteU8(dst
, ';');
168 int serializeBoxPermanentIdFn(sbuf_t
*dst
, const box_t
*box
)
170 if (sbufBytesRemaining(dst
) < 1) {
173 sbufWriteU8(dst
, box
->permanentId
);
177 // serialize 'page' of boxNames.
178 // Each page contains at most 32 boxes
179 void serializeBoxReply(sbuf_t
*dst
, int page
, serializeBoxFn
*serializeBox
)
182 unsigned pageStart
= page
* 32;
183 unsigned pageEnd
= pageStart
+ 32;
184 for (boxId_e id
= 0; id
< CHECKBOX_ITEM_COUNT
; id
++) {
185 if (activeBoxIdGet(id
)) {
186 if (boxIdx
>= pageStart
&& boxIdx
< pageEnd
) {
187 if ((*serializeBox
)(dst
, findBoxByBoxId(id
)) < 0) {
188 // failed to serialize, abort
192 boxIdx
++; // count active boxes
197 void initActiveBoxIds(void)
199 // calculate used boxes based on features and set corresponding activeBoxIds bits
200 boxBitmask_t ena
; // temporary variable to collect result
201 memset(&ena
, 0, sizeof(ena
));
203 // macro to enable boxId (BoxidMaskEnable). Reference to ena is hidden, local use only
204 #define BME(boxId) do { bitArraySet(&ena, boxId); } while (0)
207 if (!featureIsEnabled(FEATURE_AIRMODE
)) {
211 bool acceleratorGainsEnabled
= false;
212 for (unsigned i
= 0; i
< PID_PROFILE_COUNT
; i
++) {
213 if (pidProfiles(i
)->anti_gravity_gain
!= ITERM_ACCELERATOR_GAIN_OFF
) {
214 acceleratorGainsEnabled
= true;
217 if (acceleratorGainsEnabled
&& !featureIsEnabled(FEATURE_ANTI_GRAVITY
)) {
221 if (sensors(SENSOR_ACC
)) {
224 #ifdef USE_ALTITUDE_HOLD
227 #ifdef USE_POSITION_HOLD
233 if (featureIsEnabled(FEATURE_INFLIGHT_ACC_CAL
)) {
236 #if defined(USE_ACRO_TRAINER) && defined(USE_ACC)
238 #endif // USE_ACRO_TRAINER
242 if (sensors(SENSOR_MAG
)) {
248 if (featureIsEnabled(FEATURE_GPS
)) {
249 #ifdef USE_GPS_RESCUE
250 if (!featureIsEnabled(FEATURE_3D
) && !isFixedWing()) {
254 BME(BOXBEEPGPSCOUNT
);
260 if (mixerConfig()->mixerMode
== MIXER_FLYING_WING
|| mixerConfig()->mixerMode
== MIXER_AIRPLANE
|| mixerConfig()->mixerMode
== MIXER_CUSTOM_AIRPLANE
) {
268 if (featureIsEnabled(FEATURE_LED_STRIP
)) {
276 BME(BOXBLACKBOXERASE
);
280 if (featureIsEnabled(FEATURE_3D
)) {
285 bool configuredMotorProtocolDshot
;
286 checkMotorProtocolEnabled(&motorConfig()->dev
, &configuredMotorProtocolDshot
);
287 if (configuredMotorProtocolDshot
) {
292 if (featureIsEnabled(FEATURE_SERVO_TILT
)) {
299 if (featureIsEnabled(FEATURE_TELEMETRY
)) {
305 if (mixerConfig()->mixerMode
== MIXER_CUSTOM_AIRPLANE
) {
318 #if defined(USE_VTX_SMARTAUDIO) || defined(USE_VTX_TRAMP) || defined(USE_VTX_MSP)
320 BME(BOXVTXCONTROLDISABLE
);
326 // Turn BOXUSERx only if pinioBox facility monitors them, as the facility is the only BOXUSERx observer.
327 // Note that pinioBoxConfig can be set to monitor any box.
328 for (int i
= 0; i
< PINIO_COUNT
; i
++) {
329 if (pinioBoxConfig()->permanentId
[i
] != PERMANENT_ID_NONE
) {
330 const box_t
*box
= findBoxByPermanentId(pinioBoxConfig()->permanentId
[i
]);
347 #if defined(USE_PID_AUDIO)
351 #ifdef USE_LAUNCH_CONTROL
352 BME(BOXLAUNCHCONTROL
);
355 #if defined(USE_RX_MSP_OVERRIDE)
356 if (rxConfig()->msp_override_channels_mask
) {
361 BME(BOXSTICKCOMMANDDISABLE
);
364 #if defined(USE_GPS_LAP_TIMER)
365 BME(BOXLAPTIMERRESET
);
369 // check that all enabled IDs are in boxes array (check may be skipped when using findBoxById() functions)
370 for (boxId_e boxId
= 0; boxId
< CHECKBOX_ITEM_COUNT
; boxId
++)
371 if (bitArrayGet(&ena
, boxId
)
372 && findBoxByBoxId(boxId
) == NULL
)
373 bitArrayClr(&ena
, boxId
); // this should not happen, but handle it gracefully
375 activeBoxIds
= ena
; // set global variable
378 // return state of given boxId box, handling ARM and FLIGHT_MODE
379 bool getBoxIdState(boxId_e boxid
)
381 const uint8_t boxIdToFlightModeMap
[] = BOXID_TO_FLIGHT_MODE_MAP_INITIALIZER
;
383 // we assume that all boxId below BOXID_FLIGHTMODE_LAST except BOXARM are mapped to flightmode
384 STATIC_ASSERT(ARRAYLEN(boxIdToFlightModeMap
) == BOXID_FLIGHTMODE_LAST
+ 1, FLIGHT_MODE_BOXID_MAP_INITIALIZER_does_not_match_boxId_e
);
386 if (boxid
== BOXARM
) {
387 return ARMING_FLAG(ARMED
);
388 } else if (boxid
<= BOXID_FLIGHTMODE_LAST
) {
389 return FLIGHT_MODE(1 << boxIdToFlightModeMap
[boxid
]);
391 return IS_RC_MODE_ACTIVE(boxid
);
395 // pack used flightModeFlags into supplied array
396 // returns number of bits used
397 int packFlightModeFlags(boxBitmask_t
*mspFlightModeFlags
)
399 // Serialize the flags in the order we delivered them, ignoring BOXNAMES and BOXINDEXES
400 memset(mspFlightModeFlags
, 0, sizeof(boxBitmask_t
));
401 // map boxId_e enabled bits to MSP status indexes
402 // only active boxIds are sent in status over MSP, other bits are not counted
403 unsigned mspBoxIdx
= 0; // index of active boxId (matches sent permanentId and boxNames)
404 for (boxId_e boxId
= 0; boxId
< CHECKBOX_ITEM_COUNT
; boxId
++) {
405 if (activeBoxIdGet(boxId
)) {
406 if (getBoxIdState(boxId
))
407 bitArraySet(mspFlightModeFlags
, mspBoxIdx
); // box is enabled
408 mspBoxIdx
++; // box is active, count it
411 // return count of used bits